lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit b9b574713837dcae18841849ec9262cf83f28d37
parent 95b147d7db8ed8461f7d405c403c40b3fb3afc9a
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Jul 2026 10:38:44 +0000

release: require path and version for public dependencies

- resolve workspace-inherited and renamed public dependency declarations
- inspect normal, dev, build, and target-specific manifest sections
- require every current public edge to carry path and exact version 0.1.0
- add a negative fixture that rejects a path-only public dependency

Diffstat:
Mtools/sdk_xtask_import/src/architecture.rs | 245++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 244 insertions(+), 1 deletion(-)

diff --git a/tools/sdk_xtask_import/src/architecture.rs b/tools/sdk_xtask_import/src/architecture.rs @@ -153,6 +153,7 @@ pub fn validate(workspace_root: &Path) -> Result<(), String> { validate_workspace_toolchain(workspace_root, &architecture)?; validate_public_package_metadata(workspace_root, &architecture)?; + validate_public_dependency_versions(workspace_root, &architecture)?; let ledger_path = workspace_root.join(DEVIATIONS_RELATIVE); let ledger_raw = fs::read_to_string(&ledger_path) @@ -411,6 +412,204 @@ fn validate_public_package_metadata( Ok(()) } +fn validate_public_dependency_versions( + workspace_root: &Path, + architecture: &ArchitectureIdentity, +) -> Result<(), String> { + let workspace_path = workspace_root.join("Cargo.toml"); + let workspace_raw = fs::read_to_string(&workspace_path) + .map_err(|error| format!("read {}: {error}", workspace_path.display()))?; + let workspace = toml::from_str::<WorkspaceManifest>(&workspace_raw) + .map_err(|error| format!("parse {}: {error}", workspace_path.display()))?; + let workspace_value = workspace_raw + .parse::<toml::Value>() + .map_err(|error| format!("parse {}: {error}", workspace_path.display()))?; + let workspace_dependencies = workspace_value + .get("workspace") + .and_then(toml::Value::as_table) + .and_then(|workspace| workspace.get("dependencies")) + .and_then(toml::Value::as_table); + let repository = architecture + .repositories + .values() + .find(|repository| repository.url == workspace.workspace.package.repository) + .ok_or_else(|| "workspace repository has no architecture allocation".to_owned())?; + let local_packages = repository.packages.iter().collect::<BTreeSet<_>>(); + let public_packages = architecture + .package + .iter() + .map(|package| package.name.as_str()) + .collect::<BTreeSet<_>>(); + + for member in &workspace.workspace.members { + let manifest_path = workspace_root.join(member).join("Cargo.toml"); + let raw = fs::read_to_string(&manifest_path) + .map_err(|error| format!("read {}: {error}", manifest_path.display()))?; + let manifest = raw + .parse::<toml::Value>() + .map_err(|error| format!("parse {}: {error}", manifest_path.display()))?; + let package_name = manifest + .get("package") + .and_then(toml::Value::as_table) + .and_then(|package| package.get("name")) + .and_then(toml::Value::as_str) + .ok_or_else(|| format!("{} is missing package.name", manifest_path.display()))?; + if !local_packages.contains(&package_name.to_owned()) { + continue; + } + validate_public_dependency_sections( + workspace_root, + member, + package_name, + &manifest, + workspace_dependencies, + &public_packages, + &architecture.initial_version, + )?; + } + Ok(()) +} + +fn validate_public_dependency_sections( + workspace_root: &Path, + member: &str, + owner: &str, + manifest: &toml::Value, + workspace_dependencies: Option<&toml::value::Table>, + public_packages: &BTreeSet<&str>, + initial_version: &str, +) -> Result<(), String> { + let manifest_table = manifest + .as_table() + .ok_or_else(|| format!("{member}/Cargo.toml must be a TOML table"))?; + for section in ["dependencies", "dev-dependencies", "build-dependencies"] { + if let Some(dependencies) = manifest_table.get(section).and_then(toml::Value::as_table) { + validate_public_dependency_table( + workspace_root, + member, + owner, + section, + dependencies, + workspace_dependencies, + public_packages, + initial_version, + )?; + } + } + if let Some(targets) = manifest_table.get("target").and_then(toml::Value::as_table) { + for (target, target_value) in targets { + let Some(target_table) = target_value.as_table() else { + continue; + }; + for section in ["dependencies", "dev-dependencies", "build-dependencies"] { + if let Some(dependencies) = + target_table.get(section).and_then(toml::Value::as_table) + { + validate_public_dependency_table( + workspace_root, + member, + owner, + &format!("target.{target}.{section}"), + dependencies, + workspace_dependencies, + public_packages, + initial_version, + )?; + } + } + } + } + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn validate_public_dependency_table( + workspace_root: &Path, + member: &str, + owner: &str, + section: &str, + dependencies: &toml::value::Table, + workspace_dependencies: Option<&toml::value::Table>, + public_packages: &BTreeSet<&str>, + initial_version: &str, +) -> Result<(), String> { + for (dependency_key, declaration) in dependencies { + let inherits_workspace = declaration + .as_table() + .and_then(|table| table.get("workspace")) + .and_then(toml::Value::as_bool) + == Some(true); + let resolved = if inherits_workspace { + workspace_dependencies + .and_then(|dependencies| dependencies.get(dependency_key)) + .ok_or_else(|| { + format!( + "public package {owner} {section}.{dependency_key} inherits a missing workspace dependency" + ) + })? + } else { + declaration + }; + let resolved_table = resolved.as_table(); + let dependency_path = resolved_table + .and_then(|table| table.get("path")) + .and_then(toml::Value::as_str); + let declared_package = resolved_table + .and_then(|table| table.get("package")) + .and_then(toml::Value::as_str); + let path_base = if inherits_workspace { + workspace_root.to_path_buf() + } else { + workspace_root.join(member) + }; + let path_package = dependency_path + .map(|path| dependency_package_name(&path_base.join(path))) + .transpose()?; + let normalized_key = dependency_key.replace('_', "-"); + let dependency_name = declared_package + .or(path_package.as_deref()) + .unwrap_or(normalized_key.as_str()); + if !public_packages.contains(dependency_name) { + continue; + } + let version = match resolved { + toml::Value::String(version) => Some(version.as_str()), + toml::Value::Table(table) => table.get("version").and_then(toml::Value::as_str), + _ => None, + }; + let exact_version = format!("={initial_version}"); + if dependency_path.is_none() || version != Some(exact_version.as_str()) { + return Err(format!( + "public package {owner} {section}.{dependency_key} dependency on {dependency_name} must declare path and exact version {exact_version}" + )); + } + } + Ok(()) +} + +fn dependency_package_name(package_root: &Path) -> Result<String, String> { + let manifest_path = package_root.join("Cargo.toml"); + let raw = fs::read_to_string(&manifest_path).map_err(|error| { + format!( + "read dependency manifest {}: {error}", + manifest_path.display() + ) + })?; + let manifest = raw.parse::<toml::Value>().map_err(|error| { + format!( + "parse dependency manifest {}: {error}", + manifest_path.display() + ) + })?; + manifest + .get("package") + .and_then(toml::Value::as_table) + .and_then(|package| package.get("name")) + .and_then(toml::Value::as_str) + .map(str::to_owned) + .ok_or_else(|| format!("{} is missing package.name", manifest_path.display())) +} + fn validate_public_manifest_field( package: &toml::value::Table, key: &str, @@ -663,7 +862,8 @@ mod tests { use super::{ ArchitectureIdentity, ArchitecturePackage, ArchitectureRepository, validate_ledger, - validate_public_package_metadata, validate_workspace_members, validate_workspace_toolchain, + validate_public_dependency_versions, validate_public_package_metadata, + validate_workspace_members, validate_workspace_toolchain, }; fn test_root(label: &str) -> PathBuf { @@ -842,4 +1042,47 @@ adr_required = false assert!(error.contains("workspace public package inventory is missing: radroots")); let _ = fs::remove_dir_all(root); } + + #[test] + fn public_dependencies_require_path_and_exact_version() { + let root = test_root("public_dependency_version"); + fs::create_dir_all(root.join("crates/radroots")).expect("create public package"); + fs::create_dir_all(root.join("crates/dependency")).expect("create dependency package"); + fs::write( + root.join("Cargo.toml"), + format!( + "{}\n[workspace.dependencies]\nradroots_core = {{ package = \"radroots-core\", path = \"crates/dependency\", version = \"=0.1.0\" }}\n", + complete_workspace_manifest("\"crates/radroots\"") + ), + ) + .expect("write workspace manifest"); + fs::write( + root.join("crates/radroots/Cargo.toml"), + "[package]\nname = \"radroots\"\nversion = \"0.1.0\"\n\n[dependencies]\nradroots_core = { workspace = true }\n", + ) + .expect("write public manifest"); + fs::write( + root.join("crates/dependency/Cargo.toml"), + "[package]\nname = \"radroots-core\"\nversion = \"0.1.0\"\n", + ) + .expect("write dependency manifest"); + let mut architecture = architecture(); + architecture.package.push(ArchitecturePackage { + name: "radroots-core".to_owned(), + }); + validate_public_dependency_versions(&root, &architecture) + .expect("path plus exact version public dependency"); + + let workspace_path = root.join("Cargo.toml"); + let workspace = fs::read_to_string(&workspace_path).expect("read workspace manifest"); + fs::write( + &workspace_path, + workspace.replace(", version = \"=0.1.0\"", ""), + ) + .expect("write path-only dependency"); + let error = validate_public_dependency_versions(&root, &architecture) + .expect_err("path-only public dependency must fail"); + assert!(error.contains("must declare path and exact version =0.1.0")); + let _ = fs::remove_dir_all(root); + } }