Skip to content

Commit 3ae0c54

Browse files
committed
Add local job execution to citool
1 parent c3cbfdc commit 3ae0c54

File tree

1 file changed

+82
-0
lines changed

1 file changed

+82
-0
lines changed

src/ci/citool/src/main.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use std::collections::HashMap;
22
use std::path::Path;
3+
use std::process::Command;
34

45
use anyhow::Context;
56
use clap::Parser;
67
use serde_yaml::Value;
78

89
const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
10+
const DOCKER_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docker");
911
const JOBS_YML_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../github-actions/jobs.yml");
1012

1113
/// Representation of a job loaded from the jobs.yml file.
@@ -24,6 +26,21 @@ struct Job {
2426
extra_keys: HashMap<String, Value>,
2527
}
2628

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+
2744
#[derive(serde::Deserialize, Debug)]
2845
struct JobEnvironments {
2946
#[serde(rename = "pr")]
@@ -267,11 +284,75 @@ fn calculate_job_matrix(
267284
Ok(())
268285
}
269286

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+
270334
#[derive(clap::Parser)]
271335
enum Args {
272336
/// Calculate a list of jobs that should be executed on CI.
273337
/// Should only be used on CI inside GitHub actions.
274338
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,
275356
}
276357

277358
fn main() -> anyhow::Result<()> {
@@ -287,6 +368,7 @@ fn main() -> anyhow::Result<()> {
287368

288369
calculate_job_matrix(db, gh_ctx, &channel).context("Failed to calculate job matrix")?;
289370
}
371+
Args::RunJobLocally { job_type, name } => run_workflow_locally(db, job_type, name)?,
290372
}
291373

292374
Ok(())

0 commit comments

Comments
 (0)