Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions prdoc/pr_12726.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
title: 'Probe cargo for `-Z json-target-spec` support instead of guessing from the version'

doc:
- audience: Runtime Dev
description: |
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, and the fixture
build fails with `error: unknown -Z flag specified: json-target-spec`.

Ask cargo which unstable flags it accepts instead of inferring it from a version.

crates:
- name: pallet-revive-fixtures
bump: patch
- name: substrate-wasm-builder
bump: patch
44 changes: 29 additions & 15 deletions substrate/frame/revive/fixtures/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,25 @@ pub fn invoke_build(current_dir: &Path) -> Result<()> {
let toolchain =
env::var(OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR).or_else(|_| env::var("RUSTUP_TOOLCHAIN"));

// Detect rustc major.minor for the version-gated flags below.
let (major, minor) = {
let mut cmd = Command::new("rustc");
// Ask a tool of the toolchain we are about to build with what it supports.
let probe = |program: &str, args: &[&str]| -> Result<String> {
let mut cmd = Command::new(program);
// `-Z` flags are nightly-only, so make a stable toolchain answer as a nightly would.
cmd.env("RUSTC_BOOTSTRAP", "1");
if let Ok(toolchain) = &toolchain {
cmd.env("RUSTUP_TOOLCHAIN", toolchain);
}
let out = cmd.arg("--version").output().context("rustc --version failed")?;
let ver = String::from_utf8(out.stdout).context("utf8 from rustc --version failed")?;
let out = cmd.args(args).output().with_context(|| format!("{program} {args:?} failed"))?;
String::from_utf8(out.stdout).with_context(|| format!("{program} output is not utf8"))
};

// Detect the major.minor of a tool for the version-gated flags below.
let version = |program: &str| -> Result<(u32, u32)> {
let ver = probe(program, &["--version"])?;
let ver_num = ver
.split_whitespace()
.nth(1)
.ok_or_else(|| anyhow::anyhow!("unexpected rustc --version output: {ver}"))?;
.ok_or_else(|| anyhow::anyhow!("unexpected {program} --version output: {ver}"))?;
let mut parts = ver_num.split('.');
let major: u32 = parts
.next()
Expand All @@ -221,17 +228,24 @@ pub fn invoke_build(current_dir: &Path) -> Result<()> {
.ok_or_else(|| anyhow::anyhow!("missing minor version"))?
.parse()
.context("invalid minor version")?;
(major, minor)
Ok((major, minor))
};

// `rustc` and `cargo` ship together but a flag can land in one before the other, so gate
// each flag on the version of the binary that parses it.
// 1.92+: `-Cpanic=immediate-abort` is a stable rustflag.
let new_immediate_abort = major > 1 || (major == 1 && minor >= 92);
// 1.95+: `-Z json-target-spec` was added; later rustc requires it for any
// `--target=*.json` invocation. Pre-1.95 doesn't know the flag.
let needs_json_target_spec = major > 1 || (major == 1 && minor >= 95);
let rustc_immediate_abort = version("rustc")? >= (1, 92);
// Older toolchains request the same from cargo when it builds `core` instead.
let cargo_immediate_abort = version("cargo")? < (1, 92);

// `-Z json-target-spec` is a *cargo* flag: newer cargo requires it to be opted into for
// any `--target=*.json` invocation, older cargo rejects it as unknown. It landed partway
// through the 1.95 cycle, so a toolchain from early in that cycle reports 1.95 while its
// cargo does not know the flag yet: no version comparison can express this.
let supports_json_target_spec = probe("cargo", &["-Z", "help"])?.contains("json-target-spec");

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

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

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

Expand Down
15 changes: 15 additions & 0 deletions substrate/utils/wasm-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,21 @@ impl CargoCommand {
version.major > 1 || (version.major == 1 && version.minor >= 68) || version.is_nightly
}

/// Returns whether this cargo accepts the `-Z json-target-spec` flag.
///
/// Newer cargo requires the flag to be opted into for any `--target=*.json` invocation,
/// older cargo rejects it as unknown. It landed partway through the 1.95 cycle, so a
/// toolchain from early in that cycle reports 1.95 while its cargo does not know the
/// flag yet: no version comparison can express this, hence the probe.
fn supports_json_target_spec(&self) -> bool {
// `-Z` flags are nightly-only, so make a stable cargo answer as a nightly would.
self.command()
.env("RUSTC_BOOTSTRAP", "1")
.args(["-Z", "help"])
.output()
.is_ok_and(|out| String::from_utf8_lossy(&out.stdout).contains("json-target-spec"))
}

/// Returns whether this version of the toolchain supports the `wasm32v1-none` target.
fn supports_wasm32v1_none_target(&self) -> bool {
self.version.map_or(false, |version| {
Expand Down
12 changes: 3 additions & 9 deletions substrate/utils/wasm-builder/src/wasm_project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,15 +1023,9 @@ fn build_bloaty_blob(
}

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

Expand Down
Loading