1*d4726bddSHONG Yifan"""The `cargo_bootstrap` rule is used for bootstrapping cargo binaries in a repository rule.""" 2*d4726bddSHONG Yifan 3*d4726bddSHONG Yifanload("//cargo/private:cargo_utils.bzl", "get_rust_tools") 4*d4726bddSHONG Yifanload("//rust:defs.bzl", "rust_common") 5*d4726bddSHONG Yifanload("//rust/platform:triple.bzl", "get_host_triple") 6*d4726bddSHONG Yifan 7*d4726bddSHONG Yifan_CARGO_BUILD_MODES = [ 8*d4726bddSHONG Yifan "release", 9*d4726bddSHONG Yifan "debug", 10*d4726bddSHONG Yifan] 11*d4726bddSHONG Yifan 12*d4726bddSHONG Yifan_FAIL_MESSAGE = """\ 13*d4726bddSHONG YifanProcess exited with code '{code}' 14*d4726bddSHONG Yifan# ARGV ######################################################################## 15*d4726bddSHONG Yifan{argv} 16*d4726bddSHONG Yifan 17*d4726bddSHONG Yifan# STDOUT ###################################################################### 18*d4726bddSHONG Yifan{stdout} 19*d4726bddSHONG Yifan 20*d4726bddSHONG Yifan# STDERR ###################################################################### 21*d4726bddSHONG Yifan{stderr} 22*d4726bddSHONG Yifan""" 23*d4726bddSHONG Yifan 24*d4726bddSHONG Yifandef cargo_bootstrap( 25*d4726bddSHONG Yifan repository_ctx, 26*d4726bddSHONG Yifan cargo_bin, 27*d4726bddSHONG Yifan rustc_bin, 28*d4726bddSHONG Yifan binary, 29*d4726bddSHONG Yifan cargo_manifest, 30*d4726bddSHONG Yifan environment = {}, 31*d4726bddSHONG Yifan quiet = False, 32*d4726bddSHONG Yifan build_mode = "release", 33*d4726bddSHONG Yifan target_dir = None, 34*d4726bddSHONG Yifan timeout = 600): 35*d4726bddSHONG Yifan """A function for bootstrapping a cargo binary within a repository rule 36*d4726bddSHONG Yifan 37*d4726bddSHONG Yifan Args: 38*d4726bddSHONG Yifan repository_ctx (repository_ctx): The rule's context object. 39*d4726bddSHONG Yifan cargo_bin (path): The path to a Cargo binary. 40*d4726bddSHONG Yifan rustc_bin (path): The path to a Rustc binary. 41*d4726bddSHONG Yifan binary (str): The binary to build (the `--bin` parameter for Cargo). 42*d4726bddSHONG Yifan cargo_manifest (path): The path to a Cargo manifest (Cargo.toml file). 43*d4726bddSHONG Yifan environment (dict): Environment variables to use during execution. 44*d4726bddSHONG Yifan quiet (bool, optional): Whether or not to print output from the Cargo command. 45*d4726bddSHONG Yifan build_mode (str, optional): The build mode to use 46*d4726bddSHONG Yifan target_dir (path, optional): The directory in which to produce build outputs 47*d4726bddSHONG Yifan (Cargo's --target-dir argument). 48*d4726bddSHONG Yifan timeout (int, optional): Maximum duration of the Cargo build command in seconds, 49*d4726bddSHONG Yifan 50*d4726bddSHONG Yifan Returns: 51*d4726bddSHONG Yifan path: The path of the built binary within the target directory 52*d4726bddSHONG Yifan """ 53*d4726bddSHONG Yifan 54*d4726bddSHONG Yifan if not target_dir: 55*d4726bddSHONG Yifan target_dir = repository_ctx.path(".") 56*d4726bddSHONG Yifan 57*d4726bddSHONG Yifan args = [ 58*d4726bddSHONG Yifan cargo_bin, 59*d4726bddSHONG Yifan "build", 60*d4726bddSHONG Yifan "--bin", 61*d4726bddSHONG Yifan binary, 62*d4726bddSHONG Yifan "--locked", 63*d4726bddSHONG Yifan "--target-dir", 64*d4726bddSHONG Yifan target_dir, 65*d4726bddSHONG Yifan "--manifest-path", 66*d4726bddSHONG Yifan cargo_manifest, 67*d4726bddSHONG Yifan ] 68*d4726bddSHONG Yifan 69*d4726bddSHONG Yifan if build_mode not in _CARGO_BUILD_MODES: 70*d4726bddSHONG Yifan fail("'{}' is not a supported build mode. Use one of {}".format(build_mode, _CARGO_BUILD_MODES)) 71*d4726bddSHONG Yifan 72*d4726bddSHONG Yifan if build_mode == "release": 73*d4726bddSHONG Yifan args.append("--release") 74*d4726bddSHONG Yifan 75*d4726bddSHONG Yifan env = dict({ 76*d4726bddSHONG Yifan "RUSTC": str(rustc_bin), 77*d4726bddSHONG Yifan }.items() + environment.items()) 78*d4726bddSHONG Yifan 79*d4726bddSHONG Yifan repository_ctx.report_progress("Cargo Bootstrapping {}".format(binary)) 80*d4726bddSHONG Yifan result = repository_ctx.execute( 81*d4726bddSHONG Yifan args, 82*d4726bddSHONG Yifan environment = env, 83*d4726bddSHONG Yifan quiet = quiet, 84*d4726bddSHONG Yifan timeout = timeout, 85*d4726bddSHONG Yifan ) 86*d4726bddSHONG Yifan 87*d4726bddSHONG Yifan if result.return_code != 0: 88*d4726bddSHONG Yifan fail(_FAIL_MESSAGE.format( 89*d4726bddSHONG Yifan code = result.return_code, 90*d4726bddSHONG Yifan argv = args, 91*d4726bddSHONG Yifan stdout = result.stdout, 92*d4726bddSHONG Yifan stderr = result.stderr, 93*d4726bddSHONG Yifan )) 94*d4726bddSHONG Yifan 95*d4726bddSHONG Yifan extension = "" 96*d4726bddSHONG Yifan if "win" in repository_ctx.os.name: 97*d4726bddSHONG Yifan extension = ".exe" 98*d4726bddSHONG Yifan 99*d4726bddSHONG Yifan binary_path = "{}/{}{}".format( 100*d4726bddSHONG Yifan build_mode, 101*d4726bddSHONG Yifan binary, 102*d4726bddSHONG Yifan extension, 103*d4726bddSHONG Yifan ) 104*d4726bddSHONG Yifan 105*d4726bddSHONG Yifan if not repository_ctx.path(binary_path).exists: 106*d4726bddSHONG Yifan fail("Failed to produce binary at {}".format(binary_path)) 107*d4726bddSHONG Yifan 108*d4726bddSHONG Yifan return binary_path 109*d4726bddSHONG Yifan 110*d4726bddSHONG Yifan_BUILD_FILE_CONTENT = """\ 111*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_binary") 112*d4726bddSHONG Yifan 113*d4726bddSHONG Yifanpackage(default_visibility = ["//visibility:public"]) 114*d4726bddSHONG Yifan 115*d4726bddSHONG Yifanexports_files([ 116*d4726bddSHONG Yifan "{binary_name}", 117*d4726bddSHONG Yifan "{binary}" 118*d4726bddSHONG Yifan]) 119*d4726bddSHONG Yifan 120*d4726bddSHONG Yifanalias( 121*d4726bddSHONG Yifan name = "binary", 122*d4726bddSHONG Yifan actual = "{binary}", 123*d4726bddSHONG Yifan) 124*d4726bddSHONG Yifan 125*d4726bddSHONG Yifanrust_binary( 126*d4726bddSHONG Yifan name = "install", 127*d4726bddSHONG Yifan rustc_env = {{ 128*d4726bddSHONG Yifan "RULES_RUST_CARGO_BOOTSTRAP_BINARY": "$(rootpath {binary})" 129*d4726bddSHONG Yifan }}, 130*d4726bddSHONG Yifan data = [ 131*d4726bddSHONG Yifan "{binary}", 132*d4726bddSHONG Yifan ], 133*d4726bddSHONG Yifan srcs = [ 134*d4726bddSHONG Yifan "@rules_rust//cargo/bootstrap:bootstrap_installer.rs" 135*d4726bddSHONG Yifan ], 136*d4726bddSHONG Yifan) 137*d4726bddSHONG Yifan""" 138*d4726bddSHONG Yifan 139*d4726bddSHONG Yifandef _collect_environ(repository_ctx, host_triple): 140*d4726bddSHONG Yifan """Gather environment varialbes to use from the current rule context 141*d4726bddSHONG Yifan 142*d4726bddSHONG Yifan Args: 143*d4726bddSHONG Yifan repository_ctx (repository_ctx): The rule's context object. 144*d4726bddSHONG Yifan host_triple (str): A string of the current host triple 145*d4726bddSHONG Yifan 146*d4726bddSHONG Yifan Returns: 147*d4726bddSHONG Yifan dict: A map of environment variables 148*d4726bddSHONG Yifan """ 149*d4726bddSHONG Yifan env_vars = dict(json.decode(repository_ctx.attr.env.get(host_triple, "{}"))) 150*d4726bddSHONG Yifan 151*d4726bddSHONG Yifan # Gather the path for each label and ensure it exists 152*d4726bddSHONG Yifan env_labels = dict(json.decode(repository_ctx.attr.env_label.get(host_triple, "{}"))) 153*d4726bddSHONG Yifan env_labels = {key: repository_ctx.path(Label(value)) for (key, value) in env_labels.items()} 154*d4726bddSHONG Yifan for key in env_labels: 155*d4726bddSHONG Yifan if not env_labels[key].exists: 156*d4726bddSHONG Yifan fail("File for key '{}' does not exist: {}", key, env_labels[key]) 157*d4726bddSHONG Yifan env_labels = {key: str(value) for (key, value) in env_labels.items()} 158*d4726bddSHONG Yifan 159*d4726bddSHONG Yifan return dict(env_vars.items() + env_labels.items()) 160*d4726bddSHONG Yifan 161*d4726bddSHONG Yifandef _detect_changes(repository_ctx): 162*d4726bddSHONG Yifan """Inspect files that are considered inputs to the build for changes 163*d4726bddSHONG Yifan 164*d4726bddSHONG Yifan Args: 165*d4726bddSHONG Yifan repository_ctx (repository_ctx): The rule's context object. 166*d4726bddSHONG Yifan """ 167*d4726bddSHONG Yifan # Simply generating a `path` object consideres the file as 'tracked' or 168*d4726bddSHONG Yifan # 'consumed' which means changes to it will trigger rebuilds 169*d4726bddSHONG Yifan 170*d4726bddSHONG Yifan for src in repository_ctx.attr.srcs: 171*d4726bddSHONG Yifan repository_ctx.path(src) 172*d4726bddSHONG Yifan 173*d4726bddSHONG Yifan repository_ctx.path(repository_ctx.attr.cargo_lockfile) 174*d4726bddSHONG Yifan repository_ctx.path(repository_ctx.attr.cargo_toml) 175*d4726bddSHONG Yifan 176*d4726bddSHONG Yifandef _cargo_bootstrap_repository_impl(repository_ctx): 177*d4726bddSHONG Yifan # Pretend to Bazel that this rule's input files have been used, so that it will re-run the rule if they change. 178*d4726bddSHONG Yifan _detect_changes(repository_ctx) 179*d4726bddSHONG Yifan 180*d4726bddSHONG Yifan # Expects something like `1.56.0`, or `nightly/2021-09-08`. 181*d4726bddSHONG Yifan version, _, iso_date = repository_ctx.attr.version.partition("/") 182*d4726bddSHONG Yifan if iso_date: 183*d4726bddSHONG Yifan channel = version 184*d4726bddSHONG Yifan version = iso_date 185*d4726bddSHONG Yifan else: 186*d4726bddSHONG Yifan channel = "stable" 187*d4726bddSHONG Yifan 188*d4726bddSHONG Yifan host_triple = get_host_triple(repository_ctx) 189*d4726bddSHONG Yifan cargo_template = repository_ctx.attr.rust_toolchain_cargo_template 190*d4726bddSHONG Yifan rustc_template = repository_ctx.attr.rust_toolchain_rustc_template 191*d4726bddSHONG Yifan 192*d4726bddSHONG Yifan tools = get_rust_tools( 193*d4726bddSHONG Yifan cargo_template = cargo_template, 194*d4726bddSHONG Yifan rustc_template = rustc_template, 195*d4726bddSHONG Yifan host_triple = host_triple, 196*d4726bddSHONG Yifan channel = channel, 197*d4726bddSHONG Yifan version = version, 198*d4726bddSHONG Yifan ) 199*d4726bddSHONG Yifan 200*d4726bddSHONG Yifan binary_name = repository_ctx.attr.binary or repository_ctx.name 201*d4726bddSHONG Yifan 202*d4726bddSHONG Yifan # In addition to platform specific environment variables, a common set (indicated by `*`) will always 203*d4726bddSHONG Yifan # be gathered. 204*d4726bddSHONG Yifan environment = dict(_collect_environ(repository_ctx, "*").items() + _collect_environ(repository_ctx, host_triple.str).items()) 205*d4726bddSHONG Yifan 206*d4726bddSHONG Yifan built_binary = cargo_bootstrap( 207*d4726bddSHONG Yifan repository_ctx = repository_ctx, 208*d4726bddSHONG Yifan cargo_bin = repository_ctx.path(tools.cargo), 209*d4726bddSHONG Yifan rustc_bin = repository_ctx.path(tools.rustc), 210*d4726bddSHONG Yifan binary = binary_name, 211*d4726bddSHONG Yifan cargo_manifest = repository_ctx.path(repository_ctx.attr.cargo_toml), 212*d4726bddSHONG Yifan build_mode = repository_ctx.attr.build_mode, 213*d4726bddSHONG Yifan environment = environment, 214*d4726bddSHONG Yifan timeout = repository_ctx.attr.timeout, 215*d4726bddSHONG Yifan ) 216*d4726bddSHONG Yifan 217*d4726bddSHONG Yifan # Create a symlink so that the binary can be accesed via it's target name 218*d4726bddSHONG Yifan repository_ctx.symlink(built_binary, binary_name) 219*d4726bddSHONG Yifan 220*d4726bddSHONG Yifan repository_ctx.file("BUILD.bazel", _BUILD_FILE_CONTENT.format( 221*d4726bddSHONG Yifan binary_name = binary_name, 222*d4726bddSHONG Yifan binary = built_binary, 223*d4726bddSHONG Yifan )) 224*d4726bddSHONG Yifan 225*d4726bddSHONG Yifancargo_bootstrap_repository = repository_rule( 226*d4726bddSHONG Yifan doc = "A rule for bootstrapping a Rust binary using [Cargo](https://doc.rust-lang.org/cargo/)", 227*d4726bddSHONG Yifan implementation = _cargo_bootstrap_repository_impl, 228*d4726bddSHONG Yifan attrs = { 229*d4726bddSHONG Yifan "binary": attr.string( 230*d4726bddSHONG Yifan doc = "The binary to build (the `--bin` parameter for Cargo). If left empty, the repository name will be used.", 231*d4726bddSHONG Yifan ), 232*d4726bddSHONG Yifan "build_mode": attr.string( 233*d4726bddSHONG Yifan doc = "The build mode the binary should be built with", 234*d4726bddSHONG Yifan values = [ 235*d4726bddSHONG Yifan "debug", 236*d4726bddSHONG Yifan "release", 237*d4726bddSHONG Yifan ], 238*d4726bddSHONG Yifan default = "release", 239*d4726bddSHONG Yifan ), 240*d4726bddSHONG Yifan "cargo_lockfile": attr.label( 241*d4726bddSHONG Yifan doc = "The lockfile of the crate_universe resolver", 242*d4726bddSHONG Yifan allow_single_file = ["Cargo.lock"], 243*d4726bddSHONG Yifan mandatory = True, 244*d4726bddSHONG Yifan ), 245*d4726bddSHONG Yifan "cargo_toml": attr.label( 246*d4726bddSHONG Yifan doc = "The path of the crate_universe resolver manifest (`Cargo.toml` file)", 247*d4726bddSHONG Yifan allow_single_file = ["Cargo.toml"], 248*d4726bddSHONG Yifan mandatory = True, 249*d4726bddSHONG Yifan ), 250*d4726bddSHONG Yifan "env": attr.string_dict( 251*d4726bddSHONG Yifan doc = ( 252*d4726bddSHONG Yifan "A mapping of platform triple to a set of environment variables. See " + 253*d4726bddSHONG Yifan "[cargo_env](#cargo_env) for usage details. Additionally, the platform triple `*` applies to all platforms." 254*d4726bddSHONG Yifan ), 255*d4726bddSHONG Yifan ), 256*d4726bddSHONG Yifan "env_label": attr.string_dict( 257*d4726bddSHONG Yifan doc = ( 258*d4726bddSHONG Yifan "A mapping of platform triple to a set of environment variables. This " + 259*d4726bddSHONG Yifan "attribute differs from `env` in that all variables passed here must be " + 260*d4726bddSHONG Yifan "fully qualified labels of files. See [cargo_env](#cargo_env) for usage details. " + 261*d4726bddSHONG Yifan "Additionally, the platform triple `*` applies to all platforms." 262*d4726bddSHONG Yifan ), 263*d4726bddSHONG Yifan ), 264*d4726bddSHONG Yifan "rust_toolchain_cargo_template": attr.string( 265*d4726bddSHONG Yifan doc = ( 266*d4726bddSHONG Yifan "The template to use for finding the host `cargo` binary. `{version}` (eg. '1.53.0'), " + 267*d4726bddSHONG Yifan "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " + 268*d4726bddSHONG Yifan "`{system}` (eg. 'darwin'), `{channel}` (eg. 'stable'), and `{tool}` (eg. 'rustc.exe') will be " + 269*d4726bddSHONG Yifan "replaced in the string if present." 270*d4726bddSHONG Yifan ), 271*d4726bddSHONG Yifan default = "@rust_{system}_{arch}__{triple}__{channel}_tools//:bin/{tool}", 272*d4726bddSHONG Yifan ), 273*d4726bddSHONG Yifan "rust_toolchain_rustc_template": attr.string( 274*d4726bddSHONG Yifan doc = ( 275*d4726bddSHONG Yifan "The template to use for finding the host `rustc` binary. `{version}` (eg. '1.53.0'), " + 276*d4726bddSHONG Yifan "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " + 277*d4726bddSHONG Yifan "`{system}` (eg. 'darwin'), `{channel}` (eg. 'stable'), and `{tool}` (eg. 'rustc.exe') will be " + 278*d4726bddSHONG Yifan "replaced in the string if present." 279*d4726bddSHONG Yifan ), 280*d4726bddSHONG Yifan default = "@rust_{system}_{arch}__{triple}__{channel}_tools//:bin/{tool}", 281*d4726bddSHONG Yifan ), 282*d4726bddSHONG Yifan "srcs": attr.label_list( 283*d4726bddSHONG Yifan doc = "Souce files of the crate to build. Passing source files here can be used to trigger rebuilds when changes are made", 284*d4726bddSHONG Yifan allow_files = True, 285*d4726bddSHONG Yifan ), 286*d4726bddSHONG Yifan "timeout": attr.int( 287*d4726bddSHONG Yifan doc = "Maximum duration of the Cargo build command in seconds", 288*d4726bddSHONG Yifan default = 600, 289*d4726bddSHONG Yifan ), 290*d4726bddSHONG Yifan "version": attr.string( 291*d4726bddSHONG Yifan doc = "The version of Rust the currently registered toolchain is using. Eg. `1.56.0`, or `nightly/2021-09-08`", 292*d4726bddSHONG Yifan default = rust_common.default_version, 293*d4726bddSHONG Yifan ), 294*d4726bddSHONG Yifan }, 295*d4726bddSHONG Yifan) 296*d4726bddSHONG Yifan 297*d4726bddSHONG Yifandef cargo_env(env): 298*d4726bddSHONG Yifan """A helper for generating platform specific environment variables 299*d4726bddSHONG Yifan 300*d4726bddSHONG Yifan ```python 301*d4726bddSHONG Yifan load("@rules_rust//rust:defs.bzl", "rust_common") 302*d4726bddSHONG Yifan load("@rules_rust//cargo:defs.bzl", "cargo_bootstrap_repository", "cargo_env") 303*d4726bddSHONG Yifan 304*d4726bddSHONG Yifan cargo_bootstrap_repository( 305*d4726bddSHONG Yifan name = "bootstrapped_bin", 306*d4726bddSHONG Yifan cargo_lockfile = "//:Cargo.lock", 307*d4726bddSHONG Yifan cargo_toml = "//:Cargo.toml", 308*d4726bddSHONG Yifan srcs = ["//:resolver_srcs"], 309*d4726bddSHONG Yifan version = rust_common.default_version, 310*d4726bddSHONG Yifan binary = "my-crate-binary", 311*d4726bddSHONG Yifan env = { 312*d4726bddSHONG Yifan "x86_64-unknown-linux-gnu": cargo_env({ 313*d4726bddSHONG Yifan "FOO": "BAR", 314*d4726bddSHONG Yifan }), 315*d4726bddSHONG Yifan }, 316*d4726bddSHONG Yifan env_label = { 317*d4726bddSHONG Yifan "aarch64-unknown-linux-musl": cargo_env({ 318*d4726bddSHONG Yifan "DOC": "//:README.md", 319*d4726bddSHONG Yifan }), 320*d4726bddSHONG Yifan } 321*d4726bddSHONG Yifan ) 322*d4726bddSHONG Yifan ``` 323*d4726bddSHONG Yifan 324*d4726bddSHONG Yifan Args: 325*d4726bddSHONG Yifan env (dict): A map of environment variables 326*d4726bddSHONG Yifan 327*d4726bddSHONG Yifan Returns: 328*d4726bddSHONG Yifan str: A json encoded string of the environment variables 329*d4726bddSHONG Yifan """ 330*d4726bddSHONG Yifan return json.encode(dict(env)) 331