Skip to content

rust_bindgen does not resolve Apple toolchain Xcode placeholders #225

Description

@DoDoENT

What happened?

rust_bindgen fails when it consumes compile flags from the C++ toolchain provided by apple_support on macOS. The action receives the Apple toolchain's placeholder paths literally:

-isysroot __BAZEL_XCODE_SDKROOT__
-F__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks
-F__BAZEL_XCODE_DEVELOPER_DIR__/Platforms/MacOSX.platform/Developer/Library/Frameworks

This causes bindgen to fail with:

warning: no such sysroot directory: '__BAZEL_XCODE_SDKROOT__'
fatal error: 'stddef.h' file not found

The same injected aws-lc-sys setup builds successfully when a hermetic LLVM C++ toolchain is selected for macOS. That toolchain supplies bindgen with a usable sysroot and Clang resource directory. The failure is specific to using the bare apple_support/Xcode C++ toolchain.

Using hermetic LLVM for the whole macOS build is not always a practical workaround: mixed C++/Rust projects may need to keep Xcode's Apple-provided toolchain for their existing C++ targets while still using rust_bindgen for Rust dependencies.

This appears distinct from #22: that issue fixed the environment for rustc, while this failure is in the custom RustBindgen action registered by @rules_rs//rs:rules_rust_bindgen.bzl.

Standalone reproducer

Environment used:

  • macOS 26.5, arm64
  • Xcode 26.6
  • Bazel 9.2.0
  • rules_rs 0.0.104
  • apple_support 2.8.0
  • rules_cc 0.2.22

MODULE.bazel:

module(name = "rules_rs_apple_bindgen_repro")

# Keep apple_support before rules_cc so its C++ toolchain is configured.
bazel_dep(name = "apple_support", version = "2.8.0")
bazel_dep(name = "rules_cc", version = "0.2.22")
bazel_dep(name = "aws-lc", version = "5.1.0.bcr.2")
bazel_dep(name = "rules_rs", version = "0.0.104")

rust_toolchains = use_extension("@rules_rs//rs/toolchains:module_extension.bzl", "toolchains")
rust_toolchains.toolchain(
    edition = "2021",
    version = "1.95.0",
)
use_repo(rust_toolchains, "default_rust_toolchains")
register_toolchains("@default_rust_toolchains//...")

crate = use_extension("@rules_rs//rs:extensions.bzl", "crate")
crate.from_cargo(
    name = "crates",
    cargo_lock = "//:Cargo.lock",
    cargo_toml = "//:Cargo.toml",
    platform_triples = ["aarch64-apple-darwin"],
)
crate.annotation(
    crate = "aws-lc-rs",
    gen_build_script = "off",
)
crate.annotation(
    additive_build_file = "@rules_rs//3rd_party/aws-lc-sys:additive.BUILD.bazel",
    crate = "aws-lc-sys",
    deps = ["@crates//:aws_lc_sys_build_info"],
    extra_aliased_targets = {"aws_lc_sys_build_info": "aws_lc_sys_build_info"},
    gen_build_script = "off",
    rustc_flags = ["--cfg=use_bindgen_pregenerated"],
)
use_repo(crate, "crates")
inject_repo(crate, "aws-lc")

rules_rust_bindgen = use_extension("@rules_rs//rs:rules_rust_bindgen.bzl", "rules_rust_bindgen")
use_repo(rules_rust_bindgen, "rules_rust_bindgen")
register_toolchains("@rules_rust_bindgen//:all")

Cargo.toml:

[package]
name = "rules-rs-apple-bindgen-repro"
version = "0.1.0"
edition = "2021"

[dependencies]
aws-lc-rs = "=1.15.2"

BUILD.bazel:

load("@crates//:defs.bzl", "all_crate_deps")
load("@rules_rs//rs:rust_binary.bzl", "rust_binary")

rust_binary(
    name = "repro",
    srcs = ["main.rs"],
    deps = all_crate_deps(normal = True),
)

main.rs:

fn main() {
    let _ = aws_lc_rs::digest::SHA256;
}

Then run:

cargo generate-lockfile
bazel build //:repro --verbose_failures

Root cause

_clang_compile_flags correctly obtains the Apple C++ toolchain flags through cc_common, but _rust_bindgen_impl registers a plain ctx.actions.run. Apple's C++ compiler wrapper normally resolves the __BAZEL_XCODE_*__ placeholders. Since the bindgen executable is invoked directly, that resolution never happens.

This explains why selecting hermetic LLVM for macOS avoids the problem: its compile flags already point at a concrete hermetic SDK/resource directory and do not rely on the Apple placeholder-expansion wrapper.

Using apple_support.run resolves the placeholders and supplies the selected Xcode environment. One additional detail is required: the bundled libclang also needs Xcode Clang's builtin resource headers (stdarg.h, etc.). Keeping --no-include-path-detection on Apple still fails after resolving the SDK root, so Apple targets need bindgen include-path detection enabled under the Xcode environment. Non-Apple targets can retain the current hermetic behavior.

Possible fix

The following patch was tested against the standalone reproducer above; the build completed successfully.

diff --git a/rs/rules_rust_bindgen.bzl b/rs/rules_rust_bindgen.bzl
--- a/rs/rules_rust_bindgen.bzl
+++ b/rs/rules_rust_bindgen.bzl
@@
+load("@apple_support//lib:apple_support.bzl", "apple_support")
 load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "C_COMPILE_ACTION_NAME")
@@
+    is_apple = apple_support.target_os_from_rule_ctx(ctx, fail_on_missing_constraint = False) != None
     output = ctx.outputs.out
     args = ctx.actions.args()
-    args.add("--no-include-path-detection")
+    if not is_apple:
+        args.add("--no-include-path-detection")
@@
-    ctx.actions.run(
+    action_kwargs = dict(
         executable = bindgen_toolchain.bindgen,
         arguments = [args],
         inputs = compilation_context.headers,
         outputs = [output],
         tools = cc_toolchain.all_files,
         mnemonic = "RustBindgen",
         progress_message = "Generating Rust bindings for {}".format(header.short_path),
         toolchain = _BINDGEN_TOOLCHAIN_TYPE,
     )
+    if is_apple:
+        apple_support.run(
+            actions = ctx.actions,
+            apple_platform_info = apple_support.platform_info_from_rule_ctx(ctx),
+            xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig],
+            xcode_path_resolve_level = apple_support.xcode_path_resolve_level.args_and_files,
+            **action_kwargs
+        )
+    else:
+        ctx.actions.run(**action_kwargs)
@@
-    },
-    fragments = ["cpp"],
+    } | apple_support.action_required_attrs() | apple_support.platform_constraint_attrs(),
+    fragments = ["apple", "cpp"],

It may be preferable to retain --no-include-path-detection and obtain the Xcode Clang resource directory another way. I used include-path detection here because the Apple toolchain's cc_common command line contains the SDK placeholders but does not include Clang's builtin resource directory; bindgen otherwise advances from missing stddef.h to missing stdarg.h.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions