Skip to content

Commit db03e3e

Browse files
authored
Add annotation support for transient crates. (#2392)
1 parent df73e91 commit db03e3e

File tree

5 files changed

+141
-6
lines changed

5 files changed

+141
-6
lines changed

crate_universe/extension.bzl

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
11
"""Module extension for generating third-party crates for use in bazel."""
22

3+
load("@bazel_skylib//lib:structs.bzl", "structs")
34
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
45
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
6+
load("//crate_universe:defs.bzl", _crate_universe_crate = "crate")
57
load("//crate_universe/private:crates_vendor.bzl", "CRATES_VENDOR_ATTRS", "generate_config_file", "generate_splicing_manifest")
68
load("//crate_universe/private:generate_utils.bzl", "render_config")
79
load("//crate_universe/private/module_extensions:cargo_bazel_bootstrap.bzl", "get_cargo_bazel_runner")
810

11+
# A list of labels which may be relative (and if so, is within the repo the rule is generated in).
12+
#
13+
# If I were to write ":foo", with attr.label_list, it would evaluate to
14+
# "@@//:foo". However, for a tag such as deps, ":foo" should refer to
15+
# "@@rules_rust~crates~<crate>//:foo".
16+
_relative_label_list = attr.string_list
17+
18+
_OPT_BOOL_VALUES = {
19+
"auto": None,
20+
"off": False,
21+
"on": True,
22+
}
23+
24+
def optional_bool(doc):
25+
return attr.string(doc = doc, values = _OPT_BOOL_VALUES.keys(), default = "auto")
26+
27+
def _get_or_insert(d, key, value):
28+
if key not in d:
29+
d[key] = value
30+
return d[key]
31+
932
def _generate_repo_impl(repo_ctx):
1033
for path, contents in repo_ctx.attr.contents.items():
1134
repo_ctx.file(path, contents)
@@ -17,7 +40,7 @@ _generate_repo = repository_rule(
1740
),
1841
)
1942

20-
def _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg):
43+
def _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg, annotations):
2144
cargo_lockfile = module_ctx.path(cfg.cargo_lockfile)
2245
tag_path = module_ctx.path(cfg.name)
2346

@@ -31,7 +54,7 @@ def _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg):
3154
content = generate_config_file(
3255
module_ctx,
3356
mode = "remote",
34-
annotations = {},
57+
annotations = annotations,
3558
generate_build_scripts = cfg.generate_build_scripts,
3659
supported_platform_triples = cfg.supported_platform_triples,
3760
generate_target_compatible_with = True,
@@ -40,6 +63,7 @@ def _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg):
4063
workspace_name = cfg.name,
4164
generate_binaries = cfg.generate_binaries,
4265
render_config = rendering_config,
66+
repository_ctx = module_ctx,
4367
),
4468
)
4569

@@ -160,16 +184,54 @@ def _crate_impl(module_ctx):
160184
cargo_bazel = get_cargo_bazel_runner(module_ctx)
161185
all_repos = []
162186
for mod in module_ctx.modules:
187+
module_annotations = {}
188+
repo_specific_annotations = {}
189+
for annotation_tag in mod.tags.annotation:
190+
annotation_dict = structs.to_dict(annotation_tag)
191+
repositories = annotation_dict.pop("repositories")
192+
crate = annotation_dict.pop("crate")
193+
194+
# The crate.annotation function can take in either a list or a bool.
195+
# For the tag-based method, because it has type safety, we have to
196+
# split it into two parameters.
197+
if annotation_dict.pop("gen_all_binaries"):
198+
annotation_dict["gen_binaries"] = True
199+
annotation_dict["gen_build_script"] = _OPT_BOOL_VALUES[annotation_dict["gen_build_script"]]
200+
annotation = _crate_universe_crate.annotation(**{
201+
k: v
202+
for k, v in annotation_dict.items()
203+
# Tag classes can't take in None, but the function requires None
204+
# instead of the empty values in many cases.
205+
# https://github.com/bazelbuild/bazel/issues/20744
206+
if v != "" and v != [] and v != {}
207+
})
208+
if not repositories:
209+
_get_or_insert(module_annotations, crate, []).append(annotation)
210+
for repo in repositories:
211+
_get_or_insert(
212+
_get_or_insert(repo_specific_annotations, repo, {}),
213+
crate,
214+
[],
215+
).append(annotation)
216+
163217
local_repos = []
164218
for cfg in mod.tags.from_cargo:
165219
if cfg.name in local_repos:
166220
fail("Defined two crate universes with the same name in the same MODULE.bazel file. Use the name tag to give them different names.")
167221
elif cfg.name in all_repos:
168222
fail("Defined two crate universes with the same name in different MODULE.bazel files. Either give one a different name, or use use_extension(isolate=True)")
169-
_generate_hub_and_spokes(module_ctx, cargo_bazel, cfg)
223+
224+
annotations = {k: v for k, v in module_annotations.items()}
225+
for crate, values in repo_specific_annotations.get(cfg.name, {}).items():
226+
_get_or_insert(annotations, crate, []).extend(values)
227+
_generate_hub_and_spokes(module_ctx, cargo_bazel, cfg, annotations)
170228
all_repos.append(cfg.name)
171229
local_repos.append(cfg.name)
172230

