TheBevyFlock / bevy_cli

A Bevy CLI tool.
Apache License 2.0
32 stars 6 forks source link

Hardcode path to `librustc_driver.so` in `bevy_lint_driver` for dynamic linking #66

Closed BD103 closed 1 month ago

BD103 commented 1 month ago

For a story-driven rant on how I came across this solution, see this chain of posts.


Hi! I spent a lot of my time drafting #32 trying to fix bevy_lint_driver's linking issues, but I've finally found a solution that works most of the time.

The problem

bevy_lint_driver, the program that actually registers the lints into the compiler, dynamically links to librustc_driver-*.so. This is perfectly fine with running it through Cargo and the RUSTC_WORKSPACE_WRAPPER environmental variable, but falls apart when run by itself.

Namely, calling ./target/debug/bevy_lint_driver directly fails with a dynamic linking error:

$ ./target/debug/bevy_lint_driver 
dyld[88854]: Library not loaded: @rpath/librustc_driver-a8e6b68b3c38c57d.dylib
  Referenced from: <72ACE28D-39EC-39CC-833A-92B4F3AC0DF5> ./target/debug/bevy_lint_driver
  Reason: no LC_RPATH's found
Abort trap: 6

When running it through Cargo, it sets the LD_LIBRARY_PATH variable so that the dynamic linker can discover librustc_driver.so. This variable is present, unfortunately, when running it alone.

This becomes a problem for two of my use-cases of the driver:

  1. Debugging it using LLDB. rustc is a new codebase that I've never worked on before, I'm using a debugger to figure out what code gets run.
  2. Running UI tests. ui_test calls rustc directly, not cargo, so I need to configure it to call bevy_lint_driver directly. See the problem?

While it is theoretically possible to set LD_LIBRARY_PATH for both of these, I struggled to get either of them to work. If you manage to do so, please let me know!

The solution

It is possible to hardcode a dynamic linker search path using the -rpath linker flag. To do this, I created a build script that uses rustup to locate librustc_driver.so and emit the proper flags. It only runs upon first run and whenever build.rs is changed, so this is really cheap for incremental builds.

I heavily commented the entire thing, so take a look! The two biggest drawbacks to this approach are:

  1. The hardcoded path is not portable, since it references the name of the user's home directory. (E.g. it hardcodes /Users/appleseedj/... into the produced binary.)
  2. The produced binary still requires the pinned nightly toolchain to be installed.

I'm willing to accept these costs for the time being, as it means I will actually be able to start writing lints and stop finnicking with old and complicated linker details.

Testing

This should be generally platform-compatible, but I will need help testing this. It works on:

To test, please run:

$ cargo clean
$ cargo build -p bevy_lint
$ ./target/debug/bevy_lint_driver

It should print a help screen if everything worked correctly. If it didn't, please paste the error message and I'll try figuring out what went wrong. Thanks!

In the future

I hope for this solution to be temporary. In the future I want to build rustc_driver from scratch when release mode is enabled, so that the outputted binary is truly portable. That's for another PR, though!

richchurcher commented 1 month ago

Linux checked and working :heavy_check_mark:

janhohenheim commented 1 month ago

Not working on Windows: image

This is my target/debug dir: image

BD103 commented 1 month ago

After lots of research, turns out this isn't necessary for dev builds. All you need to do is run rustup run nightly-2024-08-21 ./target/debug/bevy_lint_driver, and it will automatically handle all of the environment setup. Closing!