blob: d7305d7ef1b0996ae46b87105c7fdac31fab48d5 [file] [log] [blame]
"""`crates_repository` rule implementation"""
load("//crate_universe/private:common_utils.bzl", "get_rust_tools")
load(
"//crate_universe/private:generate_utils.bzl",
"CRATES_REPOSITORY_ENVIRON",
"determine_repin",
"execute_generator",
"generate_config",
"get_generator",
"get_lockfile",
)
load(
"//crate_universe/private:splicing_utils.bzl",
"create_splicing_manifest",
"splice_workspace_manifest",
)
load("//crate_universe/private:urls.bzl", "CARGO_BAZEL_SHA256S", "CARGO_BAZEL_URLS")
load("//rust:defs.bzl", "rust_common")
load("//rust/platform:triple.bzl", "get_host_triple")
load("//rust/platform:triple_mappings.bzl", "SUPPORTED_PLATFORM_TRIPLES")
def _crates_repository_impl(repository_ctx):
# Determine the current host's platform triple
host_triple = get_host_triple(repository_ctx)
# Locate the generator to use
generator, generator_sha256 = get_generator(repository_ctx, host_triple.str)
# Generate a config file for all settings
config_path = generate_config(repository_ctx)
# Locate the lockfile
lockfile = get_lockfile(repository_ctx)
# Locate Rust tools (cargo, rustc)
tools = get_rust_tools(repository_ctx, host_triple)
cargo_path = repository_ctx.path(tools.cargo)
rustc_path = repository_ctx.path(tools.rustc)
# Create a manifest of all dependency inputs
splicing_manifest = create_splicing_manifest(repository_ctx)
# Determine whether or not to repin depednencies
repin = determine_repin(
repository_ctx = repository_ctx,
generator = generator,
lockfile_path = lockfile.path,
lockfile_kind = lockfile.kind,
config = config_path,
splicing_manifest = splicing_manifest,
cargo = cargo_path,
rustc = rustc_path,
)
# If re-pinning is enabled, gather additional inputs for the generator
kwargs = dict()
if repin or lockfile.kind == "cargo":
# Generate a top level Cargo workspace and manifest for use in generation
metadata_path = splice_workspace_manifest(
repository_ctx = repository_ctx,
generator = generator,
lockfile = lockfile,
splicing_manifest = splicing_manifest,
cargo = cargo_path,
rustc = rustc_path,
)
kwargs.update({
"metadata": metadata_path,
"repin": True,
})
# Run the generator
execute_generator(
repository_ctx = repository_ctx,
generator = generator,
config = config_path,
splicing_manifest = splicing_manifest,
lockfile_path = lockfile.path,
lockfile_kind = lockfile.kind,
repository_dir = repository_ctx.path("."),
cargo = cargo_path,
rustc = rustc_path,
# sysroot = tools.sysroot,
**kwargs
)
# Determine the set of reproducible values
attrs = {attr: getattr(repository_ctx.attr, attr) for attr in dir(repository_ctx.attr)}
exclude = ["to_json", "to_proto"]
for attr in exclude:
attrs.pop(attr, None)
# Note that this is only scoped to the current host platform. Users should
# ensure they provide all the values necessary for the host environments
# they support
if generator_sha256:
attrs.update({"generator_sha256s": generator_sha256})
return attrs
crates_repository = repository_rule(
doc = """\
A rule for defining and downloading Rust dependencies (crates). This rule
handles all the same [workflows](#workflows) `crate_universe` rules do.
Environment Variables:
| variable | usage |
| --- | --- |
| `CARGO_BAZEL_GENERATOR_SHA256` | The sha256 checksum of the file located at `CARGO_BAZEL_GENERATOR_URL` |
| `CARGO_BAZEL_GENERATOR_URL` | The URL of a cargo-bazel binary. This variable takes precedence over attributes and can use `file://` for local paths |
| `CARGO_BAZEL_ISOLATED` | An authorative flag as to whether or not the `CARGO_HOME` environment variable should be isolated from the host configuration |
| `CARGO_BAZEL_REPIN` | An indicator that the dependencies represented by the rule should be regenerated. `REPIN` may also be used. |
Example:
Given the following workspace structure:
```
[workspace]/
WORKSPACE
BUILD
Cargo.toml
Cargo.Bazel.lock
src/
main.rs
```
The following is something that'd be found in the `WORKSPACE` file:
```python
load("@rules_rust//crate_universe:defs.bzl", "crates_repository", "crate")
crates_repository(
name = "crate_index",
annotations = annotations = {
"rand": [crate.annotation(
default_features = False,
features = ["small_rng"],
)],
},
lockfile = "//:Cargo.Bazel.lock",
manifests = ["//:Cargo.toml"],
# Should match the version represented by the currently registered `rust_toolchain`.
rust_version = "1.60.0",
)
```
The above will create an external repository which contains aliases and macros for accessing
Rust targets found in the dependency graph defined by the given manifests.
**NOTE**: The `lockfile` must be manually created. The rule unfortunately does not yet create
it on its own. When initially setting up this rule, an empty file should be created and then
populated by repinning dependencies.
### Repinning / Updating Dependencies
Dependency syncing and updating is done in the repository rule which means it's done during the
analysis phase of builds. As mentioned in the environments variable table above, the `CARGO_BAZEL_REPIN`
(or `REPIN`) environment variables can be used to force the rule to update dependencies and potentially
render a new lockfile. Given an instance of this repository rule named `crate_index`, the easiest way to
repin dependencies is to run:
```shell
CARGO_BAZEL_REPIN=1 bazel sync --only=crate_index
```
""",
implementation = _crates_repository_impl,
attrs = {
"annotations": attr.string_list_dict(
doc = "Extra settings to apply to crates. See [crate.annotation](#crateannotation).",
),
"cargo_config": attr.label(
doc = "A [Cargo configuration](https://doc.rust-lang.org/cargo/reference/config.html) file",
),
"generate_build_scripts": attr.bool(
doc = (
"Whether or not to generate " +
"[cargo build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) by default."
),
default = True,
),
"generator": attr.string(
doc = (
"The absolute label of a generator. Eg. `@cargo_bazel_bootstrap//:cargo-bazel`. " +
"This is typically used when bootstrapping"
),
),
"generator_sha256s": attr.string_dict(
doc = "Dictionary of `host_triple` -> `sha256` for a `cargo-bazel` binary.",
default = CARGO_BAZEL_SHA256S,
),
"generator_urls": attr.string_dict(
doc = (
"URL template from which to download the `cargo-bazel` binary. `{host_triple}` and will be " +
"filled in according to the host platform."
),
default = CARGO_BAZEL_URLS,
),
"isolated": attr.bool(
doc = (
"If true, `CARGO_HOME` will be overwritten to a directory within the generated repository in " +
"order to prevent other uses of Cargo from impacting having any effect on the generated targets " +
"produced by this rule. For users who either have multiple `crate_repository` definitions in a " +
"WORKSPACE or rapidly re-pin dependencies, setting this to false may improve build times. This " +
"variable is also controled by `CARGO_BAZEL_ISOLATED` environment variable."
),
default = True,
),
"lockfile": attr.label(
doc = (
"The path to a file to use for reproducible renderings. Two kinds of lock files are supported, " +
"Cargo (`Cargo.lock` files) and Bazel (custom files generated by this rule, naming is irrelevant). " +
"Bazel lockfiles should be the prefered kind as they're desigend with Bazel's notions of " +
"reporducibility in mind. Cargo lockfiles can be used in cases where it's intended to be the " +
"source of truth, but more work will need to be done to generate BUILD files which are not " +
"guaranteed to be determinsitic."
),
mandatory = True,
),
"lockfile_kind": attr.string(
doc = (
"Two different kinds of lockfiles are supported, the custom \"Bazel\" lockfile, which is generated " +
"by this rule, and Cargo lockfiles (`Cargo.lock`). This attribute allows for explicitly defining " +
"the type in cases where it may not be auto-detectable."
),
values = [
"auto",
"bazel",
"cargo",
],
default = "auto",
),
"manifests": attr.label_list(
doc = "A list of Cargo manifests (`Cargo.toml` files).",
),
"packages": attr.string_dict(
doc = "A set of crates (packages) specifications to depend on. See [crate.spec](#crate.spec).",
),
"quiet": attr.bool(
doc = "If stdout and stderr should not be printed to the terminal.",
default = True,
),
"render_config": attr.string(
doc = (
"The configuration flags to use for rendering. Use `//crate_universe:defs.bzl\\%render_config` to " +
"generate the value for this field. If unset, the defaults defined there will be used."
),
),
"rust_toolchain_cargo_template": attr.string(
doc = (
"The template to use for finding the host `cargo` binary. `{version}` (eg. '1.53.0'), " +
"`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
"`{system}` (eg. 'darwin'), `{cfg}` (eg. 'exec'), and `{tool}` (eg. 'rustc.exe') will be replaced in " +
"the string if present."
),
default = "@rust_{system}_{arch}//:bin/{tool}",
),
"rust_toolchain_rustc_template": attr.string(
doc = (
"The template to use for finding the host `rustc` binary. `{version}` (eg. '1.53.0'), " +
"`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
"`{system}` (eg. 'darwin'), `{cfg}` (eg. 'exec'), and `{tool}` (eg. 'cargo.exe') will be replaced in " +
"the string if present."
),
default = "@rust_{system}_{arch}//:bin/{tool}",
),
"rust_version": attr.string(
doc = "The version of Rust the currently registered toolchain is using. Eg. `1.56.0`, or `nightly-2021-09-08`",
default = rust_common.default_version,
),
"splicing_config": attr.string(
doc = (
"The configuration flags to use for splicing Cargo maniests. Use `//crate_universe:defs.bzl\\%rsplicing_config` to " +
"generate the value for this field. If unset, the defaults defined there will be used."
),
),
"supported_platform_triples": attr.string_list(
doc = "A set of all platform triples to consider when generating dependencies.",
default = SUPPORTED_PLATFORM_TRIPLES,
),
},
environ = CRATES_REPOSITORY_ENVIRON,
)