CI: Unconditional cleaning for benchmark runs. (#8839)

Now the `clean` CI steps are run always for benchmarking jobs. We run the full `./run git-clean` before and after benchmarks. Benchmarks take long enough to make any savings by not cleaning negligible.

### Important Notes
This PR brings partial refactoring in the workflow generating code which was very dirty. I'll build on this further soon when adding proper aarch64 macOS support.

Also, some minor tweaks to the generation were made:
* not writing `always() &&` twice;
* run only the latter cleaning step for canceled jobs.
This commit is contained in:
Michał Wawrzyniec Urbańczyk 2024-01-29 13:02:02 +01:00 committed by GitHub
parent 2ca4fe94e2
commit 0b16db4399
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 309 additions and 161 deletions

View File

@ -62,7 +62,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:

View File

@ -50,7 +50,7 @@ jobs:
run: ./run --help run: ./run --help
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: always()
name: Clean before name: Clean before
run: ./run git-clean run: ./run git-clean
env: env:
@ -64,7 +64,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: always()
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:

View File

@ -62,7 +62,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -118,7 +118,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -176,7 +176,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -245,7 +245,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -303,7 +303,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -361,7 +361,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -417,7 +417,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -475,7 +475,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -533,7 +533,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -593,7 +593,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -664,7 +664,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -726,7 +726,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -784,7 +784,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:

View File

@ -75,7 +75,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:

View File

@ -116,7 +116,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -179,7 +179,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -236,15 +236,15 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.ECR_PUSH_RUNTIME_ACCESS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.ECR_PUSH_RUNTIME_ACCESS_KEY_ID }}
AWS_DEFAULT_REGION: eu-west-1 AWS_DEFAULT_REGION: eu-west-1
AWS_SECRET_ACCESS_KEY: ${{ secrets.ECR_PUSH_RUNTIME_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.ECR_PUSH_RUNTIME_SECRET_ACCESS_KEY }}
ENSO_BUILD_ECR_REPOSITORY: runtime
GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }} GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }}
crate_ECR_REPOSITORY: runtime
- if: failure() && runner.os == 'Windows' - if: failure() && runner.os == 'Windows'
name: List files if failed (Windows) name: List files if failed (Windows)
run: Get-ChildItem -Force -Recurse run: Get-ChildItem -Force -Recurse
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -307,7 +307,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -368,7 +368,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -431,7 +431,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -502,7 +502,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -570,7 +570,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -646,7 +646,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -713,7 +713,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -777,7 +777,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -852,7 +852,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -918,7 +918,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:

View File

@ -94,7 +94,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -173,7 +173,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:
@ -254,7 +254,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: "always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:

View File

@ -50,7 +50,7 @@ jobs:
run: ./run --help run: ./run --help
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: always()
name: Clean before name: Clean before
run: ./run git-clean run: ./run git-clean
env: env:
@ -64,7 +64,7 @@ jobs:
- if: failure() && runner.os != 'Windows' - if: failure() && runner.os != 'Windows'
name: List files if failed (non-Windows) name: List files if failed (non-Windows)
run: ls -lAR run: ls -lAR
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" - if: always()
name: Clean after name: Clean after
run: ./run git-clean run: ./run git-clean
env: env:

View File

