use std::fs; use std::path::{Path, PathBuf}; const TARGET_MANIFESTS: &[&str] = &["crates/console/prometeu-bytecode/Cargo.toml", "crates/console/prometeu-vm/Cargo.toml"]; const FORBIDDEN_DEPENDENCY_FRAGMENTS: &[&str] = &["compiler", "frontend"]; #[test] fn pbx_and_pvm_crates_must_not_depend_on_compiler_or_frontend_crates() { let workspace_root = find_workspace_root().expect("runtime workspace root"); let mut violations = Vec::new(); for relative_path in TARGET_MANIFESTS { let manifest_path = workspace_root.join(relative_path); let manifest = fs::read_to_string(&manifest_path) .unwrap_or_else(|error| panic!("failed to read {}: {error}", manifest_path.display())); for dependency in dependency_names(&manifest) { let normalized = dependency.to_ascii_lowercase(); if FORBIDDEN_DEPENDENCY_FRAGMENTS.iter().any(|fragment| normalized.contains(fragment)) { violations.push(format!("{relative_path}: forbidden dependency `{dependency}`")); } } } assert!( violations.is_empty(), "PBX/PVM crates must remain independent of compiler and frontend crates:\n{}", violations.join("\n") ); } fn dependency_names(manifest: &str) -> Vec<&str> { let mut names = Vec::new(); let mut in_dependency_section = false; for raw_line in manifest.lines() { let line = raw_line.trim(); if line.starts_with('[') && line.ends_with(']') { let section = &line[1..line.len() - 1]; in_dependency_section = section == "dependencies" || section == "dev-dependencies" || section == "build-dependencies" || section.ends_with(".dependencies") || section.ends_with(".dev-dependencies") || section.ends_with(".build-dependencies"); continue; } if !in_dependency_section || line.is_empty() || line.starts_with('#') { continue; } if let Some((name, _)) = line.split_once('=') { names.push(name.trim().trim_matches('"')); } } names } fn find_workspace_root() -> Option { let mut cursor = Path::new(env!("CARGO_MANIFEST_DIR")).to_path_buf(); loop { let manifest = cursor.join("Cargo.toml"); if fs::read_to_string(&manifest).is_ok_and(|contents| contents.contains("[workspace]")) { return Some(cursor); } if !cursor.pop() { return None; } } }