Skip to content

Commit 45810e5

Browse files
authored
Add some infrastructure for keeping old CI working (#10438)
This commit is preparation for the infrastructure to be used when supporting [Wasmtime LTS releases][rfc]. The goal here is to add some automation and infrastructure to perform a weekly build of all active release branches which will file an issue on failure. This should ideally keep branches up-to-date and ensure that we don't forget to backport any fixes to older branches. Or rather when we do forget to backport fixes this'll be a reminder to go do that anyway. The general architecture here is: * A new `ci-cron-trigger.yml` workflow is added. * This new workflow runs once-a-week and runs a small script that triggers CI for all active release branches. * The main CI, `main.yml`, is updated to file an issue on failure when triggered in this fashion. While I was here I additionally removed the `schedule:` from the `main.yml` to instead fold the daily scheduling of CI runs into this new script as well. That way all our cron CI jobs are gated in workflows that require this exact repository meaning that forks won't be running cron jobs. [rfc]: bytecodealliance/rfcs#42
1 parent 5256c6b commit 45810e5

File tree

3 files changed

+149
-5
lines changed

3 files changed

+149
-5
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: "Cron triggers for CI"
2+
on:
3+
schedule:
4+
# “At 02:34 on Monday.”
5+
#
6+
# https://crontab.guru/#34_2_*_*_1
7+
#
8+
# This is used to perform a weekly run of CI for all release branches,
9+
# ideally in off-work-hours to not clog up the queue.
10+
- cron: '34 2 * * 1'
11+
12+
# "At 02:34 on Sunday and every day-of-week from Tuesday through Saturday"
13+
#
14+
# https://crontab.guru/#34_2_*_*_0,2-6
15+
#
16+
# This is used to perform a daily run of CI for the `main` branch to prime
17+
# caches for github actions and the merge queue. Note that this frequency
18+
# doesn't overlap the above schedule to avoid triggering two builds on the
19+
# same day.
20+
- cron: '34 2 * * 0,2-6'
21+
22+
# Allow manually triggering this request via a button
23+
workflow_dispatch:
24+
25+
permissions:
26+
issues: write
27+
actions: write
28+
29+
jobs:
30+
run:
31+
if: "github.repository == 'bytecodealliance/wasmtime' || !github.event.schedule"
32+
name: Trigger release branch CI
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: actions/checkout@v4
36+
with:
37+
submodules: true
38+
fetch-depth: 0
39+
40+
# Always trigger a CI run on the `main` branch to prime GHA caches.
41+
- run: gh workflow run main.yml --ref main
42+
name: Trigger main branch CI daily
43+
env:
44+
GH_TOKEN: ${{ github.token }}
45+
46+
# If this is a once-a-week run then additionally trigger CI for release
47+
# branches to ensure that the CI there is kept up-to-date.
48+
- run: rustc ci/trigger-release-branch-ci.rs
49+
- run: ./trigger-release-branch-ci
50+
name: Trigger release branch CI weekly
51+
env:
52+
GH_TOKEN: ${{ github.token }}
53+
if: github.event.schedule == '34 2 * * 1'

.github/workflows/main.yml

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@ on:
99
- main
1010
- 'release-*'
1111

12-
# Run full CI on the `main` branch once a day to prime the GitHub Actions
13-
# caches used by PRs and the merge queue.
14-
schedule:
15-
- cron: '13 4 * * *'
16-
1712
# This is the CI that runs for PRs-to-merge.
1813
merge_group:
1914

@@ -26,6 +21,9 @@ on:
2621
# uploads as well as publication to crates.io.
2722
- 'release-*'
2823

24+
# Allow manually triggering this request via a button or another workflow.
25+
workflow_dispatch:
26+
2927
defaults:
3028
run:
3129
shell: bash
@@ -1274,3 +1272,29 @@ jobs:
12741272
}
12751273
EOF
12761274
if: steps.tag.outputs.push_tag == 'yes'
1275+
1276+
# File an issue on the repo if this run failed and was triggered via
1277+
# `workflow_dispatch`, which mostly means that
1278+
# `.github/workflows/trigger-release-branch-ci.yml` will file issues on
1279+
# failure so we get to see a notification when a build fails for a historical
1280+
# release branch.
1281+
file-issue-on-error:
1282+
name: File an issue if this build failed and was cron-triggered
1283+
runs-on: ubuntu-latest
1284+
needs: ci-status
1285+
if: |
1286+
always()
1287+
&& needs.ci-status.result != 'success'
1288+
&& github.event_name == 'workflow_dispatch'
1289+
permissions:
1290+
issues: write
1291+
steps:
1292+
- uses: actions/github-script@v7
1293+
with:
1294+
script: |
1295+
github.rest.issues.create({
1296+
owner: context.repo.owner,
1297+
repo: context.repo.repo,
1298+
title: `Failed CI build for ${context.ref}`,
1299+
body: `See https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
1300+
})

ci/trigger-release-branch-ci.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//! Helper script used by `.github/workflows/ci-cron-trigger.yml`
2+
3+
use std::process::Command;
4+
5+
fn main() {
6+
let output = Command::new("git")
7+
.arg("for-each-ref")
8+
.arg("refs/remotes/origin")
9+
.arg("--format")
10+
.arg("%(refname)")
11+
.output()
12+
.unwrap();
13+
assert!(output.status.success());
14+
let mut releases = std::str::from_utf8(&output.stdout)
15+
.unwrap()
16+
.lines()
17+
.filter_map(|l| l.strip_prefix("refs/remotes/origin/release-"))
18+
.filter_map(|l| {
19+
let mut parts = l.split('.');
20+
let major = parts.next()?.parse::<u32>().ok()?;
21+
let minor = parts.next()?.parse::<u32>().ok()?;
22+
let patch = parts.next()?.parse::<u32>().ok()?;
23+
Some((major, minor, patch))
24+
})
25+
.collect::<Vec<_>>();
26+
releases.sort();
27+
28+
let mut to_trigger: Vec<(u32, u32, u32)> = Vec::new();
29+
let mut iter = releases.iter().rev();
30+
31+
// Pick the latest 3 release branches to keep up-to-date. Although we
32+
// only promise the last 2 are going to be released with security fixes when
33+
// a new release branch is made that means there's one "pending" release
34+
// branch and two "active" release branches. In that situation we want to
35+
// update 3 branches. If there's no "pending" branch then we'll just be
36+
// keeping some older branch's CI working, which shouldn't be too hard.
37+
to_trigger.extend(iter.by_ref().take(3));
38+
39+
// We support two LTS channels 12 versions apart. If one is already included
40+
// in the above set of 3 latest releases, however, then we're only picking
41+
// one historical LTS release.
42+
let mut lts_channels = 2;
43+
if to_trigger.iter().any(|(major, _, _)| *major % 12 == 0) {
44+
lts_channels -= 1;
45+
}
46+
47+
// Look for LTS releases, defined by every-12-versions which are after v24.
48+
to_trigger.extend(
49+
iter.filter(|(major, _, _)| *major % 12 == 0 && *major > 20)
50+
.take(lts_channels),
51+
);
52+
53+
println!("{to_trigger:?}");
54+
55+
for (major, minor, patch) in to_trigger {
56+
dbg!(major, minor, patch);
57+
let status = Command::new("gh")
58+
.arg("workflow")
59+
.arg("run")
60+
.arg("main.yml")
61+
.arg("--ref")
62+
.arg(format!("release-{major}.{minor}.{patch}"))
63+
.status()
64+
.unwrap();
65+
assert!(status.success());
66+
}
67+
}

0 commit comments

Comments
 (0)