lib

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

target_qualification.rs (7936B)


      1 use std::{fs, path::Path, process::Command};
      2 
      3 use serde::Deserialize;
      4 
      5 #[derive(Debug, Deserialize)]
      6 struct TargetMatrix {
      7     schema_version: u32,
      8     msrv_toolchain: String,
      9     current_toolchain: String,
     10     operating_system: Vec<OperatingSystem>,
     11 }
     12 
     13 #[derive(Debug, Deserialize, PartialEq, Eq)]
     14 struct OperatingSystem {
     15     name: String,
     16     target: String,
     17     cross_compiler: Option<String>,
     18     cross_cflags: Option<String>,
     19 }
     20 
     21 #[derive(Debug, Deserialize)]
     22 struct Catalog {
     23     package: Vec<CatalogPackage>,
     24 }
     25 
     26 #[derive(Debug, Deserialize)]
     27 struct CatalogPackage {
     28     name: String,
     29     state: String,
     30     groups: Vec<String>,
     31 }
     32 
     33 pub fn run(workspace_root: &Path) -> Result<(), String> {
     34     let (matrix, packages) = load(workspace_root)?;
     35     for toolchain in [&matrix.msrv_toolchain, &matrix.current_toolchain] {
     36         run_command(
     37             workspace_root,
     38             "rustup",
     39             &[
     40                 "run",
     41                 toolchain,
     42                 "cargo",
     43                 "check",
     44                 "--workspace",
     45                 "--all-targets",
     46                 "--locked",
     47             ],
     48         )?;
     49     }
     50     for operating_system in matrix.operating_system {
     51         for package in &packages {
     52             run_target_command(
     53                 workspace_root,
     54                 &operating_system,
     55                 &[
     56                     "check",
     57                     "-p",
     58                     package,
     59                     "--lib",
     60                     "--target",
     61                     &operating_system.target,
     62                     "--locked",
     63                 ],
     64             )?;
     65         }
     66     }
     67     Ok(())
     68 }
     69 
     70 fn load(workspace_root: &Path) -> Result<(TargetMatrix, Vec<String>), String> {
     71     let matrix_path = workspace_root.join("contracts/releases/target_matrix.toml");
     72     let matrix = toml::from_str::<TargetMatrix>(&read(&matrix_path)?)
     73         .map_err(|error| format!("failed to parse {}: {error}", matrix_path.display()))?;
     74     validate(&matrix)?;
     75 
     76     let catalog_path = workspace_root.join("contracts/crates/catalog.v2.toml");
     77     let catalog = toml::from_str::<Catalog>(&read(&catalog_path)?)
     78         .map_err(|error| format!("failed to parse {}: {error}", catalog_path.display()))?;
     79     let mut packages = catalog
     80         .package
     81         .into_iter()
     82         .filter(|package| {
     83             package.state == "active" && package.groups.iter().any(|group| group == "public_native")
     84         })
     85         .map(|package| package.name)
     86         .collect::<Vec<_>>();
     87     packages.sort();
     88     if packages.len() != 19 {
     89         return Err(format!(
     90             "target qualification requires exactly 19 public packages, found {}",
     91             packages.len()
     92         ));
     93     }
     94     Ok((matrix, packages))
     95 }
     96 
     97 fn read(path: &Path) -> Result<String, String> {
     98     fs::read_to_string(path).map_err(|error| format!("failed to read {}: {error}", path.display()))
     99 }
    100 
    101 fn validate(matrix: &TargetMatrix) -> Result<(), String> {
    102     if matrix.schema_version != 1 {
    103         return Err(format!(
    104             "target matrix schema_version must be 1, found {}",
    105             matrix.schema_version
    106         ));
    107     }
    108     if matrix.msrv_toolchain != "1.97.1" || matrix.current_toolchain != "stable" {
    109         return Err("target matrix must retain MSRV 1.97.1 and current stable".to_owned());
    110     }
    111     let expected = [
    112         (
    113             "linux",
    114             "x86_64-unknown-linux-gnu",
    115             Some("zig cc"),
    116             Some("-target x86_64-linux-gnu"),
    117         ),
    118         ("macos", "aarch64-apple-darwin", None, None),
    119     ];
    120     if matrix.operating_system.len() != expected.len()
    121         || expected.iter().any(|(name, target, compiler, cflags)| {
    122             !matrix.operating_system.iter().any(|entry| {
    123                 entry.name == *name
    124                     && entry.target == *target
    125                     && entry.cross_compiler.as_deref() == *compiler
    126                     && entry.cross_cflags.as_deref() == *cflags
    127             })
    128         })
    129     {
    130         return Err(
    131             "target matrix must contain the exact Linux x86_64 and macOS aarch64 triples"
    132                 .to_owned(),
    133         );
    134     }
    135     Ok(())
    136 }
    137 
    138 fn run_target_command(
    139     workspace_root: &Path,
    140     operating_system: &OperatingSystem,
    141     args: &[&str],
    142 ) -> Result<(), String> {
    143     eprintln!("cargo {}", args.join(" "));
    144     let mut command = Command::new("cargo");
    145     command.args(args).current_dir(workspace_root);
    146     let target_key = operating_system.target.replace('-', "_");
    147     if let Some(compiler) = operating_system.cross_compiler.as_deref() {
    148         command.env(format!("CC_{target_key}"), compiler);
    149     }
    150     if let Some(cflags) = operating_system.cross_cflags.as_deref() {
    151         command.env(format!("CFLAGS_{target_key}"), cflags);
    152     }
    153     let status = command
    154         .status()
    155         .map_err(|error| format!("failed to start cargo: {error}"))?;
    156     if status.success() {
    157         Ok(())
    158     } else {
    159         Err(format!(
    160             "target qualification failed: cargo {}",
    161             args.join(" ")
    162         ))
    163     }
    164 }
    165 
    166 fn run_command(workspace_root: &Path, program: &str, args: &[&str]) -> Result<(), String> {
    167     eprintln!("{program} {}", args.join(" "));
    168     let status = Command::new(program)
    169         .args(args)
    170         .current_dir(workspace_root)
    171         .status()
    172         .map_err(|error| format!("failed to start {program}: {error}"))?;
    173     if status.success() {
    174         Ok(())
    175     } else {
    176         Err(format!(
    177             "target qualification failed: {program} {}",
    178             args.join(" ")
    179         ))
    180     }
    181 }
    182 
    183 #[cfg(test)]
    184 mod tests {
    185     use super::{OperatingSystem, TargetMatrix, load, validate};
    186 
    187     #[test]
    188     fn current_contract_selects_exact_toolchains_targets_and_packages() {
    189         let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
    190             .parent()
    191             .and_then(std::path::Path::parent)
    192             .expect("workspace root");
    193         let (matrix, packages) = load(root).expect("target matrix");
    194         assert_eq!(matrix.msrv_toolchain, "1.97.1");
    195         assert_eq!(matrix.current_toolchain, "stable");
    196         assert_eq!(matrix.operating_system.len(), 2);
    197         assert_eq!(
    198             matrix
    199                 .operating_system
    200                 .iter()
    201                 .map(|entry| (entry.name.as_str(), entry.target.as_str()))
    202                 .collect::<Vec<_>>(),
    203             [
    204                 ("linux", "x86_64-unknown-linux-gnu"),
    205                 ("macos", "aarch64-apple-darwin"),
    206             ]
    207         );
    208         assert_eq!(packages.len(), 19);
    209     }
    210 
    211     #[test]
    212     fn unsupported_production_targets_are_rejected() {
    213         let mut matrix = TargetMatrix {
    214             schema_version: 1,
    215             msrv_toolchain: "1.97.1".to_owned(),
    216             current_toolchain: "stable".to_owned(),
    217             operating_system: vec![
    218                 OperatingSystem {
    219                     name: "linux".to_owned(),
    220                     target: "x86_64-unknown-linux-gnu".to_owned(),
    221                     cross_compiler: Some("zig cc".to_owned()),
    222                     cross_cflags: Some("-target x86_64-linux-gnu".to_owned()),
    223                 },
    224                 OperatingSystem {
    225                     name: "macos".to_owned(),
    226                     target: "aarch64-apple-darwin".to_owned(),
    227                     cross_compiler: None,
    228                     cross_cflags: None,
    229                 },
    230             ],
    231         };
    232         for (name, target) in [
    233             ("windows", "x86_64-pc-windows-gnu"),
    234             ("macos", "x86_64-apple-darwin"),
    235             ("linux", "aarch64-unknown-linux-gnu"),
    236         ] {
    237             matrix.operating_system.push(OperatingSystem {
    238                 name: name.to_owned(),
    239                 target: target.to_owned(),
    240                 cross_compiler: None,
    241                 cross_cflags: None,
    242             });
    243             assert!(validate(&matrix).is_err(), "accepted {target}");
    244             matrix.operating_system.pop();
    245         }
    246     }
    247 }