Skip to content

Commit 41bde5a

Browse files
committed
Probe cargo for -Z json-target-spec support instead of guessing from the version
Both PolkaVM build paths compile against a `.json` target spec produced by polkavm-linker and have to decide whether to pass cargo `-Z json-target-spec`: newer cargo requires the flag, older cargo rejects it as unknown. Both decided by comparing a version against 1.95 -- the fixture builder against the *rustc* version, wasm-builder against the cargo version. Neither works. The flag landed partway through the 1.95 cycle, so a nightly from early in that cycle reports 1.95 from both binaries while its cargo does not know the flag yet: rustc 1.95.0-nightly (474276961 2026-01-26) cargo 1.95.0-nightly (efcd9f586 2026-01-23) error: unknown `-Z` flag specified: json-target-spec Ask cargo which unstable flags it accepts instead of inferring it from a version.
1 parent 8574f19 commit 41bde5a

4 files changed

Lines changed: 69 additions & 24 deletions

File tree

prdoc/pr_12683.prdoc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
title: 'Probe cargo for `-Z json-target-spec` support instead of guessing from the version'
2+
3+
doc:
4+
- audience: Runtime Dev
5+
description: |
6+
Both PolkaVM build paths compile against a `.json` target spec produced by
7+
`polkavm-linker` and have to decide whether to pass cargo `-Z json-target-spec`:
8+
newer cargo requires the flag, older cargo rejects it as unknown.
9+
10+
Both decided by comparing a version against 1.95: the fixture builder against the
11+
*rustc* version, `wasm-builder` against the cargo version. Neither works. The flag
12+
landed partway through the 1.95 cycle, so a nightly from early in that cycle reports
13+
1.95 from both binaries while its cargo does not know the flag yet, and the fixture
14+
build fails with `error: unknown -Z flag specified: json-target-spec`.
15+
16+
Ask cargo which unstable flags it accepts instead of inferring it from a version.
17+
18+
crates:
19+
- name: pallet-revive-fixtures
20+
bump: patch
21+
- name: substrate-wasm-builder
22+
bump: patch

