Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persistent worker support #421

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a88defb
tweak to use a pretend persistent worker executable
nikhilm Sep 17, 2020
ec046ba
add program path to args as well.
nikhilm Sep 17, 2020
7cdb169
actually, the program path should be separate to make it easy in the …
nikhilm Sep 17, 2020
12f8abc
add original process wrapper to tools depset
nikhilm Sep 17, 2020
55ee86a
enable incremental compilation
nikhilm Sep 17, 2020
8fa4e54
remove incremental. rustc-worker should pass it and point at a shared…
nikhilm Sep 18, 2020
6b9ee79
pass workspace name to create cache dir
nikhilm Sep 20, 2020
715794b
clean up
nikhilm Sep 20, 2020
b693c68
pass compilation mode and rustc path to worker to namespace the cache
nikhilm Sep 22, 2020
da0c8b8
add the worker as a toolchain
nikhilm Oct 7, 2020
801e2ae
make workers optional
nikhilm Oct 7, 2020
77fe9c0
Fix toolchain registration
nikhilm Oct 22, 2020
8a7b18a
oops! Toolchain order matters.
nikhilm Oct 22, 2020
e4d92e3
update proto rules toolchains
nikhilm Oct 22, 2020
68c326c
Remove TODO
nikhilm Oct 22, 2020
068b6c9
Fix rebase issues
nikhilm Oct 22, 2020
3c2184b
Run buildifier
nikhilm Oct 22, 2020
1f79d09
Fix documentation generation
nikhilm Oct 22, 2020
a3e0ebb
Change examples to use the worker
nikhilm Oct 22, 2020
a68c7e2
Fix buildifier lints
nikhilm Oct 22, 2020
917bee9
Switch to a musl linked static library so it works without glibc
nikhilm Oct 22, 2020
81353d6
Final buildifier fix
nikhilm Oct 22, 2020
7eba058
Add documentation about using the worker
nikhilm Oct 22, 2020
96a0d34
Fix linux version URL
nikhilm Nov 7, 2020
7a487c5
Merge branch 'master' into persistentworker
nikhilm Nov 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ bzl_library(
"@io_bazel_rules_rust//proto:rules",
"@io_bazel_rules_rust//rust:rules",
"@io_bazel_rules_rust//wasm_bindgen:rules",
"@io_bazel_rules_rust//worker:rules",
"@rules_proto//proto:rules",
],
)
Expand Down
9 changes: 9 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ Similarly, `rustfmt_version` may also be configured:
```python
rust_repositories(rustfmt_version = "1.4.20")
```
# Using Bazel Persistent Workers

