diff --git a/src/lib.rs b/src/lib.rs index cfd56d5e..9a2ef888 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,8 @@ pub struct Check { release_type: Option, current_feature_config: rustdoc_gen::FeatureConfig, baseline_feature_config: rustdoc_gen::FeatureConfig, + /// Which `--target` to use, if unset pass no flag + build_target: Option, } /// The kind of release we're making. @@ -236,6 +238,7 @@ impl Check { release_type: None, current_feature_config: rustdoc_gen::FeatureConfig::default_for_current(), baseline_feature_config: rustdoc_gen::FeatureConfig::default_for_baseline(), + build_target: None, } } @@ -300,6 +303,13 @@ impl Check { self } + /// Set what `--target` to build the documentation with, by default will not pass any flag + /// relying on the users cargo configuration. + pub fn with_build_target(&mut self, build_target: String) -> &mut Self { + self.build_target = Some(build_target); + self + } + /// Some `RustdocSource`s don't contain a path to the project root, /// so they don't have a target directory. We try to deduce the target directory /// on a "best effort" basis -- when the source contains a target dir, @@ -419,6 +429,7 @@ impl Check { crate_type: rustdoc_gen::CrateType::Current, name: &name, feature_config: &self.current_feature_config, + build_target: self.build_target.as_deref(), }, CrateDataForRustdoc { crate_type: rustdoc_gen::CrateType::Baseline { @@ -426,6 +437,7 @@ impl Check { }, name: &name, feature_config: &self.baseline_feature_config, + build_target: self.build_target.as_deref(), }, )?; @@ -485,6 +497,7 @@ impl Check { crate_type: rustdoc_gen::CrateType::Current, name: crate_name, feature_config: &self.current_feature_config, + build_target: self.build_target.as_deref(), }, CrateDataForRustdoc { crate_type: rustdoc_gen::CrateType::Baseline { @@ -492,6 +505,7 @@ impl Check { }, name: crate_name, feature_config: &self.baseline_feature_config, + build_target: self.build_target.as_deref(), }, )?; diff --git a/src/main.rs b/src/main.rs index 64cdad10..bc175c9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -257,6 +257,11 @@ struct CheckRelease { )] all_features: bool, + /// Which target to build the crate for, to check platform-specific APIs, e.g. + /// `x86_64-unknown-linux-gnu`. + #[arg(long = "target")] + build_target: Option, + #[command(flatten)] verbosity: clap_verbosity_flag::Verbosity, } @@ -349,6 +354,11 @@ impl From for cargo_semver_checks::Check { trim_features(&mut baseline_features); check.with_extra_features(current_features, baseline_features); + + if let Some(build_target) = value.build_target { + check.with_build_target(build_target); + } + check } } diff --git a/src/rustdoc_cmd.rs b/src/rustdoc_cmd.rs index f7ec4998..60a88990 100644 --- a/src/rustdoc_cmd.rs +++ b/src/rustdoc_cmd.rs @@ -107,6 +107,9 @@ impl RustdocCommand { .arg(target_dir) .arg("--package") .arg(pkg_spec); + if let Some(build_target) = crate_data.build_target { + cmd.arg("--target").arg(build_target); + } if !self.deps { cmd.arg("--no-deps"); } @@ -130,6 +133,48 @@ impl RustdocCommand { } } + let rustdoc_dir = if let Some(build_target) = crate_data.build_target { + target_dir.join(build_target).join("doc") + } else { + // If not passing an explicit `--target` flag, cargo may still pick a target to use + // instead of the "host" target, based on its config files and environment variables. + + let build_target = { + let output = std::process::Command::new("cargo") + .env("RUSTC_BOOTSTRAP", "1") + .args([ + "config", + "-Zunstable-options", + "--color=never", + "get", + "--format=json-value", + "build.target", + ]) + .output()?; + if output.status.success() { + serde_json::from_slice::>(&output.stdout)? + } else if std::str::from_utf8(&output.stderr) + .context("non-utf8 cargo output")? + // this is the only way to detect a not set config value currently: + // https://github.com/rust-lang/cargo/issues/13223 + .contains("config value `build.target` is not set") + { + None + } else { + anyhow::bail!( + "running cargo-config failed:\n{}", + String::from_utf8_lossy(&output.stderr), + ) + } + }; + + if let Some(build_target) = build_target { + target_dir.join(build_target).join("doc") + } else { + target_dir.join("doc") + } + }; + // There's no great way to figure out whether that crate version has a lib target. // We can't easily do it via the index, and we can't reliably do it via metadata. // We're reduced to this heuristic: @@ -175,7 +220,7 @@ in the metadata and stderr didn't mention it was lacking a lib target. This is p let lib_name = lib_target.name.as_str(); let rustdoc_json_file_name = lib_name.replace('-', "_"); - let json_path = target_dir.join(format!("doc/{rustdoc_json_file_name}.json")); + let json_path = rustdoc_dir.join(format!("{rustdoc_json_file_name}.json")); if json_path.exists() { return Ok(json_path); } else { @@ -195,7 +240,7 @@ in the metadata and stderr didn't mention it was lacking a lib target. This is p let bin_name = bin_target.name.as_str(); let rustdoc_json_file_name = bin_name.replace('-', "_"); - let json_path = target_dir.join(format!("doc/{rustdoc_json_file_name}.json")); + let json_path = rustdoc_dir.join(format!("{rustdoc_json_file_name}.json")); if json_path.exists() { return Ok(json_path); } else { diff --git a/src/rustdoc_gen.rs b/src/rustdoc_gen.rs index be404a62..866b74d9 100644 --- a/src/rustdoc_gen.rs +++ b/src/rustdoc_gen.rs @@ -300,6 +300,7 @@ pub(crate) struct CrateDataForRustdoc<'a> { pub(crate) crate_type: CrateType<'a>, pub(crate) name: &'a str, pub(crate) feature_config: &'a FeatureConfig, + pub(crate) build_target: Option<&'a str>, } impl<'a> CrateType<'a> { diff --git a/tests/specified_target.rs b/tests/specified_target.rs new file mode 100644 index 00000000..c12241b3 --- /dev/null +++ b/tests/specified_target.rs @@ -0,0 +1,34 @@ +use assert_cmd::Command; + +fn base() -> Command { + let mut cmd = Command::cargo_bin("cargo-semver-checks").unwrap(); + cmd.args([ + "semver-checks", + "check-release", + "--manifest-path=test_crates/template/new", + "--baseline-root=test_crates/template/old", + ]); + cmd +} + +#[test] +fn with_default() { + base().env_remove("CARGO_BUILD_TARGET").assert().success(); +} + +#[test] +fn with_env_var() { + base() + .env("CARGO_BUILD_TARGET", "x86_64-unknown-linux-gnu") + .assert() + .success(); +} + +#[test] +fn with_flag() { + base() + .env_remove("CARGO_BUILD_TARGET") + .arg("--target=x86_64-unknown-linux-gnu") + .assert() + .success(); +}