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