@@ -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