use sha2::{Digest, Sha256};
use crate::config::schema::{ConfigDocument, OriginMap};
use crate::constants::BUILTIN_CATALOG;
use crate::error::ForgeError;
pub(crate) fn load_builtin() -> Result<(ConfigDocument, OriginMap), ForgeError> {
let mut document = ConfigDocument::parse_catalog(BUILTIN_CATALOG)?;
let digest = Sha256::digest(BUILTIN_CATALOG.as_bytes())
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>();
document.catalog = "rust-dev".to_string();
document.catalog_digest = digest.clone();
let mut origins = OriginMap::default();
let origin = format!("builtin:rust-dev@sha256:{digest}");
for field in [
"policy.network",
"policy.allow_shell",
"policy.allow_unlocked_cargo",
"policy.max_parallel",
"policy.max_downloads",
"environment.mutations.managed-bin",
] {
origins.record(field, &origin);
}
for name in document.versions.keys() {
origins.record(format!("versions.{name}"), &origin);
}
for name in document.groups.keys() {
origins.record(format!("groups.{name}"), &origin);
}
for name in document.profiles.keys() {
origins.record(format!("profiles.{name}"), &origin);
}
for component in &document.components {
origins.record(format!("components.{}", component.id), &origin);
}
record_shorthand_origins(BUILTIN_CATALOG, &origin, &mut origins)?;
origins.record("catalog.rust-dev", origin);
Ok((document, origins))
}
fn record_shorthand_origins(
input: &str,
origin: &str,
origins: &mut OriginMap,
) -> Result<(), ForgeError> {
let value: toml::Value = toml::from_str(input)
.map_err(|error| ForgeError::Parse(format!("invalid TOML: {error}")))?;
for section in ["cargo-tools", "package-tools", "rustup-tools"] {
if let Some(table) = value.get(section).and_then(toml::Value::as_table) {
for id in table.keys() {
origins.record(
format!("components.{id}"),
format!("{origin} (expanded from {section}.{id})"),
);
}
}
}
if let Some(toolsets) = value.get("cargo-toolsets").and_then(toml::Value::as_table) {
for (group, tools) in toolsets {
if let Some(tools) = tools.as_table() {
for id in tools.keys() {
origins.record(
format!("components.{id}"),
format!("{origin} (expanded from cargo-toolsets.{group}.{id})"),
);
}
}
}
}
Ok(())
}
pub(crate) fn validate_selection(input: &str, origin: &str) -> Result<(), ForgeError> {
let value: toml::Value = toml::from_str(input)
.map_err(|error| ForgeError::Parse(format!("invalid TOML: {error}")))?;
if value.get("catalog").and_then(toml::Value::as_str) != Some("rust-dev") {
return Err(ForgeError::Config(format!(
"main configuration {origin} must declare catalog = \"rust-dev\""
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::config::catalog::load_builtin;
use crate::config::schema::InstallSpec;
use crate::planning::{PlanRequest, TargetPlatform, build_plan};
#[test]
fn supported_platform_profiles_always_plan() {
let (document, origins) = load_builtin().unwrap();
for target in [
TargetPlatform {
os: "linux".into(),
arch: "x86_64".into(),
abi: "gnu".into(),
},
TargetPlatform {
os: "windows".into(),
arch: "x86_64".into(),
abi: "msvc".into(),
},
TargetPlatform {
os: "windows".into(),
arch: "aarch64".into(),
abi: "msvc".into(),
},
TargetPlatform {
os: "macos".into(),
arch: "x86_64".into(),
abi: "native".into(),
},
TargetPlatform {
os: "macos".into(),
arch: "aarch64".into(),
abi: "native".into(),
},
] {
for profile in ["minimal", "standard", "advanced"] {
let plan = build_plan(PlanRequest {
document: &document,
origins: &origins,
profile,
target: target.clone(),
only: &[],
exclude: &[],
source_root: None,
})
.unwrap_or_else(|error| {
panic!("{profile} must plan for {}: {error}", target.selector())
});
if profile == "advanced" {
let has_valgrind = plan.components.iter().any(|component| {
matches!(component.id.as_str(), "valgrind" | "cargo-valgrind")
});
assert_eq!(has_valgrind, target.os == "linux");
let node = plan
.components
.iter()
.find(|component| component.id == "nodejs")
.expect("advanced must include Node.js 22");
match (&target.os[..], &node.install) {
("linux", Some(InstallSpec::Archive(_)))
| ("macos", Some(InstallSpec::Brew(_)))
| ("windows", Some(InstallSpec::Archive(_))) => {}
_ => panic!("unexpected Node.js backend for {}", target.selector()),
}
}
}
}
let plan = build_plan(PlanRequest {
document: &document,
origins: &origins,
profile: "advanced",
target: TargetPlatform {
os: "linux".into(),
arch: "x86_64".into(),
abi: "gnu".into(),
},
only: &[],
exclude: &[],
source_root: None,
})
.unwrap();
assert!(
plan.components
.iter()
.any(|component| component.id == "valgrind")
);
assert!(
plan.components
.iter()
.any(|component| component.id == "cargo-valgrind")
);
for (id, version) in [("bot-gate", "=1.2.1"), ("bot-metric", "=1.2.0")] {
let component = plan
.components
.iter()
.find(|component| component.id == id)
.unwrap_or_else(|| panic!("standard profile must include {id}"));
let Some(InstallSpec::Cargo(install)) = &component.install else {
panic!("{id} must use the Cargo backend");
};
assert_eq!(install.crate_name, id);
assert_eq!(install.version, version);
assert!(install.source.is_none(), "{id} must use crates.io");
assert!(
!component
.dependencies
.iter()
.any(|dependency| dependency == "git"),
"{id} must not require Git"
);
assert!(
component
.dependencies
.iter()
.any(|dependency| dependency == "rust-toolchain")
);
}
let rust_bot = plan
.components
.iter()
.find(|component| component.id == "rust-bot")
.expect("minimal level must include rust-bot");
let Some(InstallSpec::Cargo(rust_bot_install)) = &rust_bot.install else {
panic!("rust-bot must use the Cargo backend");
};
assert_eq!(rust_bot_install.crate_name, "rust-bot");
assert_eq!(rust_bot_install.version, "=2.0.0");
assert!(
rust_bot_install.source.is_none(),
"rust-bot must use crates.io"
);
assert!(rust_bot_install.revision.is_none());
assert!(
!rust_bot
.dependencies
.iter()
.any(|dependency| dependency == "git")
);
for component in &plan.components {
if let Some(InstallSpec::Cargo(cargo)) = &component.install {
assert_eq!(
cargo.toolchain.as_deref(),
Some("1.89.0"),
"{} must use the pinned Rust toolchain",
component.id
);
}
}
for component in plan.components.iter().filter(|component| {
matches!(component.install, Some(InstallSpec::Cargo(_)))
|| matches!(
component.install,
Some(InstallSpec::Rustup(ref rustup))
if rustup.bootstrap.is_none()
)
}) {
assert!(
component
.dependencies
.iter()
.any(|dependency| dependency == "rust-toolchain"),
"{} must depend on the typed Rust provider",
component.id
);
}
}
}