Add support for Rust ABI dynamic libraries - #4179
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
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`.
Updates dylib, binary, and test targets to have an additional flag for explicitly enabling the dynamic linking of the stdlib without a toolchain transition.
ead702e to
df80dac
Compare
df80dac to
9ea36c6
Compare
UebelAndre
left a comment
There was a problem hiding this comment.
I'd love @krasimirgg to take a look since I think there's some google uses of experimental_link_std_dylib. The change makes sense to me but I wonder if link_std_dylib should have more impact outside of just the allocators and codegen flag. The attribute sounds like it would directly link the dylibs but I don't think there's any wiring for that. Maybe @krasimirgg can correct me if I'm wrong
| 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 |
There was a problem hiding this comment.
Maybe rename these attributes on the toolchain as well?
|
Hey, I'm not an expert on dynamic linking, so please bear with me...
, you'll likely get rustc error: cannot satisfy dependencies so d only shows up once while compiling a. Example rustc commands I'm thinking about -- https://gist.github.com/krasimirgg/66c52fbebee7af4813e1e71af495bd4a. Tangent 1: On a high-level, I see a way to set up rust dynamic libraries support in a way similar to how rules_cc does dynamic libraries -- the rust_library itself has actions that produce both the rlib and the dylib, and clients pick up the appropriate dependency based on the --dynamic_mode build setting. This is composable, in particular any transitive dependencies of a dylib are automatically dylibs, avoiding the case where duplicated common dependencies render intermediate dylibs incompatible. But in a world like this, having a separate rust_dylib_library rule becomes just something almost equivalent to a rust_library. Tangent 2: The reason why I feel this is different than rust_shared_library (cdylib) is that that one is designed to act like a final artifact -- you collect all of your rust into a single such target, which acts more like a final binary; standard rust libraries cannot direclty depend on it and you just hook it up as a special dependency of the final binary. |
I would expect Bazel to bundle the
What makes the
I'm not sure it's always desirable to turn everything into dynamic libraries globally like this. Lets say you had a
I agree |
|
Thanks for thinking about this @krasimirgg ! Some thoughts:
Bazel should provide the
I feel like this is a limitation of dynamic linking with This is definitely a possible footgun though--asking for dynamic linking at some point in the chain might imply other libraries should become dynamically linked. It's not required if no dependency diamonds like this occur, though.
I think this is very cool, and could be something to build toward. In my use case, I wanted to avoid forcing every transitive dep to be unconditionally built as both an rlib and a dylib, when for many steps in that graph, only the rlib version was required (in my simpler use case, I had a deep dependencies with a dylib at the focal interface point). Instead, this lets me mark only the required targets as dylibs to save compilation time. |
|
@krasimirgg friendly ping here |
|
Hey sorry, was a bit out of office, replies below.
Here, both of the dylibs-s
I think the introduction of this rule is OK as long as we put some big disclaimers around these limitations and document this rule so whenever some user tries using it and linking fails, we can point to that. About the tangent, just informationally:
We've started internally poking around adding full |
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.
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.
|
Hey, no problem--thanks again for the time!
I'm 100% with you here. I added some language in the docs about this, let me know if it needs revising though. I made a few other improvements to help support this PR:
Also for your tangent:
This sounds awesome. If I can support at all in the future I'd be stoked to! |
|
cc @krasimirgg |
|
Hey @buntonj , we chatted a bit with @UebelAndre last week about this, who would also follow-up on this review. The whole thing is because of the above concerns so we should tread carefully with the whole feature. |
| mandatory = False, | ||
| default = False, | ||
| doc = dedent("""\ | ||
| Flag to dynamically link the standard library as a Rust dylib .so object when building this test. |
There was a problem hiding this comment.
don't we also need this for rust_binary?
| crates will instead use the `.rlib` file. | ||
| """), | ||
| ), | ||
| "link_std_dylib": attr.bool( |
There was a problem hiding this comment.
Do we have use cases at all for rust_dylib_library where we don't wanna link the stdlibs dynamically? Naively, for this rule the default should be true?
There was a problem hiding this comment.
Yeah, that's totally fair. AFAICT, Rust ABI dylibs will just be depended on by Rust targets, which would require link_std_dylib anywho, so setting the default to true is harmless. Done!
| ), | ||
| mandatory = True, | ||
| ), | ||
| "experimental_link_std_dylib": attr.label( |
There was a problem hiding this comment.
I think we're losing something with this change -- previously, it was possible to have 2 rust_toolchain-s where one has this enabled and the other doesn't. With the new version, all possible toolchains use same default. I think we should switch to a pattern where _link_std_dylib points to a label as in the new version, but there is an optional per-toolchain override with values [-1, 0, 1] as in experimental_use_allocator_libraries_with_mangled_symbols.
There was a problem hiding this comment.
Ah, great catch and suggestion. Migrated to using the same [-1, 0, 1] pattern, which leaves us with the ability to toolchain-level or target-level change this flag.
|
|
||
| # 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. |
There was a problem hiding this comment.
(just informational): I personally am not familiar with the RPATH feature and ways it's working in bazel. I'm aiming to read up on this, but currently I can't vouch for those parts.
UebelAndre
left a comment
There was a problem hiding this comment.
Looking good so far! Can you also make sure we add a section for rust_dylib_library and rust_cdylib_library to docs with this change?
| ) | ||
|
|
||
| if experimental_link_std_dylib: | ||
| if link_std_dylib: |
There was a problem hiding this comment.
Is this flag no longer experimental now?
There was a problem hiding this comment.
Ah, less that it's not experimental and more that it's now a computed quantity/property as a function of the toolchain-level experimental_link_std_dylib and the target-level link_std_dylib.
I opted to try and rename things so that the experimental_link_std_dylib hook and name are the same, but all internal customers/byproducts of the new link_std_dylib are not experimental_ anymore.
|
|
||
| # 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): |
There was a problem hiding this comment.
I'm concerned this will lead to conflicting action outputs. Do you know what rules_cc does here?
There was a problem hiding this comment.
I actually borrowed this pattern from rules_cc's _create_dynamic_libraries_copy_actions. See here and here.
Bazel handles the deduplication of targets in the relevant cases also.
There was a problem hiding this comment.
(I added a comment in this spot to this effect so we remember! For those who come after...)
Currently, there is not explicit stable Rust ABI for dynamic libraries, but Rust library may still be compiled as one. This can be useful if a large project wants to compile some core portion of its code as a shared object that can be dynamically linked against several different binaries. Bevy, for example, offers this capability as a way to allow customers of Bevy to tweak their own code without having to statically re-link the central Bevy engine code.
This PR exposes the Rust ABI dylib capabilities with a new rule,
rust_dylib_library. For clarity, it also creates arust_cylib_libraryrule, and updates methods to this name, withrust_shared_libraryjust aliasing to therust_cdylib_libraryimplementation.Building a Rust ABI dylib may cause the standard library to be linked dynamically against the generated dylib. When depended on, the dynamic standard library needs to be included to get the binary to execute. Currently,
rules_rustoffers this as a toolchain-level flag, but applying a toolchain transition at the dylib's interface imposes a new toolchain on its deps, which then forces any upstreamrlibs, for example, to recompile.To fix this, this PR exposes a
link_std_dylibattr torust_binary,rust_dylib_libraryandrust_testto allow these targets to optionally bundle themselves with the dynamically-linked stdlib.