Skip to content

Unquoted [target.<triple>] config is ignored for triples that contain a dot #17529

Closed

Description

@driveraid

Problem

Minimized Claude report:

Since nightly-2026-09-24, Cargo ignores [target.<triple>] config when the triple contains a dot and the key is written without quotes. The environment variable form is also ignored. Examples of such triples are thumbv8m.main-none-eabihf and thumbv8m.main-none-eabi.

All keys in the table are affected. I confirmed rustflags and runner. Cargo builds without the flags and runs the binary directly instead of through the runner.

The quoted key [target."thumbv8m.main-none-eabihf"] now works. It did not work before, see #15901. Triples without a dot, such as thumbv7em-none-eabihf, are not affected.

Unquoted headers for these triples are common in embedded projects. Before this change they were the only form that worked.

Version

  • Last good: cargo 1.100.0-nightly (495c385d0 2026-09-16), rustc 6bb1652a0 (nightly-2026-09-23).
  • First bad: cargo 1.100.0-nightly (98a09e7e7 2026-09-21), rustc 6eeff9a52 (nightly-2026-09-24).

Steps

rustup toolchain install nightly-2026-09-24 --profile minimal -t thumbv8m.main-none-eabihf
cargo new --lib repro && cd repro
echo '#![no_std]' > src/lib.rs
mkdir .cargo
cat > .cargo/config.toml <<'EOF'
[target.thumbv8m.main-none-eabihf]
rustflags = ["-Cbogus"]
EOF
cargo +nightly-2026-09-24 check --target thumbv8m.main-none-eabihf

Expected: error: unknown codegen option: `bogus` .

Actual: the build succeeds. The flag is not passed.

Results for -Cbogus in each form:

Config form nightly-2026-09-23 nightly-2026-09-24
[target.thumbv8m.main-none-eabihf] applied ignored
[target."thumbv8m.main-none-eabihf"] ignored applied
CARGO_TARGET_THUMBV8M_MAIN_NONE_EABIHF_RUSTFLAGS applied ignored

Possible cause

#17490 "refactor(context): Split config keys in code, not at runtime" is in the range. It changes the lookup from format!("target.{}.{}", target, key), which was split on dots at runtime, to ["target", target, key]. See the rustflags lookup and the target table lookup. TOML parses the unquoted header [target.thumbv8m.main-none-eabihf] as the nested tables target.thumbv8m."main-none-eabihf". The old runtime split matched that nesting. The new lookup does not. The environment variable form breaks in the same lookup, because the env name is now built from the single part thumbv8m.main-none-eabihf and keeps the dot.

Possible Solution(s)

Use the quoted key when it is set in config. Otherwise use the nested form, split on dots. rustflags_from_target reads the flags from the same TargetConfig, so both lookups agree. ConfigKey::push maps . to _ in the env name, so the environment variable works with either form.

--- a/src/compiler/build_context/target_info.rs
+++ b/src/compiler/build_context/target_info.rs
@@ -14,3 +14,3 @@ use crate::compiler::CrateType;
 use crate::compiler::apply_env_config;
-use crate::context::{GlobalContext, StringList, TargetConfig};
+use crate::context::{GlobalContext, TargetConfig};
 use crate::util::interning::InternedString;
