|
| 1 | +{ |
| 2 | + lib, |
| 3 | + buildPackages, |
| 4 | +}: |
| 5 | + |
| 6 | +let |
| 7 | + inherit (buildPackages) rsync; |
| 8 | +in |
| 9 | + |
| 10 | +{ |
| 11 | + /* |
| 12 | + Prepare a kernel derivation for checkpoint-based incremental builds. |
| 13 | +
|
| 14 | + Runs the full kernel build (unpack → patch → configure → build) but |
| 15 | + captures the source tree and the kbuild output directory instead of |
| 16 | + installing. The result is passed to `mkKernelCheckpointBuild` for |
| 17 | + fast incremental rebuilds. |
| 18 | +
|
| 19 | + What gets saved: |
| 20 | + $out/sources/ – post-patch source tree (reference for diffing) |
| 21 | + $out/build/ – complete $buildRoot with .o, .cmd, vmlinux, … |
| 22 | + $out/sourceRoot – absolute source path (for .cmd fixup) |
| 23 | + $out/buildRoot – absolute build path (for .cmd fixup) |
| 24 | +
|
| 25 | + Usage: |
| 26 | + checkpoint = kernelCheckpointTools.prepareKernelCheckpoint pkgs.linux; |
| 27 | + */ |
| 28 | + prepareKernelCheckpoint = |
| 29 | + drv: |
| 30 | + drv.overrideAttrs (old: { |
| 31 | + outputs = [ "out" ]; |
| 32 | + name = drv.name + "-kernelCheckpoint"; |
| 33 | + |
| 34 | + # After configurePhase, CWD = $buildRoot (the out-of-tree build dir). |
| 35 | + # Snapshot the source tree (one level up, excluding build/) so we can |
| 36 | + # diff against it later. |
| 37 | + preBuild = |
| 38 | + (old.preBuild or "") |
| 39 | + + '' |
| 40 | + checkpointSourceRoot="$(dirname "$buildRoot")" |
| 41 | + mkdir -p "$out/sources" |
| 42 | + ${rsync}/bin/rsync -a --exclude='/build/' \ |
| 43 | + "$checkpointSourceRoot/" "$out/sources/" |
| 44 | +
|
| 45 | + # Record the absolute paths so mkKernelCheckpointBuild can fixup |
| 46 | + # kbuild .cmd files when the sandbox path changes between builds. |
| 47 | + echo "$checkpointSourceRoot" > "$out/sourceRoot" |
| 48 | + echo "$buildRoot" > "$out/buildRoot" |
| 49 | + ''; |
| 50 | + |
| 51 | + # Replace the normal install with a snapshot of the build directory. |
| 52 | + # This preserves every kbuild artifact needed for incremental builds: |
| 53 | + # .o files, .cmd dependency trackers, generated headers, vmlinux, etc. |
| 54 | + installPhase = '' |
| 55 | + mkdir -p "$out/build" |
| 56 | + cp -a . "$out/build/" |
| 57 | + ''; |
| 58 | + |
| 59 | + dontFixup = true; |
| 60 | + doInstallCheck = false; |
| 61 | + doDist = false; |
| 62 | + }); |
| 63 | + |
| 64 | + /* |
| 65 | + Build a kernel incrementally from a previously prepared checkpoint. |
| 66 | +
|
| 67 | + Restores the checkpoint's build artifacts into the current build tree |
| 68 | + and lets kbuild's native incremental compilation rebuild only the |
| 69 | + files whose sources changed. This turns multi-hour kernel builds |
| 70 | + into minutes when only a few files differ. |
| 71 | +
|
| 72 | + The function: |
| 73 | + 1. Diffs the current (possibly patched) sources against the checkpoint. |
| 74 | + 2. Restores the checkpoint's source tree and build directory. |
| 75 | + 3. Applies the source diff — only touched files get a new mtime. |
| 76 | + 4. Fixes up absolute sandbox paths inside kbuild .cmd files. |
| 77 | + 5. Re-links the current .config and runs `make oldconfig`. |
| 78 | + 6. Hands off to the normal build phase — kbuild recompiles only |
| 79 | + what changed. |
| 80 | +
|
| 81 | + Usage: |
| 82 | + checkpoint = kernelCheckpointTools.prepareKernelCheckpoint pkgs.linux; |
| 83 | + modified = pkgs.linux.overrideAttrs (old: { src = ./my-tree; }); |
| 84 | + fast = kernelCheckpointTools.mkKernelCheckpointBuild modified checkpoint; |
| 85 | + */ |
| 86 | + mkKernelCheckpointBuild = |
| 87 | + drv: checkpoint: |
| 88 | + drv.overrideAttrs (old: { |
| 89 | + preBuild = |
| 90 | + (old.preBuild or "") |
| 91 | + + '' |
| 92 | + checkpointSourceRoot="$(dirname "$buildRoot")" |
| 93 | +
|
| 94 | + # ── 1. Compute source diff ────────────────────────────────────── |
| 95 | + # Diff the checkpoint's sources against the current (post-patch) |
| 96 | + # sources. Exit code 1 = differences found, 0 = identical. |
| 97 | + sourceDiffFile=$(mktemp) |
| 98 | + set +e |
| 99 | + diff -urN \ |
| 100 | + ${checkpoint}/sources/ \ |
| 101 | + "$checkpointSourceRoot/" \ |
| 102 | + > "$sourceDiffFile" |
| 103 | + diffExit=$? |
| 104 | + set -e |
| 105 | +
|
| 106 | + if [ "$diffExit" -gt 1 ]; then |
| 107 | + echo "diff failed with exit code $diffExit" |
| 108 | + exit 1 |
| 109 | + fi |
| 110 | +
|
| 111 | + # ── 2. Restore build directory from checkpoint ────────────────── |
| 112 | + # CWD is $buildRoot. Wipe the freshly-configured build dir |
| 113 | + # and replace it with the checkpoint's compiled artifacts. |
| 114 | + shopt -s dotglob |
| 115 | + rm -rf -- * |
| 116 | + ${rsync}/bin/rsync -a ${checkpoint}/build/ ./ |
| 117 | + chmod -R u+w . |
| 118 | + shopt -u dotglob |
| 119 | +
|
| 120 | + # ── 3. Restore source tree and apply diff ─────────────────────── |
| 121 | + # Replace the source tree with the checkpoint's snapshot, then |
| 122 | + # apply the diff. Only files mentioned in the diff get a new |
| 123 | + # mtime, which is what tells kbuild to recompile them. |
| 124 | + ( |
| 125 | + cd "$checkpointSourceRoot" |
| 126 | + ${rsync}/bin/rsync -a --delete --exclude='/build/' \ |
| 127 | + ${checkpoint}/sources/ ./ |
| 128 | + chmod -R u+w . |
| 129 | +
|
| 130 | + if [ "$diffExit" -eq 1 ]; then |
| 131 | + echo "Applying source diff to trigger incremental rebuild…" |
| 132 | + patch -p1 --no-backup-if-mismatch < "$sourceDiffFile" |
| 133 | + else |
| 134 | + echo "Sources identical to checkpoint — pure config/flag rebuild." |
| 135 | + fi |
| 136 | + ) |
| 137 | +
|
| 138 | + # ── 4. Fix up kbuild .cmd paths ───────────────────────────────── |
| 139 | + # .cmd files record the absolute compile commands. If the build |
| 140 | + # sandbox path differs between the checkpoint derivation and this |
| 141 | + # one, every command looks "changed" and kbuild recompiles |
| 142 | + # everything. Rewrite the paths to prevent that. |
| 143 | + oldSourceRoot=$(cat ${checkpoint}/sourceRoot) |
| 144 | + oldBuildRoot=$(cat ${checkpoint}/buildRoot) |
| 145 | +
|
| 146 | + if [ "$oldSourceRoot" != "$checkpointSourceRoot" ] \ |
| 147 | + || [ "$oldBuildRoot" != "$buildRoot" ]; then |
| 148 | + echo "Fixing kbuild .cmd paths:" |
| 149 | + echo " source: $oldSourceRoot -> $checkpointSourceRoot" |
| 150 | + echo " build: $oldBuildRoot -> $buildRoot" |
| 151 | + find . -name '*.cmd' -print0 \ |
| 152 | + | xargs -0 -r -P "''${NIX_BUILD_CORES:-1}" sed -i \ |
| 153 | + -e "s|$oldBuildRoot|$buildRoot|g" \ |
| 154 | + -e "s|$oldSourceRoot|$checkpointSourceRoot|g" |
| 155 | + fi |
| 156 | +
|
| 157 | + # ── 5. Reconcile .config ──────────────────────────────────────── |
| 158 | + # The checkpoint may have been built with a different config. |
| 159 | + # Re-link the current configfile and let oldconfig resolve any |
| 160 | + # new or removed options. kbuild will then compare .config |
| 161 | + # against include/config/auto.conf and recompile affected files. |
| 162 | + rm -f .config |
| 163 | + ln -sv ${drv.configfile} .config |
| 164 | + make "''${makeFlags[@]}" oldconfig |
| 165 | +
|
| 166 | + rm -f "$sourceDiffFile" |
| 167 | + ''; |
| 168 | + }); |
| 169 | +} |
0 commit comments