Skip to content

Commit 5366544

Browse files
committed
Render crates into per-alias subpackages
1 parent d882a2d commit 5366544

12 files changed

Lines changed: 127 additions & 37 deletions

File tree

crate_universe/private/crates_repository.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def _crates_repository_impl(repository_ctx):
114114

115115
paths_to_track_file = repository_ctx.path("paths-to-track")
116116
warnings_output_file = repository_ctx.path("warnings-output-file")
117+
hub_packages_output_file = repository_ctx.path("hub-packages.json")
117118

118119
# Run the generator
119120
repository_ctx.report_progress("Generating crate BUILD files.")
@@ -128,6 +129,7 @@ def _crates_repository_impl(repository_ctx):
128129
nonhermetic_root_bazel_workspace_dir = nonhermetic_root_bazel_workspace_dir,
129130
paths_to_track_file = paths_to_track_file,
130131
warnings_output_file = warnings_output_file,
132+
hub_packages_output_file = hub_packages_output_file,
131133
skip_cargo_lockfile_overwrite = repository_ctx.attr.skip_cargo_lockfile_overwrite,
132134
strip_internal_dependencies_from_cargo_lockfile = repository_ctx.attr.strip_internal_dependencies_from_cargo_lockfile,
133135
# sysroot = tools.sysroot,

