|
| 1 | +rustc-worker |
| 2 | +============ |
| 3 | + |
| 4 | +rustc-worker is an implementation of [Bazel Persistent |
| 5 | +Workers](https://docs.bazel.build/versions/master/persistent-workers.html) for |
| 6 | +Rust. It is meant to be used with |
| 7 | +[rules_rust](https://github.com/bazelbuild/rules_rust). It can be used to speed |
| 8 | +up building Rust code with Bazel by using a shared, incremental cache for |
| 9 | +`rustc`. |
| 10 | + |
| 11 | +In a default Bazel execution, each rustc compiler invocation is run in a |
| 12 | +sandbox, which means that Bazel builds of Rust code only benefit from Bazel |
| 13 | +caching at the crate boundaries. Each rebuild of a crate has to start from |
| 14 | +scratch. |
| 15 | + |
| 16 | +In worker mode, a thin wrapper around rustc creates a directory for rustc to |
| 17 | +cache its [incremental compilation |
| 18 | +artifacts](https://blog.rust-lang.org/2018/02/15/Rust-1.24.html), such that |
| 19 | +rebuilding a crate can re-use unchanged parts of the crate. |
| 20 | + |
| 21 | +This is _NOT_ a full persistent worker in the style of the |
| 22 | +Java/TypeScript/Scala workers since `rustc` does not offer a "service" mode |
| 23 | +where the same compiler process can accept multiple compilation requests and |
| 24 | +re-use state like existing parse trees. There is a possibility that some of the |
| 25 | +work from [rust-analyzer](https://rust-analyzer.github.io/) could enable such |
| 26 | +behavior in the future. |
| 27 | + |
| 28 | +On my Thinkpad x230, building [ninja-rs](https://github.com/nikhilm/ninja-rs), |
| 29 | +here are the improvements I see when building the `ninja` binary, with a |
| 30 | +comment-only change to `build/src/lib.rs`. (Using the `bazel` branch.) |
| 31 | +All times are for debug builds as that is the standard developer workflow, |
| 32 | +where incremental builds matter. |
| 33 | + |
| 34 | +``` |
| 35 | +cargo build (incremental by default) 1.65s |
| 36 | +bazel build (without worker) 2.47s |
| 37 | +bazel build (with worker) 1.2s |
| 38 | +``` |
| 39 | + |
| 40 | +Bazel is possibly slightly faster than Cargo due to not paying the cost of startup. |
| 41 | + |
| 42 | +## How to use |
| 43 | + |
| 44 | +This currently requires a special branch of `rules_rust` until it is accepted |
| 45 | +and merged into the original rules. |
| 46 | + |
| 47 | +Assuming you are already using `rules_rust`, you will need to make the |
| 48 | +following changes to your `WORKSPACE` file. |
| 49 | + |
| 50 | +1. Change your `rules_rust` repository to point to the branch, like this. This |
| 51 | + should replace any existing entry for the rules. |
| 52 | + |
| 53 | +``` |
| 54 | +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") |
| 55 | +git_repository( |
| 56 | + name = "io_bazel_rules_rust", |
| 57 | + branch = "persistentworker", |
| 58 | + remote = "https://github.com/nikhilm/rules_rust", |
| 59 | +) |
| 60 | +``` |
| 61 | + |
| 62 | +2. Add a repository for the rustc-worker binary for your platform. |
| 63 | + |
| 64 | +``` |
| 65 | +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") |
| 66 | +
|
| 67 | +http_file( |
| 68 | + name = "rustc_worker", |
| 69 | + urls = ["https://github.com/nikhilm/rustc-worker/releases/download/v0.1.0/rustc-worker-linux"], |
| 70 | + sha256 = "0e2be6198d556e4972c52ee4bcd76be2c2d8fd74c58e0bf9be195c798e2a9a4e", |
| 71 | + executable = True, |
| 72 | +) |
| 73 | +``` |
| 74 | + |
| 75 | +That's it! Bazel 0.27 and higher will use workers by default when available. You can simply build any Rust targets as usual with Bazel. |
| 76 | + |
| 77 | +If you want to play with this, but don't have an existing Rust project handy, you can: |
| 78 | + |
| 79 | +``` |
| 80 | +git clone https://github.com/nikhilm/ninja-rs |
| 81 | +cd ninja-rs |
| 82 | +git checkout bazel |
| 83 | +bazel build ninja |
| 84 | +``` |
| 85 | + |
| 86 | +## Design |
| 87 | + |
| 88 | +Incrementality is obtained like this: |
| 89 | + |
| 90 | +1. On startup, the worker creates a [temporary directory](https://github.com/nikhilm/rustc-worker/blob/b840ea9f9276c47b97591d274823da54e4cbd75b/src/lib.rs#L20) uniquely identified by a hash of the path to `rustc` (actually a wrapper from rules\_rust) and the name of the Bazel workspace. This is the incremental cache. This ensures the cache is shared among all instances of rustc workers within the same workspace, but not in other workspaces. |
| 91 | +2. Bazel takes care of spawning multiple workers for parallelism. They all share the same cache. Since rustc operates at the crate level, and Bazel's design means that each crate has only one compilation artifact in the workspace, we can be reasonably sure that multiple `rustc` invocations never try to build the same crate at the same time. I'm not sure if this matters. |
| 92 | +3. The worker invokes `rustc` for each compilation request with `--codegen incremental=/path/to/cache`. |
| 93 | + |
| 94 | +## Updating the worker protocol |
| 95 | + |
| 96 | +The Worker protocol is described in a [protocol |
| 97 | +buffer](https://github.com/bazelbuild/bazel/blob/07e152e508d9926f1ec87cdf33c9970ee2f18a41/src/main/protobuf/worker_protocol.proto). |
| 98 | +This protocol will change very rarely, so to simplify the build process, we |
| 99 | +vendor the generated code in the tree. This avoids the need for worker |
| 100 | +consumers (via Bazel) to build `protoc` and `protobuf-codegen`. If you need to |
| 101 | +update this: |
| 102 | + |
| 103 | +1. Make sure `protoc` is installed for your operating system and in the path. |
| 104 | +2. `cargo install protobuf-codegen --version 2.8.2`. |
| 105 | +3. `protoc --rust_out src/ src/worker_protocol.proto`. |
| 106 | + |
| 107 | +## TODO |
| 108 | + |
| 109 | +[ ] Tests |
| 110 | +[ ] How to build with Bazel to bootstrap in rules\_rust. |
| 111 | +[ ] Submit PR for rules\_rust. |
| 112 | + |
| 113 | +## Contributing |
| 114 | + |
| 115 | +Please file an issue discussing what you want to do if you are doing any major changes. |
0 commit comments