1let
2 genericBuild =
3 {
4 lib,
5 stdenv,
6 fetchFromGitHub,
7 autoreconfHook269,
8 util-linux,
9 nukeReferences,
10 coreutils,
11 linuxPackages,
12 perl,
13 udevCheckHook,
14 configFile ? "all",
15
16 # Userspace dependencies
17 zlib,
18 libuuid,
19 python3,
20 attr,
21 openssl,
22 libtirpc,
23 nfs-utils,
24 gawk,
25 gnugrep,
26 gnused,
27 systemd,
28 smartmontools,
29 enableMail ? false,
30 sysstat,
31 pkg-config,
32 curl,
33 pam,
34 nix-update-script,
35
36 # Kernel dependencies
37 kernel ? null,
38 kernelModuleMakeFlags ? [ ],
39 enablePython ? true,
40 ...
41 }@outerArgs:
42
43 assert (configFile == "kernel") -> (kernel != null);
44 {
45 version,
46 hash,
47 kernelModuleAttribute,
48 extraLongDescription ? "",
49 extraPatches ? [ ],
50 rev ? "zfs-${version}",
51 kernelMinSupportedMajorMinor,
52 kernelMaxSupportedMajorMinor,
53 enableUnsupportedExperimentalKernel ? false, # allows building against unsupported Kernel versions
54 maintainers ? (with lib.maintainers; [ amarshall ]),
55 tests,
56 }@innerArgs:
57
58 let
59 inherit (lib)
60 any
61 optionalString
62 optionals
63 optional
64 makeBinPath
65 ;
66
67 smartmon = smartmontools.override { inherit enableMail; };
68
69 buildKernel = any (n: n == configFile) [
70 "kernel"
71 "all"
72 ];
73 buildUser = any (n: n == configFile) [
74 "user"
75 "all"
76 ];
77 kernelIsCompatible =
78 kernel:
79 let
80 nextMajorMinor =
81 ver:
82 "${lib.versions.major ver}.${
83 lib.pipe ver [
84 lib.versions.minor
85 lib.toInt
86 (x: x + 1)
87 toString
88 ]
89 }";
90 in
91 (lib.versionAtLeast kernel.version kernelMinSupportedMajorMinor)
92 && (lib.versionOlder kernel.version (nextMajorMinor kernelMaxSupportedMajorMinor));
93
94 # XXX: You always want to build kernel modules with the same stdenv as the
95 # kernel was built with. However, since zfs can also be built for userspace we
96 # need to correctly pick between the provided/default stdenv, and the one used
97 # by the kernel.
98 # If you don't do this your ZFS builds will fail on any non-standard (e.g.
99 # clang-built) kernels.
100 stdenv' = if kernel == null then stdenv else kernel.stdenv;
101 in
102
103 stdenv'.mkDerivation {
104 name = "zfs-${configFile}-${version}${optionalString buildKernel "-${kernel.version}"}";
105 pname = "zfs";
106 inherit version;
107
108 src = fetchFromGitHub {
109 owner = "openzfs";
110 repo = "zfs";
111 inherit rev hash;
112 };
113
114 patches = extraPatches;
115
116 postPatch =
117 optionalString buildKernel ''
118 patchShebangs scripts
119 # The arrays must remain the same length, so we repeat a flag that is
120 # already part of the command and therefore has no effect.
121 substituteInPlace ./module/os/linux/zfs/zfs_ctldir.c \
122 --replace-fail '"/usr/bin/env", "umount"' '"${util-linux}/bin/umount", "-n"' \
123 --replace-fail '"/usr/bin/env", "mount"' '"${util-linux}/bin/mount", "-n"'
124 ''
125 + optionalString buildUser ''
126 substituteInPlace ./lib/libshare/os/linux/nfs.c --replace-fail "/usr/sbin/exportfs" "${
127 # We don't *need* python support, but we set it like this to minimize closure size:
128 # If it's disabled by default, no need to enable it, even if we have python enabled
129 # And if it's enabled by default, only change that if we explicitly disable python to remove python from the closure
130 nfs-utils.override (old: {
131 enablePython = old.enablePython or true && enablePython;
132 })
133 }/bin/exportfs"
134 substituteInPlace ./lib/libshare/smb.h --replace-fail "/usr/bin/net" "/run/current-system/sw/bin/net"
135 # Disable dynamic loading of libcurl
136 substituteInPlace ./config/user-libfetch.m4 --replace-fail "curl-config --built-shared" "true"
137 substituteInPlace ./config/user-systemd.m4 --replace-fail "/usr/lib/modules-load.d" "$out/etc/modules-load.d"
138 substituteInPlace ./config/zfs-build.m4 --replace-fail "\$sysconfdir/init.d" "$out/etc/init.d" \
139 --replace-fail "/etc/default" "$out/etc/default"
140 substituteInPlace ./contrib/initramfs/Makefile.am \
141 --replace-fail "/usr/share/initramfs-tools" "$out/usr/share/initramfs-tools"
142
143 substituteInPlace ./udev/vdev_id \
144 --replace-fail "PATH=/bin:/sbin:/usr/bin:/usr/sbin" \
145 "PATH=${
146 makeBinPath [
147 coreutils
148 gawk
149 gnused
150 gnugrep
151 systemd
152 ]
153 }"
154
155 substituteInPlace ./config/zfs-build.m4 \
156 --replace-fail "bashcompletiondir=/etc/bash_completion.d" \
157 "bashcompletiondir=$out/share/bash-completion/completions"
158
159 substituteInPlace ./cmd/arc_summary --replace-fail "/sbin/modinfo" "modinfo"
160 ''
161 + ''
162 echo 'Supported Kernel versions:'
163 grep '^Linux-' META
164 echo 'Checking kernelMinSupportedMajorMinor is correct...'
165 grep --quiet '^Linux-Minimum: *${lib.escapeRegex kernelMinSupportedMajorMinor}$' META
166 echo 'Checking kernelMaxSupportedMajorMinor is correct...'
167 grep --quiet '^Linux-Maximum: *${lib.escapeRegex kernelMaxSupportedMajorMinor}$' META
168 '';
169
170 nativeBuildInputs = [
171 autoreconfHook269
172 nukeReferences
173 ]
174 ++ optionals buildKernel (kernel.moduleBuildDependencies ++ [ perl ])
175 ++ optionals buildUser [
176 pkg-config
177 udevCheckHook
178 ];
179 buildInputs =
180 optionals buildUser [
181 zlib
182 libuuid
183 attr
184 libtirpc
185 pam
186 ]
187 ++ optional buildUser openssl
188 ++ optional buildUser curl
189 ++ optional (buildUser && enablePython) python3;
190
191 # for zdb to get the rpath to libgcc_s, needed for pthread_cancel to work
192 NIX_CFLAGS_LINK = "-lgcc_s";
193
194 hardeningDisable = [
195 "fortify"
196 "stackprotector"
197 "pic"
198 ];
199
200 configureFlags = [
201 "--with-config=${configFile}"
202 "--with-tirpc=1"
203 (lib.withFeatureAs (buildUser && enablePython) "python" python3.interpreter)
204 ]
205 ++ optional enableUnsupportedExperimentalKernel "--enable-linux-experimental"
206 ++ optionals buildUser [
207 "--with-dracutdir=$(out)/lib/dracut"
208 "--with-udevdir=$(out)/lib/udev"
209 "--with-systemdunitdir=$(out)/etc/systemd/system"
210 "--with-systemdpresetdir=$(out)/etc/systemd/system-preset"
211 "--with-systemdgeneratordir=$(out)/lib/systemd/system-generator"
212 "--with-mounthelperdir=$(out)/bin"
213 "--libexecdir=$(out)/libexec"
214 "--sysconfdir=/etc"
215 "--localstatedir=/var"
216 "--enable-systemd"
217 "--enable-pam"
218 ]
219 ++ optionals buildKernel (
220 [
221 "--with-linux=${kernel.dev}/lib/modules/${kernel.modDirVersion}/source"
222 "--with-linux-obj=${kernel.dev}/lib/modules/${kernel.modDirVersion}/build"
223 ]
224 ++ kernelModuleMakeFlags
225 );
226
227 makeFlags = optionals buildKernel kernelModuleMakeFlags;
228
229 enableParallelBuilding = true;
230
231 doInstallCheck = true;
232
233 installFlags = [
234 "sysconfdir=\${out}/etc"
235 "DEFAULT_INITCONF_DIR=\${out}/default"
236 "INSTALL_MOD_PATH=\${out}"
237 ];
238
239 preConfigure = ''
240 # The kernel module builds some tests during the configurePhase, this envvar controls their parallelism
241 export TEST_JOBS=$NIX_BUILD_CORES
242 if [ -z "$enableParallelBuilding" ]; then
243 export TEST_JOBS=1
244 fi
245 '';
246
247 # Enabling BTF causes zfs to be build with debug symbols.
248 # Since zfs compress kernel modules on installation, our strip hooks skip stripping them.
249 # Hence we strip modules prior to compression.
250 postBuild = optionalString buildKernel ''
251 find . -name "*.ko" -print0 | xargs -0 -P$NIX_BUILD_CORES ${stdenv.cc.targetPrefix}strip --strip-debug
252 '';
253
254 postInstall =
255 optionalString buildKernel ''
256 # Add reference that cannot be detected due to compressed kernel module
257 mkdir -p "$out/nix-support"
258 echo "${util-linux}" >> "$out/nix-support/extra-refs"
259 ''
260 + optionalString buildUser ''
261 # Remove provided services as they are buggy
262 rm $out/etc/systemd/system/zfs-import-*.service
263
264 for i in $out/etc/systemd/system/*; do
265 if [ -L $i ]; then
266 continue
267 fi
268 sed -i '/zfs-import-scan.service/d' $i
269 substituteInPlace $i --replace-warn "zfs-import-cache.service" "zfs-import.target"
270 done
271
272 # Remove tests because they add a runtime dependency on gcc
273 rm -rf $out/share/zfs/zfs-tests
274 '';
275
276 postFixup =
277 let
278 path = "PATH=${
279 makeBinPath [
280 coreutils
281 gawk
282 gnused
283 gnugrep
284 util-linux
285 smartmon
286 sysstat
287 ]
288 }:$PATH";
289 in
290 ''
291 for i in $out/libexec/zfs/zpool.d/*; do
292 sed -i '2i${path}' $i
293 done
294 '';
295
296 outputs = [ "out" ] ++ optionals buildUser [ "dev" ];
297
298 passthru = {
299 inherit kernel;
300 inherit enableMail kernelModuleAttribute;
301 latestCompatibleLinuxPackages = lib.warn "zfs.latestCompatibleLinuxPackages is deprecated and is now pointing at the default kernel. If using the stable LTS kernel (default `linuxPackages` is not possible then you must explicitly pin a specific kernel release. For example, `boot.kernelPackages = pkgs.linuxPackages_6_6`. Please be aware that non-LTS kernels are likely to go EOL before ZFS supports the latest supported non-LTS release, requiring manual intervention." linuxPackages;
302
303 # The corresponding userspace tools to this instantiation
304 # of the ZFS package set.
305 userspaceTools = genericBuild (
306 outerArgs
307 // {
308 configFile = "user";
309 }
310 ) innerArgs;
311
312 inherit tests;
313 }
314 // lib.optionalAttrs (kernelModuleAttribute != "zfs_unstable") {
315 updateScript = nix-update-script {
316 extraArgs = [
317 "--version-regex=^zfs-(${lib.versions.major version}\\.${lib.versions.minor version}\\.[0-9]+)"
318 "--override-filename=pkgs/os-specific/linux/zfs/${lib.versions.major version}_${lib.versions.minor version}.nix"
319 ];
320 };
321 };
322
323 meta = {
324 description = "ZFS Filesystem Linux" + (if buildUser then " Userspace Tools" else " Kernel Module");
325 longDescription = ''
326 ZFS is a filesystem that combines a logical volume manager with a
327 Copy-On-Write filesystem with data integrity detection and repair,
328 snapshotting, cloning, block devices, deduplication, and more.
329
330 ${
331 if buildUser then "This is the userspace tools package." else "This is the kernel module package."
332 }
333 ''
334 + extraLongDescription;
335 homepage = "https://github.com/openzfs/zfs";
336 changelog = "https://github.com/openzfs/zfs/releases/tag/zfs-${version}";
337 license = lib.licenses.cddl;
338
339 # The case-block for TARGET_CPU has branches for only some CPU families,
340 # which prevents ZFS from building on any other platform. Since the NixOS
341 # `boot.zfs.enabled` property is `readOnly`, excluding platforms where ZFS
342 # does not build is the only way to produce a NixOS installer on such
343 # platforms.
344 # https://github.com/openzfs/zfs/blob/6723d1110f6daf93be93db74d5ea9f6b64c9bce5/config/always-arch.m4#L12
345 platforms =
346 with lib.systems.inspect.patterns;
347 map (p: p // isLinux) (
348 [
349 isx86_32
350 isx86_64
351 isPower
352 isAarch64
353 isSparc
354 ]
355 ++ isArmv7
356 );
357
358 inherit maintainers;
359 mainProgram = "zfs";
360 broken = buildKernel && !((kernelIsCompatible kernel) || enableUnsupportedExperimentalKernel);
361 };
362 };
363in
364genericBuild