Check for a new stable kernel release #45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Check for a new stable kernel release | |
| on: | |
| schedule: | |
| - cron: "0 */6 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| force: | |
| description: > | |
| Skip the "already have a config"/"already have an open PR" checks | |
| and generate configs even if this version was already handled. | |
| For testing the pipeline itself. | |
| required: false | |
| default: false | |
| type: boolean | |
| kernel_version_override: | |
| description: > | |
| Test the whole pipeline (generate, diff, branch, PR, comments) | |
| against this exact kernel version instead of whatever | |
| releases.json currently calls "stable". Use any real, already | |
| released version -- it doesn't have to be the current latest, just | |
| a real signed tag on the stable tree, since this only affects | |
| which version gets processed, not how. Leave empty for normal | |
| (scheduled) behavior. | |
| required: false | |
| default: "" | |
| type: string | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: check-new-stable-release | |
| cancel-in-progress: false | |
| jobs: | |
| check-and-generate: | |
| name: Check for a new release and generate its configs | |
| runs-on: ubuntu-latest | |
| steps: | |
| # persist-credentials stays at its default (true) here, unlike the | |
| # other workflows -- "Create branch and commit..." below pushes with | |
| # plain `git push`, which relies on actions/checkout's persisted | |
| # credential helper rather than an explicit token. | |
| - name: Check out | |
| # zizmor: ignore[artipacked] | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| submodules: recursive | |
| # www.kernel.org/releases.json's single "stable" moniker entry is | |
| # exactly kernel.org's own notion of "the current latest stable | |
| # release" -- the same one shown as "latest stable" on the site's front | |
| # page. There's also "longterm" (multiple concurrently supported LTS | |
| # branches) and "mainline" (dev), but this workflow only follows the | |
| # one "stable" line, not every branch this repo has a misc/<series>/ | |
| # reference for. | |
| - name: Determine the latest stable kernel version | |
| id: latest | |
| env: | |
| KERNEL_VERSION_OVERRIDE: ${{ inputs.kernel_version_override }} | |
| run: | | |
| set -euo pipefail | |
| if [ -n "$KERNEL_VERSION_OVERRIDE" ]; then | |
| version="$KERNEL_VERSION_OVERRIDE" | |
| echo "Using override version: $version (not consulting releases.json)" | |
| else | |
| version="$(curl -fsSL https://www.kernel.org/releases.json \ | |
| | jq -r '.releases[] | select(.moniker=="stable") | .version')" | |
| if [ -z "$version" ] || [ "$version" = "null" ]; then | |
| echo "error: could not determine the latest stable version from releases.json" >&2 | |
| exit 1 | |
| fi | |
| echo "Latest stable: $version" | |
| fi | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| - name: Check whether we already have configs for this version | |
| id: existing | |
| env: | |
| FORCE: ${{ inputs.force }} | |
| VERSION: ${{ steps.latest.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$FORCE" != "true" ] && [ -d "configs/$VERSION" ]; then | |
| echo "configs/$VERSION already exists -- nothing to do" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Check whether a PR for this version is already open | |
| id: existing_pr | |
| if: steps.existing.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| FORCE: ${{ inputs.force }} | |
| VERSION: ${{ steps.latest.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| branch="kernel-config/v$VERSION" | |
| if [ "$FORCE" != "true" ]; then | |
| count="$(gh pr list --state open --head "$branch" --json number --jq 'length')" | |
| if [ "$count" -gt 0 ]; then | |
| echo "An open PR for $branch already exists -- nothing to do" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| fi | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| - name: Install kernel build dependencies | |
| if: steps.existing.outputs.skip != 'true' && steps.existing_pr.outputs.skip != 'true' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends \ | |
| build-essential flex bison bc libelf-dev libssl-dev dwarves \ | |
| python3 gnupg xz-utils | |
| # Config generation only needs the Kconfig tree on disk, not git | |
| # history -- unlike builds.yml (which needs an actual git tree because | |
| # `make deb-pkg` shells out to git for versioning), a plain verified | |
| # tarball extraction is enough and much lighter than a full stable-tree | |
| # clone. | |
| - name: Download and verify the kernel source tree | |
| if: steps.existing.outputs.skip != 'true' && steps.existing_pr.outputs.skip != 'true' | |
| env: | |
| TARGETDIR: ${{ runner.temp }}/kernel-dl | |
| GNUPGHOME: ${{ runner.temp }}/gnupg | |
| VERSION: ${{ steps.latest.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$TARGETDIR" | |
| mkdir -p "$GNUPGHOME" | |
| chmod 0700 "$GNUPGHOME" | |
| ./scripts/get-verified-tarball "$VERSION" | |
| mkdir -p ~/kernel | |
| tar -xf "$TARGETDIR/linux-$VERSION.tar.xz" -C ~/kernel | |
| test -f ~/kernel/linux-"$VERSION"/Makefile | |
| - name: Create .env | |
| if: steps.existing.outputs.skip != 'true' && steps.existing_pr.outputs.skip != 'true' | |
| env: | |
| VERSION: ${{ steps.latest.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| sed -e "s|^KERNEL_TREE_PATH=.*|KERNEL_TREE_PATH=$HOME/kernel/linux-$VERSION|" \ | |
| -e "s|^KERNEL_TREE_BUILD_PATH=.*|KERNEL_TREE_BUILD_PATH=${RUNNER_TEMP}/kernel-build|" \ | |
| .env.example > .env | |
| - name: Determine flavors | |
| if: steps.existing.outputs.skip != 'true' && steps.existing_pr.outputs.skip != 'true' | |
| id: flavors | |
| run: | | |
| set -euo pipefail | |
| flavors="$(find flavors -mindepth 1 -maxdepth 1 -type d -printf '%f ')" | |
| echo "Flavors: $flavors" | |
| echo "list=$flavors" >> "$GITHUB_OUTPUT" | |
| # --no-validate: this is tracking version-to-version drift, not | |
| # fidelity against misc/<series>/zabbly-config (which may not even | |
| # exist yet for a brand new series, or may be stale relative to this | |
| # exact point release either way). | |
| - name: Generate configs for every flavor | |
| if: steps.existing.outputs.skip != 'true' && steps.existing_pr.outputs.skip != 'true' | |
| env: | |
| VERSION: ${{ steps.latest.outputs.version }} | |
| FLAVORS: ${{ steps.flavors.outputs.list }} | |
| run: | | |
| set -euo pipefail | |
| outdir="configs/$VERSION" | |
| mkdir -p "$outdir" | |
| for flavor in $FLAVORS; do | |
| ./check_slices.py "$flavor" | |
| ./genconfig.sh "$flavor" --normalize --no-validate | |
| if [ "$flavor" = "generic" ]; then | |
| src="generated_config" | |
| else | |
| src="generated_config-$flavor" | |
| fi | |
| cp "$src" "$outdir/${flavor}-config" | |
| cp "${src}-defconfig" "$outdir/${flavor}-config-defconfig" | |
| done | |
| # Both forms matter enough to show in full: the defconfig (minimal) | |
| # diff is what actually changed with dependencies/defaults stripped | |
| # out, but the full config is the one that's actually shipped, and a | |
| # defconfig-only diff can hide a real difference if something's | |
| # default value itself moved between kernel versions. | |
| - name: Compute diffs against the previous tracked version | |
| if: steps.existing.outputs.skip != 'true' && steps.existing_pr.outputs.skip != 'true' | |
| id: diffs | |
| env: | |
| VERSION: ${{ steps.latest.outputs.version }} | |
| FLAVORS: ${{ steps.flavors.outputs.list }} | |
| run: | | |
| set -euo pipefail | |
| prev="$(find configs -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | grep -vF "$VERSION" | sort -V | tail -1 || true)" | |
| echo "Previous tracked version: ${prev:-(none)}" | |
| echo "prev=$prev" >> "$GITHUB_OUTPUT" | |
| mkdir -p "${RUNNER_TEMP}/diffs" | |
| for flavor in $FLAVORS; do | |
| for kind in config config-defconfig; do | |
| new="configs/$VERSION/${flavor}-${kind}" | |
| old="configs/${prev}/${flavor}-${kind}" | |
| out="${RUNNER_TEMP}/diffs/${flavor}.${kind}.diff" | |
| if [ -n "$prev" ] && [ -f "$old" ]; then | |
| diff -u "$old" "$new" > "$out" || true | |
| else | |
| echo "(no previous tracked version to diff against)" > "$out" | |
| fi | |
| done | |
| done | |
| - name: Create branch and commit the generated configs | |
| if: steps.existing.outputs.skip != 'true' && steps.existing_pr.outputs.skip != 'true' | |
| env: | |
| GIT_AUTHOR_NAME: github-actions[bot] | |
| GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_COMMITTER_NAME: github-actions[bot] | |
| GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com | |
| VERSION: ${{ steps.latest.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| branch="kernel-config/v$VERSION" | |
| git checkout -b "$branch" | |
| git add "configs/$VERSION" | |
| git commit -m "configs: add generated configs for Linux $VERSION" | |
| git push origin "$branch" --force-with-lease | |
| - name: Open the PR | |
| if: steps.existing.outputs.skip != 'true' && steps.existing_pr.outputs.skip != 'true' | |
| id: pr | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| VERSION: ${{ steps.latest.outputs.version }} | |
| PREV: ${{ steps.diffs.outputs.prev }} | |
| run: | | |
| set -euo pipefail | |
| branch="kernel-config/v$VERSION" | |
| prev="$PREV" | |
| body="Automatically generated configs for Linux $VERSION" | |
| if [ -n "$prev" ]; then | |
| body="$body, diffed against the previously tracked $prev." | |
| fi | |
| body="$body Per-flavor diffs follow as comments below." | |
| url="$(gh pr create \ | |
| --title "configs: Linux $VERSION" \ | |
| --body "$body" \ | |
| --head "$branch" \ | |
| --base main)" | |
| echo "url=$url" >> "$GITHUB_OUTPUT" | |
| # Each diff goes inside its own <details> spoiler -- GitHub renders | |
| # that as collapsible, which matters here since a full-config diff can | |
| # run to thousands of lines. Note the blank line right after | |
| # <summary>: GitHub's markdown renderer needs it to treat what follows | |
| # as markdown (and so render the ```diff fence as a real code block) | |
| # rather than as a literal HTML blob. | |
| - name: Post per-flavor diffs as PR comments | |
| if: steps.existing.outputs.skip != 'true' && steps.existing_pr.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| FLAVORS: ${{ steps.flavors.outputs.list }} | |
| PREV: ${{ steps.diffs.outputs.prev }} | |
| PR_URL: ${{ steps.pr.outputs.url }} | |
| run: | | |
| set -euo pipefail | |
| for flavor in $FLAVORS; do | |
| { | |
| echo "### $flavor: diff vs $PREV" | |
| echo | |
| echo "<details><summary>Full config diff</summary>" | |
| echo | |
| echo '```diff' | |
| cat "${RUNNER_TEMP}/diffs/${flavor}.config.diff" | |
| echo '```' | |
| echo | |
| echo "</details>" | |
| echo | |
| echo "<details><summary>Minimal (defconfig) diff</summary>" | |
| echo | |
| echo '```diff' | |
| cat "${RUNNER_TEMP}/diffs/${flavor}.config-defconfig.diff" | |
| echo '```' | |
| echo | |
| echo "</details>" | |
| } | gh pr comment "$PR_URL" --body-file - | |
| done |