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