| """Utilities directly related to the `splicing` step of `cargo-bazel`.""" |
| |
| load(":common_utils.bzl", "CARGO_BAZEL_DEBUG", "CARGO_BAZEL_REPIN", "REPIN", "cargo_environ", "execute") |
| |
| def splicing_config(resolver_version = "2"): |
| """Various settings used to configure Cargo manifest splicing behavior. |
| |
| [rv]: https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions |
| |
| Args: |
| resolver_version (str, optional): The [resolver version][rv] to use in generated Cargo |
| manifests. This flag is **only** used when splicing a manifest from direct package |
| definitions. See `crates_repository::packages`. |
| |
| Returns: |
| str: A json encoded string of the parameters provided |
| """ |
| return json.encode(struct( |
| resolver_version = resolver_version, |
| )) |
| |
| def kebab_case_keys(data): |
| """Ensure the key value of the data given are kebab-case |
| |
| Args: |
| data (dict): A deserialized json blob |
| |
| Returns: |
| dict: The same `data` but with kebab-case keys |
| """ |
| return { |
| key.lower().replace("_", "-"): val |
| for (key, val) in data.items() |
| } |
| |
| def compile_splicing_manifest(splicing_config, manifests, cargo_config_path, packages): |
| """Produce a manifest containing required components for splicing a new Cargo workspace |
| |
| [cargo_config]: https://doc.rust-lang.org/cargo/reference/config.html |
| [cargo_toml]: https://doc.rust-lang.org/cargo/reference/manifest.html |
| |
| Args: |
| splicing_config (dict): A deserialized `splicing_config` |
| manifests (dict): A mapping of paths to Bazel labels which represent [Cargo manifests][cargo_toml]. |
| cargo_config_path (str): The absolute path to a [Cargo config][cargo_config]. |
| packages (dict): A set of crates (packages) specifications to depend on |
| |
| Returns: |
| dict: A dictionary representation of a `cargo_bazel::splicing::SplicingManifest` |
| """ |
| |
| # Deserialize information about direct packges |
| direct_packages_info = { |
| # Ensure the data is using kebab-case as that's what `cargo_toml::DependencyDetail` expects. |
| pkg: kebab_case_keys(dict(json.decode(data))) |
| for (pkg, data) in packages.items() |
| } |
| |
| # Auto-generated splicer manifest values |
| splicing_manifest_content = { |
| "cargo_config": cargo_config_path, |
| "direct_packages": direct_packages_info, |
| "manifests": manifests, |
| } |
| |
| return splicing_config | splicing_manifest_content |
| |
| def _no_at_label(label): |
| """Strips leading '@'s for stringified labels in the main repository for backwards-comaptibility reasons.""" |
| s = str(label) |
| if s.startswith("@@//"): |
| return s[2:] |
| if s.startswith("@//"): |
| return s[1:] |
| return s |
| |
| def create_splicing_manifest(repository_ctx): |
| """Produce a manifest containing required components for splicing a new Cargo workspace |
| |
| Args: |
| repository_ctx (repository_ctx): The rule's context object. |
| |
| Returns: |
| path: The path to a json encoded manifest |
| """ |
| |
| manifests = {str(repository_ctx.path(m)): _no_at_label(m) for m in repository_ctx.attr.manifests} |
| |
| if repository_ctx.attr.cargo_config: |
| cargo_config = str(repository_ctx.path(repository_ctx.attr.cargo_config)) |
| else: |
| cargo_config = None |
| |
| # Load user configurable splicing settings |
| config = json.decode(repository_ctx.attr.splicing_config or splicing_config()) |
| |
| splicing_manifest = repository_ctx.path("splicing_manifest.json") |
| |
| data = compile_splicing_manifest( |
| splicing_config = config, |
| manifests = manifests, |
| cargo_config_path = cargo_config, |
| packages = repository_ctx.attr.packages, |
| ) |
| |
| # Serialize information required for splicing |
| repository_ctx.file( |
| splicing_manifest, |
| json.encode_indent( |
| data, |
| indent = " " * 4, |
| ), |
| ) |
| |
| return splicing_manifest |
| |
| def splice_workspace_manifest(repository_ctx, generator, cargo_lockfile, splicing_manifest, config_path, cargo, rustc): |
| """Splice together a Cargo workspace from various other manifests and package definitions |
| |
| Args: |
| repository_ctx (repository_ctx): The rule's context object. |
| generator (path): The `cargo-bazel` binary. |
| cargo_lockfile (path): The path to a "Cargo.lock" file. |
| splicing_manifest (path): The path to a splicing manifest. |
| config_path: The path to the config file (containing `cargo_bazel::config::Config`.) |
| cargo (path): The path to a Cargo binary. |
| rustc (path): The Path to a Rustc binary. |
| |
| Returns: |
| path: The path to a Cargo metadata json file found in the spliced workspace root. |
| """ |
| repository_ctx.report_progress("Splicing Cargo workspace.") |
| |
| splicing_output_dir = repository_ctx.path("splicing-output") |
| |
| # Generate a workspace root which contains all workspace members |
| arguments = [ |
| generator, |
| "splice", |
| "--output-dir", |
| splicing_output_dir, |
| "--splicing-manifest", |
| splicing_manifest, |
| "--config", |
| config_path, |
| "--cargo", |
| cargo, |
| "--rustc", |
| rustc, |
| "--cargo-lockfile", |
| cargo_lockfile, |
| ] |
| |
| # Optionally set the splicing workspace directory to somewhere within the repository directory |
| # to improve the debugging experience. |
| if CARGO_BAZEL_DEBUG in repository_ctx.os.environ: |
| arguments.extend([ |
| "--workspace-dir", |
| repository_ctx.path("splicing-workspace"), |
| ]) |
| |
| env = { |
| "CARGO": str(cargo), |
| "RUSTC": str(rustc), |
| "RUST_BACKTRACE": "full", |
| } |
| |
| # Ensure the short hand repin variable is set to the full name. |
| if REPIN in repository_ctx.os.environ and CARGO_BAZEL_REPIN not in repository_ctx.os.environ: |
| env["CARGO_BAZEL_REPIN"] = repository_ctx.os.environ[REPIN] |
| |
| # Add any Cargo environment variables to the `cargo-bazel` execution |
| env |= cargo_environ(repository_ctx) |
| |
| execute( |
| repository_ctx = repository_ctx, |
| args = arguments, |
| env = env, |
| ) |
| |
| # This file must have been produced by the execution above. |
| spliced_lockfile = repository_ctx.path(splicing_output_dir.get_child("Cargo.lock")) |
| if not spliced_lockfile.exists: |
| fail("Lockfile file does not exist: " + str(spliced_lockfile)) |
| spliced_metadata = repository_ctx.path(splicing_output_dir.get_child("metadata.json")) |
| if not spliced_metadata.exists: |
| fail("Metadata file does not exist: " + str(spliced_metadata)) |
| |
| return spliced_metadata |