1# We have tests for PCRE and PHP-FPM in nixos/tests/php/ or
2# both in the same attribute named nixosTests.php
3
4let
5 generic =
6 {
7 callPackage,
8 lib,
9 stdenv,
10 nixosTests,
11 tests,
12 fetchurl,
13 makeBinaryWrapper,
14 symlinkJoin,
15 writeText,
16 autoconf,
17 automake,
18 bison,
19 flex,
20 libtool,
21 pkg-config,
22 re2c,
23 apacheHttpd,
24 libargon2,
25 libxml2,
26 pcre2,
27 systemdLibs,
28 system-sendmail,
29 valgrind,
30 xcbuild,
31 writeShellScript,
32 common-updater-scripts,
33 curl,
34 jq,
35 coreutils,
36 formats,
37
38 version,
39 phpSrc ? null,
40 hash ? null,
41 extraPatches ? [ ],
42 packageOverrides ? (final: prev: { }),
43 phpAttrsOverrides ? (final: prev: { }),
44 pearInstallPhar ? (callPackage ./install-pear-nozlib-phar.nix { }),
45
46 # Sapi flags
47 cgiSupport ? true,
48 cliSupport ? true,
49 fpmSupport ? true,
50 pearSupport ? true,
51 pharSupport ? true,
52 phpdbgSupport ? true,
53
54 # Misc flags
55 apxs2Support ? false,
56 argon2Support ? true,
57 cgotoSupport ? false,
58 embedSupport ? false,
59 staticSupport ? false,
60 ipv6Support ? true,
61 zendSignalsSupport ? true,
62 zendMaxExecutionTimersSupport ? false,
63 systemdSupport ? lib.meta.availableOn stdenv.hostPlatform systemdLibs,
64 valgrindSupport ?
65 !stdenv.hostPlatform.isDarwin && lib.meta.availableOn stdenv.hostPlatform valgrind,
66 ztsSupport ? apxs2Support,
67 }@args:
68
69 let
70 # buildEnv wraps php to provide additional extensions and
71 # configuration. Its usage is documented in
72 # doc/languages-frameworks/php.section.md.
73 #
74 # Create a buildEnv with earlier overridden values and
75 # extensions functions in its closure. This is necessary for
76 # consecutive calls to buildEnv and overrides to work as
77 # expected.
78 mkBuildEnv =
79 prevArgs: prevExtensionFunctions:
80 lib.makeOverridable (
81 {
82 extensions ? ({ enabled, ... }: enabled),
83 extraConfig ? "",
84 ...
85 }@innerArgs:
86 let
87 allArgs = args // prevArgs // innerArgs;
88 filteredArgs = builtins.removeAttrs allArgs [
89 "extensions"
90 "extraConfig"
91 ];
92 php = generic filteredArgs;
93
94 php-packages =
95 (callPackage ../../../top-level/php-packages.nix {
96 phpPackage = phpWithExtensions;
97 }).overrideScope
98 packageOverrides;
99
100 allExtensionFunctions = prevExtensionFunctions ++ [ extensions ];
101 enabledExtensions = builtins.foldl' (
102 enabled: f:
103 f {
104 inherit enabled;
105 all = php-packages.extensions;
106 }
107 ) [ ] allExtensionFunctions;
108
109 getExtName = ext: ext.extensionName;
110
111 # Recursively get a list of all internal dependencies
112 # for a list of extensions.
113 getDepsRecursively =
114 extensions:
115 let
116 deps = lib.concatMap (ext: (ext.internalDeps or [ ]) ++ (ext.peclDeps or [ ])) extensions;
117 in
118 if !(deps == [ ]) then deps ++ (getDepsRecursively deps) else deps;
119
120 # Generate extension load configuration snippets from the
121 # extension parameter. This is an attrset suitable for use
122 # with textClosureList, which is used to put the strings in
123 # the right order - if a plugin which is dependent on
124 # another plugin is placed before its dependency, it will
125 # fail to load.
126 extensionTexts = lib.listToAttrs (
127 map (
128 ext:
129 let
130 extName = getExtName ext;
131 phpDeps = (ext.internalDeps or [ ]) ++ (ext.peclDeps or [ ]);
132 type = "${lib.optionalString (ext.zendExtension or false) "zend_"}extension";
133 in
134 lib.nameValuePair extName {
135 text = "${type}=${ext}/lib/php/extensions/${extName}.so";
136 deps = map getExtName phpDeps;
137 }
138 ) (enabledExtensions ++ (getDepsRecursively enabledExtensions))
139 );
140
141 extNames = map getExtName enabledExtensions;
142 extraInit = writeText "php-extra-init-${version}.ini" ''
143 ${lib.concatStringsSep "\n" (lib.textClosureList extensionTexts extNames)}
144 ${extraConfig}
145 '';
146
147 phpWithExtensions = symlinkJoin {
148 name = "php-with-extensions-${version}";
149 inherit (php) version;
150 nativeBuildInputs = [ makeBinaryWrapper ];
151 passthru = php.passthru // {
152 buildEnv = mkBuildEnv allArgs allExtensionFunctions;
153 withExtensions = mkWithExtensions allArgs allExtensionFunctions;
154 overrideAttrs =
155 f:
156 let
157 phpAttrsOverrides = filteredArgs.phpAttrsOverrides or (final: prev: { });
158 newPhpAttrsOverrides = lib.composeExtensions (lib.toExtension phpAttrsOverrides) (
159 lib.toExtension f
160 );
161 php = generic (filteredArgs // { phpAttrsOverrides = newPhpAttrsOverrides; });
162 in
163 php.buildEnv { inherit extensions extraConfig; };
164 phpIni = "${phpWithExtensions}/lib/php.ini";
165 unwrapped = php;
166 # Select the right php tests for the php version
167 tests = {
168 nixos =
169 lib.recurseIntoAttrs
170 nixosTests."php${lib.strings.replaceStrings [ "." ] [ "" ] (lib.versions.majorMinor php.version)}";
171 package = tests.php;
172 };
173 inherit (php-packages)
174 extensions
175 buildPecl
176 mkComposerRepository
177 mkComposerVendor
178 buildComposerProject
179 buildComposerProject2
180 buildComposerWithPlugin
181 composerHooks
182 composerHooks2
183 mkExtension
184 ;
185 packages = php-packages.tools;
186 meta = php.meta // {
187 outputsToInstall = [ "out" ];
188 };
189 };
190 paths = [ php ];
191 postBuild = ''
192 ln -s ${extraInit} $out/lib/php.ini
193
194 if test -e $out/bin/php; then
195 wrapProgram $out/bin/php --set-default PHP_INI_SCAN_DIR $out/lib
196 fi
197
198 if test -e $out/bin/php-fpm; then
199 wrapProgram $out/bin/php-fpm --set-default PHP_INI_SCAN_DIR $out/lib
200 fi
201
202 if test -e $out/bin/phpdbg; then
203 wrapProgram $out/bin/phpdbg --set-default PHP_INI_SCAN_DIR $out/lib
204 fi
205
206 if test -e $out/bin/php-cgi; then
207 wrapProgram $out/bin/php-cgi --set-default PHP_INI_SCAN_DIR $out/lib
208 fi
209 '';
210 };
211 in
212 phpWithExtensions
213 );
214
215 mkWithExtensions =
216 prevArgs: prevExtensionFunctions: extensions:
217 mkBuildEnv prevArgs prevExtensionFunctions { inherit extensions; };
218
219 defaultPhpSrc = fetchurl {
220 url = "https://www.php.net/distributions/php-${version}.tar.bz2";
221 inherit hash;
222 };
223 in
224 stdenv.mkDerivation (
225 finalAttrs:
226 let
227 attrs = {
228 pname = "php";
229
230 inherit version;
231
232 enableParallelBuilding = true;
233
234 nativeBuildInputs = [
235 autoconf
236 automake
237 bison
238 flex
239 libtool
240 pkg-config
241 re2c
242 ]
243 ++ lib.optional stdenv.hostPlatform.isDarwin xcbuild;
244
245 buildInputs =
246 # PCRE extension
247 [ pcre2 ]
248
249 # Enable sapis
250 ++ lib.optionals pearSupport [ libxml2.dev ]
251
252 # Misc deps
253 ++ lib.optional apxs2Support apacheHttpd
254 ++ lib.optional argon2Support libargon2
255 ++ lib.optional systemdSupport systemdLibs
256 ++ lib.optional valgrindSupport valgrind;
257
258 CXXFLAGS = lib.optionalString stdenv.cc.isClang "-std=c++11";
259 SKIP_PERF_SENSITIVE = 1;
260
261 configureFlags =
262 # Disable all extensions
263 [ "--disable-all" ]
264
265 # PCRE
266 ++ [ "--with-external-pcre=${pcre2.dev}" ]
267
268 # Enable sapis
269 ++ lib.optional (!cgiSupport) "--disable-cgi"
270 ++ lib.optional (!cliSupport) "--disable-cli"
271 ++ lib.optional fpmSupport "--enable-fpm"
272 ++ lib.optionals pearSupport [
273 "--with-pear"
274 "--enable-xml"
275 "--with-libxml"
276 ]
277 ++ lib.optional pharSupport "--enable-phar"
278 ++ lib.optional (!phpdbgSupport) "--disable-phpdbg"
279
280 # Misc flags
281 ++ lib.optional apxs2Support "--with-apxs2=${apacheHttpd.dev}/bin/apxs"
282 ++ lib.optional argon2Support "--with-password-argon2=${libargon2}"
283 ++ lib.optional cgotoSupport "--enable-re2c-cgoto"
284 ++ lib.optional embedSupport "--enable-embed${lib.optionalString staticSupport "=static"}"
285 ++ lib.optional (!ipv6Support) "--disable-ipv6"
286 ++ lib.optional systemdSupport "--with-fpm-systemd"
287 ++ lib.optional valgrindSupport "--with-valgrind=${valgrind.dev}"
288 ++ lib.optional ztsSupport "--enable-zts"
289 ++ lib.optional staticSupport "--enable-static"
290 ++ lib.optional (!zendSignalsSupport) [ "--disable-zend-signals" ]
291 ++ lib.optional zendMaxExecutionTimersSupport "--enable-zend-max-execution-timers"
292
293 # Sendmail
294 ++ [ "PROG_SENDMAIL=${system-sendmail}/bin/sendmail" ];
295
296 hardeningDisable = [ "bindnow" ];
297
298 preConfigure =
299 # Don't record the configure flags since this causes unnecessary
300 # runtime dependencies
301 ''
302 substituteInPlace main/build-defs.h.in \
303 --replace-fail '@CONFIGURE_COMMAND@' '(omitted)'
304 substituteInPlace scripts/php-config.in \
305 --replace-fail '@CONFIGURE_OPTIONS@' "" \
306 --replace-fail '@PHP_LDFLAGS@' ""
307
308 export EXTENSION_DIR=$out/lib/php/extensions
309
310 ./buildconf --copy --force
311
312 if [ -f "scripts/dev/genfiles" ]; then
313 ./scripts/dev/genfiles
314 fi
315 ''
316 + lib.optionalString stdenv.hostPlatform.isDarwin ''
317 substituteInPlace configure --replace-fail "-lstdc++" "-lc++"
318 '';
319
320 # When compiling PHP sources from Github, this file is missing and we
321 # need to install it ourselves.
322 # On the other hand, a distribution includes this file by default.
323 preInstall = ''
324 if [[ ! -f ./pear/install-pear-nozlib.phar ]]; then
325 cp ${pearInstallPhar} ./pear/install-pear-nozlib.phar
326 fi
327 '';
328
329 postInstall = ''
330 test -d $out/etc || mkdir $out/etc
331 cp php.ini-production $out/etc/php.ini
332 '';
333
334 postFixup = ''
335 mkdir -p $dev/bin $dev/lib $dev/share/man/man1
336 mv $out/bin/phpize $out/bin/php-config $dev/bin/
337 mv $out/lib/build $dev/lib/
338 mv $out/share/man/man1/phpize.1.gz \
339 $out/share/man/man1/php-config.1.gz \
340 $dev/share/man/man1/
341
342 substituteInPlace $dev/bin/phpize \
343 --replace-fail "$out/lib" "$dev/lib"
344 '';
345
346 src = if phpSrc == null then defaultPhpSrc else phpSrc;
347
348 patches =
349 lib.optionals (lib.versionOlder version "8.4") [
350 ./fix-paths-php7.patch
351 ]
352 ++ lib.optionals (lib.versionAtLeast version "8.4") [
353 ./fix-paths-php84.patch
354 ]
355 ++ extraPatches;
356
357 separateDebugInfo = true;
358
359 outputs = [
360 "out"
361 "dev"
362 ];
363
364 passthru = {
365 updateScript =
366 let
367 script = writeShellScript "php${lib.versions.major version}${lib.versions.minor version}-update-script" ''
368 set -o errexit
369 PATH=${
370 lib.makeBinPath [
371 common-updater-scripts
372 curl
373 jq
374 ]
375 }
376 new_version=$(curl --silent "https://www.php.net/releases/active" | jq --raw-output '."${lib.versions.major version}"."${lib.versions.majorMinor version}".version')
377 update-source-version "$UPDATE_NIX_ATTR_PATH.unwrapped" "$new_version" "--file=$1"
378 '';
379 in
380 [
381 script
382 # Passed as an argument so that update.nix can ensure it does not become a store path.
383 (./. + "/${lib.versions.majorMinor version}.nix")
384 ];
385 buildEnv = mkBuildEnv { } [ ];
386 withExtensions = mkWithExtensions { } [ ];
387 overrideAttrs =
388 f:
389 let
390 newPhpAttrsOverrides = lib.composeExtensions (lib.toExtension phpAttrsOverrides) (
391 lib.toExtension f
392 );
393 php = generic (args // { phpAttrsOverrides = newPhpAttrsOverrides; });
394 in
395 php;
396 inherit ztsSupport;
397
398 services.default = {
399 imports = [
400 (lib.modules.importApply ./service.nix {
401 inherit formats coreutils;
402 })
403 ];
404 php-fpm.package = lib.mkDefault finalAttrs.finalPackage;
405 };
406 };
407
408 meta = with lib; {
409 description = "HTML-embedded scripting language";
410 homepage = "https://www.php.net/";
411 license = licenses.php301;
412 mainProgram = "php";
413 teams = [ teams.php ];
414 platforms = platforms.all;
415 outputsToInstall = [
416 "out"
417 "dev"
418 ];
419 };
420 };
421 final = attrs // (lib.toExtension phpAttrsOverrides) final attrs;
422 in
423 final
424 );
425in
426generic