@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
use crate::ci::labels::CLEAN_BUILD_REQUIRED;
use crate::ci_gen::job::plain_job; use crate::ci_gen::job::plain_job;
use crate::ci_gen::job::plain_job_customized;
use crate::ci_gen::job::with_packaging_steps; use crate::ci_gen::job::with_packaging_steps;
use crate::ci_gen::job::RunsOn; use crate::ci_gen::job::RunsOn;
use crate::version::promote::Designation; use crate::version::promote::Designation;
@ -118,11 +118,154 @@ impl RunsOn for BenchmarkRunner {
fn runs_on(&self) -> Vec<RunnerLabel> { fn runs_on(&self) -> Vec<RunnerLabel> {
vec![RunnerLabel::Benchmark] vec![RunnerLabel::Benchmark]
} }
fn os_name(&self) -> Option<String> { fn job_name_suffix(&self) -> Option<String> {
None None
} }
} }
/// Condition under which the runner should be cleaned.
#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq)]
pub enum CleaningCondition {
/// Always clean, even if the job was canceled or failed.
Always,
/// Clean only if the "Clean required" label is present on the pull request.
#[default]
OnLabel,
}
impl CleaningCondition {
/// Pretty print (for GH Actions) the `if` condition for the cleaning step.
pub fn format(self) -> String {
// Note that we need to use `always() &&` to make this condition evaluate on failed and
// canceled runs. See: https://docs.github.com/en/actions/learn-github-actions/expressions#always
//
// Using `always() &&` is not a no-op like `true &&` would be.
match self {
Self::Always => "always()".into(),
Self::OnLabel => format!(
"contains(github.event.pull_request.labels.*.name, '{CLEAN_BUILD_REQUIRED}')"
),
}
}
/// Format condition as `if` expression.
///
/// All the conditions are joined with `&&`.
pub fn format_conjunction(conditions: impl IntoIterator<Item = Self>) -> Option<String> {
let conditions = conditions.into_iter().collect::<BTreeSet<_>>();
if conditions.is_empty() {
None
} else {
Some(conditions.into_iter().map(Self::format).join(" && "))
}
}
}
/// Create a step that cleans the runner if the conditions are met.
pub fn cleaning_step(
name: impl Into<String>,
conditions: impl IntoIterator<Item = CleaningCondition>,
) -> Step {
let mut ret = run("git-clean").with_name(name);
ret.r#if = CleaningCondition::format_conjunction(conditions);
ret
}
/// Data needed to generate a typical sequence of CI steps invoking `./run` script.
#[derive(Derivative)]
#[derivative(Debug)]
pub struct RunStepsBuilder {
/// The command passed to `./run` script.
pub run_command: String,
/// Condition under which the runner should be cleaned before and after the run.
pub cleaning: CleaningCondition,
/// Customize the step that runs the command.
///
/// Allows replacing the run step with one or more custom steps.
#[derivative(Debug = "ignore")]
pub customize: Option<Box<dyn FnOnce(Step) -> Vec<Step>>>,
}
impl RunStepsBuilder {
/// Create a builder with the given command.
pub fn new(run_command: impl Into<String>) -> Self {
Self { run_command: run_command.into(), cleaning: default(), customize: default() }
}
/// Set the cleaning condition.
pub fn cleaning(mut self, cleaning: CleaningCondition) -> Self {
self.cleaning = cleaning;
self
}
/// Customize the step that runs the command.
pub fn customize(mut self, customize: impl FnOnce(Step) -> Vec<Step> + 'static) -> Self {
self.customize = Some(Box::new(customize));
self
}
/// Build the steps.
pub fn build(self) -> Vec<Step> {
let clean_before = cleaning_step("Clean before", [self.cleaning]);
let clean_after = cleaning_step("Clean after", [CleaningCondition::Always, self.cleaning]);
let run_step = run(self.run_command);
let run_steps = match self.customize {
Some(customize) => customize(run_step),
None => vec![run_step],
};
let mut steps = setup_script_steps();
steps.push(clean_before);
steps.extend(run_steps);
steps.extend(list_everything_on_failure());
steps.push(clean_after);
steps
}
pub fn job_builder(self, name: impl Into<String>, runs_on: impl RunsOn) -> RunJobBuilder {
RunJobBuilder::new(self, name, runs_on)
}
pub fn build_job(self, name: impl Into<String>, runs_on: impl RunsOn) -> Job {
self.job_builder(name, runs_on).build()
}
}
/// Data needed to generate a job that invokes `./run` script.
#[derive(Debug)]
pub struct RunJobBuilder {
/// Data to generate the steps.
pub inner: RunStepsBuilder,
/// Name of the job. Might be modified to include the runner info.
pub name: String,
/// The runners on which the job should run.
pub runs_on: Box<dyn RunsOn>,
}
impl RunJobBuilder {
pub fn new(
build_steps: RunStepsBuilder,
name: impl Into<String>,
runs_on: impl RunsOn + 'static,
) -> Self {
Self { name: name.into(), runs_on: Box::new(runs_on), inner: build_steps }
}
pub fn build(self) -> Job {
let name = if let Some(os_name) = self.runs_on.job_name_suffix() {
format!("{} ({})", self.name, os_name)
} else {
self.name
};
let steps = self.inner.build();
let runs_on = self.runs_on.runs_on();
let strategy = self.runs_on.strategy();
Job { name, runs_on, steps, strategy, ..default() }
}
}
/// Trigger the workflow on push to the default branch.
pub fn on_default_branch_push() -> Push { pub fn on_default_branch_push() -> Push {
Push { inner_branches: Branches::new([DEFAULT_BRANCH_NAME]), ..default() } Push { inner_branches: Branches::new([DEFAULT_BRANCH_NAME]), ..default() }
} }
@ -136,13 +279,19 @@ pub fn runs_on(os: OS) -> Vec<RunnerLabel> {
} }
} }
/// Initial CI job steps: check out the source code and set up the environment.
pub fn setup_script_steps() -> Vec<Step> { pub fn setup_script_steps() -> Vec<Step> {
let mut ret = vec![setup_conda(), setup_wasm_pack_step(), setup_artifact_api()]; let mut ret = vec![setup_conda(), setup_wasm_pack_step(), setup_artifact_api()];
ret.extend(checkout_repo_step()); ret.extend(checkout_repo_step());
// We run `./run --help` so:
// * The build-script is build in a separate step. This allows us to monitor its build-time and
// not affect timing of the actual build.
// * The help message is printed to the log, including environment-dependent flag defaults.
ret.push(run("--help").with_name("Build Script Setup")); ret.push(run("--help").with_name("Build Script Setup"));
ret ret
} }
pub fn list_everything_on_failure() -> impl IntoIterator<Item = Step> { pub fn list_everything_on_failure() -> impl IntoIterator<Item = Step> {
let win = Step { let win = Step {
name: Some("List files if failed (Windows)".into()), name: Some("List files if failed (Windows)".into()),
@ -161,32 +310,6 @@ pub fn list_everything_on_failure() -> impl IntoIterator<Item = Step> {
[win, non_win] [win, non_win]
} }
/// The `f` is applied to the step that does an actual script invocation.
pub fn setup_customized_script_steps(
command_line: impl AsRef<str>,
customize: impl FnOnce(Step) -> Vec<Step>,
) -> Vec<Step> {
use crate::ci::labels::CLEAN_BUILD_REQUIRED;
// Check if the pull request has a "Clean required" label.
let pre_clean_condition =
format!("contains(github.event.pull_request.labels.*.name, '{CLEAN_BUILD_REQUIRED}')",);
let post_clean_condition = format!("always() && {pre_clean_condition}");
let mut steps = setup_script_steps();
let clean_step = run("git-clean").with_if(&pre_clean_condition).with_name("Clean before");
steps.push(clean_step.clone());
steps.extend(customize(run(command_line)));
steps.extend(list_everything_on_failure());
steps.push(
clean_step.with_if(format!("always() && {post_clean_condition}")).with_name("Clean after"),
);
steps
}
pub fn setup_script_and_steps(command_line: impl AsRef<str>) -> Vec<Step> {
setup_customized_script_steps(command_line, |s| vec![s])
}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct DraftRelease; pub struct DraftRelease;
impl JobArchetype for DraftRelease { impl JobArchetype for DraftRelease {
@ -220,7 +343,7 @@ impl DraftRelease {
pub struct PublishRelease; pub struct PublishRelease;
impl JobArchetype for PublishRelease { impl JobArchetype for PublishRelease {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
let mut ret = plain_job(&os, "Publish release", "release publish"); let mut ret = plain_job(os, "Publish release", "release publish");
ret.expose_secret_as(secret::ARTEFACT_S3_ACCESS_KEY_ID, crate::aws::env::AWS_ACCESS_KEY_ID); ret.expose_secret_as(secret::ARTEFACT_S3_ACCESS_KEY_ID, crate::aws::env::AWS_ACCESS_KEY_ID);
ret.expose_secret_as( ret.expose_secret_as(
secret::ARTEFACT_S3_SECRET_ACCESS_KEY, secret::ARTEFACT_S3_SECRET_ACCESS_KEY,
@ -235,7 +358,9 @@ impl JobArchetype for PublishRelease {
pub struct UploadIde; pub struct UploadIde;
impl JobArchetype for UploadIde { impl JobArchetype for UploadIde {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job_customized(&os, "Build Old IDE", "ide upload --wasm-source current-ci-run --backend-source release --backend-release ${{env.ENSO_RELEASE_ID}}", with_packaging_steps(os)) RunStepsBuilder::new("ide upload --wasm-source current-ci-run --backend-source release --backend-release ${{env.ENSO_RELEASE_ID}}")
.customize(with_packaging_steps(os))
.build_job("Build Old IDE", os)
} }
} }
@ -243,12 +368,11 @@ impl JobArchetype for UploadIde {
pub struct UploadIde2; pub struct UploadIde2;
impl JobArchetype for UploadIde2 { impl JobArchetype for UploadIde2 {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job_customized( RunStepsBuilder::new(
&os,
"Build New IDE",
"ide2 upload --backend-source release --backend-release ${{env.ENSO_RELEASE_ID}}", "ide2 upload --backend-source release --backend-release ${{env.ENSO_RELEASE_ID}}",
with_packaging_steps(os),
) )
.customize(with_packaging_steps(os))
.build_job("Build New IDE", os)
} }
} }
@ -257,9 +381,9 @@ pub struct PromoteReleaseJob;
impl JobArchetype for PromoteReleaseJob { impl JobArchetype for PromoteReleaseJob {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
let command = format!("release promote {}", get_input_expression(DESIGNATOR_INPUT_NAME)); let command = format!("release promote {}", get_input_expression(DESIGNATOR_INPUT_NAME));
let mut job = plain_job_customized(&os, "Promote release", command, |step| { let mut job = RunStepsBuilder::new(command)
vec![step.with_id(Self::PROMOTE_STEP_ID)] .customize(|step| vec![step.with_id(Self::PROMOTE_STEP_ID)])
}); .build_job("Promote release", os);
self.expose_outputs(&mut job); self.expose_outputs(&mut job);
job job
} }
@ -289,12 +413,7 @@ pub fn changelog() -> Result<Workflow> {
Opened, Opened,
Reopened, Reopened,
])); ]));
ret.add_job(Job { ret.add_job(RunStepsBuilder::new("changelog-check").build_job("Changelog", RunnerLabel::X64));
name: "Changelog".into(),
runs_on: vec![RunnerLabel::X64],
steps: setup_script_and_steps("changelog-check"),
..default()
});
Ok(ret) Ok(ret)
} }
@ -480,7 +599,7 @@ pub fn std_libs_benchmark() -> Result<Workflow> {
benchmark("Benchmark Standard Libraries", "backend benchmark enso-jmh", Some(4 * 60)) benchmark("Benchmark Standard Libraries", "backend benchmark enso-jmh", Some(4 * 60))
} }
fn benchmark(name: &str, cmd_line: &str, timeout: Option<u32>) -> Result<Workflow> { fn benchmark(name: &str, command_line: &str, timeout_minutes: Option<u32>) -> Result<Workflow> {
let just_check_input_name = "just-check"; let just_check_input_name = "just-check";
let just_check_input = WorkflowDispatchInput { let just_check_input = WorkflowDispatchInput {
r#type: WorkflowDispatchInputType::Boolean{default: Some(false)}, r#type: WorkflowDispatchInputType::Boolean{default: Some(false)},
@ -501,8 +620,10 @@ fn benchmark(name: &str, cmd_line: &str, timeout: Option<u32>) -> Result<Workflo
wrap_expression(format!("true == inputs.{just_check_input_name}")), wrap_expression(format!("true == inputs.{just_check_input_name}")),
); );
let mut benchmark_job = plain_job(&BenchmarkRunner, name, cmd_line); let mut benchmark_job = RunStepsBuilder::new(command_line)
benchmark_job.timeout_minutes = timeout; .cleaning(CleaningCondition::Always)
.build_job(name, BenchmarkRunner);
benchmark_job.timeout_minutes = timeout_minutes;
workflow.add_job(benchmark_job); workflow.add_job(benchmark_job);
Ok(workflow) Ok(workflow)
} }

