Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ bazel_dep(name = "platforms", version = "1.1.0")
bazel_dep(name = "rules_cc", version = "0.2.4")
bazel_dep(name = "rules_license", version = "1.0.0")
bazel_dep(name = "rules_shell", version = "0.6.1")
bazel_dep(name = "toml.bzl", version = "0.4.1")
bazel_dep(name = "apple_support", version = "1.24.1")

internal_deps = use_extension("//rust/private:internal_extensions.bzl", "i")
Expand Down
10 changes: 10 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ rust.toolchain(
)
```

If the repository has a [`rust-toolchain.toml`](https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file) for rustup and cargo, `rust_toolchain_file` reads the version from it instead, so it is only written once. Its `channel` must be an exact release (`1.85.0`, `nightly-2025-01-01`), as rules_rust downloads a fixed release rather than asking rustup what `stable` is today:

```python
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
edition = "2021",
rust_toolchain_file = "//:rust-toolchain.toml",
)
```

By default, a `stable` and `nightly` toolchain will be registered if no `toolchain` method is called (and thus no specific versions are registered). However, if only 1 version is passed and it is from the `nightly` or `beta` release channels (i.e. __not__ `stable`), then the following build setting flag must be present, either on the command line or set in the project's `.bazelrc` file:

```text
Expand Down
5 changes: 4 additions & 1 deletion examples/hello_world_no_cargo/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ local_path_override(
)

rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(edition = "2021")
rust.toolchain(
edition = "2021",
rust_toolchain_file = "//:rust-toolchain.toml",
)
use_repo(rust, "rust_toolchains")

register_toolchains("@rust_toolchains//:all")
Expand Down
3 changes: 3 additions & 0 deletions examples/hello_world_no_cargo/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# What rustup and cargo read; MODULE.bazel points rules_rust at it too.
[toolchain]
channel = "1.98.0"
34 changes: 30 additions & 4 deletions rust/extensions.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ load(
"DEFAULT_NIGHTLY_VERSION",
"DEFAULT_STATIC_RUST_URL_TEMPLATES",
)
load("//rust/private:rust_toolchain_file.bzl", "parse_rust_toolchain_file")

