Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions crate_universe/src/cli/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,21 @@ pub struct GenerateOptions {
}

pub fn generate(opt: GenerateOptions) -> Result<()> {
// Load the config
// Load the config. `config.label_injection_mapping` is the per-session
// apparent -> canonical map reflecting the root module's current
// `single_version_override` / `multiple_version_override` choices. It is
// applied to the Context just before rendering and is sanitized out of
// the digest hash in `Digest::new`, so consumer-side overrides don't
// force a producer-side repin (the producer's lockfile may live in a
// read-only bzlmod cache).
let config = Config::try_from_path(&opt.config)?;

// Go straight to rendering if there is no need to repin
if !opt.repin {
if let Some(lockfile) = &opt.lockfile {
let context = Context::try_from_path(lockfile)?;
// Lockfile stores apparent labels; rewrite to canonical here.
let context = Context::try_from_path(lockfile)?
.apply_label_injection_mapping(&config.label_injection_mapping)?;

// Render build files
let outputs = Renderer::new(
Expand Down Expand Up @@ -205,15 +213,23 @@ pub fn generate(opt: GenerateOptions) -> Result<()> {
&opt.nonhermetic_root_bazel_workspace_dir,
)?;

// Generate renderable contexts for each package
// Generate renderable contexts for each package. The Context here holds
// the user's APPARENT labels (e.g. `@openssl//:install`) because the
// label_injection mapping was detached at config load.
let context = Context::new(annotations, config.rendering.are_sources_present())?;

// Render build files
// Render build files. Apply the apparent -> canonical mapping just for
// rendering — the lockfile written below uses the original apparent
// Context, so consumer-side overrides stay sound without producer-side
// repin.
let render_context = context
.clone()
.apply_label_injection_mapping(&config.label_injection_mapping)?;
let outputs = Renderer::new(
Arc::new(config.rendering.clone()),
Arc::new(config.supported_platform_triples.clone()),
)
.render(&context, opt.generator)?;
.render(&render_context, opt.generator)?;

// make file paths compatible with bazel labels
let normalized_outputs = normalize_cargo_file_paths(outputs, &opt.repository_dir);
Expand Down
6 changes: 5 additions & 1 deletion crate_universe/src/cli/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ pub fn query(opt: QueryOptions) -> Result<()> {
None => bail!("No digest provided in lockfile"),
};

// Load the config file
// Load the config file. `config.label_injection_mapping` is populated
// but unused for the digest check — `Digest::new` sanitizes it out
// before hashing so this comparison stays stable across consumer-side
// overrides (e.g., `single_version_override` on an injected dep);
// otherwise every override would force a producer-side repin.
let config = Config::try_from_path(&opt.config)?;

let splicing_manifest = SplicingManifest::try_from_path(&opt.splicing_manifest)?;
Expand Down
2 changes: 2 additions & 0 deletions crate_universe/src/cli/splice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ pub fn splice(opt: SpliceOptions) -> Result<()> {
.context("Failed to generate lockfile")?
};

// Splice doesn't render BUILD files; `config.label_injection_mapping` is
// populated but unused here. The substitution happens in `generate`.
let config = Config::try_from_path(&opt.config).context("Failed to parse config")?;

let resolver_data = TreeResolver::new(cargo.clone())
Expand Down
11 changes: 10 additions & 1 deletion crate_universe/src/cli/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,9 @@ pub fn vendor(opt: VendorOptions) -> anyhow::Result<()> {
&opt.repin,
)?;

// Load the config from disk
// Load the config from disk. `config.label_injection_mapping` is applied
// to the Context just before render (see near `Renderer::new` below) and
// is sanitized out of the digest hash by `Digest::new`.
let config = Config::try_from_path(&opt.config)?;

let resolver_data = TreeResolver::new(cargo.clone()).generate(
Expand Down Expand Up @@ -288,6 +290,13 @@ pub fn vendor(opt: VendorOptions) -> anyhow::Result<()> {
// Generate renderable contexts for search package
let context = Context::new(annotations, config.rendering.are_sources_present())?;

// Apply label_injection just before render. The Context at this point
// contains the user's apparent labels (e.g. `@openssl//:install`); the
// mapping rewrites them to the per-session canonical (e.g.
// `@@openssl+v3.5.5//:install`), reflecting whatever the root module's
// current `single_version_override` etc. resolved to.
let context = context.apply_label_injection_mapping(&config.label_injection_mapping)?;

// Render build files
let outputs = Renderer::new(
Arc::new(config.rendering.clone()),
Expand Down
38 changes: 35 additions & 3 deletions crate_universe/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! A module for configuration information

mod label_injection;
pub(crate) mod label_injection;

use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet};
Expand Down Expand Up @@ -719,6 +719,18 @@ pub(crate) struct Config {
/// A set of platform triples to use in generated select statements
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub(crate) supported_platform_triples: BTreeSet<TargetTriple>,

/// Apparent -> canonical label_injection map extracted from each
/// annotation's `label_injections` field at load time. Populated by
/// `Config::try_from_path`; not present in config.json itself
/// (`deny_unknown_fields` is fine because `extract_global_mapping`
/// strips `label_injections` before deserialization). Sanitized to
/// `Default::default()` in `Digest::new` before hashing — same trick
/// as `Context.checksum = None` in `lockfile.rs` — so consumer-side
/// `single_version_override` shifts (which change the canonical names
/// here) don't perturb the digest and force a producer-side repin.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub(crate) label_injection_mapping: LabelInjectionMapping,
}

// rules_rust/crate_universe/private/generate_utils.bzl:generate_config
Expand All @@ -744,14 +756,34 @@ where
}

impl Config {
/// Load a config JSON and extract its per-annotation `label_injections`
/// into a single `label_injection_mapping` on the Config. The Config
/// itself keeps the user's APPARENT labels in annotation strings — the
/// rewrite to canonical happens via
/// [`label_injection::apply_mapping_to_value`] just before each render
/// (see [`crate::context::Context::apply_label_injection_mapping`]),
/// using whatever mapping the current bazel session resolved (reflecting
/// any root-level `single_version_override` / `multiple_version_override`).
///
/// The mapping is excluded from the digest hash via
/// [`crate::lockfile::Digest::new`], mirroring the
/// `Context.checksum = None` clear in the same file. That's what keeps
/// the digest stable across consumer-side overrides.
pub(crate) fn try_from_path<T: AsRef<Path>>(path: T) -> Result<Self> {
let data = fs::read_to_string(path)?;
let mut value: serde_json::Value = serde_json::from_str(&data)?;
label_injection::apply(&mut value);
Ok(serde_json::from_value(value)?)
let mapping = label_injection::extract_global_mapping(&mut value)?;
let mut config: Self = serde_json::from_value(value)?;
config.label_injection_mapping = mapping;
Ok(config)
}
}

/// Apparent-repo-prefix -> canonical-repo-prefix map carried on
/// [`Config::label_injection_mapping`]. See [`label_injection`] for the WHY
/// of the deferred-substitution design.
pub(crate) type LabelInjectionMapping = std::collections::BTreeMap<String, String>;

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CrateNameAndVersionReq {
/// The name of the crate
Expand Down
Loading
Loading