crate_universe/private/generate_utils.bzl

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def get_generator(repository_ctx, host_triple):
9292
def render_config(
9393
build_file_template = "//:BUILD.{name}-{version}.bazel",
9494
crate_label_template = "@{repository}__{name}-{version}//:{target}",
95-
crate_alias_template = "//:{name}-{version}",
95+
crate_alias_template = "//{name}-{version}",
9696
crate_repository_template = "{repository}__{name}-{version}",
9797
crates_module_template = "//:{file}",
9898
default_alias_rule = "alias",
@@ -102,7 +102,8 @@ def render_config(
102102
platforms_template = "@rules_rust//rust/platform:{triple}",
103103
regen_command = None,
104104
vendor_mode = None,
105-
generate_rules_license_metadata = False):
105+
generate_rules_license_metadata = False,
106+
incompatible_no_root_alias_targets = False):
106107
"""Various settings used to configure rendered outputs
107108
108109
The template parameters each support a select number of format keys. A description of each key
@@ -145,6 +146,11 @@ def render_config(
145146
regen_command (str, optional): An optional command to demonstrate how generated files should be regenerated.
146147
vendor_mode (str, optional): An optional configuration for rendirng content to be rendered into repositories.
147148
generate_rules_license_metadata (bool, optional): Whether to generate rules license metadata
149+
incompatible_no_root_alias_targets (bool, optional): Incompatibility flag. Suppresses the top-level
150+
`alias()` rules in the hub repository's root `BUILD.bazel` (e.g. `@crate_index//:clap`). Per-alias
151+
subpackages (e.g. `@crate_index//clap`) are always emitted, so flipping this flag on lets users
152+
keep consuming aliases through the subpackage path while the root version disappears. Planned to
153+
flip to default-on in a future release.
148154
149155
Returns:
150156
string: A json encoded struct to match the Rust `config::RenderConfig` struct
@@ -160,6 +166,7 @@ def render_config(
160166
generate_cargo_toml_env_vars = generate_cargo_toml_env_vars,
161167
generate_rules_license_metadata = generate_rules_license_metadata,
162168
generate_target_compatible_with = generate_target_compatible_with,
169+
incompatible_no_root_alias_targets = incompatible_no_root_alias_targets,
163170
platforms_template = platforms_template,
164171
regen_command = regen_command,
165172
vendor_mode = vendor_mode,
@@ -440,6 +447,7 @@ def execute_generator(
440447
nonhermetic_root_bazel_workspace_dir,
441448
paths_to_track_file,
442449
warnings_output_file,
450+
hub_packages_output_file,
443451
skip_cargo_lockfile_overwrite,
444452
strip_internal_dependencies_from_cargo_lockfile,
445453
metadata = None,
@@ -456,6 +464,9 @@ def execute_generator(
456464
nonhermetic_root_bazel_workspace_dir (path): The path to the current workspace root
457465
paths_to_track_file (path): Path to file where generator should write which files should trigger re-generating as a JSON list.
458466
warnings_output_file (path): Path to file where generator should write warnings to print.
467+
hub_packages_output_file (path): Path to file where the generator should write the names of hub alias
468+
subpackages it produced. Always populated; the bzlmod extension reads it to learn which
469+
`<name>/BUILD.bazel` files to slurp into the hub repository.
459470
skip_cargo_lockfile_overwrite (bool): Whether to skip writing the cargo lockfile back after resolving.
460471
You may want to set this if your dependency versions are maintained externally through a non-trivial set-up.
461472
But you probably don't want to set this.
@@ -488,6 +499,8 @@ def execute_generator(
488499
paths_to_track_file,
489500
"--warnings-output-path",
490501
warnings_output_file,
502+
"--hub-packages-output-path",
503+
hub_packages_output_file,
491504
]
492505

493506
if generator_label:

crate_universe/src/cli/generate.rs

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ pub struct GenerateOptions {
9797
#[clap(long)]
9898
pub warnings_output_path: PathBuf,
9999

100+
/// Path to write a JSON list of hub alias subpackage names. The bzlmod
101+
/// extension reads this file to learn which `<name>/BUILD.bazel` files
102+
/// to slurp into the hub repository (the per-alias subpackages always
103+
/// render; `module_ctx` cannot enumerate directories, so this sidecar
104+
/// is how the extension learns the set).
105+
#[clap(long)]
106+
pub hub_packages_output_path: PathBuf,
107+
100108
/// Whether to skip writing the cargo lockfile back after resolving.
101109
/// You may want to set this if your dependency versions are maintained externally through a non-trivial set-up.
102110
/// But you probably don't want to set this.
@@ -130,17 +138,18 @@ pub fn generate(opt: GenerateOptions) -> Result<()> {
130138
.apply_label_injection_mapping(&config.label_injection_mapping)?;
131139

132140
// Render build files
133-
let outputs = Renderer::new(
141+
let renderer = Renderer::new(
134142
Arc::new(config.rendering),
135143
Arc::new(config.supported_platform_triples),
136-
)
137-
.render(&context, opt.generator)?;
138-
139-
// make file paths compatible with bazel labels
140-
let normalized_outputs = normalize_cargo_file_paths(outputs, &opt.repository_dir);
141-
142-
// Write the outputs to disk
143-
write_outputs(normalized_outputs, opt.dry_run)?;
144+
);
145+
render_and_write_outputs(
146+
&renderer,
147+
&context,
148+
opt.generator.clone(),
149+
&opt.repository_dir,
150+
&opt.hub_packages_output_path,
151+
opt.dry_run,
152+
)?;
144153

145154
let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)?;
146155

@@ -225,17 +234,18 @@ pub fn generate(opt: GenerateOptions) -> Result<()> {
225234
let render_context = context
226235
.clone()
227236
.apply_label_injection_mapping(&config.label_injection_mapping)?;
228-
let outputs = Renderer::new(
237+
let renderer = Renderer::new(
229238
Arc::new(config.rendering.clone()),
230239
Arc::new(config.supported_platform_triples.clone()),
231-
)
232-
.render(&render_context, opt.generator)?;
233-
234-
// make file paths compatible with bazel labels
235-
let normalized_outputs = normalize_cargo_file_paths(outputs, &opt.repository_dir);
236-
237-
// Write the outputs to disk
238-
write_outputs(normalized_outputs, opt.dry_run)?;
240+
);
241+
render_and_write_outputs(
242+
&renderer,
243+
&render_context,
244+
opt.generator.clone(),
245+
&opt.repository_dir,
246+
&opt.hub_packages_output_path,
247+
opt.dry_run,
248+
)?;
239249

240250
// Ensure Bazel lockfiles are written to disk so future generations can be short-circuited.
241251
if let Some(lockfile) = opt.lockfile {
@@ -286,6 +296,30 @@ fn update_cargo_lockfile(path: &Path, cargo_lockfile: Lockfile) -> Result<()> {
286296
Ok(())
287297
}
288298

299+
fn render_and_write_outputs(
300+
renderer: &Renderer,
301+
context: &Context,
302+
generator: Option<Label>,
303+
repository_dir: &Path,
304+
hub_packages_output_path: &Path,
305+
dry_run: bool,
306+
) -> Result<()> {
307+
let rendered = renderer.render_hub(context, generator)?;
308+
let normalized_outputs = normalize_cargo_file_paths(rendered.files, repository_dir);
309+
write_outputs(normalized_outputs, dry_run)?;
310+
write_hub_packages(hub_packages_output_path, &rendered.hub_packages)?;
311+
Ok(())
312+
}
313+
314+
fn write_hub_packages(output_file: &Path, hub_packages: &[String]) -> Result<()> {
315+
std::fs::write(
316+
output_file,
317+
serde_json::to_string(hub_packages).context("Failed to serialize hub alias subpackages")?,
318+
)
319+
.context("Failed to write hub alias subpackages file")?;
320+
Ok(())
321+
}
322+
289323
fn write_paths_to_track<
290324
'a,
291325
SourceAnnotations: Iterator<Item = &'a SourceAnnotation>,

crate_universe/src/config.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ pub(crate) struct RenderConfig {
6262
#[serde(default = "default_crate_label_template")]
6363
pub(crate) crate_label_template: String,
6464

65-
/// The pattern to use for a crate alias.
66-
/// Eg. `@{repository}//:{name}-{version}-{target}`
65+
/// The pattern to use for a crate alias. Defaults to the hub subpackage
66+
/// form (`//{name}-{version}`) emitted by the always-on per-alias
67+
/// subpackage layout. Override to point `aliases()` /
68+
/// `all_crate_deps()` at custom labels.
6769
#[serde(default = "default_crate_alias_template")]
6870
pub(crate) crate_alias_template: String,
6971

@@ -109,6 +111,27 @@ pub(crate) struct RenderConfig {
109111
/// Whether to generate cargo_toml_env_vars targets.
110112
/// This is expected to always be true except for bootstrapping.
111113
pub(crate) generate_cargo_toml_env_vars: bool,
114+
115+
/// Incompatibility flag. Suppresses the top-level `alias()` rules in the
116+
/// hub repository's root `BUILD.bazel` (e.g. `@crate_index//:clap`).
117+
/// Per-alias subpackages (e.g. `@crate_index//clap`) are always emitted,
118+
/// so flipping this flag on lets users keep consuming aliases through
119+
/// the subpackage path while the root version disappears. Default
120+
/// `false`; planned to flip to `true` (and ultimately have the
121+
/// root-emitting code removed) in a future release.
122+
#[serde(default)]
123+
pub(crate) incompatible_no_root_alias_targets: bool,
124+
125+
/// Internal: true when the rendered output is destined for the
126+
/// `crates_vendor` workflow (committed into the user's workspace
127+
/// alongside `crates.bzl`). The renderer routes per-alias subpackage
128+
/// BUILDs into `_HUB_PACKAGE_BUILDS` inside `crates.bzl` (synthesized
129+
/// at fetch time by `crates_vendor_remote_repository`) instead of
130+
/// writing them to disk — keeps the vendor tree free of generated
131+
/// subdirectories. Defaults `false`; bzlmod / `crates_repository`
132+
/// continue to write subpackage BUILDs into the hub repo directly.
133+
#[serde(default)]
134+
pub(crate) crates_vendor_synthesizes_subpackages: bool,
112135
}
113136

114137
// Default is manually implemented so that the default values match the default
@@ -131,6 +154,8 @@ impl Default for RenderConfig {
131154
regen_command: String::default(),
132155
vendor_mode: Option::default(),
133156
generate_rules_license_metadata: default_generate_rules_license_metadata(),
157+
incompatible_no_root_alias_targets: false,
158+
crates_vendor_synthesizes_subpackages: false,
134159
}
135160
}
136161
}
@@ -154,7 +179,11 @@ fn default_crate_label_template() -> String {
154179
}
155180

156181
fn default_crate_alias_template() -> String {
157-
"//:{name}-{version}".to_owned()
182+
// Points `aliases()` / `all_crate_deps()` at the per-alias subpackage
183+
// layout (e.g. `Label("@crate_index//clap-1.0.0")`). Subpackage `BUILD.bazel`s
184+
// are always emitted, so this default works whether or not the user
185+
// sets `incompatible_no_root_alias_targets`.
186+
"//{name}-{version}".to_owned()
158187
}
159188

160189
fn default_crate_repository_template() -> String {

crate_universe/src/lockfile.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ mod test {
260260
);
261261

262262
assert_eq!(
263-
Digest("edd73970897c01af3bb0e6c9d62f572203dd38a03c189dcca555d463990aa086".to_owned()),
263+
Digest("caafd4eaa60b599509522968ce17d6ba07b8118b767b7dddb00bef8d5ddba7fa".to_owned()),
264264
digest,
265265
);
266266
}
@@ -305,7 +305,7 @@ mod test {
305305
);
306306

307307
assert_eq!(
308-
Digest("8a4c1b3bb4c2d6c36e27565e71a13d54cff9490696a492c66a3a37bdd3893edf".to_owned()),
308+
Digest("76d987edaa3f6a281133997c7d31a657acfaeb639585d9e8c629996c1fcb94af".to_owned()),
309309
digest,
310310
);
311311
}
@@ -336,7 +336,7 @@ mod test {
336336
);
337337

338338
assert_eq!(
339-
Digest("1e01331686ba1f26f707dc098cd9d21c39d6ccd8e46be03329bb2470d3833e15".to_owned()),
339+
Digest("3288cda3c2194f5ca7f1929b74bd13f36eb624e859d77b143d762c60631d700d".to_owned()),
340340
digest,
341341
);
342342
}
@@ -385,7 +385,7 @@ mod test {
385385
);
386386

387387
assert_eq!(
388-
Digest("45ccf7109db2d274420fac521f4736a1fb55450ec60e6df698e1be4dc2c89fad".to_owned()),
388+
Digest("ccc875599ade8873ba48496875d29f08772710651b9bf6d428a97f45a559aa2e".to_owned()),
389389
digest,
390390
);
391391
}

crate_universe/src/utils.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,17 @@ pub(crate) fn normalize_cargo_file_paths(
4242
.to_str()
4343
.expect("All file paths should be strings");
4444

45-
let path = if original_parent_path_str.contains('+') {
46-
let new_parent_file_path = sanitize_repository_name(original_parent_path_str);
45+
// Vendor mode pre-creates the source directory (cargo's
46+
// `+`-bearing layout) and we rewrite it to be label-friendly.
47+
// Other render paths — e.g. the per-alias hub subpackage
48+
// `BUILD.bazel`s whose parent name happens to contain a semver `+` —
49+
// have nothing pre-created on disk; leave their paths alone so
50+
// the directory and the alias name stay in sync.
51+
let src = out_dir.join(original_parent_path_str);
52+
let path = if original_parent_path_str.contains('+') && src.exists() {
4753
std::fs::rename(
48-
out_dir.join(original_parent_path_str),
49-
out_dir.join(new_parent_file_path),
54+
src,
55+
out_dir.join(sanitize_repository_name(original_parent_path_str)),
5056
)
5157
.expect("Could not rename paths");
5258
PathBuf::from(&original_path_str.replace('+', "-"))

crate_universe/tests/integration/cargo_aliases/BUILD.bazel

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ rust_test(
1919
deps = all_crate_deps(normal_dev = True),
2020
)
2121

22-
# Ensures that Bazel aliases from the crates_repository are actually usable.
22+
# Ensures that Bazel aliases from the crates_repository are actually usable
23+
# via the subpackage form. The legacy root alias `@cargo_aliases//:names` is
24+
# suppressed by this fixture's `incompatible_no_root_alias_targets = True`.
2325
build_test(
2426
name = "names_build_test",
25-
targets = ["@cargo_aliases//:names"],
27+
targets = ["@cargo_aliases//names"],
2628
)

crate_universe/tests/integration/cargo_aliases/MODULE.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ use_repo(rust, "rust_toolchains")
1717
register_toolchains("@rust_toolchains//:all")
1818

1919
crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate")
20+
crate.render_config(
21+
incompatible_no_root_alias_targets = True,
22+
repositories = ["cargo_aliases"],
23+
)
2024
crate.annotation(
2125
crate = "names",
2226
repositories = ["cargo_aliases"],

crate_universe/tests/integration/cargo_aliases/cargo-bazel-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crate_universe/tests/integration/ext_annotations/cargo-bazel-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)