1
1
mod cpu_usage;
2
2
mod datadog;
3
+ mod jobs;
3
4
mod merge_report;
4
5
mod metrics;
5
6
mod utils;
@@ -10,10 +11,12 @@ use std::process::Command;
10
11
11
12
use anyhow:: Context ;
12
13
use clap:: Parser ;
14
+ use jobs:: JobDatabase ;
13
15
use serde_yaml:: Value ;
14
16
15
17
use crate :: cpu_usage:: load_cpu_usage;
16
18
use crate :: datadog:: upload_datadog_metric;
19
+ use crate :: jobs:: RunType ;
17
20
use crate :: merge_report:: post_merge_report;
18
21
use crate :: metrics:: postprocess_metrics;
19
22
use crate :: utils:: load_env_var;
@@ -22,104 +25,6 @@ const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
22
25
const DOCKER_DIRECTORY : & str = concat ! ( env!( "CARGO_MANIFEST_DIR" ) , "/../docker" ) ;
23
26
const JOBS_YML_PATH : & str = concat ! ( env!( "CARGO_MANIFEST_DIR" ) , "/../github-actions/jobs.yml" ) ;
24
27
25
- /// Representation of a job loaded from the `src/ci/github-actions/jobs.yml` file.
26
- #[ derive( serde:: Deserialize , Debug , Clone ) ]
27
- struct Job {
28
- /// Name of the job, e.g. mingw-check
29
- name : String ,
30
- /// GitHub runner on which the job should be executed
31
- os : String ,
32
- env : BTreeMap < String , Value > ,
33
- /// Should the job be only executed on a specific channel?
34
- #[ serde( default ) ]
35
- only_on_channel : Option < String > ,
36
- /// Rest of attributes that will be passed through to GitHub actions
37
- #[ serde( flatten) ]
38
- extra_keys : BTreeMap < String , Value > ,
39
- }
40
-
41
- impl Job {
42
- fn is_linux ( & self ) -> bool {
43
- self . os . contains ( "ubuntu" )
44
- }
45
-
46
- /// By default, the Docker image of a job is based on its name.
47
- /// However, it can be overridden by its IMAGE environment variable.
48
- fn image ( & self ) -> String {
49
- self . env
50
- . get ( "IMAGE" )
51
- . map ( |v| v. as_str ( ) . expect ( "IMAGE value should be a string" ) . to_string ( ) )
52
- . unwrap_or_else ( || self . name . clone ( ) )
53
- }
54
- }
55
-
56
- #[ derive( serde:: Deserialize , Debug ) ]
57
- struct JobEnvironments {
58
- #[ serde( rename = "pr" ) ]
59
- pr_env : BTreeMap < String , Value > ,
60
- #[ serde( rename = "try" ) ]
61
- try_env : BTreeMap < String , Value > ,
62
- #[ serde( rename = "auto" ) ]
63
- auto_env : BTreeMap < String , Value > ,
64
- }
65
-
66
- #[ derive( serde:: Deserialize , Debug ) ]
67
- struct JobDatabase {
68
- #[ serde( rename = "pr" ) ]
69
- pr_jobs : Vec < Job > ,
70
- #[ serde( rename = "try" ) ]
71
- try_jobs : Vec < Job > ,
72
- #[ serde( rename = "auto" ) ]
73
- auto_jobs : Vec < Job > ,
74
-
75
- /// Shared environments for the individual run types.
76
- envs : JobEnvironments ,
77
- }
78
-
79
- impl JobDatabase {
80
- fn find_auto_job_by_name ( & self , name : & str ) -> Option < Job > {
81
- self . auto_jobs . iter ( ) . find ( |j| j. name == name) . cloned ( )
82
- }
83
- }
84
-
85
- fn load_job_db ( path : & Path ) -> anyhow:: Result < JobDatabase > {
86
- let db = utils:: read_to_string ( path) ?;
87
- let mut db: Value = serde_yaml:: from_str ( & db) ?;
88
-
89
- // We need to expand merge keys (<<), because serde_yaml can't deal with them
90
- // `apply_merge` only applies the merge once, so do it a few times to unwrap nested merges.
91
- db. apply_merge ( ) ?;
92
- db. apply_merge ( ) ?;
93
-
94
- let db: JobDatabase = serde_yaml:: from_value ( db) ?;
95
- Ok ( db)
96
- }
97
-
98
- /// Representation of a job outputted to a GitHub Actions workflow.
99
- #[ derive( serde:: Serialize , Debug ) ]
100
- struct GithubActionsJob {
101
- /// The main identifier of the job, used by CI scripts to determine what should be executed.
102
- name : String ,
103
- /// Helper label displayed in GitHub Actions interface, containing the job name and a run type
104
- /// prefix (PR/try/auto).
105
- full_name : String ,
106
- os : String ,
107
- env : BTreeMap < String , serde_json:: Value > ,
108
- #[ serde( flatten) ]
109
- extra_keys : BTreeMap < String , serde_json:: Value > ,
110
- }
111
-
112
- /// Type of workflow that is being executed on CI
113
- #[ derive( Debug ) ]
114
- enum RunType {
115
- /// Workflows that run after a push to a PR branch
116
- PullRequest ,
117
- /// Try run started with @bors try
118
- TryJob { custom_jobs : Option < Vec < String > > } ,
119
- /// Merge attempt workflow
120
- AutoJob ,
121
- }
122
-
123
28
struct GitHubContext {
124
29
event_name : String ,
125
30
branch_ref : String ,
@@ -130,21 +35,21 @@ impl GitHubContext {
130
35
fn get_run_type ( & self ) -> Option < RunType > {
131
36
match ( self . event_name . as_str ( ) , self . branch_ref . as_str ( ) ) {
132
37
( "pull_request" , _) => Some ( RunType :: PullRequest ) ,
133
- ( "push" , "refs/heads/try-perf" ) => Some ( RunType :: TryJob { custom_jobs : None } ) ,
38
+ ( "push" , "refs/heads/try-perf" ) => Some ( RunType :: TryJob { job_patterns : None } ) ,
134
39
( "push" , "refs/heads/try" | "refs/heads/automation/bors/try" ) => {
135
- let custom_jobs = self . get_custom_jobs ( ) ;
136
- let custom_jobs = if !custom_jobs . is_empty ( ) { Some ( custom_jobs ) } else { None } ;
137
- Some ( RunType :: TryJob { custom_jobs } )
40
+ let patterns = self . get_try_job_patterns ( ) ;
41
+ let patterns = if !patterns . is_empty ( ) { Some ( patterns ) } else { None } ;
42
+ Some ( RunType :: TryJob { job_patterns : patterns } )
138
43
}
139
44
( "push" , "refs/heads/auto" ) => Some ( RunType :: AutoJob ) ,
140
45
_ => None ,
141
46
}
142
47
}
143
48
144
- /// Tries to parse names of specific CI jobs that should be executed in the form of
145
- /// try-job: <job-name >
49
+ /// Tries to parse patterns of CI jobs that should be executed in the form of
50
+ /// try-job: <job-pattern >
146
51
/// from the commit message of the passed GitHub context.
147
- fn get_custom_jobs ( & self ) -> Vec < String > {
52
+ fn get_try_job_patterns ( & self ) -> Vec < String > {
148
53
if let Some ( ref msg) = self . commit_message {
149
54
msg. lines ( )
150
55
. filter_map ( |line| line. trim ( ) . strip_prefix ( "try-job: " ) )
@@ -164,15 +69,6 @@ fn load_github_ctx() -> anyhow::Result<GitHubContext> {
164
69
Ok ( GitHubContext { event_name, branch_ref : load_env_var ( "GITHUB_REF" ) ?, commit_message } )
165
70
}
166
71
167
- /// Skip CI jobs that are not supposed to be executed on the given `channel`.
168
- fn skip_jobs ( jobs : Vec < Job > , channel : & str ) -> Vec < Job > {
169
- jobs. into_iter ( )
170
- . filter ( |job| {
171
- job. only_on_channel . is_none ( ) || job. only_on_channel . as_deref ( ) == Some ( channel)
172
- } )
173
- . collect ( )
174
- }
175
-
176
72
fn yaml_map_to_json ( map : & BTreeMap < String , Value > ) -> BTreeMap < String , serde_json:: Value > {
177
73
map. into_iter ( )
178
74
. map ( |( key, value) | {
@@ -184,124 +80,13 @@ fn yaml_map_to_json(map: &BTreeMap<String, Value>) -> BTreeMap<String, serde_jso
184
80
. collect ( )
185
81
}
186
82
187
- /// Maximum number of custom try jobs that can be requested in a single
188
- /// `@bors try` request.
189
- const MAX_TRY_JOBS_COUNT : usize = 20 ;
190
-
191
- fn calculate_jobs (
192
- run_type : & RunType ,
193
- db : & JobDatabase ,
194
- channel : & str ,
195
- ) -> anyhow:: Result < Vec < GithubActionsJob > > {
196
- let ( jobs, prefix, base_env) = match run_type {
197
- RunType :: PullRequest => ( db. pr_jobs . clone ( ) , "PR" , & db. envs . pr_env ) ,
198
- RunType :: TryJob { custom_jobs } => {
199
- let jobs = if let Some ( custom_jobs) = custom_jobs {
200
- if custom_jobs. len ( ) > MAX_TRY_JOBS_COUNT {
201
- return Err ( anyhow:: anyhow!(
202
- "It is only possible to schedule up to {MAX_TRY_JOBS_COUNT} custom jobs, received {} custom jobs" ,
203
- custom_jobs. len( )
204
- ) ) ;
205
- }
206
-
207
- let mut jobs = vec ! [ ] ;
208
- let mut unknown_jobs = vec ! [ ] ;
209
- for custom_job in custom_jobs {
210
- if let Some ( job) = db. find_auto_job_by_name ( custom_job) {
211
- jobs. push ( job) ;
212
- } else {
213
- unknown_jobs. push ( custom_job. clone ( ) ) ;
214
- }
215
- }
216
- if !unknown_jobs. is_empty ( ) {
217
- return Err ( anyhow:: anyhow!(
218
- "Custom job(s) `{}` not found in auto jobs" ,
219
- unknown_jobs. join( ", " )
220
- ) ) ;
221
- }
222
- jobs
223
- } else {
224
- db. try_jobs . clone ( )
225
- } ;
226
- ( jobs, "try" , & db. envs . try_env )
227
- }
228
- RunType :: AutoJob => ( db. auto_jobs . clone ( ) , "auto" , & db. envs . auto_env ) ,
229
- } ;
230
- let jobs = skip_jobs ( jobs, channel) ;
231
- let jobs = jobs
232
- . into_iter ( )
233
- . map ( |job| {
234
- let mut env: BTreeMap < String , serde_json:: Value > = yaml_map_to_json ( base_env) ;
235
- env. extend ( yaml_map_to_json ( & job. env ) ) ;
236
- let full_name = format ! ( "{prefix} - {}" , job. name) ;
237
-
238
- GithubActionsJob {
239
- name : job. name ,
240
- full_name,
241
- os : job. os ,
242
- env,
243
- extra_keys : yaml_map_to_json ( & job. extra_keys ) ,
244
- }
245
- } )
246
- . collect ( ) ;
247
-
248
- Ok ( jobs)
249
- }
250
-
251
- fn calculate_job_matrix (
252
- db : JobDatabase ,
253
- gh_ctx : GitHubContext ,
254
- channel : & str ,
255
- ) -> anyhow:: Result < ( ) > {
256
- let run_type = gh_ctx. get_run_type ( ) . ok_or_else ( || {
257
- anyhow:: anyhow!( "Cannot determine the type of workflow that is being executed" )
258
- } ) ?;
259
- eprintln ! ( "Run type: {run_type:?}" ) ;
260
-
261
- let jobs = calculate_jobs ( & run_type, & db, channel) ?;
262
- if jobs. is_empty ( ) {
263
- return Err ( anyhow:: anyhow!( "Computed job list is empty" ) ) ;
264
- }
265
-
266
- let run_type = match run_type {
267
- RunType :: PullRequest => "pr" ,
268
- RunType :: TryJob { .. } => "try" ,
269
- RunType :: AutoJob => "auto" ,
270
- } ;
271
-
272
- eprintln ! ( "Output" ) ;
273
- eprintln ! ( "jobs={jobs:?}" ) ;
274
- eprintln ! ( "run_type={run_type}" ) ;
275
- println ! ( "jobs={}" , serde_json:: to_string( & jobs) ?) ;
276
- println ! ( "run_type={run_type}" ) ;
277
-
278
- Ok ( ( ) )
279
- }
280
-
281
- fn find_linux_job < ' a > ( jobs : & ' a [ Job ] , name : & str ) -> anyhow:: Result < & ' a Job > {
282
- let Some ( job) = jobs. iter ( ) . find ( |j| j. name == name) else {
283
- let available_jobs: Vec < & Job > = jobs. iter ( ) . filter ( |j| j. is_linux ( ) ) . collect ( ) ;
284
- let mut available_jobs =
285
- available_jobs. iter ( ) . map ( |j| j. name . to_string ( ) ) . collect :: < Vec < _ > > ( ) ;
286
- available_jobs. sort ( ) ;
287
- return Err ( anyhow:: anyhow!(
288
- "Job {name} not found. The following jobs are available:\n {}" ,
289
- available_jobs. join( ", " )
290
- ) ) ;
291
- } ;
292
- if !job. is_linux ( ) {
293
- return Err ( anyhow:: anyhow!( "Only Linux jobs can be executed locally" ) ) ;
294
- }
295
-
296
- Ok ( job)
297
- }
298
-
299
83
fn run_workflow_locally ( db : JobDatabase , job_type : JobType , name : String ) -> anyhow:: Result < ( ) > {
300
84
let jobs = match job_type {
301
85
JobType :: Auto => & db. auto_jobs ,
302
86
JobType :: PR => & db. pr_jobs ,
303
87
} ;
304
- let job = find_linux_job ( jobs, & name) . with_context ( || format ! ( "Cannot find job {name}" ) ) ?;
88
+ let job =
89
+ jobs:: find_linux_job ( jobs, & name) . with_context ( || format ! ( "Cannot find job {name}" ) ) ?;
305
90
306
91
let mut custom_env: BTreeMap < String , String > = BTreeMap :: new ( ) ;
307
92
// Replicate src/ci/scripts/setup-environment.sh
@@ -385,7 +170,7 @@ enum Args {
385
170
}
386
171
387
172
#[ derive( clap:: ValueEnum , Clone ) ]
388
- enum JobType {
173
+ pub enum JobType {
389
174
/// Merge attempt ("auto") job
390
175
Auto ,
391
176
/// Pull request job
@@ -395,7 +180,10 @@ enum JobType {
395
180
fn main ( ) -> anyhow:: Result < ( ) > {
396
181
let args = Args :: parse ( ) ;
397
182
let default_jobs_file = Path :: new ( JOBS_YML_PATH ) ;
398
- let load_db = |jobs_path| load_job_db ( jobs_path) . context ( "Cannot load jobs.yml" ) ;
183
+ let load_db = |jobs_path| {
184
+ let db = utils:: read_to_string ( jobs_path) ?;
185
+ Ok :: < _ , anyhow:: Error > ( jobs:: load_job_db ( & db) . context ( "Cannot load jobs.yml" ) ?)
186
+ } ;
399
187
400
188
match args {
401
189
Args :: CalculateJobMatrix { jobs_file } => {
@@ -407,7 +195,7 @@ fn main() -> anyhow::Result<()> {
407
195
. trim ( )
408
196
. to_string ( ) ;
409
197
410
- calculate_job_matrix ( load_db ( jobs_path) ?, gh_ctx, & channel)
198
+ jobs :: calculate_job_matrix ( load_db ( jobs_path) ?, gh_ctx, & channel)
411
199
. context ( "Failed to calculate job matrix" ) ?;
412
200
}
413
201
Args :: RunJobLocally { job_type, name } => {
0 commit comments