@@ -35,16 +35,35 @@ pub async fn extract_wits(
3535
3636 // TODO: figure out what to do if we import two itfs from same dep
3737 for ( index, ( dependency_name, dependency) ) in source. enumerate ( ) {
38- // TODO: map `export`
39- let ( wasm_path, _export) = loader
38+ let import_name = match dependency_name {
39+ DependencyName :: Plain ( _) => None ,
40+ DependencyName :: Package ( dependency_package_name) => {
41+ dependency_package_name. interface . as_ref ( )
42+ // match dependency_package_name.interface.as_ref() {
43+ // Some(itf) => Some(itf),
44+ // None => None,
45+ // }
46+ }
47+ } ;
48+
49+ let ( wasm_path, export) = loader
4050 . load_component_dependency ( dependency_name, dependency)
4151 . await ?;
4252 let wasm_bytes = tokio:: fs:: read ( & wasm_path) . await ?;
4353
4454 let decoded = read_wasm ( & wasm_bytes) ?;
55+ let decoded = match export {
56+ None => decoded,
57+ Some ( export) => munge_aliased_export ( decoded, & export, dependency_name) ?,
58+ } ;
4559 let impo_world = format ! ( "impo-world{index}" ) ;
4660 let importised = importize ( decoded, None , Some ( & impo_world) ) ?;
4761
62+ let imports = match import_name {
63+ None => all_imports ( & importised) ,
64+ Some ( itf) => one_import ( & importised, itf. as_ref ( ) ) ,
65+ } ;
66+
4867 // Capture WITs for all packages used in the importised thing.
4968 // Things like WASI packages may be depended on my multiple packages
5069 // so we index on the package name to avoid emitting them twice.
@@ -68,16 +87,6 @@ pub async fn extract_wits(
6887
6988 // Now add the imports to the aggregating component import world
7089
71- let imports = match dependency_name {
72- DependencyName :: Plain ( _) => all_imports ( & importised) ,
73- DependencyName :: Package ( dependency_package_name) => {
74- match dependency_package_name. interface . as_ref ( ) {
75- Some ( itf) => one_import ( & importised, itf) ,
76- None => all_imports ( & importised) ,
77- }
78- }
79- } ;
80-
8190 let remap = aggregating_resolve. merge ( importised. resolve ( ) . clone ( ) ) ?;
8291 for iid in imports {
8392 let mapped_iid = remap. map_interface ( iid, None ) ?;
@@ -113,6 +122,122 @@ pub async fn extract_wits(
113122 Ok ( buf)
114123}
115124
125+ fn munge_aliased_export (
126+ decoded : DecodedWasm ,
127+ export : & str ,
128+ new_name : & DependencyName ,
129+ ) -> anyhow:: Result < DecodedWasm > {
130+ // TODO: I am not sure how `export` is meant to work if you are
131+ // depping on a package rather than an itf
132+
133+ let export_qname = spin_serde:: DependencyPackageName :: try_from ( export. to_string ( ) ) ?;
134+ let Some ( export_itf_name) = export_qname. interface . as_ref ( ) else {
135+ anyhow:: bail!( "the export name should be a qualified interface name - missing interface" ) ;
136+ } ;
137+ let export_pkg_name = wit_parser:: PackageName {
138+ namespace : export_qname. package . namespace ( ) . to_string ( ) ,
139+ name : export_qname. package . name ( ) . to_string ( ) ,
140+ version : export_qname. version ,
141+ } ;
142+
143+ let DependencyName :: Package ( new_name) = new_name else {
144+ anyhow:: bail!( "the dependency name should be a qualified interface name - not qualified" ) ;
145+ } ;
146+ let Some ( new_itf_name) = new_name. interface . as_ref ( ) else {
147+ anyhow:: bail!(
148+ "the dependency name should be a qualified interface name - missing interface"
149+ ) ;
150+ } ;
151+ let new_pkg_name = wit_parser:: PackageName {
152+ namespace : new_name. package . namespace ( ) . to_string ( ) ,
153+ name : new_name. package . name ( ) . to_string ( ) ,
154+ version : new_name. version . clone ( ) ,
155+ } ;
156+
157+ let ( mut resolve, decode_id) = match decoded {
158+ DecodedWasm :: WitPackage ( resolve, id) => ( resolve, WorldOrPackageId :: Package ( id) ) ,
159+ DecodedWasm :: Component ( resolve, id) => ( resolve, WorldOrPackageId :: World ( id) ) ,
160+ } ;
161+
162+ // Two scenarios:
163+ // 1. The new name is in a package that is already in the Resolve
164+ // 1a. The package already contains an interface with the right name
165+ // 1b. The package does not already contain an interface with the right name
166+ // 2. The new name is in a package that is NOT already in the Resolve
167+
168+ let existing_pkg = resolve
169+ . packages
170+ . iter ( )
171+ . find ( |( _pkg_id, pkg) | pkg. name == new_pkg_name) ;
172+
173+ // We address the first level by creating the new-name package if it doesn't exist
174+ let ( inserting_into_pkg_id, inserting_into_pkg) = match existing_pkg {
175+ Some ( tup) => tup,
176+ None => {
177+ // insert the needed package
178+ let package_wit = format ! ( "package {new_pkg_name};" ) ;
179+ let pkg_id = resolve
180+ . push_str ( std:: env:: current_dir ( ) . unwrap ( ) , & package_wit)
181+ . context ( "failed at setting up fake pkg" ) ?;
182+ let pkg = resolve. packages . get ( pkg_id) . unwrap ( ) ;
183+ ( pkg_id, pkg)
184+ }
185+ } ;
186+
187+ // Second level asks if the package already contains the interface
188+ let existing_itf = inserting_into_pkg. interfaces . get ( new_itf_name. as_ref ( ) ) ;
189+ if existing_itf. is_some ( ) {
190+ // no rename is needed, but we might need to do some extra work to make sure
191+ // that the export, rather than the import, gets included in the aggregated world
192+ return Ok ( decode_id. make_decoded_wasm ( resolve) ) ;
193+ }
194+
195+ // It does not: we need to slurp the EXPORTED itf into the `inserting_into`
196+ // package under the NEW (importing) interface name
197+ let Some ( export_pkg_id) = resolve. package_names . get ( & export_pkg_name) else {
198+ anyhow:: bail!( "export is from a package that doesn't exist" ) ;
199+ } ;
200+ let Some ( export_pkg) = resolve. packages . get ( * export_pkg_id) else {
201+ anyhow:: bail!( "export pkg id doesn't point to a package wtf" ) ;
202+ } ;
203+ let Some ( export_itf_id) = export_pkg. interfaces . get ( export_itf_name. as_ref ( ) ) else {
204+ anyhow:: bail!( "export pkg doesn't contain export itf" ) ;
205+ } ;
206+ let Some ( export_itf) = resolve. interfaces . get ( * export_itf_id) else {
207+ anyhow:: bail!( "export pkg doesn't contain export itf part 2" ) ;
208+ } ;
209+
210+ let mut export_itf = export_itf. clone ( ) ;
211+ export_itf. package = Some ( inserting_into_pkg_id) ;
212+ export_itf. name = Some ( new_itf_name. to_string ( ) ) ;
213+ let export_itf_id_2 = resolve. interfaces . alloc ( export_itf) ;
214+
215+ // OKAY TIME TO ADD THIS UNDER THE WRONG NAME TO THE THINGY
216+ // oh man there is some nonsense about worlds as well
217+ let inserting_into_pkg_mut = resolve. packages . get_mut ( inserting_into_pkg_id) . unwrap ( ) ; // SHENANIGANS to get around a "mutable borrow at the same time as immutable borrow" woe
218+ inserting_into_pkg_mut
219+ . interfaces
220+ . insert ( new_itf_name. to_string ( ) , export_itf_id_2) ;
221+
222+ let thingy = decode_id. make_decoded_wasm ( resolve) ;
223+
224+ Ok ( thingy)
225+ }
226+
227+ enum WorldOrPackageId {
228+ Package ( wit_parser:: PackageId ) ,
229+ World ( wit_parser:: WorldId ) ,
230+ }
231+
232+ impl WorldOrPackageId {
233+ pub fn make_decoded_wasm ( & self , resolve : wit_parser:: Resolve ) -> DecodedWasm {
234+ match self {
235+ Self :: Package ( id) => DecodedWasm :: WitPackage ( resolve, * id) ,
236+ Self :: World ( id) => DecodedWasm :: Component ( resolve, * id) ,
237+ }
238+ }
239+ }
240+
116241fn all_imports ( wasm : & DecodedWasm ) -> Vec < wit_parser:: InterfaceId > {
117242 wasm. resolve ( )
118243 . worlds
@@ -129,7 +254,7 @@ fn as_interface(wi: &wit_parser::WorldItem) -> Option<wit_parser::InterfaceId> {
129254 }
130255}
131256
132- fn one_import ( wasm : & DecodedWasm , name : & spin_serde :: KebabId ) -> Vec < wit_parser:: InterfaceId > {
257+ fn one_import ( wasm : & DecodedWasm , name : & str ) -> Vec < wit_parser:: InterfaceId > {
133258 let id = wasm
134259 . resolve ( )
135260 . interfaces
@@ -182,28 +307,131 @@ fn importize(
182307 Ok ( DecodedWasm :: Component ( resolve, world_id) )
183308}
184309
185- // fn all_imports(wasm: &DecodedWasm) -> Vec<(wit_parser::PackageName, String)> {
186- // let mut itfs = vec![];
187-
188- // for (_pid, pp) in &wasm.resolve().packages {
189- // for (_w, wid) in &pp.worlds {
190- // if let Some(world) = wasm.resolve().worlds.get(*wid) {
191- // for (_wk, witem) in &world.imports {
192- // if let wit_parser::WorldItem::Interface { id, .. } = witem {
193- // if let Some(itf) = wasm.resolve().interfaces.get(*id) {
194- // if let Some(itfp) = itf.package.as_ref() {
195- // if let Some(ppp) = wasm.resolve().packages.get(*itfp) {
196- // if let Some(itfname) = itf.name.as_ref() {
197- // itfs.push((ppp.name.clone(), itfname.clone()));
198- // }
199- // }
200- // }
201- // }
202- // }
203- // }
204- // }
205- // }
206- // }
207-
208- // itfs
209- // }
310+ #[ cfg( test) ]
311+ mod test {
312+ use super :: * ;
313+
314+ fn parse_wit ( wit : & str ) -> anyhow:: Result < wit_parser:: Resolve > {
315+ let mut resolve = wit_parser:: Resolve :: new ( ) ;
316+ resolve. push_str ( "dummy.wit" , wit) ?;
317+ Ok ( resolve)
318+ }
319+
320+ fn generate_dummy_component ( wit : & str , world : & str ) -> Vec < u8 > {
321+ let mut resolve = wit_parser:: Resolve :: default ( ) ;
322+ let package_id = resolve. push_str ( "test" , wit) . expect ( "should parse WIT" ) ;
323+ let world_id = resolve
324+ . select_world ( & [ package_id] , Some ( world) )
325+ . expect ( "should select world" ) ;
326+
327+ let mut wasm = wit_component:: dummy_module (
328+ & resolve,
329+ world_id,
330+ wit_parser:: ManglingAndAbi :: Legacy ( wit_parser:: LiftLowerAbi :: Sync ) ,
331+ ) ;
332+ wit_component:: embed_component_metadata (
333+ & mut wasm,
334+ & resolve,
335+ world_id,
336+ wit_component:: StringEncoding :: UTF8 ,
337+ )
338+ . expect ( "should embed component metadata" ) ;
339+
340+ let mut encoder = wit_component:: ComponentEncoder :: default ( )
341+ . validate ( true )
342+ . module ( & wasm)
343+ . expect ( "should set module" ) ;
344+ encoder. encode ( ) . expect ( "should encode component" )
345+ }
346+
347+ #[ tokio:: test]
348+ async fn if_no_dependencies_then_empty_valid_wit ( ) -> anyhow:: Result < ( ) > {
349+ let wit = extract_wits ( std:: iter:: empty ( ) , "." ) . await ?;
350+
351+ let resolve = parse_wit ( & wit) . expect ( "should have emitted valid WIT" ) ;
352+
353+ assert_eq ! ( 1 , resolve. packages. len( ) ) ;
354+ assert_eq ! (
355+ "root:component" ,
356+ resolve. packages. iter( ) . next( ) . unwrap( ) . 1 . name. to_string( )
357+ ) ;
358+
359+ assert_eq ! ( 0 , resolve. interfaces. len( ) ) ;
360+
361+ assert_eq ! ( 1 , resolve. worlds. len( ) ) ;
362+
363+ let world = resolve. worlds . iter ( ) . next ( ) . unwrap ( ) . 1 ;
364+ assert_eq ! ( "root" , world. name) ;
365+ assert_eq ! ( 0 , world. imports. len( ) ) ;
366+
367+ Ok ( ( ) )
368+ }
369+
370+ #[ tokio:: test]
371+ async fn single_dep_wit_extracted ( ) -> anyhow:: Result < ( ) > {
372+ let tempdir = tempfile:: TempDir :: new ( ) ?;
373+ let dep_file = tempdir. path ( ) . join ( "regex.wasm" ) ;
374+
375+ let dep_wit = "package my:regex@1.0.0;\n \n interface regex {\n matches: func(s: string) -> bool;\n }\n world matcher {\n export regex;\n }" ;
376+ let dep_wasm = generate_dummy_component ( dep_wit, "matcher" ) ;
377+ tokio:: fs:: write ( & dep_file, & dep_wasm) . await ?;
378+
379+ let dep_name =
380+ DependencyName :: Package ( "my:regex/regex@1.0.0" . to_string ( ) . try_into ( ) . unwrap ( ) ) ;
381+ let dep_src = ComponentDependency :: Local {
382+ path : dep_file,
383+ export : None ,
384+ } ;
385+ let deps = std:: iter:: once ( ( & dep_name, & dep_src) ) ;
386+
387+ let wit = extract_wits ( deps, "." ) . await ?;
388+
389+ let resolve = parse_wit ( & wit) . expect ( "should have emitted valid WIT" ) ;
390+
391+ assert_eq ! ( 2 , resolve. packages. len( ) ) ; // root:component and my:regex
392+ let ( _rc_pkg_id, rc_pkg) = resolve
393+ . packages
394+ . iter ( )
395+ . find ( |( _, p) | p. name . to_string ( ) == "root:component" )
396+ . expect ( "should have had `root:component`" ) ;
397+ let ( _mr_pkg_id, _mr_pkg) = resolve
398+ . packages
399+ . iter ( )
400+ . find ( |( _, p) | p. name . to_string ( ) == "my:regex@1.0.0" )
401+ . expect ( "should have had `my:regex`" ) ;
402+
403+ assert_eq ! ( 1 , resolve. interfaces. len( ) ) ;
404+ assert_eq ! (
405+ "regex" ,
406+ resolve
407+ . interfaces
408+ . iter( )
409+ . next( )
410+ . unwrap( )
411+ . 1
412+ . name
413+ . as_ref( )
414+ . unwrap( )
415+ ) ;
416+ let regex_itf_id = resolve. interfaces . iter ( ) . next ( ) . unwrap ( ) . 0 ;
417+
418+ assert_eq ! ( 2 , rc_pkg. worlds. len( ) ) ; // root and synthetic "impo*" wart
419+ let root_world_id = rc_pkg
420+ . worlds
421+ . iter ( )
422+ . find ( |w| w. 0 == "root" )
423+ . expect ( "should have had `root` world" )
424+ . 1 ;
425+
426+ let world = resolve. worlds . get ( * root_world_id) . unwrap ( ) ;
427+ assert_eq ! ( 1 , world. imports. len( ) ) ;
428+ let expected_import = wit_parser:: WorldItem :: Interface {
429+ id : regex_itf_id,
430+ stability : wit_parser:: Stability :: Unknown ,
431+ } ;
432+ let import = world. imports . values ( ) . next ( ) . unwrap ( ) ;
433+ assert_eq ! ( & expected_import, import) ;
434+
435+ Ok ( ( ) )
436+ }
437+ }
0 commit comments