@@ -16,14 +16,18 @@ use subprocess::{Exec, Redirection};
1616
1717use crate :: manifest:: component_build_configs;
1818
19+ const LAST_BUILD_PROFILE_FILE : & str = "last-build.txt" ;
20+ const LAST_BUILD_ANON_VALUE : & str = "<anonymous>" ;
21+
1922/// If present, run the build command of each component.
2023pub async fn build (
2124 manifest_file : & Path ,
25+ profile : Option < & str > ,
2226 component_ids : & [ String ] ,
2327 target_checks : TargetChecking ,
2428 cache_root : Option < PathBuf > ,
2529) -> Result < ( ) > {
26- let build_info = component_build_configs ( manifest_file)
30+ let build_info = component_build_configs ( manifest_file, profile )
2731 . await
2832 . with_context ( || {
2933 format ! (
@@ -54,6 +58,10 @@ pub async fn build(
5458 // If the build failed, exit with an error at this point.
5559 build_result?;
5660
61+ if let Err ( e) = save_last_build_profile ( & app_dir, profile) {
62+ tracing:: warn!( "Failed to save build profile: {e:?}" ) ;
63+ }
64+
5765 let Some ( manifest) = build_info. manifest ( ) else {
5866 // We can't proceed to checking (because that needs a full healthy manifest), and we've
5967 // already emitted any necessary warning, so quit.
@@ -90,8 +98,19 @@ pub async fn build(
9098/// Run all component build commands, using the default options (build all
9199/// components, perform target checking). We run a "default build" in several
92100/// places and this centralises the logic of what such a "default build" means.
93- pub async fn build_default ( manifest_file : & Path , cache_root : Option < PathBuf > ) -> Result < ( ) > {
94- build ( manifest_file, & [ ] , TargetChecking :: Check , cache_root) . await
101+ pub async fn build_default (
102+ manifest_file : & Path ,
103+ profile : Option < & str > ,
104+ cache_root : Option < PathBuf > ,
105+ ) -> Result < ( ) > {
106+ build (
107+ manifest_file,
108+ profile,
109+ & [ ] ,
110+ TargetChecking :: Check ,
111+ cache_root,
112+ )
113+ . await
95114}
96115
97116fn build_components (
@@ -316,6 +335,69 @@ fn sort(components: Vec<ComponentBuildInfo>) -> (Vec<ComponentBuildInfo>, bool)
316335 }
317336}
318337
338+ /// Saves the build profile to the "last build profile" file.
339+ pub fn save_last_build_profile ( app_dir : & Path , profile : Option < & str > ) -> anyhow:: Result < ( ) > {
340+ let app_stash_dir = app_dir. join ( ".spin" ) ;
341+ let last_build_profile_file = app_stash_dir. join ( LAST_BUILD_PROFILE_FILE ) ;
342+
343+ // This way, if the user never uses build profiles, they won't see a
344+ // weird savefile that they have no idea what it is.
345+ if profile. is_none ( ) && !last_build_profile_file. exists ( ) {
346+ return Ok ( ( ) ) ;
347+ }
348+
349+ std:: fs:: create_dir_all ( & app_stash_dir) ?;
350+ std:: fs:: write (
351+ & last_build_profile_file,
352+ profile. unwrap_or ( LAST_BUILD_ANON_VALUE ) ,
353+ ) ?;
354+
355+ Ok ( ( ) )
356+ }
357+
358+ /// Reads the last build profile from the "last build profile" file.
359+ pub fn read_last_build_profile ( app_dir : & Path ) -> anyhow:: Result < Option < String > > {
360+ let app_stash_dir = app_dir. join ( ".spin" ) ;
361+ let last_build_profile_file = app_stash_dir. join ( LAST_BUILD_PROFILE_FILE ) ;
362+ if !last_build_profile_file. exists ( ) {
363+ return Ok ( None ) ;
364+ }
365+
366+ let last_build_str = std:: fs:: read_to_string ( & last_build_profile_file) ?;
367+
368+ if last_build_str == LAST_BUILD_ANON_VALUE {
369+ Ok ( None )
370+ } else {
371+ Ok ( Some ( last_build_str) )
372+ }
373+ }
374+
375+ /// Prints a warning to stderr if the given profile is not the same
376+ /// as the most recent build in the given application directory.
377+ pub fn warn_if_not_latest_build ( manifest_path : & Path , profile : Option < & str > ) {
378+ let Some ( app_dir) = manifest_path. parent ( ) else {
379+ return ;
380+ } ;
381+
382+ let latest_build = match read_last_build_profile ( app_dir) {
383+ Ok ( profile) => profile,
384+ Err ( e) => {
385+ tracing:: warn!(
386+ "Failed to read last build profile: using anonymous profile. Error was {e:?}"
387+ ) ;
388+ None
389+ }
390+ } ;
391+
392+ if profile != latest_build. as_deref ( ) {
393+ let profile_opt = match profile {
394+ Some ( p) => format ! ( " --profile {p}" ) ,
395+ None => "" . to_string ( ) ,
396+ } ;
397+ 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}`." ) ;
398+ }
399+ }
400+
319401/// Specifies target environment checking behaviour
320402pub enum TargetChecking {
321403 /// The build should check that all components are compatible with all target environments.
@@ -343,23 +425,23 @@ mod tests {
343425 #[ tokio:: test]
344426 async fn can_load_even_if_trigger_invalid ( ) {
345427 let bad_trigger_file = test_data_root ( ) . join ( "bad_trigger.toml" ) ;
346- build ( & bad_trigger_file, & [ ] , TargetChecking :: Skip , None )
428+ build ( & bad_trigger_file, None , & [ ] , TargetChecking :: Skip , None )
347429 . await
348430 . unwrap ( ) ;
349431 }
350432
351433 #[ tokio:: test]
352434 async fn succeeds_if_target_env_matches ( ) {
353435 let manifest_path = test_data_root ( ) . join ( "good_target_env.toml" ) ;
354- build ( & manifest_path, & [ ] , TargetChecking :: Check , None )
436+ build ( & manifest_path, None , & [ ] , TargetChecking :: Check , None )
355437 . await
356438 . unwrap ( ) ;
357439 }
358440
359441 #[ tokio:: test]
360442 async fn fails_if_target_env_does_not_match ( ) {
361443 let manifest_path = test_data_root ( ) . join ( "bad_target_env.toml" ) ;
362- let err = build ( & manifest_path, & [ ] , TargetChecking :: Check , None )
444+ let err = build ( & manifest_path, None , & [ ] , TargetChecking :: Check , None )
363445 . await
364446 . expect_err ( "should have failed" )
365447 . to_string ( ) ;
@@ -374,7 +456,8 @@ mod tests {
374456 #[ tokio:: test]
375457 async fn has_meaningful_error_if_target_env_does_not_match ( ) {
376458 let manifest_file = test_data_root ( ) . join ( "bad_target_env.toml" ) ;
377- let manifest = spin_manifest:: manifest_from_file ( & manifest_file) . unwrap ( ) ;
459+ let mut manifest = spin_manifest:: manifest_from_file ( & manifest_file) . unwrap ( ) ;
460+ spin_manifest:: normalize:: normalize_manifest ( & mut manifest, None ) . unwrap ( ) ;
378461 let application = spin_environments:: ApplicationToValidate :: new (
379462 manifest. clone ( ) ,
380463 manifest_file. parent ( ) . unwrap ( ) ,
0 commit comments