bazelbuild / rules_rust

Rust rules for Bazel
https://bazelbuild.github.io/rules_rust/
Apache License 2.0
671 stars 434 forks source link

[Bug] Unused transitive dependencies got built #3001

Open fa93hws opened 1 week ago

fa93hws commented 1 week ago

Mini reproducible repository: https://github.com/fa93hws/rules_rust_transitive_deps

Step to reproduce:

  1. Checkout repo
  2. rustup target add wasm32-wasi
  3. cd swc_plugin
  4. cargo build-wasi and it passes
  5. bazel build :wasm, and it failed with
    
    ...
    Running Cargo build script onig_sys failed
    ...

--stderr:

error occurred: Failed to find tool. Is DUMMY_GCC_TOOL installed?


## Step to fix the error

https://github.com/fa93hws/rules_rust_transitive_deps/pull/1 fixes the issue, by removing `miette = { version = "7.2.0", features = ["fancy", "syntect-highlighter"] }` from the closure.

## Where I think goes wrong
I'm pretty new to `rust` and `rules_rust` so I maybe wrong. I'm not sure about the GCC tooling error but I think it's irrelevant here. The real problem is that `onig_sys` shouldn't be included in the build graph.
If I run `cargo build-wasi` under `swc_plugin` dir, the output looks like this, and there's no `onig_sys` at all.
![QQ_1731652246337](https://github.com/user-attachments/assets/aef9a1c3-f8d7-4a6b-998c-54eed06fb99c)
But it's somehow built by `rules_rust`.

For context, `onig_sys` is a tranistive deps of `miette` with `features = ["fancy", "syntect-highlighter"]`. When there's only `swc_plugin` in the workspace, `miette` only has

dependencies = [ "cfg-if", "miette-derive", "owo-colors", "textwrap", "thiserror", "unicode-width", ]

in the lock file.

But when `miette = { version = "7.2.0", features = ["fancy", "syntect-highlighter"] }` is added to another project in the workspace (`another_rust_project`), `miette`'s dependencies becomes:

dependencies = [ ... "supports-unicode", "syntect", <- it depends on onig_sys "terminal_size 0.3.0", ... ]


So I feel that the issue is that, `rules_rust` will take a look at the lock file, and build all transitive dependencies no matter whether they are actually needed or not? As a comparison, `cargo build` builds the needed dependencies only.

## Other info:
Platform: MacOS 15.1 (24B83)
Rust version:

rustc --version rustc 1.82.0 (f6e511eec 2024-10-15)

max-heller commented 5 days ago

This is caused by Cargo feature unification:

When building multiple packages in a workspace (such as with --workspace or multiple -p flags), the features of the dependencies of all of those packages are unified. If you have a circumstance where you want to avoid that unification for different workspace members, you will need to build them via separate cargo invocations.

The working behavior you're seeing is because you're invoking cargo for just one of the packages.

Why is rules_rust doing things this way? Unlike cargo, which resolves crate and feature dependencies in the context of the target it's building (e.g., cargo build -p foo resolves dependencies differently from cargo build --workspace), rules_rust needs to resolve the entire set of crate and feature dependencies upfront to define bazel targets for each crate. As a result, it gets the "bigger picture" set of dependencies including the "unused" transitive dependency because as far as rules_rust knows it is used through the other crate.

max-heller commented 5 days ago

As for fixing your issue, I don't fully understand the context of what's going wrong, but if it's a matter of compiling for WASI vs not you may want to look into platform specific dependencies.

fa93hws commented 5 days ago

Many thanks for your kindly explanation and helpful link, I think platform specific dependencies is exactly what I want.