View File

@ -3,6 +3,7 @@ use crate::prelude::*;
use crate::ci_gen::runs_on; use crate::ci_gen::runs_on;
use crate::ci_gen::secret; use crate::ci_gen::secret;
use crate::ci_gen::step; use crate::ci_gen::step;
use crate::ci_gen::RunStepsBuilder;
use ide_ci::actions::workflow::definition::cancel_workflow_action; use ide_ci::actions::workflow::definition::cancel_workflow_action;
use ide_ci::actions::workflow::definition::Access; use ide_ci::actions::workflow::definition::Access;
@ -24,21 +25,55 @@ use ide_ci::actions::workflow::definition::Strategy;
/// https://github.com/electron-userland/electron-builder/issues/6865 /// https://github.com/electron-userland/electron-builder/issues/6865
const ELECTRON_BUILDER_MACOS_VERSION: Version = Version::new(24, 6, 4); const ELECTRON_BUILDER_MACOS_VERSION: Version = Version::new(24, 6, 4);
pub trait RunsOn { /// Target runners set (or just a single runner) for a job.
pub trait RunsOn: 'static + Debug {
/// A strategy that will be used for the job.
///
/// Needs to be customized only for matrix jobs.
fn strategy(&self) -> Option<Strategy> { fn strategy(&self) -> Option<Strategy> {
None None
} }
/// Labels required on the runner to run this job.
fn runs_on(&self) -> Vec<RunnerLabel>; fn runs_on(&self) -> Vec<RunnerLabel>;
fn os_name(&self) -> Option<String> {
/// A name that will be added to the job name.
///
/// Should not be used if there is per-os matrix.
fn job_name_suffix(&self) -> Option<String> {
None None
} }
} }
impl RunsOn for RunnerLabel {
fn runs_on(&self) -> Vec<RunnerLabel> {
vec![*self]
}
fn job_name_suffix(&self) -> Option<String> {
match self {
RunnerLabel::MacOS => Some("MacOS".to_string()),
RunnerLabel::Linux => Some("Linux".to_string()),
RunnerLabel::Windows => Some("Windows".to_string()),
RunnerLabel::MacOSLatest => Some("MacOSLatest".to_string()),
RunnerLabel::LinuxLatest => Some("LinuxLatest".to_string()),
RunnerLabel::WindowsLatest => Some("WindowsLatest".to_string()),
// Other labels are not OS-specific, so None.
RunnerLabel::SelfHosted
| RunnerLabel::Engine
| RunnerLabel::X64
| RunnerLabel::Benchmark
| RunnerLabel::Metarunner
| RunnerLabel::MatrixOs => None,
}
}
}
impl RunsOn for OS { impl RunsOn for OS {
fn runs_on(&self) -> Vec<RunnerLabel> { fn runs_on(&self) -> Vec<RunnerLabel> {
runs_on(*self) runs_on(*self)
} }
fn os_name(&self) -> Option<String> { fn job_name_suffix(&self) -> Option<String> {
Some(self.to_string()) Some(self.to_string())
} }
} }
@ -54,28 +89,11 @@ impl RunsOn for Strategy {
} }
pub fn plain_job( pub fn plain_job(
runs_on_info: &impl RunsOn, runs_on: impl RunsOn,
name: impl AsRef<str>, name: impl Into<String>,
command_line: impl AsRef<str>, command_line: impl Into<String>,
) -> Job { ) -> Job {
plain_job_customized(runs_on_info, name, command_line, |s| vec![s]) RunStepsBuilder::new(command_line).build_job(name, runs_on)
}
pub fn plain_job_customized(
runs_on_info: &impl RunsOn,
name: impl AsRef<str>,
command_line: impl AsRef<str>,
f: impl FnOnce(Step) -> Vec<Step>,
) -> Job {
let name = if let Some(os_name) = runs_on_info.os_name() {
format!("{} ({})", name.as_ref(), os_name)
} else {
name.as_ref().to_string()
};
let steps = crate::ci_gen::setup_customized_script_steps(command_line, f);
let runs_on = runs_on_info.runs_on();
let strategy = runs_on_info.strategy();
Job { name, runs_on, steps, strategy, ..default() }
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -102,7 +120,7 @@ impl JobArchetype for CancelWorkflow {
pub struct Lint; pub struct Lint;
impl JobArchetype for Lint { impl JobArchetype for Lint {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job(&os, "Lint", "lint") plain_job(os, "Lint", "lint")
} }
} }
@ -110,7 +128,7 @@ impl JobArchetype for Lint {
pub struct NativeTest; pub struct NativeTest;
impl JobArchetype for NativeTest { impl JobArchetype for NativeTest {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job(&os, "Native GUI tests", "wasm test --no-wasm") plain_job(os, "Native GUI tests", "wasm test --no-wasm")
} }
} }
@ -118,7 +136,7 @@ impl JobArchetype for NativeTest {
pub struct NewGuiTest; pub struct NewGuiTest;
impl JobArchetype for NewGuiTest { impl JobArchetype for NewGuiTest {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job(&os, "New (Vue) GUI tests", "gui2 test") plain_job(os, "New (Vue) GUI tests", "gui2 test")
} }
} }
@ -126,7 +144,7 @@ impl JobArchetype for NewGuiTest {
pub struct NewGuiBuild; pub struct NewGuiBuild;
impl JobArchetype for NewGuiBuild { impl JobArchetype for NewGuiBuild {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job(&os, "New (Vue) GUI build", "gui2 build") plain_job(os, "New (Vue) GUI build", "gui2 build")
} }
} }
@ -134,7 +152,7 @@ impl JobArchetype for NewGuiBuild {
pub struct WasmTest; pub struct WasmTest;
impl JobArchetype for WasmTest { impl JobArchetype for WasmTest {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job(&os, "WASM GUI tests", "wasm test --no-native") plain_job(os, "WASM GUI tests", "wasm test --no-native")
} }
} }
@ -143,7 +161,7 @@ pub struct IntegrationTest;
impl JobArchetype for IntegrationTest { impl JobArchetype for IntegrationTest {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job( plain_job(
&os, os,
"IDE integration tests", "IDE integration tests",
"ide integration-test --backend-source current-ci-run", "ide integration-test --backend-source current-ci-run",
) )
@ -154,12 +172,10 @@ impl JobArchetype for IntegrationTest {
pub struct BuildWasm; pub struct BuildWasm;
impl JobArchetype for BuildWasm { impl JobArchetype for BuildWasm {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job_customized( let command = "wasm build --wasm-upload-artifact ${{ runner.os == 'Linux' }}";
&os, RunStepsBuilder::new(command)
"Build GUI (WASM)", .customize(|step| vec![step.with_secret_exposed(crate::env::ENSO_AG_GRID_LICENSE_KEY)])
"wasm build --wasm-upload-artifact ${{ runner.os == 'Linux' }}", .build_job("Build GUI (WASM)", os)
|step| vec![step.with_secret_exposed(crate::env::ENSO_AG_GRID_LICENSE_KEY)],
)
} }
} }
@ -167,7 +183,7 @@ impl JobArchetype for BuildWasm {
pub struct BuildBackend; pub struct BuildBackend;
impl JobArchetype for BuildBackend { impl JobArchetype for BuildBackend {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job(&os, "Build Backend", "backend get") plain_job(os, "Build Backend", "backend get")
} }
} }
@ -175,7 +191,7 @@ impl JobArchetype for BuildBackend {
pub struct UploadBackend; pub struct UploadBackend;
impl JobArchetype for UploadBackend { impl JobArchetype for UploadBackend {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job(&os, "Upload Backend", "backend upload") plain_job(os, "Upload Backend", "backend upload")
} }
} }
@ -183,18 +199,22 @@ impl JobArchetype for UploadBackend {
pub struct DeployRuntime; pub struct DeployRuntime;
impl JobArchetype for DeployRuntime { impl JobArchetype for DeployRuntime {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job_customized(&os, "Upload Runtime to ECR", "release deploy-runtime", |step| { RunStepsBuilder::new("release deploy-runtime")
let step = step .customize(|step| {
.with_secret_exposed_as("CI_PRIVATE_TOKEN", "GITHUB_TOKEN") vec![step
.with_env("crate_ECR_REPOSITORY", crate::aws::ecr::runtime::NAME) .with_secret_exposed_as(secret::CI_PRIVATE_TOKEN, ide_ci::github::GITHUB_TOKEN)
.with_secret_exposed_as(secret::ECR_PUSH_RUNTIME_ACCESS_KEY_ID, "AWS_ACCESS_KEY_ID") .with_env("ENSO_BUILD_ECR_REPOSITORY", crate::aws::ecr::runtime::NAME)
.with_secret_exposed_as(
secret::ECR_PUSH_RUNTIME_ACCESS_KEY_ID,
"AWS_ACCESS_KEY_ID",
)
.with_secret_exposed_as( .with_secret_exposed_as(
secret::ECR_PUSH_RUNTIME_SECRET_ACCESS_KEY, secret::ECR_PUSH_RUNTIME_SECRET_ACCESS_KEY,
"AWS_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY",
) )
.with_env("AWS_DEFAULT_REGION", crate::aws::ecr::runtime::REGION); .with_env("AWS_DEFAULT_REGION", crate::aws::ecr::runtime::REGION)]
vec![step]
}) })
.build_job("Upload Runtime to ECR", os)
} }
} }
@ -202,20 +222,22 @@ impl JobArchetype for DeployRuntime {
pub struct DeployGui; pub struct DeployGui;
impl JobArchetype for DeployGui { impl JobArchetype for DeployGui {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job_customized(&os, "Upload GUI to S3", "release deploy-gui", |step| { RunStepsBuilder::new("release deploy-gui")
let step = step .customize(|step| {
.with_secret_exposed_as("CI_PRIVATE_TOKEN", "GITHUB_TOKEN") vec![step
.with_secret_exposed_as(secret::CI_PRIVATE_TOKEN, ide_ci::github::GITHUB_TOKEN)
.with_secret_exposed_as(secret::ARTEFACT_S3_ACCESS_KEY_ID, "AWS_ACCESS_KEY_ID") .with_secret_exposed_as(secret::ARTEFACT_S3_ACCESS_KEY_ID, "AWS_ACCESS_KEY_ID")
.with_secret_exposed_as( .with_secret_exposed_as(
secret::ARTEFACT_S3_SECRET_ACCESS_KEY, secret::ARTEFACT_S3_SECRET_ACCESS_KEY,
"AWS_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY",
) )
.with_secret_exposed_as(secret::ENSO_ADMIN_TOKEN, crate::env::ENSO_ADMIN_TOKEN); .with_secret_exposed_as(secret::ENSO_ADMIN_TOKEN, crate::env::ENSO_ADMIN_TOKEN)]
vec![step]
}) })
.build_job("Upload GUI to S3", os)
} }
} }
pub fn expose_os_specific_signing_secret(os: OS, step: Step) -> Step { pub fn expose_os_specific_signing_secret(os: OS, step: Step) -> Step {
match os { match os {
OS::Windows => step OS::Windows => step
@ -293,12 +315,11 @@ pub fn with_packaging_steps(os: OS) -> impl FnOnce(Step) -> Vec<Step> {
pub struct PackageNewIde; pub struct PackageNewIde;
impl JobArchetype for PackageNewIde { impl JobArchetype for PackageNewIde {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job_customized( RunStepsBuilder::new(
&os,
"Package New IDE",
"ide2 build --backend-source current-ci-run --gui2-upload-artifact false", "ide2 build --backend-source current-ci-run --gui2-upload-artifact false",
with_packaging_steps(os),
) )
.customize(with_packaging_steps(os))
.build_job("Package New IDE", os)
} }
} }
@ -306,9 +327,13 @@ impl JobArchetype for PackageNewIde {
pub struct CiCheckBackend; pub struct CiCheckBackend;
impl JobArchetype for CiCheckBackend { impl JobArchetype for CiCheckBackend {
fn job(&self, os: OS) -> Job { fn job(&self, os: OS) -> Job {
plain_job_customized(&os, "Engine", "backend ci-check", |main_step| { RunStepsBuilder::new("backend ci-check")
let main_step = main_step .customize(move |step| {
.with_secret_exposed_as(secret::ENSO_LIB_S3_AWS_REGION, crate::aws::env::AWS_REGION) let main_step = step
.with_secret_exposed_as(
secret::ENSO_LIB_S3_AWS_REGION,
crate::aws::env::AWS_REGION,
)
.with_secret_exposed_as( .with_secret_exposed_as(
secret::ENSO_LIB_S3_AWS_ACCESS_KEY_ID, secret::ENSO_LIB_S3_AWS_ACCESS_KEY_ID,
crate::aws::env::AWS_ACCESS_KEY_ID, crate::aws::env::AWS_ACCESS_KEY_ID,
@ -319,6 +344,7 @@ impl JobArchetype for CiCheckBackend {
); );
vec![main_step, step::engine_test_reporter(os), step::stdlib_test_reporter(os)] vec![main_step, step::engine_test_reporter(os), step::stdlib_test_reporter(os)]
}) })
.build_job("Engine", os)
.with_permission(Permission::Checks, Access::Write) .with_permission(Permission::Checks, Access::Write)
} }
} }

View File

@ -1053,6 +1053,7 @@ pub fn checkout_repo_step_customized(f: impl FnOnce(Step) -> Step) -> Vec<Step>
vec![submodules_workaround_win, submodules_workaround_linux, actual_checkout] vec![submodules_workaround_win, submodules_workaround_linux, actual_checkout]
} }
/// See [`checkout_repo_step_customized`].
pub fn checkout_repo_step() -> impl IntoIterator<Item = Step> { pub fn checkout_repo_step() -> impl IntoIterator<Item = Step> {
checkout_repo_step_customized(identity) checkout_repo_step_customized(identity)
} }