Repository: rust-lang/cargo (rust)

Issue:
feature to env var mapping is not unique
Build scripts receive information about activated features as environment variables `CARGO_FEATURE_$name`, where `$name` is the feature name uppercased and with dashes converted to underscores.

However, feature names are allowed to contain underscores, so this transformation can create collisions. Example:

- Cargo.toml
  ```toml
  [package]
  name = "tester"
  version = "0.1.0"
  
  build = "build.rs"
  
  [features]
  foo-bar = []
  foo_bar = []
  ```
- build.rs
  ```rust
  use std::env;
  
  fn main() {
      for (k, v) in env::vars() {
          if k.starts_with("CARGO_FEATURE") {
              println!("{} => {}", k, v);
          }
      }
      panic!();
  }
  ```
- output
  ```
  --- stdout
  CARGO_FEATURE_FOO_BAR => 1
  
  --- stderr
  thread 'main' panicked at 'explicit panic', build.rs:9
  note: Run with `RUST_BACKTRACE=1` for a backtrace.
  ```

Pull request:
feat(build-script): Pass CARGO_CFG_FEATURE 
### What does this PR try to resolve?

This may look redundant with `CARGO_FEATURE_<CASE_CONVERTED_NAME>=1`
except that doesn't provide a lossless way of getting the names, e.g. for
forwarding to child builds like tests that need to build examples. which clap does to [test examples](https://github.com/clap-rs/clap/blob/master/tests/examples.rs). Maintaining that manually is easy to mess up.

This also makes things more consistent as users
conditionalize on features through `cfg` and this even fits with what
the `CARGO_CFG_` docs say:

> For each configuration option of the package being built, this
> environment variable will contain the value of the configuration, where
> <cfg> is the name of the configuration uppercased and having -
> translated to _. Boolean configurations are present if they are set, and
> not present otherwise. Configurations with multiple values are joined to
> a single variable with the values delimited by , (comma). This includes values
> built-in to the compiler (which can be seen with rustc --print=cfg) and
> values set by build scripts and extra flags passed to rustc (such as
> those defined in RUSTFLAGS). Some examples of what these variables are: 

Fixes #3702

### How should we test and review this PR?



### Additional information



Patch:
diff --git a/crates/build-rs/src/input.rs b/crates/build-rs/src/input.rs
index 112aa5fed..6cfdd3268 100644
--- a/crates/build-rs/src/input.rs
+++ b/crates/build-rs/src/input.rs
@@ -102,6 +102,13 @@ mod cfg {
     // those disabled with #[cfg(any())] don't seem meaningfully useful
     // but we list all cfg that are default known to check-cfg
 
+    /// Each activated feature of the package being built
+    #[doc = requires_msrv!("1.85")]
+    #[track_caller]
+    pub fn cargo_cfg_feature() -> Vec<String> {
+        to_strings(var_or_panic(&cargo_cfg_var("target_feature")), ',')
+    }
+
     #[cfg(any())]
     #[track_caller]
     pub fn cargo_cfg_clippy() -> bool {
diff --git a/crates/build-rs/src/lib.rs b/crates/build-rs/src/lib.rs
index bc511f6f6..6221e44d3 100644
--- a/crates/build-rs/src/lib.rs
+++ b/crates/build-rs/src/lib.rs
@@ -40,6 +40,20 @@ MSRV: Respected as of "#,
     };
 }
 
+macro_rules! requires_msrv {
+    ($ver:literal) => {
+        concat!(
+            r#"<div class="warning">
+
+MSRV: Requires "#,
+            $ver,
+            r#".
+
+</div>"#
+        )
+    };
+}
+
 mod ident;
 
 pub mod input;
diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs
index a9928ab58..9f7287baa 100644
--- a/src/cargo/core/compiler/custom_build.rs
+++ b/src/cargo/core/compiler/custom_build.rs
@@ -336,6 +336,10 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
     }
 
     let mut cfg_map = HashMap::new();
+    cfg_map.insert(
+        "feature",
+        unit.features.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
+    );
     for cfg in bcx.target_data.cfg(unit.kind) {
         match *cfg {
             Cfg::Name(ref n) => {
diff --git a/src/doc/src/reference/environment-variables.md b/src/doc/src/reference/environment-variables.md
index c47c5d178..0e1dcd343 100644
--- a/src/doc/src/reference/environment-variables.md
+++ b/src/doc/src/reference/environment-variables.md
@@ -342,6 +342,7 @@ let out_dir = env::var("OUT_DIR").unwrap();
   values built-in to the compiler (which can be seen with `rustc --print=cfg`)
   and values set by build scripts and extra flags passed to `rustc` (such as
   those defined in `RUSTFLAGS`). Some examples of what these variables are:
+    * `CARGO_CFG_FEATURE` --- Each activated feature of the package being built.
     * `CARGO_CFG_UNIX` --- Set on [unix-like platforms].
     * `CARGO_CFG_WINDOWS` --- Set on [windows-like platforms].
     * `CARGO_CFG_TARGET_FAMILY=unix,wasm` --- The [target family].
@@ -356,8 +357,7 @@ let out_dir = env::var("OUT_DIR").unwrap();
   > Note that different [target triples][Target Triple] have different sets of `cfg` values,
   > hence variables present in one target triple might not be available in the other.
   >
-  > Some cfg values like `debug_assertions`, `test`, and Cargo features like
-  > `feature="foo"` are not available.
+  > Some cfg values like `debug_assertions` and `test` are not available.
 * `OUT_DIR` --- the folder in which all output and intermediate artifacts should
   be placed. This folder is inside the build directory for the package being built,
   and it is unique for the package in question.


Test patch:
diff --git a/crates/build-rs-test-lib/build.rs b/crates/build-rs-test-lib/build.rs
index eff04806a..0592a3749 100644
--- a/crates/build-rs-test-lib/build.rs
+++ b/crates/build-rs-test-lib/build.rs
@@ -9,6 +9,7 @@ fn main() {
 fn smoke_test_inputs() {
     use build_rs::input::*;
     dbg!(cargo());
+    dbg!(cargo_cfg_feature());
     dbg!(cargo_cfg("careful"));
     #[cfg(feature = "unstable")]
     dbg!(cargo_cfg_fmt_debug());
diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs
index 5725c728e..78af30351 100644
--- a/tests/testsuite/build_script.rs
+++ b/tests/testsuite/build_script.rs
@@ -156,7 +156,7 @@ fn custom_build_env_vars() {
                 authors = ["wycats@example.com"]
 
                 [features]
-                bar_feat = ["bar/foo"]
+                bar_feat = ["bar/foo", "bar/other-feature"]
 
                 [dependencies.bar]
                 path = "bar"
@@ -176,6 +176,7 @@ fn custom_build_env_vars() {
 
                 [features]
                 foo = []
+                other-feature = []
             "#,
         )
         .file("bar/src/lib.rs", "pub fn hello() {}");
@@ -213,6 +214,8 @@ fn custom_build_env_vars() {
                 let _host = env::var("HOST").unwrap();
 
                 let _feat = env::var("CARGO_FEATURE_FOO").unwrap();
+                let feat = env::var("CARGO_CFG_FEATURE").unwrap();
+                assert_eq!(feat, "foo,other-feature");
 
                 let cargo = env::var("CARGO").unwrap();
                 if env::var_os("CHECK_CARGO_IS_RUSTC").is_some() {{