Hyperion Kernel Build CI #36
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: Hyperion Kernel Build CI | |
| on: | |
| workflow_dispatch: | |
| concurrency: | |
| group: hyperion-kernel | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # CACHING ARCHITECTURE | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # | |
| # Layer 1 โ ccache โ ccache-<os>-<ver>-<cfg>-<patches>-<run_id> | |
| # โ โ Always saves a NEW key per run so restore-keys | |
| # โ โ always finds the freshest partial cache. | |
| # โ โ SLOPPINESS + DIRECT mode = max hit rate. | |
| # | |
| # Layer 2 โ Source tarball โ kernel-tarball-<version> | |
| # โ โ Immutable. Downloaded once, cached forever. | |
| # | |
| # Layer 3 โ 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" | |
| # โโ Dependencies โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # APT caching is intentionally omitted: GitHub runners refresh their | |
| # package index on every boot, making cached .deb archives unreliable. | |
| # The install is fast enough (~20โ30 s) that the complexity isn't worth it. | |
| - 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 \ | |
| ccache zstd | |
| # Put ccache shims first so 'gcc' resolves to ccache transparently | |
| echo "/usr/lib/ccache" >> "$GITHUB_PATH" | |
| # โโ Layer 1: 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 2: 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: | | |
| curl -fsSL --output "linux-${FULL_VERSION}.tar.xz" \ | |
| "https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION}.x/linux-${FULL_VERSION}.tar.xz" | |
| curl -fsSL "https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION}.x/sha256sums.asc" \ | |
| | grep "linux-${FULL_VERSION}.tar.xz" | sha256sum -c - | |
| # โโ Layer 3: 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 | |
| # bzImage only: modules are not packaged so skip the modules target to | |
| # avoid spending ~30โ60 % of build time on unused output. | |
| # 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 | |
| # โโ ccache report โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| - name: ccache statistics | |
| if: always() | |
| run: ccache --show-stats --verbose | |
| # โโ Layer 3: 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 1: 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 | |