xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/private/crates_repository.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1"""`crates_repository` rule implementation"""
2
3load("//crate_universe/private:common_utils.bzl", "get_rust_tools")
4load(
5    "//crate_universe/private:generate_utils.bzl",
6    "CRATES_REPOSITORY_ENVIRON",
7    "determine_repin",
8    "execute_generator",
9    "generate_config",
10    "get_generator",
11    "get_lockfiles",
12)
13load(
14    "//crate_universe/private:splicing_utils.bzl",
15    "create_splicing_manifest",
16    "splice_workspace_manifest",
17)
18load("//crate_universe/private:urls.bzl", "CARGO_BAZEL_SHA256S", "CARGO_BAZEL_URLS")
19load("//rust:defs.bzl", "rust_common")
20load("//rust/platform:triple.bzl", "get_host_triple")
21load("//rust/platform:triple_mappings.bzl", "SUPPORTED_PLATFORM_TRIPLES")
22
23def _crates_repository_impl(repository_ctx):
24    # Determine the current host's platform triple
25    host_triple = get_host_triple(repository_ctx)
26
27    # Locate the generator to use
28    generator, generator_sha256 = get_generator(repository_ctx, host_triple.str)
29
30    # Generate a config file for all settings
31    config_path = generate_config(repository_ctx)
32
33    # Locate the lockfiles
34    lockfiles = get_lockfiles(repository_ctx)
35
36    # Locate Rust tools (cargo, rustc)
37    tools = get_rust_tools(repository_ctx, host_triple)
38    cargo_path = repository_ctx.path(tools.cargo)
39    rustc_path = repository_ctx.path(tools.rustc)
40
41    # Create a manifest of all dependency inputs
42    splicing_manifest = create_splicing_manifest(repository_ctx)
43
44    # Determine whether or not to repin depednencies
45    repin = determine_repin(
46        repository_ctx = repository_ctx,
47        generator = generator,
48        lockfile_path = lockfiles.bazel,
49        config = config_path,
50        splicing_manifest = splicing_manifest,
51        cargo = cargo_path,
52        rustc = rustc_path,
53        repin_instructions = repository_ctx.attr.repin_instructions,
54    )
55
56    # If re-pinning is enabled, gather additional inputs for the generator
57    kwargs = dict()
58    if repin:
59        # Generate a top level Cargo workspace and manifest for use in generation
60        metadata_path = splice_workspace_manifest(
61            repository_ctx = repository_ctx,
62            generator = generator,
63            cargo_lockfile = lockfiles.cargo,
64            splicing_manifest = splicing_manifest,
65            config_path = config_path,
66            cargo = cargo_path,
67            rustc = rustc_path,
68        )
69
70        kwargs.update({
71            "metadata": metadata_path,
72        })
73
74    # Run the generator
75    execute_generator(
76        repository_ctx = repository_ctx,
77        generator = generator,
78        config = config_path,
79        splicing_manifest = splicing_manifest,
80        lockfile_path = lockfiles.bazel,
81        cargo_lockfile_path = lockfiles.cargo,
82        repository_dir = repository_ctx.path("."),
83        cargo = cargo_path,
84        rustc = rustc_path,
85        # sysroot = tools.sysroot,
86        **kwargs
87    )
88
89    # Determine the set of reproducible values
90    attrs = {attr: getattr(repository_ctx.attr, attr) for attr in dir(repository_ctx.attr)}
91    exclude = ["to_json", "to_proto"]
92    for attr in exclude:
93        attrs.pop(attr, None)
94
95    # Note that this is only scoped to the current host platform. Users should
96    # ensure they provide all the values necessary for the host environments
97    # they support
98    if generator_sha256:
99        attrs.update({"generator_sha256s": generator_sha256})
100
101    # Inform users that the repository rule can be made deterministic if they
102    # add a label to a lockfile path specifically for Bazel.
103    if not lockfiles.bazel:
104        attrs.update({"lockfile": repository_ctx.attr.cargo_lockfile.relative("cargo-bazel-lock.json")})
105
106    return attrs
107
108crates_repository = repository_rule(
109    doc = """\
110A rule for defining and downloading Rust dependencies (crates). This rule
111handles all the same [workflows](#workflows) `crate_universe` rules do.
112
113Environment Variables:
114
115| variable | usage |
116| --- | --- |
117| `CARGO_BAZEL_GENERATOR_SHA256` | The sha256 checksum of the file located at `CARGO_BAZEL_GENERATOR_URL` |
118| `CARGO_BAZEL_GENERATOR_URL` | The URL of a cargo-bazel binary. This variable takes precedence over attributes and can use `file://` for local paths |
119| `CARGO_BAZEL_ISOLATED` | An authorative flag as to whether or not the `CARGO_HOME` environment variable should be isolated from the host configuration |
120| `CARGO_BAZEL_REPIN` | An indicator that the dependencies represented by the rule should be regenerated. `REPIN` may also be used. See [Repinning / Updating Dependencies](#repinning--updating-dependencies) for more details. |
121| `CARGO_BAZEL_REPIN_ONLY` | A comma-delimited allowlist for rules to execute repinning. Can be useful if multiple instances of the repository rule are used in a Bazel workspace, but repinning should be limited to one of them. |
122
123Example:
124
125Given the following workspace structure:
126
127```text
128[workspace]/
129    WORKSPACE.bazel
130    BUILD.bazel
131    Cargo.toml
132    Cargo.Bazel.lock
133    src/
134        main.rs
135```
136
137The following is something that'd be found in the `WORKSPACE` file:
138
139```python
140load("@rules_rust//crate_universe:defs.bzl", "crates_repository", "crate")
141
142crates_repository(
143    name = "crate_index",
144    annotations = {
145        "rand": [crate.annotation(
146            default_features = False,
147            features = ["small_rng"],
148        )],
149    },
150    cargo_lockfile = "//:Cargo.Bazel.lock",
151    lockfile = "//:cargo-bazel-lock.json",
152    manifests = ["//:Cargo.toml"],
153    # Should match the version represented by the currently registered `rust_toolchain`.
154    rust_version = "1.60.0",
155)
156```
157
158The above will create an external repository which contains aliases and macros for accessing
159Rust targets found in the dependency graph defined by the given manifests.
160
161**NOTE**: The `cargo_lockfile` and `lockfile` must be manually created. The rule unfortunately does not yet create
162it on its own. When initially setting up this rule, an empty file should be created and then
163populated by repinning dependencies.
164
165### Repinning / Updating Dependencies
166
167Dependency syncing and updating is done in the repository rule which means it's done during the
168analysis phase of builds. As mentioned in the environments variable table above, the `CARGO_BAZEL_REPIN`
169(or `REPIN`) environment variables can be used to force the rule to update dependencies and potentially
170render a new lockfile. Given an instance of this repository rule named `crate_index`, the easiest way to
171repin dependencies is to run:
172
173```shell
174CARGO_BAZEL_REPIN=1 bazel sync --only=crate_index
175```
176
177This will result in all dependencies being updated for a project. The `CARGO_BAZEL_REPIN` environment variable
178can also be used to customize how dependencies are updated. The following table shows translations from environment
179variable values to the equivilant [cargo update](https://doc.rust-lang.org/cargo/commands/cargo-update.html) command
180that is called behind the scenes to update dependencies.
181
182| Value | Cargo command |
183| --- | --- |
184| Any of [`true`, `1`, `yes`, `on`, `workspace`] | `cargo update --workspace` |
185| Any of [`full`, `eager`, `all`] | `cargo update` |
186| `package_name` | `cargo upgrade --package package_name` |
187| `[email protected]` | `cargo upgrade --package [email protected]` |
188| `[email protected]=4.5.6` | `cargo upgrade --package [email protected] --precise=4.5.6` |
189
190If the `crates_repository` is used multiple times in the same Bazel workspace (e.g. for multiple independent
191Rust workspaces), it may additionally be useful to use the `CARGO_BAZEL_REPIN_ONLY` environment variable, which
192limits execution of the repinning to one or multiple instances of the `crates_repository` rule via a comma-delimited
193allowlist:
194
195```shell
196CARGO_BAZEL_REPIN=1 CARGO_BAZEL_REPIN_ONLY=crate_index bazel sync --only=crate_index
197```
198
199""",
200    implementation = _crates_repository_impl,
201    attrs = {
202        "annotations": attr.string_list_dict(
203            doc = "Extra settings to apply to crates. See [crate.annotation](#crateannotation).",
204        ),
205        "cargo_config": attr.label(
206            doc = "A [Cargo configuration](https://doc.rust-lang.org/cargo/reference/config.html) file",
207        ),
208        "cargo_lockfile": attr.label(
209            doc = (
210                "The path used to store the `crates_repository` specific " +
211                "[Cargo.lock](https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html) file. " +
212                "In the case that your `crates_repository` corresponds directly with an existing " +
213                "`Cargo.toml` file which has a paired `Cargo.lock` file, that `Cargo.lock` file " +
214                "should be used here, which will keep the versions used by cargo and bazel in sync."
215            ),
216            mandatory = True,
217        ),
218        "generate_binaries": attr.bool(
219            doc = (
220                "Whether to generate `rust_binary` targets for all the binary crates in every package. " +
221                "By default only the `rust_library` targets are generated."
222            ),
223            default = False,
224        ),
225        "generate_build_scripts": attr.bool(
226            doc = (
227                "Whether or not to generate " +
228                "[cargo build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) by default."
229            ),
230            default = True,
231        ),
232        "generate_target_compatible_with": attr.bool(
233            doc = "DEPRECATED: Moved to `render_config`.",
234            default = True,
235        ),
236        "generator": attr.string(
237            doc = (
238                "The absolute label of a generator. Eg. `@cargo_bazel_bootstrap//:cargo-bazel`. " +
239                "This is typically used when bootstrapping"
240            ),
241        ),
242        "generator_sha256s": attr.string_dict(
243            doc = "Dictionary of `host_triple` -> `sha256` for a `cargo-bazel` binary.",
244            default = CARGO_BAZEL_SHA256S,
245        ),
246        "generator_urls": attr.string_dict(
247            doc = (
248                "URL template from which to download the `cargo-bazel` binary. `{host_triple}` and will be " +
249                "filled in according to the host platform."
250            ),
251            default = CARGO_BAZEL_URLS,
252        ),
253        "isolated": attr.bool(
254            doc = (
255                "If true, `CARGO_HOME` will be overwritten to a directory within the generated repository in " +
256                "order to prevent other uses of Cargo from impacting having any effect on the generated targets " +
257                "produced by this rule. For users who either have multiple `crate_repository` definitions in a " +
258                "WORKSPACE or rapidly re-pin dependencies, setting this to false may improve build times. This " +
259                "variable is also controled by `CARGO_BAZEL_ISOLATED` environment variable."
260            ),
261            default = True,
262        ),
263        "lockfile": attr.label(
264            doc = (
265                "The path to a file to use for reproducible renderings. " +
266                "If set, this file must exist within the workspace (but can be empty) before this rule will work."
267            ),
268        ),
269        "manifests": attr.label_list(
270            doc = "A list of Cargo manifests (`Cargo.toml` files).",
271        ),
272        "packages": attr.string_dict(
273            doc = "A set of crates (packages) specifications to depend on. See [crate.spec](#crate.spec).",
274        ),
275        "quiet": attr.bool(
276            doc = "If stdout and stderr should not be printed to the terminal.",
277            default = True,
278        ),
279        "render_config": attr.string(
280            doc = (
281                "The configuration flags to use for rendering. Use `//crate_universe:defs.bzl\\%render_config` to " +
282                "generate the value for this field. If unset, the defaults defined there will be used."
283            ),
284        ),
285        "repin_instructions": attr.string(
286            doc = "Instructions to re-pin the repository if required. Many people have wrapper scripts for keeping dependencies up to date, and would like to point users to that instead of the default.",
287        ),
288        "rust_toolchain_cargo_template": attr.string(
289            doc = (
290                "The template to use for finding the host `cargo` binary. `{version}` (eg. '1.53.0'), " +
291                "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
292                "`{system}` (eg. 'darwin'), `{cfg}` (eg. 'exec'), `{channel}` (eg. 'stable'), and `{tool}` (eg. " +
293                "'rustc.exe') will be replaced in the string if present."
294            ),
295            default = "@rust_{system}_{arch}__{triple}__{channel}_tools//:bin/{tool}",
296        ),
297        "rust_toolchain_rustc_template": attr.string(
298            doc = (
299                "The template to use for finding the host `rustc` binary. `{version}` (eg. '1.53.0'), " +
300                "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
301                "`{system}` (eg. 'darwin'), `{cfg}` (eg. 'exec'), `{channel}` (eg. 'stable'), and `{tool}` (eg. " +
302                "'cargo.exe') will be replaced in the string if present."
303            ),
304            default = "@rust_{system}_{arch}__{triple}__{channel}_tools//:bin/{tool}",
305        ),
306        "rust_version": attr.string(
307            doc = "The version of Rust the currently registered toolchain is using. Eg. `1.56.0`, or `nightly/2021-09-08`",
308            default = rust_common.default_version,
309        ),
310        "splicing_config": attr.string(
311            doc = (
312                "The configuration flags to use for splicing Cargo maniests. Use `//crate_universe:defs.bzl\\%rsplicing_config` to " +
313                "generate the value for this field. If unset, the defaults defined there will be used."
314            ),
315        ),
316        "supported_platform_triples": attr.string_list(
317            doc = "A set of all platform triples to consider when generating dependencies.",
318            default = SUPPORTED_PLATFORM_TRIPLES,
319        ),
320    },
321    environ = CRATES_REPOSITORY_ENVIRON,
322)
323