portable_qualification.rs (4163B)
1 use std::{fs, path::Path, process::Command}; 2 3 use serde::Deserialize; 4 5 #[derive(Debug, Deserialize)] 6 struct PortableMatrix { 7 schema_version: u32, 8 no_std_target: String, 9 no_std_cross_compiler: String, 10 no_std_cross_cflags: String, 11 wasm_target: String, 12 package: Vec<PortablePackage>, 13 } 14 15 #[derive(Debug, Deserialize)] 16 struct PortablePackage { 17 name: String, 18 wasm: bool, 19 } 20 21 pub fn run(workspace_root: &Path) -> Result<(), String> { 22 let matrix = load(workspace_root)?; 23 for package in matrix.package { 24 check( 25 workspace_root, 26 &package.name, 27 &matrix.no_std_target, 28 Some(&matrix.no_std_cross_compiler), 29 Some(&matrix.no_std_cross_cflags), 30 )?; 31 if package.wasm { 32 check( 33 workspace_root, 34 &package.name, 35 &matrix.wasm_target, 36 None, 37 None, 38 )?; 39 } 40 } 41 Ok(()) 42 } 43 44 fn load(workspace_root: &Path) -> Result<PortableMatrix, String> { 45 let path = workspace_root.join("contracts/releases/portable_matrix.toml"); 46 let raw = fs::read_to_string(&path) 47 .map_err(|error| format!("failed to read {}: {error}", path.display()))?; 48 let mut matrix = toml::from_str::<PortableMatrix>(&raw) 49 .map_err(|error| format!("failed to parse {}: {error}", path.display()))?; 50 if matrix.schema_version != 1 51 || matrix.no_std_target != "thumbv7em-none-eabihf" 52 || matrix.no_std_cross_compiler != "zig cc" 53 || matrix.no_std_cross_cflags != "-target thumb-freestanding-eabihf -mcpu=cortex_m4" 54 || matrix.wasm_target != "wasm32-unknown-unknown" 55 { 56 return Err( 57 "portable matrix must retain schema 1 and the governed no_std/WASM targets".to_owned(), 58 ); 59 } 60 matrix 61 .package 62 .sort_by(|left, right| left.name.cmp(&right.name)); 63 let names = matrix 64 .package 65 .iter() 66 .map(|package| package.name.as_str()) 67 .collect::<Vec<_>>(); 68 let expected = [ 69 "radroots_blossom", 70 "radroots_core", 71 "radroots_event", 72 "radroots_event_codec", 73 "radroots_identity", 74 "radroots_nostr", 75 "radroots_protocol", 76 "radroots_secrets", 77 "radroots_signing", 78 "radroots_trade", 79 "radroots_transport", 80 ]; 81 if names != expected || matrix.package.iter().any(|package| !package.wasm) { 82 return Err( 83 "portable matrix must contain exactly the 11 declared no_std and WASM packages" 84 .to_owned(), 85 ); 86 } 87 Ok(matrix) 88 } 89 90 fn check( 91 workspace_root: &Path, 92 package: &str, 93 target: &str, 94 cross_compiler: Option<&str>, 95 cross_cflags: Option<&str>, 96 ) -> Result<(), String> { 97 let args = [ 98 "check", 99 "-p", 100 package, 101 "--lib", 102 "--no-default-features", 103 "--target", 104 target, 105 "--locked", 106 ]; 107 eprintln!("cargo {}", args.join(" ")); 108 let mut command = Command::new("cargo"); 109 command.args(args).current_dir(workspace_root); 110 if let Some(cross_compiler) = cross_compiler { 111 command.env(format!("CC_{}", target.replace('-', "_")), cross_compiler); 112 } 113 if let Some(cross_cflags) = cross_cflags { 114 command.env(format!("CFLAGS_{}", target.replace('-', "_")), cross_cflags); 115 } 116 let status = command 117 .status() 118 .map_err(|error| format!("failed to start cargo: {error}"))?; 119 if status.success() { 120 Ok(()) 121 } else { 122 Err(format!( 123 "portable qualification failed for {package} on {target}" 124 )) 125 } 126 } 127 128 #[cfg(test)] 129 mod tests { 130 use super::load; 131 132 #[test] 133 fn current_contract_selects_exact_portable_public_packages() { 134 let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) 135 .parent() 136 .and_then(std::path::Path::parent) 137 .expect("workspace root"); 138 let matrix = load(root).expect("portable matrix"); 139 assert_eq!(matrix.package.len(), 11); 140 assert!(matrix.package.iter().all(|package| package.wasm)); 141 } 142 }