From dce8357a03198c4866f4bbd06998d73686399ed6 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Mon, 27 Jul 2026 15:53:34 -0700 Subject: [PATCH 01/16] Add `rust_dylib_library` rule Adds a rule that allows building a dynamic library using the unstable Rust ABI. For easier contrast, converts the existing `rust_shared_library` rule to be a thin alias for `rust_cydylib_library`. --- CONTRIBUTING.md | 1 + rust/defs.bzl | 8 ++++-- rust/private/rust.bzl | 55 ++++++++++++++++++++++++++++++++++++++---- rust/private/rustc.bzl | 16 ++++++++++-- 4 files changed, 71 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6fbbaa6759..92c660ecd0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,6 +46,7 @@ Rules used to compile Rust code. * `rust_binary` * `rust_test` * `rust_static_library` +* `rust_dylib_library` * `rust_shared_library` * `rust_proc_macro` * `cargo_build_script` diff --git a/rust/defs.bzl b/rust/defs.bzl index a33ecc492a..8ab2b76998 100644 --- a/rust/defs.bzl +++ b/rust/defs.bzl @@ -37,10 +37,11 @@ load( load( "//rust/private:rust.bzl", _rust_binary = "rust_binary", + _rust_cdylib_library = "rust_cdylib_library", + _rust_dylib_library = "rust_dylib_library", _rust_library = "rust_library", _rust_library_group = "rust_library_group", _rust_proc_macro = "rust_proc_macro", - _rust_shared_library = "rust_shared_library", _rust_static_library = "rust_static_library", _rust_test = "rust_test", _rust_test_suite = "rust_test_suite", @@ -85,7 +86,10 @@ rust_library = _rust_library rust_static_library = _rust_static_library # See @rules_rust//rust/private:rust.bzl for a complete description. -rust_shared_library = _rust_shared_library +rust_dylib_library = _rust_dylib_library +# See @rules_rust//rust/private:rust.bzl for a complete description. + +rust_shared_library = _rust_cdylib_library # See @rules_rust//rust/private:rust.bzl for a complete description. rust_proc_macro = _rust_proc_macro diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index 0697c6c2f8..738ffcac63 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -159,8 +159,22 @@ def _rust_static_library_impl(ctx): """ return _rust_library_common(ctx, "staticlib") -def _rust_shared_library_impl(ctx): - """The implementation of the `rust_shared_library` rule. +def _rust_dylib_library_impl(ctx): + """The implementation of the `rust_dylib_library` rule. + + This rule provides CcInfo, so it can be used everywhere Bazel + expects rules_cc. + + Args: + ctx (ctx): The rule's context object + + Returns: + list: A list of providers. + """ + return _rust_library_common(ctx, "dylib") + +def _rust_cdylib_library_impl(ctx): + """The implementation of the `rust_cdylib_library` rule. This rule provides CcInfo, so it can be used everywhere Bazel expects rules_cc. @@ -1082,6 +1096,37 @@ rust_library = rule( """), ) +rust_dylib_library = rule( + implementation = _rust_dylib_library_impl, + provides = COMMON_PROVIDERS, + attrs = _COMMON_ATTRS | { + "disable_pipelining": attr.bool( + default = False, + doc = dedent("""\ + Disables pipelining for this rule if it is globally enabled. + This will cause this rule to not produce a `.rmeta` file and all the dependent + crates will instead use the `.rlib` file. + """), + ), + }, + fragments = ["cpp"], + toolchains = [ + str(Label("//rust:toolchain_type")), + config_common.toolchain_type("@bazel_tools//tools/cpp:toolchain_type", mandatory = False), + ], + doc = dedent("""\ + Builds a Rust ABI shared library. + + This shared library will contain all transitively reachable crates and native objects. + It is meant to be used when producing an artifact that is then consumed by some other build system + (for example to produce a shared library that Python program links against). + + This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`. + + When building the whole binary in Bazel, use `rust_library` instead. + """), +) + def _resolve_platform(settings, attr): """Resolve the platform label for a transition, adding @ prefix if needed. @@ -1158,8 +1203,8 @@ _rust_shared_library_transition = transition( ], ) -rust_shared_library = rule( - implementation = _rust_shared_library_impl, +rust_cdylib_library = rule( + implementation = _rust_cdylib_library_impl, attrs = _COMMON_ATTRS | _PLATFORM_ATTRS | _EXPERIMENTAL_USE_CC_COMMON_LINK_ATTRS, fragments = ["cpp"], cfg = _rust_shared_library_transition, @@ -1172,7 +1217,7 @@ rust_shared_library = rule( rust_common.test_crate_info, ], doc = dedent("""\ - Builds a Rust shared library. + Builds a C ABI Rust shared library. This shared library will contain all transitively reachable crates and native objects. It is meant to be used when producing an artifact that is then consumed by some other build system diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index ef902f7e17..d2201477cb 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -290,6 +290,11 @@ def collect_deps( transitive_link_search_paths.append(dep_info.link_search_path_files) transitive_build_infos.append(dep_info.transitive_build_infos) + + # If the dep is a dylib, include its own CcInfo in transitive_noncrates + # so downstream binaries get the RPATH and runfiles for the .so + if crate_info.type == "dylib" and cc_info: + transitive_noncrates.append(cc_info.linking_context.linker_inputs) elif cc_info or dep_build_info: if cc_info: # This dependency is a cc_library @@ -2306,7 +2311,7 @@ def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_co return [] # Only generate CcInfo for particular crate types - if crate_info.type not in ("staticlib", "cdylib", "rlib", "lib"): + if crate_info.type not in ("dylib", "staticlib", "cdylib", "rlib", "lib"): return [] dot_a = None @@ -2356,6 +2361,14 @@ def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_co dynamic_library = crate_info.output, interface_library = interface_library, ) + elif crate_info.type == "dylib": + if cc_toolchain: + library_to_link = cc_common.create_library_to_link( + actions = ctx.actions, + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + dynamic_library = crate_info.output, + ) else: fail("Unexpected case") @@ -2471,7 +2484,6 @@ def _process_build_scripts( if dep_build_info.out_dir: direct_inputs.append(dep_build_info.out_dir) transitive_inputs.append(dep_build_info.compile_data) - out_dir_compile_inputs = depset( direct_inputs, transitive = transitive_inputs, From 69c94243eacda76335e2db7cdc304f6243deb82f Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Wed, 29 Jul 2026 14:59:32 -0700 Subject: [PATCH 02/16] Let individual targets dynamically link stdlib Updates dylib, binary, and test targets to have an additional flag for explicitly enabling the dynamic linking of the stdlib without a toolchain transition. --- rust/private/providers.bzl | 2 + rust/private/rust.bzl | 28 ++++++ rust/private/rust_allocator_libraries.bzl | 17 ++-- rust/private/rustc.bzl | 45 +++++++--- rust/private/toolchain.bzl | 17 ++-- test/link_std_dylib/link_std_dylib_test.bzl | 98 ++++++++++++++++++--- 6 files changed, 171 insertions(+), 36 deletions(-) diff --git a/rust/private/providers.bzl b/rust/private/providers.bzl index f3e8b682ec..fdb952c155 100644 --- a/rust/private/providers.bzl +++ b/rust/private/providers.bzl @@ -221,6 +221,8 @@ AllocatorLibrariesInfo = provider( "global_allocator_library": "Optional[CcInfo]: used when a global rust allocator is used", "libstd_and_allocator_ccinfo": "Optional[CcInfo]: used when the default rust allocator is used", "libstd_and_global_allocator_ccinfo": "Optional[CcInfo]: used when a global rust allocator is used", + "libstd_dylib_and_allocator_ccinfo": "Optional[CcInfo]: used when the default rust allocator is used with a dylib stdlib", + "libstd_dylib_and_global_allocator_ccinfo": "Optional[CcInfo]: used when a global rust allocator is used with a dylib stdlib", "nostd_and_global_allocator_ccinfo": "Optional[CcInfo]: used when nostd with a global rust allocator is used", }, ) diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index 738ffcac63..d0e74f3f7b 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -1001,6 +1001,15 @@ _RUST_TEST_ATTRS = { "env_inherit": attr.string_list( doc = "Specifies additional environment variables to inherit from the external environment when the test is executed by bazel test.", ), + "link_std_dylib": attr.bool( + mandatory = False, + default = False, + doc = dedent("""\ + Flag to dynamically link the standard library as a Rust dylib .so object when building this test. + + Default is false. This is often required when testing a target that depends on a Rust ABI dylib. + """), + ), "use_libtest_harness": attr.bool( mandatory = False, default = True, @@ -1108,6 +1117,16 @@ rust_dylib_library = rule( crates will instead use the `.rlib` file. """), ), + "link_std_dylib": attr.bool( + mandatory = False, + default = False, + doc = dedent("""\ + Flag to dynamically link the standard library as a Rust dylib .so object when building this library. + + Default is false. This is typically required for Rust ABI dylibs so that the stdlib is shared + with the binary that loads them, avoiding duplicate symbols. + """), + ), }, fragments = ["cpp"], toolchains = [ @@ -1271,6 +1290,15 @@ _RUST_BINARY_ATTRS = { more complicated debugger attachment. """), ), + "link_std_dylib": attr.bool( + mandatory = False, + default = False, + doc = dedent("""\ + Flag to dynamically link the standard library as a Rust dylib .so object when building this binary. + + Default is false. This is often required when building a binary that depends on a Rust ABI dylib. + """), + ), "linker_script": attr.label( doc = dedent("""\ Link script to forward into linker via rustc options. diff --git a/rust/private/rust_allocator_libraries.bzl b/rust/private/rust_allocator_libraries.bzl index 4667faa090..1ad055e11e 100644 --- a/rust/private/rust_allocator_libraries.bzl +++ b/rust/private/rust_allocator_libraries.bzl @@ -43,7 +43,7 @@ def make_libstd_and_allocator_ccinfo( feature_configuration, label, actions, - experimental_link_std_dylib, + link_std_dylib, rust_std, allocator_library, std = "std"): @@ -54,7 +54,7 @@ def make_libstd_and_allocator_ccinfo( feature_configuration (feature_configuration): feature_configuration to be queried. label (Label): The rule's label. actions: The rule's ctx.actions object. - experimental_link_std_dylib (boolean): The value of the standard library's `_experimental_link_std_dylib(ctx)`. + link_std_dylib (boolean): If the standard library should be included as a dylib. rust_std: The Rust standard library. allocator_library (struct): The target to use for providing allocator functions. This should be a struct with either: @@ -183,7 +183,7 @@ def make_libstd_and_allocator_ccinfo( order = "topological", ) - if experimental_link_std_dylib: + if link_std_dylib: # std dylib has everything so that we do not need to include all std_files std_inputs = depset( [cc_common.create_library_to_link( @@ -264,20 +264,23 @@ def _rust_allocator_libraries_impl(ctx): toolchain = find_toolchain(ctx) - def make_cc_info(info, std): + def make_cc_info(info, std, link_std_dylib): return toolchain.make_libstd_and_allocator_ccinfo( ctx.label, ctx.actions, struct(allocator_libraries_impl_info = info), std, + link_std_dylib, ) providers = [AllocatorLibrariesInfo( allocator_library = allocator_library, global_allocator_library = global_allocator_library, - libstd_and_allocator_ccinfo = make_cc_info(allocator_library, "std"), - libstd_and_global_allocator_ccinfo = make_cc_info(global_allocator_library, "std"), - nostd_and_global_allocator_ccinfo = make_cc_info(global_allocator_library, "no_std_with_alloc"), + libstd_and_allocator_ccinfo = make_cc_info(allocator_library, "std", False), + libstd_and_global_allocator_ccinfo = make_cc_info(global_allocator_library, "std", False), + nostd_and_global_allocator_ccinfo = make_cc_info(global_allocator_library, "no_std_with_alloc", False), + libstd_dylib_and_allocator_ccinfo = make_cc_info(allocator_library, "std", True), + libstd_dylib_and_global_allocator_ccinfo = make_cc_info(global_allocator_library, "std", True), )] return providers diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index d2201477cb..f92b0e80ae 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -290,7 +290,7 @@ def collect_deps( transitive_link_search_paths.append(dep_info.link_search_path_files) transitive_build_infos.append(dep_info.transitive_build_infos) - + # If the dep is a dylib, include its own CcInfo in transitive_noncrates # so downstream binaries get the RPATH and runfiles for the .so if crate_info.type == "dylib" and cc_info: @@ -961,7 +961,8 @@ def construct_arguments( skip_expanding_rustc_env = False, require_explicit_unstable_features = False, error_format = None, - allowed_unstable_rust_features = None): + allowed_unstable_rust_features = None, + link_std_dylib = False): """Builds an Args object containing common rustc flags Args: @@ -1033,6 +1034,7 @@ def construct_arguments( require_explicit_unstable_features (bool): Whether to require all unstable features to be explicitly opted in to using `-Zallow-features=...`. error_format (str, optional): Error format to pass to the `--error-format` command line argument. If set to None, uses the "_error_format" entry in `attr`. allowed_unstable_rust_features (list, optional): List of unstable Rust language features allowed for this target. + link_std_dylib (bool): Whether to dynamically link the Rust standard library using `--prefer-dynamic`. Returns: tuple: A tuple of the following items @@ -1310,7 +1312,7 @@ def construct_arguments( compilation_mode = compilation_mode, toolchain = toolchain, ) - rpaths = _compute_rpaths(toolchain, output_dir, dep_info, use_pic) + rpaths = _compute_rpaths(toolchain, output_dir, dep_info, use_pic, link_std_dylib) else: rpaths = depset() @@ -1397,7 +1399,7 @@ def construct_arguments( map_each = _args_map_bin_dir, ) - if toolchain._experimental_link_std_dylib: + if link_std_dylib: rustc_flags.add("--codegen=prefer-dynamic") # Make bin crate data deps available to tests. @@ -1799,6 +1801,11 @@ def rustc_compile_action( dwo_outputs = ctx.actions.declare_directory(fission_directory, sibling = crate_info.output) rust_flags.append(("-Zsplit-dwarf-out-dir=%s", dwo_outputs)) + if hasattr(ctx.attr, "link_std_dylib"): + link_std_dylib = toolchain._experimental_link_std_dylib or ctx.attr.link_std_dylib + else: + link_std_dylib = toolchain._experimental_link_std_dylib + args, env_from_args = construct_arguments( ctx = ctx, attr = attr, @@ -1823,6 +1830,7 @@ def rustc_compile_action( skip_expanding_rustc_env = skip_expanding_rustc_env, require_explicit_unstable_features = require_explicit_unstable_features, allowed_unstable_rust_features = allowed_unstable_rust_features, + link_std_dylib = link_std_dylib, ) args_metadata = None @@ -1851,6 +1859,7 @@ def rustc_compile_action( build_metadata = True, require_explicit_unstable_features = require_explicit_unstable_features, allowed_unstable_rust_features = allowed_unstable_rust_features, + link_std_dylib = link_std_dylib, ) env = dict(ctx.configuration.default_shell_env) @@ -1978,7 +1987,7 @@ def rustc_compile_action( # Collect the linking contexts of the standard library and dependencies. linking_contexts = [ malloc_library[CcInfo].linking_context, - _get_std_and_alloc_info(ctx, toolchain, crate_info).linking_context, + _get_std_and_alloc_info(ctx, toolchain, crate_info, link_std_dylib).linking_context, toolchain.stdlib_linkflags.linking_context, ] @@ -2190,10 +2199,12 @@ def _should_use_rustc_allocator_libraries(toolchain): return toolchain._experimental_use_allocator_libraries_with_mangled_symbols_setting return bool(use_or_default) -def _get_std_and_alloc_info(ctx, toolchain, crate_info): +def _get_std_and_alloc_info(ctx, toolchain, crate_info, link_std_dylib): # Handles standard libraries and allocator shims. # - # The standard libraries vary between "std" and "nostd" flavors. + # The standard libraries vary across two dimensions: + # * "std" vs "nostd" flavors, + # * dynamically vs statically linked. # # The allocator libraries vary along two dimensions: # * the type of rust allocator used (default or global) @@ -2212,7 +2223,11 @@ def _get_std_and_alloc_info(ctx, toolchain, crate_info): attr_global_allocator_library = libs.global_allocator_library if is_exec_configuration(ctx): if attr_allocator_library: + if link_std_dylib: + return libs.libstd_dylib_and_allocator_ccinfo return libs.libstd_and_allocator_ccinfo + if link_std_dylib: + return toolchain.libstd_dylib_and_allocator_ccinfo return toolchain.libstd_and_allocator_ccinfo if toolchain._experimental_use_global_allocator: if is_no_std(ctx, toolchain, crate_info.is_test): @@ -2221,10 +2236,18 @@ def _get_std_and_alloc_info(ctx, toolchain, crate_info): return toolchain.nostd_and_global_allocator_ccinfo else: if attr_global_allocator_library: + if link_std_dylib: + return libs.libstd_dylib_and_global_allocator_ccinfo return libs.libstd_and_global_allocator_ccinfo + if link_std_dylib: + return toolchain.libstd_dylib_and_global_allocator_ccinfo return toolchain.libstd_and_global_allocator_ccinfo if attr_allocator_library: + if link_std_dylib: + return libs.libstd_dylib_and_allocator_ccinfo return libs.libstd_and_allocator_ccinfo + if link_std_dylib: + return toolchain.libstd_dylib_and_allocator_ccinfo return toolchain.libstd_and_allocator_ccinfo def _is_dylib(dep): @@ -2406,7 +2429,8 @@ def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_co cc_infos.append(dep.cc_info) if crate_info.type in ("rlib", "lib"): - libstd_and_allocator_cc_info = _get_std_and_alloc_info(ctx, toolchain, crate_info) + # We're an rlib or lib, which uses the default toolchain setting for std dylib linking. + libstd_and_allocator_cc_info = _get_std_and_alloc_info(ctx, toolchain, crate_info, toolchain._experimental_link_std_dylib) if libstd_and_allocator_cc_info: # TODO: if we already have an rlib in our deps, we could skip this cc_infos.append(libstd_and_allocator_cc_info) @@ -2496,7 +2520,7 @@ def _process_build_scripts( depset(build_flags_files, transitive = [dep_info.link_search_path_files]), ) -def _compute_rpaths(toolchain, output_dir, dep_info, use_pic): +def _compute_rpaths(toolchain, output_dir, dep_info, use_pic, link_std_dylib): """Determine the artifact's rpaths relative to the bazel root for runtime linking of shared libraries. Args: @@ -2504,6 +2528,7 @@ def _compute_rpaths(toolchain, output_dir, dep_info, use_pic): output_dir (str): The output directory of the current target dep_info (DepInfo): The current target's dependency info use_pic: If set, prefers pic_static_library over static_library. + link_std_dylib (bool): If the current target should link the stdlib as a dynamic library. Returns: depset: A set of relative paths from the output directory to each dependency @@ -2522,7 +2547,7 @@ def _compute_rpaths(toolchain, output_dir, dep_info, use_pic): ] # Include std dylib if dylib linkage is enabled - if toolchain._experimental_link_std_dylib: + if link_std_dylib: # TODO: Make toolchain.rust_std to only include libstd.so # When dylib linkage is enabled, toolchain.rust_std should only need to # include libstd.so. Hence, no filtering needed. diff --git a/rust/private/toolchain.bzl b/rust/private/toolchain.bzl index 8effcbca4e..a2f590e951 100644 --- a/rust/private/toolchain.bzl +++ b/rust/private/toolchain.bzl @@ -555,26 +555,25 @@ def _rust_toolchain_impl(ctx): ctx.label, )) - experimental_link_std_dylib = _experimental_link_std_dylib(ctx) - - def make_ccinfo(label, actions, allocator_library, std): + def make_ccinfo(label, actions, allocator_library, std, link_std_dylib): return make_libstd_and_allocator_ccinfo( cc_toolchain = cc_toolchain, feature_configuration = feature_configuration, label = label, actions = actions, - experimental_link_std_dylib = experimental_link_std_dylib, + link_std_dylib = link_std_dylib, rust_std = rust_std, allocator_library = allocator_library, std = std, ) - def make_local_ccinfo(allocator_library, std): + def make_local_ccinfo(allocator_library, std, link_std_dylib): return make_ccinfo( ctx.label, ctx.actions, struct(cc_info = allocator_library), std, + link_std_dylib, ) # Include C++ toolchain files to ensure tools like 'ar' are available for cross-compilation @@ -600,9 +599,11 @@ def _rust_toolchain_impl(ctx): env = ctx.attr.env, exec_triple = exec_triple, iso_date = ctx.attr.iso_date, - libstd_and_allocator_ccinfo = make_local_ccinfo(ctx.attr.allocator_library[CcInfo], "std"), - libstd_and_global_allocator_ccinfo = make_local_ccinfo(ctx.attr.global_allocator_library[CcInfo], "std"), - nostd_and_global_allocator_ccinfo = make_local_ccinfo(ctx.attr.global_allocator_library[CcInfo], "no_std_with_alloc"), + libstd_and_allocator_ccinfo = make_local_ccinfo(ctx.attr.allocator_library[CcInfo], "std", False), + libstd_and_global_allocator_ccinfo = make_local_ccinfo(ctx.attr.global_allocator_library[CcInfo], "std", False), + nostd_and_global_allocator_ccinfo = make_local_ccinfo(ctx.attr.global_allocator_library[CcInfo], "no_std_with_alloc", False), + libstd_dylib_and_allocator_ccinfo = make_local_ccinfo(ctx.attr.allocator_library[CcInfo], "std", True), + libstd_dylib_and_global_allocator_ccinfo = make_local_ccinfo(ctx.attr.global_allocator_library[CcInfo], "std", True), make_libstd_and_allocator_ccinfo = make_ccinfo, linker = sysroot.linker, linker_preference = linker_preference, diff --git a/test/link_std_dylib/link_std_dylib_test.bzl b/test/link_std_dylib/link_std_dylib_test.bzl index f1338086d0..d118963e38 100644 --- a/test/link_std_dylib/link_std_dylib_test.bzl +++ b/test/link_std_dylib/link_std_dylib_test.bzl @@ -1,22 +1,20 @@ """Analysis tests for experimental_link_std_dylib flag""" load("@rules_cc//cc:defs.bzl", "CcInfo") -load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library") +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_dylib_library", "rust_library", "rust_test") load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") # buildifier: disable=bzl-visibility load("//rust/private:utils.bzl", "is_std_dylib") -def _test_rust_binary_impl(env, targets): - env.expect.that_action(targets.default_binary.actions[0]) \ +def _test_prefer_dynamic_impl(env, targets): + env.expect.that_action(targets.default_target.actions[0]) \ .contains_none_of_flag_values([ ("--codegen", "prefer-dynamic"), ]) - # Make sure with @rules_rust//rust/settings:experimental_link_std_dylib, - # the linker flags are set up correct so that the binary dynamically links - # the stdlib - env.expect.that_action(targets.binary_with_std_dylib.actions[0]) \ + # Make sure the target with std dylib linkage has the correct codegen flag + env.expect.that_action(targets.target_with_std_dylib.actions[0]) \ .contains_flag_values([ ("--codegen", "prefer-dynamic"), ]) @@ -31,13 +29,13 @@ def _test_rust_binary(name): analysis_test( name = name, - impl = _test_rust_binary_impl, + impl = _test_prefer_dynamic_impl, targets = { - "binary_with_std_dylib": name + "_rust_binary", - "default_binary": name + "_rust_binary", + "default_target": name + "_rust_binary", + "target_with_std_dylib": name + "_rust_binary", }, attrs = { - "binary_with_std_dylib": { + "target_with_std_dylib": { "@config_settings": { str(Label("@rules_rust//rust/settings:experimental_link_std_dylib")): True, }, @@ -45,6 +43,56 @@ def _test_rust_binary(name): }, ) +def _test_rust_binary_with_attr_dylib(name): + rust_binary( + name = name + "_rust_binary", + srcs = ["main.rs"], + edition = "2021", + tags = ["manual"], + ) + + rust_binary( + name = name + "_dylib_rust_binary", + srcs = ["main.rs"], + edition = "2021", + tags = ["manual"], + link_std_dylib = True, + ) + + analysis_test( + name = name, + impl = _test_prefer_dynamic_impl, + targets = { + "default_target": name + "_rust_binary", + "target_with_std_dylib": name + "_dylib_rust_binary", + }, + ) + +def _test_rust_dylib_with_attr(name): + rust_dylib_library( + name = name + "_rust_dylib", + srcs = ["lib.rs"], + edition = "2021", + tags = ["manual"], + ) + + rust_dylib_library( + name = name + "_rust_dylib_link_std", + srcs = ["lib.rs"], + edition = "2021", + tags = ["manual"], + link_std_dylib = True, + ) + + analysis_test( + name = name, + impl = _test_prefer_dynamic_impl, + targets = { + "default_target": name + "_rust_dylib", + "target_with_std_dylib": name + "_rust_dylib_link_std", + }, + ) + def _export_static_stdlibs_in_cc_info(target): linker_inputs = target[CcInfo].linking_context.linker_inputs for linker_input in linker_inputs.to_list(): @@ -108,11 +156,39 @@ def _test_rust_library(name): }, ) +def _test_rust_test_with_attr(name): + rust_test( + name = name + "_rust_test", + srcs = ["main.rs"], + edition = "2021", + tags = ["manual"], + ) + + rust_test( + name = name + "_rust_test_link_std", + srcs = ["main.rs"], + edition = "2021", + tags = ["manual"], + link_std_dylib = True, + ) + + analysis_test( + name = name, + impl = _test_prefer_dynamic_impl, + targets = { + "default_target": name + "_rust_test", + "target_with_std_dylib": name + "_rust_test_link_std", + }, + ) + def link_std_dylib_test_suite(name): test_suite( name = name, tests = [ _test_rust_binary, _test_rust_library, + _test_rust_binary_with_attr_dylib, + _test_rust_dylib_with_attr, + _test_rust_test_with_attr, ], ) From 9ea36c6895af529d58255929fd1ebbe7544d2cbd Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Wed, 29 Jul 2026 17:21:09 -0700 Subject: [PATCH 03/16] Appease CC toolchains for not my computer --- rust/private/rust_allocator_libraries.bzl | 3 +++ rust/private/rustc.bzl | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/rust/private/rust_allocator_libraries.bzl b/rust/private/rust_allocator_libraries.bzl index 1ad055e11e..b5bf67eb34 100644 --- a/rust/private/rust_allocator_libraries.bzl +++ b/rust/private/rust_allocator_libraries.bzl @@ -82,6 +82,9 @@ def make_libstd_and_allocator_ccinfo( """).format(label, rust_std)) rust_stdlib_info = rust_std[rust_common.stdlib_info] + if link_std_dylib and (not rust_stdlib_info.std_dylib or not cc_toolchain): + return None + if rust_stdlib_info.self_contained_files: compilation_outputs = cc_common.create_compilation_outputs( objects = depset(rust_stdlib_info.self_contained_files), diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index f92b0e80ae..cf025ccec0 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -20,7 +20,6 @@ load( "@bazel_tools//tools/build_defs/cc:action_names.bzl", "CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME", "CPP_LINK_EXECUTABLE_ACTION_NAME", - "CPP_LINK_NODEPS_DYNAMIC_LIBRARY_ACTION_NAME", "CPP_LINK_STATIC_LIBRARY_ACTION_NAME", ) load("@rules_cc//cc/common:cc_common.bzl", "cc_common") @@ -407,7 +406,7 @@ def get_linker_and_args(ctx, crate_type, toolchain, cc_toolchain, feature_config action_name = CPP_LINK_EXECUTABLE_ACTION_NAME elif crate_type in ("dylib"): is_linking_dynamic_library = True - action_name = CPP_LINK_NODEPS_DYNAMIC_LIBRARY_ACTION_NAME + action_name = CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME elif crate_type in ("staticlib"): is_linking_dynamic_library = False action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME @@ -2214,6 +2213,9 @@ def _get_std_and_alloc_info(ctx, toolchain, crate_info, link_std_dylib): # # When provided, the allocator_libraries attribute takes precedence over the # toolchain allocator attributes. + if link_std_dylib and not toolchain.libstd_dylib_and_allocator_ccinfo: + fail("link_std_dylib was requested but no std dylib is available for this toolchain target.") + libs = None attr_allocator_library = None attr_global_allocator_library = None From 328b1970b0e5bde316c9c4c5c430069a420f18cd Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Thu, 30 Jul 2026 13:05:46 -0700 Subject: [PATCH 04/16] Rename toolchain attr for better parity --- rust/private/rustc.bzl | 6 +++--- rust/private/toolchain.bzl | 12 ++++++------ rust/rust_cdylib_library.bzl | 8 ++++++++ rust/rust_dylib_library.bzl | 8 ++++++++ 4 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 rust/rust_cdylib_library.bzl create mode 100644 rust/rust_dylib_library.bzl diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index cf025ccec0..d656e22987 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -1801,9 +1801,9 @@ def rustc_compile_action( rust_flags.append(("-Zsplit-dwarf-out-dir=%s", dwo_outputs)) if hasattr(ctx.attr, "link_std_dylib"): - link_std_dylib = toolchain._experimental_link_std_dylib or ctx.attr.link_std_dylib + link_std_dylib = toolchain._link_std_dylib or ctx.attr.link_std_dylib else: - link_std_dylib = toolchain._experimental_link_std_dylib + link_std_dylib = toolchain._link_std_dylib args, env_from_args = construct_arguments( ctx = ctx, @@ -2432,7 +2432,7 @@ def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_co if crate_info.type in ("rlib", "lib"): # We're an rlib or lib, which uses the default toolchain setting for std dylib linking. - libstd_and_allocator_cc_info = _get_std_and_alloc_info(ctx, toolchain, crate_info, toolchain._experimental_link_std_dylib) + libstd_and_allocator_cc_info = _get_std_and_alloc_info(ctx, toolchain, crate_info, toolchain._link_std_dylib) if libstd_and_allocator_cc_info: # TODO: if we already have an rlib in our deps, we could skip this cc_infos.append(libstd_and_allocator_cc_info) diff --git a/rust/private/toolchain.bzl b/rust/private/toolchain.bzl index a2f590e951..d96b645aee 100644 --- a/rust/private/toolchain.bzl +++ b/rust/private/toolchain.bzl @@ -122,7 +122,7 @@ rust_stdlib_filegroup = rule( def _experimental_link_std_dylib(ctx): return not is_exec_configuration(ctx) and \ - ctx.attr.experimental_link_std_dylib[BuildSettingInfo].value and \ + ctx.attr._link_std_dylib[BuildSettingInfo].value and \ ctx.attr.rust_std[rust_common.stdlib_info].std_dylib != None def _symlink_sysroot_tree(ctx, name, target, target_files = None): @@ -644,7 +644,7 @@ def _rust_toolchain_impl(ctx): _rename_first_party_crates = rename_first_party_crates, _third_party_dir = third_party_dir, _pipelined_compilation = pipelined_compilation, - _experimental_link_std_dylib = _experimental_link_std_dylib(ctx), + _link_std_dylib = _experimental_link_std_dylib(ctx), _experimental_use_cc_common_link = _experimental_use_cc_common_link(ctx), _experimental_use_global_allocator = experimental_use_global_allocator, _experimental_compile_rustdoc_tests = ctx.attr._experimental_compile_rustdoc_tests[BuildSettingInfo].value, @@ -721,10 +721,6 @@ rust_toolchain = rule( ), mandatory = True, ), - "experimental_link_std_dylib": attr.label( - default = Label("@rules_rust//rust/settings:experimental_link_std_dylib"), - doc = "Label to a boolean build setting that controls whether whether to link libstd dynamically.", - ), "experimental_use_allocator_libraries_with_mangled_symbols": attr.int( doc = ( "Whether to use rust-based allocator libraries with " + @@ -914,6 +910,10 @@ rust_toolchain = rule( default = Label("//rust/settings:incompatible_do_not_include_transitive_data_in_compile_inputs"), doc = "Label to a boolean build setting that controls whether to include transitive data dependencies in compile inputs.", ), + "_link_std_dylib": attr.label( + default = Label("@rules_rust//rust/settings:experimental_link_std_dylib"), + doc = "Label to a boolean build setting that controls whether whether to link libstd dynamically.", + ), "_linker_preference": attr.label( default = Label("//rust/settings:toolchain_linker_preference"), ), diff --git a/rust/rust_cdylib_library.bzl b/rust/rust_cdylib_library.bzl new file mode 100644 index 0000000000..c0f757220e --- /dev/null +++ b/rust/rust_cdylib_library.bzl @@ -0,0 +1,8 @@ +"""rust_cdylib_library""" + +load( + "//rust/private:rust.bzl", + _rust_cdylib_library = "rust_cdylib_library", +) + +rust_cdylib_library = _rust_cdylib_library diff --git a/rust/rust_dylib_library.bzl b/rust/rust_dylib_library.bzl new file mode 100644 index 0000000000..ea3f40d005 --- /dev/null +++ b/rust/rust_dylib_library.bzl @@ -0,0 +1,8 @@ +"""rust_dylib_library""" + +load( + "//rust/private:rust.bzl", + _rust_dylib_library = "rust_dylib_library", +) + +rust_dylib_library = _rust_dylib_library From 20aeb2eaa8cef60034722c5fee751feeb2ec1b4a Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Wed, 12 Aug 2026 10:34:56 -0700 Subject: [PATCH 05/16] Include Rust ABI dylib .so's as runfiles Includes the dylibs in runfiles even if in a non-cc toolchain, and also include the rpaths to dependency dylibs for all exeuction environments. Mirrors how rules_cc operates by supplying multiple RPATHs to support the varied execution environments. --- rust/private/rustc.bzl | 60 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index d656e22987..350d410c88 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -446,6 +446,11 @@ def get_linker_and_args(ctx, crate_type, toolchain, cc_toolchain, feature_config ld = toolchain.linker.path ld_is_direct_driver = toolchain.linker_type == "direct" + # Make sure we include RPATHs for Rust ABI dylibs even when no cc_toolchain. + if not cc_toolchain and rpaths: + for rpath in rpaths.to_list(): + link_args.append("-Wl,-rpath,$ORIGIN/" + rpath) + # When using rust-lld directly, we still need library search paths from cc_toolchain # to find system libraries that rustc's stdlib depends on (like -lgcc_s, -lutil, etc.) # Filter link_args to only include flags that help locate libraries. @@ -1311,7 +1316,7 @@ def construct_arguments( compilation_mode = compilation_mode, toolchain = toolchain, ) - rpaths = _compute_rpaths(toolchain, output_dir, dep_info, use_pic, link_std_dylib) + rpaths = _compute_rpaths(toolchain, output_dir, dep_info, use_pic, link_std_dylib, crate_info.output, ctx.workspace_name) else: rpaths = depset() @@ -2069,7 +2074,11 @@ def rustc_compile_action( runfiles = ctx.runfiles( files = getattr(ctx.files, "data", []) + - ([] if experimental_use_coverage_metadata_files else coverage_runfiles), + ([] if experimental_use_coverage_metadata_files else coverage_runfiles) + + # Include any generated Rust ABI dylibs as required runfiles. + ([crate_info.output] if crate_info.type == "dylib" else []) + + # Include the stdlib dylib when dynamically linking the standard library. + ([f for f in toolchain.rust_std.to_list() if is_std_dylib(f)] if link_std_dylib else []), ) transitive_runfiles = [] crate_attr = getattr(ctx.attr, "crate", None) @@ -2522,7 +2531,7 @@ def _process_build_scripts( depset(build_flags_files, transitive = [dep_info.link_search_path_files]), ) -def _compute_rpaths(toolchain, output_dir, dep_info, use_pic, link_std_dylib): +def _compute_rpaths(toolchain, output_dir, dep_info, use_pic, link_std_dylib, output_file = None, workspace_name = ""): """Determine the artifact's rpaths relative to the bazel root for runtime linking of shared libraries. Args: @@ -2531,6 +2540,8 @@ def _compute_rpaths(toolchain, output_dir, dep_info, use_pic, link_std_dylib): dep_info (DepInfo): The current target's dependency info use_pic: If set, prefers pic_static_library over static_library. link_std_dylib (bool): If the current target should link the stdlib as a dynamic library. + output_file (File): The output binary file, used for runfiles-tree RPATHs. + workspace_name (str): The workspace name, used for runfiles-tree RPATHs. Returns: depset: A set of relative paths from the output directory to each dependency @@ -2570,11 +2581,48 @@ def _compute_rpaths(toolchain, output_dir, dep_info, use_pic, link_std_dylib): dep_info.transitive_noncrates, )) - # Multiple dylibs can be present in the same directory, so deduplicate them. - return depset([ + # RPATHs must cover three execution scenarios: + # + # A) Binary runs from exec root (local, sandboxed, or as symlink). + # $ORIGIN is the exec-root dir. Exec-root-relative RPATHs resolve. + # + # B) Binary runs from exec root but dylibs are only in the runfiles tree. + # $ORIGIN is the exec-root dir, and the runfiles tree is at + # $ORIGIN/.runfiles/. + # + # C) Binary runs from inside the runfiles tree. $ORIGIN is inside + # .runfiles//pkg/. Short-path-relative RPATHs + # navigate within the runfiles tree. + + # (A) Exec-root RPATHs. + rpaths = [ relativize(lib_dir, output_dir) for lib_dir in _get_dir_names(dylibs) - ]) + ] + + if output_file and workspace_name: + # (B) Runfiles-from-outside RPATHs. + runfiles_base = output_file.basename + ".runfiles" + runfiles_dirs = {} + for f in dylibs: + short_dir = paths.dirname(f.short_path) + if f.short_path.startswith("../"): + # External repo: short_path is "..//", runfiles + # tree places it at "/" at the runfiles root. + runfiles_dirs[paths.join(runfiles_base, short_dir[3:])] = None + else: + # Main repo: runfiles tree places it under "/". + runfiles_dirs[paths.join(runfiles_base, workspace_name, short_dir)] = None + rpaths.extend(runfiles_dirs.keys()) + + # (C) Short-path RPATHs (binary running inside runfiles tree). + output_short_dir = paths.dirname(output_file.short_path) + for f in dylibs: + rpath = relativize(paths.dirname(f.short_path), output_short_dir) + if rpath not in rpaths: + rpaths.append(rpath) + + return depset(rpaths) def _get_dir_names(files): """Returns a list of directory names from the given list of File objects From 827755c6ceb3b6b1dd23d6178ca17767708f930c Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Thu, 13 Aug 2026 18:27:26 -0700 Subject: [PATCH 06/16] Add tests for dylibs as deps --- test/link_std_dylib/link_std_dylib_test.bzl | 82 +++++++++++++++++++++ test/rust_dylib/BUILD | 37 ++++++++++ test/rust_dylib/lib.rs | 5 ++ test/rust_dylib/main.rs | 9 +++ 4 files changed, 133 insertions(+) create mode 100644 test/rust_dylib/BUILD create mode 100644 test/rust_dylib/lib.rs create mode 100644 test/rust_dylib/main.rs diff --git a/test/link_std_dylib/link_std_dylib_test.bzl b/test/link_std_dylib/link_std_dylib_test.bzl index d118963e38..1292e4b54a 100644 --- a/test/link_std_dylib/link_std_dylib_test.bzl +++ b/test/link_std_dylib/link_std_dylib_test.bzl @@ -181,6 +181,86 @@ def _test_rust_test_with_attr(name): }, ) +def _has_runfiles_rpath(argv, binary_basename): + """Check that at least one RPATH entry points into the runfiles tree.""" + runfiles_marker = binary_basename + ".runfiles/" + for arg in argv: + if runfiles_marker in arg: + return True + return False + +def _has_short_path_rpath(argv): + """Check that at least one RPATH value uses short-path-relative navigation (for inside-runfiles execution).""" + for arg in argv: + if "../" in arg and ".runfiles" not in arg and "$ORIGIN" in arg: + return True + return False + +def _test_runfiles_rpath_impl(env, targets): + action = targets.binary_with_dylib_dep.actions[0] + binary_basename = targets.binary_with_dylib_dep.label.name + + # Verify runfiles-from-outside RPATHs are present (scenario B). + env.expect \ + .that_bool(_has_runfiles_rpath(action.argv, binary_basename)) \ + .equals(True) + +def _test_runfiles_rpath(name): + rust_dylib_library( + name = name + "_rust_dylib", + srcs = ["lib.rs"], + edition = "2021", + tags = ["manual"], + ) + + rust_binary( + name = name + "_rust_binary", + srcs = ["main.rs"], + deps = [name + "_rust_dylib"], + edition = "2021", + tags = ["manual"], + ) + + analysis_test( + name = name, + impl = _test_runfiles_rpath_impl, + targets = { + "binary_with_dylib_dep": name + "_rust_binary", + }, + ) + +def _test_std_dylib_runfiles_rpath_impl(env, targets): + action = targets.binary_with_std_dylib.actions[0] + binary_basename = targets.binary_with_std_dylib.label.name + + # Verify runfiles-from-outside RPATHs for stdlib are present (scenario B). + env.expect \ + .that_bool(_has_runfiles_rpath(action.argv, binary_basename)) \ + .equals(True) + + # Verify short-path RPATHs for stdlib are present (scenario C). + # The stdlib is from an external repo, so its short-path RPATH uses "../". + env.expect \ + .that_bool(_has_short_path_rpath(action.argv)) \ + .equals(True) + +def _test_std_dylib_runfiles_rpath(name): + rust_binary( + name = name + "_rust_binary", + srcs = ["main.rs"], + edition = "2021", + link_std_dylib = True, + tags = ["manual"], + ) + + analysis_test( + name = name, + impl = _test_std_dylib_runfiles_rpath_impl, + targets = { + "binary_with_std_dylib": name + "_rust_binary", + }, + ) + def link_std_dylib_test_suite(name): test_suite( name = name, @@ -190,5 +270,7 @@ def link_std_dylib_test_suite(name): _test_rust_binary_with_attr_dylib, _test_rust_dylib_with_attr, _test_rust_test_with_attr, + _test_runfiles_rpath, + _test_std_dylib_runfiles_rpath, ], ) diff --git a/test/rust_dylib/BUILD b/test/rust_dylib/BUILD new file mode 100644 index 0000000000..45321c64cc --- /dev/null +++ b/test/rust_dylib/BUILD @@ -0,0 +1,37 @@ +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_dylib_library", "rust_test") + +# Integration tests for rust_dylib_library: verifies that a binary depending +# on a Rust ABI dylib can be built and run, exercising runfiles inclusion +# and RPATH resolution end-to-end. + +NOT_WINDOWS = select({ + "@platforms//os:linux": [], + "@platforms//os:macos": [], + "//conditions:default": ["@platforms//:incompatible"], +}) + +rust_dylib_library( + name = "rust_dylib_lib", + srcs = ["lib.rs"], + edition = "2021", + link_std_dylib = True, + target_compatible_with = NOT_WINDOWS, +) + +rust_binary( + name = "dylib_dep_binary", + srcs = ["main.rs"], + deps = [":rust_dylib_lib"], + edition = "2021", + link_std_dylib = True, + target_compatible_with = NOT_WINDOWS, +) + +rust_test( + name = "dylib_dep_test", + srcs = ["main.rs"], + deps = [":rust_dylib_lib"], + edition = "2021", + link_std_dylib = True, + target_compatible_with = NOT_WINDOWS, +) diff --git a/test/rust_dylib/lib.rs b/test/rust_dylib/lib.rs new file mode 100644 index 0000000000..5252327577 --- /dev/null +++ b/test/rust_dylib/lib.rs @@ -0,0 +1,5 @@ +//! A library to be compiled as a dynamically linked library with the Rust ABI. + +pub fn example_test_dep_fn() -> u32 { + 1 +} diff --git a/test/rust_dylib/main.rs b/test/rust_dylib/main.rs new file mode 100644 index 0000000000..d096220215 --- /dev/null +++ b/test/rust_dylib/main.rs @@ -0,0 +1,9 @@ +//! An example binary that depends on a dynamically linked library. +//! +extern crate rust_dylib_lib; + +fn main() { + let val = rust_dylib_lib::example_test_dep_fn(); + assert_eq!(val, 1); + println!("dylib dep works: {}", val); +} From 699208ed1e5c12804a02ddaf815493280438ee13 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Thu, 13 Aug 2026 18:55:24 -0700 Subject: [PATCH 07/16] Update docs to reflect limitations of approach --- rust/private/rust.bzl | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index d0e74f3f7b..dfa5a5ebc9 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -162,8 +162,15 @@ def _rust_static_library_impl(ctx): def _rust_dylib_library_impl(ctx): """The implementation of the `rust_dylib_library` rule. - This rule provides CcInfo, so it can be used everywhere Bazel - expects rules_cc. + This rule provides CcInfo, so it can be used everywhere Bazel expects + rules_cc. + + **Note**: When dynamic libraries are listed as dependencies for other Rust + binaries they can induce errors from multiply defined symbols, causing + linker errors in rustc. Some libraries in the dependency graph may need to + be converted to dynamic libraries, and/or have the standard library + dynamically linked (`link_std_dylib`) to avoid this. These rules do not + attempt to resolve these linking issues automatically. Args: ctx (ctx): The rule's context object @@ -1134,15 +1141,21 @@ rust_dylib_library = rule( config_common.toolchain_type("@bazel_tools//tools/cpp:toolchain_type", mandatory = False), ], doc = dedent("""\ - Builds a Rust ABI shared library. + Builds a shared library using the unstable Rust ABI. - This shared library will contain all transitively reachable crates and native objects. - It is meant to be used when producing an artifact that is then consumed by some other build system - (for example to produce a shared library that Python program links against). + This library can be depended on by other Rust targets via --extern, + making it suitable for splitting a Rust project into separately compiled + dynamic libraries. Note that the Rust ABI is not stable across compiler + versions. This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`. - When building the whole binary in Bazel, use `rust_library` instead. + *Note**: When dynamic libraries are listed as dependencies for other Rust + binaries they can induce errors from multiply defined symbols, causing + linker errors in rustc. Some libraries in the dependency graph may need to + be converted to dynamic libraries, and/or have the standard library + dynamically linked (`link_std_dylib`) to avoid this. These rules do not + attempt to resolve these linking issues automatically. """), ) From e2b071d86707ae105a79587df97474dfcef193e1 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Thu, 13 Aug 2026 23:32:03 -0700 Subject: [PATCH 08/16] tweaks for formatting --- rust/private/rust.bzl | 2 +- test/rust_dylib/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index dfa5a5ebc9..8157fb6766 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -1150,7 +1150,7 @@ rust_dylib_library = rule( This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`. - *Note**: When dynamic libraries are listed as dependencies for other Rust + **Note**: When dynamic libraries are listed as dependencies for other Rust binaries they can induce errors from multiply defined symbols, causing linker errors in rustc. Some libraries in the dependency graph may need to be converted to dynamic libraries, and/or have the standard library diff --git a/test/rust_dylib/main.rs b/test/rust_dylib/main.rs index d096220215..21633aafc0 100644 --- a/test/rust_dylib/main.rs +++ b/test/rust_dylib/main.rs @@ -1,5 +1,5 @@ //! An example binary that depends on a dynamically linked library. -//! + extern crate rust_dylib_lib; fn main() { From df9ca0be53cdb57f2c1a8a8af49774aa11cc8471 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Thu, 13 Aug 2026 23:48:11 -0700 Subject: [PATCH 09/16] Mark RPATH-specific tests as linux/mac compatible RPATH specific testing is not relevant for windows, where RPATHs don't even exist as a concept. Also existing RPATH tests should handle the different path structure followed by MacOS. --- test/link_std_dylib/link_std_dylib_test.bzl | 24 +++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/test/link_std_dylib/link_std_dylib_test.bzl b/test/link_std_dylib/link_std_dylib_test.bzl index 1292e4b54a..68a12a98b9 100644 --- a/test/link_std_dylib/link_std_dylib_test.bzl +++ b/test/link_std_dylib/link_std_dylib_test.bzl @@ -182,17 +182,17 @@ def _test_rust_test_with_attr(name): ) def _has_runfiles_rpath(argv, binary_basename): - """Check that at least one RPATH entry points into the runfiles tree.""" + """Check that at least one link arg contains a path into the runfiles tree.""" runfiles_marker = binary_basename + ".runfiles/" for arg in argv: - if runfiles_marker in arg: + if "link-arg=" in arg and runfiles_marker in arg: return True return False def _has_short_path_rpath(argv): - """Check that at least one RPATH value uses short-path-relative navigation (for inside-runfiles execution).""" + """Check that at least one link arg uses short-path-relative navigation (for inside-runfiles execution).""" for arg in argv: - if "../" in arg and ".runfiles" not in arg and "$ORIGIN" in arg: + if "link-arg=" in arg and "../" in arg and ".runfiles" not in arg: return True return False @@ -200,17 +200,25 @@ def _test_runfiles_rpath_impl(env, targets): action = targets.binary_with_dylib_dep.actions[0] binary_basename = targets.binary_with_dylib_dep.label.name - # Verify runfiles-from-outside RPATHs are present (scenario B). + # Verify runfiles-from-outside RPATHs are present. env.expect \ .that_bool(_has_runfiles_rpath(action.argv, binary_basename)) \ .equals(True) +def _not_windows(): + return select({ + "@platforms//os:linux": [], + "@platforms//os:macos": [], + "//conditions:default": ["@platforms//:incompatible"], + }) + def _test_runfiles_rpath(name): rust_dylib_library( name = name + "_rust_dylib", srcs = ["lib.rs"], edition = "2021", tags = ["manual"], + target_compatible_with = _not_windows(), ) rust_binary( @@ -219,6 +227,7 @@ def _test_runfiles_rpath(name): deps = [name + "_rust_dylib"], edition = "2021", tags = ["manual"], + target_compatible_with = _not_windows(), ) analysis_test( @@ -233,12 +242,12 @@ def _test_std_dylib_runfiles_rpath_impl(env, targets): action = targets.binary_with_std_dylib.actions[0] binary_basename = targets.binary_with_std_dylib.label.name - # Verify runfiles-from-outside RPATHs for stdlib are present (scenario B). + # Verify runfiles-from-outside RPATHs for stdlib are present. env.expect \ .that_bool(_has_runfiles_rpath(action.argv, binary_basename)) \ .equals(True) - # Verify short-path RPATHs for stdlib are present (scenario C). + # Verify short-path RPATHs for stdlib are present. # The stdlib is from an external repo, so its short-path RPATH uses "../". env.expect \ .that_bool(_has_short_path_rpath(action.argv)) \ @@ -251,6 +260,7 @@ def _test_std_dylib_runfiles_rpath(name): edition = "2021", link_std_dylib = True, tags = ["manual"], + target_compatible_with = _not_windows(), ) analysis_test( From 8456bf7bae21b25e2ff83139b437a36fc6c16b58 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Fri, 14 Aug 2026 00:05:34 -0700 Subject: [PATCH 10/16] Add support for windows, which has no RPATHs --- rust/private/rustc.bzl | 21 ++++++++-- test/link_std_dylib/link_std_dylib_test.bzl | 45 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 350d410c88..f00201e552 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -2092,16 +2092,29 @@ def rustc_compile_action( continue for target in runfiles_attr: transitive_runfiles.append(target[DefaultInfo].default_runfiles) - if crate_info.type in ["bin", "cdylib", "staticlib"]: - dynamic_libraries = ctx.runfiles(files = [ + dep_dylib_files = [] + if crate_info.type in ["bin", "cdylib", "dylib", "staticlib"]: + dep_dylib_files = [ library_to_link.dynamic_library for dep in getattr(ctx.attr, "deps", []) if CcInfo in dep for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list() for library_to_link in linker_input.libraries if _is_dylib(library_to_link) and library_to_link.dynamic_library - ]) - transitive_runfiles.append(dynamic_libraries) + ] + transitive_runfiles.append(ctx.runfiles(files = dep_dylib_files)) + + # On Windows there is no RPATH equivalent. Create symlinks of dylib files + # next to the binary so the Windows loader can find them. + if toolchain.target_os == "windows" and (crate_info.type == "bin" or crate_info.is_test): + win_dylibs = list(dep_dylib_files) + if link_std_dylib: + win_dylibs.extend([f for f in toolchain.rust_std.to_list() if is_std_dylib(f)]) + for dylib in win_dylibs: + symlink = ctx.actions.declare_file(dylib.basename, sibling = crate_info.output) + ctx.actions.symlink(output = symlink, target_file = dylib) + outputs.append(symlink) + runfiles = runfiles.merge_all(transitive_runfiles) executable = crate_info.output if crate_info.type == "bin" or crate_info.is_test else None diff --git a/test/link_std_dylib/link_std_dylib_test.bzl b/test/link_std_dylib/link_std_dylib_test.bzl index 68a12a98b9..cbf1ef9300 100644 --- a/test/link_std_dylib/link_std_dylib_test.bzl +++ b/test/link_std_dylib/link_std_dylib_test.bzl @@ -271,6 +271,50 @@ def _test_std_dylib_runfiles_rpath(name): }, ) +def _has_dylib_symlink_next_to_binary(target, dylib_dep): + """Check that a symlink of the dylib exists next to the binary in DefaultInfo.files.""" + dylib_basename = dylib_dep.label.name + binary_dir = target.label.package + for f in target[DefaultInfo].files.to_list(): + if f.basename.startswith("lib" + dylib_basename) and f.short_path.startswith(binary_dir): + return True + return False + +def _test_windows_dylib_symlink_impl(env, targets): + env.expect \ + .that_bool(_has_dylib_symlink_next_to_binary( + targets.binary_with_dylib_dep, + targets.dylib_dep, + )) \ + .equals(True) + +def _test_windows_dylib_symlink(name): + rust_dylib_library( + name = name + "_rust_dylib", + srcs = ["lib.rs"], + edition = "2021", + tags = ["manual"], + target_compatible_with = ["@platforms//os:windows"], + ) + + rust_binary( + name = name + "_rust_binary", + srcs = ["main.rs"], + deps = [name + "_rust_dylib"], + edition = "2021", + tags = ["manual"], + target_compatible_with = ["@platforms//os:windows"], + ) + + analysis_test( + name = name, + impl = _test_windows_dylib_symlink_impl, + targets = { + "binary_with_dylib_dep": name + "_rust_binary", + "dylib_dep": name + "_rust_dylib", + }, + ) + def link_std_dylib_test_suite(name): test_suite( name = name, @@ -282,5 +326,6 @@ def link_std_dylib_test_suite(name): _test_rust_test_with_attr, _test_runfiles_rpath, _test_std_dylib_runfiles_rpath, + _test_windows_dylib_symlink, ], ) From 663af415b120747b9a9f3feacc88b2a8b2ca4d2d Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Fri, 14 Aug 2026 00:11:38 -0700 Subject: [PATCH 11/16] Avoid double symlinks --- rust/private/rustc.bzl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index f00201e552..bd6f8533d1 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -2111,9 +2111,10 @@ def rustc_compile_action( if link_std_dylib: win_dylibs.extend([f for f in toolchain.rust_std.to_list() if is_std_dylib(f)]) for dylib in win_dylibs: - symlink = ctx.actions.declare_file(dylib.basename, sibling = crate_info.output) - ctx.actions.symlink(output = symlink, target_file = dylib) - outputs.append(symlink) + if dylib.dirname != crate_info.output.dirname: + symlink = ctx.actions.declare_file(dylib.basename, sibling = crate_info.output) + ctx.actions.symlink(output = symlink, target_file = dylib) + outputs.append(symlink) runfiles = runfiles.merge_all(transitive_runfiles) From f53b392b463f13e07630476333f81580392775d6 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Fri, 14 Aug 2026 00:20:40 -0700 Subject: [PATCH 12/16] Repair windows test, update MacOS RPATH discovery --- rust/private/rustc.bzl | 5 +++++ test/link_std_dylib/link_std_dylib_test.bzl | 12 ++---------- test/rust_dylib/BUILD | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index bd6f8533d1..e96a00728e 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -1360,6 +1360,11 @@ def construct_arguments( include_link_flags = include_link_flags, ) + # On macOS, set the dylib install name to @rpath/ so that + # consumers find it via RPATH rather than the exec-root build path. + if crate_info.type == "dylib" and toolchain.target_os in ["macos", "darwin"]: + rustc_flags.add("--codegen=link-arg=-Wl,-install_name,@rpath/" + crate_info.output.basename) + use_metadata = _depend_on_metadata(crate_info, force_depend_on_objects) # These always need to be added, even if not linking this crate. diff --git a/test/link_std_dylib/link_std_dylib_test.bzl b/test/link_std_dylib/link_std_dylib_test.bzl index cbf1ef9300..5c001abb25 100644 --- a/test/link_std_dylib/link_std_dylib_test.bzl +++ b/test/link_std_dylib/link_std_dylib_test.bzl @@ -289,18 +289,10 @@ def _test_windows_dylib_symlink_impl(env, targets): .equals(True) def _test_windows_dylib_symlink(name): - rust_dylib_library( - name = name + "_rust_dylib", - srcs = ["lib.rs"], - edition = "2021", - tags = ["manual"], - target_compatible_with = ["@platforms//os:windows"], - ) - rust_binary( name = name + "_rust_binary", srcs = ["main.rs"], - deps = [name + "_rust_dylib"], + deps = ["//test/rust_dylib:rust_dylib_lib"], edition = "2021", tags = ["manual"], target_compatible_with = ["@platforms//os:windows"], @@ -311,7 +303,7 @@ def _test_windows_dylib_symlink(name): impl = _test_windows_dylib_symlink_impl, targets = { "binary_with_dylib_dep": name + "_rust_binary", - "dylib_dep": name + "_rust_dylib", + "dylib_dep": "//test/rust_dylib:rust_dylib_lib", }, ) diff --git a/test/rust_dylib/BUILD b/test/rust_dylib/BUILD index 45321c64cc..9e0d713b1b 100644 --- a/test/rust_dylib/BUILD +++ b/test/rust_dylib/BUILD @@ -15,7 +15,7 @@ rust_dylib_library( srcs = ["lib.rs"], edition = "2021", link_std_dylib = True, - target_compatible_with = NOT_WINDOWS, + visibility = ["//test:__subpackages__"], ) rust_binary( From 8ffb4ca791986408f52544fdca4cb82ed76af207 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Fri, 14 Aug 2026 00:32:23 -0700 Subject: [PATCH 13/16] Simplify windows test assertions somewhat --- test/link_std_dylib/link_std_dylib_test.bzl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/link_std_dylib/link_std_dylib_test.bzl b/test/link_std_dylib/link_std_dylib_test.bzl index 5c001abb25..8d09d96082 100644 --- a/test/link_std_dylib/link_std_dylib_test.bzl +++ b/test/link_std_dylib/link_std_dylib_test.bzl @@ -272,11 +272,10 @@ def _test_std_dylib_runfiles_rpath(name): ) def _has_dylib_symlink_next_to_binary(target, dylib_dep): - """Check that a symlink of the dylib exists next to the binary in DefaultInfo.files.""" - dylib_basename = dylib_dep.label.name - binary_dir = target.label.package + """Check that a dylib output exists next to the binary in DefaultInfo.files.""" + dep_name = dylib_dep.label.name for f in target[DefaultInfo].files.to_list(): - if f.basename.startswith("lib" + dylib_basename) and f.short_path.startswith(binary_dir): + if dep_name in f.basename and (f.basename.endswith(".dll") or f.basename.endswith(".so") or f.basename.endswith(".dylib")): return True return False From 8da0e1439c528e0a27eba998f37096b54e713b90 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Mon, 24 Aug 2026 14:43:53 -0700 Subject: [PATCH 14/16] Swap to +1/0/-1 flag for link_std_dylib --- rust/private/rust.bzl | 4 ++-- rust/private/toolchain.bzl | 22 ++++++++++++++++++--- test/link_std_dylib/link_std_dylib_test.bzl | 5 +++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index 8157fb6766..6d17836c61 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -1126,11 +1126,11 @@ rust_dylib_library = rule( ), "link_std_dylib": attr.bool( mandatory = False, - default = False, + default = True, doc = dedent("""\ Flag to dynamically link the standard library as a Rust dylib .so object when building this library. - Default is false. This is typically required for Rust ABI dylibs so that the stdlib is shared + Default is true. This is typically required for Rust ABI dylibs so that the stdlib is shared with the binary that loads them, avoiding duplicate symbols. """), ), diff --git a/rust/private/toolchain.bzl b/rust/private/toolchain.bzl index d96b645aee..7840c629e6 100644 --- a/rust/private/toolchain.bzl +++ b/rust/private/toolchain.bzl @@ -121,8 +121,13 @@ rust_stdlib_filegroup = rule( ) def _experimental_link_std_dylib(ctx): + per_toolchain = ctx.attr.link_std_dylib + if per_toolchain == -1: + enabled = ctx.attr._link_std_dylib_setting[BuildSettingInfo].value + else: + enabled = bool(per_toolchain) return not is_exec_configuration(ctx) and \ - ctx.attr._link_std_dylib[BuildSettingInfo].value and \ + enabled and \ ctx.attr.rust_std[rust_common.stdlib_info].std_dylib != None def _symlink_sysroot_tree(ctx, name, target, target_files = None): @@ -910,9 +915,20 @@ rust_toolchain = rule( default = Label("//rust/settings:incompatible_do_not_include_transitive_data_in_compile_inputs"), doc = "Label to a boolean build setting that controls whether to include transitive data dependencies in compile inputs.", ), - "_link_std_dylib": attr.label( + "link_std_dylib": attr.int( + doc = ( + "Whether to link libstd dynamically. Possible values: [-1, 0, 1]. " + + "-1 means to use the value of the build setting " + + "//rust/settings:experimental_link_std_dylib. " + + "0 means do not link libstd dynamically. " + + "1 means link libstd dynamically." + ), + values = [-1, 0, 1], + default = -1, + ), + "_link_std_dylib_setting": attr.label( default = Label("@rules_rust//rust/settings:experimental_link_std_dylib"), - doc = "Label to a boolean build setting that controls whether whether to link libstd dynamically.", + doc = "Label to a boolean build setting that controls whether to link libstd dynamically.", ), "_linker_preference": attr.label( default = Label("//rust/settings:toolchain_linker_preference"), diff --git a/test/link_std_dylib/link_std_dylib_test.bzl b/test/link_std_dylib/link_std_dylib_test.bzl index 8d09d96082..f35137a4e8 100644 --- a/test/link_std_dylib/link_std_dylib_test.bzl +++ b/test/link_std_dylib/link_std_dylib_test.bzl @@ -70,10 +70,11 @@ def _test_rust_binary_with_attr_dylib(name): def _test_rust_dylib_with_attr(name): rust_dylib_library( - name = name + "_rust_dylib", + name = name + "_rust_dylib_no_std_dylib", srcs = ["lib.rs"], edition = "2021", tags = ["manual"], + link_std_dylib = False, ) rust_dylib_library( @@ -88,7 +89,7 @@ def _test_rust_dylib_with_attr(name): name = name, impl = _test_prefer_dynamic_impl, targets = { - "default_target": name + "_rust_dylib", + "default_target": name + "_rust_dylib_no_std_dylib", "target_with_std_dylib": name + "_rust_dylib_link_std", }, ) From 315ada9513d4322784bf7cd0d54b1103b1317cf1 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Mon, 24 Aug 2026 16:13:52 -0700 Subject: [PATCH 15/16] Update docs, add comment for symlinking shenanigans --- docs/BUILD.bazel | 18 ++++++++++++++++++ docs/src/SUMMARY.md | 2 ++ docs/src/rust.md | 6 +++++- rust/private/rustc.bzl | 3 ++- rust/rust_shared_library.bzl | 4 ++-- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index d1339df731..26e906f3b2 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -96,6 +96,22 @@ stardoc( deps = [":all_docs"], ) +stardoc( + name = "rust_cdylib_library", + out = "src/rust_cdylib_library.md", + input = "@rules_rust//rust:rust_cdylib_library.bzl", + symbol_names = ["rust_cdylib_library"], + deps = [":all_docs"], +) + +stardoc( + name = "rust_dylib_library", + out = "src/rust_dylib_library.md", + input = "@rules_rust//rust:rust_dylib_library.bzl", + symbol_names = ["rust_dylib_library"], + deps = [":all_docs"], +) + stardoc( name = "rust_shared_library", out = "src/rust_shared_library.md", @@ -345,12 +361,14 @@ mdbook( ":rust_analyzer_toolchain", ":rust_binary", ":rust_bindgen", + ":rust_cdylib_library", ":rust_bzlmod", ":rust_clippy", ":rust_clippy_aspect", ":rust_clippy_test", ":rust_doc", ":rust_doc_test", + ":rust_dylib_library", ":rust_library", ":rust_library_group", ":rust_lint_config", diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index ed984eaee0..4224c1031c 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -10,6 +10,8 @@ - [rust_library](./rust_library.md) - [rust_library_group](./rust_library_group.md) - [rust_lint_config](./rust_lint_config.md) + - [rust_cdylib_library](./rust_cdylib_library.md) + - [rust_dylib_library](./rust_dylib_library.md) - [rust_proc_macro](./rust_proc_macro.md) - [rust_shared_library](./rust_shared_library.md) - [rust_static_library](./rust_static_library.md) diff --git a/docs/src/rust.md b/docs/src/rust.md index 0ca031a7da..99927458c9 100644 --- a/docs/src/rust.md +++ b/docs/src/rust.md @@ -18,9 +18,13 @@ directly to the crate types Cargo produces. other Rust targets can depend on. - [rust_static_library](./rust_static_library.md) — Build a `staticlib` (`--crate-type=staticlib`) for linking Rust code into a C/C++ binary. -- [rust_shared_library](./rust_shared_library.md) — Build a `cdylib` +- [rust_cdylib_library](./rust_cdylib_library.md) — Build a `cdylib` (`--crate-type=cdylib`) for use as a shared library from C/C++ or other languages. +- [rust_dylib_library](./rust_dylib_library.md) — Build a `dylib` + (`--crate-type=dylib`) for use as a shared library with the unstable Rust ABI. +- [rust_shared_library](./rust_shared_library.md) — Convenience alias over + `rust_cdylib_library`. - [rust_proc_macro](./rust_proc_macro.md) — Build a procedural macro crate (`--crate-type=proc-macro`) that other Rust targets can consume as a compile-time plugin. diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 4685680353..0f01c709a7 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -2122,7 +2122,8 @@ def rustc_compile_action( transitive_runfiles.append(ctx.runfiles(files = dep_dylib_files)) # On Windows there is no RPATH equivalent. Create symlinks of dylib files - # next to the binary so the Windows loader can find them. + # next to the binary so the Windows loader can find them. Bazel deduplicates + # identical symlink actions when multiple binaries in a package share a dylib dep. if toolchain.target_os == "windows" and (crate_info.type == "bin" or crate_info.is_test): win_dylibs = list(dep_dylib_files) if link_std_dylib: diff --git a/rust/rust_shared_library.bzl b/rust/rust_shared_library.bzl index 665d85699e..2ef86f3df8 100644 --- a/rust/rust_shared_library.bzl +++ b/rust/rust_shared_library.bzl @@ -2,7 +2,7 @@ load( "//rust/private:rust.bzl", - _rust_shared_library = "rust_shared_library", + _rust_cdylib_library = "rust_cdylib_library", ) -rust_shared_library = _rust_shared_library +rust_shared_library = _rust_cdylib_library From e4365f95ce0cd3afd7dd906a14ec0b11a50341c0 Mon Sep 17 00:00:00 2001 From: Jonathan Bunton Date: Mon, 24 Aug 2026 16:35:56 -0700 Subject: [PATCH 16/16] Migrate from experimental_ naming where possible --- rust/private/toolchain.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/private/toolchain.bzl b/rust/private/toolchain.bzl index f5848523ba..c45a350ce4 100644 --- a/rust/private/toolchain.bzl +++ b/rust/private/toolchain.bzl @@ -120,7 +120,7 @@ rust_stdlib_filegroup = rule( }, ) -def _experimental_link_std_dylib(ctx): +def _resolve_link_std_dylib(ctx): per_toolchain = ctx.attr.link_std_dylib if per_toolchain == -1: enabled = ctx.attr._link_std_dylib_setting[BuildSettingInfo].value @@ -649,7 +649,7 @@ def _rust_toolchain_impl(ctx): _rename_first_party_crates = rename_first_party_crates, _third_party_dir = third_party_dir, _pipelined_compilation = pipelined_compilation, - _link_std_dylib = _experimental_link_std_dylib(ctx), + _link_std_dylib = _resolve_link_std_dylib(ctx), _experimental_use_cc_common_link = _experimental_use_cc_common_link(ctx), _experimental_use_global_allocator = experimental_use_global_allocator, _experimental_compile_rustdoc_tests = ctx.attr._experimental_compile_rustdoc_tests[BuildSettingInfo].value,