1
1
use std:: collections:: HashMap ;
2
2
use std:: path:: Path ;
3
+ use std:: process:: Command ;
3
4
4
5
use anyhow:: Context ;
5
6
use clap:: Parser ;
6
7
use serde_yaml:: Value ;
7
8
8
9
const CI_DIRECTORY : & str = concat ! ( env!( "CARGO_MANIFEST_DIR" ) , "/.." ) ;
10
+ const DOCKER_DIRECTORY : & str = concat ! ( env!( "CARGO_MANIFEST_DIR" ) , "/../docker" ) ;
9
11
const JOBS_YML_PATH : & str = concat ! ( env!( "CARGO_MANIFEST_DIR" ) , "/../github-actions/jobs.yml" ) ;
10
12
11
13
/// Representation of a job loaded from the jobs.yml file.
@@ -24,6 +26,21 @@ struct Job {
24
26
extra_keys : HashMap < String , Value > ,
25
27
}
26
28
29
+ impl Job {
30
+ fn is_linux ( & self ) -> bool {
31
+ self . os . contains ( "ubuntu" )
32
+ }
33
+
34
+ /// By default, the Docker image of a job is based on its name.
35
+ /// However, it can be overridden by its IMAGE environment variable.
36
+ fn image ( & self ) -> String {
37
+ self . env
38
+ . get ( "IMAGE" )
39
+ . map ( |v| v. as_str ( ) . expect ( "IMAGE value should be a string" ) . to_string ( ) )
40
+ . unwrap_or_else ( || self . name . clone ( ) )
41
+ }
42
+ }
43
+
27
44
#[ derive( serde:: Deserialize , Debug ) ]
28
45
struct JobEnvironments {
29
46
#[ serde( rename = "pr" ) ]
@@ -267,11 +284,75 @@ fn calculate_job_matrix(
267
284
Ok ( ( ) )
268
285
}
269
286
287
+ fn find_linux_job < ' a > ( jobs : & ' a [ Job ] , name : & str ) -> anyhow:: Result < & ' a Job > {
288
+ let Some ( job) = jobs. iter ( ) . find ( |j| j. name == name) else {
289
+ let available_jobs: Vec < & Job > = jobs. iter ( ) . filter ( |j| j. is_linux ( ) ) . collect ( ) ;
290
+ let mut available_jobs =
291
+ available_jobs. iter ( ) . map ( |j| j. name . to_string ( ) ) . collect :: < Vec < _ > > ( ) ;
292
+ available_jobs. sort ( ) ;
293
+ return Err ( anyhow:: anyhow!(
294
+ "Job {name} not found. The following jobs are available:\n {}" ,
295
+ available_jobs. join( ", " )
296
+ ) ) ;
297
+ } ;
298
+ if !job. is_linux ( ) {
299
+ return Err ( anyhow:: anyhow!( "Only Linux jobs can be executed locally" ) ) ;
300
+ }
301
+
302
+ Ok ( job)
303
+ }
304
+
305
+ fn run_workflow_locally ( db : JobDatabase , job_type : JobType , name : String ) -> anyhow:: Result < ( ) > {
306
+ let jobs = match job_type {
307
+ JobType :: Auto => & db. auto_jobs ,
308
+ JobType :: PR => & db. pr_jobs ,
309
+ } ;
310
+ let job = find_linux_job ( & jobs, & name) . with_context ( || format ! ( "Cannot find job {name}" ) ) ?;
311
+
312
+ let mut custom_env: HashMap < String , String > = HashMap :: new ( ) ;
313
+ // Replicate src/ci/scripts/setup-environment.sh
314
+ // Adds custom environment variables to the job
315
+ if name. starts_with ( "dist-" ) {
316
+ if name. ends_with ( "-alt" ) {
317
+ custom_env. insert ( "DEPLOY_ALT" . to_string ( ) , "1" . to_string ( ) ) ;
318
+ } else {
319
+ custom_env. insert ( "DEPLOY" . to_string ( ) , "1" . to_string ( ) ) ;
320
+ }
321
+ }
322
+ custom_env. extend ( to_string_map ( & job. env ) ) ;
323
+
324
+ let mut cmd = Command :: new ( Path :: new ( DOCKER_DIRECTORY ) . join ( "run.sh" ) ) ;
325
+ cmd. arg ( job. image ( ) ) ;
326
+ cmd. envs ( custom_env) ;
327
+
328
+ eprintln ! ( "Executing {cmd:?}" ) ;
329
+
330
+ let result = cmd. spawn ( ) ?. wait ( ) ?;
331
+ if !result. success ( ) { Err ( anyhow:: anyhow!( "Job failed" ) ) } else { Ok ( ( ) ) }
332
+ }
333
+
270
334
#[ derive( clap:: Parser ) ]
271
335
enum Args {
272
336
/// Calculate a list of jobs that should be executed on CI.
273
337
/// Should only be used on CI inside GitHub actions.
274
338
CalculateJobMatrix ,
339
+ /// Execute a given CI job locally.
340
+ #[ clap( name = "run-local" ) ]
341
+ RunJobLocally {
342
+ /// Name of the job that should be executed.
343
+ name : String ,
344
+ /// Type of the job that should be executed.
345
+ #[ clap( long = "type" , default_value = "auto" ) ]
346
+ job_type : JobType ,
347
+ } ,
348
+ }
349
+
350
+ #[ derive( clap:: ValueEnum , Clone ) ]
351
+ enum JobType {
352
+ /// Merge attempt ("auto") job
353
+ Auto ,
354
+ /// Pull request job
355
+ PR ,
275
356
}
276
357
277
358
fn main ( ) -> anyhow:: Result < ( ) > {
@@ -287,6 +368,7 @@ fn main() -> anyhow::Result<()> {
287
368
288
369
calculate_job_matrix ( db, gh_ctx, & channel) . context ( "Failed to calculate job matrix" ) ?;
289
370
}
371
+ Args :: RunJobLocally { job_type, name } => run_workflow_locally ( db, job_type, name) ?,
290
372
}
291
373
292
374
Ok ( ( ) )
0 commit comments