@@ -34,10 +34,11 @@ def add_job_properties(jobs: List[Dict], prefix: str) -> List[Job]:
34
34
"""
35
35
modified_jobs = []
36
36
for job in jobs :
37
- job = dict (job )
38
- job ["image" ] = get_job_image (job )
39
- job ["name" ] = f"{ prefix } - { job ['name' ]} "
40
- modified_jobs .append (job )
37
+ # Create a copy of the `job` dictionary to avoid modifying `jobs`
38
+ new_job = dict (job )
39
+ new_job ["image" ] = get_job_image (new_job )
40
+ new_job ["name" ] = f"{ prefix } - { new_job ['name' ]} "
41
+ modified_jobs .append (new_job )
41
42
return modified_jobs
42
43
43
44
@@ -46,11 +47,15 @@ def add_base_env(jobs: List[Job], environment: Dict[str, str]) -> List[Job]:
46
47
Prepends `environment` to the `env` attribute of each job.
47
48
The `env` of each job has higher precedence than `environment`.
48
49
"""
50
+ modified_jobs = []
49
51
for job in jobs :
50
52
env = environment .copy ()
51
53
env .update (job .get ("env" , {}))
52
- job ["env" ] = env
53
- return jobs
54
+
55
+ new_job = dict (job )
56
+ new_job ["env" ] = env
57
+ modified_jobs .append (new_job )
58
+ return modified_jobs
54
59
55
60
56
61
@dataclasses .dataclass
@@ -123,7 +128,9 @@ def find_run_type(ctx: GitHubCtx) -> Optional[WorkflowRunType]:
123
128
124
129
def calculate_jobs (run_type : WorkflowRunType , job_data : Dict [str , Any ]) -> List [Job ]:
125
130
if isinstance (run_type , PRRunType ):
126
- return add_base_env (add_job_properties (job_data ["pr" ], "PR" ), job_data ["envs" ]["pr" ])
131
+ return add_base_env (
132
+ add_job_properties (job_data ["pr" ], "PR" ), job_data ["envs" ]["pr" ]
133
+ )
127
134
elif isinstance (run_type , TryRunType ):
128
135
jobs = job_data ["try" ]
129
136
custom_jobs = run_type .custom_jobs
@@ -188,95 +195,123 @@ def format_run_type(run_type: WorkflowRunType) -> str:
188
195
raise AssertionError ()
189
196
190
197
191
- def get_job_image (job ) -> str :
198
+ def get_job_image (job : Job ) -> str :
192
199
"""
193
200
By default, the Docker image of a job is based on its name.
194
201
However, it can be overridden by its IMAGE environment variable.
195
202
"""
196
- return job .get ("env" , {}).get ("IMAGE" , job ["name" ])
203
+ env = job .get ("env" , {})
204
+ # Return the IMAGE environment variable if it exists, otherwise return the job name
205
+ return env .get ("IMAGE" , job ["name" ])
197
206
198
207
199
- def run_workflow_locally ( job_data : Dict [ str , Any ], job_name : str , pr_jobs : bool ) :
200
- DOCKER_DIR = Path ( __file__ ). absolute (). parent . parent / "docker"
208
+ def is_linux_job ( job : Job ) -> bool :
209
+ return "ubuntu" in job [ "os" ]
201
210
202
- jobs = job_data ["pr" ] if pr_jobs else job_data ["auto" ]
203
- jobs = [job for job in jobs if job .get ("name" ) == job_name ]
211
+
212
+ def find_linux_job (job_data : Dict [str , Any ], job_name : str , pr_jobs : bool ) -> Job :
213
+ candidates = job_data ["pr" ] if pr_jobs else job_data ["auto" ]
214
+ jobs = [job for job in candidates if job .get ("name" ) == job_name ]
204
215
if len (jobs ) == 0 :
205
- raise Exception (f"Job `{ job_name } ` not found in { 'pr' if pr_jobs else 'auto' } jobs" )
216
+ available_jobs = "\n " .join (
217
+ sorted (job ["name" ] for job in candidates if is_linux_job (job ))
218
+ )
219
+ raise Exception (f"""Job `{ job_name } ` not found in { 'pr' if pr_jobs else 'auto' } jobs.
220
+ The following jobs are available:
221
+ { available_jobs } """ )
206
222
assert len (jobs ) == 1
223
+
207
224
job = jobs [0 ]
208
- if "ubuntu" not in job [ "os" ] :
225
+ if not is_linux_job ( job ) :
209
226
raise Exception ("Only Linux jobs can be executed locally" )
227
+ return job
228
+
229
+
230
+ def run_workflow_locally (job_data : Dict [str , Any ], job_name : str , pr_jobs : bool ):
231
+ DOCKER_DIR = Path (__file__ ).absolute ().parent .parent / "docker"
232
+
233
+ job = find_linux_job (job_data , job_name = job_name , pr_jobs = pr_jobs )
210
234
211
235
custom_env = {}
212
- custom_env ["DEPLOY" ] = "1"
236
+ # Replicate src/ci/scripts/setup-environment.sh
237
+ # Adds custom environment variables to the job
238
+ if job_name .startswith ("dist-" ):
239
+ if job_name .endswith ("-alt" ):
240
+ custom_env ["DEPLOY_ALT" ] = "1"
241
+ else :
242
+ custom_env ["DEPLOY" ] = "1"
213
243
custom_env .update ({k : str (v ) for (k , v ) in job .get ("env" , {}).items ()})
214
244
215
- args = [
216
- str (DOCKER_DIR / "run.sh" ),
217
- get_job_image (job )
218
- ]
245
+ args = [str (DOCKER_DIR / "run.sh" ), get_job_image (job )]
219
246
env_formatted = [f"{ k } ={ v } " for (k , v ) in sorted (custom_env .items ())]
220
247
print (f"Executing `{ ' ' .join (env_formatted )} { ' ' .join (args )} `" )
221
248
222
249
env = os .environ .copy ()
223
250
env .update (custom_env )
224
251
225
- process = subprocess .Popen (args , env = env )
226
- try :
227
- process .wait ()
228
- except KeyboardInterrupt :
229
- process .kill ()
252
+ subprocess .run (args , env = env )
230
253
231
254
232
- if __name__ == "__main__" :
233
- logging . basicConfig ( level = logging . INFO )
255
+ def calculate_job_matrix ( job_data : Dict [ str , Any ]) :
256
+ github_ctx = get_github_ctx ( )
234
257
235
- with open ( JOBS_YAML_PATH ) as f :
236
- data = yaml . safe_load ( f )
258
+ run_type = find_run_type ( github_ctx )
259
+ logging . info ( f"Job type: { run_type } " )
237
260
261
+ with open (CI_DIR / "channel" ) as f :
262
+ channel = f .read ().strip ()
263
+
264
+ jobs = []
265
+ if run_type is not None :
266
+ jobs = calculate_jobs (run_type , job_data )
267
+ jobs = skip_jobs (jobs , channel )
268
+
269
+ if not jobs :
270
+ raise Exception ("Scheduled job list is empty, this is an error" )
271
+
272
+ run_type = format_run_type (run_type )
273
+
274
+ logging .info (f"Output:\n { yaml .dump (dict (jobs = jobs , run_type = run_type ), indent = 4 )} " )
275
+ print (f"jobs={ json .dumps (jobs )} " )
276
+ print (f"run_type={ run_type } " )
277
+
278
+
279
+ def create_cli_parser ():
238
280
parser = argparse .ArgumentParser (
239
- prog = "ci.py" ,
240
- description = "Generate or run CI workflows"
281
+ prog = "ci.py" , description = "Generate or run CI workflows"
282
+ )
283
+ subparsers = parser .add_subparsers (
284
+ help = "Command to execute" , dest = "command" , required = True
285
+ )
286
+ subparsers .add_parser (
287
+ "calculate-job-matrix" ,
288
+ help = "Generate a matrix of jobs that should be executed in CI" ,
289
+ )
290
+ run_parser = subparsers .add_parser (
291
+ "run-local" , help = "Run a CI jobs locally (on Linux)"
241
292
)
242
- generate_matrix = argparse .ArgumentParser ()
243
- subparsers = parser .add_subparsers (help = "Command to execute" , dest = "command" , required = True )
244
- subparsers .add_parser ("calculate-job-matrix" )
245
- run_parser = subparsers .add_parser ("run-local" )
246
293
run_parser .add_argument (
247
294
"job_name" ,
248
295
help = "CI job that should be executed. By default, a merge (auto) "
249
- "job with the given name will be executed"
296
+ "job with the given name will be executed" ,
250
297
)
251
298
run_parser .add_argument (
252
- "--pr" ,
253
- action = "store_true" ,
254
- help = "Run a PR job instead of an auto job"
299
+ "--pr" , action = "store_true" , help = "Run a PR job instead of an auto job"
255
300
)
256
- args = parser .parse_args ()
257
-
258
- if args .command == "calculate-job-matrix" :
259
- github_ctx = get_github_ctx ()
301
+ return parser
260
302
261
- run_type = find_run_type (github_ctx )
262
- logging .info (f"Job type: { run_type } " )
263
303
264
- with open (CI_DIR / "channel" ) as f :
265
- channel = f .read ().strip ()
266
-
267
- jobs = []
268
- if run_type is not None :
269
- jobs = calculate_jobs (run_type , data )
270
- jobs = skip_jobs (jobs , channel )
304
+ if __name__ == "__main__" :
305
+ logging .basicConfig (level = logging .INFO )
271
306
272
- if not jobs :
273
- raise Exception ( "Scheduled job list is empty, this is an error" )
307
+ with open ( JOBS_YAML_PATH ) as f :
308
+ data = yaml . safe_load ( f )
274
309
275
- run_type = format_run_type (run_type )
310
+ parser = create_cli_parser ()
311
+ args = parser .parse_args ()
276
312
277
- logging .info (f"Output:\n { yaml .dump (dict (jobs = jobs , run_type = run_type ), indent = 4 )} " )
278
- print (f"jobs={ json .dumps (jobs )} " )
279
- print (f"run_type={ run_type } " )
313
+ if args .command == "calculate-job-matrix" :
314
+ calculate_job_matrix (data )
280
315
elif args .command == "run-local" :
281
316
run_workflow_locally (data , args .job_name , args .pr )
282
317
else :
0 commit comments