@@ -4,6 +4,7 @@ use std::collections::{BTreeMap, HashMap};
44use std:: path:: { Path , PathBuf } ;
55
66use anyhow:: { bail, Context , Result } ;
7+ use async_trait:: async_trait;
78use docker_credential:: DockerCredential ;
89use futures_util:: future;
910use futures_util:: stream:: { self , StreamExt , TryStreamExt } ;
@@ -18,7 +19,7 @@ use spin_common::ui::quoted_path;
1819use spin_common:: url:: parse_file_url;
1920use spin_loader:: cache:: Cache ;
2021use spin_loader:: FilesMountStrategy ;
21- use spin_locked_app:: locked:: { ContentPath , ContentRef , LockedApp } ;
22+ use spin_locked_app:: locked:: { ContentPath , ContentRef , LockedApp , LockedComponentSource } ;
2223use tokio:: fs;
2324use walkdir:: WalkDir ;
2425
@@ -119,6 +120,7 @@ impl Client {
119120 reference : impl AsRef < str > ,
120121 annotations : Option < BTreeMap < String , String > > ,
121122 infer_annotations : InferPredefinedAnnotations ,
123+ skip_compose : bool ,
122124 ) -> Result < Option < String > > {
123125 let reference: Reference = reference
124126 . as_ref ( )
@@ -137,8 +139,15 @@ impl Client {
137139 )
138140 . await ?;
139141
140- self . push_locked_core ( locked, auth, reference, annotations, infer_annotations)
141- . await
142+ self . push_locked_core (
143+ locked,
144+ auth,
145+ reference,
146+ annotations,
147+ infer_annotations,
148+ skip_compose,
149+ )
150+ . await
142151 }
143152
144153 /// Push a Spin application to an OCI registry and return the digest (or None
@@ -149,15 +158,23 @@ impl Client {
149158 reference : impl AsRef < str > ,
150159 annotations : Option < BTreeMap < String , String > > ,
151160 infer_annotations : InferPredefinedAnnotations ,
161+ skip_compose : bool ,
152162 ) -> Result < Option < String > > {
153163 let reference: Reference = reference
154164 . as_ref ( )
155165 . parse ( )
156166 . with_context ( || format ! ( "cannot parse reference {}" , reference. as_ref( ) ) ) ?;
157167 let auth = Self :: auth ( & reference) . await ?;
158168
159- self . push_locked_core ( locked, auth, reference, annotations, infer_annotations)
160- . await
169+ self . push_locked_core (
170+ locked,
171+ auth,
172+ reference,
173+ annotations,
174+ infer_annotations,
175+ skip_compose,
176+ )
177+ . await
161178 }
162179
163180 /// Push a Spin application to an OCI registry and return the digest (or None
@@ -169,10 +186,11 @@ impl Client {
169186 reference : Reference ,
170187 annotations : Option < BTreeMap < String , String > > ,
171188 infer_annotations : InferPredefinedAnnotations ,
189+ skip_compose : bool ,
172190 ) -> Result < Option < String > > {
173191 let mut locked_app = locked. clone ( ) ;
174192 let mut layers = self
175- . assemble_layers ( & mut locked_app, AssemblyMode :: Simple )
193+ . assemble_layers ( & mut locked_app, AssemblyMode :: Simple , skip_compose )
176194 . await
177195 . context ( "could not assemble layers for locked application" ) ?;
178196
@@ -183,7 +201,7 @@ impl Client {
183201 {
184202 locked_app = locked. clone ( ) ;
185203 layers = self
186- . assemble_layers ( & mut locked_app, AssemblyMode :: Archive )
204+ . assemble_layers ( & mut locked_app, AssemblyMode :: Archive , skip_compose )
187205 . await
188206 . context ( "could not assemble archive layers for locked application" ) ?;
189207 }
@@ -246,43 +264,57 @@ impl Client {
246264 & mut self ,
247265 locked : & mut LockedApp ,
248266 assembly_mode : AssemblyMode ,
267+ skip_compose : bool ,
249268 ) -> Result < Vec < ImageLayer > > {
250269 let mut layers = Vec :: new ( ) ;
251270 let mut components = Vec :: new ( ) ;
252271 for mut c in locked. clone ( ) . components {
253- // Add the wasm module for the component as layers.
254- let source = c
255- . clone ( )
256- . source
257- . content
258- . source
259- . context ( "component loaded from disk should contain a file source" ) ?;
260-
261- let source = parse_file_url ( source. as_str ( ) ) ?;
262- let layer = Self :: wasm_layer ( & source) . await ?;
263-
264- // Update the module source with the content ref of the layer.
265- c. source . content = self . content_ref_for_layer ( & layer) ;
266-
267- layers. push ( layer) ;
268-
269- let mut deps = BTreeMap :: default ( ) ;
270- for ( dep_name, mut dep) in c. dependencies {
271- let source = dep
272+ if !skip_compose {
273+ let composed = spin_compose:: compose ( & ComponentSourceLoader , & c)
274+ . await
275+ . with_context ( || {
276+ format ! ( "failed to resolve dependencies for component {:?}" , c. id)
277+ } ) ?;
278+
279+ let layer = ImageLayer :: new ( composed, WASM_LAYER_MEDIA_TYPE . to_string ( ) , None ) ;
280+ c. source . content = self . content_ref_for_layer ( & layer) ;
281+ c. dependencies . clear ( ) ;
282+ layers. push ( layer) ;
283+ } else {
284+ // Add the wasm module for the component as layers.
285+ let source = c
286+ . clone ( )
272287 . source
273288 . content
274289 . source
275- . context ( "dependency loaded from disk should contain a file source" ) ?;
276- let source = parse_file_url ( source. as_str ( ) ) ?;
290+ . context ( "component loaded from disk should contain a file source" ) ?;
277291
292+ let source = parse_file_url ( source. as_str ( ) ) ?;
278293 let layer = Self :: wasm_layer ( & source) . await ?;
279294
280- dep . source . content = self . content_ref_for_layer ( & layer) ;
281- deps . insert ( dep_name , dep ) ;
295+ // Update the module source with the content ref of the layer.
296+ c . source . content = self . content_ref_for_layer ( & layer ) ;
282297
283298 layers. push ( layer) ;
299+
300+ let mut deps = BTreeMap :: default ( ) ;
301+ for ( dep_name, mut dep) in c. dependencies {
302+ let source = dep
303+ . source
304+ . content
305+ . source
306+ . context ( "dependency loaded from disk should contain a file source" ) ?;
307+ let source = parse_file_url ( source. as_str ( ) ) ?;
308+
309+ let layer = Self :: wasm_layer ( & source) . await ?;
310+
311+ dep. source . content = self . content_ref_for_layer ( & layer) ;
312+ deps. insert ( dep_name, dep) ;
313+
314+ layers. push ( layer) ;
315+ }
316+ c. dependencies = deps;
284317 }
285- c. dependencies = deps;
286318
287319 let mut files = Vec :: new ( ) ;
288320 for f in c. files {
@@ -669,6 +701,32 @@ impl Client {
669701 }
670702}
671703
704+ struct ComponentSourceLoader ;
705+
706+ #[ async_trait]
707+ impl spin_compose:: ComponentSourceLoader for ComponentSourceLoader {
708+ async fn load_component_source (
709+ & self ,
710+ source : & LockedComponentSource ,
711+ ) -> anyhow:: Result < Vec < u8 > > {
712+ let source = source
713+ . content
714+ . source
715+ . as_ref ( )
716+ . context ( "component loaded from disk should contain a file source" ) ?;
717+
718+ let source = parse_file_url ( source. as_str ( ) ) ?;
719+
720+ let bytes = fs:: read ( & source)
721+ . await
722+ . with_context ( || format ! ( "cannot read wasm module {}" , quoted_path( source) ) ) ?;
723+
724+ let component = spin_componentize:: componentize_if_necessary ( & bytes) ?;
725+
726+ Ok ( component. into ( ) )
727+ }
728+ }
729+
672730/// Unpack contents of the provided archive layer, represented by bytes and its
673731/// corresponding digest, into the provided cache.
674732/// A temporary staging directory is created via tempfile::tempdir() to store
@@ -1137,7 +1195,7 @@ mod test {
11371195 assert_eq ! (
11381196 e,
11391197 client
1140- . assemble_layers( & mut locked, AssemblyMode :: Simple )
1198+ . assemble_layers( & mut locked, AssemblyMode :: Simple , true )
11411199 . await
11421200 . unwrap_err( )
11431201 . to_string( ) ,
@@ -1149,7 +1207,7 @@ mod test {
11491207 assert_eq ! (
11501208 tc. expected_layer_count,
11511209 client
1152- . assemble_layers( & mut locked, AssemblyMode :: Simple )
1210+ . assemble_layers( & mut locked, AssemblyMode :: Simple , true )
11531211 . await
11541212 . unwrap( )
11551213 . len( ) ,
0 commit comments