xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/private/splicing_utils.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1"""Utilities directly related to the `splicing` step of `cargo-bazel`."""
2
3load(":common_utils.bzl", "CARGO_BAZEL_DEBUG", "CARGO_BAZEL_REPIN", "REPIN", "cargo_environ", "execute")
4
5def splicing_config(resolver_version = "2"):
6    """Various settings used to configure Cargo manifest splicing behavior.
7
8    [rv]: https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
9
10    Args:
11        resolver_version (str, optional): The [resolver version][rv] to use in generated Cargo
12            manifests. This flag is **only** used when splicing a manifest from direct package
13            definitions. See `crates_repository::packages`.
14
15    Returns:
16        str: A json encoded string of the parameters provided
17    """
18    return json.encode(struct(
19        resolver_version = resolver_version,
20    ))
21
22def kebab_case_keys(data):
23    """Ensure the key value of the data given are kebab-case
24
25    Args:
26        data (dict): A deserialized json blob
27
28    Returns:
29        dict: The same `data` but with kebab-case keys
30    """
31    return {
32        key.lower().replace("_", "-"): val
33        for (key, val) in data.items()
34    }
35
36def compile_splicing_manifest(splicing_config, manifests, cargo_config_path, packages):
37    """Produce a manifest containing required components for splicing a new Cargo workspace
38
39    [cargo_config]: https://doc.rust-lang.org/cargo/reference/config.html
40    [cargo_toml]: https://doc.rust-lang.org/cargo/reference/manifest.html
41
42    Args:
43        splicing_config (dict): A deserialized `splicing_config`
44        manifests (dict): A mapping of paths to Bazel labels which represent [Cargo manifests][cargo_toml].
45        cargo_config_path (str): The absolute path to a [Cargo config][cargo_config].
46        packages (dict): A set of crates (packages) specifications to depend on
47
48    Returns:
49        dict: A dictionary representation of a `cargo_bazel::splicing::SplicingManifest`
50    """
51
52    # Deserialize information about direct packges
53    direct_packages_info = {
54        # Ensure the data is using kebab-case as that's what `cargo_toml::DependencyDetail` expects.
55        pkg: kebab_case_keys(dict(json.decode(data)))
56        for (pkg, data) in packages.items()
57    }
58
59    # Auto-generated splicer manifest values
60    splicing_manifest_content = {
61        "cargo_config": cargo_config_path,
62        "direct_packages": direct_packages_info,
63        "manifests": manifests,
64    }
65
66    return splicing_config | splicing_manifest_content
67
68def _no_at_label(label):
69    """Strips leading '@'s for stringified labels in the main repository for backwards-comaptibility reasons."""
70    s = str(label)
71    if s.startswith("@@//"):
72        return s[2:]
73    if s.startswith("@//"):
74        return s[1:]
75    return s
76
77def create_splicing_manifest(repository_ctx):
78    """Produce a manifest containing required components for splicing a new Cargo workspace
79
80    Args:
81        repository_ctx (repository_ctx): The rule's context object.
82
83    Returns:
84        path: The path to a json encoded manifest
85    """
86
87    manifests = {str(repository_ctx.path(m)): _no_at_label(m) for m in repository_ctx.attr.manifests}
88
89    if repository_ctx.attr.cargo_config:
90        cargo_config = str(repository_ctx.path(repository_ctx.attr.cargo_config))
91    else:
92        cargo_config = None
93
94    # Load user configurable splicing settings
95    config = json.decode(repository_ctx.attr.splicing_config or splicing_config())
96
97    splicing_manifest = repository_ctx.path("splicing_manifest.json")
98
99    data = compile_splicing_manifest(
100        splicing_config = config,
101        manifests = manifests,
102        cargo_config_path = cargo_config,
103        packages = repository_ctx.attr.packages,
104    )
105
106    # Serialize information required for splicing
107    repository_ctx.file(
108        splicing_manifest,
109        json.encode_indent(
110            data,
111            indent = " " * 4,
112        ),
113    )
114
115    return splicing_manifest
116
117def splice_workspace_manifest(repository_ctx, generator, cargo_lockfile, splicing_manifest, config_path, cargo, rustc):
118    """Splice together a Cargo workspace from various other manifests and package definitions
119
120    Args:
121        repository_ctx (repository_ctx): The rule's context object.
122        generator (path): The `cargo-bazel` binary.
123        cargo_lockfile (path): The path to a "Cargo.lock" file.
124        splicing_manifest (path): The path to a splicing manifest.
125        config_path: The path to the config file (containing `cargo_bazel::config::Config`.)
126        cargo (path): The path to a Cargo binary.
127        rustc (path): The Path to a Rustc binary.
128
129    Returns:
130        path: The path to a Cargo metadata json file found in the spliced workspace root.
131    """
132    repository_ctx.report_progress("Splicing Cargo workspace.")
133
134    splicing_output_dir = repository_ctx.path("splicing-output")
135
136    # Generate a workspace root which contains all workspace members
137    arguments = [
138        generator,
139        "splice",
140        "--output-dir",
141        splicing_output_dir,
142        "--splicing-manifest",
143        splicing_manifest,
144        "--config",
145        config_path,
146        "--cargo",
147        cargo,
148        "--rustc",
149        rustc,
150        "--cargo-lockfile",
151        cargo_lockfile,
152    ]
153
154    # Optionally set the splicing workspace directory to somewhere within the repository directory
155    # to improve the debugging experience.
156    if CARGO_BAZEL_DEBUG in repository_ctx.os.environ:
157        arguments.extend([
158            "--workspace-dir",
159            repository_ctx.path("splicing-workspace"),
160        ])
161
162    env = {
163        "CARGO": str(cargo),
164        "RUSTC": str(rustc),
165        "RUST_BACKTRACE": "full",
166    }
167
168    # Ensure the short hand repin variable is set to the full name.
169    if REPIN in repository_ctx.os.environ and CARGO_BAZEL_REPIN not in repository_ctx.os.environ:
170        env["CARGO_BAZEL_REPIN"] = repository_ctx.os.environ[REPIN]
171
172    # Add any Cargo environment variables to the `cargo-bazel` execution
173    env |= cargo_environ(repository_ctx)
174
175    execute(
176        repository_ctx = repository_ctx,
177        args = arguments,
178        env = env,
179    )
180
181    # This file must have been produced by the execution above.
182    spliced_lockfile = repository_ctx.path(splicing_output_dir.get_child("Cargo.lock"))
183    if not spliced_lockfile.exists:
184        fail("Lockfile file does not exist: " + str(spliced_lockfile))
185    spliced_metadata = repository_ctx.path(splicing_output_dir.get_child("metadata.json"))
186    if not spliced_metadata.exists:
187        fail("Metadata file does not exist: " + str(spliced_metadata))
188
189    return spliced_metadata
190