@@ -10,16 +10,39 @@ concurrency:
1010permissions :
1111 contents : write
1212
13+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
14+ # CACHING ARCHITECTURE
15+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
16+ #
17+ # Layer 1 โ APT packages โ apt-kernel-build-v1
18+ # โ โ Saves ~30s of apt-get install per run
19+ #
20+ # Layer 2 โ ccache โ ccache-<os>-<ver>-<cfg>-<patches>-<run_id>
21+ # โ โ KEY BUG FIX: always save a NEW key per run
22+ # โ โ so restore-keys always finds the freshest
23+ # โ โ partial cache, never a stale pinned key.
24+ # โ โ SLOPPINESS + DIRECT mode = max hit rate.
25+ #
26+ # Layer 3 โ Source tarball โ kernel-tarball-<version>
27+ # โ โ Immutable. Downloaded once, cached forever.
28+ #
29+ # Layer 4 โ Full build tree โ kernel-tree-<ver>-<cfg>-<patches>
30+ # โ โ Exact match = zero rebuild (incremental make).
31+ # โ โ Partial match via restore-keys still helps.
32+ # โ โ Saved unconditionally after every build.
33+ #
34+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
35+
1336jobs :
1437
1538 validate-config :
1639 runs-on : ubuntu-latest
1740 outputs :
18- VERSION : ${{ steps.parse.outputs.VERSION }}
41+ VERSION : ${{ steps.parse.outputs.VERSION }}
1942 FULL_VERSION : ${{ steps.parse.outputs.FULL_VERSION }}
20- TAG : ${{ steps.parse.outputs.TAG }}
43+ TAG : ${{ steps.parse.outputs.TAG }}
2144 HYPERION_VER : ${{ steps.parse.outputs.HYPERION_VER }}
22- CONFIG_HASH : ${{ steps.parse.outputs.CONFIG_HASH }}
45+ CONFIG_HASH : ${{ steps.parse.outputs.CONFIG_HASH }}
2346 PATCHES_HASH : ${{ steps.parse.outputs.PATCHES_HASH }}
2447
2548 steps :
@@ -31,160 +54,237 @@ jobs:
3154 set -euo pipefail
3255 python3 << 'EOF'
3356 import os, hashlib, glob
34- config= {}
57+ config = {}
3558 with open("hyperion.config") as f:
3659 for line in f:
37- line= line.strip()
60+ line = line.strip()
3861 if not line or line.startswith("#"): continue
39- k,v=line.split("=",1)
40- config[k.strip()]=v.strip().strip('"')
41- VERSION=int(config.get("CONFIG_VERSION",0))
42- PATCH=int(config.get("CONFIG_PATCHLEVEL",0))
43- SUB=int(config.get("CONFIG_SUBLEVEL",0))
44- FULL_VERSION=f"{VERSION}.{PATCH}" if SUB==0 else f"{VERSION}.{PATCH}.{SUB}"
45- LOCAL=config.get("CONFIG_LOCALVERSION","")
46- HYPERION_VER=LOCAL.split("-Hyperion-",1)[-1] if "-Hyperion-" in LOCAL else LOCAL.lstrip("-")
47- TAG=HYPERION_VER
48- CONFIG_HASH=hashlib.sha256(open("hyperion.config","rb").read()).hexdigest()[:12]
49- patches=sorted(glob.glob("patches/*.patch"))
50- PATCHES_HASH=hashlib.sha256(b''.join(open(p,"rb").read() for p in patches)).hexdigest()[:12] if patches else "none"
51- out=os.environ["GITHUB_OUTPUT"]
52- 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():
53- open(out,"a").write(f"{k}={v}\n")
62+ k, v = line.split("=", 1)
63+ config[k.strip()] = v.strip().strip('"')
64+ VERSION = int(config.get("CONFIG_VERSION", 0))
65+ PATCH = int(config.get("CONFIG_PATCHLEVEL", 0))
66+ SUB = int(config.get("CONFIG_SUBLEVEL", 0))
67+ FULL_VERSION = f"{VERSION}.{PATCH}" if SUB == 0 else f"{VERSION}.{PATCH}.{SUB}"
68+ LOCAL = config.get("CONFIG_LOCALVERSION", "")
69+ HYPERION_VER = LOCAL.split("-Hyperion-", 1)[-1] if "-Hyperion-" in LOCAL else LOCAL.lstrip("-")
70+ TAG = HYPERION_VER
71+ CONFIG_HASH = hashlib.sha256(open("hyperion.config", "rb").read()).hexdigest()[:12]
72+ patches = sorted(glob.glob("patches/*.patch"))
73+ PATCHES_HASH = hashlib.sha256(b"".join(open(p, "rb").read() for p in patches)).hexdigest()[:12] if patches else "none"
74+ out = os.environ["GITHUB_OUTPUT"]
75+ with open(out, "a") as f:
76+ for k, v in {
77+ "VERSION": VERSION, "FULL_VERSION": FULL_VERSION,
78+ "TAG": TAG, "HYPERION_VER": HYPERION_VER,
79+ "CONFIG_HASH": CONFIG_HASH, "PATCHES_HASH": PATCHES_HASH,
80+ }.items():
81+ f.write(f"{k}={v}\n")
5482 EOF
5583
84+
5685 build-kernel :
5786 needs : validate-config
5887 runs-on : ubuntu-latest
88+
5989 env :
6090 KERNEL_VERSION : ${{ needs.validate-config.outputs.VERSION }}
61- FULL_VERSION : ${{ needs.validate-config.outputs.FULL_VERSION }}
62- TAG_NAME : ${{ needs.validate-config.outputs.TAG }}
63- HYPERION_VER : ${{ needs.validate-config.outputs.HYPERION_VER }}
64- CONFIG_HASH : ${{ needs.validate-config.outputs.CONFIG_HASH }}
65- PATCHES_HASH : ${{ needs.validate-config.outputs.PATCHES_HASH }}
91+ FULL_VERSION : ${{ needs.validate-config.outputs.FULL_VERSION }}
92+ TAG_NAME : ${{ needs.validate-config.outputs.TAG }}
93+ HYPERION_VER : ${{ needs.validate-config.outputs.HYPERION_VER }}
94+ CONFIG_HASH : ${{ needs.validate-config.outputs.CONFIG_HASH }}
95+ PATCHES_HASH : ${{ needs.validate-config.outputs.PATCHES_HASH }}
96+
97+ # โโ Reproducible build identity โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
6698 KBUILD_BUILD_USER : Soumalya
6799 KBUILD_BUILD_HOST : github-runner
68- CCACHE_DIR : ${{ github.workspace }}/.ccache
69- CCACHE_MAXSIZE : 5G
70- CCACHE_COMPRESS : true
71- CCACHE_BASEDIR : ${{ github.workspace }}
72- CCACHE_NOHASHDIR : true
100+ # KBUILD_BUILD_TIMESTAMP is set dynamically from git history below
101+
102+ # โโ ccache โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
103+ CCACHE_DIR : ${{ github.workspace }}/.ccache
104+ CCACHE_MAXSIZE : 8G
105+ # Compression: on, level 6 (good balance of speed vs size)
106+ CCACHE_COMPRESS : 1
107+ CCACHE_COMPRESSLEVEL : 6
108+ # Direct mode: skip preprocessing step, cache by hash of source + headers
109+ CCACHE_DIRECT : 1
110+ # Basedir strips the workspace prefix from paths in the cache, so the
111+ # cache is portable across different runner checkout paths
112+ CCACHE_BASEDIR : ${{ github.workspace }}
113+ CCACHE_NOHASHDIR : 1
114+ # Sloppiness: critical for kernel builds
115+ # time_macros โ ignore __DATE__ / __TIME__ (we fix the timestamp anyway)
116+ # include_file_mtime โ don't invalidate on header mtime, only content
117+ # include_file_ctime โ same for ctime
118+ # file_stat_matches โ use stat() instead of content hash for unchanged files
119+ # pch_defines โ handle precompiled-header define changes gracefully
120+ CCACHE_SLOPPINESS : time_macros,include_file_mtime,include_file_ctime,file_stat_matches,pch_defines
73121
74122 steps :
75123
76124 - uses : actions/checkout@v4
77125
78- - name : Generate reproducible timestamp
126+ # โโ Layer 0: Reproducible timestamps โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
127+ # Pin KBUILD_BUILD_TIMESTAMP to the last git commit so every build of
128+ # the same source tree produces the same byte-for-byte object files,
129+ # which is what lets ccache achieve high hit rates.
130+ - name : Pin build timestamps to last commit
79131 run : |
80132 SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
81- echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" >> $GITHUB_ENV
82- echo "KBUILD_BUILD_TIMESTAMP=$(date -u -d "@$SOURCE_DATE_EPOCH")" >> $GITHUB_ENV
83- echo "KBUILD_BUILD_VERSION=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
133+ echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" >> "$GITHUB_ENV"
134+ echo "KBUILD_BUILD_TIMESTAMP=$(date -u -d "@$SOURCE_DATE_EPOCH" '+%a %b %e %H:%M:%S UTC %Y')" >> "$GITHUB_ENV"
135+ echo "KBUILD_BUILD_VERSION=${GITHUB_RUN_NUMBER}" >> "$GITHUB_ENV"
136+
137+ # โโ Layer 1: APT cache โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
138+ - name : Restore APT cache
139+ id : apt-cache
140+ uses : actions/cache@v4
141+ with :
142+ path : |
143+ /var/cache/apt/archives/*.deb
144+ /var/lib/apt/lists
145+ key : apt-kernel-build-v1
146+ restore-keys : apt-kernel-build-
84147
85- - name : Install dependencies
148+ - name : Install build dependencies
86149 run : |
87- sudo apt-get update
150+ sudo apt-get update -qq
88151 sudo apt-get install -y --no-install-recommends \
89152 build-essential bc bison flex \
90153 libssl-dev libelf-dev libncurses-dev \
91- dwarves rsync cpio git wget curl \
154+ dwarves rsync cpio wget \
92155 ccache zstd ca-certificates
156+ # Put ccache shims first so 'gcc' resolves to ccache transparently
157+ echo "/usr/lib/ccache" >> "$GITHUB_PATH"
93158
94- - name : Restore compiler cache
95- id : cache-ccache
96- uses : actions/cache@v4
159+ # โโ Layer 2: ccache (restore) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
160+ # Use cache/restore (not cache) so we can save a fresh key after build.
161+ # Primary key includes run_id so we ALWAYS write a new entry, and
162+ # restore-keys fan out from most- to least-specific for the best warmup.
163+ - name : Restore ccache
164+ id : ccache-restore
165+ uses : actions/cache/restore@v4
97166 with :
98167 path : .ccache
99- key : ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}
168+ key : ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}-${{ github.run_id }}
100169 restore-keys : |
170+ ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}-
101171 ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-
102172 ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-
103173 ccache-${{ runner.os }}-
104174
175+ - name : Zero ccache stats (for clean per-run reporting)
176+ run : ccache --zero-stats
177+
178+ # โโ Layer 3: Source tarball โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
105179 - name : Restore kernel source tarball
106- id : kernel-src
180+ id : kernel-tarball
107181 uses : actions/cache@v4
108182 with :
109183 path : linux-${{ env.FULL_VERSION }}.tar.xz
110- key : kernel-src -${{ env.FULL_VERSION }}
184+ key : kernel-tarball -${{ env.FULL_VERSION }}
111185
112186 - name : Download kernel tarball
113- if : steps.kernel-src .outputs.cache-hit != 'true'
187+ if : steps.kernel-tarball .outputs.cache-hit != 'true'
114188 run : |
115- wget https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION}.x/linux-${FULL_VERSION}.tar.xz
116- wget https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION}.x/sha256sums.asc
189+ wget -q --show-progress \
190+ "https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION}.x/linux-${FULL_VERSION}.tar.xz" \
191+ "https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION}.x/sha256sums.asc"
117192 grep "linux-${FULL_VERSION}.tar.xz" sha256sums.asc | sha256sum -c -
118193
119- - name : Restore object cache
120- id : cache-obj
121- uses : actions/cache@v4
194+ # โโ Layer 4: Build tree (restore) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
195+ # Exact hit = incremental make (only changed files recompile).
196+ # Partial hit = more ccache warmup, still much faster than cold.
197+ - name : Restore build tree
198+ id : build-tree
199+ uses : actions/cache/restore@v4
122200 with :
123201 path : kernel
124- key : kernel-obj -${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}
202+ key : kernel-tree -${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}
125203 restore-keys : |
126- kernel-obj -${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-
127- kernel-obj -${{ env.FULL_VERSION }}-
204+ kernel-tree -${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-
205+ kernel-tree -${{ env.FULL_VERSION }}-
128206
129- - name : Restore module object cache
130- id : cache-mod
131- uses : actions/cache@v4
132- with :
133- path : |
134- kernel/.modlib
135- kernel/.tmp_mod
136- key : kernel-mod-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}
137- restore-keys : |
138- kernel-mod-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-
139- kernel-mod-${{ env.FULL_VERSION }}-
140-
141- - name : Extract kernel
142- if : steps.cache-obj.outputs.cache-hit != 'true'
207+ # โโ Source extraction โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
208+ # Only run if we have no usable build tree at all.
209+ # Multi-threaded xz decompression cuts extraction time ~4ร.
210+ - name : Extract kernel source
211+ if : steps.build-tree.outputs.cache-hit != 'true'
143212 run : |
144- rm -rf kernel
145- mkdir kernel
146- tar -xf linux-${FULL_VERSION}.tar.xz -C kernel --strip-components=1
213+ rm -rf kernel && mkdir kernel
214+ XZ_OPT="-T0" tar -xf "linux-${FULL_VERSION}.tar.xz" \
215+ -C kernel --strip-components=1
147216
148- - name : Apply config
217+ # โโ Configure โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
218+ # LOCALVERSION passed to olddefconfig so it's baked into autoconf.h,
219+ # keeping the value consistent across configure + build (ccache needs
220+ # the same preprocessed output both times).
221+ - name : Configure kernel
149222 working-directory : kernel
150223 run : |
151224 cp ../hyperion.config .config
152- make olddefconfig
153-
154- - name : Prepare kernel
155- working-directory : kernel
156- run : |
157- make prepare
158- make modules_prepare
225+ make olddefconfig LOCALVERSION="-Hyperion-${HYPERION_VER}"
159226
227+ # โโ Patches โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
160228 - name : Apply patches
161229 working-directory : kernel
162230 run : |
163231 shopt -s nullglob
164232 for patch in ../patches/*.patch; do
233+ echo " โ applying $(basename "$patch")"
165234 patch -p1 < "$patch"
166235 done
167236
237+ # โโ Build โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
238+ # -pipe: use pipes instead of temp files between compiler passes
239+ # LOCALVERSION: must match what was passed to olddefconfig above
168240 - name : Build kernel
169241 working-directory : kernel
170242 run : |
171243 make -j$(nproc) \
172244 CC="ccache gcc" \
173245 LOCALVERSION="-Hyperion-${HYPERION_VER}" \
246+ KCFLAGS="-pipe" \
174247 bzImage modules
175248
249+ # โโ ccache report โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
250+ - name : ccache statistics
251+ if : always()
252+ run : ccache --show-stats --verbose
253+
254+ # โโ Layer 4: Build tree (save) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
255+ # Always save so the next run gets the freshest incremental state.
256+ # If key already exists GitHub skips the upload silently.
257+ - name : Save build tree
258+ if : success()
259+ uses : actions/cache/save@v4
260+ with :
261+ path : kernel
262+ key : kernel-tree-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}
263+
264+ # โโ Layer 2: ccache (save) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
265+ # Save even on build failure so a partial run still warms future runs.
266+ - name : Save ccache
267+ if : always()
268+ uses : actions/cache/save@v4
269+ with :
270+ path : .ccache
271+ key : ccache-${{ runner.os }}-${{ env.FULL_VERSION }}-${{ env.CONFIG_HASH }}-${{ env.PATCHES_HASH }}-${{ github.run_id }}
272+
273+ # โโ Package โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
274+ # vmlinux is excluded: it's ~600 MB unstripped and rarely needed in CI.
275+ # Add it back if you need BTF / pahole post-processing.
176276 - name : Package artifacts
177277 run : |
178- mkdir artifacts
278+ mkdir -p artifacts
179279 cp kernel/arch/x86/boot/bzImage artifacts/
180- cp kernel/System.map artifacts/ || true
181- cp kernel/vmlinux artifacts/ || true
182- cp hyperion.config artifacts/
183- tar --zstd -cf Hyperion-Kernel-${FULL_VERSION}.tar.zst artifacts
280+ cp kernel/System.map artifacts/
281+ cp hyperion.config artifacts/
282+ tar --zstd -cf "Hyperion-Kernel-${FULL_VERSION}.tar.zst" artifacts/
184283
185284 - name : Generate checksum
186285 run : |
187- sha256sum Hyperion-Kernel-${FULL_VERSION}.tar.zst > Hyperion-Kernel-${FULL_VERSION}.sha256
286+ sha256sum "Hyperion-Kernel-${FULL_VERSION}.tar.zst" \
287+ > "Hyperion-Kernel-${FULL_VERSION}.sha256"
188288
189289 - name : Create release
190290 uses : softprops/action-gh-release@v2
0 commit comments