mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 08:52:58 +03:00
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:
parent
2ca4fe94e2
commit
0b16db4399
2
.github/workflows/changelog.yml
vendored
2
.github/workflows/changelog.yml
vendored
@ -62,7 +62,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
|
4
.github/workflows/engine-benchmark.yml
vendored
4
.github/workflows/engine-benchmark.yml
vendored
@ -50,7 +50,7 @@ jobs:
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
- if: always()
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -64,7 +64,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
- if: always()
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
|
26
.github/workflows/gui.yml
vendored
26
.github/workflows/gui.yml
vendored
@ -62,7 +62,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -118,7 +118,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -176,7 +176,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -245,7 +245,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -303,7 +303,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -361,7 +361,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -417,7 +417,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -475,7 +475,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -533,7 +533,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -593,7 +593,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -664,7 +664,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -726,7 +726,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -784,7 +784,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
|
2
.github/workflows/promote.yml
vendored
2
.github/workflows/promote.yml
vendored
@ -75,7 +75,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
|
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@ -116,7 +116,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -179,7 +179,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -236,15 +236,15 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ECR_PUSH_RUNTIME_ACCESS_KEY_ID }}
|
||||
AWS_DEFAULT_REGION: eu-west-1
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ECR_PUSH_RUNTIME_SECRET_ACCESS_KEY }}
|
||||
ENSO_BUILD_ECR_REPOSITORY: runtime
|
||||
GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }}
|
||||
crate_ECR_REPOSITORY: runtime
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -307,7 +307,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -368,7 +368,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -431,7 +431,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -502,7 +502,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -570,7 +570,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -646,7 +646,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -713,7 +713,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -777,7 +777,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -852,7 +852,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -918,7 +918,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
|
6
.github/workflows/scala-new.yml
vendored
6
.github/workflows/scala-new.yml
vendored
@ -94,7 +94,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -173,7 +173,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -254,7 +254,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
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
|
||||
run: ./run git-clean
|
||||
env:
|
||||
|
4
.github/workflows/std-libs-benchmark.yml
vendored
4
.github/workflows/std-libs-benchmark.yml
vendored
@ -50,7 +50,7 @@ jobs:
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
- if: always()
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
@ -64,7 +64,7 @@ jobs:
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
- if: always()
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::ci::labels::CLEAN_BUILD_REQUIRED;
|
||||
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::RunsOn;
|
||||
use crate::version::promote::Designation;
|
||||
@ -118,11 +118,154 @@ impl RunsOn for BenchmarkRunner {
|
||||
fn runs_on(&self) -> Vec<RunnerLabel> {
|
||||
vec![RunnerLabel::Benchmark]
|
||||
}
|
||||
fn os_name(&self) -> Option<String> {
|
||||
fn job_name_suffix(&self) -> Option<String> {
|
||||
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 {
|
||||
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> {
|
||||
let mut ret = vec![setup_conda(), setup_wasm_pack_step(), setup_artifact_api()];
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
pub fn list_everything_on_failure() -> impl IntoIterator<Item = Step> {
|
||||
let win = Step {
|
||||
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]
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub struct DraftRelease;
|
||||
impl JobArchetype for DraftRelease {
|
||||
@ -220,7 +343,7 @@ impl DraftRelease {
|
||||
pub struct PublishRelease;
|
||||
impl JobArchetype for PublishRelease {
|
||||
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_SECRET_ACCESS_KEY,
|
||||
@ -235,7 +358,9 @@ impl JobArchetype for PublishRelease {
|
||||
pub struct UploadIde;
|
||||
impl JobArchetype for UploadIde {
|
||||
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;
|
||||
impl JobArchetype for UploadIde2 {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(
|
||||
&os,
|
||||
"Build New IDE",
|
||||
RunStepsBuilder::new(
|
||||
"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 {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
let command = format!("release promote {}", get_input_expression(DESIGNATOR_INPUT_NAME));
|
||||
let mut job = plain_job_customized(&os, "Promote release", command, |step| {
|
||||
vec![step.with_id(Self::PROMOTE_STEP_ID)]
|
||||
});
|
||||
let mut job = RunStepsBuilder::new(command)
|
||||
.customize(|step| vec![step.with_id(Self::PROMOTE_STEP_ID)])
|
||||
.build_job("Promote release", os);
|
||||
self.expose_outputs(&mut job);
|
||||
job
|
||||
}
|
||||
@ -289,12 +413,7 @@ pub fn changelog() -> Result<Workflow> {
|
||||
Opened,
|
||||
Reopened,
|
||||
]));
|
||||
ret.add_job(Job {
|
||||
name: "Changelog".into(),
|
||||
runs_on: vec![RunnerLabel::X64],
|
||||
steps: setup_script_and_steps("changelog-check"),
|
||||
..default()
|
||||
});
|
||||
ret.add_job(RunStepsBuilder::new("changelog-check").build_job("Changelog", RunnerLabel::X64));
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
@ -480,7 +599,7 @@ pub fn std_libs_benchmark() -> Result<Workflow> {
|
||||
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 = WorkflowDispatchInput {
|
||||
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}")),
|
||||
);
|
||||
|
||||
let mut benchmark_job = plain_job(&BenchmarkRunner, name, cmd_line);
|
||||
benchmark_job.timeout_minutes = timeout;
|
||||
let mut benchmark_job = RunStepsBuilder::new(command_line)
|
||||
.cleaning(CleaningCondition::Always)
|
||||
.build_job(name, BenchmarkRunner);
|
||||
benchmark_job.timeout_minutes = timeout_minutes;
|
||||
workflow.add_job(benchmark_job);
|
||||
Ok(workflow)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use crate::prelude::*;
|
||||
use crate::ci_gen::runs_on;
|
||||
use crate::ci_gen::secret;
|
||||
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::Access;
|
||||
@ -24,21 +25,55 @@ use ide_ci::actions::workflow::definition::Strategy;
|
||||
/// https://github.com/electron-userland/electron-builder/issues/6865
|
||||
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> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Labels required on the runner to run this job.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fn runs_on(&self) -> Vec<RunnerLabel> {
|
||||
runs_on(*self)
|
||||
}
|
||||
fn os_name(&self) -> Option<String> {
|
||||
fn job_name_suffix(&self) -> Option<String> {
|
||||
Some(self.to_string())
|
||||
}
|
||||
}
|
||||
@ -54,28 +89,11 @@ impl RunsOn for Strategy {
|
||||
}
|
||||
|
||||
pub fn plain_job(
|
||||
runs_on_info: &impl RunsOn,
|
||||
name: impl AsRef<str>,
|
||||
command_line: impl AsRef<str>,
|
||||
runs_on: impl RunsOn,
|
||||
name: impl Into<String>,
|
||||
command_line: impl Into<String>,
|
||||
) -> Job {
|
||||
plain_job_customized(runs_on_info, name, command_line, |s| vec![s])
|
||||
}
|
||||
|
||||
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() }
|
||||
RunStepsBuilder::new(command_line).build_job(name, runs_on)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@ -102,7 +120,7 @@ impl JobArchetype for CancelWorkflow {
|
||||
pub struct Lint;
|
||||
impl JobArchetype for Lint {
|
||||
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;
|
||||
impl JobArchetype for NativeTest {
|
||||
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;
|
||||
impl JobArchetype for NewGuiTest {
|
||||
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;
|
||||
impl JobArchetype for NewGuiBuild {
|
||||
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;
|
||||
impl JobArchetype for WasmTest {
|
||||
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 {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job(
|
||||
&os,
|
||||
os,
|
||||
"IDE integration tests",
|
||||
"ide integration-test --backend-source current-ci-run",
|
||||
)
|
||||
@ -154,12 +172,10 @@ impl JobArchetype for IntegrationTest {
|
||||
pub struct BuildWasm;
|
||||
impl JobArchetype for BuildWasm {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(
|
||||
&os,
|
||||
"Build GUI (WASM)",
|
||||
"wasm build --wasm-upload-artifact ${{ runner.os == 'Linux' }}",
|
||||
|step| vec![step.with_secret_exposed(crate::env::ENSO_AG_GRID_LICENSE_KEY)],
|
||||
)
|
||||
let command = "wasm build --wasm-upload-artifact ${{ runner.os == 'Linux' }}";
|
||||
RunStepsBuilder::new(command)
|
||||
.customize(|step| vec![step.with_secret_exposed(crate::env::ENSO_AG_GRID_LICENSE_KEY)])
|
||||
.build_job("Build GUI (WASM)", os)
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +183,7 @@ impl JobArchetype for BuildWasm {
|
||||
pub struct BuildBackend;
|
||||
impl JobArchetype for BuildBackend {
|
||||
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;
|
||||
impl JobArchetype for UploadBackend {
|
||||
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;
|
||||
impl JobArchetype for DeployRuntime {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(&os, "Upload Runtime to ECR", "release deploy-runtime", |step| {
|
||||
let step = step
|
||||
.with_secret_exposed_as("CI_PRIVATE_TOKEN", "GITHUB_TOKEN")
|
||||
.with_env("crate_ECR_REPOSITORY", crate::aws::ecr::runtime::NAME)
|
||||
.with_secret_exposed_as(secret::ECR_PUSH_RUNTIME_ACCESS_KEY_ID, "AWS_ACCESS_KEY_ID")
|
||||
RunStepsBuilder::new("release deploy-runtime")
|
||||
.customize(|step| {
|
||||
vec![step
|
||||
.with_secret_exposed_as(secret::CI_PRIVATE_TOKEN, ide_ci::github::GITHUB_TOKEN)
|
||||
.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(
|
||||
secret::ECR_PUSH_RUNTIME_SECRET_ACCESS_KEY,
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
)
|
||||
.with_env("AWS_DEFAULT_REGION", crate::aws::ecr::runtime::REGION);
|
||||
vec![step]
|
||||
.with_env("AWS_DEFAULT_REGION", crate::aws::ecr::runtime::REGION)]
|
||||
})
|
||||
.build_job("Upload Runtime to ECR", os)
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,20 +222,22 @@ impl JobArchetype for DeployRuntime {
|
||||
pub struct DeployGui;
|
||||
impl JobArchetype for DeployGui {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(&os, "Upload GUI to S3", "release deploy-gui", |step| {
|
||||
let step = step
|
||||
.with_secret_exposed_as("CI_PRIVATE_TOKEN", "GITHUB_TOKEN")
|
||||
RunStepsBuilder::new("release deploy-gui")
|
||||
.customize(|step| {
|
||||
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_SECRET_ACCESS_KEY,
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
)
|
||||
.with_secret_exposed_as(secret::ENSO_ADMIN_TOKEN, crate::env::ENSO_ADMIN_TOKEN);
|
||||
vec![step]
|
||||
.with_secret_exposed_as(secret::ENSO_ADMIN_TOKEN, crate::env::ENSO_ADMIN_TOKEN)]
|
||||
})
|
||||
.build_job("Upload GUI to S3", os)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn expose_os_specific_signing_secret(os: OS, step: Step) -> Step {
|
||||
match os {
|
||||
OS::Windows => step
|
||||
@ -293,12 +315,11 @@ pub fn with_packaging_steps(os: OS) -> impl FnOnce(Step) -> Vec<Step> {
|
||||
pub struct PackageNewIde;
|
||||
impl JobArchetype for PackageNewIde {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(
|
||||
&os,
|
||||
"Package New IDE",
|
||||
RunStepsBuilder::new(
|
||||
"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;
|
||||
impl JobArchetype for CiCheckBackend {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(&os, "Engine", "backend ci-check", |main_step| {
|
||||
let main_step = main_step
|
||||
.with_secret_exposed_as(secret::ENSO_LIB_S3_AWS_REGION, crate::aws::env::AWS_REGION)
|
||||
RunStepsBuilder::new("backend ci-check")
|
||||
.customize(move |step| {
|
||||
let main_step = step
|
||||
.with_secret_exposed_as(
|
||||
secret::ENSO_LIB_S3_AWS_REGION,
|
||||
crate::aws::env::AWS_REGION,
|
||||
)
|
||||
.with_secret_exposed_as(
|
||||
secret::ENSO_LIB_S3_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)]
|
||||
})
|
||||
.build_job("Engine", os)
|
||||
.with_permission(Permission::Checks, Access::Write)
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
}
|
||||
|
||||
/// See [`checkout_repo_step_customized`].
|
||||
pub fn checkout_repo_step() -> impl IntoIterator<Item = Step> {
|
||||
checkout_repo_step_customized(identity)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user