231+
for repo in repo_specific_annotations:
232+
if repo not in local_repos:
233+
fail("Annotation specified for repo %s, but the module defined repositories %s" % (repo, local_repos))
234+
173235
_from_cargo = tag_class(
174236
doc = "Generates a repo @crates from a Cargo.toml / Cargo.lock pair",
175237
attrs = dict(
@@ -183,9 +245,54 @@ _from_cargo = tag_class(
183245
),
184246
)
185247

248+
# This should be kept in sync with crate_universe/private/crate.bzl.
249+
_annotation = tag_class(
250+
attrs = dict(
251+
repositories = attr.string_list(doc = "A list of repository names specified from `crate.from_cargo(name=...)` that this annotation is applied to. Defaults to all repositories.", default = []),
252+
crate = attr.string(doc = "The name of the crate the annotation is applied to", mandatory = True),
253+
version = attr.string(doc = "The versions of the crate the annotation is applied to. Defaults to all versions.", default = "*"),
254+
additive_build_file_content = attr.string(doc = "Extra contents to write to the bottom of generated BUILD files."),
255+
additive_build_file = attr.label(doc = "A file containing extra contents to write to the bottom of generated BUILD files."),
256+
alias_rule = attr.string(doc = "Alias rule to use instead of `native.alias()`. Overrides [render_config](#render_config)'s 'default_alias_rule'."),
257+
build_script_data = _relative_label_list(doc = "A list of labels to add to a crate's `cargo_build_script::data` attribute."),
258+
build_script_tools = _relative_label_list(doc = "A list of labels to add to a crate's `cargo_build_script::tools` attribute."),
259+
build_script_data_glob = attr.string_list(doc = "A list of glob patterns to add to a crate's `cargo_build_script::data` attribute"),
260+
build_script_deps = _relative_label_list(doc = "A list of labels to add to a crate's `cargo_build_script::deps` attribute."),
261+
build_script_env = attr.string_dict(doc = "Additional environment variables to set on a crate's `cargo_build_script::env` attribute."),
262+
build_script_proc_macro_deps = _relative_label_list(doc = "A list of labels to add to a crate's `cargo_build_script::proc_macro_deps` attribute."),
263+
build_script_rundir = attr.string(doc = "An override for the build script's rundir attribute."),
264+
build_script_rustc_env = attr.string_dict(doc = "Additional environment variables to set on a crate's `cargo_build_script::env` attribute."),
265+
build_script_toolchains = attr.label_list(doc = "A list of labels to set on a crates's `cargo_build_script::toolchains` attribute."),
266+
compile_data = _relative_label_list(doc = "A list of labels to add to a crate's `rust_library::compile_data` attribute."),
267+
compile_data_glob = attr.string_list(doc = "A list of glob patterns to add to a crate's `rust_library::compile_data` attribute."),
268+
crate_features = attr.string_list(doc = "A list of strings to add to a crate's `rust_library::crate_features` attribute."),
269+
data = _relative_label_list(doc = "A list of labels to add to a crate's `rust_library::data` attribute."),
270+
data_glob = attr.string_list(doc = "A list of glob patterns to add to a crate's `rust_library::data` attribute."),
271+
deps = _relative_label_list(doc = "A list of labels to add to a crate's `rust_library::deps` attribute."),
272+
extra_aliased_targets = attr.string_dict(doc = "A list of targets to add to the generated aliases in the root crate_universe repository."),
273+
gen_binaries = attr.string_list(doc = "As a list, the subset of the crate's bins that should get `rust_binary` targets produced."),
274+
gen_all_binaries = attr.bool(doc = "If true, generates `rust_binary` targets for all of the crates bins"),
275+
disable_pipelining = attr.bool(doc = "If True, disables pipelining for library targets for this crate."),
276+
gen_build_script = attr.string(
277+
doc = "An authorative flag to determine whether or not to produce `cargo_build_script` targets for the current crate. Supported values are 'on', 'off', and 'auto'.",
278+
values = _OPT_BOOL_VALUES.keys(),
279+
default = "auto",
280+
),
281+
patch_args = attr.string_list(doc = "The `patch_args` attribute of a Bazel repository rule. See [http_archive.patch_args](https://docs.bazel.build/versions/main/repo/http.html#http_archive-patch_args)"),
282+
patch_tool = attr.string_list(doc = "The `patch_tool` attribute of a Bazel repository rule. See [http_archive.patch_tool](https://docs.bazel.build/versions/main/repo/http.html#http_archive-patch_tool)"),
283+
patches = attr.label_list(doc = "The `patches` attribute of a Bazel repository rule. See [http_archive.patches](https://docs.bazel.build/versions/main/repo/http.html#http_archive-patches)"),
284+
proc_macro_deps = _relative_label_list(doc = "A list of labels to add to a crate's `rust_library::proc_macro_deps` attribute."),
285+
rustc_env = attr.string_dict(doc = "Additional variables to set on a crate's `rust_library::rustc_env` attribute."),
286+
rustc_env_files = _relative_label_list(doc = "A list of labels to set on a crate's `rust_library::rustc_env_files` attribute."),
287+
rustc_flags = attr.string_list(doc = "A list of strings to set on a crate's `rust_library::rustc_flags` attribute."),
288+
shallow_since = attr.string(doc = "An optional timestamp used for crates originating from a git repository instead of a crate registry. This flag optimizes fetching the source code."),
289+
),
290+
)
291+
186292
crate = module_extension(
187293
implementation = _crate_impl,
188294
tag_classes = dict(
189295
from_cargo = _from_cargo,
296+
annotation = _annotation,
190297
),
191298
)

crate_universe/private/crate.bzl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ def _assert_absolute(label):
7676
label (Label): The label to check
7777
"""
7878
label_str = str(label)
79-
if not label.startswith("@"):
79+
if not label_str.startswith("@"):
8080
fail("The labels must be absolute. Please update '{}'".format(
8181
label_str,
8282
))
8383

84+
# This should be kept in sync crate_universe/extension.bzl.
8485
def _annotation(
8586
version = "*",
8687
additive_build_file = None,
@@ -178,7 +179,7 @@ def _annotation(
178179
return json.encode((
179180
version,
180181
struct(
181-
additive_build_file = additive_build_file,
182+
additive_build_file = _stringify_label(additive_build_file),
182183
additive_build_file_content = additive_build_file_content,
183184
alias_rule = parse_alias_rule(alias_rule),
184185
build_script_data = _stringify_list(build_script_data),
@@ -211,6 +212,11 @@ def _annotation(
211212
),
212213
))
213214

215+
def _stringify_label(value):
216+
if not value:
217+
return value
218+
return str(value)
219+
214220
# In bzlmod, attributes of type `attr.label_list` end up as `Label`s not `str`,
215221
# and the `json` module doesn't know how to serialize `Label`s,
216222
# so we proactively convert them to strings before serializing.

crate_universe/private/crates_vendor.bzl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ def generate_config_file(
176176
repository_name,
177177
output_pkg,
178178
workspace_name,
179-
render_config):
179+
render_config,
180+
repository_ctx = None):
180181
"""Writes the rendering config to cargo-bazel-config.json.
181182
182183
Args:
@@ -192,6 +193,8 @@ def generate_config_file(
192193
output_pkg: The path to the package containing the build files.
193194
workspace_name (str): The name of the workspace.
194195
render_config: The render config to use.
196+
repository_ctx (repository_ctx, optional): A repository context object
197+
used for enabling certain functionality.
195198
196199
Returns:
197200
file: The cargo-bazel-config.json written.
@@ -244,6 +247,7 @@ def generate_config_file(
244247
render_config = render_config,
245248
supported_platform_triples = supported_platform_triples,
246249
repository_name = repository_name or ctx.label.name,
250+
repository_ctx = repository_ctx,
247251
)
248252

249253
return json.encode_indent(

examples/bzlmod/hello_world/MODULE.bazel

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ crate.from_cargo(
4040
manifests = ["//third-party:Cargo.toml"],
4141
)
4242
use_repo(crate, "crates")
43+
crate.annotation(
44+
additive_build_file = "//:anyhow.BUILD.bazel",
45+
crate = "anyhow",
46+
# Defined in additive_build_file.
47+
data = [":cargo_toml"],
48+
# Optional, you probably don't need this. Defaults to all from_cargo
49+
# invocations in this module.
50+
repositories = ["crates"],
51+
# Optional, you probably don't need this, defaults to "*".
52+
version = "*",
53+
)
4354

4455
# Option 2: Vendored crates
4556
crate_repositories = use_extension("//third-party:extension.bzl", "crate_repositories")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# See MODULE.bazel's additive_build_file tag.
2+
# Extra build file content from file
3+
4+
alias(
5+
name = "cargo_toml",
6+
actual = "Cargo.toml",
7+
)

0 commit comments

Comments
 (0)