Squashed 'third_party/rules_rust/' content from commit bf59038ca
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/crate_universe/private/generate_utils.bzl b/crate_universe/private/generate_utils.bzl
new file mode 100644
index 0000000..91e8c6e
--- /dev/null
+++ b/crate_universe/private/generate_utils.bzl
@@ -0,0 +1,410 @@
+"""Utilities directly related to the `generate` step of `cargo-bazel`."""
+
+load(":common_utils.bzl", "CARGO_BAZEL_ISOLATED", "cargo_environ", "execute")
+
+CARGO_BAZEL_GENERATOR_SHA256 = "CARGO_BAZEL_GENERATOR_SHA256"
+CARGO_BAZEL_GENERATOR_URL = "CARGO_BAZEL_GENERATOR_URL"
+CARGO_BAZEL_REPIN = "CARGO_BAZEL_REPIN"
+REPIN = "REPIN"
+
+GENERATOR_ENV_VARS = [
+ CARGO_BAZEL_GENERATOR_URL,
+ CARGO_BAZEL_GENERATOR_SHA256,
+]
+
+REPIN_ENV_VARS = [
+ REPIN,
+ CARGO_BAZEL_REPIN,
+]
+
+CRATES_REPOSITORY_ENVIRON = GENERATOR_ENV_VARS + REPIN_ENV_VARS + [
+ CARGO_BAZEL_ISOLATED,
+]
+
+def get_generator(repository_ctx, host_triple):
+ """Query network resources to locate a `cargo-bazel` binary
+
+ Args:
+ repository_ctx (repository_ctx): The rule's context object.
+ host_triple (string): A string representing the host triple
+
+ Returns:
+ tuple(path, dict): The path to a `cargo-bazel` binary and the host sha256 pairing.
+ The pairing (dict) may be `None` if there is no need to update the attribute
+ """
+ use_environ = False
+ for var in GENERATOR_ENV_VARS:
+ if var in repository_ctx.os.environ:
+ use_environ = True
+
+ output = repository_ctx.path("cargo-bazel.exe" if "win" in repository_ctx.os.name else "cargo-bazel")
+
+ # The `generator` attribute is the next highest priority behind
+ # environment variables. We check those first before deciding to
+ # use an explicitly provided variable.
+ if not use_environ and repository_ctx.attr.generator:
+ generator = repository_ctx.path(Label(repository_ctx.attr.generator))
+
+ # Resolve a few levels of symlinks to ensure we're accessing the direct binary
+ for _ in range(1, 100):
+ real_generator = generator.realpath
+ if real_generator == generator:
+ break
+ generator = real_generator
+ return generator, None
+
+ # The environment variable will take precedence if set
+ if use_environ:
+ generator_sha256 = repository_ctx.os.environ.get(CARGO_BAZEL_GENERATOR_SHA256)
+ generator_url = repository_ctx.os.environ.get(CARGO_BAZEL_GENERATOR_URL)
+ else:
+ generator_sha256 = repository_ctx.attr.generator_sha256s.get(host_triple)
+ generator_url = repository_ctx.attr.generator_urls.get(host_triple)
+
+ if not generator_url:
+ fail((
+ "No generator URL was found either in the `CARGO_BAZEL_GENERATOR_URL` " +
+ "environment variable or for the `{}` triple in the `generator_urls` attribute"
+ ).format(host_triple))
+
+ # Download the file into place
+ if generator_sha256:
+ repository_ctx.download(
+ output = output,
+ url = generator_url,
+ sha256 = generator_sha256,
+ executable = True,
+ )
+ return output, None
+
+ result = repository_ctx.download(
+ output = output,
+ url = generator_url,
+ executable = True,
+ )
+
+ return output, {host_triple: result.sha256}
+
+def render_config(
+ build_file_template = "//:BUILD.{name}-{version}.bazel",
+ crate_label_template = "@{repository}__{name}-{version}//:{target}",
+ crate_repository_template = "{repository}__{name}-{version}",
+ crates_module_template = "//:{file}",
+ default_package_name = None,
+ platforms_template = "@rules_rust//rust/platform:{triple}",
+ vendor_mode = None):
+ """Various settings used to configure rendered outputs
+
+ The template parameters each support a select number of format keys. A description of each key
+ can be found below where the supported keys for each template can be found in the parameter docs
+
+ | key | definition |
+ | --- | --- |
+ | `name` | The name of the crate. Eg `tokio` |
+ | `repository` | The rendered repository name for the crate. Directly relates to `crate_repository_template`. |
+ | `triple` | A platform triple. Eg `x86_64-unknown-linux-gnu` |
+ | `version` | The crate version. Eg `1.2.3` |
+ | `target` | The library or binary target of the crate |
+ | `file` | The basename of a file |
+
+ Args:
+ build_file_template (str, optional): The base template to use for BUILD file names. The available format keys
+ are [`{name}`, {version}`].
+ crate_label_template (str, optional): The base template to use for crate labels. The available format keys
+ are [`{repository}`, `{name}`, `{version}`, `{target}`].
+ crate_repository_template (str, optional): The base template to use for Crate label repository names. The
+ available format keys are [`{repository}`, `{name}`, `{version}`].
+ crates_module_template (str, optional): The pattern to use for the `defs.bzl` and `BUILD.bazel`
+ file names used for the crates module. The available format keys are [`{file}`].
+ default_package_name (str, optional): The default package name to in the rendered macros. This affects the
+ auto package detection of things like `all_crate_deps`.
+ platforms_template (str, optional): The base template to use for platform names.
+ See [platforms documentation](https://docs.bazel.build/versions/main/platforms.html). The available format
+ keys are [`{triple}`].
+ vendor_mode (str, optional): An optional configuration for rendirng content to be rendered into repositories.
+
+ Returns:
+ string: A json encoded struct to match the Rust `config::RenderConfig` struct
+ """
+ return json.encode(struct(
+ build_file_template = build_file_template,
+ crate_label_template = crate_label_template,
+ crate_repository_template = crate_repository_template,
+ crates_module_template = crates_module_template,
+ default_package_name = default_package_name,
+ platforms_template = platforms_template,
+ vendor_mode = vendor_mode,
+ ))
+
+def _crate_id(name, version):
+ """Creates a `cargo_bazel::config::CrateId`.
+
+ Args:
+ name (str): The name of the crate
+ version (str): The crate's version
+
+ Returns:
+ str: A serialized representation of a CrateId
+ """
+ return "{} {}".format(name, version)
+
+def collect_crate_annotations(annotations, repository_name):
+ """Deserialize and sanitize crate annotations.
+
+ Args:
+ annotations (dict): A mapping of crate names to lists of serialized annotations
+ repository_name (str): The name of the repository that owns the annotations
+
+ Returns:
+ dict: A mapping of `cargo_bazel::config::CrateId` to sets of annotations
+ """
+ annotations = {name: [json.decode(a) for a in annotation] for name, annotation in annotations.items()}
+ crate_annotations = {}
+ for name, annotation in annotations.items():
+ for (version, data) in annotation:
+ if name == "*" and version != "*":
+ fail(
+ "Wildcard crate names must have wildcard crate versions. " +
+ "Please update the `annotations` attribute of the {} crates_repository".format(
+ repository_name,
+ ),
+ )
+ id = _crate_id(name, version)
+ if id in crate_annotations:
+ fail("Found duplicate entries for {}".format(id))
+
+ crate_annotations.update({id: data})
+ return crate_annotations
+
+def _read_cargo_config(repository_ctx):
+ if repository_ctx.attr.cargo_config:
+ config = repository_ctx.path(repository_ctx.attr.cargo_config)
+ return repository_ctx.read(config)
+ return None
+
+def _get_render_config(repository_ctx):
+ if repository_ctx.attr.render_config:
+ config = dict(json.decode(repository_ctx.attr.render_config))
+ else:
+ config = dict(json.decode(render_config()))
+
+ # Add the repository name as it's very relevant to rendering.
+ config.update({"repository_name": repository_ctx.name})
+
+ return struct(**config)
+
+def generate_config(repository_ctx):
+ """Generate a config file from various attributes passed to the rule.
+
+ Args:
+ repository_ctx (repository_ctx): The rule's context object.
+
+ Returns:
+ struct: A struct containing the path to a config and it's contents
+ """
+ annotations = collect_crate_annotations(repository_ctx.attr.annotations, repository_ctx.name)
+
+ # Load additive build files if any have been provided.
+ content = list()
+ for data in annotations.values():
+ additive_build_file_content = data.pop("additive_build_file_content", None)
+ if additive_build_file_content:
+ content.append(additive_build_file_content)
+ additive_build_file = data.pop("additive_build_file", None)
+ if additive_build_file:
+ file_path = repository_ctx.path(Label(additive_build_file))
+ content.append(repository_ctx.read(file_path))
+ data.update({"additive_build_file_content": "\n".join(content) if content else None})
+
+ config = struct(
+ generate_build_scripts = repository_ctx.attr.generate_build_scripts,
+ annotations = annotations,
+ cargo_config = _read_cargo_config(repository_ctx),
+ rendering = _get_render_config(repository_ctx),
+ supported_platform_triples = repository_ctx.attr.supported_platform_triples,
+ )
+
+ config_path = repository_ctx.path("cargo-bazel.json")
+ repository_ctx.file(
+ config_path,
+ json.encode_indent(config, indent = " " * 4),
+ )
+
+ # This was originally written to return a struct and not just the config path
+ # so splicing can have access to some rendering information embedded in the config
+ # If splicing should no longer need that info, it'd be simpler to just return a `path`.
+ return struct(
+ path = config_path,
+ info = config,
+ )
+
+def get_lockfile(repository_ctx):
+ """Locate the lockfile and identify the it's type (Cargo or Bazel).
+
+ Args:
+ repository_ctx (repository_ctx): The rule's context object.
+
+ Returns:
+ struct: The path to the lockfile as well as it's type
+ """
+ if repository_ctx.attr.lockfile_kind == "auto":
+ if str(repository_ctx.attr.lockfile).endswith("Cargo.lock"):
+ kind = "cargo"
+ else:
+ kind = "bazel"
+ else:
+ kind = repository_ctx.attr.lockfile_kind
+
+ return struct(
+ path = repository_ctx.path(repository_ctx.attr.lockfile),
+ kind = kind,
+ )
+
+def determine_repin(repository_ctx, generator, lockfile_path, lockfile_kind, config, splicing_manifest, cargo, rustc):
+ """Use the `cargo-bazel` binary to determine whether or not dpeendencies need to be re-pinned
+
+ Args:
+ repository_ctx (repository_ctx): The rule's context object.
+ generator (path): The path to a `cargo-bazel` binary.
+ config (path): The path to a `cargo-bazel` config file. See `generate_config`.
+ splicing_manifest (path): The path to a `cargo-bazel` splicing manifest. See `create_splicing_manifest`
+ lockfile_path (path): The path to a "lock" file for reproducible outputs.
+ lockfile_kind (str): The type of lock file represented by `lockfile_path`
+ cargo (path): The path to a Cargo binary.
+ rustc (path): The path to a Rustc binary.
+
+ Returns:
+ bool: True if dependencies need to be re-pinned
+ """
+
+ # If a repin environment variable is set, always repin
+ for var in REPIN_ENV_VARS:
+ if repository_ctx.os.environ.get(var, "").lower() in ["true", "yes", "1", "on"]:
+ return True
+
+ # Cargo lockfiles should always be repinned.
+ if lockfile_kind == "cargo":
+ return True
+
+ # Run the binary to check if a repin is needed
+ args = [
+ generator,
+ "query",
+ "--lockfile",
+ lockfile_path,
+ "--config",
+ config,
+ "--splicing-manifest",
+ splicing_manifest,
+ "--cargo",
+ cargo,
+ "--rustc",
+ rustc,
+ ]
+
+ env = {
+ "CARGO": str(cargo),
+ "RUSTC": str(rustc),
+ "RUST_BACKTRACE": "full",
+ }
+
+ # Add any Cargo environment variables to the `cargo-bazel` execution
+ env.update(cargo_environ(repository_ctx))
+
+ result = execute(
+ repository_ctx = repository_ctx,
+ args = args,
+ env = env,
+ )
+
+ # If it was determined repinning should occur but there was no
+ # flag indicating repinning was requested, an error is raised
+ # since repinning should be an explicit action
+ if result.stdout.strip().lower() == "repin":
+ # buildifier: disable=print
+ print(result.stderr)
+ fail((
+ "The current `lockfile` is out of date for '{}'. Please re-run " +
+ "bazel using `CARGO_BAZEL_REPIN=true` if this is expected " +
+ "and the lockfile should be updated."
+ ).format(repository_ctx.name))
+
+ return False
+
+def execute_generator(
+ repository_ctx,
+ lockfile_path,
+ lockfile_kind,
+ generator,
+ config,
+ splicing_manifest,
+ repository_dir,
+ cargo,
+ rustc,
+ repin = False,
+ metadata = None):
+ """Execute the `cargo-bazel` binary to produce `BUILD` and `.bzl` files.
+
+ Args:
+ repository_ctx (repository_ctx): The rule's context object.
+ lockfile_path (path): The path to a "lock" file (file used for reproducible renderings).
+ lockfile_kind (str): The type of lockfile given (Cargo or Bazel).
+ generator (path): The path to a `cargo-bazel` binary.
+ config (path): The path to a `cargo-bazel` config file.
+ splicing_manifest (path): The path to a `cargo-bazel` splicing manifest. See `create_splicing_manifest`
+ repository_dir (path): The output path for the Bazel module and BUILD files.
+ cargo (path): The path of a Cargo binary.
+ rustc (path): The path of a Rustc binary.
+ repin (bool, optional): Whether or not to repin dependencies
+ metadata (path, optional): The path to a Cargo metadata json file.
+
+ Returns:
+ struct: The results of `repository_ctx.execute`.
+ """
+ repository_ctx.report_progress("Generating crate BUILD files.")
+
+ args = [
+ generator,
+ "generate",
+ "--lockfile",
+ lockfile_path,
+ "--lockfile-kind",
+ lockfile_kind,
+ "--config",
+ config,
+ "--splicing-manifest",
+ splicing_manifest,
+ "--repository-dir",
+ repository_dir,
+ "--cargo",
+ cargo,
+ "--rustc",
+ rustc,
+ ]
+
+ env = {
+ "RUST_BACKTRACE": "full",
+ }
+
+ # Some components are not required unless re-pinning is enabled
+ if repin:
+ args.extend([
+ "--repin",
+ "--metadata",
+ metadata,
+ ])
+ env.update({
+ "CARGO": str(cargo),
+ "RUSTC": str(rustc),
+ })
+
+ # Add any Cargo environment variables to the `cargo-bazel` execution
+ env.update(cargo_environ(repository_ctx))
+
+ result = execute(
+ repository_ctx = repository_ctx,
+ args = args,
+ env = env,
+ )
+
+ return result