@@ -15,9 +15,13 @@ use subprocess::{Exec, Redirection};
15
15
16
16
use crate :: manifest:: component_build_configs;
17
17
18
+ const LAST_BUILD_PROFILE_FILE : & str = "last-build.txt" ;
19
+ const LAST_BUILD_ANON_VALUE : & str = "<anonymous>" ;
20
+
18
21
/// If present, run the build command of each component.
19
22
pub async fn build (
20
23
manifest_file : & Path ,
24
+ profile : Option < & str > ,
21
25
component_ids : & [ String ] ,
22
26
target_checks : TargetChecking ,
23
27
cache_root : Option < PathBuf > ,
@@ -32,7 +36,7 @@ pub async fn build(
32
36
} ) ?;
33
37
let app_dir = parent_dir ( manifest_file) ?;
34
38
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 ) ;
36
40
37
41
// Emit any required warnings now, so that they don't bury any errors.
38
42
if let Some ( e) = build_info. load_error ( ) {
@@ -53,6 +57,8 @@ pub async fn build(
53
57
// If the build failed, exit with an error at this point.
54
58
build_result?;
55
59
60
+ save_last_build_profile ( & app_dir, profile) ;
61
+
56
62
let Some ( manifest) = build_info. manifest ( ) else {
57
63
// We can't proceed to checking (because that needs a full healthy manifest), and we've
58
64
// already emitted any necessary warning, so quit.
@@ -89,14 +95,26 @@ pub async fn build(
89
95
/// Run all component build commands, using the default options (build all
90
96
/// components, perform target checking). We run a "default build" in several
91
97
/// 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
94
111
}
95
112
96
113
fn build_components (
97
114
component_ids : & [ String ] ,
98
115
components : Vec < ComponentBuildInfo > ,
99
116
app_dir : & Path ,
117
+ profile : Option < & str > ,
100
118
) -> Result < ( ) , anyhow:: Error > {
101
119
let components_to_build = if component_ids. is_empty ( ) {
102
120
components
@@ -126,18 +144,24 @@ fn build_components(
126
144
127
145
components_to_build
128
146
. into_iter ( )
129
- . map ( |c| build_component ( c, app_dir) )
147
+ . map ( |c| build_component ( c, app_dir, profile ) )
130
148
. collect :: < Result < Vec < _ > , _ > > ( ) ?;
131
149
132
150
terminal:: step!( "Finished" , "building all Spin components" ) ;
133
151
Ok ( ( ) )
134
152
}
135
153
136
154
/// 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 < ( ) > {
138
160
match build_info. build {
139
161
Some ( b) => {
140
- let command_count = b. commands ( ) . len ( ) ;
162
+ let commands = b. commands ( profile) ;
163
+
164
+ let command_count = commands. len ( ) ;
141
165
142
166
if command_count > 1 {
143
167
terminal:: step!(
@@ -148,7 +172,7 @@ fn build_component(build_info: ComponentBuildInfo, app_dir: &Path) -> Result<()>
148
172
) ;
149
173
}
150
174
151
- for ( index, command) in b . commands ( ) . enumerate ( ) {
175
+ for ( index, command) in commands. into_iter ( ) . enumerate ( ) {
152
176
if command_count > 1 {
153
177
terminal:: step!(
154
178
"Running build step" ,
@@ -215,6 +239,56 @@ fn construct_workdir(app_dir: &Path, workdir: Option<impl AsRef<Path>>) -> Resul
215
239
Ok ( cwd)
216
240
}
217
241
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
+
218
292
/// Specifies target environment checking behaviour
219
293
pub enum TargetChecking {
220
294
/// The build should check that all components are compatible with all target environments.
@@ -242,23 +316,23 @@ mod tests {
242
316
#[ tokio:: test]
243
317
async fn can_load_even_if_trigger_invalid ( ) {
244
318
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 )
246
320
. await
247
321
. unwrap ( ) ;
248
322
}
249
323
250
324
#[ tokio:: test]
251
325
async fn succeeds_if_target_env_matches ( ) {
252
326
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 )
254
328
. await
255
329
. unwrap ( ) ;
256
330
}
257
331
258
332
#[ tokio:: test]
259
333
async fn fails_if_target_env_does_not_match ( ) {
260
334
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 )
262
336
. await
263
337
. expect_err ( "should have failed" )
264
338
. to_string ( ) ;
0 commit comments