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