Iterating on Rust code may benefit from [incremental compilation](https://doc.rust-lang.org/edition-guide/rust-2018/the-compiler/incremental-compilation-for-faster-compiles.html). This is supported by using a [Bazel Persistent Worker](https://docs.bazel.build/versions/master/persistent-workers.html). While Bazel can determine what needs to be rebuilt at a crate level, the compiler can speed up building a single crate by sharing information across runs. It does this by storing intermediate information in a directory across invocations. This is enabled by default in Cargo. Persistent workers bring this feature to Bazel.

The Rust rules have preliminary support for workers. Pass `use_worker = True` to enable this when available.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is linux only, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I posted a Mac version above - but each platform would need its own binary at the moment.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rustc-worker also has a mac build available now. I can update this PR with an entry for that.


```python
rust_repositories(use_worker = True)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why this shouldn't be the default, was there a reason to be conservative about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pulling in and running a binary hosted on a third-party repo, so off by default is probably the best option.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, what @dae said. Plus given that only 2(?) people have actually used it, it seemed very alpha.

```

## External Dependencies

Expand Down
5 changes: 4 additions & 1 deletion docs/rust_repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ Generates a toolchain-bearing repository that declares the toolchains from some
## rust_repositories

<pre>
rust_repositories(<a href="#rust_repositories-version">version</a>, <a href="#rust_repositories-iso_date">iso_date</a>, <a href="#rust_repositories-rustfmt_version">rustfmt_version</a>, <a href="#rust_repositories-edition">edition</a>, <a href="#rust_repositories-dev_components">dev_components</a>, <a href="#rust_repositories-sha256s">sha256s</a>)
rust_repositories(<a href="#rust_repositories-version">version</a>, <a href="#rust_repositories-iso_date">iso_date</a>, <a href="#rust_repositories-rustfmt_version">rustfmt_version</a>, <a href="#rust_repositories-edition">edition</a>, <a href="#rust_repositories-dev_components">dev_components</a>, <a href="#rust_repositories-sha256s">sha256s</a>, <a href="#rust_repositories-use_worker">use_worker</a>)
</pre>

Emits a default set of toolchains for Linux, OSX, and Freebsd
Expand All @@ -158,6 +158,8 @@ This would match for `exec_triple = "x86_64-unknown-linux-gnu"`. If not specifi

See `load_arbitrary_tool` in `@io_bazel_rules_rust//rust:repositories.bzl` for more details.

The `use_worker` boolean enables [Bazel Persistent Workers] to be used when available. Not all execution platforms have a pre-built binary worker available. In such a case, the rules will fall back to invoking `rustc` directly. Supported platforms:
- linux-x86_64

**PARAMETERS**

Expand All @@ -170,6 +172,7 @@ See `load_arbitrary_tool` in `@io_bazel_rules_rust//rust:repositories.bzl` for m
| <a id="rust_repositories-edition"></a>edition | The rust edition to be used by default (2015 (default) or 2018) | <code>None</code> |
| <a id="rust_repositories-dev_components"></a>dev_components | Whether to download the rustc-dev components (defaults to False). Requires version to be "nightly". | <code>False</code> |
| <a id="rust_repositories-sha256s"></a>sha256s | A dict associating tool subdirectories to sha256 hashes. | <code>None</code> |
| <a id="rust_repositories-use_worker"></a>use_worker | Set this to True to enable worker. | <code>False</code> |


<a id="#rust_repository_set"></a>
Expand Down
2 changes: 1 addition & 1 deletion examples/examples_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ load("@rules_foreign_cc//:workspace_definitions.bzl", "rules_foreign_cc_dependen
def deps():
"""Define dependencies for `rules_rust` examples"""

rust_repositories()
rust_repositories(use_worker = True)

rust_bindgen_repositories()

Expand Down
2 changes: 2 additions & 0 deletions proto/proto.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ rust_proto_library = rule(
"@io_bazel_rules_rust//proto:toolchain",
"@io_bazel_rules_rust//rust:toolchain",
"@bazel_tools//tools/cpp:toolchain_type",
"@io_bazel_rules_rust//worker:toolchain_type",
],
doc = """\
Builds a Rust library crate from a set of `proto_library`s.
Expand Down Expand Up @@ -384,6 +385,7 @@ rust_grpc_library = rule(
"@io_bazel_rules_rust//proto:toolchain",
"@io_bazel_rules_rust//rust:toolchain",
"@bazel_tools//tools/cpp:toolchain_type",
"@io_bazel_rules_rust//worker:toolchain_type",
],
doc = """\
Builds a Rust library crate from a set of `proto_library`s suitable for gRPC.
Expand Down
5 changes: 5 additions & 0 deletions rust/private/rust.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ rust_library = rule(
toolchains = [
"@io_bazel_rules_rust//rust:toolchain",
"@bazel_tools//tools/cpp:toolchain_type",
"@io_bazel_rules_rust//worker:toolchain_type",
],
doc = """\
Builds a Rust library crate.
Expand Down Expand Up @@ -585,6 +586,7 @@ rust_binary = rule(
toolchains = [
"@io_bazel_rules_rust//rust:toolchain",
"@bazel_tools//tools/cpp:toolchain_type",
"@io_bazel_rules_rust//worker:toolchain_type",
],
doc = """\
Builds a Rust binary crate.
Expand Down Expand Up @@ -683,6 +685,7 @@ rust_test = rule(
toolchains = [
"@io_bazel_rules_rust//rust:toolchain",
"@bazel_tools//tools/cpp:toolchain_type",
"@io_bazel_rules_rust//worker:toolchain_type",
],
doc = """\
Builds a Rust test crate.
Expand Down Expand Up @@ -831,6 +834,7 @@ rust_test_binary = rule(
toolchains = [
"@io_bazel_rules_rust//rust:toolchain",
"@bazel_tools//tools/cpp:toolchain_type",
"@io_bazel_rules_rust//worker:toolchain_type",
],
doc = """\
Builds a Rust test binary, without marking this rule as a Bazel test.
Expand All @@ -854,6 +858,7 @@ rust_benchmark = rule(
toolchains = [
"@io_bazel_rules_rust//rust:toolchain",
"@bazel_tools//tools/cpp:toolchain_type",
"@io_bazel_rules_rust//worker:toolchain_type",
],
doc = """\
Builds a Rust benchmark test.
Expand Down
29 changes: 26 additions & 3 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,8 @@ def construct_arguments(
build_env_file,
build_flags_files,
maker_path = None,
aspect = False):
aspect = False,
use_worker = False):
"""Builds an Args object containing common rustc flags

Args:
Expand All @@ -431,6 +432,7 @@ def construct_arguments(
build_flags_files (list): The output files of a `cargo_build_script` actions containing rustc build flags
maker_path (File): An optional clippy marker file
aspect (bool): True if called in an aspect context.
use_worker (bool): If True, sets up the arguments in a worker-compatible fashion

Returns:
tuple: A tuple of the following items
Expand All @@ -445,6 +447,10 @@ def construct_arguments(

# Wrapper args first
args = ctx.actions.args()
if use_worker:
# Write the args to a param file that will be used by Bazel to send messages to the worker.
args.set_param_file_format("multiline")
args.use_param_file("@%s", use_always = True)

if build_env_file != None:
args.add("--env-file", build_env_file)
Expand Down Expand Up @@ -589,6 +595,8 @@ def rustc_compile_action(
- (DepInfo): The transitive dependencies of this crate.
- (DefaultInfo): The output file for this crate, and its runfiles.
"""
worker_binary = ctx.toolchains["@io_bazel_rules_rust//worker:toolchain_type"].worker_binary

dep_info, build_info = collect_deps(
ctx.label,
crate_info.deps,
Expand Down Expand Up @@ -623,20 +631,35 @@ def rustc_compile_action(
out_dir,
build_env_file,
build_flags_files,
use_worker = worker_binary != None,
)

if hasattr(ctx.attr, "version") and ctx.attr.version != "0.0.0":
formatted_version = " v{}".format(ctx.attr.version)
else:
formatted_version = ""

if worker_binary != None:
executable = worker_binary
tools = [ctx.executable._process_wrapper]
arguments = [ctx.executable._process_wrapper.path, toolchain.rustc.path, ctx.var["COMPILATION_MODE"], args]
execution_requirements = {"supports-workers": "1"}
else:
# Not all execution platforms support a worker.
executable = ctx.executable._process_wrapper
tools = []
arguments = [args]
execution_requirements = {}

ctx.actions.run(
executable = ctx.executable._process_wrapper,
executable = executable,
inputs = compile_inputs,
outputs = [crate_info.output],
tools = tools,
env = env,
arguments = [args],
arguments = arguments,
mnemonic = "Rustc",
execution_requirements = execution_requirements,
progress_message = "Compiling Rust {} {}{} ({} files)".format(
crate_info.type,
ctx.label.name,
Expand Down
16 changes: 15 additions & 1 deletion rust/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
load(":known_shas.bzl", "FILE_KEY_TO_SHA")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
load("//worker:repositories.bzl", "rust_worker_repositories", "rust_worker_toolchains")
load(
"//rust/platform:triple_mappings.bzl",
"system_to_binary_ext",
Expand All @@ -21,7 +22,8 @@ def rust_repositories(
rustfmt_version = "1.4.20",
edition = None,
dev_components = False,
sha256s = None):
sha256s = None,
use_worker = False):
"""Emits a default set of toolchains for Linux, OSX, and Freebsd

Skip this macro and call the `rust_repository_set` macros directly if you need a compiler for \
Expand All @@ -47,6 +49,7 @@ def rust_repositories(
edition: The rust edition to be used by default (2015 (default) or 2018)
dev_components: Whether to download the rustc-dev components (defaults to False). Requires version to be "nightly".
sha256s: A dict associating tool subdirectories to sha256 hashes.
use_worker: boolean. Set to True to use Bazel workers for Rust. This downloads binaries for https://github.com/nikhilm/rustc-worker.
"""

if dev_components and version != "nightly":
Expand Down Expand Up @@ -142,6 +145,17 @@ def rust_repositories(
edition = edition,
)

rust_worker_repositories()

# Register the real toolchains.
if use_worker:
rust_worker_toolchains()

# Register a fallback for when workers are not enabled or not available for the execution platform.
native.register_toolchains(
"@io_bazel_rules_rust//worker:dummy",
)

def _check_version_valid(version, iso_date, param_prefix = ""):
"""Verifies that the provided rust version and iso_date make sense."""

Expand Down
40 changes: 40 additions & 0 deletions worker/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("//worker:toolchain.bzl", "worker_toolchain")

package(default_visibility = ["//visibility:public"])

exports_files([
"repositories.bzl",
])

bzl_library(
name = "rules",
srcs = glob(["**/*.bzl"]),
)

toolchain_type(name = "toolchain_type")

worker_toolchain(name = "worker_dummy")

toolchain(
name = "dummy",
toolchain = ":worker_dummy",
toolchain_type = ":toolchain_type",
)

# These toolchains are only registered if workers are enabled in rust_repositories().

worker_toolchain(
name = "worker_linux_x86_64",
worker_binary = "@rust_worker_linux_x86_64//file",
)

toolchain(
name = "linux_x86_64",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
toolchain = ":worker_linux_x86_64",
toolchain_type = ":toolchain_type",
)
14 changes: 14 additions & 0 deletions worker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

# Rust Persistent Worker

The Rust Persistent Worker is itself implemented in Rust. It is built via Cargo and distributed as binaries. Source and release binaries are maintained at the [rustc-worker project](https://github.com/nikhilm/rustc-worker). Contributions should be submitted there and then the version of the binaries updated in `worker/repositories.bzl`.

## Why isn't this built via Bazel?

Bootstrapping the worker using these same rules (e.g. `rust_binary`) may be possible, but is not supported right now. There are a couple of challenges:
1. Since the worker has dependencies on various crates, it uses cargo-raze, which generates relevant rules. This means "don't use the worker to build this target" is transitive and such information needs to be propagated down the tree in a way that works with restrictions in Bazel. Initial experiments repeatedly encountered failures due to Bazel treating the rule dependency on the worker executable target as a cycle, even when building in non-worker mode. This may be user error or a restriction in Bazel. Until that is addressed, the easiest way to fix this is to change cargo-raze to customize what rules are used, and provide `rust_no_worker_binary` and `rust_no_worker_library` rules that do not have this cycle.
2. Figuring out a good strategy for dependencies. Since Bazel doesn't really have transitive dependencies, attempts to build this worker from source necessarily require users of these rules to register all the external repositories for the worker in their `WORKSPACE`. This could cause collisions with other dependencies. In addition, if the `_no_worker_` approach above is adopted, users will lose the benefits of workers for those dependencies (like `protobuf`) shared between the worker and their code. There is no satisfactory solution for this right now.

## How about rewriting the worker in C++?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the worker benefit from being written in a non-gc'd language? Curious about whether python is a reasonable choice, given other utilities in rules_rust have been written in python.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there is nothing preventing writing this in Python for someone with such an interest. I did not find any references to Python rules in this repo. I suppose as a bootstrap language (i.e. one that already has Bazel rules for it), that is a better alternative than C++.


That is certainly an option!
18 changes: 18 additions & 0 deletions worker/repositories.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# buildifier: disable=module-docstring
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")

def rust_worker_repositories():
"""Registers rustc-worker binary archives."""
http_file(
name = "rust_worker_linux_x86_64",
executable = True,
sha256 = "c7ee178d658a9ff9c9b10f7acce48af57227170d454741072aff5fabf923f8fb",
urls = ["https://github.com/nikhilm/rustc-worker/releases/download/v0.2.0/rustc-worker-0.2.0-linux-x86_64"],
)

# buildifier: disable=unnamed-macro
def rust_worker_toolchains():
"""Registers worker toolchains for supported platforms."""
native.register_toolchains(
"@io_bazel_rules_rust//worker:linux_x86_64",
)
13 changes: 13 additions & 0 deletions worker/toolchain.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# buildifier: disable=module-docstring
def _worker_toolchain_impl(ctx):
toolchain_info = platform_common.ToolchainInfo(
worker_binary = ctx.executable.worker_binary,
)
return [toolchain_info]

worker_toolchain = rule(
implementation = _worker_toolchain_impl,
attrs = {
"worker_binary": attr.label(allow_single_file = True, executable = True, cfg = "exec"),
},
)