blob: c799647a233988b323a0552dbfa18ee3dd5af9f5 [file]
"""Utilities directly related to the `splicing` step of `cargo-bazel`."""
load(":common_utils.bzl", "CARGO_BAZEL_DEBUG", "CARGO_BAZEL_REPIN", "REPIN")
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,
cargo_bazel_fn,
cargo_lockfile,
splicing_manifest,
config_path,
output_dir,
debug_workspace_dir = None):
"""Splice together a Cargo workspace from various other manifests and package definitions
Args:
repository_ctx (repository_ctx or module_ctx): The repository's context object.
cargo_bazel_fn (callable): A callback for invoking 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 (path): The path to the config file (containing `cargo_bazel::config::Config`.)
output_dir (path): THe location in which to write splicing outputs.
debug_workspace_dir (path): The location in which to save splicing outputs for future review.
Returns:
path: The path to a Cargo metadata json file found in the spliced workspace root.
"""
# Generate a workspace root which contains all workspace members
arguments = [
"splice",
"--output-dir",
output_dir,
"--splicing-manifest",
splicing_manifest,
"--config",
config_path,
]
if cargo_lockfile:
arguments.extend([
"--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:
if debug_workspace_dir == None:
debug_workspace_dir = repository_ctx.path("splicing-workspace")
arguments.extend([
"--workspace-dir",
debug_workspace_dir,
])
env = {}
# 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]
cargo_bazel_fn(
args = arguments,
env = env,
)
# This file must have been produced by the execution above.
spliced_lockfile = repository_ctx.path(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(output_dir.get_child("metadata.json"))
if not spliced_metadata.exists:
fail("Metadata file does not exist: " + str(spliced_metadata))
return struct(
metadata = spliced_metadata,
cargo_lock = spliced_lockfile,
)