From c51af35c8915fb622a8a8a41a8df8c0ff28216ec Mon Sep 17 00:00:00 2001 From: Sergej Salnikov Date: Mon, 26 Oct 2026 14:59:54 +0100 Subject: [PATCH 1/5] version support version is the plain MAJOR.MINOR.PATCH part of RUSTC_CFG_VERSION (1.101.0, not 1.101.0-nightly): the bindgen rule forwards version verbatim as --rust-target, and bindgen rejects pre-release suffixes; the channel is carried by the channel attribute, which keeps its default of nightly and matches the Crosstool compilers. The bindgen rule is additionally hardened to derive --rust-target from version_semver so a suffixed version can never break it again. --- extensions/bindgen/private/bindgen.bzl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/extensions/bindgen/private/bindgen.bzl b/extensions/bindgen/private/bindgen.bzl index 90a3f236be..fe563d714c 100644 --- a/extensions/bindgen/private/bindgen.bzl +++ b/extensions/bindgen/private/bindgen.bzl @@ -273,14 +273,17 @@ def _rust_bindgen_impl(ctx): rust_toolchain = ctx.toolchains[Label("@rules_rust//rust:toolchain_type")] user_flags = [f.split("=")[0] for f in ctx.attr.bindgen_flags] - # Ignore `nightly` or `beta` versions. - if rust_toolchain.version and rust_toolchain.version[0].isdigit(): + # Ignore `nightly` or `beta` versions (`version_semver` is `None` for channel labels). + version_semver = getattr(rust_toolchain, "version_semver", None) + if version_semver: # Pass `--rust-target` so bindgen knows the actual toolchain version. # Without this, bindgen defaults to it's built-in `LATEST_STABLE_RUST` # which may be older than the actual toolchain version and would # reject newer editions (e.g. edition 2024 requires `rust-target >= 1.85.0`). + # bindgen only accepts `MAJOR.MINOR[.PATCH]`, so drop any pre-release + # (`-nightly`) or build-metadata (`+...`) part of the version. if "--rust-target" not in user_flags: - args.add("--rust-target=%s" % rust_toolchain.version) + args.add("--rust-target=%d.%d.%d" % (version_semver.major, version_semver.minor, version_semver.patch)) if "--rust-edition" not in user_flags: args.add("--rust-edition=%s" % rust_toolchain.default_edition) From 0edcd35d654d5dc1f22c35979bc64f18e1ee600d Mon Sep 17 00:00:00 2001 From: Sergej Salnikov Date: Mon, 26 Oct 2026 19:58:23 +0100 Subject: [PATCH 2/5] add tests --- extensions/bindgen/test/bindgen_test.bzl | 92 ++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/extensions/bindgen/test/bindgen_test.bzl b/extensions/bindgen/test/bindgen_test.bzl index 2cb1baa862..fe6389ff1e 100644 --- a/extensions/bindgen/test/bindgen_test.bzl +++ b/extensions/bindgen/test/bindgen_test.bzl @@ -354,6 +354,96 @@ def _test_optimization_flags(name): }, ) +def _fake_rust_toolchain(name, version): + """Registers a `rust_toolchain` with mock tools whose only property of interest is `version`. + The toolchain is only ever used for analysis (the test never executes `rustc`), so empty + executables and an arbitrary file standing in for the standard library are sufficient. + """ + mock_rustc = name + "_mock_rustc" + write_file( + name = mock_rustc, + out = name + "/mock_rustc.exe", + content = [], + is_executable = True, + ) + mock_rustdoc = name + "_mock_rustdoc" + write_file( + name = mock_rustdoc, + out = name + "/mock_rustdoc.exe", + content = [], + is_executable = True, + ) + std_libs = name + "_std_libs" + rust_stdlib_filegroup( + name = std_libs, + srcs = [mock_rustc], + ) + rust_toolchain( + name = name + "_rust_toolchain", + binary_ext = "", + default_edition = "2021", + dylib_ext = ".so", + exec_triple = "x86_64-unknown-linux-gnu", + rust_doc = mock_rustdoc, + rust_std = std_libs, + rustc = mock_rustc, + staticlib_ext = ".a", + stdlib_linkflags = [], + target_triple = "x86_64-unknown-linux-gnu", + version = version, + ) + native.toolchain( + name = name, + toolchain = name + "_rust_toolchain", + toolchain_type = "@rules_rust//rust:toolchain_type", + ) +def _test_rust_target_from_prerelease_version_impl(env, target): + env.expect.that_int(len(target.actions)).is_greater_than(0) + env.expect.that_action(target.actions[0]).mnemonic().contains("RustBindgen") + # bindgen only accepts `MAJOR.MINOR[.PATCH]` (or the literal `nightly`) for + # `--rust-target`; a pre-release suffix such as `-nightly` makes it fail with + # "the patch version number must be an unsigned 64-bit integer". + env.expect.that_action(target.actions[0]).contains_at_least_args( + ["--rust-target=1.87.0"], + ) + env.expect.that_action(target.actions[0]).not_contains_arg( + "--rust-target=1.87.0-nightly", + ) +def _test_rust_target_from_prerelease_version(name): + # Test that a `rust_toolchain` declaring a pre-release `version` (as + # toolchains built from a nightly commit do) results in a plain + # `MAJOR.MINOR.PATCH` `--rust-target`. + _fake_rust_toolchain(name + "_toolchain", version = "1.87.0-nightly") + _create_simple_rust_bindgen_library(name) + analysis_test( + name = name, + target = name + "_rust_bindgen__bindgen", + impl = _test_rust_target_from_prerelease_version_impl, + config_settings = { + "//command_line_option:extra_toolchains": [str(native.package_relative_label(name + "_toolchain"))], + }, + ) +def _test_no_rust_target_for_channel_label_impl(env, target): + env.expect.that_int(len(target.actions)).is_greater_than(0) + env.expect.that_action(target.actions[0]).mnemonic().contains("RustBindgen") + env.expect.that_action(target.actions[0]).argv().not_contains_predicate( + matching.str_startswith("--rust-target="), + ) +def _test_no_rust_target_for_channel_label(name): + # Test that a `rust_toolchain` whose `version` is a bare channel label + # (`nightly`, `beta`) does not produce a `--rust-target` flag, leaving + # bindgen on its built-in default. + _fake_rust_toolchain(name + "_toolchain", version = "nightly") + _create_simple_rust_bindgen_library(name) + analysis_test( + name = name, + target = name + "_rust_bindgen__bindgen", + impl = _test_no_rust_target_for_channel_label_impl, + config_settings = { + "//command_line_option:extra_toolchains": [str(native.package_relative_label(name + "_toolchain"))], + }, + ) + def bindgen_test_suite(name): test_suite( name = name, @@ -365,5 +455,7 @@ def bindgen_test_suite(name): _test_strip_xclang, _test_isystem_preservation, _test_optimization_flags, + _test_rust_target_from_prerelease_version, + _test_no_rust_target_for_channel_label, ], ) From b82be993486c93f90cfd61b4e312cc5238176e09 Mon Sep 17 00:00:00 2001 From: Sergej Salnikov Date: Mon, 26 Oct 2026 20:05:35 +0100 Subject: [PATCH 3/5] Update bindgen_test.bzl --- extensions/bindgen/test/bindgen_test.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/bindgen/test/bindgen_test.bzl b/extensions/bindgen/test/bindgen_test.bzl index fe6389ff1e..8323af41b5 100644 --- a/extensions/bindgen/test/bindgen_test.bzl +++ b/extensions/bindgen/test/bindgen_test.bzl @@ -7,6 +7,7 @@ load("@rules_cc//cc:defs.bzl", "cc_library", "cc_toolchain") load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_cc//cc/toolchains:cc_toolchain_config_info.bzl", "CcToolchainConfigInfo") load("@rules_rust//rust:defs.bzl", "rust_binary") +load("@rules_rust//rust:toolchain.bzl", "rust_stdlib_filegroup", "rust_toolchain") load("@rules_rust_bindgen//:defs.bzl", "rust_bindgen_library") load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") load("@rules_testing//lib:truth.bzl", "matching") From d76fe0b57d9fdfeebef7954b047f2372482c3a5b Mon Sep 17 00:00:00 2001 From: Sergej Salnikov Date: Mon, 26 Oct 2026 20:18:38 +0100 Subject: [PATCH 4/5] Update bindgen_test.bzl --- extensions/bindgen/test/bindgen_test.bzl | 66 +++++++++++++++++++++--- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/extensions/bindgen/test/bindgen_test.bzl b/extensions/bindgen/test/bindgen_test.bzl index 8323af41b5..c0d8dccd5a 100644 --- a/extensions/bindgen/test/bindgen_test.bzl +++ b/extensions/bindgen/test/bindgen_test.bzl @@ -6,6 +6,7 @@ load("@rules_cc//cc:cc_toolchain_config_lib.bzl", "feature", "flag_group", "flag load("@rules_cc//cc:defs.bzl", "cc_library", "cc_toolchain") load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_cc//cc/toolchains:cc_toolchain_config_info.bzl", "CcToolchainConfigInfo") +load("@rules_rust//cargo:defs.bzl", "cargo_toolchain") load("@rules_rust//rust:defs.bzl", "rust_binary") load("@rules_rust//rust:toolchain.bzl", "rust_stdlib_filegroup", "rust_toolchain") load("@rules_rust_bindgen//:defs.bzl", "rust_bindgen_library") @@ -356,9 +357,21 @@ def _test_optimization_flags(name): ) def _fake_rust_toolchain(name, version): - """Registers a `rust_toolchain` with mock tools whose only property of interest is `version`. - The toolchain is only ever used for analysis (the test never executes `rustc`), so empty - executables and an arbitrary file standing in for the standard library are sufficient. + """Registers mock `rust_toolchain` and `cargo_toolchain`s that declare `version`. + + The toolchains are only ever used for analysis (the tests never execute + `rustc` or `cargo`), so empty executables and an arbitrary file standing in + for the standard library are sufficient. + + `--extra_toolchains` applies to every configuration, including the exec + configuration that builds the bindgen CLI and its crates. Their + `cargo_build_script` targets fail analysis when the `rust_toolchain` and + `cargo_toolchain` they resolve disagree on `version`, so a `cargo_toolchain` + stating the same `version` is registered together with the `rust_toolchain`. + + Returns: + list: The labels of the `toolchain` targets, to be passed to + `--extra_toolchains` together. """ mock_rustc = name + "_mock_rustc" write_file( @@ -367,6 +380,7 @@ def _fake_rust_toolchain(name, version): content = [], is_executable = True, ) + mock_rustdoc = name + "_mock_rustdoc" write_file( name = mock_rustdoc, @@ -374,11 +388,21 @@ def _fake_rust_toolchain(name, version): content = [], is_executable = True, ) + + mock_cargo = name + "_mock_cargo" + write_file( + name = mock_cargo, + out = name + "/mock_cargo.exe", + content = [], + is_executable = True, + ) + std_libs = name + "_std_libs" rust_stdlib_filegroup( name = std_libs, srcs = [mock_rustc], ) + rust_toolchain( name = name + "_rust_toolchain", binary_ext = "", @@ -393,11 +417,30 @@ def _fake_rust_toolchain(name, version): target_triple = "x86_64-unknown-linux-gnu", version = version, ) + native.toolchain( name = name, toolchain = name + "_rust_toolchain", toolchain_type = "@rules_rust//rust:toolchain_type", ) + + cargo_toolchain( + name = name + "_cargo_toolchain", + cargo = mock_cargo, + version = version, + ) + + native.toolchain( + name = name + "_cargo", + toolchain = name + "_cargo_toolchain", + toolchain_type = "@rules_rust//cargo:toolchain_type", + ) + + return [ + str(native.package_relative_label(name)), + str(native.package_relative_label(name + "_cargo")), + ] + def _test_rust_target_from_prerelease_version_impl(env, target): env.expect.that_int(len(target.actions)).is_greater_than(0) env.expect.that_action(target.actions[0]).mnemonic().contains("RustBindgen") @@ -410,38 +453,47 @@ def _test_rust_target_from_prerelease_version_impl(env, target): env.expect.that_action(target.actions[0]).not_contains_arg( "--rust-target=1.87.0-nightly", ) + def _test_rust_target_from_prerelease_version(name): # Test that a `rust_toolchain` declaring a pre-release `version` (as # toolchains built from a nightly commit do) results in a plain # `MAJOR.MINOR.PATCH` `--rust-target`. - _fake_rust_toolchain(name + "_toolchain", version = "1.87.0-nightly") + + toolchains = _fake_rust_toolchain(name + "_toolchain", version = "1.87.0-nightly") + _create_simple_rust_bindgen_library(name) + analysis_test( name = name, target = name + "_rust_bindgen__bindgen", impl = _test_rust_target_from_prerelease_version_impl, config_settings = { - "//command_line_option:extra_toolchains": [str(native.package_relative_label(name + "_toolchain"))], + "//command_line_option:extra_toolchains": toolchains, }, ) + def _test_no_rust_target_for_channel_label_impl(env, target): env.expect.that_int(len(target.actions)).is_greater_than(0) env.expect.that_action(target.actions[0]).mnemonic().contains("RustBindgen") env.expect.that_action(target.actions[0]).argv().not_contains_predicate( matching.str_startswith("--rust-target="), ) + def _test_no_rust_target_for_channel_label(name): # Test that a `rust_toolchain` whose `version` is a bare channel label # (`nightly`, `beta`) does not produce a `--rust-target` flag, leaving # bindgen on its built-in default. - _fake_rust_toolchain(name + "_toolchain", version = "nightly") + + toolchains = _fake_rust_toolchain(name + "_toolchain", version = "nightly") + _create_simple_rust_bindgen_library(name) + analysis_test( name = name, target = name + "_rust_bindgen__bindgen", impl = _test_no_rust_target_for_channel_label_impl, config_settings = { - "//command_line_option:extra_toolchains": [str(native.package_relative_label(name + "_toolchain"))], + "//command_line_option:extra_toolchains": toolchains, }, ) From cee34049eeccd6c58a276105298f05c8811cde2a Mon Sep 17 00:00:00 2001 From: Sergej Salnikov Date: Tue, 27 Oct 2026 15:46:50 +0100 Subject: [PATCH 5/5] Update bindgen_test.bzl --- extensions/bindgen/test/bindgen_test.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/bindgen/test/bindgen_test.bzl b/extensions/bindgen/test/bindgen_test.bzl index c0d8dccd5a..646d8cebbb 100644 --- a/extensions/bindgen/test/bindgen_test.bzl +++ b/extensions/bindgen/test/bindgen_test.bzl @@ -444,6 +444,7 @@ def _fake_rust_toolchain(name, version): def _test_rust_target_from_prerelease_version_impl(env, target): env.expect.that_int(len(target.actions)).is_greater_than(0) env.expect.that_action(target.actions[0]).mnemonic().contains("RustBindgen") + # bindgen only accepts `MAJOR.MINOR[.PATCH]` (or the literal `nightly`) for # `--rust-target`; a pre-release suffix such as `-nightly` makes it fail with # "the patch version number must be an unsigned 64-bit integer".