Preserve native final-link requirements through rust_static_library - #4232
Merged
UebelAndre merged 1 commit intoAug 19, 2026
Merged
Conversation
cerisier
marked this pull request as ready for review
August 16, 2026 04:46
UebelAndre
approved these changes
Aug 19, 2026
UebelAndre
left a comment
Collaborator
There was a problem hiding this comment.
Thank you for the links and docs to upstream rustc behavior! This looks good to me 😄
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Preserve native final-link requirements when a
rust_static_libraryis consumed by C or C++.The existing
CcInfoconstruction correctly avoids propagating dependency static archives that rustc has already bundled into the Rust staticlib. However, it rebuilds each dependencyLinkerInputusing only dynamic libraries, which also dropsuser_link_flagsandadditional_inputs. Those values cannot be encoded in a.aarchive and are still required by the eventual foreign-language final link.Rustc behavior being ported
This change maps rustc's staticlib orchestration onto Bazel's linking-provider model rather than inventing new linkage behavior.
At rust revision
67854e511de21d881bb16426996cd4259d44aa2e:link_staticlibdescribes a staticlib as an archive containing upstream objects and bundled native libraries, while noting that dynamic libraries cannot be linked into the archive.print_native_static_libsconverts the requirements that were not bundled into platform-appropriate final-link arguments, including native dynamic/unspecified libraries, unbundled static libraries, frameworks, and Rust dylibs.The Rust Reference describes the same contract: a
staticlibcontains local and upstream Rust code, but its system and dynamic dependencies must be supplied when another linker consumes it.--print=native-static-libsexists to communicate those requirements: https://doc.rust-lang.org/reference/linkage.html#static-and-dynamic-runtimesRust tests this behavior directly:
print-native-static-libsbuilds an upstream rlib and a staticlib, then asserts that--print=native-static-libscontains native requirements from both the current crate and the transitive crate, including command-line-larguments.staticlib-dylib-linkagetakes the emitted native link arguments, passes them to a C compiler together with the Rust staticlib, and runs the resulting executable.Mapping to Bazel
Rustc and Bazel represent the same boundary differently:
LibraryToLinkvalues out of the exportedCcInfo; they are already in the Rust archiveLibraryToLinkvalues and dependencyuser_link_flags--print=native-static-libsrust_static_librarytarget'sCcInfoadditional_inputs, such as linker scripts, so the final action remains hermeticBazel defines
LinkerInputas the libraries, flags, and other files passed to a linker, and definesadditional_linker_inputsas files required specifically by the link action: https://bazel.build/rules/lib/builtins/LinkerInput and https://bazel.build/reference/be/c-cppThe implementation preserves the complete
user_link_flagsfield instead of parsing only-lflags. Rustc has semanticNativeLibKindinformation when producing its list; rules_rust receives platform- and toolchain-encoded Bazel linker flags. Parsing those strings here would duplicate C++ toolchain behavior and mishandle constructs such as frameworks, MSVC options, library search paths, and linker scripts.Root cause
_collect_nonstatic_linker_inputswas shared bystaticlibandcdylib. It reconstructed dependencyLinkerInputvalues with only non-static libraries:LibraryToLinkvalues is correct for both outputs because those archives were consumed while creating the Rust output.user_link_flagsandadditional_inputsis correct forcdylib, whose final link has already consumed them.staticlibis incorrect because archive creation is not the foreign final link.Consequently, a native dependency such as
cc_library(linkopts = ["-lresolv"])could be used while compiling a Rust staticlib, but its requirement disappeared from theCcInfoseen by a downstreamcc_binaryorcc_test.Change
user_link_flagsandadditional_inputsforstaticlibonly.cdylibpropagation unchanged.libresolv.Proof
On current upstream
main, with only the new analysis fixture applied, the regression fails because the finalCppLinkaction contains neither-L/doesnotexistnorempty.so. With the implementation change applied, the same test passes and confirms that the dependency static archive remains excluded.The Linux regression is:
The Rust staticlib exports a function returning the address of
ns_initparse; the C++ test references that function, forcing the relevant Rust object out of the archive.The same production change was additionally tested in the hermeticbuild fork with its hermetic LLVM C/C++ toolchain. With the fixture but without the fix, the Linux x86_64 link fails:
The archive contains
U ns_initparse, confirming that the system dependency was not bundled. With the fix, the final C++ link contains-lresolv, succeeds, and produces an x86_64 ELF recording:The undefined dynamic symbol is expected because
libresolv.so.2is now recorded as the provider that resolves it at load time.Tests
bazel test //test/unit/linker_inputs_propagation:linker_inputs_propagation_test_suiteBaseline with test fixture only: the new regression fails. With this change: two analysis tests pass and four platform-incompatible tests are skipped on macOS.
Additional Linux x86_64 cross-link verification in the hermeticbuild fork:
The Linux executable was cross-linked from a macOS host and inspected as an ELF; it was not executed on that host.