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", "1") 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 let adapter = match target.as_str() { 69 "reactor" => &reactor_adapter, 70 s if s.starts_with("api_proxy") => &proxy_adapter, 71 _ => &command_adapter, 72 }; 73 let path = compile_component(&wasm, adapter); 74 generated_code += &format!("pub const {camel}_COMPONENT: &'static str = {path:?};\n"); 75 76 // Bucket, based on the name of the test, into a "kind" which generates 77 // a `foreach_*` macro below. 78 let kind = match target.as_str() { 79 s if s.starts_with("http_") => "http", 80 s if s.starts_with("preview1_") => "preview1", 81 s if s.starts_with("preview2_") => "preview2", 82 s if s.starts_with("cli_") => "cli", 83 s if s.starts_with("api_") => "api", 84 // If you're reading this because you hit this panic, either add it 85 // to a test suite above or add a new "suite". The purpose of the 86 // categorization above is to have a static assertion that tests 87 // added are actually run somewhere, so as long as you're also 88 // adding test code somewhere that's ok. 89 other => { 90 panic!("don't know how to classify test name `{other}` to a kind") 91 } 92 }; 93 if !kind.is_empty() { 94 kinds.entry(kind).or_insert(Vec::new()).push(target); 95 } 96 } 97 98 for (kind, targets) in kinds { 99 generated_code += &format!("#[macro_export]"); 100 generated_code += &format!("macro_rules! foreach_{kind} {{\n"); 101 generated_code += &format!(" ($mac:ident) => {{\n"); 102 for target in targets { 103 generated_code += &format!("$mac!({target});\n") 104 } 105 generated_code += &format!(" }}\n"); 106 generated_code += &format!("}}\n"); 107 } 108 109 std::fs::write(out_dir.join("gen.rs"), generated_code).unwrap(); 110 } 111 112 // Build the WASI Preview 1 adapter, and get the binary: 113 fn build_adapter(out_dir: &PathBuf, name: &str, features: &[&str]) -> Vec<u8> { 114 println!("cargo:rerun-if-changed=../../wasi-preview1-component-adapter"); 115 let mut cmd = cargo(); 116 cmd.arg("build") 117 .arg("--release") 118 .arg("--package=wasi-preview1-component-adapter") 119 .arg("--target=wasm32-unknown-unknown") 120 .env("CARGO_TARGET_DIR", out_dir) 121 .env("RUSTFLAGS", rustflags()) 122 .env_remove("CARGO_ENCODED_RUSTFLAGS"); 123 for f in features { 124 cmd.arg(f); 125 } 126 eprintln!("running: {cmd:?}"); 127 let status = cmd.status().unwrap(); 128 assert!(status.success()); 129 130 let adapter = out_dir.join(format!("wasi_snapshot_preview1.{name}.wasm")); 131 std::fs::copy( 132 out_dir 133 .join("wasm32-unknown-unknown") 134 .join("release") 135 .join("wasi_snapshot_preview1.wasm"), 136 &adapter, 137 ) 138 .unwrap(); 139 println!("wasi {name} adapter: {:?}", &adapter); 140 fs::read(&adapter).unwrap() 141 } 142 143 fn rustflags() -> &'static str { 144 match option_env!("RUSTFLAGS") { 145 // If we're in CI which is denying warnings then deny warnings to code 146 // built here too to keep the tree warning-free. 147 Some(s) if s.contains("-D warnings") => "-D warnings", 148 _ => "", 149 } 150 } 151 152 // Compile a component, return the path of the binary: 153 fn compile_component(wasm: &Path, adapter: &[u8]) -> PathBuf { 154 println!("creating a component from {wasm:?}"); 155 let module = fs::read(wasm).expect("read wasm module"); 156 let component = ComponentEncoder::default() 157 .module(module.as_slice()) 158 .unwrap() 159 .validate(true) 160 .adapter("wasi_snapshot_preview1", adapter) 161 .unwrap() 162 .encode() 163 .expect("module can be translated to a component"); 164 let out_dir = wasm.parent().unwrap(); 165 let stem = wasm.file_stem().unwrap().to_str().unwrap(); 166 let component_path = out_dir.join(format!("{stem}.component.wasm")); 167 fs::write(&component_path, component).expect("write component to disk"); 168 component_path 169 } 170 171 fn cargo() -> Command { 172 // Miri configures its own sysroot which we don't want to use, so remove 173 // miri's own wrappers around rustc to ensure that we're using the real 174 // rustc to build these programs. 175 let mut cargo = Command::new("cargo"); 176 if std::env::var("CARGO_CFG_MIRI").is_ok() { 177 cargo.env_remove("RUSTC").env_remove("RUSTC_WRAPPER"); 178 } 179 cargo 180 } 181