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