@@ -15,9 +15,13 @@ 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 > ,
@@ -32,7 +36,7 @@ pub async fn build(
3236 } ) ?;
3337 let app_dir = parent_dir ( manifest_file) ?;
3438
35- let build_result = build_components ( component_ids, build_info. components ( ) , & app_dir) ;
39+ let build_result = build_components ( component_ids, build_info. components ( ) , & app_dir, profile ) ;
3640
3741 // Emit any required warnings now, so that they don't bury any errors.
3842 if let Some ( e) = build_info. load_error ( ) {
@@ -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,14 +95,26 @@ 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 (
97114 component_ids : & [ String ] ,
98115 components : Vec < ComponentBuildInfo > ,
99116 app_dir : & Path ,
117+ profile : Option < & str > ,
100118) -> Result < ( ) , anyhow:: Error > {
101119 let components_to_build = if component_ids. is_empty ( ) {
102120 components
@@ -126,18 +144,24 @@ fn build_components(
126144
127145 components_to_build
128146 . into_iter ( )
129- . map ( |c| build_component ( c, app_dir) )
147+ . map ( |c| build_component ( c, app_dir, profile ) )
130148 . collect :: < Result < Vec < _ > , _ > > ( ) ?;
131149
132150 terminal:: step!( "Finished" , "building all Spin components" ) ;
133151 Ok ( ( ) )
134152}
135153
136154/// Run the build command of the component.
137- fn build_component ( build_info : ComponentBuildInfo , app_dir : & Path ) -> Result < ( ) > {
155+ fn build_component (
156+ build_info : ComponentBuildInfo ,
157+ app_dir : & Path ,
158+ profile : Option < & str > ,
159+ ) -> Result < ( ) > {
138160 match build_info. build {
139161 Some ( b) => {
140- let command_count = b. commands ( ) . len ( ) ;
162+ let commands = b. commands ( profile) ;
163+
164+ let command_count = commands. len ( ) ;
141165
142166 if command_count > 1 {
143167 terminal:: step!(
@@ -148,7 +172,7 @@ fn build_component(build_info: ComponentBuildInfo, app_dir: &Path) -> Result<()>
148172 ) ;
149173 }
150174
151- for ( index, command) in b . commands ( ) . enumerate ( ) {
175+ for ( index, command) in commands. into_iter ( ) . enumerate ( ) {
152176 if command_count > 1 {
153177 terminal:: step!(
154178 "Running build step" ,
@@ -215,6 +239,56 @@ fn construct_workdir(app_dir: &Path, workdir: Option<impl AsRef<Path>>) -> Resul
215239 Ok ( cwd)
216240}
217241
242+ /// Saves the build profile to the "last build profile" file.
243+ /// Errors are ignored as they should not block building.
244+ pub fn save_last_build_profile ( app_dir : & Path , profile : Option < & str > ) {
245+ let app_stash_dir = app_dir. join ( ".spin" ) ;
246+ _ = std:: fs:: create_dir_all ( & app_stash_dir) ;
247+ let last_build_profile_file = app_stash_dir. join ( LAST_BUILD_PROFILE_FILE ) ;
248+ _ = std:: fs:: write (
249+ & last_build_profile_file,
250+ profile. unwrap_or ( LAST_BUILD_ANON_VALUE ) ,
251+ ) ;
252+ }
253+
254+ /// Reads the last build profile from the "last build profile" file.
255+ /// Errors are ignored.
256+ pub fn read_last_build_profile ( app_dir : & Path ) -> Option < String > {
257+ let app_stash_dir = app_dir. join ( ".spin" ) ;
258+ let last_build_profile_file = app_stash_dir. join ( LAST_BUILD_PROFILE_FILE ) ;
259+ let last_build_str = std:: fs:: read_to_string ( & last_build_profile_file) . ok ( ) ?;
260+
261+ if last_build_str == LAST_BUILD_ANON_VALUE {
262+ None
263+ } else {
264+ Some ( last_build_str)
265+ }
266+ }
267+
268+ /// Prints a warning to stderr if the given profile is not the same
269+ /// as the most recent build in the given application directory.
270+ pub fn warn_if_not_latest_build ( manifest_path : & Path , profile : Option < & str > ) {
271+ let Some ( app_dir) = manifest_path. parent ( ) else {
272+ return ;
273+ } ;
274+
275+ let latest_build = read_last_build_profile ( app_dir) ;
276+
277+ let is_match = match ( profile, latest_build) {
278+ ( None , None ) => true ,
279+ ( Some ( _) , None ) | ( None , Some ( _) ) => false ,
280+ ( Some ( p) , Some ( latest) ) => p == latest,
281+ } ;
282+
283+ if !is_match {
284+ let profile_opt = match profile {
285+ Some ( p) => format ! ( " --profile {p}" ) ,
286+ None => "" . to_string ( ) ,
287+ } ;
288+ 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}`." ) ;
289+ }
290+ }
291+
218292/// Specifies target environment checking behaviour
219293pub enum TargetChecking {
220294 /// The build should check that all components are compatible with all target environments.
@@ -242,23 +316,23 @@ mod tests {
242316 #[ tokio:: test]
243317 async fn can_load_even_if_trigger_invalid ( ) {
244318 let bad_trigger_file = test_data_root ( ) . join ( "bad_trigger.toml" ) ;
245- build ( & bad_trigger_file, & [ ] , TargetChecking :: Skip , None )
319+ build ( & bad_trigger_file, None , & [ ] , TargetChecking :: Skip , None )
246320 . await
247321 . unwrap ( ) ;
248322 }
249323
250324 #[ tokio:: test]
251325 async fn succeeds_if_target_env_matches ( ) {
252326 let manifest_path = test_data_root ( ) . join ( "good_target_env.toml" ) ;
253- build ( & manifest_path, & [ ] , TargetChecking :: Check , None )
327+ build ( & manifest_path, None , & [ ] , TargetChecking :: Check , None )
254328 . await
255329 . unwrap ( ) ;
256330 }
257331
258332 #[ tokio:: test]
259333 async fn fails_if_target_env_does_not_match ( ) {
260334 let manifest_path = test_data_root ( ) . join ( "bad_target_env.toml" ) ;
261- let err = build ( & manifest_path, & [ ] , TargetChecking :: Check , None )
335+ let err = build ( & manifest_path, None , & [ ] , TargetChecking :: Check , None )
262336 . await
263337 . expect_err ( "should have failed" )
264338 . to_string ( ) ;
0 commit comments