1 use heck::*; 2 use std::collections::BTreeMap; 3 use std::env; 4 use std::fs; 5 use std::path::{Path, PathBuf}; 6 use std::process::Command; 7 use wit_component::ComponentEncoder; 8 9 fn main() { 10 build_and_generate_tests(); 11 } 12 13 fn build_and_generate_tests() { 14 let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 15 16 let reactor_adapter = build_adapter(&out_dir, "reactor", &[]); 17 let command_adapter = build_adapter( 18 &out_dir, 19 "command", 20 &["--no-default-features", "--features=command"], 21 ); 22 let proxy_adapter = build_adapter( 23 &out_dir, 24 "proxy", 25 &["--no-default-features", "--features=proxy"], 26 ); 27 28 println!("cargo:rerun-if-changed=../src"); 29 30 // Build the test programs: 31 let mut cmd = cargo(); 32 cmd.arg("build") 33 .arg("--target=wasm32-wasi") 34 .arg("--package=test-programs") 35 .env("CARGO_TARGET_DIR", &out_dir) 36 .env("CARGO_PROFILE_DEV_DEBUG", "2") 37 .env("RUSTFLAGS", rustflags()) 38 .env_remove("CARGO_ENCODED_RUSTFLAGS"); 39 eprintln!("running: {cmd:?}"); 40 let status = cmd.status().unwrap(); 41 assert!(status.success()); 42 43 let meta = cargo_metadata::MetadataCommand::new().exec().unwrap(); 44 let targets = meta 45 .packages 46 .iter() 47 .find(|p| p.name == "test-programs") 48 .unwrap() 49 .targets 50 .iter() 51 .filter(move |t| t.kind == &["bin"]) 52 .map(|t| &t.name) 53 .collect::<Vec<_>>(); 54 55 let mut generated_code = String::new(); 56 57 let mut kinds = BTreeMap::new(); 58 59 for target in targets { 60 let camel = target.to_shouty_snake_case(); 61 let wasm = out_dir 62 .join("wasm32-wasi") 63 .join("debug") 64 .join(format!("{target}.wasm")); 65 66 generated_code += &format!("pub const {camel}: &'static str = {wasm:?};\n"); 67 68 // Bucket, based on the name of the test, into a "kind" which generates 69 // a `foreach_*` macro below. 70 let kind = match target.as_str() { 71 s if s.starts_with("http_") => "http", 72 s if s.starts_with("preview1_") => "preview1", 73 s if s.starts_with("preview2_") => "preview2", 74 s if s.starts_with("cli_") => "cli", 75 s if s.starts_with("api_") => "api", 76 s if s.starts_with("nn_") => "nn", 77 s if s.starts_with("piped_") => "piped", 78 s if s.starts_with("dwarf_") => "dwarf", 79 // If you're reading this because you hit this panic, either add it 80 // to a test suite above or add a new "suite". The purpose of the 81 // categorization above is to have a static assertion that tests 82 // added are actually run somewhere, so as long as you're also 83 // adding test code somewhere that's ok. 84 other => { 85 panic!("don't know how to classify test name `{other}` to a kind") 86 } 87 }; 88 if !kind.is_empty() { 89 kinds.entry(kind).or_insert(Vec::new()).push(target); 90 } 91 92 // Generate a component from each test. 93 if target == "dwarf_imported_memory" 94 || target == "dwarf_shared_memory" 95 || target.starts_with("nn_witx") 96 { 97 continue; 98 } 99 let adapter = match target.as_str() { 100 "reactor" => &reactor_adapter, 101 s if s.starts_with("api_proxy") => &proxy_adapter, 102 _ => &command_adapter, 103 }; 104 let path = compile_component(&wasm, adapter); 105 generated_code += &format!("pub const {camel}_COMPONENT: &'static str = {path:?};\n"); 106 } 107 108 for (kind, targets) in kinds { 109 generated_code += &format!("#[macro_export]"); 110 generated_code += &format!("macro_rules! foreach_{kind} {{\n"); 111 generated_code += &format!(" ($mac:ident) => {{\n"); 112 for target in targets { 113 generated_code += &format!("$mac!({target});\n") 114 } 115 generated_code += &format!(" }}\n"); 116 generated_code += &format!("}}\n"); 117 } 118 119 std::fs::write(out_dir.join("gen.rs"), generated_code).unwrap(); 120 } 121 122 // Build the WASI Preview 1 adapter, and get the binary: 123 fn build_adapter(out_dir: &PathBuf, name: &str, features: &[&str]) -> Vec<u8> { 124 println!("cargo:rerun-if-changed=../../wasi-preview1-component-adapter"); 125 let mut cmd = cargo(); 126 cmd.arg("build") 127 .arg("--release") 128 .arg("--package=wasi-preview1-component-adapter") 129 .arg("--target=wasm32-unknown-unknown") 130 .env("CARGO_TARGET_DIR", out_dir) 131 .env("RUSTFLAGS", rustflags()) 132 .env_remove("CARGO_ENCODED_RUSTFLAGS"); 133 for f in features { 134 cmd.arg(f); 135 } 136 eprintln!("running: {cmd:?}"); 137 let status = cmd.status().unwrap(); 138 assert!(status.success()); 139 140 let adapter = out_dir.join(format!("wasi_snapshot_preview1.{name}.wasm")); 141 std::fs::copy( 142 out_dir 143 .join("wasm32-unknown-unknown") 144 .join("release") 145 .join("wasi_snapshot_preview1.wasm"), 146 &adapter, 147 ) 148 .unwrap(); 149 println!("wasi {name} adapter: {:?}", &adapter); 150 fs::read(&adapter).unwrap() 151 } 152 153 fn rustflags() -> &'static str { 154 match option_env!("RUSTFLAGS") { 155 // If we're in CI which is denying warnings then deny warnings to code 156 // built here too to keep the tree warning-free. 157 Some(s) if s.contains("-D warnings") => "-D warnings", 158 _ => "", 159 } 160 } 161 162 // Compile a component, return the path of the binary: 163 fn compile_component(wasm: &Path, adapter: &[u8]) -> PathBuf { 164 println!("creating a component from {wasm:?}"); 165 let module = fs::read(wasm).expect("read wasm module"); 166 let component = ComponentEncoder::default() 167 .module(module.as_slice()) 168 .unwrap() 169 .validate(true) 170 .adapter("wasi_snapshot_preview1", adapter) 171 .unwrap() 172 .encode() 173 .expect("module can be translated to a component"); 174 let out_dir = wasm.parent().unwrap(); 175 let stem = wasm.file_stem().unwrap().to_str().unwrap(); 176 let component_path = out_dir.join(format!("{stem}.component.wasm")); 177 fs::write(&component_path, component).expect("write component to disk"); 178 component_path 179 } 180 181 fn cargo() -> Command { 182 // Miri configures its own sysroot which we don't want to use, so remove 183 // miri's own wrappers around rustc to ensure that we're using the real 184 // rustc to build these programs. 185 let mut cargo = Command::new("cargo"); 186 if std::env::var("CARGO_CFG_MIRI").is_ok() { 187 cargo.env_remove("RUSTC").env_remove("RUSTC_WRAPPER"); 188 } 189 cargo 190 } 191