diff --git a/src/cargo/core/compiler/context/mod.rs b/src/cargo/core/compiler/context/mod.rs index 8447895317c..40ab6aaf38e 100644 --- a/src/cargo/core/compiler/context/mod.rs +++ b/src/cargo/core/compiler/context/mod.rs @@ -235,6 +235,14 @@ impl<'a, 'cfg> Context<'a, 'cfg> { args.push(cfg.into()); } + if !output.check_cfgs.is_empty() { + args.push("-Zunstable-options".into()); + for check_cfg in &output.check_cfgs { + args.push("--check-cfg".into()); + args.push(check_cfg.into()); + } + } + for (lt, arg) in &output.linker_args { if lt.applies_to(&unit.target) { args.push("-C".into()); diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 2ebec054791..b3929b8df70 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -29,6 +29,8 @@ pub struct BuildOutput { pub linker_args: Vec<(LinkType, String)>, /// Various `--cfg` flags to pass to the compiler. pub cfgs: Vec, + /// Various `--check-cfg` flags to pass to the compiler. + pub check_cfgs: Vec, /// Additional environment variables to run the compiler with. pub env: Vec<(String, String)>, /// Metadata to pass to the immediate dependencies. @@ -322,6 +324,10 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult { paths::create_dir_all(&script_out_dir)?; let nightly_features_allowed = cx.bcx.config.nightly_features_allowed; + let extra_check_cfg = match cx.bcx.config.cli_unstable().check_cfg { + Some((_, _, _, output)) => output, + None => false, + }; let targets: Vec = unit.pkg.targets().to_vec(); // Need a separate copy for the fresh closure. let targets_fresh = targets.clone(); @@ -432,6 +438,7 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult { &pkg_descr, &script_out_dir, &script_out_dir, + extra_check_cfg, nightly_features_allowed, &targets, )?; @@ -459,6 +466,7 @@ fn build_work(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult { &pkg_descr, &prev_script_out_dir, &script_out_dir, + extra_check_cfg, nightly_features_allowed, &targets_fresh, )?, @@ -511,6 +519,7 @@ impl BuildOutput { pkg_descr: &str, script_out_dir_when_generated: &Path, script_out_dir: &Path, + extra_check_cfg: bool, nightly_features_allowed: bool, targets: &[Target], ) -> CargoResult { @@ -521,6 +530,7 @@ impl BuildOutput { pkg_descr, script_out_dir_when_generated, script_out_dir, + extra_check_cfg, nightly_features_allowed, targets, ) @@ -536,6 +546,7 @@ impl BuildOutput { pkg_descr: &str, script_out_dir_when_generated: &Path, script_out_dir: &Path, + extra_check_cfg: bool, nightly_features_allowed: bool, targets: &[Target], ) -> CargoResult { @@ -543,6 +554,7 @@ impl BuildOutput { let mut library_links = Vec::new(); let mut linker_args = Vec::new(); let mut cfgs = Vec::new(); + let mut check_cfgs = Vec::new(); let mut env = Vec::new(); let mut metadata = Vec::new(); let mut rerun_if_changed = Vec::new(); @@ -669,6 +681,13 @@ impl BuildOutput { linker_args.push((LinkType::All, value)); } "rustc-cfg" => cfgs.push(value.to_string()), + "rustc-check-cfg" => { + if extra_check_cfg { + check_cfgs.push(value.to_string()); + } else { + warnings.push(format!("cargo:{} requires -Zcheck-cfg=output flag", key)); + } + } "rustc-env" => { let (key, val) = BuildOutput::parse_rustc_env(&value, &whence)?; // Build scripts aren't allowed to set RUSTC_BOOTSTRAP. @@ -728,6 +747,7 @@ impl BuildOutput { library_links, linker_args, cfgs, + check_cfgs, env, metadata, rerun_if_changed, @@ -968,6 +988,10 @@ fn prev_build_output(cx: &mut Context<'_, '_>, unit: &Unit) -> (Option output, + None => false, + }, cx.bcx.config.nightly_features_allowed, unit.pkg.targets(), ) diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 553778206db..622e8733d40 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -292,7 +292,7 @@ fn rustc(cx: &mut Context<'_, '_>, unit: &Unit, exec: &Arc) -> Car )?; add_plugin_deps(&mut rustc, &script_outputs, &build_scripts, &root_output)?; } - add_custom_env(&mut rustc, &script_outputs, script_metadata); + add_custom_flags(&mut rustc, &script_outputs, script_metadata)?; } for output in outputs.iter() { @@ -408,9 +408,6 @@ fn rustc(cx: &mut Context<'_, '_>, unit: &Unit, exec: &Arc) -> Car } if key.0 == current_id { - for cfg in &output.cfgs { - rustc.arg("--cfg").arg(cfg); - } if pass_l_flag { for name in output.library_links.iter() { rustc.arg("-l").arg(name); @@ -431,22 +428,6 @@ fn rustc(cx: &mut Context<'_, '_>, unit: &Unit, exec: &Arc) -> Car } Ok(()) } - - // Add all custom environment variables present in `state` (after they've - // been put there by one of the `build_scripts`) to the command provided. - fn add_custom_env( - rustc: &mut ProcessBuilder, - build_script_outputs: &BuildScriptOutputs, - metadata: Option, - ) { - if let Some(metadata) = metadata { - if let Some(output) = build_script_outputs.get(metadata) { - for &(ref name, ref value) in output.env.iter() { - rustc.env(name, value); - } - } - } - } } /// Link the compiled target (often of form `foo-{metadata_hash}`) to the @@ -713,16 +694,11 @@ fn rustdoc(cx: &mut Context<'_, '_>, unit: &Unit) -> CargoResult { let mut output_options = OutputOptions::new(cx, unit); let script_metadata = cx.find_build_script_metadata(unit); Ok(Work::new(move |state| { - if let Some(script_metadata) = script_metadata { - if let Some(output) = build_script_outputs.lock().unwrap().get(script_metadata) { - for cfg in output.cfgs.iter() { - rustdoc.arg("--cfg").arg(cfg); - } - for &(ref name, ref value) in output.env.iter() { - rustdoc.env(name, value); - } - } - } + add_custom_flags( + &mut rustdoc, + &build_script_outputs.lock().unwrap(), + script_metadata, + )?; let crate_dir = doc_dir.join(&crate_name); if crate_dir.exists() { // Remove output from a previous build. This ensures that stale @@ -1055,7 +1031,7 @@ fn features_args(unit: &Unit) -> Vec { /// Generate the --check-cfg arguments for the unit fn check_cfg_args(cx: &Context<'_, '_>, unit: &Unit) -> Vec { - if let Some((features, well_known_names, well_known_values)) = + if let Some((features, well_known_names, well_known_values, _output)) = cx.bcx.config.cli_unstable().check_cfg { let mut args = Vec::with_capacity(unit.pkg.summary().features().len() * 2 + 4); @@ -1184,6 +1160,32 @@ fn build_deps_args( Ok(()) } +/// Add custom flags from the output a of build-script to a `ProcessBuilder` +fn add_custom_flags( + cmd: &mut ProcessBuilder, + build_script_outputs: &BuildScriptOutputs, + metadata: Option, +) -> CargoResult<()> { + if let Some(metadata) = metadata { + if let Some(output) = build_script_outputs.get(metadata) { + for cfg in output.cfgs.iter() { + cmd.arg("--cfg").arg(cfg); + } + if !output.check_cfgs.is_empty() { + cmd.arg("-Zunstable-options"); + for check_cfg in &output.check_cfgs { + cmd.arg("--check-cfg").arg(check_cfg); + } + } + for &(ref name, ref value) in output.env.iter() { + cmd.env(name, value); + } + } + } + + Ok(()) +} + /// Generates a list of `--extern` arguments. pub fn extern_args( cx: &Context<'_, '_>, diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 99d72d099e6..b8f52b4dc65 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -641,7 +641,7 @@ unstable_cli_options!( build_std_features: Option> = ("Configure features enabled for the standard library itself when building the standard library"), config_include: bool = ("Enable the `include` key in config files"), credential_process: bool = ("Add a config setting to fetch registry authentication tokens by calling an external process"), - check_cfg: Option<(/*features:*/ bool, /*well_known_names:*/ bool, /*well_known_values:*/ bool)> = ("Specify scope of compile-time checking of `cfg` names/values"), + check_cfg: Option<(/*features:*/ bool, /*well_known_names:*/ bool, /*well_known_values:*/ bool, /*output:*/ bool)> = ("Specify scope of compile-time checking of `cfg` names/values"), doctest_in_workspace: bool = ("Compile doctests with paths relative to the workspace root"), doctest_xcompile: bool = ("Compile and run doctests for non-host target using runner config"), dual_proc_macros: bool = ("Build proc-macros for both the host and the target"), @@ -783,22 +783,29 @@ impl CliUnstable { } } - fn parse_check_cfg(value: Option<&str>) -> CargoResult> { + fn parse_check_cfg(value: Option<&str>) -> CargoResult> { if let Some(value) = value { let mut features = false; let mut well_known_names = false; let mut well_known_values = false; + let mut output = false; for e in value.split(',') { match e { "features" => features = true, "names" => well_known_names = true, "values" => well_known_values = true, - _ => bail!("flag -Zcheck-cfg only takes `features`, `names` or `values` as valid inputs"), + "output" => output = true, + _ => bail!("flag -Zcheck-cfg only takes `features`, `names`, `values` or `output` as valid inputs"), } } - Ok(Some((features, well_known_names, well_known_values))) + Ok(Some(( + features, + well_known_names, + well_known_values, + output, + ))) } else { Ok(None) } diff --git a/src/cargo/util/config/target.rs b/src/cargo/util/config/target.rs index 21089ce491b..8190529a686 100644 --- a/src/cargo/util/config/target.rs +++ b/src/cargo/util/config/target.rs @@ -120,7 +120,7 @@ fn load_config_table(config: &Config, prefix: &str) -> CargoResult // Links do not support environment variables. let target_key = ConfigKey::from_str(prefix); let links_overrides = match config.get_table(&target_key)? { - Some(links) => parse_links_overrides(&target_key, links.val)?, + Some(links) => parse_links_overrides(&target_key, links.val, config)?, None => BTreeMap::new(), }; Ok(TargetConfig { @@ -134,8 +134,14 @@ fn load_config_table(config: &Config, prefix: &str) -> CargoResult fn parse_links_overrides( target_key: &ConfigKey, links: HashMap, + config: &Config, ) -> CargoResult> { let mut links_overrides = BTreeMap::new(); + let extra_check_cfg = match config.cli_unstable().check_cfg { + Some((_, _, _, output)) => output, + None => false, + }; + for (lib_name, value) in links { // Skip these keys, it shares the namespace with `TargetConfig`. match lib_name.as_str() { @@ -200,6 +206,17 @@ fn parse_links_overrides( let list = value.list(key)?; output.cfgs.extend(list.iter().map(|v| v.0.clone())); } + "rustc-check-cfg" => { + if extra_check_cfg { + let list = value.list(key)?; + output.check_cfgs.extend(list.iter().map(|v| v.0.clone())); + } else { + config.shell().warn(format!( + "target config `{}.{}` requires -Zcheck-cfg=output flag", + target_key, key + ))?; + } + } "rustc-env" => { for (name, val) in value.table(key)?.0 { let val = val.string(name)?.0; diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index b3badd43c19..5ce187ee3bc 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1201,6 +1201,7 @@ It's values are: Note than this command line options will probably become the default when stabilizing. - `names`: enables well known names checking via `--check-cfg=names()`. - `values`: enables well known values checking via `--check-cfg=values()`. + - `output`: enable the use of `rustc-check-cfg` in build script. For instance: @@ -1211,6 +1212,29 @@ cargo check -Z unstable-options -Z check-cfg=values cargo check -Z unstable-options -Z check-cfg=features,names,values ``` +Or for `output`: + +```rust,no_run +// build.rs +println!("cargo:rustc-check-cfg=names(foo, bar)"); +``` + +``` +cargo check -Z unstable-options -Z check-cfg=output +``` + +### `cargo:rustc-check-cfg=CHECK_CFG` + +The `rustc-check-cfg` instruction tells Cargo to pass the given value to the +`--check-cfg` flag to the compiler. This may be used for compile-time +detection of unexpected conditional compilation name and/or values. + +This can only be used in combination with `-Zcheck-cfg=output` otherwise it is ignored +with a warning. + +If you want to integrate with Cargo features, use `-Zcheck-cfg=features` instead of +trying to do it manually with this option. + ### workspace-inheritance * RFC: [#2906](https://github.com/rust-lang/rfcs/blob/master/text/2906-cargo-workspace-deduplicate.md) diff --git a/tests/testsuite/check_cfg.rs b/tests/testsuite/check_cfg.rs index 2ce3ca4f154..ab7d4f9a2e0 100644 --- a/tests/testsuite/check_cfg.rs +++ b/tests/testsuite/check_cfg.rs @@ -458,3 +458,174 @@ fn features_doc() { .with_stderr_contains(x!("rustdoc" => "values" of "feature" with "default" "f_a" "f_b")) .run(); } + +#[cargo_test] +fn build_script_feedback() { + if !is_nightly() { + // rustc-check-cfg: is only availaible on nightly + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + build = "build.rs" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "build.rs", + r#"fn main() { println!("cargo:rustc-check-cfg=names(foo)"); }"#, + ) + .build(); + + p.cargo("build -v -Zcheck-cfg=output") + .masquerade_as_nightly_cargo() + .with_stderr_contains(x!("rustc" => "names" of "foo")) + .run(); +} + +#[cargo_test] +fn build_script_doc() { + if !is_nightly() { + // rustc-check-cfg: is only availaible on nightly + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + build = "build.rs" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "build.rs", + r#"fn main() { println!("cargo:rustc-check-cfg=names(foo)"); }"#, + ) + .build(); + p.cargo("doc -v -Zcheck-cfg=output") + .with_stderr_does_not_contain("rustc [..] --check-cfg [..]") + .with_stderr_contains(x!("rustdoc" => "names" of "foo")) + .with_stderr( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..] build.rs [..]` +[RUNNING] `[..]/build-script-build` +[DOCUMENTING] foo [..] +[RUNNING] `rustdoc [..] src/main.rs [..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]", + ) + .masquerade_as_nightly_cargo() + .run(); +} + +#[cargo_test] +fn build_script_override() { + if !is_nightly() { + // rustc-check-cfg: is only availaible on nightly + return; + } + let target = cargo_test_support::rustc_host(); + + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + links = "a" + build = "build.rs" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("build.rs", "") + .file( + ".cargo/config", + &format!( + r#" + [target.{}.a] + rustc-check-cfg = ["names(foo)"] + "#, + target + ), + ) + .build(); + + p.cargo("build -v -Zcheck-cfg=output") + .with_stderr_contains(x!("rustc" => "names" of "foo")) + .masquerade_as_nightly_cargo() + .run(); +} + +#[cargo_test] +fn build_script_test() { + if !is_nightly() { + // rustc-check-cfg: is only availaible on nightly + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + build = "build.rs" + "#, + ) + .file( + "build.rs", + r#"fn main() { + println!("cargo:rustc-check-cfg=names(foo)"); + println!("cargo:rustc-cfg=foo"); + }"#, + ) + .file( + "src/lib.rs", + r#" + /// + /// ``` + /// extern crate foo; + /// + /// fn main() { + /// foo::foo() + /// } + /// ``` + /// + #[cfg(foo)] + pub fn foo() {} + + #[cfg(foo)] + #[test] + fn test_foo() { + foo() + } + "#, + ) + .file("tests/test.rs", "#[cfg(foo)] #[test] fn test_bar() {}") + .build(); + + p.cargo("test -v -Zcheck-cfg=output") + .with_stderr_contains(x!("rustc" => "names" of "foo")) + .with_stderr_contains(x!("rustdoc" => "names" of "foo")) + .with_stdout_contains("test test_foo ... ok") + .with_stdout_contains("test test_bar ... ok") + .with_stdout_contains_n("test [..] ... ok", 3) + .masquerade_as_nightly_cargo() + .run(); +}