Skip to content

Hyperion Kernel Build CI #24

Hyperion Kernel Build CI

Hyperion Kernel Build CI #24

Workflow file for this run

name: Hyperion Kernel Build CI
on:
workflow_dispatch:
concurrency:
group: hyperion-kernel
cancel-in-progress: false
permissions:
contents: write
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# CACHING ARCHITECTURE
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
#
# Layer 1 โ”‚ APT packages โ”‚ apt-kernel-build-v1
# โ”‚ โ”‚ Saves ~30s of apt-get install per run
#
# Layer 2 โ”‚ ccache โ”‚ ccache-<os>-<ver>-<cfg>-<patches>-<run_id>
# โ”‚ โ”‚ KEY BUG FIX: always save a NEW key per run
# โ”‚ โ”‚ so restore-keys always finds the freshest
# โ”‚ โ”‚ partial cache, never a stale pinned key.
# โ”‚ โ”‚ SLOPPINESS + DIRECT mode = max hit rate.
#
# Layer 3 โ”‚ Source tarball โ”‚ kernel-tarball-<version>
# โ”‚ โ”‚ Immutable. Downloaded once, cached forever.
#
# Layer 4 โ”‚ Full build tree โ”‚ kernel-tree-<ver>-<cfg>-<patches>
# โ”‚ โ”‚ Exact match = zero rebuild (incremental make).
# โ”‚ โ”‚ Partial match via restore-keys still helps.
# โ”‚ โ”‚ Saved unconditionally after every build.
#
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
jobs:
validate-config:
runs-on: ubuntu-latest
outputs:
VERSION: ${{ steps.parse.outputs.VERSION }}
FULL_VERSION: ${{ steps.parse.outputs.FULL_VERSION }}
TAG: ${{ steps.parse.outputs.TAG }}
HYPERION_VER: ${{ steps.parse.outputs.HYPERION_VER }}
CONFIG_HASH: ${{ steps.parse.outputs.CONFIG_HASH }}
PATCHES_HASH: ${{ steps.parse.outputs.PATCHES_HASH }}
steps:
- uses: actions/checkout@v4
- name: Parse hyperion.config
id: parse
run: |
set -euo pipefail
python3 << 'EOF'
import os, hashlib, glob
config = {}
with open("hyperion.config") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"): continue
k, v = line.split("=", 1)
config[k.strip()] = v.strip().strip('"')
VERSION = int(config.get("CONFIG_VERSION", 0))
PATCH = int(config.get("CONFIG_PATCHLEVEL", 0))
SUB = int(config.get("CONFIG_SUBLEVEL", 0))
FULL_VERSION = f"{VERSION}.{PATCH}" if SUB == 0 else f"{VERSION}.{PATCH}.{SUB}"
LOCAL = config.get("CONFIG_LOCALVERSION", "")
HYPERION_VER = LOCAL.split("-Hyperion-", 1)[-1] if "-Hyperion-" in LOCAL else LOCAL.lstrip("-")
TAG = HYPERION_VER
CONFIG_HASH = hashlib.sha256(open("hyperion.config", "rb").read()).hexdigest()[:12]
patches = sorted(glob.glob("patches/*.patch"))
PATCHES_HASH = hashlib.sha256(b"".join(open(p, "rb").read() for p in patches)).hexdigest()[:12] if patches else "none"
out = os.environ["GITHUB_OUTPUT"]
with open(out, "a") as f:
for k, v in {
"VERSION": VERSION, "FULL_VERSION": FULL_VERSION,
"TAG": TAG, "HYPERION_VER": HYPERION_VER,
"CONFIG_HASH": CONFIG_HASH, "PATCHES_HASH": PATCHES_HASH,
}.items():
f.write(f"{k}={v}\n")
EOF
build-kernel:
needs: validate-config
runs-on: ubuntu-latest
env:
KERNEL_VERSION: ${{ needs.validate-config.outputs.VERSION }}
FULL_VERSION: ${{ needs.validate-config.outputs.FULL_VERSION }}
TAG_NAME: ${{ needs.validate-config.outputs.TAG }}
HYPERION_VER: ${{ needs.validate-config.outputs.HYPERION_VER }}
CONFIG_HASH: ${{ needs.validate-config.outputs.CONFIG_HASH }}
PATCHES_HASH: ${{ needs.validate-config.outputs.PATCHES_HASH }}
# โ”€โ”€ Reproducible build identity โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
KBUILD_BUILD_USER: Soumalya
KBUILD_BUILD_HOST: github-runner
# KBUILD_BUILD_TIMESTAMP is set dynamically from git history below
# โ”€โ”€ ccache โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 8G
# Compression: on, level 6 (good balance of speed vs size)
CCACHE_COMPRESS: 1
CCACHE_COMPRESSLEVEL: 6
# Direct mode: skip preprocessing step, cache by hash of source + headers
CCACHE_DIRECT: 1
# Basedir strips the workspace prefix from paths in the cache, so the
# cache is portable across different runner checkout paths
CCACHE_BASEDIR: ${{ github.workspace }}
CCACHE_NOHASHDIR: 1
# Sloppiness: critical for kernel builds
# time_macros โ€” ignore __DATE__ / __TIME__ (we fix the timestamp anyway)
# include_file_mtime โ€” don't invalidate on header mtime, only content
# include_file_ctime โ€” same for ctime
# file_stat_matches โ€” use stat() instead of content hash for unchanged files
# pch_defines โ€” handle precompiled-header define changes gracefully
CCACHE_SLOPPINESS: time_macros,include_file_mtime,include_file_ctime,file_stat_matches,pch_defines
steps:
- uses: actions/checkout@v4
# โ”€โ”€ Layer 0: Reproducible timestamps โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Pin KBUILD_BUILD_TIMESTAMP to the last git commit so every build of
# the same source tree produces the same byte-for-byte object files,
# which is what lets ccache achieve high hit rates.
- name: Pin build timestamps to last commit
run: |
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" >> "$GITHUB_ENV"
echo "KBUILD_BUILD_TIMESTAMP=$(date -u -d "@$SOURCE_DATE_EPOCH" '+%a %b %e %H:%M:%S UTC %Y')" >> "$GITHUB_ENV"
echo "KBUILD_BUILD_VERSION=${GITHUB_RUN_NUMBER}" >> "$GITHUB_ENV"
# โ”€โ”€ Layer 1: APT cache โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
- name: Restore APT cache
id: apt-cache
uses: actions/cache@v4
with:
path: |
/var/cache/apt/archives/*.deb
/var/lib/apt/lists
key: apt-kernel-build-v1
restore-keys: apt-kernel-build-
- name: Install build dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
build-essential bc bison flex \
libssl-dev libelf-dev libncurses-dev \
dwarves rsync cpio wget \
ccache zstd ca-certificates
# Put ccache shims first so 'gcc' resolves to ccache transparently
echo "/usr/lib/ccache" >> "$GITHUB_PATH"
# โ”€โ”€ Layer 2: ccache (restore) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Use cache/restore (not cache) so we can save a fresh key after build.
# Primary key includes run_id so we ALWAYS write a new entry, and
# restore-keys fan out from most- to least-specific for the best warmup.
- name: Restore ccache
id: ccache-restore
uses: actions/cache/restore@v4
with:
path: .ccache
key: ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}-${{ github.run_id }}
restore-keys: |
ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}-
ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-
ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-
ccache-${{ runner.os }}-
- name: Zero ccache stats (for clean per-run reporting)
run: ccache --zero-stats
# โ”€โ”€ Layer 3: Source tarball โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
- name: Restore kernel source tarball
id: kernel-tarball
uses: actions/cache@v4
with:
path: linux-${{ env.FULL_VERSION }}.tar.xz
key: kernel-tarball-${{ env.FULL_VERSION }}
- name: Download kernel tarball
if: steps.kernel-tarball.outputs.cache-hit != 'true'
run: |
wget -q --show-progress \
"https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION}.x/linux-${FULL_VERSION}.tar.xz" \
"https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION}.x/sha256sums.asc"
grep "linux-${FULL_VERSION}.tar.xz" sha256sums.asc | sha256sum -c -
# โ”€โ”€ Layer 4: Build tree (restore) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Exact hit = incremental make (only changed files recompile).
# Partial hit = more ccache warmup, still much faster than cold.
- name: Restore build tree
id: build-tree
uses: actions/cache/restore@v4
with:
path: kernel
key: kernel-tree-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}
restore-keys: |
kernel-tree-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-
kernel-tree-${{ env.FULL_VERSION }}-
# โ”€โ”€ Source extraction โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Only run if we have no usable build tree at all.
# Multi-threaded xz decompression cuts extraction time ~4ร—.
- name: Extract kernel source
if: steps.build-tree.outputs.cache-hit != 'true'
run: |
rm -rf kernel && mkdir kernel
XZ_OPT="-T0" tar -xf "linux-${FULL_VERSION}.tar.xz" \
-C kernel --strip-components=1
# โ”€โ”€ Configure โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# LOCALVERSION passed to olddefconfig so it's baked into autoconf.h,
# keeping the value consistent across configure + build (ccache needs
# the same preprocessed output both times).
- name: Configure kernel
working-directory: kernel
run: |
cp ../hyperion.config .config
make olddefconfig LOCALVERSION="-Hyperion-${HYPERION_VER}"
# โ”€โ”€ Patches โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
- name: Apply patches
working-directory: kernel
run: |
shopt -s nullglob
for patch in ../patches/*.patch; do
echo " โ†’ applying $(basename "$patch")"
patch -p1 < "$patch"
done
# โ”€โ”€ Build โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# -pipe: use pipes instead of temp files between compiler passes
# LOCALVERSION: must match what was passed to olddefconfig above
- name: Build kernel
working-directory: kernel
run: |
make -j$(nproc) \
CC="ccache gcc" \
LOCALVERSION="-Hyperion-${HYPERION_VER}" \
KCFLAGS="-pipe" \
bzImage modules
# โ”€โ”€ ccache report โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
- name: ccache statistics
if: always()
run: ccache --show-stats --verbose
# โ”€โ”€ Layer 4: Build tree (save) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Always save so the next run gets the freshest incremental state.
# If key already exists GitHub skips the upload silently.
- name: Save build tree
if: success()
uses: actions/cache/save@v4
with:
path: kernel
key: kernel-tree-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}
# โ”€โ”€ Layer 2: ccache (save) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# Save even on build failure so a partial run still warms future runs.
- name: Save ccache
if: always()
uses: actions/cache/save@v4
with:
path: .ccache
key: ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}-${{ github.run_id }}
# โ”€โ”€ Package โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# vmlinux is excluded: it's ~600 MB unstripped and rarely needed in CI.
# Add it back if you need BTF / pahole post-processing.
- name: Package artifacts
run: |
mkdir -p artifacts
cp kernel/arch/x86/boot/bzImage artifacts/
cp kernel/System.map artifacts/
cp hyperion.config artifacts/
tar --zstd -cf "Hyperion-Kernel-${FULL_VERSION}.tar.zst" artifacts/
- name: Generate checksum
run: |
sha256sum "Hyperion-Kernel-${FULL_VERSION}.tar.zst" \
> "Hyperion-Kernel-${FULL_VERSION}.sha256"
- name: Create release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG_NAME }}
files: |
Hyperion-Kernel-${{ env.FULL_VERSION }}.tar.zst
Hyperion-Kernel-${{ env.FULL_VERSION }}.sha256