Skip to content

Commit d34eaed

Browse files
committed
Allow use of third party crates with bzlmod.
1 parent 4329918 commit d34eaed

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed

crate_universe/extension.bzl

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""Module extension for generating third-party crates for use in bazel."""
2+
3+
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
4+
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
5+
load("//crate_universe:defs.bzl", _crate_universe_crate = "crate")
6+
load("//crate_universe/private:crates_vendor.bzl", "CRATES_VENDOR_ATTRS", "generate_config_file", "generate_splicing_manifest")
7+
load("//crate_universe/private:generate_utils.bzl", "render_config")
8+
load("//crate_universe/private/module_extensions:cargo_bazel_bootstrap.bzl", "get_cargo_bazel_runner")
9+
10+
def _generate_repo_impl(repo_ctx):
11+
for path, contents in repo_ctx.attr.contents.items():
12+
repo_ctx.file(path, contents)
13+
14+
_generate_repo = repository_rule(
15+
implementation = _generate_repo_impl,
16+
attrs = dict(
17+
contents = attr.string_dict(mandatory = True),
18+
),
19+
)
20+
21+
def _generate_hub_and_spokes(module_ctx, cargo_bazel, cfg):
22+
cargo_lockfile = module_ctx.path(cfg.cargo_lockfile)
23+
tag_path = module_ctx.path(cfg.name)
24+
25+
rendering_config = json.decode(render_config(
26+
regen_command = "Run 'cargo update [--workspace]'",
27+
))
28+
config_file = tag_path.get_child("config.json")
29+
module_ctx.file(
30+
config_file,
31+
executable = False,
32+
content = generate_config_file(
33+
module_ctx,
34+
mode = "remote",
35+
annotations = {},
36+
generate_build_scripts = cfg.generate_build_scripts,
37+
supported_platform_triples = cfg.supported_platform_triples,
38+
generate_target_compatible_with = True,
39+
repository_name = cfg.name,
40+
output_pkg = cfg.name,
41+
workspace_name = cfg.name,
42+
generate_binaries = cfg.generate_binaries,
43+
render_config = rendering_config,
44+
),
45+
)
46+
47+
manifests = {module_ctx.path(m): m for m in cfg.manifests}
48+
splicing_manifest = tag_path.get_child("splicing_manifest.json")
49+
module_ctx.file(
50+
splicing_manifest,
51+
executable = False,
52+
content = generate_splicing_manifest(
53+
packages = {},
54+
splicing_config = "",
55+
cargo_config = cfg.cargo_config,
56+
manifests = {str(k): str(v) for k, v in manifests.items()},
57+
manifest_to_path = module_ctx.path,
58+
),
59+
)
60+
61+
splicing_output_dir = tag_path.get_child("splicing-output")
62+
cargo_bazel([
63+
"splice",
64+
"--output-dir",
65+
splicing_output_dir,
66+
"--config",
67+
config_file,
68+
"--splicing-manifest",
69+
splicing_manifest,
70+
"--cargo-lockfile",
71+
cargo_lockfile,
72+
])
73+
74+
# Create a lockfile, since we need to parse it to generate spoke
75+
# repos.
76+
lockfile_path = tag_path.get_child("lockfile.json")
77+
module_ctx.file(lockfile_path, "")
78+
79+
cargo_bazel([
80+
"generate",
81+
"--cargo-lockfile",
82+
cargo_lockfile,
83+
"--config",
84+
config_file,
85+
"--splicing-manifest",
86+
splicing_manifest,
87+
"--repository-dir",
88+
tag_path,
89+
"--metadata",
90+
splicing_output_dir.get_child("metadata.json"),
91+
"--repin",
92+
"--lockfile",
93+
lockfile_path,
94+
])
95+
96+
crates_dir = tag_path.get_child(cfg.name)
97+
_generate_repo(
98+
name = cfg.name,
99+
contents = {
100+
"BUILD.bazel": module_ctx.read(crates_dir.get_child("BUILD.bazel")),
101+
},
102+
)
103+
104+
contents = json.decode(module_ctx.read(lockfile_path))
105+
106+
for crate in contents["crates"].values():
107+
repo = crate["repository"]
108+
if repo == None:
109+
continue
110+
name = crate["name"]
111+
version = crate["version"]
112+
113+
# "+" isn't valid in a repo name.
114+
crate_repo_name = "{repo_name}__{name}-{version}".format(
115+
repo_name = cfg.name,
116+
name = name,
117+
version = version.replace("+", "-"),
118+
)
119+
120+
build_file_content = module_ctx.read(crates_dir.get_child("BUILD.%s-%s.bazel" % (name, version)))
121+
if "Http" in repo:
122+
# Replicates functionality in repo_http.j2.
123+
repo = repo["Http"]
124+
http_archive(
125+
name = crate_repo_name,
126+
patch_args = repo.get("patch_args", None),
127+
patch_tool = repo.get("patch_tool", None),
128+
patches = repo.get("patches", None),
129+
remote_patch_strip = 1,
130+
sha256 = repo.get("sha256", None),
131+
type = "tar.gz",
132+
urls = [repo["url"]],
133+
strip_prefix = "%s-%s" % (crate["name"], crate["version"]),
134+
build_file_content = build_file_content,
135+
)
136+
elif "Git" in repo:
137+
# Replicates functionality in repo_git.j2
138+
repo = repo["Git"]
139+
kwargs = {}
140+
for k, v in repo["commitish"].items():
141+
if k == "Rev":
142+
kwargs["commit"] = v
143+
else:
144+
kwargs[k.lower()] = v
145+
new_git_repository(
146+
name = crate_repo_name,
147+
init_submodules = True,
148+
patch_args = repo.get("patch_args", None),
149+
patch_tool = repo.get("patch_tool", None),
150+
patches = repo.get("patches", None),
151+
shallow_since = repo.get("shallow_since", None),
152+
remote = repo["remote"],
153+
build_file_content = build_file_content,
154+
strip_prefix = repo.get("strip_prefix", None),
155+
**kwargs
156+
)
157+
else:
158+
fail("Invalid repo: expected Http or Git to exist for crate %s-%s, got %s" % (name, version, repo))
159+
160+
def _crate_impl(module_ctx):
161+
cargo_bazel = get_cargo_bazel_runner(module_ctx)
162+
all_repos = []
163+
for mod in module_ctx.modules:
164+
local_repos = []
165+
for cfg in mod.tags.from_cargo:
166+
if cfg.name in local_repos:
167+
fail("Defined two crate universes with the same name in the same MODULE.bazel file. Use the name tag to give them different names.")
168+
elif cfg.name in all_repos:
169+
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)")
170+
_generate_hub_and_spokes(module_ctx, cargo_bazel, cfg)
171+
all_repos.append(cfg.name)
172+
local_repos.append(cfg.name)
173+
174+
_from_cargo = tag_class(
175+
doc = "Generates a repo @crates from a Cargo.toml / Cargo.lock pair",
176+
attrs = dict(
177+
name = attr.string(doc = "The name of the repo to generate", default = "crates"),
178+
cargo_lockfile = CRATES_VENDOR_ATTRS["cargo_lockfile"],
179+
manifests = CRATES_VENDOR_ATTRS["manifests"],
180+
cargo_config = CRATES_VENDOR_ATTRS["cargo_config"],
181+
generate_binaries = CRATES_VENDOR_ATTRS["generate_binaries"],
182+
generate_build_scripts = CRATES_VENDOR_ATTRS["generate_build_scripts"],
183+
supported_platform_triples = CRATES_VENDOR_ATTRS["supported_platform_triples"],
184+
),
185+
)
186+
187+
crate = module_extension(
188+
implementation = _crate_impl,
189+
tag_classes = dict(
190+
from_cargo = _from_cargo,
191+
),
192+
)

0 commit comments

Comments
 (0)