@@ -15,14 +15,18 @@ use subprocess::{Exec, Redirection};
1515
1616use  crate :: manifest:: component_build_configs; 
1717
18+ const  LAST_BUILD_PROFILE_FILE :  & str  = "last-build.txt" ; 
19+ const  LAST_BUILD_ANON_VALUE :  & str  = "<anonymous>" ; 
20+ 
1821/// If present, run the build command of each component. 
1922pub  async  fn  build ( 
2023    manifest_file :  & Path , 
24+     profile :  Option < & str > , 
2125    component_ids :  & [ String ] , 
2226    target_checks :  TargetChecking , 
2327    cache_root :  Option < PathBuf > , 
2428)  -> Result < ( ) >  { 
25-     let  build_info = component_build_configs ( manifest_file) 
29+     let  build_info = component_build_configs ( manifest_file,  profile ) 
2630        . await 
2731        . with_context ( || { 
2832            format ! ( 
@@ -53,6 +57,10 @@ pub async fn build(
5357    // If the build failed, exit with an error at this point. 
5458    build_result?; 
5559
60+     if  let  Err ( e)  = save_last_build_profile ( & app_dir,  profile)  { 
61+         tracing:: warn!( "Failed to save build profile: {e:?}" ) ; 
62+     } 
63+ 
5664    let  Some ( manifest)  = build_info. manifest ( )  else  { 
5765        // We can't proceed to checking (because that needs a full healthy manifest), and we've 
5866        // already emitted any necessary warning, so quit. 
@@ -89,8 +97,19 @@ pub async fn build(
8997/// Run all component build commands, using the default options (build all 
9098/// components, perform target checking). We run a "default build" in several 
9199/// 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 
100+ pub  async  fn  build_default ( 
101+     manifest_file :  & Path , 
102+     profile :  Option < & str > , 
103+     cache_root :  Option < PathBuf > , 
104+ )  -> Result < ( ) >  { 
105+     build ( 
106+         manifest_file, 
107+         profile, 
108+         & [ ] , 
109+         TargetChecking :: Check , 
110+         cache_root, 
111+     ) 
112+     . await 
94113} 
95114
96115fn  build_components ( 
@@ -215,6 +234,69 @@ fn construct_workdir(app_dir: &Path, workdir: Option<impl AsRef<Path>>) -> Resul
215234    Ok ( cwd) 
216235} 
217236
237+ /// Saves the build profile to the "last build profile" file. 
238+ pub  fn  save_last_build_profile ( app_dir :  & Path ,  profile :  Option < & str > )  -> anyhow:: Result < ( ) >  { 
239+     let  app_stash_dir = app_dir. join ( ".spin" ) ; 
240+     let  last_build_profile_file = app_stash_dir. join ( LAST_BUILD_PROFILE_FILE ) ; 
241+ 
242+     // This way, if the user never uses build profiles, they won't see a 
243+     // weird savefile that they have no idea what it is. 
244+     if  profile. is_none ( )  && !last_build_profile_file. exists ( )  { 
245+         return  Ok ( ( ) ) ; 
246+     } 
247+ 
248+     std:: fs:: create_dir_all ( & app_stash_dir) ?; 
249+     std:: fs:: write ( 
250+         & last_build_profile_file, 
251+         profile. unwrap_or ( LAST_BUILD_ANON_VALUE ) , 
252+     ) ?; 
253+ 
254+     Ok ( ( ) ) 
255+ } 
256+ 
257+ /// Reads the last build profile from the "last build profile" file. 
258+ pub  fn  read_last_build_profile ( app_dir :  & Path )  -> anyhow:: Result < Option < String > >  { 
259+     let  app_stash_dir = app_dir. join ( ".spin" ) ; 
260+     let  last_build_profile_file = app_stash_dir. join ( LAST_BUILD_PROFILE_FILE ) ; 
261+     if  !last_build_profile_file. exists ( )  { 
262+         return  Ok ( None ) ; 
263+     } 
264+ 
265+     let  last_build_str = std:: fs:: read_to_string ( & last_build_profile_file) ?; 
266+ 
267+     if  last_build_str == LAST_BUILD_ANON_VALUE  { 
268+         Ok ( None ) 
269+     }  else  { 
270+         Ok ( Some ( last_build_str) ) 
271+     } 
272+ } 
273+ 
274+ /// Prints a warning to stderr if the given profile is not the same 
275+ /// as the most recent build in the given application directory. 
276+ pub  fn  warn_if_not_latest_build ( manifest_path :  & Path ,  profile :  Option < & str > )  { 
277+     let  Some ( app_dir)  = manifest_path. parent ( )  else  { 
278+         return ; 
279+     } ; 
280+ 
281+     let  latest_build = match  read_last_build_profile ( app_dir)  { 
282+         Ok ( profile)  => profile, 
283+         Err ( e)  => { 
284+             tracing:: warn!( 
285+                 "Failed to read last build profile: using anonymous profile. Error was {e:?}" 
286+             ) ; 
287+             None 
288+         } 
289+     } ; 
290+ 
291+     if  profile != latest_build. as_deref ( )  { 
292+         let  profile_opt = match  profile { 
293+             Some ( p)  => format ! ( " --profile {p}" ) , 
294+             None  => "" . to_string ( ) , 
295+         } ; 
296+         terminal:: warn!( "You built a different profile more recently than the one you are running. If the app appears to be behaving like an older version then run `spin up --build{profile_opt}`." ) ; 
297+     } 
298+ } 
299+ 
218300/// Specifies target environment checking behaviour 
219301pub  enum  TargetChecking  { 
220302    /// The build should check that all components are compatible with all target environments. 
@@ -242,23 +324,23 @@ mod tests {
242324    #[ tokio:: test]  
243325    async  fn  can_load_even_if_trigger_invalid ( )  { 
244326        let  bad_trigger_file = test_data_root ( ) . join ( "bad_trigger.toml" ) ; 
245-         build ( & bad_trigger_file,  & [ ] ,  TargetChecking :: Skip ,  None ) 
327+         build ( & bad_trigger_file,  None ,   & [ ] ,  TargetChecking :: Skip ,  None ) 
246328            . await 
247329            . unwrap ( ) ; 
248330    } 
249331
250332    #[ tokio:: test]  
251333    async  fn  succeeds_if_target_env_matches ( )  { 
252334        let  manifest_path = test_data_root ( ) . join ( "good_target_env.toml" ) ; 
253-         build ( & manifest_path,  & [ ] ,  TargetChecking :: Check ,  None ) 
335+         build ( & manifest_path,  None ,   & [ ] ,  TargetChecking :: Check ,  None ) 
254336            . await 
255337            . unwrap ( ) ; 
256338    } 
257339
258340    #[ tokio:: test]  
259341    async  fn  fails_if_target_env_does_not_match ( )  { 
260342        let  manifest_path = test_data_root ( ) . join ( "bad_target_env.toml" ) ; 
261-         let  err = build ( & manifest_path,  & [ ] ,  TargetChecking :: Check ,  None ) 
343+         let  err = build ( & manifest_path,  None ,   & [ ] ,  TargetChecking :: Check ,  None ) 
262344            . await 
263345            . expect_err ( "should have failed" ) 
264346            . to_string ( ) ; 
@@ -273,7 +355,8 @@ mod tests {
273355    #[ tokio:: test]  
274356    async  fn  has_meaningful_error_if_target_env_does_not_match ( )  { 
275357        let  manifest_file = test_data_root ( ) . join ( "bad_target_env.toml" ) ; 
276-         let  manifest = spin_manifest:: manifest_from_file ( & manifest_file) . unwrap ( ) ; 
358+         let  mut  manifest = spin_manifest:: manifest_from_file ( & manifest_file) . unwrap ( ) ; 
359+         spin_manifest:: normalize:: normalize_manifest ( & mut  manifest,  None ) ; 
277360        let  application = spin_environments:: ApplicationToValidate :: new ( 
278361            manifest. clone ( ) , 
279362            manifest_file. parent ( ) . unwrap ( ) , 
0 commit comments