refactor(xtask): parse Cargo manifests with toml
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
Test / Lint (push) Failing after 2m1s
Test / Test (push) Skipped
Test / Render parity (push) Skipped

This commit is contained in:
2026-07-18 05:58:21 +04:00
parent fd4be6c825
commit cd57d16d60
+91 -76
View File
@@ -895,12 +895,11 @@ fn validate_cargo_metadata_dependency_closures(
collect_cargo_manifests(root, &mut manifests)?; collect_cargo_manifests(root, &mut manifests)?;
let mut deps_by_package = BTreeMap::new(); let mut deps_by_package = BTreeMap::new();
for manifest in manifests { for manifest in manifests {
let text = fs::read_to_string(&manifest) let policy = load_cargo_manifest_policy(&manifest)?;
.map_err(|err| format!("{}: {err}", manifest.display()))?; let Some(package) = policy.package_name() else {
let Some(package) = parse_package_name(&text) else {
continue; continue;
}; };
deps_by_package.insert(package, parse_manifest_dependencies(&text)); deps_by_package.insert(package.to_string(), policy.dependency_names());
} }
validate_package_closure_excludes("fparkan-headless", &deps_by_package, failures); validate_package_closure_excludes("fparkan-headless", &deps_by_package, failures);
@@ -1109,9 +1108,8 @@ fn validate_workspace_license(root: &Path, failures: &mut Vec<String>) -> Result
manifests.dedup(); manifests.dedup();
for manifest in manifests { for manifest in manifests {
let text = fs::read_to_string(&manifest) let policy = load_cargo_manifest_policy(&manifest)?;
.map_err(|err| format!("{}: {err}", manifest.display()))?; let explicit_license = policy.license();
let explicit_license = parse_manifest_license(&text);
let is_root = manifest == root.join("Cargo.toml"); let is_root = manifest == root.join("Cargo.toml");
if is_root { if is_root {
if explicit_license.as_deref() != Some(expected) { if explicit_license.as_deref() != Some(expected) {
@@ -1136,9 +1134,8 @@ fn validate_dependency_boundaries(root: &Path, failures: &mut Vec<String>) -> Re
let mut manifests = Vec::new(); let mut manifests = Vec::new();
collect_cargo_manifests(root, &mut manifests)?; collect_cargo_manifests(root, &mut manifests)?;
for manifest in manifests { for manifest in manifests {
let text = fs::read_to_string(&manifest) let policy = load_cargo_manifest_policy(&manifest)?;
.map_err(|err| format!("{}: {err}", manifest.display()))?; let Some(package) = policy.package_name() else {
let Some(package) = parse_package_name(&text) else {
continue; continue;
}; };
if is_removed_legacy_adapter_manifest(root, &manifest) { if is_removed_legacy_adapter_manifest(root, &manifest) {
@@ -1148,8 +1145,8 @@ fn validate_dependency_boundaries(root: &Path, failures: &mut Vec<String>) -> Re
)); ));
continue; continue;
} }
let dependencies = parse_manifest_dependencies(&text); let dependencies = policy.dependency_names();
if !is_adapter_like_package(&package) && !is_app_package(&package) { if !is_adapter_like_package(package) && !is_app_package(package) {
for dependency in &dependencies { for dependency in &dependencies {
if is_forbidden_gui_dependency(dependency) { if is_forbidden_gui_dependency(dependency) {
failures.push(format!( failures.push(format!(
@@ -1159,7 +1156,7 @@ fn validate_dependency_boundaries(root: &Path, failures: &mut Vec<String>) -> Re
} }
} }
} }
if is_app_package(&package) { if is_app_package(package) {
if let Some(forbidden) = first_forbidden_parser_dependency(&dependencies) { if let Some(forbidden) = first_forbidden_parser_dependency(&dependencies) {
failures.push(format!( failures.push(format!(
"{}: app package {package} depends on parser crate {forbidden}", "{}: app package {package} depends on parser crate {forbidden}",
@@ -1328,71 +1325,75 @@ fn collect_cargo_manifests(dir: &Path, out: &mut Vec<PathBuf>) -> Result<(), Str
Ok(()) Ok(())
} }
fn parse_manifest_license(manifest: &str) -> Option<String> { /// Typed Cargo manifest subset used by the architecture policy.
let mut in_package = false; ///
let mut in_workspace_package = false; /// Dependency values deliberately remain `toml::Value`: the policy only needs
for line in manifest.lines() { /// keys and Cargo accepts several value forms (version string, inline table,
let trimmed = line.trim(); /// or workspace inheritance).
if trimmed.starts_with('[') { #[derive(Debug, Default, Deserialize)]
in_package = trimmed == "[package]"; struct CargoManifestPolicy {
in_workspace_package = trimmed == "[workspace.package]"; package: Option<CargoPackagePolicy>,
continue; workspace: Option<CargoWorkspacePolicy>,
} dependencies: Option<BTreeMap<String, toml::Value>>,
if (in_package || in_workspace_package) && trimmed.starts_with("license") { #[serde(rename = "dev-dependencies")]
return parse_toml_string_value(trimmed); dev_dependencies: Option<BTreeMap<String, toml::Value>>,
} #[serde(rename = "build-dependencies")]
} build_dependencies: Option<BTreeMap<String, toml::Value>>,
None
} }
fn parse_package_name(manifest: &str) -> Option<String> { #[derive(Debug, Deserialize)]
let mut in_package = false; struct CargoPackagePolicy {
for line in manifest.lines() { name: String,
let trimmed = line.trim(); license: Option<toml::Value>,
if trimmed.starts_with('[') {
in_package = trimmed == "[package]";
continue;
}
if in_package && trimmed.starts_with("name") {
return parse_toml_string_value(trimmed);
}
}
None
} }
fn parse_manifest_dependencies(manifest: &str) -> BTreeSet<String> { #[derive(Debug, Deserialize)]
let mut dependencies = BTreeSet::new(); struct CargoWorkspacePolicy {
let mut in_dependency_section = false; package: Option<CargoWorkspacePackagePolicy>,
for line in manifest.lines() {
let trimmed = line.trim();
if trimmed.starts_with('[') {
in_dependency_section = matches!(
trimmed,
"[dependencies]" | "[dev-dependencies]" | "[build-dependencies]"
);
continue;
}
if !in_dependency_section || trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
let Some((name, _)) = trimmed.split_once('=') else {
continue;
};
let dependency = name.trim().trim_matches('"');
if !dependency.is_empty() {
dependencies.insert(dependency.to_string());
}
}
dependencies
} }
fn parse_toml_string_value(line: &str) -> Option<String> { #[derive(Debug, Deserialize)]
let (_, value) = line.split_once('=')?; struct CargoWorkspacePackagePolicy {
let value = value.trim(); license: Option<toml::Value>,
if !(value.starts_with('"') && value.ends_with('"')) { }
return None;
impl CargoManifestPolicy {
fn package_name(&self) -> Option<&str> {
self.package.as_ref().map(|package| package.name.as_str())
} }
Some(value.trim_matches('"').to_string())
fn license(&self) -> Option<String> {
self.package
.as_ref()
.and_then(|package| cargo_license_string(package.license.as_ref()))
.or_else(|| {
self.workspace
.as_ref()
.and_then(|workspace| workspace.package.as_ref())
.and_then(|package| cargo_license_string(package.license.as_ref()))
})
}
fn dependency_names(&self) -> BTreeSet<String> {
[
self.dependencies.as_ref(),
self.dev_dependencies.as_ref(),
self.build_dependencies.as_ref(),
]
.into_iter()
.flatten()
.flat_map(|dependencies| dependencies.keys().cloned())
.collect()
}
}
fn cargo_license_string(value: Option<&toml::Value>) -> Option<String> {
value.and_then(toml::Value::as_str).map(ToString::to_string)
}
fn load_cargo_manifest_policy(path: &Path) -> Result<CargoManifestPolicy, String> {
let text = fs::read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))?;
toml::from_str(&text).map_err(|err| format!("{}: invalid Cargo TOML: {err}", path.display()))
} }
fn is_removed_legacy_adapter_manifest(root: &Path, manifest: &Path) -> bool { fn is_removed_legacy_adapter_manifest(root: &Path, manifest: &Path) -> bool {
@@ -3494,16 +3495,30 @@ fparkan-render = { path = "../fparkan-render" }
fparkan-render-vulkan = { path = "../../adapters/fparkan-render-vulkan" } fparkan-render-vulkan = { path = "../../adapters/fparkan-render-vulkan" }
"#; "#;
assert_eq!( let policy = toml::from_str::<CargoManifestPolicy>(manifest)
parse_package_name(manifest), .expect("typed policy manifest should parse");
Some("fparkan-example".to_string()) assert_eq!(policy.package_name(), Some("fparkan-example"));
); let deps = policy.dependency_names();
let deps = parse_manifest_dependencies(manifest);
assert!(deps.contains("fparkan-render")); assert!(deps.contains("fparkan-render"));
assert!(deps.contains("quoted-dep")); assert!(deps.contains("quoted-dep"));
assert!(deps.contains("fparkan-render-vulkan")); assert!(deps.contains("fparkan-render-vulkan"));
} }
#[test]
fn typed_manifest_policy_accepts_workspace_inherited_license() {
let manifest = r#"
[package]
name = "fparkan-example"
license.workspace = true
"#;
let policy = toml::from_str::<CargoManifestPolicy>(manifest)
.expect("workspace inheritance should be valid Cargo TOML");
assert_eq!(policy.package_name(), Some("fparkan-example"));
assert_eq!(policy.license(), None);
}
#[test] #[test]
fn workspace_manifest_closure_detects_transitive_platform_bridge() { fn workspace_manifest_closure_detects_transitive_platform_bridge() {
let deps_by_package = [ let deps_by_package = [