@@ -763,9 +763,2 @@ enum Flags {
 impl Flags {
-    fn as_key(self) -> &'static str {
-        match self {
-            Flags::Rust => "rustflags",
-            Flags::Rustdoc => "rustdocflags",
-        }
-    }
-
     fn as_env(self) -> &'static str {
@@ -874,4 +867,9 @@ fn rustflags_from_target(
     };
-    if let Some(args) = gctx.get::<Option<StringList>>(["target", target, flag.as_key()])? {
-        rustflags.extend(args.as_slice().iter().cloned());
+    let target_config = gctx.target_cfg_triple(target)?;
+    let args = match flag {
+        Flags::Rust => target_config.rustflags,
+        Flags::Rustdoc => target_config.rustdocflags,
+    };
+    if let Some(args) = args {
+        rustflags.extend(args.val.as_slice().iter().cloned());
     }
--- a/src/context/key.rs
+++ b/src/context/key.rs
@@ -67,3 +67,3 @@ impl ConfigKey {
     pub fn push(&mut self, name: &str) {
-        let env = name.replace("-", "_").to_uppercase();
+        let env = name.replace(['-', '.'], "_").to_uppercase();
         self._push(&env, name);
--- a/src/context/target.rs
+++ b/src/context/target.rs
@@ -115,3 +115,10 @@ pub(super) fn load_host_triple(gctx: &GlobalContext, triple: &str) -> CargoResul
 pub(super) fn load_target_triple(gctx: &GlobalContext, triple: &str) -> CargoResult<TargetConfig> {
-    load_config_table(gctx, &["target", triple])
+    // TOML parses the unquoted `[target.thumbv8m.main-none-eabihf]` as nested
+    // tables. Use the quoted key when it is set, else the nested form.
+    let quoted = ["target", triple];
+    if gctx.get_cv(&ConfigKey::from_parts(quoted))?.is_some() {
+        return load_config_table(gctx, &quoted);
+    }
+    let nested: Vec<&str> = std::iter::once("target").chain(triple.split('.')).collect();
+    load_config_table(gctx, &nested)
 }

I tested this on top of 98a09e7e7. All three forms in the table above apply -Cbogus. The environment variable also applies when a quoted table for the triple exists. An unquoted runner is used again. cargo test --test testsuite -- config:: rustflags:: passes. A regression test for the unquoted header and the environment variable is not included.

Notes

No response

Version


Activity

  1. added
    C-bugCategory: bug
    S-triageStatus: This issue is waiting on initial triage.
    on Sep 28, 2026
  2. weihanglo commented on Sep 28, 2026

    @weihanglo
    Member

    I am surprised that [target.thumbv8m.main-none-eabihf] worked, and it was actually mentioned as a workaround in #15901 (comment).

  3. epage commented on Sep 29, 2026

    @epage
    Contributor

    It appears that the bug that caused target."thumbv8m.main-none-eabihf" to not work is the bug that caused target.thumbv8m.main-none-eabihf to work: we looked up the the target by dotted key and weren't properly escaping the ., so we looked an extra level deeper inside the config

    cargo/src/context/target.rs

    Lines 114 to 117 in 523e530

    /// Loads a single `[target]` table for the given tuple.
    pub(super) fn load_target_triple(gctx: &GlobalContext, triple: &str) -> CargoResult<TargetConfig> {
    load_config_table(gctx, &["target", triple])
    }

    At #15901 (comment), I commented

    Note that that workaround is relying on what I would consider a bug and would not be guaranteed to work going forward.

    #15901 (comment) noted that these keys/targets shouldn't exist and that #17490 is incomplete

    It's probably feasible to fix this specific case by allowing get to take something like ConfigKey, and to construct those without using formatted strings. There's a comment about this here. However, there are probably other edge cases (like env vars, cargo config, etc.) that will have problems.

  4. epage commented on Sep 29, 2026

    @epage
    Contributor

    I'm assuming this could be worked around by specifying both keys

  5. weihanglo commented on Sep 29, 2026

    @weihanglo
    Member

    To me, I probably care less about a unintended unquoted TOML but more the env case. The latter should either continue working, or we provide some alternative (-Zadvanced-env?). However, I wonder if we can give any future-incompat warning for the unquoted TOML case at least

  6. epage commented on Sep 29, 2026

    @epage
    Contributor

    For env, I wonder if we could just add another replace in

    let env = name.replace("-", "_").to_uppercase();

    . shouldn't be in existing keys, so it shouldn't cause any issues to replace it with _.

  7. added a commit that references this issue on Sep 29, 2026
    3f31400
  8. added a commit that references this issue on Sep 29, 2026
    f3865b2
  9. added a commit that references this issue on Sep 29, 2026
    96610ee
  10. epage commented on Sep 29, 2026

    @epage
    Contributor

    #17537 backports to Rust 1.100.0 (beta)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-configurationArea: cargo config files and env varsC-bugCategory: bugS-triageStatus: This issue is waiting on initial triage.regression-from-stable-to-betaRegression in beta that previously worked in stable.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions