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