@@ -16,31 +16,87 @@ use subprocess::{Exec, Redirection};
1616use crate :: manifest:: component_build_configs;
1717
1818/// If present, run the build command of each component.
19- pub async fn build ( manifest_file : & Path , component_ids : & [ String ] ) -> Result < ( ) > {
20- let ( components, manifest_err) =
21- component_build_configs ( manifest_file)
22- . await
23- . with_context ( || {
24- format ! (
25- "Cannot read manifest file from {}" ,
26- quoted_path( manifest_file)
27- )
28- } ) ?;
19+ pub async fn build (
20+ manifest_file : & Path ,
21+ component_ids : & [ String ] ,
22+ target_checks : TargetChecking ,
23+ cache_root : Option < PathBuf > ,
24+ ) -> Result < ( ) > {
25+ let build_info = component_build_configs ( manifest_file)
26+ . await
27+ . with_context ( || {
28+ format ! (
29+ "Cannot read manifest file from {}" ,
30+ quoted_path( manifest_file)
31+ )
32+ } ) ?;
2933 let app_dir = parent_dir ( manifest_file) ?;
3034
31- let build_result = build_components ( component_ids, components, app_dir) ;
35+ let build_result = build_components ( component_ids, build_info . components ( ) , & app_dir) ;
3236
33- if let Some ( e) = manifest_err {
37+ // Emit any required warnings now, so that they don't bury any errors.
38+ if let Some ( e) = build_info. load_error ( ) {
39+ // The manifest had errors. We managed to attempt a build anyway, but we want to
40+ // let the user know about them.
3441 terminal:: warn!( "The manifest has errors not related to the Wasm component build. Error details:\n {e:#}" ) ;
42+ // Checking deployment targets requires a healthy manifest (because trigger types etc.),
43+ // if any of these were specified, warn they are being skipped.
44+ let should_have_checked_targets =
45+ target_checks. check ( ) && build_info. has_deployment_targets ( ) ;
46+ if should_have_checked_targets {
47+ terminal:: warn!(
48+ "The manifest error(s) prevented Spin from checking the deployment targets."
49+ ) ;
50+ }
51+ }
52+
53+ // If the build failed, exit with an error at this point.
54+ build_result?;
55+
56+ let Some ( manifest) = build_info. manifest ( ) else {
57+ // We can't proceed to checking (because that needs a full healthy manifest), and we've
58+ // already emitted any necessary warning, so quit.
59+ return Ok ( ( ) ) ;
60+ } ;
61+
62+ if target_checks. check ( ) {
63+ let application = spin_environments:: ApplicationToValidate :: new (
64+ manifest. clone ( ) ,
65+ manifest_file. parent ( ) . unwrap ( ) ,
66+ )
67+ . await
68+ . context ( "unable to load application for checking against deployment targets" ) ?;
69+ let target_validation = spin_environments:: validate_application_against_environment_ids (
70+ & application,
71+ build_info. deployment_targets ( ) ,
72+ cache_root. clone ( ) ,
73+ & app_dir,
74+ )
75+ . await
76+ . context ( "unable to check if the application is compatible with deployment targets" ) ?;
77+
78+ if !target_validation. is_ok ( ) {
79+ for error in target_validation. errors ( ) {
80+ terminal:: error!( "{error}" ) ;
81+ }
82+ anyhow:: bail!( "All components built successfully, but one or more was incompatible with one or more of the deployment targets." ) ;
83+ }
3584 }
3685
37- build_result
86+ Ok ( ( ) )
87+ }
88+
89+ /// Run all component build commands, using the default options (build all
90+ /// components, perform target checking). We run a "default build" in several
91+ /// places and this centralises the logic of what such a "default build" means.
92+ pub async fn build_default ( manifest_file : & Path , cache_root : Option < PathBuf > ) -> Result < ( ) > {
93+ build ( manifest_file, & [ ] , TargetChecking :: Check , cache_root) . await
3894}
3995
4096fn build_components (
4197 component_ids : & [ String ] ,
4298 components : Vec < ComponentBuildInfo > ,
43- app_dir : PathBuf ,
99+ app_dir : & Path ,
44100) -> Result < ( ) , anyhow:: Error > {
45101 let components_to_build = if component_ids. is_empty ( ) {
46102 components
@@ -70,7 +126,7 @@ fn build_components(
70126
71127 components_to_build
72128 . into_iter ( )
73- . map ( |c| build_component ( c, & app_dir) )
129+ . map ( |c| build_component ( c, app_dir) )
74130 . collect :: < Result < Vec < _ > , _ > > ( ) ?;
75131
76132 terminal:: step!( "Finished" , "building all Spin components" ) ;
@@ -159,6 +215,21 @@ fn construct_workdir(app_dir: &Path, workdir: Option<impl AsRef<Path>>) -> Resul
159215 Ok ( cwd)
160216}
161217
218+ /// Specifies target environment checking behaviour
219+ pub enum TargetChecking {
220+ /// The build should check that all components are compatible with all target environments.
221+ Check ,
222+ /// The build should not check target environments.
223+ Skip ,
224+ }
225+
226+ impl TargetChecking {
227+ /// Should the build check target environments?
228+ fn check ( & self ) -> bool {
229+ matches ! ( self , Self :: Check )
230+ }
231+ }
232+
162233#[ cfg( test) ]
163234mod tests {
164235 use super :: * ;
@@ -171,6 +242,8 @@ mod tests {
171242 #[ tokio:: test]
172243 async fn can_load_even_if_trigger_invalid ( ) {
173244 let bad_trigger_file = test_data_root ( ) . join ( "bad_trigger.toml" ) ;
174- build ( & bad_trigger_file, & [ ] ) . await . unwrap ( ) ;
245+ build ( & bad_trigger_file, & [ ] , TargetChecking :: Skip , None )
246+ . await
247+ . unwrap ( ) ;
175248 }
176249}
0 commit comments