substrate/frame/revive/fixtures/src/builder.rs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -198,18 +198,25 @@ pub fn invoke_build(current_dir: &Path) -> Result<()> {
198198
let toolchain =
199199
env::var(OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR).or_else(|_| env::var("RUSTUP_TOOLCHAIN"));
200200

201-
// Detect rustc major.minor for the version-gated flags below.
202-
let (major, minor) = {
203-
let mut cmd = Command::new("rustc");
201+
// Ask a tool of the toolchain we are about to build with what it supports.
202+
let probe = |program: &str, args: &[&str]| -> Result<String> {
203+
let mut cmd = Command::new(program);
204+
// `-Z` flags are nightly-only, so make a stable toolchain answer as a nightly would.
205+
cmd.env("RUSTC_BOOTSTRAP", "1");
204206
if let Ok(toolchain) = &toolchain {
205207
cmd.env("RUSTUP_TOOLCHAIN", toolchain);
206208
}
207-
let out = cmd.arg("--version").output().context("rustc --version failed")?;
208-
let ver = String::from_utf8(out.stdout).context("utf8 from rustc --version failed")?;
209+
let out = cmd.args(args).output().with_context(|| format!("{program} {args:?} failed"))?;
210+
String::from_utf8(out.stdout).with_context(|| format!("{program} output is not utf8"))
211+
};
212+
213+
// Detect the major.minor of a tool for the version-gated flags below.
214+
let version = |program: &str| -> Result<(u32, u32)> {
215+
let ver = probe(program, &["--version"])?;
209216
let ver_num = ver
210217
.split_whitespace()
211218
.nth(1)
212-
.ok_or_else(|| anyhow::anyhow!("unexpected rustc --version output: {ver}"))?;
219+
.ok_or_else(|| anyhow::anyhow!("unexpected {program} --version output: {ver}"))?;
213220
let mut parts = ver_num.split('.');
214221
let major: u32 = parts
215222
.next()
@@ -221,17 +228,24 @@ pub fn invoke_build(current_dir: &Path) -> Result<()> {
221228
.ok_or_else(|| anyhow::anyhow!("missing minor version"))?
222229
.parse()
223230
.context("invalid minor version")?;
224-
(major, minor)
231+
Ok((major, minor))
225232
};
226233

234+
// `rustc` and `cargo` ship together but a flag can land in one before the other, so gate
235+
// each flag on the version of the binary that parses it.
227236
// 1.92+: `-Cpanic=immediate-abort` is a stable rustflag.
228-
let new_immediate_abort = major > 1 || (major == 1 && minor >= 92);
229-
// 1.95+: `-Z json-target-spec` was added; later rustc requires it for any
230-
// `--target=*.json` invocation. Pre-1.95 doesn't know the flag.
231-
let needs_json_target_spec = major > 1 || (major == 1 && minor >= 95);
237+
let rustc_immediate_abort = version("rustc")? >= (1, 92);
238+
// Older toolchains request the same from cargo when it builds `core` instead.
239+
let cargo_immediate_abort = version("cargo")? < (1, 92);
240+
241+
// `-Z json-target-spec` is a *cargo* flag: newer cargo requires it to be opted into for
242+
// any `--target=*.json` invocation, older cargo rejects it as unknown. It landed partway
243+
// through the 1.95 cycle, so a toolchain from early in that cycle reports 1.95 while its
244+
// cargo does not know the flag yet: no version comparison can express this.
245+
let supports_json_target_spec = probe("cargo", &["-Z", "help"])?.contains("json-target-spec");
232246

233247
// on newer version this is a stable compiler flag
234-
let encoded_rustflags = if new_immediate_abort {
248+
let encoded_rustflags = if rustc_immediate_abort {
235249
["-Dwarnings", "-Zunstable-options", "-Cpanic=immediate-abort"].join("\x1f")
236250
} else {
237251
["-Dwarnings"].join("\x1f")
@@ -252,13 +266,13 @@ pub fn invoke_build(current_dir: &Path) -> Result<()> {
252266
.arg("--target")
253267
.arg(polkavm_linker::target_json_path(args).unwrap());
254268

255-
// 1.95+: opt into JSON target specs explicitly (required by recent rustc).
256-
if needs_json_target_spec {
269+
// opt into JSON target specs explicitly where cargo understands the flag
270+
if supports_json_target_spec {
257271
build_command.arg("-Zjson-target-spec");
258272
}
259273

260274
// on older versions this is a unstable cargo cli argument
261-
if !new_immediate_abort {
275+
if cargo_immediate_abort {
262276
build_command.arg("-Zbuild-std-features=panic_immediate_abort");
263277
}
264278

substrate/utils/wasm-builder/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,21 @@ impl CargoCommand {
334334
version.major > 1 || (version.major == 1 && version.minor >= 68) || version.is_nightly
335335
}
336336

337+
/// Returns whether this cargo accepts the `-Z json-target-spec` flag.
338+
///
339+
/// Newer cargo requires the flag to be opted into for any `--target=*.json` invocation,
340+
/// older cargo rejects it as unknown. It landed partway through the 1.95 cycle, so a
341+
/// toolchain from early in that cycle reports 1.95 while its cargo does not know the
342+
/// flag yet: no version comparison can express this, hence the probe.
343+
fn supports_json_target_spec(&self) -> bool {
344+
// `-Z` flags are nightly-only, so make a stable cargo answer as a nightly would.
345+
self.command()
346+
.env("RUSTC_BOOTSTRAP", "1")
347+
.args(["-Z", "help"])
348+
.output()
349+
.is_ok_and(|out| String::from_utf8_lossy(&out.stdout).contains("json-target-spec"))
350+
}
351+
337352
/// Returns whether this version of the toolchain supports the `wasm32v1-none` target.
338353
fn supports_wasm32v1_none_target(&self) -> bool {
339354
self.version.map_or(false, |version| {

substrate/utils/wasm-builder/src/wasm_project.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,15 +1023,9 @@ fn build_bloaty_blob(
10231023
}
10241024

10251025
// `--target` for the Riscv runtime points at a JSON target spec produced by
1026-
// `polkavm-linker`. Rust 1.95 added `-Z json-target-spec`; later versions
1027-
// require it to be opted into explicitly. Older versions don't know the flag,
1028-
// so guard on the version. `RUSTC_BOOTSTRAP=1` is already set by the
1029-
// `-Z build-std` block above (Riscv always opts into `build-std`).
1030-
if matches!(target, RuntimeTarget::Riscv) &&
1031-
cargo_cmd
1032-
.version()
1033-
.is_some_and(|v| v.major > 1 || (v.major == 1 && v.minor >= 95))
1034-
{
1026+
// `polkavm-linker`. Newer cargo requires opting into those explicitly, older cargo
1027+
// does not know the flag at all, so ask this cargo which of the two it is.
1028+
if matches!(target, RuntimeTarget::Riscv) && cargo_cmd.supports_json_target_spec() {
10351029
build_cmd.arg("-Z").arg("json-target-spec");
10361030
}
10371031

0 commit comments

Comments
 (0)