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