11mod launch_metadata;
22
3- use std:: path:: PathBuf ;
3+ use std:: future:: Future ;
4+ use std:: path:: { Path , PathBuf } ;
45
56use anyhow:: { Context , Result } ;
67use clap:: { Args , IntoApp , Parser } ;
@@ -13,7 +14,7 @@ use spin_runtime_config::ResolvedRuntimeConfig;
1314
1415use crate :: factors:: { TriggerFactors , TriggerFactorsRuntimeConfig } ;
1516use crate :: stdio:: { FollowComponents , StdioLoggingExecutorHooks } ;
16- use crate :: Trigger ;
17+ use crate :: { Trigger , TriggerApp } ;
1718pub use launch_metadata:: LaunchMetadata ;
1819
1920pub const APP_LOG_DIR : & str = "APP_LOG_DIR" ;
@@ -172,43 +173,154 @@ impl<T: Trigger> FactorsTriggerCommand<T> {
172173 anyhow:: bail!( "This application requires the following features that are not available in this version of the '{}' trigger: {unmet}" , T :: TYPE ) ;
173174 }
174175
175- let mut trigger = T :: new ( self . trigger_args , & app) ?;
176+ let trigger = T :: new ( self . trigger_args , & app) ?;
177+ let mut builder = TriggerAppBuilder :: new ( trigger, PathBuf :: from ( working_dir) ) ;
178+ let config = builder. engine_config ( ) ;
176179
177- let mut core_engine_builder = {
178- let mut config = spin_core:: Config :: default ( ) ;
180+ // Apply --cache / --disable-cache
181+ if !self . disable_cache {
182+ config. enable_cache ( & self . cache ) ?;
183+ }
179184
180- // Apply --cache / --disable-cache
181- if !self . disable_cache {
182- config. enable_cache ( & self . cache ) ?;
183- }
185+ if self . disable_pooling {
186+ config. disable_pooling ( ) ;
187+ }
184188
185- if self . disable_pooling {
186- config. disable_pooling ( ) ;
189+ let run_fut = builder
190+ . run (
191+ app,
192+ TriggerAppOptions {
193+ runtime_config_file : self . runtime_config_file . as_deref ( ) ,
194+ state_dir : self . state_dir . as_deref ( ) ,
195+ initial_key_values : self . key_values ,
196+ allow_transient_write : self . allow_transient_write ,
197+ follow_components,
198+ log_dir : self . log ,
199+ } ,
200+ )
201+ . await ?;
202+
203+ let ( abortable, abort_handle) = futures:: future:: abortable ( run_fut) ;
204+ ctrlc:: set_handler ( move || abort_handle. abort ( ) ) ?;
205+ match abortable. await {
206+ Ok ( Ok ( ( ) ) ) => {
207+ tracing:: info!( "Trigger executor shut down: exiting" ) ;
208+ Ok ( ( ) )
209+ }
210+ Ok ( Err ( err) ) => {
211+ tracing:: error!( "Trigger executor failed" ) ;
212+ Err ( err)
213+ }
214+ Err ( _aborted) => {
215+ tracing:: info!( "User requested shutdown: exiting" ) ;
216+ Ok ( ( ) )
187217 }
218+ }
219+ }
220+
221+ fn follow_components ( & self ) -> FollowComponents {
222+ if self . silence_component_logs {
223+ FollowComponents :: None
224+ } else if self . follow_components . is_empty ( ) {
225+ FollowComponents :: All
226+ } else {
227+ let followed = self . follow_components . clone ( ) . into_iter ( ) . collect ( ) ;
228+ FollowComponents :: Named ( followed)
229+ }
230+ }
231+ }
232+
233+ const SLOTH_WARNING_DELAY_MILLIS : u64 = 1250 ;
234+
235+ fn warn_if_wasm_build_slothful ( ) -> sloth:: SlothGuard {
236+ #[ cfg( debug_assertions) ]
237+ let message = "\
238+ This is a debug build - preparing Wasm modules might take a few seconds\n \
239+ If you're experiencing long startup times please switch to the release build";
240+
241+ #[ cfg( not( debug_assertions) ) ]
242+ let message = "Preparing Wasm modules is taking a few seconds..." ;
243+
244+ sloth:: warn_if_slothful ( SLOTH_WARNING_DELAY_MILLIS , format ! ( "{message}\n " ) )
245+ }
246+
247+ fn help_heading < T : Trigger > ( ) -> Option < & ' static str > {
248+ if T :: TYPE == help:: HelpArgsOnlyTrigger :: TYPE {
249+ Some ( "TRIGGER OPTIONS" )
250+ } else {
251+ let heading = format ! ( "{} TRIGGER OPTIONS" , T :: TYPE . to_uppercase( ) ) ;
252+ let as_str = Box :: new ( heading) . leak ( ) ;
253+ Some ( as_str)
254+ }
255+ }
256+
257+ /// A builder for a [`TriggerApp`].
258+ pub struct TriggerAppBuilder < T > {
259+ engine_config : spin_core:: Config ,
260+ working_dir : PathBuf ,
261+ pub trigger : T ,
262+ }
188263
189- trigger. update_core_config ( & mut config) ?;
264+ /// Options for building a [`TriggerApp`].
265+ #[ derive( Default ) ]
266+ pub struct TriggerAppOptions < ' a > {
267+ /// Path to the runtime config file.
268+ runtime_config_file : Option < & ' a Path > ,
269+ /// Path to the state directory.
270+ state_dir : Option < & ' a str > ,
271+ /// Initial key/value pairs to set in the app's default store.
272+ initial_key_values : Vec < ( String , String ) > ,
273+ /// Whether to allow transient writes to mounted files
274+ allow_transient_write : bool ,
275+ /// Which components should have their logs followed.
276+ follow_components : FollowComponents ,
277+ /// Log directory for component stdout/stderr.
278+ log_dir : Option < PathBuf > ,
279+ }
280+
281+ impl < T : Trigger > TriggerAppBuilder < T > {
282+ pub fn new ( trigger : T , working_dir : PathBuf ) -> Self {
283+ Self {
284+ engine_config : spin_core:: Config :: default ( ) ,
285+ working_dir,
286+ trigger,
287+ }
288+ }
289+
290+ pub fn engine_config ( & mut self ) -> & mut spin_core:: Config {
291+ & mut self . engine_config
292+ }
190293
191- spin_core:: Engine :: builder ( & config) ?
294+ /// Build a [`TriggerApp`] from the given [`App`] and options.
295+ pub async fn build (
296+ & mut self ,
297+ app : App ,
298+ options : TriggerAppOptions < ' _ > ,
299+ ) -> anyhow:: Result < TriggerApp < T > > {
300+ let mut core_engine_builder = {
301+ self . trigger . update_core_config ( & mut self . engine_config ) ?;
302+
303+ spin_core:: Engine :: builder ( & self . engine_config ) ?
192304 } ;
193- trigger. add_to_linker ( core_engine_builder. linker ( ) ) ?;
305+ self . trigger . add_to_linker ( core_engine_builder. linker ( ) ) ?;
194306
195- let runtime_config = match & self . runtime_config_file {
307+ let runtime_config = match options . runtime_config_file {
196308 Some ( runtime_config_path) => {
197309 ResolvedRuntimeConfig :: < TriggerFactorsRuntimeConfig > :: from_file (
198310 runtime_config_path,
199- self . state_dir . as_deref ( ) ,
311+ options . state_dir ,
200312 ) ?
201313 }
202- None => ResolvedRuntimeConfig :: default ( self . state_dir . as_deref ( ) ) ,
314+ None => ResolvedRuntimeConfig :: default ( options . state_dir ) ,
203315 } ;
204316
205317 runtime_config
206- . set_initial_key_values ( & self . key_values )
318+ . set_initial_key_values ( & options . initial_key_values )
207319 . await ?;
208320
209321 let factors = TriggerFactors :: new (
210- working_dir,
211- self . allow_transient_write ,
322+ self . working_dir . clone ( ) ,
323+ options . allow_transient_write ,
212324 runtime_config. key_value_resolver ,
213325 runtime_config. sqlite_resolver ,
214326 ) ;
@@ -249,8 +361,10 @@ impl<T: Trigger> FactorsTriggerCommand<T> {
249361
250362 let mut executor = FactorsExecutor :: new ( core_engine_builder, factors) ?;
251363
252- let log_dir = self . log . clone ( ) ;
253- executor. add_hooks ( StdioLoggingExecutorHooks :: new ( follow_components, log_dir) ) ;
364+ executor. add_hooks ( StdioLoggingExecutorHooks :: new (
365+ options. follow_components ,
366+ options. log_dir ,
367+ ) ) ;
254368 // TODO:
255369 // builder.hooks(SummariseRuntimeConfigHook::new(&self.runtime_config_file));
256370 // builder.hooks(KeyValuePersistenceMessageHook);
@@ -261,59 +375,17 @@ impl<T: Trigger> FactorsTriggerCommand<T> {
261375 executor. load_app ( app, runtime_config. runtime_config , SimpleComponentLoader ) ?
262376 } ;
263377
264- let run_fut = trigger. run ( configured_app) ;
265-
266- let ( abortable, abort_handle) = futures:: future:: abortable ( run_fut) ;
267- ctrlc:: set_handler ( move || abort_handle. abort ( ) ) ?;
268- match abortable. await {
269- Ok ( Ok ( ( ) ) ) => {
270- tracing:: info!( "Trigger executor shut down: exiting" ) ;
271- Ok ( ( ) )
272- }
273- Ok ( Err ( err) ) => {
274- tracing:: error!( "Trigger executor failed" ) ;
275- Err ( err)
276- }
277- Err ( _aborted) => {
278- tracing:: info!( "User requested shutdown: exiting" ) ;
279- Ok ( ( ) )
280- }
281- }
282- }
283-
284- fn follow_components ( & self ) -> FollowComponents {
285- if self . silence_component_logs {
286- FollowComponents :: None
287- } else if self . follow_components . is_empty ( ) {
288- FollowComponents :: All
289- } else {
290- let followed = self . follow_components . clone ( ) . into_iter ( ) . collect ( ) ;
291- FollowComponents :: Named ( followed)
292- }
378+ Ok ( configured_app)
293379 }
294- }
295-
296- const SLOTH_WARNING_DELAY_MILLIS : u64 = 1250 ;
297-
298- fn warn_if_wasm_build_slothful ( ) -> sloth:: SlothGuard {
299- #[ cfg( debug_assertions) ]
300- let message = "\
301- This is a debug build - preparing Wasm modules might take a few seconds\n \
302- If you're experiencing long startup times please switch to the release build";
303-
304- #[ cfg( not( debug_assertions) ) ]
305- let message = "Preparing Wasm modules is taking a few seconds..." ;
306-
307- sloth:: warn_if_slothful ( SLOTH_WARNING_DELAY_MILLIS , format ! ( "{message}\n " ) )
308- }
309380
310- fn help_heading < T : Trigger > ( ) -> Option < & ' static str > {
311- if T :: TYPE == help:: HelpArgsOnlyTrigger :: TYPE {
312- Some ( "TRIGGER OPTIONS" )
313- } else {
314- let heading = format ! ( "{} TRIGGER OPTIONS" , T :: TYPE . to_uppercase( ) ) ;
315- let as_str = Box :: new ( heading) . leak ( ) ;
316- Some ( as_str)
381+ /// Run the [`TriggerApp`] with the given [`App`] and options.
382+ pub async fn run (
383+ mut self ,
384+ app : App ,
385+ options : TriggerAppOptions < ' _ > ,
386+ ) -> anyhow:: Result < impl Future < Output = anyhow:: Result < ( ) > > > {
387+ let configured_app = self . build ( app, options) . await ?;
388+ Ok ( self . trigger . run ( configured_app) )
317389 }
318390}
319391
0 commit comments