• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 splciing 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 splicier 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 dict(splicing_config.items() + splicing_manifest_content.items())
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 splciing 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    repo_dir = repository_ctx.path(".")
98
99    splicing_manifest = repository_ctx.path("{}/splicing_manifest.json".format(repo_dir))
100
101    data = compile_splicing_manifest(
102        splicing_config = config,
103        manifests = manifests,
104        cargo_config_path = cargo_config,
105        packages = repository_ctx.attr.packages,
106    )
107
108    # Serialize information required for splicing
109    repository_ctx.file(
110        splicing_manifest,
111        json.encode_indent(
112            data,
113            indent = " " * 4,
114        ),
115    )
116
117    return splicing_manifest
118
119def splice_workspace_manifest(repository_ctx, generator, cargo_lockfile, splicing_manifest, config_path, cargo, rustc):
120    """Splice together a Cargo workspace from various other manifests and package definitions
121
122    Args:
123        repository_ctx (repository_ctx): The rule's context object.
124        generator (path): The `cargo-bazel` binary.
125        cargo_lockfile (path): The path to a "Cargo.lock" file.
126        splicing_manifest (path): The path to a splicing manifest.
127        config_path: The path to the config file (containing `cargo_bazel::config::Config`.)
128        cargo (path): The path to a Cargo binary.
129        rustc (path): The Path to a Rustc binary.
130
131    Returns:
132        path: The path to a Cargo metadata json file found in the spliced workspace root.
133    """
134    repository_ctx.report_progress("Splicing Cargo workspace.")
135    repo_dir = repository_ctx.path(".")
136
137    splicing_output_dir = repository_ctx.path("splicing-output")
138
139    # Generate a workspace root which contains all workspace members
140    arguments = [
141        generator,
142        "splice",
143        "--output-dir",
144        splicing_output_dir,
145        "--splicing-manifest",
146        splicing_manifest,
147        "--config",
148        config_path,
149        "--cargo",
150        cargo,
151        "--rustc",
152        rustc,
153        "--cargo-lockfile",
154        cargo_lockfile,
155    ]
156
157    # Optionally set the splicing workspace directory to somewhere within the repository directory
158    # to improve the debugging experience.
159    if CARGO_BAZEL_DEBUG in repository_ctx.os.environ:
160        arguments.extend([
161            "--workspace-dir",
162            repository_ctx.path("{}/splicing-workspace".format(repo_dir)),
163        ])
164
165    env = {
166        "CARGO": str(cargo),
167        "RUSTC": str(rustc),
168        "RUST_BACKTRACE": "full",
169    }
170
171    # Ensure the short hand repin variable is set to the full name.
172    if REPIN in repository_ctx.os.environ and CARGO_BAZEL_REPIN not in repository_ctx.os.environ:
173        env.update({CARGO_BAZEL_REPIN: repository_ctx.os.environ[REPIN]})
174
175    # Add any Cargo environment variables to the `cargo-bazel` execution
176    env.update(cargo_environ(repository_ctx))
177
178    execute(
179        repository_ctx = repository_ctx,
180        args = arguments,
181        env = env,
182    )
183
184    # This file must have been produced by the execution above.
185    spliced_lockfile = repository_ctx.path("{}/Cargo.lock".format(splicing_output_dir))
186    if not spliced_lockfile.exists:
187        fail("Lockfile file does not exist: {}".format(spliced_lockfile))
188    spliced_metadata = repository_ctx.path("{}/metadata.json".format(splicing_output_dir))
189    if not spliced_metadata.exists:
190        fail("Metadata file does not exist: {}".format(spliced_metadata))
191
192    return spliced_metadata
193