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