1{
2 lib,
3 stdenv,
4 buildPackages,
5 runCommand,
6 bc,
7 bison,
8 flex,
9 perl,
10 rsync,
11 gmp,
12 libmpc,
13 mpfr,
14 openssl,
15 cpio,
16 elfutils,
17 hexdump,
18 zstd,
19 python3Minimal,
20 zlib,
21 pahole,
22 kmod,
23 ubootTools,
24 fetchpatch,
25 rustc,
26 rust-bindgen,
27 rustPlatform,
28}:
29
30let
31 lib_ = lib;
32 stdenv_ = stdenv;
33
34 readConfig =
35 configfile:
36 import
37 (runCommand "config.nix" { } ''
38 echo "{" > "$out"
39 while IFS='=' read key val; do
40 [ "x''${key#CONFIG_}" != "x$key" ] || continue
41 no_firstquote="''${val#\"}";
42 echo ' "'"$key"'" = "'"''${no_firstquote%\"}"'";' >> "$out"
43 done < "${configfile}"
44 echo "}" >> $out
45 '').outPath;
46in
47lib.makeOverridable (
48 {
49 # The kernel version
50 version,
51 # The kernel pname (should be set for variants)
52 pname ? "linux",
53 # Position of the Linux build expression
54 pos ? null,
55 # Additional kernel make flags
56 extraMakeFlags ? [ ],
57 # The name of the kernel module directory
58 # Needs to be X.Y.Z[-extra], so pad with zeros if needed.
59 modDirVersion ? null, # derive from version
60 # The kernel source (tarball, git checkout, etc.)
61 src,
62 # a list of { name=..., patch=..., extraConfig=...} patches
63 kernelPatches ? [ ],
64 # The kernel .config file
65 configfile,
66 # Manually specified nixexpr representing the config
67 # If unspecified, this will be autodetected from the .config
68 config ? lib.optionalAttrs allowImportFromDerivation (readConfig configfile),
69 # Custom seed used for CONFIG_GCC_PLUGIN_RANDSTRUCT if enabled. This is
70 # automatically extended with extra per-version and per-config values.
71 randstructSeed ? "",
72 # Extra meta attributes
73 extraMeta ? { },
74
75 # for module compatibility
76 isZen ? false,
77 isLibre ? false,
78 isHardened ? false,
79
80 # Whether to utilize the controversial import-from-derivation feature to parse the config
81 allowImportFromDerivation ? false,
82 # ignored
83 features ? null,
84 lib ? lib_,
85 stdenv ? stdenv_,
86 }:
87
88 let
89 # Provide defaults. Note that we support `null` so that callers don't need to use optionalAttrs,
90 # which can lead to unnecessary strictness and infinite recursions.
91 modDirVersion_ = if modDirVersion == null then lib.versions.pad 3 version else modDirVersion;
92 in
93 let
94 # Shadow the un-defaulted parameter; don't want null.
95 modDirVersion = modDirVersion_;
96 inherit (lib)
97 hasAttr
98 getAttr
99 optional
100 optionals
101 optionalString
102 optionalAttrs
103 maintainers
104 teams
105 platforms
106 ;
107
108 drvAttrs =
109 config_: kernelConf: kernelPatches: configfile:
110 let
111 # Folding in `ubootTools` in the default nativeBuildInputs is problematic, as
112 # it makes updating U-Boot cumbersome, since it will go above the current
113 # threshold of rebuilds
114 #
115 # To prevent these needless rounds of staging for U-Boot builds, we can
116 # limit the inclusion of ubootTools to target platforms where uImage *may*
117 # be produced.
118 #
119 # This command lists those (kernel-named) platforms:
120 # .../linux $ grep -l uImage ./arch/*/Makefile | cut -d'/' -f3 | sort
121 #
122 # This is still a guesstimation, but since none of our cached platforms
123 # coincide in that list, this gives us "perfect" decoupling here.
124 linuxPlatformsUsingUImage = [
125 "arc"
126 "arm"
127 "csky"
128 "mips"
129 "powerpc"
130 "sh"
131 "sparc"
132 "xtensa"
133 ];
134 needsUbootTools = lib.elem stdenv.hostPlatform.linuxArch linuxPlatformsUsingUImage;
135
136 config =
137 let
138 attrName = attr: "CONFIG_" + attr;
139 in
140 {
141 isSet = attr: hasAttr (attrName attr) config;
142
143 getValue = attr: if config.isSet attr then getAttr (attrName attr) config else null;
144
145 isYes = attr: (config.getValue attr) == "y";
146
147 isNo = attr: (config.getValue attr) == "n";
148
149 isModule = attr: (config.getValue attr) == "m";
150
151 isEnabled = attr: (config.isModule attr) || (config.isYes attr);
152
153 isDisabled = attr: (!(config.isSet attr)) || (config.isNo attr);
154 }
155 // config_;
156
157 isModular = config.isYes "MODULES";
158 withRust = config.isYes "RUST";
159
160 buildDTBs = kernelConf.DTB or false;
161
162 # Dependencies that are required to build kernel modules
163 moduleBuildDependencies = [
164 pahole
165 perl
166 elfutils
167 # module makefiles often run uname commands to find out the kernel version
168 (buildPackages.deterministic-uname.override { inherit modDirVersion; })
169 ]
170 ++ optional (lib.versionAtLeast version "5.13") zstd
171 ++ optionals withRust [
172 rustc
173 rust-bindgen
174 ];
175
176 in
177 (optionalAttrs isModular {
178 outputs = [
179 "out"
180 "dev"
181 ];
182 })
183 // {
184 passthru = rec {
185 inherit
186 version
187 modDirVersion
188 config
189 kernelPatches
190 configfile
191 moduleBuildDependencies
192 stdenv
193 ;
194 inherit
195 isZen
196 isHardened
197 isLibre
198 withRust
199 ;
200 isXen = lib.warn "The isXen attribute is deprecated. All Nixpkgs kernels that support it now have Xen enabled." true;
201 baseVersion = lib.head (lib.splitString "-rc" version);
202 kernelOlder = lib.versionOlder baseVersion;
203 kernelAtLeast = lib.versionAtLeast baseVersion;
204 };
205
206 inherit src;
207
208 depsBuildBuild = [ buildPackages.stdenv.cc ];
209 nativeBuildInputs = [
210 bison
211 flex
212 perl
213 bc
214 openssl
215 rsync
216 gmp
217 libmpc
218 mpfr
219 elfutils
220 zstd
221 python3Minimal
222 kmod
223 hexdump
224 ]
225 ++ optional needsUbootTools ubootTools
226 ++ optionals (lib.versionAtLeast version "5.2") [
227 cpio
228 pahole
229 zlib
230 ]
231 ++ optionals withRust [
232 rustc
233 rust-bindgen
234 ];
235
236 RUST_LIB_SRC = lib.optionalString withRust rustPlatform.rustLibSrc;
237
238 # avoid leaking Rust source file names into the final binary, which adds
239 # a false dependency on rust-lib-src on targets with uncompressed kernels
240 KRUSTFLAGS = lib.optionalString withRust "--remap-path-prefix ${rustPlatform.rustLibSrc}=/";
241
242 patches =
243 map (p: p.patch) kernelPatches
244 # Required for deterministic builds along with some postPatch magic.
245 ++ optional (lib.versionOlder version "5.19") ./randstruct-provide-seed.patch
246 ++ optional (lib.versionAtLeast version "5.19") ./randstruct-provide-seed-5.19.patch
247 # Linux 5.12 marked certain PowerPC-only symbols as GPL, which breaks
248 # OpenZFS; this was fixed in Linux 5.19 so we backport the fix
249 # https://github.com/openzfs/zfs/pull/13367
250 ++
251 optional
252 (
253 lib.versionAtLeast version "5.12" && lib.versionOlder version "5.19" && stdenv.hostPlatform.isPower
254 )
255 (fetchpatch {
256 url = "https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git/patch/?id=d9e5c3e9e75162f845880535957b7fd0b4637d23";
257 hash = "sha256-bBOyJcP6jUvozFJU0SPTOf3cmnTQ6ZZ4PlHjiniHXLU=";
258 });
259
260 postPatch = ''
261 # Ensure that depmod gets resolved through PATH
262 sed -i Makefile -e 's|= /sbin/depmod|= depmod|'
263
264 # Some linux-hardened patches now remove certain files in the scripts directory, so the file may not exist.
265 [[ -f scripts/ld-version.sh ]] && patchShebangs scripts/ld-version.sh
266
267 # Set randstruct seed to a deterministic but diversified value. Note:
268 # we could have instead patched gen-random-seed.sh to take input from
269 # the buildFlags, but that would require also patching the kernel's
270 # toplevel Makefile to add a variable export. This would be likely to
271 # cause future patch conflicts.
272 for file in scripts/gen-randstruct-seed.sh scripts/gcc-plugins/gen-random-seed.sh; do
273 if [ -f "$file" ]; then
274 substituteInPlace "$file" \
275 --replace NIXOS_RANDSTRUCT_SEED \
276 $(echo ${randstructSeed}${src} ${placeholder "configfile"} | sha256sum | cut -d ' ' -f 1 | tr -d '\n')
277 break
278 fi
279 done
280
281 patchShebangs scripts
282
283 # also patch arch-specific install scripts
284 for i in $(find arch -name install.sh); do
285 patchShebangs "$i"
286 done
287
288 # unset $src because the build system tries to use it and spams a bunch of warnings
289 # see: https://github.com/torvalds/linux/commit/b1992c3772e69a6fd0e3fc81cd4d2820c8b6eca0
290 unset src
291 '';
292
293 configurePhase = ''
294 runHook preConfigure
295
296 mkdir build
297 export buildRoot="$(pwd)/build"
298
299 echo "manual-config configurePhase buildRoot=$buildRoot pwd=$PWD"
300
301 if [ -f "$buildRoot/.config" ]; then
302 echo "Could not link $buildRoot/.config : file exists"
303 exit 1
304 fi
305 ln -sv ${configfile} $buildRoot/.config
306
307 # reads the existing .config file and prompts the user for options in
308 # the current kernel source that are not found in the file.
309 make $makeFlags "''${makeFlagsArray[@]}" oldconfig
310 runHook postConfigure
311
312 make $makeFlags "''${makeFlagsArray[@]}" prepare
313 actualModDirVersion="$(cat $buildRoot/include/config/kernel.release)"
314 if [ "$actualModDirVersion" != "${modDirVersion}" ]; then
315 echo "Error: modDirVersion ${modDirVersion} specified in the Nix expression is wrong, it should be: $actualModDirVersion"
316 exit 1
317 fi
318
319 buildFlagsArray+=("KBUILD_BUILD_TIMESTAMP=$(date -u -d @$SOURCE_DATE_EPOCH)")
320
321 cd $buildRoot
322 '';
323
324 buildFlags = [
325 "KBUILD_BUILD_VERSION=1-NixOS"
326 kernelConf.target
327 "vmlinux" # for "perf" and things like that
328 ]
329 ++ optional isModular "modules"
330 ++ optionals buildDTBs [
331 "dtbs"
332 "DTC_FLAGS=-@"
333 ]
334 ++ extraMakeFlags;
335
336 installFlags = [
337 "INSTALL_PATH=$(out)"
338 ]
339 ++ (optional isModular "INSTALL_MOD_PATH=$(out)")
340 ++ optionals buildDTBs [
341 "dtbs_install"
342 "INSTALL_DTBS_PATH=$(out)/dtbs"
343 ];
344
345 preInstall =
346 let
347 # All we really need to do here is copy the final image and System.map to $out,
348 # and use the kernel's modules_install, firmware_install, dtbs_install, etc. targets
349 # for the rest. Easy, right?
350 #
351 # Unfortunately for us, the obvious way of getting the built image path,
352 # make -s image_name, does not work correctly, because some architectures
353 # (*cough* aarch64 *cough*) change KBUILD_IMAGE on the fly in their install targets,
354 # so we end up attempting to install the thing we didn't actually build.
355 #
356 # Thankfully, there's a way out that doesn't involve just hardcoding everything.
357 #
358 # The kernel has an install target, which runs a pretty simple shell script
359 # (located at scripts/install.sh or arch/$arch/boot/install.sh, depending on
360 # which kernel version you're looking at) that tries to do something sensible.
361 #
362 # (it would be great to hijack this script immediately, as it has all the
363 # information we need passed to it and we don't need it to try and be smart,
364 # but unfortunately, the exact location of the scripts differs between kernel
365 # versions, and they're seemingly not considered to be public API at all)
366 #
367 # One of the ways it tries to discover what "something sensible" actually is
368 # is by delegating to what's supposed to be a user-provided install script
369 # located at ~/bin/installkernel.
370 #
371 # (the other options are:
372 # - a distribution-specific script at /sbin/installkernel,
373 # which we can't really create in the sandbox easily
374 # - an architecture-specific script at arch/$arch/boot/install.sh,
375 # which attempts to guess _something_ and usually guesses very wrong)
376 #
377 # More specifically, the install script exec's into ~/bin/installkernel, if one
378 # exists, with the following arguments:
379 #
380 # $1: $KERNELRELEASE - full kernel version string
381 # $2: $KBUILD_IMAGE - the final image path
382 # $3: System.map - path to System.map file, seemingly hardcoded everywhere
383 # $4: $INSTALL_PATH - path to the destination directory as specified in installFlags
384 #
385 # $2 is exactly what we want, so hijack the script and use the knowledge given to it
386 # by the makefile overlords for our own nefarious ends.
387 #
388 # Note that the makefiles specifically look in ~/bin/installkernel, and
389 # writeShellScriptBin writes the script to <store path>/bin/installkernel,
390 # so HOME needs to be set to just the store path.
391 #
392 # FIXME: figure out a less roundabout way of doing this.
393 installkernel = buildPackages.writeShellScriptBin "installkernel" ''
394 cp -av $2 $4
395 cp -av $3 $4
396 '';
397 in
398 ''
399 installFlagsArray+=("-j$NIX_BUILD_CORES")
400 export HOME=${installkernel}
401 '';
402
403 # Some image types need special install targets (e.g. uImage is installed with make uinstall on arm)
404 installTargets = [
405 (kernelConf.installTarget or (
406 if kernelConf.target == "uImage" && stdenv.hostPlatform.linuxArch == "arm" then
407 "uinstall"
408 else if
409 (
410 kernelConf.target == "zImage"
411 || kernelConf.target == "Image.gz"
412 || kernelConf.target == "vmlinuz.efi"
413 )
414 && builtins.elem stdenv.hostPlatform.linuxArch [
415 "arm"
416 "arm64"
417 "parisc"
418 "riscv"
419 ]
420 then
421 "zinstall"
422 else
423 "install"
424 )
425 )
426 ];
427
428 # We remove a bunch of stuff that is symlinked from other places to save space,
429 # which trips the broken symlink check. So, just skip it. We'll know if it explodes.
430 dontCheckForBrokenSymlinks = true;
431
432 postInstall = optionalString isModular ''
433 mkdir -p $dev
434 cp vmlinux $dev/
435 if [ -z "''${dontStrip-}" ]; then
436 installFlagsArray+=("INSTALL_MOD_STRIP=1")
437 fi
438 make modules_install $makeFlags "''${makeFlagsArray[@]}" \
439 $installFlags "''${installFlagsArray[@]}"
440 unlink $out/lib/modules/${modDirVersion}/build
441 rm -f $out/lib/modules/${modDirVersion}/source
442
443 mkdir -p $dev/lib/modules/${modDirVersion}/{build,source}
444
445 # To save space, exclude a bunch of unneeded stuff when copying.
446 (cd .. && rsync --archive --prune-empty-dirs \
447 --exclude='/build/' \
448 * $dev/lib/modules/${modDirVersion}/source/)
449
450 cd $dev/lib/modules/${modDirVersion}/source
451
452 cp $buildRoot/{.config,Module.symvers} $dev/lib/modules/${modDirVersion}/build
453 make modules_prepare $makeFlags "''${makeFlagsArray[@]}" O=$dev/lib/modules/${modDirVersion}/build
454
455 # For reproducibility, removes accidental leftovers from a `cc1` call
456 # from a `try-run` call from the Makefile
457 rm -f $dev/lib/modules/${modDirVersion}/build/.[0-9]*.d
458
459 # Keep some extra files on some arches (powerpc, aarch64)
460 for f in arch/powerpc/lib/crtsavres.o arch/arm64/kernel/ftrace-mod.o; do
461 if [ -f "$buildRoot/$f" ]; then
462 cp $buildRoot/$f $dev/lib/modules/${modDirVersion}/build/$f
463 fi
464 done
465
466 # !!! No documentation on how much of the source tree must be kept
467 # If/when kernel builds fail due to missing files, you can add
468 # them here. Note that we may see packages requiring headers
469 # from drivers/ in the future; it adds 50M to keep all of its
470 # headers on 3.10 though.
471
472 chmod u+w -R ..
473 arch=$(cd $dev/lib/modules/${modDirVersion}/build/arch; ls)
474
475 # Remove unused arches
476 for d in $(cd arch/; ls); do
477 if [ "$d" = "$arch" ]; then continue; fi
478 if [ "$arch" = arm64 ] && [ "$d" = arm ]; then continue; fi
479 rm -rf arch/$d
480 done
481
482 # Remove all driver-specific code (50M of which is headers)
483 rm -fR drivers
484
485 # Keep all headers
486 find . -type f -name '*.h' -print0 | xargs -0 -r chmod u-w
487
488 # Keep linker scripts (they are required for out-of-tree modules on aarch64)
489 find . -type f -name '*.lds' -print0 | xargs -0 -r chmod u-w
490
491 # Keep root and arch-specific Makefiles
492 chmod u-w Makefile arch/"$arch"/Makefile*
493
494 # Keep whole scripts dir
495 chmod u-w -R scripts
496
497 # Delete everything not kept
498 find . -type f -perm -u=w -print0 | xargs -0 -r rm
499
500 # Delete empty directories
501 find -empty -type d -delete
502 '';
503
504 requiredSystemFeatures = [ "big-parallel" ];
505
506 meta = {
507 # https://github.com/NixOS/nixpkgs/pull/345534#issuecomment-2391238381
508 broken = withRust && lib.versionOlder version "6.12";
509
510 description =
511 "The Linux kernel"
512 + (
513 if kernelPatches == [ ] then
514 ""
515 else
516 " (with patches: " + lib.concatStringsSep ", " (map (x: x.name) kernelPatches) + ")"
517 );
518 license = lib.licenses.gpl2Only;
519 homepage = "https://www.kernel.org/";
520 maintainers = [ maintainers.thoughtpolice ];
521 teams = [ teams.linux-kernel ];
522 platforms = platforms.linux;
523 badPlatforms =
524 lib.optionals (lib.versionOlder version "4.15") [
525 "riscv32-linux"
526 "riscv64-linux"
527 ]
528 ++ lib.optional (lib.versionOlder version "5.19") "loongarch64-linux";
529 timeout = 14400; # 4 hours
530 }
531 // extraMeta;
532 };
533
534 # Absolute paths for compilers avoid any PATH-clobbering issues.
535 commonMakeFlags = [
536 "ARCH=${stdenv.hostPlatform.linuxArch}"
537 "CROSS_COMPILE=${stdenv.cc.targetPrefix}"
538 ]
539 ++ lib.optionals (stdenv.isx86_64 && stdenv.cc.bintools.isLLVM) [
540 # The wrapper for ld.lld breaks linking the kernel. We use the
541 # unwrapped linker as workaround. See:
542 #
543 # https://github.com/NixOS/nixpkgs/issues/321667
544 "LD=${stdenv.cc.bintools.bintools}/bin/${stdenv.cc.targetPrefix}ld"
545 ]
546 ++ (stdenv.hostPlatform.linux-kernel.makeFlags or [ ])
547 ++ extraMakeFlags;
548 in
549
550 stdenv.mkDerivation (
551 builtins.foldl' lib.recursiveUpdate { } [
552 (drvAttrs config stdenv.hostPlatform.linux-kernel kernelPatches configfile)
553 {
554 inherit pname version;
555
556 enableParallelBuilding = true;
557
558 hardeningDisable = [
559 "bindnow"
560 "format"
561 "fortify"
562 "stackprotector"
563 "pic"
564 "pie"
565 ];
566
567 makeFlags = [
568 "O=$(buildRoot)"
569 ]
570 ++ commonMakeFlags;
571
572 passthru = { inherit commonMakeFlags; };
573
574 karch = stdenv.hostPlatform.linuxArch;
575 }
576 (optionalAttrs (pos != null) { inherit pos; })
577 ]
578 )
579)