_RUST_TOOLCHAIN_VERSIONS = [
rust_common.default_version,
Expand Down Expand Up @@ -120,7 +121,23 @@ def _rust_impl(module_ctx):
fail("Cannot define both extra_rustc_flags and extra_rustc_flags_triples")
if toolchain.extra_exec_rustc_flags and toolchain.extra_exec_rustc_flags_triples:
fail("Cannot define both extra_exec_rustc_flags and extra_exec_rustc_flags_triples")
if len(toolchain.versions) == 0:

versions = toolchain.versions
extra_target_triples = toolchain.extra_target_triples
dev_components = toolchain.dev_components
if toolchain.rust_toolchain_file:
if toolchain.versions != _RUST_TOOLCHAIN_VERSIONS:
fail("Cannot define both versions and rust_toolchain_file")
toolchain_file = parse_rust_toolchain_file(module_ctx.read(module_ctx.path(toolchain.rust_toolchain_file)))
versions = toolchain_file.versions
extra_target_triples = extra_target_triples + [
triple
for triple in toolchain_file.extra_target_triples
if triple not in extra_target_triples
]
dev_components = dev_components or toolchain_file.dev_components

if len(versions) == 0:
# If the root module has asked for rules_rust to not register default
# toolchains, an empty repository named `rust_toolchains` is created
# so that the `register_toolchains()` in MODULES.bazel is still
Expand All @@ -132,7 +149,7 @@ def _rust_impl(module_ctx):

rust_register_toolchains(
hub_name = "rust_toolchains",
dev_components = toolchain.dev_components,
dev_components = dev_components,
edition = toolchain.edition,
extra_rustc_flags = extra_rustc_flags,
extra_exec_rustc_flags = extra_exec_rustc_flags,
Expand All @@ -141,11 +158,11 @@ def _rust_impl(module_ctx):
rustfmt_version = toolchain.rustfmt_version,
rust_analyzer_version = toolchain.rust_analyzer_version,
sha256s = toolchain.sha256s,
extra_target_triples = toolchain.extra_target_triples,
extra_target_triples = extra_target_triples,
opt_level = toolchain.opt_level if toolchain.opt_level else None,
strip_level = toolchain.strip_level if toolchain.strip_level else None,
urls = toolchain.urls,
versions = toolchain.versions,
versions = versions,
compact_windows_names = True,
aliases = toolchain.aliases,
toolchain_triples = toolchain_triples,
Expand Down Expand Up @@ -281,6 +298,15 @@ _RUST_TOOLCHAIN_TAG = tag_class(
"rust_analyzer_version": attr.string(
doc = "The version of Rustc to pair with rust-analyzer.",
),
"rust_toolchain_file": attr.label(
doc = (
"A `rust-toolchain.toml` file, the one rustup and cargo read, to take the toolchain from " +
"instead of `versions`: its `channel` must be an exact release (`1.85.0`, `nightly-2025-01-01`), " +
"its `targets` are added to `extra_target_triples`, and `rustc-dev` in its `components` " +
"enables `dev_components`."
),
allow_single_file = True,
),
"rustfmt_toolchain_triples": attr.string_dict(
doc = "Like toolchain_triples, but for rustfmt toolchains. Mapping of rust target triple to repository name.",
),
Expand Down
1 change: 1 addition & 0 deletions rust/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ bzl_library(
"@bazel_skylib//lib:paths",
"@bazel_skylib//lib:structs",
"@bazel_skylib//rules:common_settings",
"@toml.bzl//:toml",
],
)

Expand Down
42 changes: 42 additions & 0 deletions rust/private/rust_toolchain_file.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Reads the `rust-toolchain.toml` file that rustup and cargo read."""

load("@toml.bzl", "toml")

def parse_rust_toolchain_file(content):
"""Turns a `rust-toolchain.toml` file into `rust.toolchain` settings.

Only what rules_rust can act on without rustup is read: `channel` must name
an exact release (`1.85.0`, `nightly-2025-01-01`, `beta-2025-01-01`),
because resolving `stable` or `1.85` to a release is something rustup does
with the network. `targets` adds to `extra_target_triples`, and
`components` enables `dev_components` if it lists `rustc-dev`.

Args:
content (str): The contents of a `rust-toolchain.toml` file.

Returns:
struct: With `versions`, `extra_target_triples` and `dev_components`
fields, in the shape the `rust.toolchain` tag takes.
"""
toolchain = toml.decode(content).get("toolchain", {})
channel = toolchain.get("channel")
if not channel:
fail("rust-toolchain.toml has no `channel` in its `[toolchain]` table")

if channel.startswith("nightly-") or channel.startswith("beta-"):
name, iso_date = channel.split("-", 1)
version = "{}/{}".format(name, iso_date)
elif channel[0].isdigit() and channel.count(".") == 2:
version = channel
else:
fail((
"rust-toolchain.toml channel `{}` is not an exact release: rules_rust " +
"downloads a fixed release, so the channel must be a full version " +
"(`1.85.0`) or a dated nightly or beta (`nightly-2025-01-01`)"
).format(channel))

return struct(
versions = [version],
extra_target_triples = toolchain.get("targets", []),
dev_components = "rustc-dev" in toolchain.get("components", []),
)
4 changes: 4 additions & 0 deletions test/unit/rust_toolchain_file/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
load(":rust_toolchain_file_test.bzl", "rust_toolchain_file_test_suite")

############################ UNIT TESTS #############################
rust_toolchain_file_test_suite(name = "rust_toolchain_file_test_suite")
61 changes: 61 additions & 0 deletions test/unit/rust_toolchain_file/rust_toolchain_file_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Unit tests for rust_toolchain_file.bzl."""

load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")

# buildifier: disable=bzl-visibility
load("//rust/private:rust_toolchain_file.bzl", "parse_rust_toolchain_file")

def _stable_release_test_impl(ctx):
env = unittest.begin(ctx)
parsed = parse_rust_toolchain_file("""\
# What cargo and rustup read.
[toolchain]
channel = "1.85.0"
components = ["rustfmt", "clippy"]
""")
asserts.equals(env, ["1.85.0"], parsed.versions)
asserts.equals(env, [], parsed.extra_target_triples)
asserts.false(env, parsed.dev_components)
return unittest.end(env)

def _dated_nightly_test_impl(ctx):
env = unittest.begin(ctx)
parsed = parse_rust_toolchain_file("""\
[toolchain]
channel = "nightly-2025-01-01"
components = [
"rustc-dev",
"rustfmt",
]
targets = ["wasm32-unknown-unknown", "aarch64-unknown-linux-gnu"]
""")
asserts.equals(env, ["nightly/2025-01-01"], parsed.versions)
asserts.equals(env, ["wasm32-unknown-unknown", "aarch64-unknown-linux-gnu"], parsed.extra_target_triples)
asserts.true(env, parsed.dev_components)
return unittest.end(env)

def _dated_beta_test_impl(ctx):
env = unittest.begin(ctx)
parsed = parse_rust_toolchain_file("""\
[toolchain]
channel = "beta-2025-01-01"
""")
asserts.equals(env, ["beta/2025-01-01"], parsed.versions)
return unittest.end(env)

stable_release_test = unittest.make(_stable_release_test_impl)
dated_nightly_test = unittest.make(_dated_nightly_test_impl)
dated_beta_test = unittest.make(_dated_beta_test_impl)

def rust_toolchain_file_test_suite(name):
"""Entry-point macro called from the BUILD file.

Args:
name (str): Name of the macro.
"""
unittest.suite(
name,
stable_release_test,
dated_nightly_test,
dated_beta_test,
)
Loading