@@ -34,10 +34,11 @@ def add_job_properties(jobs: List[Dict], prefix: str) -> List[Job]:
3434 """
3535 modified_jobs = []
3636 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 )
4142 return modified_jobs
4243
4344
@@ -46,11 +47,15 @@ def add_base_env(jobs: List[Job], environment: Dict[str, str]) -> List[Job]:
4647 Prepends `environment` to the `env` attribute of each job.
4748 The `env` of each job has higher precedence than `environment`.
4849 """
50+ modified_jobs = []
4951 for job in jobs :
5052 env = environment .copy ()
5153 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
5459
5560
5661@dataclasses .dataclass
@@ -123,7 +128,9 @@ def find_run_type(ctx: GitHubCtx) -> Optional[WorkflowRunType]:
123128
124129def calculate_jobs (run_type : WorkflowRunType , job_data : Dict [str , Any ]) -> List [Job ]:
125130 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+ )
127134 elif isinstance (run_type , TryRunType ):
128135 jobs = job_data ["try" ]
129136 custom_jobs = run_type .custom_jobs
@@ -188,95 +195,123 @@ def format_run_type(run_type: WorkflowRunType) -> str:
188195 raise AssertionError ()
189196
190197
191- def get_job_image (job ) -> str :
198+ def get_job_image (job : Job ) -> str :
192199 """
193200 By default, the Docker image of a job is based on its name.
194201 However, it can be overridden by its IMAGE environment variable.
195202 """
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" ])
197206
198207
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" ]
201210
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 ]
204215 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 } """ )
206222 assert len (jobs ) == 1
223+
207224 job = jobs [0 ]
208- if "ubuntu" not in job [ "os" ] :
225+ if not is_linux_job ( job ) :
209226 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 )
210234
211235 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"
213243 custom_env .update ({k : str (v ) for (k , v ) in job .get ("env" , {}).items ()})
214244
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 )]
219246 env_formatted = [f"{ k } ={ v } " for (k , v ) in sorted (custom_env .items ())]
220247 print (f"Executing `{ ' ' .join (env_formatted )} { ' ' .join (args )} `" )
221248
222249 env = os .environ .copy ()
223250 env .update (custom_env )
224251
225- process = subprocess .Popen (args , env = env )
226- try :
227- process .wait ()
228- except KeyboardInterrupt :
229- process .kill ()
252+ subprocess .run (args , env = env )
230253
231254
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 ( )
234257
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 } " )
237260
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 ():
238280 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)"
241292 )
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" )
246293 run_parser .add_argument (
247294 "job_name" ,
248295 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" ,
250297 )
251298 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"
255300 )
256- args = parser .parse_args ()
257-
258- if args .command == "calculate-job-matrix" :
259- github_ctx = get_github_ctx ()
301+ return parser
260302
261- run_type = find_run_type (github_ctx )
262- logging .info (f"Job type: { run_type } " )
263303
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 )
271306
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 )
274309
275- run_type = format_run_type (run_type )
310+ parser = create_cli_parser ()
311+ args = parser .parse_args ()
276312
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 )
280315 elif args .command == "run-local" :
281316 run_workflow_locally (data , args .job_name , args .pr )
282317 else :
0 commit comments