11use heck:: * ;
2+ use rayon:: prelude:: * ;
23use std:: collections:: { BTreeMap , HashSet } ;
34use std:: env;
45use std:: fs;
56use std:: path:: { Path , PathBuf } ;
67use std:: process:: Command ;
8+ use std:: time:: SystemTime ;
79use wit_component:: ComponentEncoder ;
810
911fn main ( ) {
@@ -62,6 +64,7 @@ impl Artifacts {
6264 let mut kinds = BTreeMap :: new ( ) ;
6365 let missing_sdk_path =
6466 PathBuf :: from ( "Asset not compiled, WASI_SDK_PATH missing at compile time" ) ;
67+ let mut components = Vec :: new ( ) ;
6568 for test in tests. iter ( ) {
6669 let shouty_snake = test. name . to_shouty_snake_case ( ) ;
6770 let snake = test. name . to_snake_case ( ) ;
@@ -122,14 +125,20 @@ impl Artifacts {
122125 {
123126 continue ;
124127 }
125- let adapter = match test. name . as_str ( ) {
128+ let ( adapter, mtime ) = match test. name . as_str ( ) {
126129 "reactor" => & reactor_adapter,
127130 s if s. starts_with ( "p3_" ) => & reactor_adapter,
128131 s if s. starts_with ( "p2_api_proxy" ) => & proxy_adapter,
129132 _ => & command_adapter,
130133 } ;
131134 let path = match & test. core_wasm {
132- Some ( path) => self . compile_component ( path, adapter) ,
135+ Some ( path) => {
136+ let out_dir = path. parent ( ) . unwrap ( ) ;
137+ let stem = path. file_stem ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
138+ let component_path = out_dir. join ( format ! ( "{stem}.component.wasm" ) ) ;
139+ components. push ( ( path, adapter, mtime. as_ref ( ) , component_path. clone ( ) ) ) ;
140+ component_path
141+ }
133142 None => missing_sdk_path. clone ( ) ,
134143 } ;
135144 generated_code +=
@@ -141,6 +150,12 @@ impl Artifacts {
141150 ) ;
142151 }
143152
153+ components
154+ . par_iter ( )
155+ . for_each ( |( wasm, adapter, mtime, component_path) | {
156+ self . compile_component ( wasm, adapter, * mtime, component_path) ;
157+ } ) ;
158+
144159 for ( kind, targets) in kinds {
145160 generated_code += & format ! ( "#[macro_export]" ) ;
146161 generated_code += & format ! ( "macro_rules! foreach_{kind} {{\n " ) ;
@@ -206,13 +221,15 @@ impl Artifacts {
206221 generated_code : & mut String ,
207222 name : & str ,
208223 features : & [ & str ] ,
209- ) -> Vec < u8 > {
224+ ) -> ( Vec < u8 > , Option < SystemTime > ) {
210225 let mut cmd = cargo ( ) ;
211226 cmd. arg ( "build" )
212227 . arg ( "--release" )
228+ . arg ( "-vv" )
213229 . arg ( "--package=wasi-preview1-component-adapter" )
214230 . arg ( "--target=wasm32-unknown-unknown" )
215- . env ( "CARGO_TARGET_DIR" , & self . out_dir )
231+ . env ( "CARGO_BUILD_BUILD_DIR" , & self . out_dir )
232+ . env ( "CARGO_TARGET_DIR" , & self . out_dir . join ( name) )
216233 . env ( "RUSTFLAGS" , rustflags ( ) )
217234 . env_remove ( "CARGO_ENCODED_RUSTFLAGS" ) ;
218235 for f in features {
@@ -224,24 +241,55 @@ impl Artifacts {
224241
225242 let artifact = self
226243 . out_dir
244+ . join ( name)
227245 . join ( "wasm32-unknown-unknown" )
228246 . join ( "release" )
229247 . join ( "wasi_snapshot_preview1.wasm" ) ;
230248 let adapter = self
231249 . out_dir
232250 . join ( format ! ( "wasi_snapshot_preview1.{name}.wasm" ) ) ;
233- std:: fs:: copy ( & artifact, & adapter) . unwrap ( ) ;
251+
252+ if let Ok ( prev) = std:: fs:: read ( & adapter)
253+ && let Ok ( cur) = std:: fs:: read ( & artifact)
254+ && prev == cur
255+ {
256+ // nothing to do ...
257+ } else {
258+ if adapter. exists ( ) {
259+ fs:: remove_file ( & adapter) . unwrap ( ) ;
260+ }
261+ std:: fs:: hard_link ( & artifact, & adapter)
262+ . or_else ( |_| std:: fs:: copy ( & artifact, & adapter) . map ( |_| ( ) ) )
263+ . unwrap ( ) ;
264+ }
234265 self . read_deps_of ( & artifact) ;
235266 println ! ( "wasi {name} adapter: {:?}" , & adapter) ;
236267 generated_code. push_str ( & format ! (
237268 "pub const ADAPTER_{}: &'static str = {adapter:?};\n " ,
238269 name. to_shouty_snake_case( ) ,
239270 ) ) ;
240- fs:: read ( & adapter) . unwrap ( )
271+ ( fs:: read ( & adapter) . unwrap ( ) , mtime ( & adapter ) )
241272 }
242273
243274 // Compile a component, return the path of the binary:
244- fn compile_component ( & self , wasm : & Path , adapter : & [ u8 ] ) -> PathBuf {
275+ fn compile_component (
276+ & self ,
277+ wasm : & Path ,
278+ adapter : & [ u8 ] ,
279+ adapter_mtime : Option < & SystemTime > ,
280+ component_path : & Path ,
281+ ) {
282+ // If the component exists and was last updated after the inputs that
283+ // make it up then there's no need to recreate it.
284+ if let Some ( wasm_mtime) = mtime ( wasm)
285+ && let Some ( adapter_mtime) = adapter_mtime
286+ && let Some ( component_mtime) = mtime ( & component_path)
287+ && wasm_mtime < component_mtime
288+ && * adapter_mtime < component_mtime
289+ {
290+ println ! ( "reusing cached component for {wasm:?}" ) ;
291+ return ;
292+ }
245293 println ! ( "creating a component from {wasm:?}" ) ;
246294 let module = fs:: read ( wasm) . expect ( "read wasm module" ) ;
247295 let component = ComponentEncoder :: default ( )
@@ -252,59 +300,63 @@ impl Artifacts {
252300 . unwrap ( )
253301 . encode ( )
254302 . expect ( "module can be translated to a component" ) ;
255- let out_dir = wasm. parent ( ) . unwrap ( ) ;
256- let stem = wasm. file_stem ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
257- let component_path = out_dir. join ( format ! ( "{stem}.component.wasm" ) ) ;
258303 fs:: write ( & component_path, component) . expect ( "write component to disk" ) ;
259- component_path
260304 }
261305
262306 fn build_non_rust_tests ( & mut self , tests : & mut Vec < Test > ) {
263307 const ASSETS_REL_SRC_DIR : & ' static str = "../src/bin" ;
264308 println ! ( "cargo:rerun-if-changed={ASSETS_REL_SRC_DIR}" ) ;
265309
266- for entry in fs:: read_dir ( ASSETS_REL_SRC_DIR ) . unwrap ( ) {
267- let entry = entry. unwrap ( ) ;
268- let path = entry. path ( ) ;
269- let name = path. file_stem ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) . to_owned ( ) ;
270- match path. extension ( ) . and_then ( |s| s. to_str ( ) ) {
271- // Compile C/C++ tests with clang
272- Some ( "c" ) | Some ( "cc" ) => self . build_c_or_cpp_test ( path, name, tests) ,
273-
274- // just a header, part of another test.
275- Some ( "h" ) => { }
276-
277- // Convert the text format to binary and use it as a test.
278- Some ( "wat" ) => {
279- let wasm = wat:: parse_file ( & path) . unwrap ( ) ;
280- let core_wasm = self . out_dir . join ( & name) . with_extension ( "wasm" ) ;
281- fs:: write ( & core_wasm, & wasm) . unwrap ( ) ;
282- tests. push ( Test {
283- name,
284- core_wasm : Some ( core_wasm) ,
285- } ) ;
310+ let entries = fs:: read_dir ( ASSETS_REL_SRC_DIR )
311+ . unwrap ( )
312+ . map ( |e| e. unwrap ( ) )
313+ . collect :: < Vec < _ > > ( ) ;
314+ let mut c_tests = entries
315+ . par_iter ( )
316+ . flat_map ( |entry| {
317+ let path = entry. path ( ) ;
318+ let name = path. file_stem ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) . to_owned ( ) ;
319+ match path. extension ( ) . and_then ( |s| s. to_str ( ) ) {
320+ // Compile C/C++ tests with clang
321+ Some ( "c" ) | Some ( "cc" ) => self . build_c_or_cpp_test ( path, name) ,
322+
323+ // just a header, part of another test.
324+ Some ( "h" ) => None ,
325+
326+ // Convert the text format to binary and use it as a test.
327+ Some ( "wat" ) => {
328+ let wasm = wat:: parse_file ( & path) . unwrap ( ) ;
329+ let core_wasm = self . out_dir . join ( & name) . with_extension ( "wasm" ) ;
330+ fs:: write ( & core_wasm, & wasm) . unwrap ( ) ;
331+ Some ( Test {
332+ name,
333+ core_wasm : Some ( core_wasm) ,
334+ } )
335+ }
336+
337+ // these are built above in `build_rust_tests`
338+ Some ( "rs" ) => None ,
339+
340+ // Prevent stray files for now that we don't understand.
341+ Some ( _) => panic ! ( "unknown file extension on {path:?}" ) ,
342+
343+ None => unreachable ! ( "no extension in path {path:?}" ) ,
286344 }
287-
288- // these are built above in `build_rust_tests`
289- Some ( "rs" ) => { }
290-
291- // Prevent stray files for now that we don't understand.
292- Some ( _) => panic ! ( "unknown file extension on {path:?}" ) ,
293-
294- None => unreachable ! ( "no extension in path {path:?}" ) ,
295- }
296- }
345+ } )
346+ . collect :: < Vec < _ > > ( ) ;
347+ c_tests. sort_by_key ( |t| t. name . clone ( ) ) ;
348+ tests. extend ( c_tests) ;
297349 }
298350
299- fn build_c_or_cpp_test ( & mut self , path : PathBuf , name : String , tests : & mut Vec < Test > ) {
351+ fn build_c_or_cpp_test ( & self , path : PathBuf , name : String ) -> Option < Test > {
300352 println ! ( "compiling {path:?}" ) ;
301353 println ! ( "cargo:rerun-if-changed={}" , path. display( ) ) ;
302354 let contents = std:: fs:: read_to_string ( & path) . unwrap ( ) ;
303355 let config =
304356 wasmtime_test_util:: wast:: parse_test_config :: < CTestConfig > ( & contents, "//!" ) . unwrap ( ) ;
305357
306358 if config. skip {
307- return ;
359+ return None ;
308360 }
309361
310362 // The debug tests relying on these assets are ignored by default,
@@ -317,11 +369,10 @@ impl Artifacts {
317369 let wasi_sdk_path = match env:: var_os ( "WASI_SDK_PATH" ) {
318370 Some ( path) => PathBuf :: from ( path) ,
319371 None => {
320- tests . push ( Test {
372+ return Some ( Test {
321373 name,
322374 core_wasm : None ,
323375 } ) ;
324- return ;
325376 }
326377 } ;
327378
@@ -352,7 +403,7 @@ impl Artifacts {
352403 assert ! ( dwp. status( ) . expect( "failed to spawn llvm-dwp" ) . success( ) ) ;
353404 }
354405
355- tests . push ( Test {
406+ return Some ( Test {
356407 name,
357408 core_wasm : Some ( wasm_path) ,
358409 } ) ;
@@ -425,3 +476,7 @@ fn rustflags() -> &'static str {
425476 _ => "" ,
426477 }
427478}
479+
480+ fn mtime ( path : & Path ) -> Option < SystemTime > {
481+ path. metadata ( ) . ok ( ) ?. modified ( ) . ok ( )
482+ }
0 commit comments