@@ -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,8 @@ pub async fn build(
5357 // If the build failed, exit with an error at this point.
5458 build_result?;
5559
60+ save_last_build_profile ( & app_dir, profile) ;
61+
5662 let Some ( manifest) = build_info. manifest ( ) else {
5763 // We can't proceed to checking (because that needs a full healthy manifest), and we've
5864 // already emitted any necessary warning, so quit.
@@ -89,8 +95,19 @@ pub async fn build(
8995/// Run all component build commands, using the default options (build all
9096/// components, perform target checking). We run a "default build" in several
9197/// 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
98+ pub async fn build_default (
99+ manifest_file : & Path ,
100+ profile : Option < & str > ,
101+ cache_root : Option < PathBuf > ,
102+ ) -> Result < ( ) > {
103+ build (
104+ manifest_file,
105+ profile,
106+ & [ ] ,
107+ TargetChecking :: Check ,
108+ cache_root,
109+ )
110+ . await
94111}
95112
96113fn build_components (
@@ -215,6 +232,50 @@ fn construct_workdir(app_dir: &Path, workdir: Option<impl AsRef<Path>>) -> Resul
215232 Ok ( cwd)
216233}
217234
235+ /// Saves the build profile to the "last build profile" file.
236+ /// Errors are ignored as they should not block building.
237+ pub fn save_last_build_profile ( app_dir : & Path , profile : Option < & str > ) {
238+ let app_stash_dir = app_dir. join ( ".spin" ) ;
239+ _ = std:: fs:: create_dir_all ( & app_stash_dir) ;
240+ let last_build_profile_file = app_stash_dir. join ( LAST_BUILD_PROFILE_FILE ) ;
241+ _ = std:: fs:: write (
242+ & last_build_profile_file,
243+ profile. unwrap_or ( LAST_BUILD_ANON_VALUE ) ,
244+ ) ;
245+ }
246+
247+ /// Reads the last build profile from the "last build profile" file.
248+ /// Errors are ignored.
249+ pub fn read_last_build_profile ( app_dir : & Path ) -> Option < String > {
250+ let app_stash_dir = app_dir. join ( ".spin" ) ;
251+ let last_build_profile_file = app_stash_dir. join ( LAST_BUILD_PROFILE_FILE ) ;
252+ let last_build_str = std:: fs:: read_to_string ( & last_build_profile_file) . ok ( ) ?;
253+
254+ if last_build_str == LAST_BUILD_ANON_VALUE {
255+ None
256+ } else {
257+ Some ( last_build_str)
258+ }
259+ }
260+
261+ /// Prints a warning to stderr if the given profile is not the same
262+ /// as the most recent build in the given application directory.
263+ pub fn warn_if_not_latest_build ( manifest_path : & Path , profile : Option < & str > ) {
264+ let Some ( app_dir) = manifest_path. parent ( ) else {
265+ return ;
266+ } ;
267+
268+ let latest_build = read_last_build_profile ( app_dir) ;
269+
270+ if profile != latest_build. as_deref ( ) {
271+ let profile_opt = match profile {
272+ Some ( p) => format ! ( " --profile {p}" ) ,
273+ None => "" . to_string ( ) ,
274+ } ;
275+ 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}`." ) ;
276+ }
277+ }
278+
218279/// Specifies target environment checking behaviour
219280pub enum TargetChecking {
220281 /// The build should check that all components are compatible with all target environments.
@@ -242,23 +303,23 @@ mod tests {
242303 #[ tokio:: test]
243304 async fn can_load_even_if_trigger_invalid ( ) {
244305 let bad_trigger_file = test_data_root ( ) . join ( "bad_trigger.toml" ) ;
245- build ( & bad_trigger_file, & [ ] , TargetChecking :: Skip , None )
306+ build ( & bad_trigger_file, None , & [ ] , TargetChecking :: Skip , None )
246307 . await
247308 . unwrap ( ) ;
248309 }
249310
250311 #[ tokio:: test]
251312 async fn succeeds_if_target_env_matches ( ) {
252313 let manifest_path = test_data_root ( ) . join ( "good_target_env.toml" ) ;
253- build ( & manifest_path, & [ ] , TargetChecking :: Check , None )
314+ build ( & manifest_path, None , & [ ] , TargetChecking :: Check , None )
254315 . await
255316 . unwrap ( ) ;
256317 }
257318
258319 #[ tokio:: test]
259320 async fn fails_if_target_env_does_not_match ( ) {
260321 let manifest_path = test_data_root ( ) . join ( "bad_target_env.toml" ) ;
261- let err = build ( & manifest_path, & [ ] , TargetChecking :: Check , None )
322+ let err = build ( & manifest_path, None , & [ ] , TargetChecking :: Check , None )
262323 . await
263324 . expect_err ( "should have failed" )
264325 . to_string ( ) ;
@@ -273,7 +334,8 @@ mod tests {
273334 #[ tokio:: test]
274335 async fn has_meaningful_error_if_target_env_does_not_match ( ) {
275336 let manifest_file = test_data_root ( ) . join ( "bad_target_env.toml" ) ;
276- let manifest = spin_manifest:: manifest_from_file ( & manifest_file) . unwrap ( ) ;
337+ let mut manifest = spin_manifest:: manifest_from_file ( & manifest_file) . unwrap ( ) ;
338+ spin_manifest:: normalize:: normalize_manifest ( & mut manifest, None ) ;
277339 let application = spin_environments:: ApplicationToValidate :: new (
278340 manifest. clone ( ) ,
279341 manifest_file. parent ( ) . unwrap ( ) ,
0 commit comments