Compare commits

...

282 Commits

Author SHA1 Message Date
DubbleClick
a6d68b9ecf fix ncurses 2025-07-01 18:09:17 +07:00
DubbleClick
d275d6cc69 fix native chain 2025-07-01 18:06:18 +07:00
DubbleClick
fd89e83798 musl paths in zig toolchain are hogwash, of course 2025-07-01 17:08:19 +07:00
DubbleClick
88f9172866 bring back opcache jit fix 2025-07-01 16:57:56 +07:00
DubbleClick
27f57cc23a fix zig runtime 2025-07-01 16:40:12 +07:00
DubbleClick
f6c6011061 err, we need to install zig of course 2025-07-01 16:25:08 +07:00
DubbleClick
408b3b4060 try to test with zig? 2025-07-01 16:21:57 +07:00
DubbleClick
55fd7ba8e6 bring back static -lstdc++ for rhel 2.17 2025-07-01 15:58:04 +07:00
DubbleClick
e34eb502db fix tests failing to deprecation 2025-07-01 15:05:44 +07:00
DubbleClick
292df38c10 isInstalled for GoXcaddy 2025-07-01 14:59:34 +07:00
DubbleClick
a483c42893 redis check if static or shared 2025-07-01 14:45:28 +07:00
DubbleClick
bebcb978f6 conflicts with shared tests 2025-07-01 14:41:14 +07:00
DubbleClick
ba7f994a71 simplify zig script 2025-07-01 14:12:51 +07:00
DubbleClick
9c7e2171f6 formatting 2025-07-01 14:07:41 +07:00
DubbleClick
b7e8587608 cs fixes 2025-07-01 14:02:10 +07:00
DubbleClick
0a33fba23e zig toolchain stuff 2025-07-01 14:01:48 +07:00
DubbleClick
5370af4a84 don't zig the tests yet 2025-07-01 13:06:03 +07:00
DubbleClick
df0d5fdbe8 merge fix 2025-07-01 13:02:59 +07:00
DubbleClick
7b33ee79dd make sure that $TARGET splits for -dynamic, otherwise it resolves to "-target 'native-native -dynamic'" 2025-07-01 12:56:51 +07:00
DubbleClick
ad8322b6a6 add zig to libc/static target parsing 2025-07-01 12:55:32 +07:00
DubbleClick
c43a10027b fix 2025-07-01 11:02:20 +07:00
DubbleClick
2ebdc4509c < 2025-07-01 11:00:24 +07:00
Jerry Ma
4cb4c9c6a5 Merge pull request #812 from crazywhalecc/sponsor-badge
Add special sponsors
2025-06-30 22:09:36 +08:00
Jerry Ma
9190335a70 Merge pull request #810 from crazywhalecc/refactor/spc-target
refactor: replace SPC_LIBC with SPC_TARGET and update related logic
2025-06-30 21:49:01 +08:00
crazywhalecc
4e244789a2 Add frankenphp SAPI 2025-06-30 21:45:45 +08:00
crazywhalecc
6de7b8e63b Add special sponsors 2025-06-30 21:41:48 +08:00
Marc
68e500eea0 Merge branch 'main' into refactor/spc-target 2025-06-30 20:16:59 +07:00
Marc
840e09a684 Merge pull request #811 from crazywhalecc/fix/micro-patches
Fix/micro patches
2025-06-30 20:16:19 +07:00
crazywhalecc
d00a5223d3 Add exception for checking patch file exist 2025-06-30 19:45:26 +08:00
crazywhalecc
3965a899c7 Add missing directory separator 2025-06-30 19:41:32 +08:00
DubbleClick
956667bf08 fix alpine doctor saying gettext-dev isn't installed 2025-06-30 13:40:51 +07:00
DubbleClick
51e23d21cd remove w32 patches (other branch isn't merged yet) 2025-06-30 12:56:12 +07:00
crazywhalecc
44c6d6288e Refactor LinuxMuslCheck to use MuslToolchain class for toolchain check 2025-06-30 13:04:49 +08:00
Jerry Ma
e5848086c3 Update bin/spc-gnu-docker
Co-authored-by: Marc <m@pyc.ac>
2025-06-30 12:19:17 +08:00
Jerry Ma
138e5588e9 Update bin/spc-gnu-docker
Co-authored-by: Marc <m@pyc.ac>
2025-06-30 12:18:49 +08:00
crazywhalecc
ab5828a560 Remove redundant path in gnu docker 2025-06-30 09:05:56 +08:00
DubbleClick
1839f13149 thanks, autocomplete, for the useless flag 2025-06-29 22:57:25 +07:00
DubbleClick
320c21d272 -static, not static 2025-06-29 22:26:30 +07:00
crazywhalecc
e1e48922de Use isStatic instead of isStaticTarget 2025-06-29 22:49:48 +08:00
DubbleClick
3940f032af fix tests 2025-06-29 21:09:59 +07:00
crazywhalecc
956688d9f4 Fix CI again 2025-06-29 19:53:22 +08:00
crazywhalecc
7f45f4aeec Fix phpunit, add SPC_SKIP_TOOLCHAIN_CHECK 2025-06-29 19:48:09 +08:00
DubbleClick
78666ff7de support for frankenphp linked dynamically too 2025-06-29 18:28:42 +07:00
DubbleClick
dbdf10c75a support for dynamic linking of musl libc 2025-06-29 18:24:25 +07:00
DubbleClick
2564da2615 Reapply "just strip out the error and return the output instead of executing again without version"
This reverts commit 1d29ac228c.
2025-06-29 16:56:24 +07:00
DubbleClick
bd25de15cc okay this drove me crazy... 2025-06-29 16:50:19 +07:00
crazywhalecc
fcaa7c5f42 Add no-env-check for builder 2025-06-29 16:21:22 +08:00
crazywhalecc
e36800b951 phpunit fix 2025-06-29 16:03:38 +08:00
crazywhalecc
977fbaa8ef Suggestions 2025-06-29 16:00:17 +08:00
DubbleClick
6ed62aa05c use CC=cc to install xcaddy 2025-06-29 13:31:43 +07:00
DubbleClick
1d29ac228c Revert "just strip out the error and return the output instead of executing again without version"
This reverts commit d588e6e4fd.
2025-06-29 10:40:48 +07:00
DubbleClick
a44d90671c fix existing check for Zig::fetch 2025-06-29 00:49:39 +07:00
DubbleClick
d6b0912492 run glibc test with shared extensions against 2.28 this time 2025-06-29 00:40:03 +07:00
DubbleClick
d588e6e4fd just strip out the error and return the output instead of executing again without version 2025-06-29 00:06:40 +07:00
DubbleClick
19ddddf618 self:: instead of SourcePatcher:: 2025-06-28 23:45:02 +07:00
DubbleClick
a7bf1e9676 we need to patch swoole early enough 2025-06-28 23:33:13 +07:00
DubbleClick
02f2837f86 adjust craft command for zig 2025-06-28 22:55:30 +07:00
crazywhalecc
0598eff9c5 Add suffix support for SPC_TARGET 2025-06-28 23:13:40 +08:00
crazywhalecc
a6364389ba Add suffix support for SPC_TARGET 2025-06-28 23:11:26 +08:00
DubbleClick
9e9474a026 extra patch for swoole for CFLAGS too 2025-06-28 22:09:58 +07:00
crazywhalecc
c23c5ae614 Disable openmp for imagemagick 2025-06-28 23:08:17 +08:00
DubbleClick
de3cb2f686 no "" 2025-06-28 21:59:12 +07:00
crazywhalecc
454b5a77ad Add SPC_LIBC mapping to SPC_TARGET 2025-06-28 22:59:02 +08:00
DubbleClick
4a493f3f6d don't set rpath because on musl it'll fall over with static binaries 2025-06-28 21:50:57 +07:00
DubbleClick
61274cb865 use extra COMPILER_EXTRA env variable to make sure it's actually passed to compiler 2025-06-28 21:21:29 +07:00
crazywhalecc
8145a7536b Fix spc-gnu-docker path 2025-06-28 19:05:52 +08:00
crazywhalecc
e41d7899c7 Add comments, re-trigger tests 2025-06-28 18:14:10 +08:00
Jerry Ma
88d99a7dea Update src/SPC/store/SourcePatcher.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-28 18:04:45 +08:00
crazywhalecc
90823e3b17 Use explict glibc toolchain target 2025-06-28 17:28:02 +08:00
crazywhalecc
3357f286ab Remove libgomp deps 2025-06-28 17:13:22 +08:00
crazywhalecc
ad080da026 Refactor micro patching logic in SourcePatcher 2025-06-28 17:06:51 +08:00
crazywhalecc
25c2def710 Remove unnecessary SPC_MICRO_PATCHES 2025-06-28 16:47:19 +08:00
crazywhalecc
604131b31d Use own exception 2025-06-28 16:45:20 +08:00
crazywhalecc
924da6ae69 extension test 2025-06-28 16:39:02 +08:00
crazywhalecc
12aadf18cc refactor: replace SPC_LIBC with SPC_TARGET and update related logic 2025-06-28 16:36:05 +08:00
DubbleClick
aa516522aa why isn't this triggered? 2025-06-27 23:35:04 +07:00
DubbleClick
efb1ff5a93 test fix 2? 2025-06-27 22:56:49 +07:00
DubbleClick
aff9ff20e8 test fix? 2025-06-27 22:55:23 +07:00
DubbleClick
4c8a173213 build_lib_path is better 2025-06-27 22:53:18 +07:00
DubbleClick
b6240f16fb clean files before compiling because it may try to link the wrong one 2025-06-27 22:51:53 +07:00
DubbleClick
f5281535d9 strip libphp.so and frankenphp 2025-06-27 22:48:15 +07:00
DubbleClick
96babd0939 crazy test 2025-06-27 22:06:19 +07:00
DubbleClick
464c2dca85 cs fix 2025-06-27 22:01:56 +07:00
DubbleClick
43dc04b4d0 building shared libphp.so on musl is really pointless and should be an error, since static compilations cannot load it 2025-06-27 22:01:37 +07:00
DubbleClick
e92f043cfa really stupid patch for swoole 2025-06-27 21:38:16 +07:00
DubbleClick
ff95b464ce really stupid patch for swoole 2025-06-27 21:26:06 +07:00
DubbleClick
69e1acd5ae really stupid patch 2025-06-27 20:28:59 +07:00
DubbleClick
e162a0ecd6 libaom uses different PIC check 2025-06-27 19:37:10 +07:00
DubbleClick
383045d1c0 cs fix 2025-06-27 19:27:02 +07:00
DubbleClick
e6bec8eb57 fix lib extra cflags 2025-06-27 19:26:08 +07:00
DubbleClick
d2dea83c63 don't check for musl toolkit when running zig, add -static to ncurses 2025-06-27 18:21:22 +07:00
DubbleClick
797aaf1ea8 remove libdir 2025-06-27 14:51:36 +07:00
DubbleClick
5a7fd8c24e we shouldn't need that, need to figure out the cause 2025-06-27 09:26:57 +07:00
DubbleClick
cc4ec4b598 add back debug 2025-06-27 00:17:24 +07:00
DubbleClick
55d78579c8 pgsql x86_64 fix? 2025-06-27 00:11:28 +07:00
Marc
d94542049c Update test-extensions.php
no debug do I can see where it fails
2025-06-26 22:58:48 +07:00
DubbleClick
6dec25b4f4 space 2025-06-26 22:03:08 +07:00
DubbleClick
8c7049913b can build shared extensions on ubuntu 24.04 now 2025-06-26 17:56:33 +07:00
DubbleClick
7edd8e6e19 test glibc of course 2025-06-26 17:39:42 +07:00
DubbleClick
2b4eb7127d what the frick is going on with my phpstorm 2025-06-26 17:39:05 +07:00
DubbleClick
9a3ce775a8 complete test suite 2025-06-26 17:36:50 +07:00
DubbleClick
2ff78e531e cs fix, where did that come from? 2025-06-26 17:25:46 +07:00
DubbleClick
03a33d87ff fix frankenphp build 2025-06-26 17:23:37 +07:00
DubbleClick
176e5627e5 hahaha, musl 2.17 is a little far away 2025-06-26 16:49:26 +07:00
DubbleClick
5801d05047 test musl (I have not tested this yet!) 2025-06-26 16:47:07 +07:00
DubbleClick
815ba75c76 we can build musl with zig cc too, don't need musl toolchain 2025-06-26 16:43:37 +07:00
DubbleClick
fff16b4019 refactor our 2025-06-26 16:03:57 +07:00
DubbleClick
e408a2cf1d suggestion and aarch64 fix 2025-06-26 15:55:41 +07:00
DubbleClick
b535600297 this is insane 2025-06-26 15:44:21 +07:00
DubbleClick
762c554468 test zig 2025-06-26 14:39:49 +07:00
DubbleClick
a8c7057aba fix windows 2025-06-26 14:30:42 +07:00
DubbleClick
935f3fc37c cs fix 2025-06-26 14:29:00 +07:00
DubbleClick
fae2878c41 refactor env var logic out to packages, abomination of a zig-cc script... 2025-06-26 14:24:00 +07:00
DubbleClick
d30ccb810c Merge remote-tracking branch 'origin/main' into zig 2025-06-26 12:30:50 +07:00
DubbleClick
8d75a85d6d ziggy 2025-06-26 12:30:04 +07:00
DubbleClick
625bfd1bec don't escape 2025-06-26 11:18:34 +07:00
Jerry Ma
b04ffadf13 Merge pull request #756 from crazywhalecc/feat/intl-win
Add intl support for windows
2025-06-26 10:50:05 +08:00
DubbleClick
b142610800 move to systemutil 2025-06-25 14:52:50 +07:00
crazywhalecc
5681722c09 Fix global env manager, add in-ini variable parsing 2025-06-25 15:32:18 +08:00
DubbleClick
9a3a536479 runtime object patch for shared extensions 2025-06-25 14:32:00 +07:00
DubbleClick
55f01149e4 prevent warning 2025-06-25 13:56:12 +07:00
DubbleClick
6f5fc43490 remove extra -lunwind patch since we don't only need it in opcache 2025-06-25 11:26:45 +07:00
DubbleClick
cb62ffdfc2 we manage -fno-sanitize in code, only needed for opcache with jit 2025-06-25 11:24:01 +07:00
DubbleClick
40ac705c46 remove openmp support entirely, system packages distribute it disabled, the ini disables it by default and the authors recommend disabling it. WIth a distinction between libgomp, libomp and the non-existent usable static libraries for them, it's just not worth it. 2025-06-25 11:21:58 +07:00
Marc
3ac4a71085 add missing comma 2025-06-25 10:31:29 +07:00
Marc
2963ced1d5 Merge branch 'main' into feat/intl-win 2025-06-25 10:28:45 +07:00
Marc
175aafee50 Merge pull request #800 from crazywhalecc/asm_arith
remove disabling of asm arithmetic
2025-06-25 10:15:37 +07:00
Marc
b75a3d6e81 Merge pull request #804 from crazywhalecc/feat/del-download-type
Allow deleting pre-built or source only for del-download
2025-06-25 09:33:52 +07:00
DubbleClick
5334727528 remove -L/usr/lib64 (need to put it in the zig-cc script, stupid to add it in 15 different places instead) 2025-06-24 23:07:17 +07:00
DubbleClick
253206de22 better 2025-06-24 22:57:12 +07:00
DubbleClick
5a60f4c02b another librdkafka patch 2025-06-24 22:56:48 +07:00
DubbleClick
63fcdd9db4 default param 2025-06-24 21:04:08 +07:00
DubbleClick
832c0230e2 getcctype default 2025-06-24 21:03:32 +07:00
DubbleClick
e1028b0348 swoole has date time conversion error in c++ code, clang needs -lunwind 2025-06-24 20:43:38 +07:00
DubbleClick
2433752273 needless default 2025-06-24 13:17:43 +07:00
DubbleClick
1b0b551fc6 handle case where libphp.so is built instead of libphp-release.so 2025-06-24 12:10:01 +07:00
DubbleClick
74849a8d48 -fno-sanitize=undefined is enabled in zig by default, not in gcc and clang 2025-06-24 09:05:44 +07:00
DubbleClick
ee0de6933f deprecated linker option 2025-06-23 19:21:51 +07:00
DubbleClick
bd863dba34 zig is clang 2025-06-23 15:26:39 +07:00
DubbleClick
f9af24e246 dont fail on -lomp 2025-06-23 13:23:13 +07:00
DubbleClick
864c55feaf oops 2025-06-23 13:12:40 +07:00
DubbleClick
7628847a46 suboptimal solutions, but a bit closer to building 2025-06-23 12:57:00 +07:00
DubbleClick
955b586908 fix lz4 2025-06-22 19:51:55 +07:00
DubbleClick
32a1fd45df don't disable warnings 2025-06-22 17:21:28 +07:00
DubbleClick
15fbde29d4 remove duplicate 2025-06-22 17:02:16 +07:00
DubbleClick
2cce02bfa9 liblz4 replacement 2025-06-22 17:01:08 +07:00
DubbleClick
7e35de8a0e CXX for watcher-c 2025-06-22 16:19:01 +07:00
DubbleClick
9a98fd9cff patch things for zig compatibility 2025-06-22 15:52:01 +07:00
crazywhalecc
f6d25153c6 Allow deleting pre-built or source only for del-download 2025-06-22 16:04:50 +08:00
DubbleClick
853294e168 remove disabling of asm arithmetic 2025-06-21 15:47:38 +07:00
Marc
1a4296386a Merge pull request #798 from crazywhalecc/fix/curl-h3
bring back http3 support for curl
2025-06-21 11:58:39 +07:00
DubbleClick
c464f78340 dont build exe 2025-06-21 10:58:54 +07:00
DubbleClick
8992c96014 remove test 2025-06-21 10:55:06 +07:00
DubbleClick
62a13f2da6 bring back http3 support for curl 2025-06-21 10:16:35 +07:00
Marc
f9005757bd Merge pull request #794 from crazywhalecc/perl-ipc-cmd
revert perl-IPC-Cmd
2025-06-20 18:50:35 +07:00
DubbleClick
cd6aca832d add back perl-IPC-Cmd for spc-gnu-docker (not included in perl package on aarch64, apparently) 2025-06-20 18:43:38 +07:00
crazywhalecc
1b4eb039ae Change the patch position for -release module suffix 2025-06-20 18:46:36 +08:00
Marc
6c47065686 Merge pull request #782 from crazywhalecc/fix/aarch64-uv-pthread
fix uv missing pthread_atfork in aarch64 centos 7
2025-06-20 17:22:52 +07:00
DubbleClick
9bfcea6feb tests 2025-06-20 17:13:01 +07:00
DubbleClick
58d979712e suggestion 2025-06-20 17:11:52 +07:00
Jerry Ma
a0f99858e3 Fix alpine-docker build in different arch issue (#787)
* Fix alpine-docker build in different arch issue

* Fix spc-gnu-docker for multi-arch build issue
2025-06-20 17:43:25 +08:00
DubbleClick
2abbb75f98 phpstan doesn't like GNU_ARCH there 2025-06-20 15:58:42 +07:00
DubbleClick
3f92df0865 patch before make for uv fix 2025-06-20 15:57:16 +07:00
DubbleClick
be0b98c467 patch before make for uv 2025-06-20 15:27:28 +07:00
DubbleClick
7d45415990 patch before make for uv 2025-06-20 15:25:07 +07:00
Marc
f3f581fe2d Merge pull request #784 from crazywhalecc/revert/embed-option
Revert build-embed option as boolean
2025-06-20 07:33:13 +07:00
crazywhalecc
f6837079d3 Revert build-embed option as boolean 2025-06-20 01:57:45 +08:00
Marc
5cd987ba3a since we explicitly delete module folder before building, we should reintroduce this check (#783)
otherwise shared extensions that other shared extensions depend on will be built multiple times
2025-06-20 01:27:33 +08:00
Marc
8293160a01 fix bug with only --build-embed (#781)
* passing --build-embed sets the result to NULL, not true (and not false, like for options that are not passed)

* prettify

* make sure embed can only contain null, static, shared or false
2025-06-20 01:27:02 +08:00
DubbleClick
625ee2703d uv needs to explicitly link -lpthread on aarch64, fails to pthread_atfork unresolved otherwise. It's a symbols that's in the static library, but not in the shared library 2025-06-19 22:06:11 +07:00
Marc
c3b520c3d4 Merge pull request #779 from crazywhalecc/docs
remove old extension file before building new php version
2025-06-19 18:45:22 +07:00
Jerry Ma
80687dfea3 Add macOS strip option -S, fix macOS micro strip option (#778)
* Add macOS strip option `-S`, fix macOS micro strip option

* Change docs and help messages
2025-06-19 19:31:25 +08:00
DubbleClick
7f9da6478f remove old extension file before building new php version 2025-06-19 18:13:00 +07:00
Marc
056971fcbc Merge pull request #776 from crazywhalecc/docs
protobuf and grpc don't conflict when they're built shared
2025-06-19 18:09:05 +07:00
DubbleClick
ba26359dde protobuf and grpc don't conflict when they're built shared 2025-06-19 15:21:25 +07:00
Jerry Ma
a2f0640b1b Merge pull request #767 from crazywhalecc/docs
patchelf and rename -release tagged extensions from php configure build
2025-06-19 15:57:10 +08:00
DubbleClick
16a4245ad7 common static extension tests 2025-06-19 12:40:42 +07:00
DubbleClick
608a5559ac fix incorrect array offset 2025-06-19 12:38:05 +07:00
DubbleClick
dfac385d21 suggestions 2025-06-19 12:23:33 +07:00
DubbleClick
40d602c82e xcaddy fails with this, because the module still calls itself dunglas/frankenphp 2025-06-19 12:08:53 +07:00
DubbleClick
fbd6360bda rename go-mod-frankenphp to go-xcaddy 2025-06-19 12:07:22 +07:00
crazywhalecc
a0047e3ad7 Merge remote-tracking branch 'origin/docs' into docs 2025-06-19 13:01:41 +08:00
crazywhalecc
437d6810b7 Bump version, update docs, change owner dunglas to php 2025-06-19 13:01:26 +08:00
DubbleClick
82ec7733ba musl: all-static! 2025-06-19 11:59:48 +07:00
crazywhalecc
1357990c4c Add github token hook for curlExec 2025-06-19 12:51:00 +08:00
DubbleClick
61a9264802 libraries must link against -lphp dynamic! 2025-06-19 11:20:57 +07:00
DubbleClick
4a70f260f3 test alpine directly too (spc-alpine-docker) 2025-06-19 11:12:58 +07:00
DubbleClick
bcea2007bd base arch instead of hardcoded x86_64 2025-06-19 11:04:10 +07:00
DubbleClick
fcf2c967ab fix setting of variables 2025-06-19 11:01:30 +07:00
DubbleClick
2f8e225abd remove copy of property that meant downloader would only lock one source at a time 2025-06-19 11:00:07 +07:00
DubbleClick
5f3f999222 run go mod frankenphp automatically when running craft 2025-06-19 10:36:31 +07:00
DubbleClick
2bc9fef758 cs fix 2025-06-19 10:09:42 +07:00
DubbleClick
aec03b2f24 move enable zts check 2025-06-19 10:08:51 +07:00
DubbleClick
ba6ed137c6 remove old file deletions 2025-06-19 10:03:35 +07:00
DubbleClick
32dc5d3cdb leftover double path 2025-06-19 10:02:22 +07:00
DubbleClick
597db25178 frankenphp requires ZTS 2025-06-19 10:00:56 +07:00
DubbleClick
c2d6b9ad2c refactor lock file constant out 2025-06-19 09:50:38 +07:00
DubbleClick
4977286936 fix test 2025-06-19 09:43:13 +07:00
DubbleClick
cc09184183 fix test 2025-06-19 09:34:44 +07:00
DubbleClick
a9713c3bfa polish merge 2025-06-19 09:32:55 +07:00
Marc
3af40a66dd Merge pull request #772 from crazywhalecc/sapi/frankenphp
Sapi/frankenphp
2025-06-19 09:30:16 +07:00
DubbleClick
6e70f16e1b Merge remote-tracking branch 'origin/docs' into sapi/frankenphp 2025-06-19 09:22:25 +07:00
DubbleClick
16fccf8184 Merge remote-tracking branch 'origin/sapi/frankenphp' into sapi/frankenphp 2025-06-19 09:14:49 +07:00
DubbleClick
804468f7b9 refactor common exec code out 2025-06-19 09:14:39 +07:00
crazywhalecc
7dc3b7c8ac Merge remote-tracking branch 'origin/main' into sapi/frankenphp 2025-06-19 10:11:26 +08:00
DubbleClick
b42409efd1 LD_LIBRARY_PATH for frankenphp sanity check 2025-06-19 09:08:42 +07:00
DubbleClick
cb010d81ac there's no documented functionality to download without building - xcaddy is meant to do both in one step 2025-06-19 09:03:40 +07:00
DubbleClick
15979d4636 fix double path 2025-06-19 08:59:56 +07:00
Marc
1a164fa057 Merge pull request #775 from crazywhalecc/sapi/frankenphp-prerequisites
Sapi/frankenphp prerequisites
2025-06-19 08:54:42 +07:00
crazywhalecc
a76f49f927 Remove libxml2 requirement for linux 2025-06-18 21:56:36 +08:00
crazywhalecc
becee5b426 Use version instead of -v 2025-06-18 21:27:07 +08:00
crazywhalecc
4ecaffd908 Fix test-extensions.php 2025-06-18 21:08:05 +08:00
crazywhalecc
74b1dda884 Fix test-extensions.php 2025-06-18 21:06:08 +08:00
crazywhalecc
d6858e18df phpstan fix 2025-06-18 20:57:14 +08:00
crazywhalecc
92284e92c9 Refactor go and frankenphp downloads and builds 2025-06-18 20:56:07 +08:00
crazywhalecc
f709f3bb18 Add custom package downloader and extractor 2025-06-18 20:55:24 +08:00
crazywhalecc
8e2dffc3b5 Add frankenphp sapi embed build at build command, not constant 2025-06-18 20:54:54 +08:00
crazywhalecc
ae569316ff Remove go download from doctor 2025-06-18 20:54:01 +08:00
DubbleClick
eee2ff6d61 don't embed minor version when loading libphp.so 2025-06-18 15:55:14 +07:00
DubbleClick
65b828c424 embed version information 2025-06-18 15:50:55 +07:00
DubbleClick
f10ba86218 add extension test for frankenphp 2025-06-18 14:18:01 +07:00
Jerry Ma
24e19deb58 Merge pull request #768 from crazywhalecc/ci/commit-tests
Add commit tests
2025-06-18 14:16:34 +08:00
crazywhalecc
f7a3f80689 Add test extensions as trigger 2025-06-18 14:06:59 +08:00
crazywhalecc
b4168d09b5 Add test extensions as trigger 2025-06-18 14:06:36 +08:00
Jerry Ma
ba0ea5b40a Refactor lock component to a single class (#773) 2025-06-18 14:05:43 +08:00
Marc
04cefda66c Merge pull request #774 from crazywhalecc/fix/arm64-musl
ah, the infamous arm64 bug with -fpic vs -fPIC
2025-06-18 12:46:57 +07:00
DubbleClick
8c6a708764 ah, the infamous arm64 bug with -fpic vs -fPIC
it's a bit slower, but oh well
2025-06-18 12:46:05 +07:00
DubbleClick
a1e76d9d02 remove watcher suggestion 2025-06-18 12:41:27 +07:00
DubbleClick
82ee6f0dee allow specifying if we want to build embed shared or static 2025-06-18 12:35:48 +07:00
DubbleClick
d58534b07d add support for frankenphp directory from file system, instead of pulling latest xcaddy module 2025-06-18 12:19:33 +07:00
DubbleClick
f37c863092 only needed on linux 2025-06-18 12:04:01 +07:00
DubbleClick
e71f76288b support building static frankenphp 2025-06-18 12:02:37 +07:00
DubbleClick
d094824d76 --with github.com/dunglas/caddy-cbrotli requires brotli 2025-06-18 11:54:03 +07:00
DubbleClick
d635b10e24 specify system gcc to build xcaddy in spc-gnu-docker 2025-06-18 11:47:05 +07:00
DubbleClick
dca43d6d8d nicer escaping 2025-06-18 11:39:22 +07:00
DubbleClick
abf3bfb98e suggest watcher 2025-06-18 11:36:58 +07:00
DubbleClick
c46f8513dd watcher... 2025-06-18 11:34:05 +07:00
DubbleClick
92338d478e don't bake the rpath in, otherwise we might run into issues when loading frankenphp after compiling a different version 2025-06-18 11:30:04 +07:00
DubbleClick
c1e68323c7 cs fix 2025-06-18 11:21:50 +07:00
DubbleClick
f64eb0dea5 build for bsd and macos too 2025-06-18 11:20:05 +07:00
DubbleClick
c1870af1b1 add frankenphp sapi 2025-06-18 11:15:16 +07:00
crazywhalecc
71783088c0 Use new trigger test message 2025-06-18 11:55:13 +08:00
crazywhalecc
7057a135cf Trigger extension test 2025-06-18 11:54:10 +08:00
crazywhalecc
5cb107b844 Test test 2025-06-18 11:53:17 +08:00
crazywhalecc
57b22782d3 Define env in phpunit.xml 2025-06-18 10:50:53 +07:00
crazywhalecc
cb0a90d1d9 Add source hash comparator & refactor download lock 2025-06-18 10:50:53 +07:00
DubbleClick
1c439a01a1 Merge remote-tracking branch 'origin/main' into docs 2025-06-18 10:49:43 +07:00
crazywhalecc
2bfc8e92ef Test test 2025-06-18 11:49:42 +08:00
crazywhalecc
68548cf248 Wrap it to test test test 2025-06-18 11:42:32 +08:00
crazywhalecc
3a64feefd0 Change test strategy for commit tests 2025-06-18 11:34:51 +08:00
DubbleClick
da75d2d707 cs fix 2025-06-17 18:04:27 +07:00
DubbleClick
3a85d96fa4 yet another damn centos 7 patch 2025-06-17 18:03:27 +07:00
DubbleClick
883cc4b6fd patch on 2.17... 2025-06-17 15:53:14 +07:00
DubbleClick
aa61a9e77b extra info on ext load failure 2025-06-17 14:01:53 +07:00
DubbleClick
9de5c62136 extract elsewhere temporarily 2025-06-17 12:34:56 +07:00
DubbleClick
7b3ea7e12e fix installing patchelf 2025-06-17 12:30:11 +07:00
DubbleClick
bafa67c8de add patchelf to gnu docker 2025-06-17 12:19:59 +07:00
crazywhalecc
0d3a80e582 Define env in phpunit.xml 2025-06-16 12:48:37 +08:00
crazywhalecc
5a401a5f92 Add source hash comparator & refactor download lock 2025-06-16 12:48:37 +08:00
crazywhalecc
0e88cdb258 Add shared extension parser
{craft}
extensions: bcmath
shared-extensions: xdebug,swoole
sapi: cli
{/craft}
[spc_prefix:bin/spc-gnu-docker]
2025-06-14 13:49:29 +08:00
crazywhalecc
e5cd3adf97 Next pr, I won't run 2025-06-14 02:36:47 +08:00
crazywhalecc
6253b7a912 Next pr, I won't run 2025-06-14 02:35:37 +08:00
crazywhalecc
fe455bf901 Fix shared-extensions as optional
{craft}
extensions: curl,ast
sapi: cli,micro
{/craft}
2025-06-14 02:30:00 +08:00
crazywhalecc
3a0d21eb44 Support multi-line
{craft}
extensions: curl,ast
sapi: cli,micro
{/craft}
2025-06-14 02:27:48 +08:00
crazywhalecc
45ec0cef24 Add checkout
{craft}
extensions: curl,ast
sapi: cli,micro
{/craft}
2025-06-14 02:20:52 +08:00
crazywhalecc
1468bb99f0 Add commit tests
{craft}
extensions: curl,ast
sapi: cli,micro
{/craft}
2025-06-14 02:06:12 +08:00
crazywhalecc
3efabee153 Remove redundant sanity check call 2025-06-14 01:13:33 +08:00
DubbleClick
962de5b25f add edant/watcher, to make spc-packages easier 2025-06-13 22:51:35 +07:00
DubbleClick
b265d6dd56 don't set unknown linker flags on macos 2025-06-13 16:25:31 +07:00
DubbleClick
302cf8345d properly handle different php versions, clean up only required files 2025-06-13 13:01:02 +07:00
DubbleClick
d249391816 don't add configure command to phpinfo when -release is set 2025-06-13 00:08:09 +07:00
DubbleClick
0a24a6af1f move check when to build to extension.php instead of builder 2025-06-12 20:51:17 +07:00
DubbleClick
52f40b7f9f the release option also affects shared extensions, which is unwanted, patchelf their soname back and rename them 2025-06-12 20:20:33 +07:00
Marc
8fbe6ee8ff Merge pull request #766 from crazywhalecc/docs
rebuild shared extensions even if they already exist
2025-06-12 19:20:05 +07:00
DubbleClick
5a3a8db772 rebuild shared extensions even if they already exist 2025-06-12 19:17:44 +07:00
Marc
56cd6711ce Merge pull request #764 from crazywhalecc/docs
remove incorrect space after --enable-yac
2025-06-12 14:21:02 +07:00
DubbleClick
29339b962c remove incorrect space after --enable-yac 2025-06-12 14:20:04 +07:00
crazywhalecc
6b330fa869 Remove redundant env 2025-06-10 19:13:35 +08:00
crazywhalecc
5cc753dec4 cs fix 2025-06-10 19:12:40 +08:00
crazywhalecc
f7a0f50f87 Sort config 2025-06-10 19:10:24 +08:00
crazywhalecc
8466970a1f Add tests 2025-06-10 19:07:16 +08:00
crazywhalecc
48cb87ada2 Add intl support for windows 2025-06-10 19:06:07 +08:00
109 changed files with 2321 additions and 555 deletions

View File

@@ -7,7 +7,11 @@
> If your PR involves the changes mentioned below and completed the action, please tick the corresponding option.
> If a modification is not involved, please skip it directly.
- [ ] If you modified `*.php`, run `composer cs-fix` at local machine.
- [ ] If it's an extension or dependency update, make sure adding related extensions in `src/global/test-extensions.php`.
- [ ] If you changed the behavior of static-php-cli, update docs in `./docs/`.
- [ ] If you updated `config/xxx.json` content, run `bin/spc dev:sort-config xxx`.
- If you modified `*.php` or `*.json`, run them locally to ensure your changes are valid:
- [ ] `PHP_CS_FIXER_IGNORE_ENV=1 composer cs-fix`
- [ ] `composer analyse`
- [ ] `composer test`
- [ ] `bin/spc dev:sort-config`
- If it's an extension or dependency update, please ensure the following:
- [ ] Add your test combination to `src/globals/test-extensions.php`.
- [ ] If adding new or fixing bugs, add commit message containing `extension test` or `test extensions` to trigger full test suite.

View File

@@ -1,16 +1,14 @@
name: "Extension matrix tests"
name: "Extension Matrix Tests"
on:
workflow_dispatch:
pull_request:
branches: [ "main" ]
paths:
- '.github/workflows/ext-matrix-tests.yml'
push:
jobs:
test:
name: "${{ matrix.extension }} (PHP ${{ matrix.php-version }} on ${{ matrix.operating-system }})"
runs-on: ${{ matrix.operating-system }}
if: contains(github.event.head_commit.message, 'extension test') || contains(github.event.head_commit.message, 'test extensions')
strategy:
fail-fast: false
matrix:

View File

@@ -190,7 +190,7 @@ jobs:
echo "UPX_CMD=$(php src/globals/test-extensions.php upx)" >> $GITHUB_ENV
- name: "Run Build Tests (download)"
run: GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} php src/globals/test-extensions.php download_cmd ${{ matrix.os }} ${{ matrix.php }}
run: php src/globals/test-extensions.php download_cmd ${{ matrix.os }} ${{ matrix.php }}
- name: "Run Build Tests (build)"
run: php src/globals/test-extensions.php build_cmd ${{ matrix.os }} ${{ matrix.php }}

View File

@@ -69,4 +69,5 @@ return (new PhpCsFixer\Config())
])
->setFinder(
PhpCsFixer\Finder::create()->in([__DIR__ . '/src', __DIR__ . '/tests/SPC'])
);
)
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect());

View File

@@ -9,7 +9,7 @@
**static-php-cli**是一个用于静态编译、构建 PHP 解释器的工具,支持众多流行扩展。
目前 static-php-cli 支持 `cli``fpm``embed``micro` SAPI。
目前 static-php-cli 支持 `cli``fpm``embed``micro``frankenphp` SAPI。
**static-php-cli**也支持将 PHP 代码和 PHP 运行时打包为一个文件并运行。
@@ -278,7 +278,7 @@ bin/spc micro:combine my-app.phar -I "memory_limit=4G" -I "disable_functions=sys
如果你知道 [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed),你应该知道如何使用它。对于有可能编译用到引入其他库的问题,你可以使用 `buildroot/bin/php-config` 来获取编译时的配置。
另外,有关如何使用此功能的高级示例,请查看[如何使用它构建 FrankenPHP 的静态版本](https://github.com/dunglas/frankenphp/blob/main/docs/static.md)。
另外,有关如何使用此功能的高级示例,请查看[如何使用它构建 FrankenPHP 的静态版本](https://github.com/php/frankenphp/blob/main/docs/static.md)。
## 贡献
@@ -292,6 +292,12 @@ bin/spc micro:combine my-app.phar -I "memory_limit=4G" -I "disable_functions=sys
你可以在 [我的个人赞助页](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md) 支持我和我的项目。你捐赠的一部分将会被用于维护 **static-php.dev** 服务器。
**特别赞助商**:
<a href="https://beyondco.de/"><img src="/docs/public/images/beyondcode-seeklogo.png" width="300" alt="Beyond Code Logo" /></a>
<a href="https://nativephp.com/"><img src="/docs/public/images/nativephp-logo.svg" width="300" alt="NativePHP Logo" /></a>
## 开源协议
本项目采用 MIT License 许可开源,下面是类似的项目:

View File

@@ -10,7 +10,7 @@
**static-php-cli** is a powerful tool designed for building static, standalone PHP runtime
with popular extensions.
Static PHP built by **static-php-cli** supports `cli`, `fpm`, `embed` and `micro` SAPI.
Static PHP built by **static-php-cli** supports `cli`, `fpm`, `embed`, `micro` and `frankenphp` SAPI.
**static-php-cli** also has the ability to package PHP projects
along with the PHP interpreter into one single executable file.
@@ -302,7 +302,7 @@ If you know [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed),
You may require the introduction of other libraries during compilation,
you can use `buildroot/bin/php-config` to obtain the compile-time configuration.
For an advanced example of how to use this feature, take a look at [how to use it to build a static version of FrankenPHP](https://github.com/dunglas/frankenphp/blob/main/docs/static.md).
For an advanced example of how to use this feature, take a look at [how to use it to build a static version of FrankenPHP](https://github.com/php/frankenphp/blob/main/docs/static.md).
## Contribution
@@ -317,6 +317,12 @@ Now there is a [static-php](https://github.com/static-php) organization, which i
You can sponsor me or my project from [GitHub Sponsor](https://github.com/crazywhalecc). A portion of your donation will be used to maintain the **static-php.dev** server.
**Special thanks to sponsors below**:
<a href="https://beyondco.de/"><img src="/docs/public/images/beyondcode-seeklogo.png" width="300" alt="Beyond Code Logo" /></a>
<a href="https://nativephp.com/"><img src="/docs/public/images/nativephp-logo.svg" width="300" alt="NativePHP Logo" /></a>
## Open-Source License
This project itself is based on MIT License,

View File

@@ -1,12 +1,14 @@
#!/usr/bin/env sh
#!/usr/bin/env bash
set -e
# This file is using docker to run commands
SPC_DOCKER_VERSION=v3
SPC_DOCKER_VERSION=v4
# Detect docker can run
if ! which docker >/dev/null; then
echo "Docker is not installed, please install docker first !"
exit 1
echo "Docker is not installed, please install docker first !"
exit 1
fi
DOCKER_EXECUTABLE="docker"
# shellcheck disable=SC2046
@@ -22,27 +24,48 @@ if [ $(id -u) -ne 0 ]; then
fi
fi
# to check if qemu-docker run
if [ "$SPC_USE_ARCH" = "" ]; then
SPC_USE_ARCH=x86_64
# Convert uname to gnu arch
CURRENT_ARCH=$(uname -m)
if [ "$CURRENT_ARCH" = "arm64" ]; then
CURRENT_ARCH=aarch64
fi
if [ -z "$SPC_USE_ARCH" ]; then
SPC_USE_ARCH=$CURRENT_ARCH
fi
# parse SPC_USE_ARCH
case $SPC_USE_ARCH in
x86_64)
ALPINE_FROM=alpine:edge
x86_64|amd64)
SPC_USE_ARCH=x86_64
if [ "$CURRENT_ARCH" != "x86_64" ]; then
PLATFORM_ARG="--platform linux/amd64"
ALPINE_FROM=multiarch/alpine:x86_64-edge
fi
;;
aarch64)
ALPINE_FROM=multiarch/alpine:aarch64-edge
# shellcheck disable=SC2039
echo -e "\e[033m* Using different arch needs to setup qemu-static for docker !\e[0m"
$DOCKER_EXECUTABLE run --rm --privileged multiarch/qemu-user-static:register --reset > /dev/null
aarch64|arm64)
SPC_USE_ARCH=aarch64
if [ "$CURRENT_ARCH" != "aarch64" ]; then
PLATFORM_ARG="--platform linux/arm64"
ALPINE_FROM=multiarch/alpine:aarch64-edge
fi
;;
*)
echo "Current arch is not supported to run in docker: $SPC_USE_ARCH"
exit 1
;;
esac
# if ALPINE_FROM is not set, use alpine:edge
if [ -z "$ALPINE_FROM" ]; then
ALPINE_FROM=alpine:edge
fi
if [ "$SPC_USE_ARCH" != "$CURRENT_ARCH" ]; then
echo "* Using different arch needs to setup qemu-static for docker !"
ALPINE_FROM=multiarch/alpine:$SPC_USE_ARCH-edge
if [ "$(uname -s)" = "Linux" ]; then
$DOCKER_EXECUTABLE run --rm --privileged multiarch/qemu-user-static:register --reset > /dev/null
fi
else
ALPINE_FROM=alpine:edge
fi
if [ "$SPC_USE_MIRROR" = "yes" ]; then
SPC_USE_MIRROR="RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories"
@@ -53,7 +76,7 @@ fi
# Detect docker env is setup
if ! $DOCKER_EXECUTABLE images | grep -q cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION; then
echo "Docker container does not exist. Building docker image ..."
$DOCKER_EXECUTABLE build -t cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION -f- . <<EOF
$DOCKER_EXECUTABLE build $PLATFORM_ARG -t cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION -f- . <<EOF
FROM $ALPINE_FROM
$SPC_USE_MIRROR
RUN apk update; \
@@ -84,7 +107,8 @@ RUN apk update; \
wget \
xz \
gettext-dev \
binutils-gold
binutils-gold \
patchelf
RUN curl -#fSL https://dl.static-php.dev/static-php-cli/bulk/php-8.4.4-cli-linux-\$(uname -m).tar.gz | tar -xz -C /usr/local/bin && \
chmod +x /usr/local/bin/php
@@ -147,7 +171,7 @@ if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then
echo "* ./pkgroot: $(pwd)/pkgroot"
echo "*"
set -ex
$DOCKER_EXECUTABLE run --rm $INTERACT $ENV_LIST $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION
$DOCKER_EXECUTABLE run $PLATFORM_ARG --rm $INTERACT $ENV_LIST $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION /bin/bash
else
$DOCKER_EXECUTABLE run --rm $INTERACT $ENV_LIST $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION bin/spc $@
$DOCKER_EXECUTABLE run $PLATFORM_ARG --rm $INTERACT $ENV_LIST $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION bin/spc $@
fi

View File

@@ -1,5 +1,10 @@
#!/usr/bin/env bash
set -e
# This file is using docker to run commands
SPC_DOCKER_VERSION=v4
# Detect docker can run
if ! which docker >/dev/null; then
echo "Docker is not installed, please install docker first !"
@@ -19,35 +24,47 @@ if [ $(id -u) -ne 0 ]; then
fi
fi
# to check if qemu-docker run
if [ "$SPC_USE_ARCH" = "" ]; then
SPC_USE_ARCH=current
# Convert uname to gnu arch
CURRENT_ARCH=$(uname -m)
if [ "$CURRENT_ARCH" = "arm64" ]; then
CURRENT_ARCH=aarch64
fi
if [ -z "$SPC_USE_ARCH" ]; then
SPC_USE_ARCH=$CURRENT_ARCH
fi
# parse SPC_USE_ARCH
case $SPC_USE_ARCH in
current)
BASE_ARCH=$(uname -m)
if [ "$BASE_ARCH" = "arm64" ]; then
BASE_ARCH=aarch64
x86_64|amd64)
SPC_USE_ARCH=x86_64
SPC_USE_ARCH_DOCKER=amd64
if [ "$CURRENT_ARCH" != "x86_64" ]; then
PLATFORM_ARG="--platform linux/amd64"
fi
;;
aarch64)
BASE_ARCH=aarch64
# shellcheck disable=SC2039
echo -e "\e[033m* Using different arch needs to setup qemu-static for docker !\e[0m"
$DOCKER_EXECUTABLE run --rm --privileged multiarch/qemu-user-static:register --reset > /dev/null
aarch64|arm64)
SPC_USE_ARCH=aarch64
SPC_USE_ARCH_DOCKER=arm64
if [ "$CURRENT_ARCH" != "aarch64" ]; then
PLATFORM_ARG="--platform linux/arm64"
fi
;;
*)
echo "Current arch is not supported to run in docker: $SPC_USE_ARCH"
exit 1
;;
esac
# detect if we need to use qemu-static
if [ "$SPC_USE_ARCH" != "$CURRENT_ARCH" ]; then
if [ "$(uname -s)" = "Linux" ]; then
echo "* Using different arch needs to setup qemu-static for docker !"
$DOCKER_EXECUTABLE run --rm --privileged multiarch/qemu-user-static --reset -p yes > /dev/null
fi
fi
# Detect docker env is setup
if ! $DOCKER_EXECUTABLE images | grep -q cwcc-spc-gnu-$SPC_USE_ARCH; then
if ! $DOCKER_EXECUTABLE images | grep -q cwcc-spc-gnu-$SPC_USE_ARCH-$SPC_DOCKER_VERSION; then
echo "Docker container does not exist. Building docker image ..."
$DOCKER_EXECUTABLE build -t cwcc-spc-gnu-$SPC_USE_ARCH -f- . <<EOF
$DOCKER_EXECUTABLE buildx build $PLATFORM_ARG -t cwcc-spc-gnu-$SPC_USE_ARCH-$SPC_DOCKER_VERSION -f- . <<EOF
FROM centos:7
RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/*.repo && \
sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/*.repo && \
@@ -59,7 +76,7 @@ RUN yum clean all && \
RUN yum install -y centos-release-scl
RUN if [ "$BASE_ARCH" = "aarch64" ]; then \
RUN if [ "$SPC_USE_ARCH" = "aarch64" ]; then \
sed -i 's|mirror.centos.org/centos|vault.centos.org/altarch|g' /etc/yum.repos.d/CentOS-SCLo-scl-rh.repo ; \
sed -i 's|mirror.centos.org/centos|vault.centos.org/altarch|g' /etc/yum.repos.d/CentOS-SCLo-scl.repo ; \
else \
@@ -74,7 +91,12 @@ RUN echo "source scl_source enable devtoolset-10" >> /etc/bashrc
RUN source /etc/bashrc
RUN yum install -y which
RUN curl -o cmake.tgz -fsSL https://github.com/Kitware/CMake/releases/download/v3.31.4/cmake-3.31.4-linux-$BASE_ARCH.tar.gz && \
RUN curl -fsSL -o patchelf.tgz https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-$SPC_USE_ARCH.tar.gz && \
mkdir -p /patchelf && \
tar -xzf patchelf.tgz -C /patchelf --strip-components=1 && \
cp /patchelf/bin/patchelf /usr/bin/
RUN curl -o cmake.tgz -#fSL https://github.com/Kitware/CMake/releases/download/v3.31.4/cmake-3.31.4-linux-$SPC_USE_ARCH.tar.gz && \
mkdir /cmake && \
tar -xzf cmake.tgz -C /cmake --strip-components 1
@@ -85,11 +107,11 @@ ADD ./bin/setup-runtime /app/bin/setup-runtime
ADD ./bin/spc /app/bin/spc
RUN /app/bin/setup-runtime
RUN /app/bin/php /app/bin/composer install --no-dev
ENV PATH="/app/bin:/cmake/bin:$PATH"
ENV SPC_LIBC=glibc
ENV PATH="/app/bin:/cmake/bin:/opt/rh/devtoolset-10/root/usr/bin:\$PATH"
ADD ./config/env.ini /app/config/env.ini
RUN bin/spc doctor --auto-fix --debug
RUN CC=gcc bin/spc doctor --auto-fix --debug
RUN curl -o make.tgz -fsSL https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && \
tar -zxvf make.tgz && \
@@ -106,7 +128,6 @@ RUN curl -o automake.tgz -fsSL https://ftp.gnu.org/gnu/automake/automake-1.17.ta
make && \
make install && \
ln -sf /usr/local/bin/automake /usr/bin/automake
EOF
fi
@@ -132,11 +153,7 @@ if [ -f "$(pwd)/craft.yml" ]; then
fi
# Apply env in temp env file
echo 'CC=/opt/rh/devtoolset-10/root/usr/bin/gcc' > /tmp/spc-gnu-docker.env
echo 'CXX=/opt/rh/devtoolset-10/root/usr/bin/g++' >> /tmp/spc-gnu-docker.env
echo 'AR=/opt/rh/devtoolset-10/root/usr/bin/ar' >> /tmp/spc-gnu-docker.env
echo 'LD=/opt/rh/devtoolset-10/root/usr/bin/ld' >> /tmp/spc-gnu-docker.env
echo 'SPC_DEFAULT_C_FLAGS=-fPIE -fPIC' >> /tmp/spc-gnu-docker.env
echo 'SPC_DEFAULT_C_FLAGS=-fPIC' > /tmp/spc-gnu-docker.env
echo 'SPC_LIBC=glibc' >> /tmp/spc-gnu-docker.env
echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-Wl,-O1 -pie"' >> /tmp/spc-gnu-docker.env
echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm -lresolv -lutil -lrt"' >> /tmp/spc-gnu-docker.env
@@ -168,7 +185,7 @@ if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then
echo "* ./pkgroot: $(pwd)/pkgroot"
echo "*"
set -ex
$DOCKER_EXECUTABLE run --rm -it --privileged $INTERACT $ENV_LIST --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH
$DOCKER_EXECUTABLE run $PLATFORM_ARG --privileged --rm -it $INTERACT $ENV_LIST --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH-$SPC_DOCKER_VERSION /bin/bash
else
$DOCKER_EXECUTABLE run --rm $INTERACT $ENV_LIST --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH bin/spc $@
$DOCKER_EXECUTABLE run $PLATFORM_ARG --rm $INTERACT $ENV_LIST --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH-$SPC_DOCKER_VERSION bin/spc $@
fi

View File

@@ -42,6 +42,9 @@ SPC_CONCURRENCY=${CPU_COUNT}
SPC_SKIP_PHP_VERSION_CHECK="no"
; Ignore some check item for bin/spc doctor command, comma separated (e.g. SPC_SKIP_DOCTOR_CHECK_ITEMS="if homebrew has installed")
SPC_SKIP_DOCTOR_CHECK_ITEMS=""
; extra modules that xcaddy will include in the FrankenPHP build
SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/frankenphp/caddy --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli"
; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them
; only useful for builds targeting not pure-static linking
; default paths
@@ -52,6 +55,8 @@ SPC_SKIP_DOCTOR_CHECK_ITEMS=""
; EXTENSION_DIR=
[windows]
; build target: win7-static
SPC_TARGET=native-windows
; php-sdk-binary-tools path
PHP_SDK_PATH="${WORKING_DIR}\php-sdk-binary-tools"
; upx executable path
@@ -60,22 +65,37 @@ UPX_EXEC="${PKG_ROOT_PATH}\bin\upx.exe"
SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime140,win32,zend_stream,cli_static
[linux]
; include PATH for musl libc.
; Linux can use different build toolchains.
; - musl (default, when SPC_LIBC=musl): used for general linux distros, can build `musl` (statically linked) only.
; - zig (will become default): usable on all Linux distros, can build `-musl`, `arch-linux-musl -dynamic` and `arch-linux-gnu` targets. Can specify version such as `x86_64-linux-gnu.2.17`.
; - musl-native: used for alpine linux, can build `musl` and `musl -dynamic` target.
; - gnu-native: used for general linux distros, can build gnu target for the installed glibc version only.
; LEGACY option to specify the target, switch to SPC_TARGET with zig toolchain instead
SPC_LIBC=musl
; Recommended: specify your target here. Zig toolchain will be used.
; examples:
; `native-native-gnu` - links against glibc, current OS version
; `native-native-gnu.2.17` - links against glibc, version 2.17
; `native-native` - static linking against musl
; `native-native-musl -dynamic` - links against musl libc
; SPC_TARGET=
; compiler environments
CC=${SPC_LINUX_DEFAULT_CC}
CXX=${SPC_LINUX_DEFAULT_CXX}
AR=${SPC_LINUX_DEFAULT_AR}
LD=ld.gold
LD=${SPC_LINUX_DEFAULT_LD}
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
SPC_DEFAULT_C_FLAGS="-fpic -Os"
SPC_DEFAULT_CXX_FLAGS="-fpic -Os"
SPC_DEFAULT_C_FLAGS="-fPIC -Os -Wno-error=date-time"
SPC_DEFAULT_CXX_FLAGS="-fPIC -Os -Wno-error=date-time"
; extra libs for building php executable, used in `make` command for building php (this value may changed by extension build process, space separated)
SPC_EXTRA_LIBS=
; upx executable path
UPX_EXEC=${PKG_ROOT_PATH}/bin/upx
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches
SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime140,win32,zend_stream
SPC_MICRO_PATCHES=cli_checks,disable_huge_page
; *** default build command for building php ***
; buildconf command
@@ -89,23 +109,26 @@ SPC_CMD_VAR_PHP_EMBED_TYPE="static"
; *** default build vars for building php ***
; CFLAGS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS="${SPC_DEFAULT_C_FLAGS} -fpie"
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS="${SPC_DEFAULT_C_FLAGS} -fPIE"
; CPPFLAGS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS="-I${BUILD_INCLUDE_PATH}"
; LDFLAGS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}"
; LIBS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lpthread -lm"
SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lrt -lpthread -lm -lresolv -lutil"
; EXTRA_CFLAGS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fno-ident -fpie ${SPC_DEFAULT_C_FLAGS}"
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fno-ident -fPIE ${SPC_DEFAULT_C_FLAGS}"
; EXTRA_LIBS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm"
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lrt -lpthread -lm -lresolv -lutil"
; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS=""
; EXTRA_LDFLAGS_PROGRAM for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-all-static -Wl,-O1 -pie"
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-all-static -pie"
[macos]
; build target: macho or macho (possibly we could support macho-universal in the future)
; Currently we do not support universal and cross-compilation for macOS.
SPC_TARGET=native-macos
; compiler environments
CC=clang
CXX=clang++
@@ -115,7 +138,7 @@ SPC_DEFAULT_CXX_FLAGS="--target=${MAC_ARCH}-apple-darwin -Os"
; extra libs for building php executable, used in `make` command for building php (this value may changed by extension build process, space separated)
SPC_EXTRA_LIBS=
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches
SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime140,win32,zend_stream,macos_iconv
SPC_MICRO_PATCHES=cli_checks,macos_iconv
; *** default build command for building php ***
; buildconf command

View File

@@ -304,12 +304,14 @@
},
"intl": {
"support": {
"Windows": "no",
"BSD": "wip"
},
"type": "builtin",
"lib-depends": [
"lib-depends-unix": [
"icu"
],
"lib-depends-windows": [
"icu-static-win"
]
},
"ldap": {
@@ -336,6 +338,9 @@
},
"type": "builtin",
"arg-type": "none",
"ext-depends": [
"xml"
],
"target": [
"static"
]

View File

@@ -207,6 +207,18 @@
"libicudata.a"
]
},
"icu-static-win": {
"source": "icu-static-win",
"static-libs-windows": [
"icudt.lib",
"icuin.lib",
"icuio.lib",
"icuuc.lib"
],
"headers-windows": [
"unicode"
]
},
"imagemagick": {
"source": "imagemagick",
"static-libs-unix": [
@@ -854,5 +866,14 @@
"zstd.h",
"zstd_errors.h"
]
},
"watcher": {
"source": "watcher",
"static-libs-unix": [
"libwatcher-c.a"
],
"headers": [
"wtr/watcher-c.h"
]
}
}

View File

@@ -42,5 +42,32 @@
"extract-files": {
"upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe"
}
},
"go-xcaddy-x86_64-linux": {
"type": "custom"
},
"go-xcaddy-aarch64-linux": {
"type": "custom"
},
"go-xcaddy-x86_64-macos": {
"type": "custom"
},
"go-xcaddy-aarch64-macos": {
"type": "custom"
},
"zig-x86_64-linux": {
"type": "custom"
},
"zig-aarch64-linux": {
"type": "custom"
},
"zig-x86_64-macos": {
"type": "custom"
},
"zig-aarch64-macos": {
"type": "custom"
},
"zig-x86_64-win": {
"type": "custom"
}
}

View File

@@ -342,6 +342,14 @@
"path": "LICENSE"
}
},
"icu-static-win": {
"type": "url",
"url": "https://dl.static-php.dev/static-php-cli/deps/icu-static-windows-x64/icu-static-windows-x64.zip",
"license": {
"type": "text",
"text": "none"
}
},
"igbinary": {
"type": "url",
"url": "https://pecl.php.net/get/igbinary",
@@ -1070,5 +1078,14 @@
"type": "file",
"path": "LICENSE"
}
},
"watcher": {
"type": "ghtar",
"repo": "e-dant/watcher",
"prefer-stable": true,
"license": {
"type": "file",
"path": "license"
}
}
}

View File

@@ -47,6 +47,10 @@ export default {
nav: [],
socialLinks: [
{icon: 'github', link: 'https://github.com/crazywhalecc/static-php-cli'}
]
],
footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2023-present crazywhalecc'
}
}
}

View File

@@ -2,6 +2,7 @@
import DefaultTheme from 'vitepress/theme'
import {inBrowser, useData} from "vitepress";
import {watchEffect} from "vue";
import './style.css';
export default {
...DefaultTheme,
@@ -13,4 +14,4 @@ export default {
}
})
}
}
}

View File

@@ -0,0 +1,6 @@
/** override default styles */
.vp-sponsor-grid-image {
max-height:36px !important;
max-width: 1000px !important;
}

View File

@@ -42,6 +42,9 @@ build-options:
# Set micro SAPI as win32 mode, without this, micro SAPI will be compiled as a console application (only for Windows, default: false)
enable-micro-win32: false
# Build options for shared extensions (list or comma-separated are both accepted)
shared-extensions: [ ]
# Download options
download-options:
# Use custom url for specified sources, format: "{source-name}:{url}" (e.g. "php-src:https://example.com/php-8.4.0.tar.gz")

View File

@@ -38,17 +38,11 @@ buildroot/bin/php -d "zend_extension=/path/to/php{PHP_VER}-{ts/nts}/xdebug.so" -
```
For macOS platform, almost all binaries under macOS cannot be truly purely statically linked, and almost all binaries will link macOS system libraries: `/usr/lib/libresolv.9.dylib` and `/usr/lib/libSystem.B.dylib`.
So on macOS, you can use statically compiled PHP binaries under certain compilation conditions, and dynamically linked extensions:
So on macOS, you can **directly** use SPC to build statically compiled PHP binaries with dynamically linked extensions:
1. Using the `--no-strip` parameter will not strip information such as debugging symbols from the binary file for use with external Zend extensions such as `Xdebug`.
2. If you want to compile some Zend extensions, use Homebrew, MacPorts, source code compilation, and install a normal version of PHP on your operating system.
3. Use the `phpize && ./configure && make` command to compile the extensions you want to use.
4. Copy the extension file `xxxx.so` to the outside, use the statically compiled PHP binary, for example to use the Xdebug extension: `cd buildroot/bin/ && ./php -d "zend_extension=/path/to/xdebug.so"`.
```bash
# build statically linked php-cli but not stripped
bin/spc build ffi --build-cli --no-strip
```
1. Build shared extension `xxx.so` using: `--build-shared=XXX` option. e.g. `bin/spc build bcmath,zlib --build-shared=xdebug --build-cli`
2. You will get `buildroot/modules/xdebug.so` and `buildroot/bin/php`.
3. The `xdebug.so` file could be used for php that version and thread-safe are the same.
## Can it support Oracle database extension?

View File

@@ -48,17 +48,17 @@ This extension contains an implementation of the coroutine environment for `pdo_
## swow
1. Only PHP 8.0 ~ 8.4 is supported.
1. Only PHP 8.0+ is supported.
## imagick
1. The imagick extension currently only has openmp support on musl libc. This means that multithreading is disabled on glibc or other operating systems. The extension is still fully functional.
1. OpenMP support is disabled, this is recommended by the maintainers and also the case system packages.
## imap
1. Kerberos is not supported
2. ext-imap is not thread safe due to the underlying c-client. It's not possible to use it in --enable-zts builds.
3. Because the extension may be dropped from php, we recommend you look for an alternative implementation, such as [Webklex/php-imap](https://github.com/Webklex/php-imap)
2. ext-imap is not thread safe due to the underlying c-client. It's not possible to use it in `--enable-zts` builds.
3. The extension was dropped from php 8.4, we recommend you look for an alternative implementation, such as [Webklex/php-imap](https://github.com/Webklex/php-imap)
## gd
@@ -82,7 +82,7 @@ and this extension cannot be compiled into php by static linking, so it cannot b
## xdebug
1. Xdebug is only buildable as a shared extension. On Linux, you need to use static-php-cli with SPC_LIBC=glibc.
1. Xdebug is only buildable as a shared extension. On Linux, you'll need to use a SPC_TARGET like `native-native -dynamic` or `native-native-gnu`.
2. When using Linux/glibc or macOS, you can compile Xdebug as a shared extension using --build-shared="xdebug".
The compiled `./php` binary can be configured and run by specifying the INI, eg `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`.

View File

@@ -167,6 +167,7 @@ If the build is successful, you will see the `buildroot/bin` directory in the cu
- fpm: The build result is `buildroot/bin/php-fpm`.
- micro: The build result is `buildroot/bin/micro.sfx`. If you need to further package it with PHP code, please refer to [Packaging micro binary](./manual-build#command-micro-combine).
- embed: See [Using embed](./manual-build#embed-usage).
- frankenphp: The build result is `buildroot/bin/frankenphp`.
If the build fails, you can use the `--debug` parameter to view detailed error information,
or use the `--with-clean` to clear the old compilation results and recompile.
@@ -290,6 +291,7 @@ You need to specify a compilation target, choose from the following parameters:
- `--build-fpm`: Build a fpm sapi (php-fpm, used in conjunction with other traditional fpm architecture software such as nginx)
- `--build-micro`: Build a micro sapi (used to build a standalone executable binary containing PHP code)
- `--build-embed`: Build an embed sapi (used to embed into other C language programs)
- `--build-frankenphp`: Build a [FrankenPHP](https://github.com/php/frankenphp) executable
- `--build-all`: build all above sapi
```bash
@@ -337,7 +339,7 @@ You can try to use the following commands:
- `--cxx=XXX`: Specifies the execution command of the C++ language compiler (Linux defaults to `g++`, macOS defaults to `clang++`)
- `--with-clean`: clean up old make files before compiling PHP
- `--enable-zts`: Make compiled PHP thread-safe version (default is NTS version)
- `--no-strip`: Do not run `strip` after compiling the PHP library to trim the binary file to reduce its size (the macOS binary file without trim can use dynamically linked third-party extensions)
- `--no-strip`: Do not run `strip` after compiling the PHP library to trim the binary file to reduce its size
- `--with-libs=XXX,YYY`: Compile the specified dependent library before compiling PHP, and activate some extended optional functions (such as libavif of the gd library, etc.)
- `--with-config-file-path=XXX`: Set the path in which to look for `php.ini` (Check [here](../faq/index.html#what-is-the-path-of-php-ini) for default paths)
- `--with-config-file-scan-dir=XXX`: Set the directory to scan for `.ini` files after reading `php.ini` (Check [here](../faq/index.html#what-is-the-path-of-php-ini) for default paths)
@@ -509,6 +511,8 @@ When `bin/spc doctor` automatically repairs the Windows environment, tools such
Here is an example of installing the tool:
- Download and install UPX (Linux and Windows only): `bin/spc install-pkg upx`
- Download and install nasm (Windows only): `bin/spc install-pkg nasm`
- Download and install go-xcaddy: `bin/spc install-pkg go-xcaddy`
## Command - del-download

View File

@@ -21,3 +21,16 @@ features:
- title: Dependency Management
details: static-php-cli comes with dependency management and supports installation of different types of PHP extensions.
---
<script setup>
import {VPSponsors} from "vitepress/theme";
const sponsors = [
{ name: 'Beyond Code', img: '/images/beyondcode-seeklogo.png', url: 'https://beyondco.de/' },
{ name: 'NativePHP', img: '/images/nativephp-logo.svg', url: 'https://nativephp.com/' },
];
</script>
## Special Sponsors
<VPSponsors :data="sponsors"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 764 100">
<path fill="#505b93" d="M29.5,98.4H0V1.2h29.5V98.4z"/>
<path fill="#00aaa6" d="M96.7,98.4H67.2V1.2h29.5V98.4z"/>
<path fill="#272d48" d="M96.7,98.4H29.5V1.2L96.7,98.4z"/>
<path fill="#272d48" d="M102.6,54.5c0-6.2,1.3-11.8,4.1-16.7c2.7-5,6.7-8.8,11.9-11.6c5.2-2.8,11.4-4.2,18.6-4.2c1.1,0,2.8,0.1,5.2,0.3 c4.9,0.4,9.1,1.3,12.5,2.8c3.4,1.5,6.4,3.4,9.1,5.9v-7.2h25.8v74.7h-25.8v-7.2c-2.5,2.6-5.6,4.6-9.4,6.1c-3.8,1.5-7.9,2.4-12.1,2.6 c-0.9,0.1-2.3,0.1-4.1,0.1c-7.2,0-13.4-1.4-18.8-4.1c-5.4-2.7-9.5-6.6-12.4-11.5c-2.9-4.9-4.3-10.5-4.3-16.6V54.5z M128.4,63.9 c0,9.2,5.9,13.8,17.7,13.8c11.8,0,17.7-4.6,17.7-13.8v-5.6c0-9.3-5.9-14-17.7-14c-11.8,0-17.7,4.7-17.7,14V63.9z M208.3,98.4V47h-12.8V23.7l12.8-0.2V1.2h25.9l0.1,22.4h18.3V47h-18.4v51.4 M287.3,98.4h-25.9V23.6h25.9V98.4z M351,98.4H325l-31-74.8H320L338,68l18-44.5h25.9L351,98.4z M454.9,87.6c-4.1,4.2-8.8,7.3-14.1,9.3c-5.3,2-11.8,2.9-19.5,2.9c-8,0-15.1-1.4-21.3-4.2 c-6.2-2.8-11.1-6.7-14.7-11.6c-3.5-5-5.3-10.6-5.3-17V54.6c0-6.5,1.7-12.2,5.2-17.1c3.4-4.9,8.2-8.7,14.2-11.3c6-2.7,12.7-4,20-4 c8,0,15,1.5,21.2,4.3c6.1,2.9,10.9,7,14.4,12.4c3.4,5.4,5.2,11.6,5.2,18.7c0,1.7-0.2,4.2-0.7,7.7l-54.2,0.1v2.1 c0,4.1,1.5,7.3,4.4,9.4c2.9,2.2,6.9,3.2,11.8,3.2c4.6,0,8.5-0.6,11.8-1.8c3.2-1.2,6.4-3.5,9.4-6.8L454.9,87.6z M434.4,51.4 c0-3.1-1.4-5.5-4.2-7.1c-2.8-1.6-6.4-2.4-10.8-2.4s-7.9,0.8-10.5,2.4c-2.6,1.6-3.8,4-3.8,7.1H434.4z M466,98.4V1.2h56c9.9,0,17.8,1.9,23.7,5.6c5.9,3.7,10,8.2,12.2,13.3c2.3,5.1,3.4,10.1,3.4,15 c0,4.8-1.2,9.7-3.5,14.7c-2.3,5-6.4,9.2-12.2,12.8c-5.8,3.6-13.7,5.4-23.7,5.4h-30.2v30.5H466z M522,44.6c9,0,13.6-3.3,13.6-9.9 c0-6.9-4.6-10.3-13.7-10.3h-30.1v20.2H522z M593,98.4h-25.8V1.2H593v38h43.9v-38h25.8v97.2h-25.8V59.9H593V98.4z M668.6,98.4V1.2h56c9.9,0,17.8,1.9,23.7,5.6c5.9,3.7,10,8.2,12.2,13.3c2.3,5.1,3.4,10.1,3.4,15 c0,4.8-1.2,9.7-3.5,14.7c-2.3,5-6.4,9.2-12.2,12.8c-5.8,3.6-13.7,5.4-23.7,5.4h-30.2v30.5H668.6z M724.6,44.6c9,0,13.6-3.3,13.6-9.9 c0-6.9-4.6-10.3-13.7-10.3h-30.1v20.2H724.6z M291.1,20.2h-33.5V0h33.5V20.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -35,17 +35,11 @@ buildroot/bin/php -d "zend_extension=/path/to/php{PHP_VER}-{ts/nts}/xdebug.so" -
```
对于 macOS 平台来说macOS 下的几乎所有二进制文件都无法真正纯静态链接,几乎所有二进制文件都会链接 macOS 的系统库:`/usr/lib/libresolv.9.dylib``/usr/lib/libSystem.B.dylib`
所以在 macOS 系统下,在特定的编译条件下可以使用静态编译的 php 二进制文件,可使用动态链接的扩展:
因此,在 macOS 上,您可以直接构建出使用静态编译的 PHP 二进制文件动态链接的扩展:
1. 使用 `--no-strip` 参数,将不会对二进制文件去除调试符号等信息,以供使用 `Xdebug` 等外部 Zend 扩展。
2. 如果要编译某些 Zend 扩展,使用 Homebrew、MacPorts、源码编译的形式在所在的操作系统安装一个普通版本的 PHP
3. 使用 `phpize && ./configure && make` 命令编译想要使用的扩展
4. 将扩展文件 `xxxx.so` 拷贝到外部,使用静态编译的 PHP 二进制,例如使用 Xdebug 扩展:`cd buildroot/bin/ && ./php -d "zend_extension=/path/to/xdebug.so"`
```bash
# 构建静态 php-cli
bin/spc build ffi --build-cli --no-strip
```
1. 使用 `--build-shared=XXX` 选项构建共享扩展 `xxx.so`。例如:`bin/spc build bcmath,zlib --build-shared=xdebug --build-cli`
2. 您将获得 `buildroot/modules/xdebug.so``buildroot/bin/php`
3. `xdebug.so` 文件可用于版本和线程安全相同的 php
## 可以支持 Oracle 数据库扩展吗

View File

@@ -45,17 +45,17 @@ swoole-hook-sqlite 与 `pdo_sqlite` 扩展冲突。如需使用 Swoole 和 `pdo_
## swow
1. swow 仅支持 PHP 8.0 ~ 8.4 版本。
1. swow 仅支持 PHP 8.0+ 版本。
## imagick
imagick 扩展目前仅在 musl libc 上支持 OpenMPlibgomp。使用 glibc 方式构建的 imagick 扩展无法支持多线程特性
1. OpenMP 支持已被禁用,这是维护者推荐的做法,系统软件包也是如此配置
## imap
1. 该扩展目前不支持 Kerberos。
2. 由于底层的 c-client、ext-imap 不是线程安全的。 无法在 `--enable-zts` 构建中使用它。
3. 由于该扩展可能会从未来的 PHP 中删除,因此我们建议您寻找替代实现,例如 [Webklex/php-imap](https://github.com/Webklex/php-imap)。
3. 该扩展已在 PHP 8.4 中被移除,因此我们建议您寻找替代实现,例如 [Webklex/php-imap](https://github.com/Webklex/php-imap)。
## gd
@@ -76,7 +76,7 @@ bin/spc build gd --with-libs=freetype,libjpeg,libavif,libwebp --build-cli
## xdebug
1. Xdebug 只能作为共享扩展进行构建。在 Linux 上,您需要使用 static-php-cli 并设置 SPC_LIBC=glibc
1. Xdebug 只能作为共享扩展进行构建。您需要使用除了 `musl-static` 外的其他 `SPC_TARGET` 构建目标
2. 使用 Linux/glibc 或 macOS 时,您可以使用 `--build-shared=xdebug` 将 Xdebug 编译为共享扩展。
编译后的 `./php` 二进制文件可以通过指定 INI 文件进行配置和运行,例如 `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`

View File

@@ -145,6 +145,7 @@ bin/spc craft --debug
- fpm: 构建结果为 `buildroot/bin/php-fpm`
- micro: 构建结果为 `buildroot/bin/micro.sfx`,如需进一步与 PHP 代码打包,请查看 [打包 micro 二进制](./manual-build#命令-micro-combine-打包-micro-二进制)。
- embed: 参见 [embed 使用](./manual-build#embed-使用)。
- frankenphp: 构建结果为 `buildroot/bin/frankenphp`
如果中途构建出错,你可以使用 `--debug` 参数查看详细的错误信息,或者使用 `--with-clean` 参数清除旧的编译结果,重新编译。
@@ -250,6 +251,7 @@ bin/spc doctor --auto-fix
- `--build-fpm`: 构建一个 fpm sapiphp-fpm用于和其他传统的 fpm 架构的软件如 nginx 配合使用)
- `--build-micro`: 构建一个 micro sapi用于构建一个包含 PHP 代码的独立可执行二进制)
- `--build-embed`: 构建一个 embed sapi用于嵌入到其他 C 语言程序中)
- `--build-frankenphp`: 构建一个 [frankenphp](https://github.com/php/frankenphp) 二进制
- `--build-all`: 构建以上所有 sapi
```bash
@@ -294,7 +296,7 @@ bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
- `--cxx=XXX`: 指定 C++ 语言编译器的执行命令Linux 默认 `g++`macOS 默认 `clang++`
- `--with-clean`: 编译 PHP 前先清理旧的 make 产生的文件
- `--enable-zts`: 让编译的 PHP 为线程安全版本(默认为 NTS 版本)
- `--no-strip`: 编译 PHP 库后不运行 `strip` 裁剪二进制文件缩小体积(不裁剪的 macOS 二进制文件可使用动态链接的第三方扩展)
- `--no-strip`: 编译 PHP 库后不运行 `strip` 裁剪二进制文件缩小体积
- `--with-libs=XXX,YYY`: 编译 PHP 前先编译指定的依赖库,激活部分扩展的可选功能(例如 gd 库的 libavif 等)
- `--with-config-file-path=XXX` 查找 `php.ini` 的路径(在 [这里](../faq/index.html#php-ini-的路径是什么) 查看默认路径)
- `--with-config-file-scan-dir=XXX` 读取 `php.ini` 后扫描 `.ini` 文件的目录(在 [这里](../faq/index.html#php-ini-的路径是什么) 查看默认路径)
@@ -457,6 +459,8 @@ bin/spc dev:sort-config ext
下面是安装工具的示例:
- 下载安装 UPX仅限 Linux 和 Windows: `bin/spc install-pkg upx`
- 下载安装 nasm仅限 Windows: `bin/spc install-pkg nasm`
- 下载安装 go-xcaddy: `bin/spc install-pkg go-xcaddy`
## 命令 del-download - 删除已下载的资源

View File

@@ -2,4 +2,8 @@
<phpunit
bootstrap="tests/bootstrap.php"
>
</phpunit>
<php>
<env name="SPC_IGNORE_BAD_HASH" value="yes" force="true" />
<env name="SPC_SKIP_TOOLCHAIN_CHECK" value="yes" force="true" />
</php>
</phpunit>

View File

@@ -9,6 +9,7 @@ use SPC\command\BuildPHPCommand;
use SPC\command\CraftCommand;
use SPC\command\DeleteDownloadCommand;
use SPC\command\dev\AllExtCommand;
use SPC\command\dev\EnvCommand;
use SPC\command\dev\ExtVerCommand;
use SPC\command\dev\GenerateExtDepDocsCommand;
use SPC\command\dev\GenerateExtDocCommand;
@@ -33,7 +34,7 @@ use Symfony\Component\Console\Application;
*/
final class ConsoleApplication extends Application
{
public const VERSION = '2.6.0';
public const VERSION = '2.6.1';
public function __construct()
{
@@ -70,6 +71,7 @@ final class ConsoleApplication extends Application
new GenerateExtDepDocsCommand(),
new GenerateLibDepDocsCommand(),
new PackLibCommand(),
new EnvCommand(),
]
);
}

View File

@@ -12,7 +12,9 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\LockFile;
use SPC\store\SourceManager;
use SPC\store\SourcePatcher;
use SPC\util\CustomExt;
abstract class BuilderBase
@@ -202,6 +204,8 @@ abstract class BuilderBase
$this->emitPatchPoint('before-exts-extract');
SourceManager::initSource(exts: [...$static_extensions, ...$shared_extensions]);
$this->emitPatchPoint('after-exts-extract');
// patch micro
SourcePatcher::patchMicro();
}
foreach ([...$static_extensions, ...$shared_extensions] as $extension) {
@@ -262,17 +266,6 @@ abstract class BuilderBase
if (!$ext->isBuildShared()) {
continue;
}
if (Config::getExt($ext->getName(), 'type') === 'builtin' || Config::getExt($ext->getName(), 'build-with-php') === true) {
if (file_exists(BUILD_MODULES_PATH . '/' . $ext->getName() . '.so')) {
logger()->info('Shared extension [' . $ext->getName() . '] was already built by php-src/configure (' . $ext->getName() . '.so)');
continue;
}
if (Config::getExt($ext->getName(), 'build-with-php') === true) {
logger()->warning('Shared extension [' . $ext->getName() . '] did not build with php-src/configure (' . $ext->getName() . '.so)');
logger()->warning('Try deleting your build and source folders and running `spc build`` again.');
continue;
}
}
$ext->buildShared();
}
} catch (RuntimeException $e) {
@@ -361,15 +354,11 @@ abstract class BuilderBase
public function getPHPVersionFromArchive(?string $file = null): false|string
{
if ($file === null) {
$lock = file_exists(DOWNLOAD_PATH . '/.lock.json') ? file_get_contents(DOWNLOAD_PATH . '/.lock.json') : false;
if ($lock === false) {
return false;
}
$lock = json_decode($lock, true);
$file = $lock['php-src']['filename'] ?? null;
if ($file === null) {
$lock = LockFile::get('php-src');
if ($lock === null) {
return false;
}
$file = LockFile::getLockFullPath($lock);
}
if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) {
return $match[1];
@@ -415,6 +404,9 @@ abstract class BuilderBase
if (($type & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED) {
$ls[] = 'embed';
}
if (($type & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) {
$ls[] = 'frankenphp';
}
return implode(', ', $ls);
}
@@ -521,6 +513,29 @@ abstract class BuilderBase
}
}
public function checkBeforeBuildPHP(int $rule): void
{
if (($rule & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) {
if (!$this->getOption('enable-zts')) {
throw new WrongUsageException('FrankenPHP SAPI requires ZTS enabled PHP, build with `--enable-zts`!');
}
// frankenphp doesn't support windows, BSD is currently not supported by static-php-cli
if (!in_array(PHP_OS_FAMILY, ['Linux', 'Darwin'])) {
throw new WrongUsageException('FrankenPHP SAPI is only available on Linux and macOS!');
}
// frankenphp needs package go-xcaddy installed
$pkg_dir = PKG_ROOT_PATH . '/go-xcaddy-' . arch2gnu(php_uname('m')) . '-' . osfamily2shortname();
if (!file_exists("{$pkg_dir}/bin/go") || !file_exists("{$pkg_dir}/bin/xcaddy")) {
global $argv;
throw new WrongUsageException("FrankenPHP SAPI requires the go-xcaddy package, please install it first: {$argv[0]} install-pkg go-xcaddy");
}
// frankenphp needs libxml2 lib on macos, see: https://github.com/php/frankenphp/blob/main/frankenphp.go#L17
if (PHP_OS_FAMILY === 'Darwin' && !$this->getLib('libxml2')) {
throw new WrongUsageException('FrankenPHP SAPI for macOS requires libxml2 library, please include the `xml` extension in your build.');
}
}
}
/**
* Generate micro extension test php code.
*/

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace SPC\builder;
use SPC\builder\linux\SystemUtil;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
@@ -193,7 +194,7 @@ class Extension
* If you need to patch some code, overwrite this
* return true if you patched something, false if not
*/
public function patchBeforeSharedBuild(): bool
public function patchBeforeSharedPhpize(): bool
{
return false;
}
@@ -208,6 +209,25 @@ class Extension
return false;
}
/**
* Patch code before shared extension make
* If you need to patch some code, overwrite this
* return true if you patched something, false if not
*/
public function patchBeforeSharedMake(): bool
{
$extra = SystemUtil::getExtraRuntimeObjects();
if (!$extra) {
return false;
}
FileSystem::replaceFileRegex(
$this->source_dir . '/Makefile',
"/^(shared_objects_{$this->getName()}\\s*=.*)$/m",
"$1 {$extra}",
);
return true;
}
/**
* @return string
* returns a command line string with all required shared extensions to load
@@ -264,9 +284,12 @@ class Extension
// If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php
// If check failed, throw RuntimeException
$sharedExtensions = $this->getSharedExtensionLoadString();
[$ret] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"');
[$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"');
if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret);
throw new RuntimeException(
'extension ' . $this->getName() . ' failed runtime check: php-cli returned ' . $ret . "\n" .
join("\n", $out)
);
}
if (file_exists(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php')) {
@@ -328,11 +351,21 @@ class Extension
*/
public function buildShared(): void
{
logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)');
if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) {
logger()->info('extension ' . $this->getName() . ' already built, skipping');
return;
if (Config::getExt($this->getName(), 'type') === 'builtin' || Config::getExt($this->getName(), 'build-with-php') === true) {
if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) {
logger()->info('Shared extension [' . $this->getName() . '] was already built by php-src/configure (' . $this->getName() . '.so)');
return;
}
if (Config::getExt($this->getName(), 'build-with-php') === true) {
logger()->warning('Shared extension [' . $this->getName() . '] did not build with php-src/configure (' . $this->getName() . '.so)');
logger()->warning('Try deleting your build and source folders and running `spc build`` again.');
return;
}
}
if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) {
logger()->info('Shared extension [' . $this->getName() . '] was already built, skipping (' . $this->getName() . '.so)');
}
logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)');
foreach ($this->dependencies as $dependency) {
if (!$dependency instanceof Extension) {
continue;
@@ -361,39 +394,56 @@ class Extension
{
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
[$staticLibString, $sharedLibString] = $this->getStaticAndSharedLibs();
// macOS ld64 doesn't understand these, while Linux and BSD do
// use them to make sure that all symbols are picked up, even if a library has already been visited before
$preStatic = PHP_OS_FAMILY !== 'Darwin' ? '-Wl,--start-group ' : '';
$postStatic = PHP_OS_FAMILY !== 'Darwin' ? ' -Wl,--end-group ' : ' ';
$env = [
'CFLAGS' => $config['cflags'],
'CXXFLAGS' => $config['cflags'],
'LDFLAGS' => $config['ldflags'],
'LIBS' => '-Wl,-Bstatic -Wl,--start-group ' . $staticLibString . ' -Wl,--end-group -Wl,-Bdynamic ' . $sharedLibString,
'LIBS' => $preStatic . $staticLibString . $postStatic . $sharedLibString,
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
];
if ($this->patchBeforeSharedPhpize()) {
logger()->info("Extension [{$this->getName()}] patched before shared phpize");
}
// prepare configure args
shell()->cd($this->source_dir)
->setEnv($env)
->appendEnv($this->getExtraEnv())
->exec(BUILD_BIN_PATH . '/phpize');
if ($this->patchBeforeSharedConfigure()) {
logger()->info('ext [ . ' . $this->getName() . '] patching before shared configure');
logger()->info("Extension [{$this->getName()}] patched before shared configure");
}
shell()->cd($this->source_dir)
->setEnv($env)
->appendEnv($this->getExtraEnv())
->exec(
'./configure ' . $this->getUnixConfigureArg(true) .
' --with-php-config=' . BUILD_BIN_PATH . '/php-config ' .
'--enable-shared --disable-static'
);
// some extensions don't define their dependencies well, this patch is only needed for a few
FileSystem::replaceFileRegex(
$this->source_dir . '/Makefile',
'/^(.*_SHARED_LIBADD\s*=.*)$/m',
'$1 ' . $staticLibString
);
if ($this->patchBeforeSharedMake()) {
logger()->info("Extension [{$this->getName()}] patched before shared make");
}
shell()->cd($this->source_dir)
->setEnv($env)
->appendEnv($this->getExtraEnv())
->exec('make clean')
->exec('make -j' . $this->builder->concurrency)
->exec('make install');
@@ -472,14 +522,13 @@ class Extension
*
* @return array [staticLibString, sharedLibString]
*/
private function getStaticAndSharedLibs(): array
protected function getStaticAndSharedLibs(): array
{
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
$sharedLibString = '';
$staticLibString = '';
$staticLibs = $this->getLibFilesString();
$staticLibs = str_replace(BUILD_LIB_PATH . '/lib', '-l', $staticLibs);
$staticLibs = str_replace('.a', '', $staticLibs);
$staticLibs = str_replace([BUILD_LIB_PATH . '/lib', '.a'], ['-l', ''], $staticLibs);
$staticLibs = explode('-l', $staticLibs . ' ' . $config['libs']);
foreach ($staticLibs as $lib) {
$lib = trim($lib);
@@ -487,7 +536,7 @@ class Extension
continue;
}
$static_lib = 'lib' . $lib . '.a';
if (file_exists(BUILD_LIB_PATH . '/' . $static_lib)) {
if (file_exists(BUILD_LIB_PATH . '/' . $static_lib) && !str_contains($static_lib, 'libphp')) {
if (!str_contains($staticLibString, '-l' . $lib . ' ')) {
$staticLibString .= '-l' . $lib . ' ';
}
@@ -498,6 +547,11 @@ class Extension
return [trim($staticLibString), trim($sharedLibString)];
}
protected function getExtraEnv(): array
{
return [];
}
private function getLibraryDependencies(bool $recursive = false): array
{
$ret = array_filter($this->dependencies, fn ($x) => $x instanceof LibraryBase);

View File

@@ -10,6 +10,7 @@ use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\LockFile;
use SPC\store\SourceManager;
use SPC\util\GlobalValueTrait;
@@ -46,12 +47,11 @@ abstract class LibraryBase
*/
public function setup(bool $force = false): int
{
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$source = Config::getLib(static::NAME, 'source');
// if source is locked as pre-built, we just tryInstall it
$pre_built_name = Downloader::getPreBuiltLockName($source);
if (isset($lock[$pre_built_name]) && ($lock[$pre_built_name]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) {
return $this->tryInstall($lock[$pre_built_name]['filename'], $force);
if (($lock = LockFile::get($pre_built_name)) && $lock['lock_as'] === SPC_DOWNLOAD_PRE_BUILT) {
return $this->tryInstall($lock, $force);
}
return $this->tryBuild($force);
}
@@ -166,14 +166,15 @@ abstract class LibraryBase
* @throws WrongUsageException
* @throws FileSystemException
*/
public function tryInstall(string $install_file, bool $force_install = false): int
public function tryInstall(array $lock, bool $force_install = false): int
{
$install_file = $lock['filename'];
if ($force_install) {
logger()->info('Installing required library [' . static::NAME . '] from pre-built binaries');
// Extract files
try {
FileSystem::extractPackage($install_file, DOWNLOAD_PATH . '/' . $install_file, BUILD_ROOT_PATH);
FileSystem::extractPackage($install_file, $lock['source_type'], DOWNLOAD_PATH . '/' . $install_file, BUILD_ROOT_PATH);
$this->install();
return LIB_STATUS_OK;
} catch (FileSystemException|RuntimeException $e) {
@@ -183,19 +184,19 @@ abstract class LibraryBase
}
foreach ($this->getStaticLibs() as $name) {
if (!file_exists(BUILD_LIB_PATH . "/{$name}")) {
$this->tryInstall($install_file, true);
$this->tryInstall($lock, true);
return LIB_STATUS_OK;
}
}
foreach ($this->getHeaders() as $name) {
if (!file_exists(BUILD_INCLUDE_PATH . "/{$name}")) {
$this->tryInstall($install_file, true);
$this->tryInstall($lock, true);
return LIB_STATUS_OK;
}
}
// pkg-config is treated specially. If it is pkg-config, check if the pkg-config binary exists
if (static::NAME === 'pkg-config' && !file_exists(BUILD_ROOT_PATH . '/bin/pkg-config')) {
$this->tryInstall($install_file, true);
$this->tryInstall($lock, true);
return LIB_STATUS_OK;
}
return LIB_STATUS_ALREADY;

View File

@@ -5,28 +5,25 @@ declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\builder\linux\SystemUtil;
use SPC\util\CustomExt;
#[CustomExt('imagick')]
class imagick extends Extension
{
public function patchBeforeMake(): bool
{
if (PHP_OS_FAMILY !== 'Linux') {
return false;
}
if (getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) {
return false;
}
// imagick with calls omp_pause_all, which requires openmp, on non-musl we build imagick without openmp
$extra_libs = trim(getenv('SPC_EXTRA_LIBS') . ' -lgomp');
f_putenv('SPC_EXTRA_LIBS=' . $extra_libs);
return true;
}
public function getUnixConfigureArg(bool $shared = false): string
{
$disable_omp = !(getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) ? '' : ' ac_cv_func_omp_pause_resource_all=no';
$disable_omp = ' ac_cv_func_omp_pause_resource_all=no';
return '--with-imagick=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $disable_omp;
}
protected function getStaticAndSharedLibs(): array
{
[$static, $shared] = parent::getStaticAndSharedLibs();
if (SystemUtil::getLibcVersionIfExists('glibc') && SystemUtil::getLibcVersionIfExists('glibc') <= '2.17') {
$static .= ' -lstdc++';
$shared = str_replace('-lstdc++', '', $shared);
}
return [$static, $shared];
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\builder\windows\WindowsBuilder;
use SPC\store\FileSystem;
use SPC\util\CustomExt;
@@ -13,16 +14,18 @@ class intl extends Extension
{
public function patchBeforeBuildconf(): bool
{
// TODO: remove the following line when https://github.com/php/php-src/pull/14002 will be released
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/ext/intl/config.m4', 'PHP_CXX_COMPILE_STDCXX(11', 'PHP_CXX_COMPILE_STDCXX(17');
// Also need to use clang++ -std=c++17 to force override the default C++ standard
if (is_string($env = getenv('CXX')) && !str_contains($env, 'std=c++17')) {
f_putenv('CXX=' . $env . ' -std=c++17');
if ($this->builder instanceof WindowsBuilder) {
FileSystem::replaceFileStr(
SOURCE_PATH . '/php-src/ext/intl/config.w32',
'EXTENSION("intl", "php_intl.c intl_convert.c intl_convertcpp.cpp intl_error.c ", true,',
'EXTENSION("intl", "php_intl.c intl_convert.c intl_convertcpp.cpp intl_error.c ", PHP_INTL_SHARED,'
);
return true;
}
return true;
return false;
}
public function patchBeforeSharedBuild(): bool
public function patchBeforeSharedPhpize(): bool
{
return $this->patchBeforeBuildconf();
}

View File

@@ -57,4 +57,11 @@ class pgsql extends Extension
}
return '--with-pgsql=' . BUILD_ROOT_PATH;
}
protected function getExtraEnv(): array
{
return [
'CFLAGS' => '-Wno-int-conversion',
];
}
}

View File

@@ -15,8 +15,9 @@ class protobuf extends Extension
if ($this->builder->getPHPVersionID() < 80000 && getenv('SPC_SKIP_PHP_VERSION_CHECK') !== 'yes') {
throw new \RuntimeException('The latest protobuf extension requires PHP 8.0 or later');
}
$grpc = $this->builder->getExt('grpc');
// protobuf conflicts with grpc
if ($this->builder->getExt('grpc') !== null) {
if ($grpc?->isBuildStatic()) {
throw new \RuntimeException('protobuf conflicts with grpc, please remove grpc or protobuf extension');
}
}

View File

@@ -13,8 +13,13 @@ class redis extends Extension
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = '--enable-redis';
$arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session';
$arg .= $this->builder->getExt('igbinary') ? ' --enable-redis-igbinary' : ' --disable-redis-igbinary';
if ($this->isBuildStatic()) {
$arg .= $this->builder->getExt('session')?->isBuildStatic() ? ' --enable-redis-session' : ' --disable-redis-session';
$arg .= $this->builder->getExt('igbinary')?->isBuildStatic() ? ' --enable-redis-igbinary' : ' --disable-redis-igbinary';
} else {
$arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session';
$arg .= $this->builder->getExt('igbinary') ? ' --enable-redis-igbinary' : ' --disable-redis-igbinary';
}
if ($this->builder->getLib('zstd')) {
$arg .= ' --enable-redis-zstd --with-libzstd="' . BUILD_ROOT_PATH . '"';
}

View File

@@ -14,13 +14,14 @@ class swoole extends Extension
{
public function patchBeforeMake(): bool
{
$patched = false;
if ($this->builder instanceof MacOSBuilder) {
// Fix swoole with event extension <util.h> conflict bug
$util_path = shell()->execWithResult('xcrun --show-sdk-path', false)[1][0] . '/usr/include/util.h';
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/ext/swoole/thirdparty/php/standard/proc_open.cc', 'include <util.h>', 'include "' . $util_path . '"');
return true;
$patched = true;
}
return false;
return $patched;
}
public function getExtVersion(): ?string

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\store\FileSystem;
use SPC\util\CustomExt;
#[CustomExt('uv')]
@@ -16,4 +17,14 @@ class uv extends Extension
throw new \RuntimeException('The latest uv extension requires PHP 8.0 or later');
}
}
public function patchBeforeSharedMake(): bool
{
parent::patchBeforeSharedMake();
if (PHP_OS_FAMILY !== 'Linux' || arch2gnu(php_uname('m')) !== 'aarch64') {
return false;
}
FileSystem::replaceFileRegex($this->source_dir . '/Makefile', '/^(LDFLAGS =.*)$/m', '$1 -luv -ldl -lrt -pthread');
return true;
}
}

View File

@@ -21,6 +21,6 @@ class yac extends Extension
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-yac ' . ($shared ? '=shared' : '') . ' --enable-igbinary --enable-json --with-system-fastlz';
return '--enable-yac' . ($shared ? '=shared' : '') . ' --enable-igbinary --enable-json --with-system-fastlz';
}
}

View File

@@ -96,6 +96,7 @@ class BSDBuilder extends UnixBuilderBase
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
shell()->cd(SOURCE_PATH . '/php-src')
->exec(
@@ -143,6 +144,10 @@ class BSDBuilder extends UnixBuilderBase
}
$this->buildEmbed();
}
if ($enableFrankenphp) {
logger()->info('building frankenphp');
$this->buildFrankenphp();
}
}
public function testPHP(int $build_target = BUILD_TARGET_NONE)

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace SPC\builder\freebsd\library;
class watcher extends BSDLibraryBase
{
use \SPC\builder\unix\library\watcher;
public const NAME = 'watcher';
}

View File

@@ -10,7 +10,10 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
use SPC\store\SourcePatcher;
use SPC\toolchain\ToolchainManager;
use SPC\toolchain\ZigToolchain;
use SPC\util\GlobalEnvManager;
use SPC\util\SPCTarget;
class LinuxBuilder extends UnixBuilderBase
{
@@ -25,23 +28,8 @@ class LinuxBuilder extends UnixBuilderBase
{
$this->options = $options;
// check musl-cross make installed if we use musl-cross-make
$arch = arch2gnu(php_uname('m'));
GlobalEnvManager::init($this);
if (getenv('SPC_LIBC') === 'musl' && !SystemUtil::isMuslDist()) {
$this->setOptionIfNotExist('library_path', "LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\"");
$this->setOptionIfNotExist('ld_library_path', "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\"");
GlobalEnvManager::putenv("PATH=/usr/local/musl/bin:/usr/local/musl/{$arch}-linux-musl/bin:" . getenv('PATH'));
$configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE');
$configure = "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\" " . $configure;
GlobalEnvManager::putenv("SPC_CMD_PREFIX_PHP_CONFIGURE={$configure}");
if (!file_exists("/usr/local/musl/{$arch}-linux-musl/lib/libc.a")) {
throw new WrongUsageException('You are building with musl-libc target in glibc distro, but musl-toolchain is not installed, please install musl-toolchain first. (You can use `doctor` command to install it)');
}
}
GlobalEnvManager::init();
GlobalEnvManager::afterInit();
// concurrency
$this->concurrency = intval(getenv('SPC_CONCURRENCY'));
@@ -81,6 +69,7 @@ class LinuxBuilder extends UnixBuilderBase
}
// add libstdc++, some extensions or libraries need it
$extra_libs .= (empty($extra_libs) ? '' : ' ') . ($this->hasCpp() ? '-lstdc++ ' : '');
$extra_libs .= (ToolchainManager::getToolchainClass() === ZigToolchain::class ? ' -lunwind' : '');
f_putenv('SPC_EXTRA_LIBS=' . $extra_libs);
$cflags = $this->arch_c_flags;
f_putenv('CFLAGS=' . $cflags);
@@ -104,16 +93,19 @@ class LinuxBuilder extends UnixBuilderBase
$zts = '';
}
$disable_jit = $this->getOption('disable-opcache-jit', false) ? '--disable-opcache-jit ' : '';
if (!$disable_jit && $this->getExt('opcache')) {
f_putenv('COMPILER_EXTRA=-fno-sanitize=undefined');
}
$config_file_path = $this->getOption('with-config-file-path', false) ?
('--with-config-file-path=' . $this->getOption('with-config-file-path') . ' ') : '';
$config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ?
('--with-config-file-scan-dir=' . $this->getOption('with-config-file-scan-dir') . ' ') : '';
$enable_cli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
$enable_fpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enable_micro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enable_embed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
$enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
// prepare build php envs
@@ -125,7 +117,7 @@ class LinuxBuilder extends UnixBuilderBase
]);
// process micro upx patch if micro sapi enabled
if ($enable_micro) {
if ($enableMicro) {
if (version_compare($this->getMicroVersion(), '0.2.0') < 0) {
// for phpmicro 0.1.x
$this->processMicroUPXLegacy();
@@ -134,13 +126,19 @@ class LinuxBuilder extends UnixBuilderBase
}
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
if ($embed_type !== 'static' && SPCTarget::isStatic()) {
throw new WrongUsageException(
'Linux does not support loading shared libraries when linking libc statically. ' .
'Change SPC_CMD_VAR_PHP_EMBED_TYPE to static.'
);
}
shell()->cd(SOURCE_PATH . '/php-src')
->exec(
getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
($enable_cli ? '--enable-cli ' : '--disable-cli ') .
($enable_fpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') .
($enable_embed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
($enable_micro ? '--enable-micro=all-static ' : '--disable-micro ') .
($enableCli ? '--enable-cli ' : '--disable-cli ') .
($enableFpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') .
($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
$config_file_path .
$config_file_scan_dir .
$disable_jit .
@@ -156,25 +154,29 @@ class LinuxBuilder extends UnixBuilderBase
$this->cleanMake();
if ($enable_cli) {
if ($enableCli) {
logger()->info('building cli');
$this->buildCli();
}
if ($enable_fpm) {
if ($enableFpm) {
logger()->info('building fpm');
$this->buildFpm();
}
if ($enable_micro) {
if ($enableMicro) {
logger()->info('building micro');
$this->buildMicro();
}
if ($enable_embed) {
if ($enableEmbed) {
logger()->info('building embed');
if ($enable_micro) {
if ($enableMicro) {
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la');
}
$this->buildEmbed();
}
if ($enableFrankenphp) {
logger()->info('building frankenphp');
$this->buildFrankenphp();
}
}
public function testPHP(int $build_target = BUILD_TARGET_NONE)
@@ -285,16 +287,48 @@ class LinuxBuilder extends UnixBuilderBase
->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install");
$ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS');
$realLibName = 'libphp.so';
if (preg_match('/-release\s+(\S+)/', $ldflags, $matches)) {
$release = $matches[1];
$realLibName = 'libphp-' . $release . '.so';
$realLib = BUILD_LIB_PATH . '/' . $realLibName;
rename(BUILD_LIB_PATH . '/libphp.so', $realLib);
$cwd = getcwd();
chdir(BUILD_LIB_PATH);
symlink($realLibName, 'libphp.so');
$libphpPath = BUILD_LIB_PATH . '/libphp.so';
$libphpRelease = BUILD_LIB_PATH . '/' . $realLibName;
if (!file_exists($libphpRelease) && file_exists($libphpPath)) {
rename($libphpPath, $libphpRelease);
}
if (file_exists($libphpRelease)) {
chdir(BUILD_LIB_PATH);
if (file_exists($libphpPath)) {
unlink($libphpPath);
}
symlink($realLibName, 'libphp.so');
}
if (is_dir(BUILD_MODULES_PATH)) {
chdir(BUILD_MODULES_PATH);
foreach ($this->getExts() as $ext) {
if (!$ext->isBuildShared()) {
continue;
}
$name = $ext->getName();
$versioned = "{$name}-{$release}.so";
$unversioned = "{$name}.so";
if (is_file(BUILD_MODULES_PATH . "/{$versioned}")) {
rename(BUILD_MODULES_PATH . "/{$versioned}", BUILD_MODULES_PATH . "/{$unversioned}");
shell()->cd(BUILD_MODULES_PATH)
->exec(sprintf(
'patchelf --set-soname %s %s',
escapeshellarg($unversioned),
escapeshellarg($unversioned)
));
}
}
}
chdir($cwd);
}
if (!$this->getOption('no-strip', false) && file_exists(BUILD_LIB_PATH . '/' . $realLibName)) {
shell()->cd(BUILD_LIB_PATH)->exec("strip --strip-all {$realLibName}");
}
$this->patchPhpScripts();
}

View File

@@ -6,6 +6,8 @@ namespace SPC\builder\linux;
use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\RuntimeException;
use SPC\toolchain\ToolchainManager;
use SPC\toolchain\ZigToolchain;
class SystemUtil
{
@@ -13,6 +15,8 @@ class SystemUtil
public static ?string $libc_version = null;
private static ?string $extra_runtime_objects = null;
/** @noinspection PhpMissingBreakStatementInspection */
public static function getOSRelease(): array
{
@@ -78,9 +82,11 @@ class SystemUtil
/**
* @throws RuntimeException
*/
public static function getCCType(string $cc): string
public static function getCCType(?string $cc = null): string
{
$cc ??= getenv('CC');
return match (true) {
str_contains($cc, 'zig') => 'clang',
str_ends_with($cc, 'c++'), str_ends_with($cc, 'cc'), str_ends_with($cc, 'g++'), str_ends_with($cc, 'gcc') => 'gcc',
$cc === 'clang++', $cc === 'clang', str_starts_with($cc, 'musl-clang') => 'clang',
default => throw new RuntimeException("unknown cc type: {$cc}"),
@@ -188,12 +194,12 @@ class SystemUtil
/**
* Get libc version string from ldd
*/
public static function getLibcVersionIfExists(): ?string
public static function getLibcVersionIfExists(?string $libc = null): ?string
{
if (self::$libc_version !== null) {
return self::$libc_version;
}
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'glibc') {
if ($libc === 'glibc') {
$result = shell()->execWithResult('ldd --version', false);
if ($result[0] !== 0) {
return null;
@@ -208,7 +214,7 @@ class SystemUtil
}
return null;
}
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') {
if ($libc === 'musl') {
if (self::isMuslDist()) {
$result = shell()->execWithResult('ldd 2>&1', false);
} else {
@@ -224,4 +230,37 @@ class SystemUtil
}
return null;
}
public static function getExtraRuntimeObjects(): string
{
if (ToolchainManager::getToolchainClass() !== ZigToolchain::class) {
return '';
}
if (self::$extra_runtime_objects !== null) {
return self::$extra_runtime_objects;
}
$paths = ['/usr/lib/gcc', '/usr/local/lib/gcc'];
$objects = ['crtbeginS.o', 'crtendS.o'];
$found = [];
foreach ($objects as $obj) {
$located = null;
foreach ($paths as $base) {
$output = shell_exec("find {$base} -name {$obj} -print -quit 2>/dev/null");
$line = trim((string) $output);
if ($line !== '') {
$located = $line;
break;
}
}
if ($located) {
$found[] = $located;
}
}
self::$extra_runtime_objects = implode(' ', $found);
return implode(' ', $found);
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\linux\library;
use SPC\store\FileSystem;
use SPC\util\SPCTarget;
class icu extends LinuxLibraryBase
{
@@ -16,7 +17,7 @@ class icu extends LinuxLibraryBase
{
$cppflags = 'CPPFLAGS="-DU_CHARSET_IS_UTF8=1 -DU_USING_ICU_NAMESPACE=1 -DU_STATIC_IMPLEMENTATION=1 -DPIC -fPIC"';
$cxxflags = 'CXXFLAGS="-std=c++17 -DPIC -fPIC -fno-ident"';
$ldflags = getenv('SPC_LIBC') !== 'glibc' ? 'LDFLAGS="-static"' : '';
$ldflags = SPCTarget::isStatic() ? 'LDFLAGS="-static"' : '';
shell()->cd($this->source_dir . '/source')->initializeEnv($this)
->exec(
"{$cppflags} {$cxxflags} {$ldflags} " .

View File

@@ -12,6 +12,8 @@ class libmemcached extends LinuxLibraryBase
public function build(): void
{
UnixCMakeExecutor::create($this)->build();
UnixCMakeExecutor::create($this)
->addConfigureArgs('-DCMAKE_INSTALL_RPATH=""')
->build();
}
}

View File

@@ -62,7 +62,8 @@ class openssl extends LinuxLibraryBase
$ex_lib = trim($ex_lib);
$clang_postfix = SystemUtil::getCCType(getenv('CC')) === 'clang' ? '-clang' : '';
/* @phpstan-ignore-next-line */
$clang_postfix = SystemUtil::getCCType() === 'clang' && (GNU_ARCH === 'x86_64' || PHP_OS_FAMILY === 'Darwin') ? '-clang' : '';
shell()->cd($this->source_dir)->initializeEnv($this)
->exec(

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace SPC\builder\linux\library;
class watcher extends LinuxLibraryBase
{
use \SPC\builder\unix\library\watcher;
public const NAME = 'watcher';
}

View File

@@ -28,7 +28,8 @@ class MacOSBuilder extends UnixBuilderBase
$this->options = $options;
// apply global environment variables
GlobalEnvManager::init($this);
GlobalEnvManager::init();
GlobalEnvManager::afterInit();
// ---------- set necessary compile vars ----------
// concurrency
@@ -122,6 +123,7 @@ class MacOSBuilder extends UnixBuilderBase
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
// prepare build php envs
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
@@ -180,9 +182,10 @@ class MacOSBuilder extends UnixBuilderBase
}
$this->buildEmbed();
}
$this->emitPatchPoint('before-sanity-check');
$this->sanityCheck($build_target);
if ($enableFrankenphp) {
logger()->info('building frankenphp');
$this->buildFrankenphp();
}
}
public function testPHP(int $build_target = BUILD_TARGET_NONE)
@@ -205,7 +208,7 @@ class MacOSBuilder extends UnixBuilderBase
$SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
$shell->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} cli");
if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/cli/php')->exec('strip sapi/cli/php');
$shell->exec('dsymutil -f sapi/cli/php')->exec('strip -S sapi/cli/php');
}
$this->deployBinary(BUILD_TARGET_CLI);
}
@@ -232,12 +235,15 @@ class MacOSBuilder extends UnixBuilderBase
// patch fake cli for micro
$vars['EXTRA_CFLAGS'] .= $enable_fake_cli;
if ($this->getOption('no-strip', false)) {
$vars['STRIP'] = 'dsymutil -f ';
}
$vars = SystemUtil::makeEnvVarString($vars);
shell()->cd(SOURCE_PATH . '/php-src')->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . " {$vars} micro");
$shell = shell()->cd(SOURCE_PATH . '/php-src');
// build
$shell->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . " {$vars} micro");
// strip
if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/micro/micro.sfx')->exec('strip -S sapi/micro/micro.sfx');
}
$this->deployBinary(BUILD_TARGET_MICRO);
@@ -259,7 +265,7 @@ class MacOSBuilder extends UnixBuilderBase
$shell = shell()->cd(SOURCE_PATH . '/php-src');
$shell->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . " {$vars} fpm");
if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/fpm/php-fpm')->exec('strip sapi/fpm/php-fpm');
$shell->exec('dsymutil -f sapi/fpm/php-fpm')->exec('strip -S sapi/fpm/php-fpm');
}
$this->deployBinary(BUILD_TARGET_FPM);
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace SPC\builder\macos\library;
class watcher extends MacOSLibraryBase
{
use \SPC\builder\unix\library\watcher;
public const NAME = 'watcher';
}

View File

@@ -119,9 +119,9 @@ trait UnixLibraryTrait
{
$env = getenv($this->getSnakeCaseName() . '_CFLAGS') ?: '';
if (!str_contains($env, $this->builder->arch_c_flags)) {
$env .= $this->builder->arch_c_flags;
$env .= ' ' . $this->builder->arch_c_flags;
}
return $env;
return trim($env);
}
public function getLibExtraLdFlags(): string
@@ -138,8 +138,8 @@ trait UnixLibraryTrait
{
$env = getenv($this->getSnakeCaseName() . '_CXXFLAGS') ?: '';
if (!str_contains($env, $this->builder->arch_cxx_flags)) {
$env .= $this->builder->arch_cxx_flags;
$env .= ' ' . $this->builder->arch_cxx_flags;
}
return $env;
return trim($env);
}
}

View File

@@ -12,9 +12,14 @@ use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\CurlHook;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\pkg\GoXcaddy;
use SPC\util\DependencyUtil;
use SPC\util\GlobalEnvManager;
use SPC\util\SPCConfigUtil;
use SPC\util\SPCTarget;
abstract class UnixBuilderBase extends BuilderBase
{
@@ -198,26 +203,43 @@ abstract class UnixBuilderBase extends BuilderBase
$util = new SPCConfigUtil($this);
$config = $util->config($this->ext_list, $this->lib_list, $this->getOption('with-suggested-exts'), $this->getOption('with-suggested-libs'));
$lens = "{$config['cflags']} {$config['ldflags']} {$config['libs']}";
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') {
if (SPCTarget::isStatic()) {
$lens .= ' -static';
}
// if someone changed to EMBED_TYPE=shared, we need to add LD_LIBRARY_PATH
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') {
$ext_path = 'LD_LIBRARY_PATH=' . BUILD_LIB_PATH . ':$LD_LIBRARY_PATH ';
FileSystem::removeFileIfExists(BUILD_LIB_PATH . '/libphp.a');
} else {
$ext_path = '';
foreach (glob(BUILD_LIB_PATH . '/libphp*.so') as $file) {
unlink($file);
}
}
[$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens);
if ($ret !== 0) {
throw new RuntimeException('embed failed sanity check: build failed. Error message: ' . implode("\n", $out));
}
// if someone changed to --enable-embed=shared, we need to add LD_LIBRARY_PATH
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') {
$ext_path = 'LD_LIBRARY_PATH=' . BUILD_ROOT_PATH . '/lib:$LD_LIBRARY_PATH ';
FileSystem::removeFileIfExists(BUILD_ROOT_PATH . '/lib/libphp.a');
} else {
$ext_path = '';
FileSystem::removeFileIfExists(BUILD_ROOT_PATH . '/lib/libphp.so');
}
[$ret, $output] = shell()->cd($sample_file_path)->execWithResult($ext_path . './embed');
if ($ret !== 0 || trim(implode('', $output)) !== 'hello') {
throw new RuntimeException('embed failed sanity check: run failed. Error message: ' . implode("\n", $output));
}
}
// sanity check for frankenphp
if (($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) {
logger()->info('running frankenphp sanity check');
$frankenphp = BUILD_BIN_PATH . '/frankenphp';
if (!file_exists($frankenphp)) {
throw new RuntimeException('FrankenPHP binary not found: ' . $frankenphp);
}
[$ret, $output] = shell()
->setEnv(['LD_LIBRARY_PATH' => BUILD_LIB_PATH])
->execWithResult("{$frankenphp} version");
if ($ret !== 0 || !str_contains(implode('', $output), 'FrankenPHP')) {
throw new RuntimeException('FrankenPHP failed sanity check: ret[' . $ret . ']. out[' . implode('', $output) . ']');
}
}
}
/**
@@ -277,4 +299,70 @@ abstract class UnixBuilderBase extends BuilderBase
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
}
}
/**
* @throws WrongUsageException
* @throws RuntimeException
*/
protected function buildFrankenphp(): void
{
$nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : '';
$nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : '';
$xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES');
// make it possible to build from a different frankenphp directory!
if (!str_contains($xcaddyModules, '--with github.com/dunglas/frankenphp')) {
$xcaddyModules = '--with github.com/dunglas/frankenphp ' . $xcaddyModules;
}
if ($this->getLib('brotli') === null && str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) {
logger()->warning('caddy-cbrotli module is enabled, but brotli library is not built. Disabling caddy-cbrotli.');
$xcaddyModules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddyModules);
}
$lrt = PHP_OS_FAMILY === 'Linux' ? '-lrt' : '';
$releaseInfo = json_decode(Downloader::curlExec(
'https://api.github.com/repos/php/frankenphp/releases/latest',
hooks: [[CurlHook::class, 'setupGithubToken']],
), true);
$frankenPhpVersion = $releaseInfo['tag_name'];
$libphpVersion = $this->getPHPVersion();
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') {
$libphpVersion = preg_replace('/\.\d$/', '', $libphpVersion);
}
$debugFlags = $this->getOption('no-strip') ? "'-w -s' " : '';
$extLdFlags = "-extldflags '-pie'";
$muslTags = '';
$staticFlags = '';
if (SPCTarget::isStatic()) {
$extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'";
$muslTags = 'static_build,';
$staticFlags = '-static -static-pie';
}
$config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list, with_dependencies: true);
$env = [
'CGO_ENABLED' => '1',
'CGO_CFLAGS' => $config['cflags'],
'CGO_LDFLAGS' => "{$staticFlags} {$config['ldflags']} {$config['libs']} {$lrt}",
'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' .
'-ldflags \"-linkmode=external ' . $extLdFlags . ' ' . $debugFlags .
'-X \'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ' .
"{$frankenPhpVersion} PHP {$libphpVersion} Caddy'\\\" " .
"-tags={$muslTags}nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}",
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
];
foreach (GoXcaddy::getEnvironment() as $key => $value) {
if ($key === 'PATH') {
GlobalEnvManager::addPathIfNotExists($value);
} else {
$env[$key] = $value;
}
}
shell()->cd(BUILD_BIN_PATH)
->setEnv($env)
->exec("xcaddy build --output frankenphp {$xcaddyModules}");
if (!$this->getOption('no-strip', false) && file_exists(BUILD_BIN_PATH . '/frankenphp')) {
shell()->cd(BUILD_BIN_PATH)->exec('strip --strip-all frankenphp');
}
}
}

View File

@@ -15,6 +15,9 @@ trait attr
protected function build(): void
{
UnixAutoconfExecutor::create($this)
->appendEnv([
'CFLAGS' => '-Wno-int-conversion -Wno-implicit-function-declaration',
])
->exec('libtoolize --force --copy')
->exec('./autogen.sh || autoreconf -if')
->configure('--disable-nls')

View File

@@ -24,6 +24,7 @@ trait curl
->optionalLib('libssh2', fn ($lib) => "-DLIBSSH2_LIBRARY=\"{$lib->getStaticLibFiles(style: 'cmake')}\" -DLIBSSH2_INCLUDE_DIR={$lib->getIncludeDir()}", '-DCURL_USE_LIBSSH2=OFF')
->optionalLib('nghttp2', fn ($lib) => "-DUSE_NGHTTP2=ON -DNGHTTP2_LIBRARY=\"{$lib->getStaticLibFiles(style: 'cmake')}\" -DNGHTTP2_INCLUDE_DIR={$lib->getIncludeDir()}", '-DUSE_NGHTTP2=OFF')
->optionalLib('nghttp3', fn ($lib) => "-DUSE_NGHTTP3=ON -DNGHTTP3_LIBRARY=\"{$lib->getStaticLibFiles(style: 'cmake')}\" -DNGHTTP3_INCLUDE_DIR={$lib->getIncludeDir()}", '-DUSE_NGHTTP3=OFF')
->optionalLib('ngtcp2', fn ($lib) => "-DUSE_NGTCP2=ON -DNGNGTCP2_LIBRARY=\"{$lib->getStaticLibFiles(style: 'cmake')}\" -DNGNGTCP2_INCLUDE_DIR={$lib->getIncludeDir()}", '-DUSE_NGTCP2=OFF')
->optionalLib('ldap', ...cmake_boolean_args('CURL_DISABLE_LDAP', true))
->optionalLib('zstd', ...cmake_boolean_args('CURL_ZSTD'))
->optionalLib('idn2', ...cmake_boolean_args('USE_LIBIDN2'))

View File

@@ -31,7 +31,7 @@ trait gettext
$autoconf->addConfigureArgs('--disable-threads');
}
$autoconf->configure()->make(with_clean: true);
$autoconf->configure()->make();
$this->patchLaDependencyPrefix();
}
}

View File

@@ -4,18 +4,19 @@ declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\builder\macos\library\MacOSLibraryBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
use SPC\util\executor\UnixAutoconfExecutor;
use SPC\util\SPCTarget;
trait imagemagick
{
/**
* @throws RuntimeException
* @throws FileSystemException
* @throws WrongUsageException
*/
protected function build(): void
{
@@ -32,17 +33,16 @@ trait imagemagick
->optionalLib('freetype', ...ac_with_args('freetype'))
->optionalLib('bzip2', ...ac_with_args('bzlib'))
->addConfigureArgs(
// TODO: glibc rh 10 toolset's libgomp.a was built without -fPIC so we can't use openmp without depending on libgomp.so
getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10') ? '--disable-openmp' : '--enable-openmp',
'--disable-openmp',
'--without-jxl',
'--without-x',
);
// special: linux musl needs `-static`
$ldflags = ($this instanceof LinuxLibraryBase) && getenv('SPC_LIBC') !== 'glibc' ? ('-static -ldl') : '-ldl';
// special: linux-static target needs `-static`
$ldflags = SPCTarget::isStatic() ? ('-static -ldl') : '-ldl';
// special: macOS needs -iconv
$libs = $this instanceof MacOSLibraryBase ? '-liconv' : '';
$libs = SPCTarget::getTargetOS() === 'Darwin' ? '-liconv' : '';
$ac->appendEnv([
'LDFLAGS' => $ldflags,

View File

@@ -6,12 +6,13 @@ namespace SPC\builder\unix\library;
use SPC\store\FileSystem;
use SPC\util\executor\UnixAutoconfExecutor;
use SPC\util\SPCTarget;
trait ldap
{
public function patchBeforeBuild(): bool
{
$extra = getenv('SPC_LIBC') === 'glibc' ? '-ldl -lpthread -lm -lresolv -lutil' : '';
$extra = SPCTarget::getLibc() === 'glibc' ? '-ldl -lpthread -lm -lresolv -lutil' : '';
FileSystem::replaceFileStr($this->source_dir . '/configure', '"-lssl -lcrypto', '"-lssl -lcrypto -lz ' . $extra);
return true;
}
@@ -26,8 +27,10 @@ trait ldap
'--disable-slapd',
'--without-systemd',
'--without-cyrus-sasl',
'ac_cv_func_pthread_kill_other_threads_np=no'
)
->appendEnv([
'CFLAGS' => '-Wno-date-time',
'LDFLAGS' => "-L{$this->getLibDir()}",
'CPPFLAGS' => "-I{$this->getIncludeDir()}",
])

View File

@@ -4,22 +4,24 @@ declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\toolchain\ToolchainManager;
use SPC\toolchain\ZigToolchain;
use SPC\util\executor\UnixCMakeExecutor;
trait libaom
{
/**
* @throws RuntimeException
* @throws FileSystemException
*/
protected function build(): void
{
$extra = getenv('COMPILER_EXTRA');
if (ToolchainManager::getToolchainClass() === ZigToolchain::class) {
$new = trim($extra . ' -D_GNU_SOURCE');
f_putenv("COMPILER_EXTRA={$new}");
}
UnixCMakeExecutor::create($this)
->setBuildDir("{$this->source_dir}/builddir")
->addConfigureArgs('-DAOM_TARGET_CPU=generic')
->build();
f_putenv("COMPILER_EXTRA={$extra}");
$this->patchPkgconfPrefix(['aom.pc']);
}
}

View File

@@ -19,7 +19,11 @@ trait liblz4
{
shell()->cd($this->source_dir)->initializeEnv($this)
->exec("make PREFIX='' clean")
->exec("make -j{$this->builder->concurrency} PREFIX=''")
->exec("make lib -j{$this->builder->concurrency} PREFIX=''");
FileSystem::replaceFileStr($this->source_dir . '/Makefile', '$(MAKE) -C $(PRGDIR) $@', '');
shell()->cd($this->source_dir)
->exec("make install PREFIX='' DESTDIR=" . BUILD_ROOT_PATH);
$this->patchPkgconfPrefix(['liblz4.pc']);

View File

@@ -6,6 +6,7 @@ namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
use SPC\util\executor\UnixAutoconfExecutor;
trait librdkafka
@@ -16,7 +17,18 @@ trait librdkafka
*/
protected function build(): void
{
FileSystem::replaceFileStr(
$this->source_dir . '/lds-gen.py',
"funcs.append('rd_ut_coverage_check')",
''
);
FileSystem::replaceFileStr(
$this->source_dir . '/src/rd.h',
'#error "IOV_MAX not defined"',
"#define IOV_MAX 1024\n#define __GNU__"
);
UnixAutoconfExecutor::create($this)
->appendEnv(['CFLAGS' => '-Wno-int-conversion -Wno-unused-but-set-variable -Wno-unused-variable'])
->optionalLib(
'zstd',
function ($lib) {

View File

@@ -33,7 +33,13 @@ trait libxslt
'--without-debugger',
"--with-libxml-prefix={$this->getBuildRootPath()}",
);
$ac->exec("{$this->builder->getOption('library_path')} {$this->builder->getOption('ld_library_path')} ./configure {$ac->getConfigureArgsString()}")->make();
if (getenv('SPC_LD_LIBRARY_PATH') && getenv('SPC_LIBRARY_PATH')) {
$ac->appendEnv([
'LD_LIBRARY_PATH' => getenv('SPC_LD_LIBRARY_PATH'),
'LIBRARY_PATH' => getenv('SPC_LIBRARY_PATH'),
]);
}
$ac->configure()->make();
$this->patchPkgconfPrefix(['libexslt.pc']);
$this->patchLaDependencyPrefix();

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\util\executor\UnixCMakeExecutor;
use SPC\util\SPCTarget;
trait mimalloc
{
@@ -13,9 +14,9 @@ trait mimalloc
$cmake = UnixCMakeExecutor::create($this)
->addConfigureArgs(
'-DMI_BUILD_SHARED=OFF',
'-DMI_INSTALL_TOPLEVEL=ON'
'-DMI_INSTALL_TOPLEVEL=ON',
);
if (getenv('SPC_LIBC') === 'musl') {
if (SPCTarget::getLibc() === 'musl') {
$cmake->addConfigureArgs('-DMI_LIBC_MUSL=ON');
}
$cmake->build();

View File

@@ -6,6 +6,7 @@ namespace SPC\builder\unix\library;
use SPC\store\FileSystem;
use SPC\util\executor\UnixAutoconfExecutor;
use SPC\util\SPCTarget;
trait ncurses
{
@@ -14,6 +15,9 @@ trait ncurses
$filelist = FileSystem::scanDirFiles(BUILD_BIN_PATH, relative: true);
UnixAutoconfExecutor::create($this)
->appendEnv([
'LDFLAGS' => SPCTarget::isStatic() ? '-static' : '',
])
->configure(
'--enable-overwrite',
'--with-curses-h',

View File

@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\util\executor\UnixAutoconfExecutor;
use SPC\util\SPCTarget;
trait pkgconfig
{
@@ -13,8 +13,8 @@ trait pkgconfig
{
UnixAutoconfExecutor::create($this)
->appendEnv([
'CFLAGS' => PHP_OS_FAMILY !== 'Linux' ? '-Wimplicit-function-declaration -Wno-int-conversion' : '',
'LDFLAGS' => !($this instanceof LinuxLibraryBase) || getenv('SPC_LIBC') === 'glibc' ? '' : '--static',
'CFLAGS' => '-Wimplicit-function-declaration -Wno-int-conversion',
'LDFLAGS' => SPCTarget::isStatic() ? '--static' : '',
])
->configure(
'--with-internal-glib',

View File

@@ -8,6 +8,7 @@ use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
use SPC\util\SPCTarget;
trait postgresql
{
@@ -50,7 +51,7 @@ trait postgresql
$error_exec_cnt += $output[0] === 0 ? 0 : 1;
if (!empty($output[1][0])) {
$ldflags = $output[1][0];
$envs .= !($this instanceof LinuxLibraryBase) || getenv('SPC_LIBC') === 'glibc' ? " LDFLAGS=\"{$ldflags}\" " : " LDFLAGS=\"{$ldflags} -static\" ";
$envs .= SPCTarget::isStatic() ? " LDFLAGS=\"{$ldflags} -static\" " : " LDFLAGS=\"{$ldflags}\" ";
}
$output = shell()->execWithResult("pkg-config --libs-only-l --static {$packages}");
$error_exec_cnt += $output[0] === 0 ? 0 : 1;

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
trait watcher
{
/**
* @throws RuntimeException
* @throws FileSystemException
*/
protected function build(): void
{
shell()->cd($this->source_dir . '/watcher-c')
->initializeEnv($this)
->exec(getenv('CXX') . ' -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra -fPIC')
->exec(getenv('AR') . ' rcs libwatcher-c.a libwatcher-c.o');
copy($this->source_dir . '/watcher-c/libwatcher-c.a', BUILD_LIB_PATH . '/libwatcher-c.a');
FileSystem::createDir(BUILD_INCLUDE_PATH . '/wtr');
copy($this->source_dir . '/watcher-c/include/wtr/watcher-c.h', BUILD_INCLUDE_PATH . '/wtr/watcher-c.h');
}
}

View File

@@ -33,7 +33,8 @@ class WindowsBuilder extends BuilderBase
{
$this->options = $options;
GlobalEnvManager::init($this);
GlobalEnvManager::init();
GlobalEnvManager::afterInit();
// ---------- set necessary options ----------
// set sdk (require visual studio 16 or 17)

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\store\FileSystem;
class icu_static_win extends WindowsLibraryBase
{
public const NAME = 'icu-static-win';
protected function build(): void
{
copy("{$this->source_dir}\\x64-windows-static\\lib\\icudt.lib", "{$this->getLibDir()}\\icudt.lib");
copy("{$this->source_dir}\\x64-windows-static\\lib\\icuin.lib", "{$this->getLibDir()}\\icuin.lib");
copy("{$this->source_dir}\\x64-windows-static\\lib\\icuio.lib", "{$this->getLibDir()}\\icuio.lib");
copy("{$this->source_dir}\\x64-windows-static\\lib\\icuuc.lib", "{$this->getLibDir()}\\icuuc.lib");
// create libpq folder in buildroot/includes/libpq
if (!file_exists("{$this->getIncludeDir()}\\unicode")) {
mkdir("{$this->getIncludeDir()}\\unicode");
}
FileSystem::copyDir("{$this->source_dir}\\x64-windows-static\\include\\unicode", "{$this->getIncludeDir()}\\unicode");
}
}

View File

@@ -99,6 +99,7 @@ abstract class BaseCommand extends Command
// init GlobalEnv
if (!$this instanceof BuildCommand) {
GlobalEnvManager::init();
f_putenv('SPC_SKIP_TOOLCHAIN_CHECK=yes');
}
if ($this->shouldExecute()) {
try {

View File

@@ -13,6 +13,7 @@ use SPC\store\SourcePatcher;
use SPC\util\DependencyUtil;
use SPC\util\GlobalEnvManager;
use SPC\util\LicenseDumper;
use SPC\util\SPCTarget;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
@@ -32,8 +33,9 @@ class BuildPHPCommand extends BuildCommand
$this->addOption('build-cli', null, null, 'Build cli SAPI');
$this->addOption('build-fpm', null, null, 'Build fpm SAPI (not available on Windows)');
$this->addOption('build-embed', null, null, 'Build embed SAPI (not available on Windows)');
$this->addOption('build-frankenphp', null, null, 'Build FrankenPHP SAPI (not available on Windows)');
$this->addOption('build-all', null, null, 'Build all SAPI');
$this->addOption('no-strip', null, null, 'build without strip, in order to debug and load external extensions');
$this->addOption('no-strip', null, null, 'build without strip, keep symbols to debug');
$this->addOption('disable-opcache-jit', null, null, 'disable opcache jit');
$this->addOption('with-config-file-path', null, InputOption::VALUE_REQUIRED, 'Set the path in which to look for php.ini', $isWindows ? null : '/usr/local/etc/php');
$this->addOption('with-config-file-scan-dir', null, InputOption::VALUE_REQUIRED, 'Set the directory to scan for .ini files after reading php.ini', $isWindows ? null : '/usr/local/etc/php/conf.d');
@@ -61,14 +63,9 @@ class BuildPHPCommand extends BuildCommand
$rule = $this->parseRules($shared_extensions);
// check dynamic extension build env
// macOS must use --no-strip option
if (!empty($shared_extensions) && PHP_OS_FAMILY === 'Darwin' && !$this->getOption('no-strip')) {
$this->output->writeln('MacOS does not support dynamic extension loading with stripped binary, please use --no-strip option!');
return static::FAILURE;
}
// linux must build with glibc
if (!empty($shared_extensions) && PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') !== 'glibc') {
$this->output->writeln('Linux does not support dynamic extension loading with musl-libc full-static build, please build with glibc!');
if (!empty($shared_extensions) && SPCTarget::isStatic()) {
$this->output->writeln('Linux does not support dynamic extension loading with fully static builds, please build with a shared C runtime target!');
return static::FAILURE;
}
$static_and_shared = array_intersect($static_extensions, $shared_extensions);
@@ -78,11 +75,12 @@ class BuildPHPCommand extends BuildCommand
if ($rule === BUILD_TARGET_NONE) {
$this->output->writeln('<error>Please add at least one build SAPI!</error>');
$this->output->writeln("<comment>\t--build-cli\tBuild php-cli SAPI</comment>");
$this->output->writeln("<comment>\t--build-micro\tBuild phpmicro SAPI</comment>");
$this->output->writeln("<comment>\t--build-fpm\tBuild php-fpm SAPI</comment>");
$this->output->writeln("<comment>\t--build-embed\tBuild embed SAPI/libphp</comment>");
$this->output->writeln("<comment>\t--build-all\tBuild all SAPI: cli, micro, fpm, embed</comment>");
$this->output->writeln("<comment>\t--build-cli\t\tBuild php-cli SAPI</comment>");
$this->output->writeln("<comment>\t--build-micro\t\tBuild phpmicro SAPI</comment>");
$this->output->writeln("<comment>\t--build-fpm\t\tBuild php-fpm SAPI</comment>");
$this->output->writeln("<comment>\t--build-embed\t\tBuild embed SAPI/libphp</comment>");
$this->output->writeln("<comment>\t--build-frankenphp\tBuild FrankenPHP SAPI/libphp</comment>");
$this->output->writeln("<comment>\t--build-all\t\tBuild all SAPI: cli, micro, fpm, embed, frankenphp</comment>");
return static::FAILURE;
}
if ($rule === BUILD_TARGET_ALL) {
@@ -136,6 +134,8 @@ class BuildPHPCommand extends BuildCommand
// print info
$indent_texts = [
'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')',
'Build Target' => getenv('SPC_TARGET'),
'Build Toolchain' => getenv('SPC_TOOLCHAIN'),
'Build SAPI' => $builder->getBuildTypeName($rule),
'Static Extensions (' . count($static_extensions) . ')' => implode(',', $static_extensions),
'Shared Extensions (' . count($shared_extensions) . ')' => implode(',', $shared_extensions),
@@ -158,14 +158,9 @@ class BuildPHPCommand extends BuildCommand
if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
$indent_texts['UPX Pack'] = 'enabled';
}
try {
$ver = $builder->getPHPVersion();
$indent_texts['PHP Version'] = $ver;
} catch (\Throwable) {
if (($ver = $builder->getPHPVersionFromArchive()) !== false) {
$indent_texts['PHP Version'] = $ver;
}
}
$ver = $builder->getPHPVersionFromArchive() ?: $builder->getPHPVersion();
$indent_texts['PHP Version'] = $ver;
if (!empty($not_included)) {
$indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included);
@@ -183,6 +178,9 @@ class BuildPHPCommand extends BuildCommand
// validate libs and extensions
$builder->validateLibsAndExts();
// check some things before building all the things
$builder->checkBeforeBuildPHP($rule);
// clean builds and sources
if ($this->input->getOption('with-clean')) {
logger()->info('Cleaning source and previous build dir...');
@@ -207,11 +205,11 @@ class BuildPHPCommand extends BuildCommand
// add static-php-cli.version to main.c, in order to debug php failure more easily
SourcePatcher::patchSPCVersionToPHP($this->getApplication()->getVersion());
// clean old modules that may conflict with the new php build
FileSystem::removeDir(BUILD_MODULES_PATH);
// start to build
$builder->buildPHP($rule);
SourcePatcher::patchBeforeSharedBuild($builder);
// build dynamic extensions if needed
if (!empty($shared_extensions)) {
logger()->info('Building shared extensions ...');
@@ -292,7 +290,8 @@ class BuildPHPCommand extends BuildCommand
$rule |= ($this->getOption('build-cli') ? BUILD_TARGET_CLI : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-embed') || !empty($shared_extensions) ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE);
$rule |= $this->getOption('build-embed') || !empty($shared_extensions) ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE;
$rule |= ($this->getOption('build-frankenphp') ? (BUILD_TARGET_FRANKENPHP | BUILD_TARGET_EMBED) : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE);
return $rule;
}

View File

@@ -5,12 +5,14 @@ declare(strict_types=1);
namespace SPC\command;
use SPC\exception\ValidationException;
use SPC\toolchain\ToolchainManager;
use SPC\toolchain\ZigToolchain;
use SPC\util\ConfigValidator;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Process\Process;
#[AsCommand('craft', 'Build static-php from craft.yml')]
class CraftCommand extends BaseCommand
class CraftCommand extends BuildCommand
{
public function configure(): void
{
@@ -49,7 +51,7 @@ class CraftCommand extends BaseCommand
}
$static_extensions = implode(',', $craft['extensions']);
$shared_extensions = implode(',', $craft['shared-extensions']);
$shared_extensions = implode(',', $craft['shared-extensions'] ?? []);
$libs = implode(',', $craft['libs']);
// init log
@@ -66,6 +68,24 @@ class CraftCommand extends BaseCommand
return static::FAILURE;
}
}
// install go and xcaddy for frankenphp
if (in_array('frankenphp', $craft['sapi'])) {
$retcode = $this->runCommand('install-pkg', 'go-xcaddy');
if ($retcode !== 0) {
$this->output->writeln('<error>craft go-xcaddy failed</error>');
$this->log("craft: spc install-pkg go-xcaddy failed with code: {$retcode}", true);
return static::FAILURE;
}
}
// install zig if requested
if (ToolchainManager::getToolchainClass() === ZigToolchain::class) {
$retcode = $this->runCommand('install-pkg', 'zig');
if ($retcode !== 0) {
$this->output->writeln('<error>craft zig failed</error>');
$this->log("craft: spc install-pkg zig failed with code: {$retcode}", true);
return static::FAILURE;
}
}
// craft download
if ($craft['craft-options']['download']) {
$sharedAppend = $shared_extensions ? ',' . $shared_extensions : '';

View File

@@ -9,6 +9,7 @@ use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\LockFile;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -21,6 +22,8 @@ class DeleteDownloadCommand extends BaseCommand
{
$this->addArgument('sources', InputArgument::REQUIRED, 'The sources/packages will be deleted, comma separated');
$this->addOption('all', 'A', null, 'Delete all downloaded and locked sources/packages');
$this->addOption('pre-built-only', 'W', null, 'Delete only pre-built sources/packages, not the original ones');
$this->addOption('source-only', 'S', null, 'Delete only sources, not the pre-built packages');
}
public function initialize(InputInterface $input, OutputInterface $output): void
@@ -46,29 +49,30 @@ class DeleteDownloadCommand extends BaseCommand
return static::SUCCESS;
}
$chosen_sources = $sources;
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$deleted_sources = [];
foreach ($chosen_sources as $source) {
$source = trim($source);
foreach ([$source, Downloader::getPreBuiltLockName($source)] as $name) {
if (isset($lock[$name])) {
$deleted_sources[] = $name;
}
if (LockFile::get($source) && !$this->getOption('pre-built-only')) {
$deleted_sources[] = $source;
}
if (LockFile::get(Downloader::getPreBuiltLockName($source)) && !$this->getOption('source-only')) {
$deleted_sources[] = Downloader::getPreBuiltLockName($source);
}
}
foreach ($deleted_sources as $lock_name) {
$lock = LockFile::get($lock_name);
// remove download file/dir if exists
if ($lock[$lock_name]['source_type'] === 'archive') {
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']))) {
if ($lock['source_type'] === SPC_SOURCE_ARCHIVE) {
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['filename']))) {
logger()->info('Deleting file ' . $path);
unlink($path);
} else {
logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file.");
}
} else {
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname']))) {
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['dirname']))) {
logger()->info('Deleting dir ' . $path);
FileSystem::removeDir($path);
} else {
@@ -76,9 +80,8 @@ class DeleteDownloadCommand extends BaseCommand
}
}
// remove locked sources
unset($lock[$lock_name]);
LockFile::put($lock_name, null);
}
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
logger()->info('Delete success!');
return static::SUCCESS;
} catch (DownloaderException $e) {

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace SPC\command;
use SPC\builder\linux\SystemUtil;
use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
@@ -12,7 +11,9 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\Downloader;
use SPC\store\LockFile;
use SPC\util\DependencyUtil;
use SPC\util\SPCTarget;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -222,8 +223,8 @@ class DownloadCommand extends BaseCommand
'{name}' => $source,
'{arch}' => arch2gnu(php_uname('m')),
'{os}' => strtolower(PHP_OS_FAMILY),
'{libc}' => getenv('SPC_LIBC') ?: 'default',
'{libcver}' => PHP_OS_FAMILY === 'Linux' ? (SystemUtil::getLibcVersionIfExists() ?? 'default') : 'default',
'{libc}' => SPCTarget::getLibc() ?? 'default',
'{libcver}' => SPCTarget::getLibcVersion() ?? 'default',
];
$find = str_replace(array_keys($replace), array_values($replace), Config::getPreBuilt('match-pattern'));
// find filename in asset list
@@ -301,7 +302,7 @@ class DownloadCommand extends BaseCommand
throw new WrongUsageException('Windows currently does not support --from-zip !');
}
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
if (!file_exists(LockFile::LOCK_FILE)) {
throw new RuntimeException('.lock.json not exist in "downloads/"');
}
} catch (RuntimeException $e) {

View File

@@ -11,7 +11,7 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
#[AsCommand('spc-config', 'Build dependencies')]
class SPCConfigCommand extends BuildCommand
class SPCConfigCommand extends BaseCommand
{
protected bool $no_motd = true;

View File

@@ -7,6 +7,7 @@ namespace SPC\command;
use SPC\store\Config;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\LockFile;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
@@ -40,16 +41,9 @@ class SwitchPhpVersionCommand extends BaseCommand
}
}
// detect if downloads/.lock.json exists
$lock_file = DOWNLOAD_PATH . '/.lock.json';
// parse php-src part of lock file
$lock_data = json_decode(file_get_contents($lock_file), true);
// get php-src downloaded file name
$php_src = $lock_data['php-src'];
$file = DOWNLOAD_PATH . '/' . ($php_src['filename'] ?? '.donot.delete.me');
if (file_exists($file)) {
if (LockFile::isLockFileExists('php-src')) {
$this->output->writeln('<info>Removing old PHP source...</info>');
unlink($file);
LockFile::put('php-src', null);
}
// Download new PHP source

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace SPC\command\dev;
use SPC\command\BaseCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand('dev:env', 'Returns the internally defined environment variables')]
class EnvCommand extends BaseCommand
{
public function configure()
{
$this->addArgument('env', InputArgument::REQUIRED, 'The environment variable to show, if not set, all will be shown');
}
public function initialize(InputInterface $input, OutputInterface $output): void
{
$this->no_motd = true;
parent::initialize($input, $output);
}
public function handle(): int
{
$env = $this->getArgument('env');
if (($val = getenv($env)) === false) {
$this->output->writeln("<error>Environment variable '{$env}' is not set.</error>");
return static::FAILURE;
}
$this->output->writeln("<info>{$val}</info>");
return static::SUCCESS;
}
}

View File

@@ -6,7 +6,6 @@ namespace SPC\command\dev;
use SPC\builder\BuilderProvider;
use SPC\builder\LibraryBase;
use SPC\builder\linux\SystemUtil;
use SPC\command\BuildCommand;
use SPC\exception\ExceptionHandler;
use SPC\exception\FileSystemException;
@@ -14,7 +13,9 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\LockFile;
use SPC\util\DependencyUtil;
use SPC\util\SPCTarget;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
@@ -47,9 +48,8 @@ class PackLibCommand extends BuildCommand
$lib->setup();
} else {
// Get lock info
$lock = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$source = Config::getLib($lib->getName(), 'source');
if (!isset($lock[$source]) || ($lock[$source]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) {
if (($lock = LockFile::get($source)) === null || ($lock['lock_as'] === SPC_DOWNLOAD_PRE_BUILT)) {
logger()->critical("The library {$lib->getName()} is downloaded as pre-built, we need to build it instead of installing pre-built.");
return static::FAILURE;
}
@@ -76,8 +76,8 @@ class PackLibCommand extends BuildCommand
'{name}' => $lib->getName(),
'{arch}' => arch2gnu(php_uname('m')),
'{os}' => strtolower(PHP_OS_FAMILY),
'{libc}' => getenv('SPC_LIBC') ?: 'default',
'{libcver}' => PHP_OS_FAMILY === 'Linux' ? (SystemUtil::getLibcVersionIfExists() ?? 'default') : 'default',
'{libc}' => SPCTarget::getLibc() ?? 'default',
'{libcver}' => SPCTarget::getLibcVersion() ?? 'default',
];
// detect suffix, for proper tar option
$tar_option = $this->getTarOptionFromSuffix(Config::getPreBuilt('match-pattern'));

View File

@@ -72,6 +72,14 @@ final class CheckListHandler
{
foreach (FileSystem::getClassesPsr4(__DIR__ . '/item', 'SPC\doctor\item') as $class) {
$ref = new \ReflectionClass($class);
$optional = $ref->getAttributes(OptionalCheck::class)[0] ?? null;
if ($optional !== null) {
/** @var OptionalCheck $instance */
$instance = $optional->newInstance();
if (is_callable($instance->check) && !call_user_func($instance->check)) {
continue; // skip this class if optional check is false
}
}
foreach ($ref->getMethods() as $method) {
foreach ($method->getAttributes() as $a) {
if (is_a($a->getName(), AsCheckItem::class, true)) {

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace SPC\doctor;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class OptionalCheck
{
public function __construct(public array $check) {}
}

View File

@@ -4,10 +4,10 @@ declare(strict_types=1);
namespace SPC\doctor\item;
use SPC\builder\linux\SystemUtil;
use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem;
use SPC\doctor\CheckResult;
use SPC\doctor\OptionalCheck;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
@@ -16,20 +16,20 @@ use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\PackageManager;
use SPC\store\SourcePatcher;
use SPC\toolchain\MuslToolchain;
#[OptionalCheck([self::class, 'optionalCheck'])]
class LinuxMuslCheck
{
public static function optionalCheck(): bool
{
return getenv('SPC_TOOLCHAIN') === MuslToolchain::class;
}
/** @noinspection PhpUnused */
#[AsCheckItem('if musl-wrapper is installed', limit_os: 'Linux', level: 800)]
public function checkMusl(): CheckResult
{
if (SystemUtil::isMuslDist()) {
return CheckResult::ok('musl-based distro, skipped');
}
if (getenv('SPC_LIBC') === 'glibc') {
return CheckResult::ok('Building with glibc, skipped');
}
$musl_wrapper_lib = sprintf('/lib/ld-musl-%s.so.1', php_uname('m'));
if (file_exists($musl_wrapper_lib) && file_exists('/usr/local/musl/lib/libc.a')) {
return CheckResult::ok();
@@ -40,13 +40,6 @@ class LinuxMuslCheck
#[AsCheckItem('if musl-cross-make is installed', limit_os: 'Linux', level: 799)]
public function checkMuslCrossMake(): CheckResult
{
if (SystemUtil::isMuslDist()) {
return CheckResult::ok('musl-based distro, skipped');
}
if (getenv('SPC_LIBC') === 'glibc') {
return CheckResult::ok('Building with glibc, skipped');
}
$arch = arch2gnu(php_uname('m'));
$cross_compile_lib = "/usr/local/musl/{$arch}-linux-musl/lib/libc.a";
$cross_compile_gcc = "/usr/local/musl/bin/{$arch}-linux-musl-gcc";
@@ -78,7 +71,7 @@ class LinuxMuslCheck
];
logger()->info('Downloading ' . $musl_source['url']);
Downloader::downloadSource($musl_version_name, $musl_source);
FileSystem::extractSource($musl_version_name, DOWNLOAD_PATH . "/{$musl_version_name}.tar.gz");
FileSystem::extractSource($musl_version_name, SPC_SOURCE_ARCHIVE, DOWNLOAD_PATH . "/{$musl_version_name}.tar.gz");
// Apply CVE-2025-26519 patch
SourcePatcher::patchFile('musl-1.2.5_CVE-2025-26519_0001.patch', SOURCE_PATH . "/{$musl_version_name}");

View File

@@ -22,6 +22,7 @@ class LinuxToolCheckList
'bzip2', 'cmake', 'gcc',
'g++', 'patch', 'binutils-gold',
'libtoolize', 'which',
'patchelf',
];
public const TOOLS_DEBIAN = [
@@ -30,6 +31,7 @@ class LinuxToolCheckList
'tar', 'unzip', 'gzip',
'bzip2', 'cmake', 'patch',
'xz', 'libtoolize', 'which',
'patchelf',
];
public const TOOLS_RHEL = [
@@ -38,6 +40,7 @@ class LinuxToolCheckList
'tar', 'unzip', 'gzip', 'gcc',
'bzip2', 'cmake', 'patch', 'which',
'xz', 'libtool', 'gettext-devel',
'perl', 'patchelf',
];
public const TOOLS_ARCH = [
@@ -48,6 +51,7 @@ class LinuxToolCheckList
'binutils-gold' => 'ld.gold',
'base-devel' => 'automake',
'gettext-devel' => 'gettextize',
'gettext-dev' => 'gettextize',
];
/** @noinspection PhpUnused */

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace SPC\doctor\item;
use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem;
use SPC\doctor\CheckResult;
use SPC\doctor\OptionalCheck;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\PackageManager;
use SPC\store\pkg\Zig;
use SPC\toolchain\ZigToolchain;
#[OptionalCheck([self::class, 'optionalCheck'])]
class ZigCheck
{
public static function optionalCheck(): bool
{
return getenv('SPC_TOOLCHAIN') === ZigToolchain::class;
}
/** @noinspection PhpUnused */
#[AsCheckItem('if zig is installed', level: 800)]
public function checkZig(): CheckResult
{
if (Zig::isInstalled()) {
return CheckResult::ok();
}
return CheckResult::fail('zig is not installed', 'install-zig');
}
/** @noinspection PhpUnused */
/**
* @throws FileSystemException
* @throws WrongUsageException
*/
#[AsFixItem('install-zig')]
public function fixMuslCrossMake(): bool
{
$arch = arch2gnu(php_uname('m'));
$os = match (PHP_OS_FAMILY) {
'Windows' => 'win',
'Darwin' => 'macos',
'BSD' => 'freebsd',
default => 'linux',
};
PackageManager::installPackage("zig-{$arch}-{$os}");
return Zig::isInstalled();
}
}

View File

@@ -4,12 +4,13 @@ declare(strict_types=1);
namespace SPC\store;
use SPC\builder\linux\SystemUtil;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\pkg\CustomPackage;
use SPC\store\source\CustomSourceBase;
use SPC\util\SPCTarget;
/**
* Source Downloader.
@@ -208,31 +209,7 @@ class Downloader
if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
$name = self::getPreBuiltLockName($name);
}
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
}
/**
* Try to lock source.
*
* @param string $name Source name
* @param array{
* source_type: string,
* dirname: ?string,
* filename: ?string,
* move_path: ?string,
* lock_as: int
* } $data Source data
* @throws FileSystemException
*/
public static function lockSource(string $name, array $data): void
{
if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.lock.json'))) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
$lock[$name] = $data;
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
LockFile::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
}
/**
@@ -278,7 +255,7 @@ class Downloader
}
// Lock
logger()->debug("Locking git source {$name}");
self::lockSource($name, ['source_type' => 'dir', 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]);
LockFile::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]);
/*
// 复制目录过去
@@ -371,12 +348,25 @@ class Downloader
SPC_DOWNLOAD_PRE_BUILT
);
break;
case 'local':
// Local directory, do nothing, just lock it
logger()->debug("Locking local source {$name}");
LockFile::lockSource($name, [
'source_type' => SPC_SOURCE_LOCAL,
'dirname' => $pkg['dirname'],
'move_path' => $pkg['extract'] ?? null,
'lock_as' => SPC_DOWNLOAD_PACKAGE,
]);
break;
case 'custom': // Custom download method, like API-based download or other
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source');
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg');
foreach ($classes as $class) {
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
(new $class())->fetch($force);
break;
if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) {
$cls = new $class();
if (in_array($name, $cls->getSupportName())) {
(new $class())->fetch($name, $force, $pkg);
break;
}
}
}
break;
@@ -477,6 +467,16 @@ class Downloader
$download_as
);
break;
case 'local':
// Local directory, do nothing, just lock it
logger()->debug("Locking local source {$name}");
LockFile::lockSource($name, [
'source_type' => SPC_SOURCE_LOCAL,
'dirname' => $source['dirname'],
'move_path' => $source['extract'] ?? null,
'lock_as' => $download_as,
]);
break;
case 'custom': // Custom download method, like API-based download or other
if (isset($source['func']) && is_callable($source['func'])) {
$source['name'] = $name;
@@ -591,7 +591,12 @@ class Downloader
public static function getPreBuiltLockName(string $source): string
{
return "{$source}-" . PHP_OS_FAMILY . '-' . getenv('GNU_ARCH') . '-' . (getenv('SPC_LIBC') ?: 'default') . '-' . (SystemUtil::getLibcVersionIfExists() ?? 'default');
$os_family = PHP_OS_FAMILY;
$gnu_arch = getenv('GNU_ARCH') ?: 'unknown';
$libc = SPCTarget::getLibc();
$libc_version = SPCTarget::getLibcVersion() ?? 'default';
return "{$source}-{$os_family}-{$gnu_arch}-{$libc}-{$libc_version}";
}
/**
@@ -629,33 +634,30 @@ class Downloader
/**
* @throws FileSystemException
* @throws WrongUsageException
*/
private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool
{
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading for source mode
if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && isset($lock[$name])) {
if (
$lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename']) ||
$lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])
) {
logger()->notice("Source [{$name}] already downloaded: " . ($lock[$name]['filename'] ?? $lock[$name]['dirname']));
// If the lock file exists, skip downloading for source mode
$lock_item = LockFile::get($name);
if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && $lock_item !== null) {
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
logger()->notice("Source [{$name}] already downloaded: {$path}");
return true;
}
}
// If lock file exists for current arch and glibc target, skip downloading
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && isset($lock[$lock_name = self::getPreBuiltLockName($name)])) {
$lock_name = self::getPreBuiltLockName($name);
$lock_item = LockFile::get($lock_name);
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && $lock_item !== null) {
// lock name with env
if (
$lock[$lock_name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']) ||
$lock[$lock_name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname'])
) {
logger()->notice("Pre-built content [{$name}] already downloaded: " . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname']));
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
logger()->notice("Pre-built content [{$name}] already downloaded: {$path}");
return true;
}
}
if (!$force && $download_as === SPC_DOWNLOAD_PACKAGE && $lock_item !== null) {
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
logger()->notice("Source [{$name}] already downloaded: {$path}");
return true;
}
}

View File

@@ -142,7 +142,7 @@ class FileSystem
* @throws RuntimeException
* @throws FileSystemException
*/
public static function extractPackage(string $name, string $filename, ?string $extract_path = null): void
public static function extractPackage(string $name, string $source_type, string $filename, ?string $extract_path = null): void
{
if ($extract_path !== null) {
// replace
@@ -151,14 +151,15 @@ class FileSystem
} else {
$extract_path = PKG_ROOT_PATH . '/' . $name;
}
logger()->info("extracting {$name} package to {$extract_path} ...");
logger()->info("Extracting {$name} package to {$extract_path} ...");
$target = self::convertPath($extract_path);
if (!is_dir($dir = dirname($target))) {
self::createDir($dir);
}
try {
self::extractArchive($filename, $target);
// extract wrapper command
self::extractWithType($source_type, $filename, $extract_path);
} catch (RuntimeException $e) {
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('rmdir /s /q ' . $target);
@@ -177,24 +178,23 @@ class FileSystem
* @throws FileSystemException
* @throws RuntimeException
*/
public static function extractSource(string $name, string $filename, ?string $move_path = null): void
public static function extractSource(string $name, string $source_type, string $filename, ?string $move_path = null): void
{
// if source hook is empty, load it
if (self::$_extract_hook === []) {
SourcePatcher::init();
}
if ($move_path !== null) {
$move_path = SOURCE_PATH . '/' . $move_path;
} else {
$move_path = SOURCE_PATH . "/{$name}";
}
$move_path = match ($move_path) {
null => SOURCE_PATH . '/' . $name,
default => self::isRelativePath($move_path) ? (SOURCE_PATH . '/' . $move_path) : $move_path,
};
$target = self::convertPath($move_path);
logger()->info("extracting {$name} source to {$target}" . ' ...');
logger()->info("Extracting {$name} source to {$target}" . ' ...');
if (!is_dir($dir = dirname($target))) {
self::createDir($dir);
}
try {
self::extractArchive($filename, $target);
self::extractWithType($source_type, $filename, $move_path);
self::emitSourceExtractHook($name, $target);
} catch (RuntimeException $e) {
if (PHP_OS_FAMILY === 'Windows') {
@@ -484,11 +484,6 @@ class FileSystem
*/
private static function extractArchive(string $filename, string $target): void
{
// Git source, just move
if (is_dir(self::convertPath($filename))) {
self::copyDir(self::convertPath($filename), $target);
return;
}
// Create base dir
if (f_mkdir(directory: $target, recursive: true) !== true) {
throw new FileSystemException('create ' . $target . ' dir failed');
@@ -553,4 +548,16 @@ class FileSystem
}
}
}
private static function extractWithType(string $source_type, string $filename, string $extract_path): void
{
logger()->debug('Extracting source [' . $source_type . ']: ' . $filename);
/* @phpstan-ignore-next-line */
match ($source_type) {
SPC_SOURCE_ARCHIVE => self::extractArchive($filename, $extract_path),
SPC_SOURCE_GIT => self::copyDir(self::convertPath($filename), $extract_path),
// soft link to the local source
SPC_SOURCE_LOCAL => symlink(self::convertPath($filename), $extract_path),
};
}
}

227
src/SPC/store/LockFile.php Normal file
View File

@@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace SPC\store;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
class LockFile
{
public const string LOCK_FILE = DOWNLOAD_PATH . '/.lock.json';
private static ?array $lock_file_content = null;
/**
* Get a lock entry by its name.
*
* @param string $lock_name Lock name to retrieve
* @return null|array{
* source_type: string,
* filename: ?string,
* dirname: ?string,
* move_path: ?string,
* lock_as: int,
* hash: string
* } Returns the lock entry as an associative array if it exists, or null if it does not
*/
public static function get(string $lock_name): ?array
{
self::init();
// Return the specific lock entry if it exists, otherwise return an empty array
$result = self::$lock_file_content[$lock_name] ?? null;
// Add old `dir` compatibility
if (($result['source_type'] ?? null) === 'dir') {
logger()->warning("Lock entry for '{$lock_name}' has 'source_type' set to 'dir', which is deprecated. Please re-download your dependencies.");
$result['source_type'] = SPC_SOURCE_GIT;
}
return $result;
}
/**
* Check if a lock file exists for a given lock name.
*
* @param string $lock_name Lock name to check
*/
public static function isLockFileExists(string $lock_name): bool
{
return match (self::get($lock_name)['source_type'] ?? null) {
SPC_SOURCE_ARCHIVE => file_exists(DOWNLOAD_PATH . '/' . (self::get($lock_name)['filename'] ?? '.never-exist-file')),
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => is_dir(DOWNLOAD_PATH . '/' . (self::get($lock_name)['dirname'] ?? '.never-exist-dir')),
default => false,
};
}
/**
* Put a lock entry into the lock file.
*
* @param string $lock_name Lock name to set or remove
* @param null|array $lock_content lock content to set, or null to remove the lock entry
* @throws FileSystemException
* @throws WrongUsageException
*/
public static function put(string $lock_name, ?array $lock_content): void
{
self::init();
if ($lock_content === null && isset(self::$lock_file_content[$lock_name])) {
self::removeLockFileIfExists(self::$lock_file_content[$lock_name]);
unset(self::$lock_file_content[$lock_name]);
} else {
self::$lock_file_content[$lock_name] = $lock_content;
}
// Write the updated lock data back to the file
FileSystem::createDir(dirname(self::LOCK_FILE));
file_put_contents(self::LOCK_FILE, json_encode(self::$lock_file_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
/**
* Get the full path of a lock file or directory based on the lock options.
*
* @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname'
* @return string the absolute path to the lock file or directory
* @throws WrongUsageException
*/
public static function getLockFullPath(array $lock_options): string
{
return match ($lock_options['source_type']) {
SPC_SOURCE_ARCHIVE => FileSystem::isRelativePath($lock_options['filename']) ? (DOWNLOAD_PATH . '/' . $lock_options['filename']) : $lock_options['filename'],
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => FileSystem::isRelativePath($lock_options['dirname']) ? (DOWNLOAD_PATH . '/' . $lock_options['dirname']) : $lock_options['dirname'],
default => throw new WrongUsageException("Unknown source type: {$lock_options['source_type']}"),
};
}
public static function getExtractPath(string $lock_name, string $default_path): ?string
{
$lock = self::get($lock_name);
if ($lock === null) {
return null;
}
// If move_path is set, use it; otherwise, use the default extract directory
if (isset($lock['move_path'])) {
if (FileSystem::isRelativePath($lock['move_path'])) {
// If move_path is relative, prepend the default extract directory
return match ($lock['lock_as']) {
SPC_DOWNLOAD_SOURCE, SPC_DOWNLOAD_PRE_BUILT => FileSystem::convertPath(SOURCE_PATH . '/' . $lock['move_path']),
SPC_DOWNLOAD_PACKAGE => FileSystem::convertPath(PKG_ROOT_PATH . '/' . $lock['move_path']),
default => throw new WrongUsageException("Unknown lock type: {$lock['lock_as']}"),
};
}
return FileSystem::convertPath($lock['move_path']);
}
return FileSystem::convertPath($default_path);
}
/**
* Get the hash of the lock source based on the lock options.
*
* @param array $lock_options Lock options
* @return string Hash of the lock source
* @throws RuntimeException
*/
public static function getLockSourceHash(array $lock_options): string
{
$result = match ($lock_options['source_type']) {
SPC_SOURCE_ARCHIVE => sha1_file(DOWNLOAD_PATH . '/' . $lock_options['filename']),
SPC_SOURCE_GIT => exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $lock_options['dirname']) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD'),
SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT',
default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new RuntimeException("Unknown source type: {$lock_options['source_type']}"),
};
if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) {
throw new RuntimeException("Failed to get hash for source: {$lock_options['source_type']}");
}
return $result ?: '';
}
/**
* @param array $lock_options Lock options
* @param string $destination Target directory
* @throws FileSystemException
* @throws RuntimeException
*/
public static function putLockSourceHash(array $lock_options, string $destination): void
{
$hash = LockFile::getLockSourceHash($lock_options);
if ($lock_options['source_type'] === SPC_SOURCE_LOCAL) {
logger()->debug("Source [{$lock_options['dirname']}] is local, no hash will be written.");
return;
}
FileSystem::writeFile("{$destination}/.spc-hash", $hash);
}
/**
* Try to lock source with hash.
*
* @param string $name Source name
* @param array{
* source_type: string,
* dirname: ?string,
* filename: ?string,
* move_path: ?string,
* lock_as: int
* } $data Source data
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
*/
public static function lockSource(string $name, array $data): void
{
// calculate hash
$hash = LockFile::getLockSourceHash($data);
$data['hash'] = $hash;
self::put($name, $data);
}
private static function init(): void
{
if (self::$lock_file_content === null) {
// Initialize the lock file content if it hasn't been loaded yet
if (!file_exists(self::LOCK_FILE)) {
logger()->debug('Lock file does not exist: ' . self::LOCK_FILE . ', initializing empty lock file.');
self::$lock_file_content = [];
file_put_contents(self::LOCK_FILE, json_encode(self::$lock_file_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
} else {
$file_content = file_get_contents(self::LOCK_FILE);
self::$lock_file_content = json_decode($file_content, true);
if (self::$lock_file_content === null) {
throw new \RuntimeException('Failed to decode lock file: ' . self::LOCK_FILE);
}
}
}
}
/**
* Remove the lock file or directory if it exists.
*
* @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname'
* @throws WrongUsageException
* @throws FileSystemException
*/
private static function removeLockFileIfExists(array $lock_options): void
{
if ($lock_options['source_type'] === SPC_SOURCE_ARCHIVE) {
$path = self::getLockFullPath($lock_options);
if (file_exists($path)) {
logger()->info('Removing file ' . $path);
unlink($path);
} else {
logger()->debug("Lock file [{$lock_options['filename']}] not found, skip removing file.");
}
} else {
$path = self::getLockFullPath($lock_options);
if (is_dir($path)) {
logger()->info('Removing directory ' . $path);
FileSystem::removeDir($path);
} else {
logger()->debug("Lock directory [{$lock_options['dirname']}] not found, skip removing directory.");
}
}
}
}

View File

@@ -6,6 +6,7 @@ namespace SPC\store;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\pkg\CustomPackage;
class PackageManager
{
@@ -32,11 +33,27 @@ class PackageManager
// Download package
Downloader::downloadPackage($pkg_name, $config, $force);
if (Config::getPkg($pkg_name)['type'] === 'custom') {
// Custom extract function
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg');
foreach ($classes as $class) {
if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) {
$cls = new $class();
if (in_array($pkg_name, $cls->getSupportName())) {
(new $class())->extract($pkg_name);
break;
}
}
}
return;
}
// After download, read lock file name
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
$filename = DOWNLOAD_PATH . '/' . ($lock[$pkg_name]['filename'] ?? $lock[$pkg_name]['dirname']);
$extract = $lock[$pkg_name]['move_path'] === null ? (PKG_ROOT_PATH . '/' . $pkg_name) : $lock[$pkg_name]['move_path'];
FileSystem::extractPackage($pkg_name, $filename, $extract);
$lock = LockFile::get($pkg_name);
$source_type = $lock['source_type'];
$filename = LockFile::getLockFullPath($lock);
$extract = LockFile::getExtractPath($pkg_name, PKG_ROOT_PATH . '/' . $pkg_name);
FileSystem::extractPackage($pkg_name, $source_type, $filename, $extract);
// if contains extract-files, we just move this file to destination, and remove extract dir
if (is_array($config['extract-files'] ?? null) && is_assoc_array($config['extract-files'])) {

View File

@@ -17,11 +17,6 @@ class SourceManager
*/
public static function initSource(?array $sources = null, ?array $libs = null, ?array $exts = null, bool $source_only = false): void
{
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
throw new WrongUsageException('Download lock file "downloads/.lock.json" not found, maybe you need to download sources first ?');
}
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
$sources_extracted = [];
// source check exist
if (is_array($sources)) {
@@ -56,8 +51,8 @@ class SourceManager
}
// check source downloaded
$pre_built_name = Downloader::getPreBuiltLockName($source);
if ($source_only || !isset($lock[$pre_built_name])) {
if (!isset($lock[$source])) {
if ($source_only || LockFile::get($pre_built_name) === null) {
if (LockFile::get($source) === null) {
throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !");
}
$lock_name = $source;
@@ -65,14 +60,38 @@ class SourceManager
$lock_name = $pre_built_name;
}
$lock_content = LockFile::get($lock_name);
// check source dir exist
$check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']);
$check = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source);
// $check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']);
if (!is_dir($check)) {
logger()->debug('Extracting source [' . $source . '] to ' . $check . ' ...');
FileSystem::extractSource($source, DOWNLOAD_PATH . '/' . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname']), $lock[$lock_name]['move_path']);
} else {
logger()->debug('Source [' . $source . '] already extracted in ' . $check . ', skip !');
$filename = LockFile::getLockFullPath($lock_content);
FileSystem::extractSource($source, $lock_content['source_type'], $filename, $check);
LockFile::putLockSourceHash($lock_content, $check);
continue;
}
// if a lock file does not have hash, calculate with the current source (backward compatibility)
if (!isset($lock_content['hash'])) {
$hash = LockFile::getLockSourceHash($lock_content);
} else {
$hash = $lock_content['hash'];
}
// when source already extracted, detect if the extracted source hash is the same as the lock file one
if (file_exists("{$check}/.spc-hash") && FileSystem::readFile("{$check}/.spc-hash") === $hash) {
logger()->debug('Source [' . $source . '] already extracted in ' . $check . ', skip !');
continue;
}
// if not, remove the source dir and extract again
logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ...");
FileSystem::removeDir($check);
$filename = LockFile::getLockFullPath($lock_content);
$move_path = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source);
FileSystem::extractSource($source, $lock_content['source_type'], $filename, $move_path);
LockFile::putLockSourceHash($lock_content, $check);
}
}
}

View File

@@ -11,27 +11,27 @@ use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\util\SPCTarget;
class SourcePatcher
{
public static function init(): void
{
// FileSystem::addSourceExtractHook('swow', [SourcePatcher::class, 'patchSwow']);
FileSystem::addSourceExtractHook('micro', [SourcePatcher::class, 'patchMicro']);
FileSystem::addSourceExtractHook('openssl', [SourcePatcher::class, 'patchOpenssl11Darwin']);
FileSystem::addSourceExtractHook('swoole', [SourcePatcher::class, 'patchSwoole']);
FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchPhpLibxml212']);
FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchGDWin32']);
FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchFfiCentos7FixO3strncmp']);
FileSystem::addSourceExtractHook('sqlsrv', [SourcePatcher::class, 'patchSQLSRVWin32']);
FileSystem::addSourceExtractHook('pdo_sqlsrv', [SourcePatcher::class, 'patchSQLSRVWin32']);
FileSystem::addSourceExtractHook('yaml', [SourcePatcher::class, 'patchYamlWin32']);
FileSystem::addSourceExtractHook('libyaml', [SourcePatcher::class, 'patchLibYaml']);
FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchImapLicense']);
FileSystem::addSourceExtractHook('ext-imagick', [SourcePatcher::class, 'patchImagickWith84']);
FileSystem::addSourceExtractHook('libaom', [SourcePatcher::class, 'patchLibaomForAlpine']);
FileSystem::addSourceExtractHook('attr', [SourcePatcher::class, 'patchAttrForAlpine']);
FileSystem::addSourceExtractHook('gmssl', [SourcePatcher::class, 'patchGMSSL']);
// FileSystem::addSourceExtractHook('swow', [__CLASS__, 'patchSwow']);
FileSystem::addSourceExtractHook('openssl', [__CLASS__, 'patchOpenssl11Darwin']);
FileSystem::addSourceExtractHook('swoole', [__CLASS__, 'patchSwoole']);
FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchPhpLibxml212']);
FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchGDWin32']);
FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchFfiCentos7FixO3strncmp']);
FileSystem::addSourceExtractHook('sqlsrv', [__CLASS__, 'patchSQLSRVWin32']);
FileSystem::addSourceExtractHook('pdo_sqlsrv', [__CLASS__, 'patchSQLSRVWin32']);
FileSystem::addSourceExtractHook('yaml', [__CLASS__, 'patchYamlWin32']);
FileSystem::addSourceExtractHook('libyaml', [__CLASS__, 'patchLibYaml']);
FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchImapLicense']);
FileSystem::addSourceExtractHook('ext-imagick', [__CLASS__, 'patchImagickWith84']);
FileSystem::addSourceExtractHook('libaom', [__CLASS__, 'patchLibaomForAlpine']);
FileSystem::addSourceExtractHook('attr', [__CLASS__, 'patchAttrForAlpine']);
FileSystem::addSourceExtractHook('gmssl', [__CLASS__, 'patchGMSSL']);
}
/**
@@ -46,12 +46,12 @@ class SourcePatcher
{
foreach ($builder->getExts() as $ext) {
if ($ext->patchBeforeBuildconf() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before buildconf');
logger()->info("Extension [{$ext->getName()}] patched before buildconf");
}
}
foreach ($builder->getLibs() as $lib) {
if ($lib->patchBeforeBuildconf() === true) {
logger()->info('Library [' . $lib->getName() . '] patched before buildconf');
logger()->info("Library [{$lib->getName()}]patched before buildconf");
}
}
// patch windows php 8.1 bug
@@ -73,18 +73,9 @@ class SourcePatcher
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/build/php.m4', 'PKG_CHECK_MODULES(', 'PKG_CHECK_MODULES_STATIC(');
if ($builder->getOption('enable-micro-win32')) {
SourcePatcher::patchMicroWin32();
self::patchMicroWin32();
} else {
SourcePatcher::unpatchMicroWin32();
}
}
public static function patchBeforeSharedBuild(BuilderBase $builder): void
{
foreach ($builder->getExts() as $ext) {
if ($ext->patchBeforeSharedBuild() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before shared build');
}
self::unpatchMicroWin32();
}
}
@@ -98,26 +89,23 @@ class SourcePatcher
{
foreach ($builder->getExts() as $ext) {
if ($ext->patchBeforeConfigure() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before configure');
logger()->info("Extension [{$ext->getName()}] patched before configure");
}
}
foreach ($builder->getLibs() as $lib) {
if ($lib->patchBeforeConfigure() === true) {
logger()->info('Library [' . $lib->getName() . '] patched before configure');
logger()->info("Library [{$lib->getName()}] patched before configure");
}
}
// patch capstone
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/configure', '/have_capstone="yes"/', 'have_capstone="no"');
if ($builder instanceof LinuxBuilder && getenv('SPC_LIBC') === 'glibc') {
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Zend/zend_operators.h', '# define ZEND_USE_ASM_ARITHMETIC 1', '# define ZEND_USE_ASM_ARITHMETIC 0');
}
}
/**
* @throws RuntimeException
* @throws FileSystemException
*/
public static function patchMicro(string $name = '', string $target = '', ?array $items = null): bool
public static function patchMicro(?array $items = null): bool
{
if (!file_exists(SOURCE_PATH . '/php-src/sapi/micro/php_micro.c')) {
return false;
@@ -164,11 +152,7 @@ class SourcePatcher
foreach ($patches as $patch) {
logger()->info("Patching micro with {$patch}");
$patchesStr = str_replace('/', DIRECTORY_SEPARATOR, $patch);
f_passthru(
'cd ' . SOURCE_PATH . '/php-src && ' .
(PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . $patchesStr . ' | patch -p1 '
);
self::patchFile(SOURCE_PATH . "/php-src/{$patch}", SOURCE_PATH . '/php-src');
}
return true;
@@ -177,24 +161,32 @@ class SourcePatcher
/**
* Use existing patch file for patching
*
* @param string $patch_name Patch file name in src/globals/patch/
* @param string $patch_name Patch file name in src/globals/patch/ or absolute path
* @param string $cwd Working directory for patch command
* @param bool $reverse Reverse patches (default: False)
* @throws RuntimeException
*/
public static function patchFile(string $patch_name, string $cwd, bool $reverse = false): bool
{
if (!file_exists(ROOT_DIR . "/src/globals/patch/{$patch_name}")) {
if (FileSystem::isRelativePath($patch_name)) {
$patch_file = ROOT_DIR . "/src/globals/patch/{$patch_name}";
} else {
$patch_file = $patch_name;
}
if (!file_exists($patch_file)) {
return false;
}
$patch_file = ROOT_DIR . "/src/globals/patch/{$patch_name}";
$patch_str = str_replace('/', DIRECTORY_SEPARATOR, $patch_file);
$patch_str = FileSystem::convertPath($patch_file);
if (!file_exists($patch_str)) {
throw new RuntimeException("Patch file [{$patch_str}] does not exist");
}
// Copy patch from phar
if (\Phar::running() !== '') {
file_put_contents(SOURCE_PATH . '/' . $patch_name, file_get_contents($patch_file));
$patch_str = str_replace('/', DIRECTORY_SEPARATOR, SOURCE_PATH . '/' . $patch_name);
if (str_starts_with($patch_str, 'phar://')) {
$filename = pathinfo($patch_file, PATHINFO_BASENAME);
file_put_contents(SOURCE_PATH . "/{$filename}", file_get_contents($patch_file));
$patch_str = FileSystem::convertPath(SOURCE_PATH . "/{$filename}");
}
// detect
@@ -241,6 +233,7 @@ class SourcePatcher
'PHP_ADD_INCLUDE([$ext_srcdir])',
"PHP_ADD_INCLUDE( [\$ext_srcdir] )\n PHP_ADD_INCLUDE([\$abs_srcdir/ext])"
);
// swoole 5.1.3 build fix
// get swoole version first
$file = SOURCE_PATH . '/php-src/ext/swoole/include/swoole_version.h';
@@ -254,6 +247,7 @@ class SourcePatcher
if ($version === '5.1.3') {
self::patchFile('spc_fix_swoole_50513.patch', SOURCE_PATH . '/php-src/ext/swoole');
}
self::patchFile('swoole_fix_date_time.patch', SOURCE_PATH . '/php-src/ext/swoole');
return true;
}
@@ -279,14 +273,28 @@ class SourcePatcher
// call extension patch before make
foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeMake() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before make');
logger()->info("Extension [{$ext->getName()}] patched before make");
}
}
foreach ($builder->getLibs() as $lib) {
if ($lib->patchBeforeMake() === true) {
logger()->info('Library [' . $lib->getName() . '] patched before make');
logger()->info("Library [{$lib->getName()}] patched before make");
}
}
if (str_contains((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), '-release')) {
FileSystem::replaceFileLineContainsString(
SOURCE_PATH . '/php-src/ext/standard/info.c',
'#ifdef CONFIGURE_COMMAND',
'#ifdef NO_CONFIGURE_COMMAND',
);
} else {
FileSystem::replaceFileLineContainsString(
SOURCE_PATH . '/php-src/ext/standard/info.c',
'#ifdef NO_CONFIGURE_COMMAND',
'#ifdef CONFIGURE_COMMAND',
);
}
}
/**
@@ -446,7 +454,7 @@ class SourcePatcher
}
$extnum = intval($match[1]);
if ($extnum < 30800) {
SourcePatcher::patchFile('imagick_php84_before_30800.patch', SOURCE_PATH . '/php-src/ext/imagick');
self::patchFile('imagick_php84_before_30800.patch', SOURCE_PATH . '/php-src/ext/imagick');
return true;
}
return false;
@@ -454,7 +462,7 @@ class SourcePatcher
public static function patchFfiCentos7FixO3strncmp(): bool
{
if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::getLibcVersionIfExists() >= '2.17') {
if (!($ver = SPCTarget::getLibcVersion()) || version_compare($ver, '2.17', '>')) {
return false;
}
if (!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) {
@@ -464,14 +472,14 @@ class SourcePatcher
if (preg_match('/PHP_VERSION_ID (\d+)/', $file, $match) !== 0 && intval($match[1]) < 80316) {
return false;
}
SourcePatcher::patchFile('ffi_centos7_fix_O3_strncmp.patch', SOURCE_PATH . '/php-src');
self::patchFile('ffi_centos7_fix_O3_strncmp.patch', SOURCE_PATH . '/php-src');
return true;
}
public static function patchLibaomForAlpine(): bool
{
if (PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist()) {
SourcePatcher::patchFile('libaom_posix_implict.patch', SOURCE_PATH . '/libaom');
self::patchFile('libaom_posix_implict.patch', SOURCE_PATH . '/libaom');
return true;
}
return false;
@@ -480,7 +488,7 @@ class SourcePatcher
public static function patchAttrForAlpine(): bool
{
if (PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist() || PHP_OS_FAMILY === 'Darwin') {
SourcePatcher::patchFile('attr_alpine_gethostname.patch', SOURCE_PATH . '/attr');
self::patchFile('attr_alpine_gethostname.patch', SOURCE_PATH . '/attr');
return true;
}
return false;

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace SPC\store\pkg;
abstract class CustomPackage
{
abstract public function getSupportName(): array;
abstract public function fetch(string $name, bool $force = false, ?array $config = null): void;
abstract public function extract(string $name): void;
abstract public static function getEnvironment(): array;
abstract public static function isInstalled(): bool;
}

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace SPC\store\pkg;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\LockFile;
class GoXcaddy extends CustomPackage
{
public static function isInstalled(): bool
{
$arch = arch2gnu(php_uname('m'));
$os = match (PHP_OS_FAMILY) {
'Windows' => 'win',
'Darwin' => 'macos',
'BSD' => 'freebsd',
default => 'linux',
};
$packageName = "go-xcaddy-{$arch}-{$os}";
$pkgroot = PKG_ROOT_PATH;
$folder = "{$pkgroot}/{$packageName}";
return is_dir($folder) && is_file("{$folder}/bin/go") && is_file("{$folder}/bin/xcaddy");
}
public function getSupportName(): array
{
return [
'go-xcaddy-x86_64-linux',
'go-xcaddy-x86_64-macos',
'go-xcaddy-aarch64-linux',
'go-xcaddy-aarch64-macos',
];
}
public function fetch(string $name, bool $force = false, ?array $config = null): void
{
$pkgroot = PKG_ROOT_PATH;
$go_exec = "{$pkgroot}/{$name}/bin/go";
$xcaddy_exec = "{$pkgroot}/{$name}/bin/xcaddy";
if ($force) {
FileSystem::removeDir("{$pkgroot}/{$name}");
}
if (file_exists($go_exec) && file_exists($xcaddy_exec)) {
return;
}
$arch = match (explode('-', $name)[2]) {
'x86_64' => 'amd64',
'aarch64' => 'arm64',
default => throw new \InvalidArgumentException('Unsupported architecture: ' . $name),
};
$os = match (explode('-', $name)[3]) {
'linux' => 'linux',
'macos' => 'darwin',
default => throw new \InvalidArgumentException('Unsupported OS: ' . $name),
};
$go_version = '1.24.4';
$config = [
'type' => 'url',
'url' => "https://go.dev/dl/go{$go_version}.{$os}-{$arch}.tar.gz",
];
Downloader::downloadPackage($name, $config, $force);
}
public function extract(string $name): void
{
$pkgroot = PKG_ROOT_PATH;
$go_exec = "{$pkgroot}/{$name}/bin/go";
$xcaddy_exec = "{$pkgroot}/{$name}/bin/xcaddy";
if (file_exists($go_exec) && file_exists($xcaddy_exec)) {
return;
}
$lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true);
$source_type = $lock[$name]['source_type'];
$filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']);
$extract = $lock[$name]['move_path'] === null ? "{$pkgroot}/{$name}" : $lock[$name]['move_path'];
FileSystem::extractPackage($name, $source_type, $filename, $extract);
// install xcaddy
shell()
->appendEnv([
'PATH' => "{$pkgroot}/{$name}/bin:" . getenv('PATH'),
'GOROOT' => "{$pkgroot}/{$name}",
'GOBIN' => "{$pkgroot}/{$name}/bin",
'GOPATH' => "{$pkgroot}/go",
])
->exec("CC=cc {$go_exec} install github.com/caddyserver/xcaddy/cmd/xcaddy@latest");
}
public static function getEnvironment(): array
{
$arch = arch2gnu(php_uname('m'));
$os = match (PHP_OS_FAMILY) {
'Windows' => 'win',
'Darwin' => 'macos',
'BSD' => 'freebsd',
default => 'linux',
};
$packageName = "go-xcaddy-{$arch}-{$os}";
$pkgroot = PKG_ROOT_PATH;
return [
'PATH' => "{$pkgroot}/{$packageName}/bin",
'GOROOT' => "{$pkgroot}/{$packageName}",
'GOBIN' => "{$pkgroot}/{$packageName}/bin",
'GOPATH' => "{$pkgroot}/go",
];
}
}

164
src/SPC/store/pkg/Zig.php Normal file
View File

@@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace SPC\store\pkg;
use SPC\store\CurlHook;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\LockFile;
class Zig extends CustomPackage
{
public static function isInstalled(): bool
{
$path = self::getPath();
return file_exists("{$path}/zig") && file_exists("{$path}/zig-cc") && file_exists("{$path}/zig-c++");
}
public function getSupportName(): array
{
return [
'zig-x86_64-linux',
'zig-aarch64-linux',
'zig-x86_64-macos',
'zig-aarch64-macos',
'zig-x86_64-win',
];
}
public function fetch(string $name, bool $force = false, ?array $config = null): void
{
$pkgroot = PKG_ROOT_PATH;
$zig_exec = match (PHP_OS_FAMILY) {
'Windows' => "{$pkgroot}/{$name}/zig.exe",
default => "{$pkgroot}/{$name}/zig",
};
if ($force) {
FileSystem::removeDir("{$pkgroot}/{$name}");
}
if (file_exists($zig_exec)) {
return;
}
$parts = explode('-', $name);
$arch = $parts[1];
$os = $parts[2];
$zig_arch = match ($arch) {
'x86_64', 'aarch64' => $arch,
default => throw new \InvalidArgumentException('Unsupported architecture: ' . $arch),
};
$zig_os = match ($os) {
'linux' => 'linux',
'macos' => 'macos',
'win' => 'windows',
default => throw new \InvalidArgumentException('Unsupported OS: ' . $os),
};
$index_json = json_decode(Downloader::curlExec('https://ziglang.org/download/index.json', hooks: [[CurlHook::class, 'setupGithubToken']]), true);
$latest_version = null;
foreach ($index_json as $version => $data) {
$latest_version = $version;
break;
}
if (!$latest_version) {
throw new \RuntimeException('Could not determine latest Zig version');
}
logger()->info("Installing Zig version {$latest_version}");
$platform_key = "{$zig_arch}-{$zig_os}";
if (!isset($index_json[$latest_version][$platform_key])) {
throw new \RuntimeException("No download available for {$platform_key} in Zig version {$latest_version}");
}
$download_info = $index_json[$latest_version][$platform_key];
$url = $download_info['tarball'];
$filename = basename($url);
$pkg = [
'type' => 'url',
'url' => $url,
'filename' => $filename,
];
Downloader::downloadPackage($name, $pkg, $force);
}
public function extract(string $name): void
{
$pkgroot = PKG_ROOT_PATH;
$zig_bin_dir = "{$pkgroot}/{$name}";
$zig_exec = match (PHP_OS_FAMILY) {
'Windows' => "{$zig_bin_dir}/zig.exe",
default => "{$zig_bin_dir}/zig",
};
if (file_exists($zig_exec) && file_exists("{$zig_bin_dir}/zig-cc")) {
return;
}
$lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true);
$source_type = $lock[$name]['source_type'];
$filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']);
$extract = "{$pkgroot}/{$name}";
FileSystem::extractPackage($name, $source_type, $filename, $extract);
$this->createZigCcScript($zig_bin_dir);
}
public static function getEnvironment(): array
{
$arch = arch2gnu(php_uname('m'));
$os = match (PHP_OS_FAMILY) {
'Linux' => 'linux',
'Windows' => 'win',
'Darwin' => 'macos',
'BSD' => 'freebsd',
default => 'linux',
};
$packageName = "zig-{$arch}-{$os}";
$path = PKG_ROOT_PATH . "/{$packageName}";
return [
'PATH' => $path,
];
}
private static function getPath(): string
{
$arch = arch2gnu(php_uname('m'));
$os = match (PHP_OS_FAMILY) {
'Linux' => 'linux',
'Windows' => 'win',
'Darwin' => 'macos',
'BSD' => 'freebsd',
default => 'linux',
};
$packageName = "zig-{$arch}-{$os}";
return PKG_ROOT_PATH . "/{$packageName}";
}
private function createZigCcScript(string $bin_dir): void
{
$script_path = __DIR__ . '/../scripts/zig-cc.sh';
$script_content = file_get_contents($script_path);
file_put_contents("{$bin_dir}/zig-cc", $script_content);
chmod("{$bin_dir}/zig-cc", 0755);
$script_content = str_replace('zig cc', 'zig c++', $script_content);
file_put_contents("{$bin_dir}/zig-c++", $script_content);
chmod("{$bin_dir}/zig-c++", 0755);
}
}

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
BUILDROOT_ABS="$(realpath "$SCRIPT_DIR/../../buildroot/include" 2>/dev/null || echo "")"
PARSED_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-isystem)
shift
ARG="$1"
[[ -n "$ARG" ]] && shift || break
ARG_ABS="$(realpath "$ARG" 2>/dev/null || echo "")"
if [[ -n "$ARG_ABS" && "$ARG_ABS" == "$BUILDROOT_ABS" ]]; then
PARSED_ARGS+=("-I$ARG")
else
PARSED_ARGS+=("-isystem" "$ARG")
fi
;;
-isystem*)
ARG="${1#-isystem}"
shift
ARG_ABS="$(realpath "$ARG" 2>/dev/null || echo "")"
if [[ -n "$ARG_ABS" && "$ARG_ABS" == "$BUILDROOT_ABS" ]]; then
PARSED_ARGS+=("-I$ARG")
else
PARSED_ARGS+=("-isystem$ARG")
fi
;;
*)
PARSED_ARGS+=("$1")
shift
;;
esac
done
TARGET=""
if [ -n "$SPC_TARGET" ]; then
TARGET="-target $SPC_TARGET"
fi
output=$(zig cc $TARGET -lstdc++ $COMPILER_EXTRA "${PARSED_ARGS[@]}" 2>&1)
status=$?
if [ $status -eq 0 ]; then
echo "$output"
exit 0
fi
if echo "$output" | grep -q "version '.*' in target triple"; then
echo "$output" | grep -v "version '.*' in target triple"
exit 0
else
exec zig cc $TARGET $COMPILER_EXTRA "${PARSED_ARGS[@]}"
fi

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
use SPC\builder\freebsd\SystemUtil as FreeBSDSystemUtil;
use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
use SPC\builder\macos\SystemUtil as MacOSSystemUtil;
use SPC\exception\WrongUsageException;
use SPC\util\GlobalEnvManager;
class ClangNativeToolchain implements ToolchainInterface
{
public function initEnv(): void
{
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=clang');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=clang++');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=ar');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld');
}
public function afterInit(): void
{
// check clang exists
match (PHP_OS_FAMILY) {
'Linux' => LinuxSystemUtil::findCommand('clang++') ?? throw new WrongUsageException('Clang++ not found, please install it or manually set CC/CXX to a valid path.'),
'Darwin' => MacOSSystemUtil::findCommand('clang++') ?? throw new WrongUsageException('Clang++ not found, please install it or set CC/CXX to a valid path.'),
'BSD' => FreeBSDSystemUtil::findCommand('clang++') ?? throw new WrongUsageException('Clang++ not found, please install it or set CC/CXX to a valid path.'),
default => throw new WrongUsageException('Clang is not supported on ' . PHP_OS_FAMILY . '.'),
};
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
use SPC\builder\freebsd\SystemUtil as FreeBSDSystemUtil;
use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
use SPC\builder\macos\SystemUtil as MacOSSystemUtil;
use SPC\exception\WrongUsageException;
use SPC\util\GlobalEnvManager;
class GccNativeToolchain implements ToolchainInterface
{
public function initEnv(): void
{
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=gcc');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=g++');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=ar');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld.gold');
}
public function afterInit(): void
{
// check gcc exists
match (PHP_OS_FAMILY) {
'Linux' => LinuxSystemUtil::findCommand('g++') ?? throw new WrongUsageException('g++ not found, please install it or set CC/CXX to a valid path.'),
'Darwin' => MacOSSystemUtil::findCommand('g++') ?? throw new WrongUsageException('g++ not found, please install it or set CC/CXX to a valid path.'),
'BSD' => FreeBSDSystemUtil::findCommand('g++') ?? throw new WrongUsageException('g++ not found, please install it or set CC/CXX to a valid path.'),
default => throw new \RuntimeException('GCC is not supported on ' . PHP_OS_FAMILY . '.'),
};
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
class MSVCToolchain implements ToolchainInterface
{
public function initEnv(): void {}
public function afterInit(): void {}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
use SPC\exception\WrongUsageException;
use SPC\util\GlobalEnvManager;
class MuslToolchain implements ToolchainInterface
{
public function initEnv(): void
{
$arch = getenv('GNU_ARCH');
// Set environment variables for musl toolchain
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_CC={$arch}-linux-musl-gcc");
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_CXX={$arch}-linux-musl-g++");
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_AR={$arch}-linux-musl-ar");
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_LD={$arch}-linux-musl-ld");
GlobalEnvManager::addPathIfNotExists('/usr/local/musl/bin');
GlobalEnvManager::addPathIfNotExists("/usr/local/musl/{$arch}-linux-musl/bin");
GlobalEnvManager::putenv("SPC_LD_LIBRARY_PATH=/usr/local/musl/lib:/usr/local/musl/{$arch}-linux-musl/lib");
GlobalEnvManager::putenv("SPC_LIBRARY_PATH=/usr/local/musl/lib:/usr/local/musl/{$arch}-linux-musl/lib");
}
public function afterInit(): void
{
$arch = getenv('GNU_ARCH');
// append LD_LIBRARY_PATH to $configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE');
$configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE');
$ld_library_path = getenv('SPC_LD_LIBRARY_PATH');
GlobalEnvManager::putenv("SPC_CMD_PREFIX_PHP_CONFIGURE=LD_LIBRARY_PATH=\"{$ld_library_path}\" {$configure}");
if (!file_exists("/usr/local/musl/{$arch}-linux-musl/lib/libc.a")) {
throw new WrongUsageException('You are building with musl-libc target in glibc distro, but musl-toolchain is not installed, please install musl-toolchain first. (You can use `doctor` command to install it)');
}
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
interface ToolchainInterface
{
/**
* Initialize the environment for the given target.
*/
public function initEnv(): void;
/**
* Perform actions after the environment has been initialized for the given target.
*/
public function afterInit(): void;
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
use SPC\builder\linux\SystemUtil;
use SPC\exception\WrongUsageException;
use SPC\util\GlobalEnvManager;
class ToolchainManager
{
public const array OS_DEFAULT_TOOLCHAIN = [
'Linux' => ZigToolchain::class,
'Windows' => MSVCToolchain::class,
'Darwin' => ClangNativeToolchain::class,
'BSD' => ClangNativeToolchain::class,
];
public static function getToolchainClass(): string
{
$libc = getenv('SPC_LIBC');
if ($libc && !getenv('SPC_TARGET')) {
// TODO: @crazywhalecc this breaks tests
// logger()->warning('SPC_LIBC is deprecated, please use SPC_TARGET instead.');
return match ($libc) {
'musl' => SystemUtil::isMuslDist() ? GccNativeToolchain::class : MuslToolchain::class,
'glibc' => !SystemUtil::isMuslDist() ? GccNativeToolchain::class : throw new WrongUsageException('SPC_LIBC must be musl for musl dist.'),
default => throw new WrongUsageException('Unsupported SPC_LIBC value: ' . $libc),
};
}
return self::OS_DEFAULT_TOOLCHAIN[PHP_OS_FAMILY];
}
/**
* @throws WrongUsageException
*/
public static function initToolchain(): void
{
$toolchainClass = self::getToolchainClass();
/* @var ToolchainInterface $toolchainClass */
(new $toolchainClass())->initEnv();
GlobalEnvManager::putenv("SPC_TOOLCHAIN={$toolchainClass}");
}
public static function afterInitToolchain(): void
{
if (!getenv('SPC_TOOLCHAIN')) {
throw new WrongUsageException('SPC_TOOLCHAIN not set');
}
$toolchain = getenv('SPC_TOOLCHAIN');
/* @var ToolchainInterface $toolchain */
(new $toolchain())->afterInit();
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
use SPC\exception\WrongUsageException;
use SPC\store\pkg\Zig;
use SPC\util\GlobalEnvManager;
class ZigToolchain implements ToolchainInterface
{
public function initEnv(): void
{
// Set environment variables for zig toolchain
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=zig-cc');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=zig-c++');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=ar');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld');
}
public function afterInit(): void
{
if (!is_dir(Zig::getEnvironment()['PATH'])) {
throw new WrongUsageException('You are building with zig, but zig is not installed, please install zig first. (You can use `doctor` command to install it)');
}
GlobalEnvManager::addPathIfNotExists(Zig::getEnvironment()['PATH']);
}
}

View File

@@ -164,6 +164,12 @@ class ConfigValidator
if (is_string($craft['extensions'])) {
$craft['extensions'] = array_filter(array_map(fn ($x) => trim($x), explode(',', $craft['extensions'])));
}
if (!isset($craft['shared-extensions'])) {
$craft['shared-extensions'] = [];
}
if (is_string($craft['shared-extensions'] ?? [])) {
$craft['shared-extensions'] = array_filter(array_map(fn ($x) => trim($x), explode(',', $craft['shared-extensions'])));
}
// check libs
if (isset($craft['libs']) && is_string($craft['libs'])) {
$craft['libs'] = array_filter(array_map(fn ($x) => trim($x), explode(',', $craft['libs'])));

View File

@@ -4,10 +4,9 @@ declare(strict_types=1);
namespace SPC\util;
use SPC\builder\BuilderBase;
use SPC\builder\linux\SystemUtil;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\toolchain\ToolchainManager;
/**
* Environment variable manager
@@ -24,11 +23,10 @@ class GlobalEnvManager
/**
* Initialize the environment variables
*
* @param null|BuilderBase $builder Builder
* @throws RuntimeException
* @throws WrongUsageException
*/
public static function init(?BuilderBase $builder = null): void
public static function init(): void
{
// Check pre-defined env vars exists
if (getenv('BUILD_ROOT_PATH') === false) {
@@ -37,25 +35,89 @@ class GlobalEnvManager
// Define env vars for unix
if (is_unix()) {
self::putenv('PATH=' . BUILD_ROOT_PATH . '/bin:' . getenv('PATH'));
self::addPathIfNotExists(BUILD_BIN_PATH);
self::putenv('PKG_CONFIG=' . BUILD_BIN_PATH . '/pkg-config');
self::putenv('PKG_CONFIG_PATH=' . BUILD_ROOT_PATH . '/lib/pkgconfig');
}
// Define env vars for linux
if (PHP_OS_FAMILY === 'Linux') {
$arch = getenv('GNU_ARCH');
if (SystemUtil::isMuslDist()) {
self::putenv('SPC_LINUX_DEFAULT_CC=gcc');
self::putenv('SPC_LINUX_DEFAULT_CXX=g++');
self::putenv('SPC_LINUX_DEFAULT_AR=ar');
} else {
self::putenv("SPC_LINUX_DEFAULT_CC={$arch}-linux-musl-gcc");
self::putenv("SPC_LINUX_DEFAULT_CXX={$arch}-linux-musl-g++");
self::putenv("SPC_LINUX_DEFAULT_AR={$arch}-linux-musl-ar");
$ini = self::readIniFile();
$default_put_list = [];
foreach ($ini['global'] as $k => $v) {
if (getenv($k) === false) {
$default_put_list[$k] = $v;
self::putenv("{$k}={$v}");
}
}
$os_ini = match (PHP_OS_FAMILY) {
'Windows' => $ini['windows'] ?? [],
'Darwin' => $ini['macos'] ?? [],
'Linux' => $ini['linux'] ?? [],
'BSD' => $ini['freebsd'] ?? [],
default => [],
};
foreach ($os_ini as $k => $v) {
if (getenv($k) === false) {
$default_put_list[$k] = $v;
self::putenv("{$k}={$v}");
}
}
ToolchainManager::initToolchain();
// apply second time
$ini2 = self::readIniFile();
foreach ($ini2['global'] as $k => $v) {
if (isset($default_put_list[$k]) && $default_put_list[$k] !== $v) {
self::putenv("{$k}={$v}");
}
}
$os_ini2 = match (PHP_OS_FAMILY) {
'Windows' => $ini2['windows'] ?? [],
'Darwin' => $ini2['macos'] ?? [],
'Linux' => $ini2['linux'] ?? [],
'BSD' => $ini2['freebsd'] ?? [],
default => [],
};
foreach ($os_ini2 as $k => $v) {
if (isset($default_put_list[$k]) && $default_put_list[$k] !== $v) {
self::putenv("{$k}={$v}");
}
}
}
public static function putenv(string $val): void
{
f_putenv($val);
self::$env_cache[] = $val;
}
public static function addPathIfNotExists(string $path): void
{
if (is_unix() && !str_contains(getenv('PATH'), $path)) {
self::putenv("PATH={$path}:" . getenv('PATH'));
}
}
/**
* Initialize the toolchain after the environment variables are set.
* The toolchain or environment availability check is done here.
*
* @throws WrongUsageException
*/
public static function afterInit(): void
{
if (!filter_var(getenv('SPC_SKIP_TOOLCHAIN_CHECK'), FILTER_VALIDATE_BOOL)) {
ToolchainManager::afterInitToolchain();
}
}
/**
* @throws WrongUsageException
*/
private static function readIniFile(): array
{
// Init env.ini file, read order:
// WORKING_DIR/config/env.ini
// ROOT_DIR/config/env.ini
@@ -97,28 +159,6 @@ class GlobalEnvManager
break;
}
}
self::applyConfig($ini['global']);
match (PHP_OS_FAMILY) {
'Windows' => self::applyConfig($ini['windows']),
'Darwin' => self::applyConfig($ini['macos']),
'Linux' => self::applyConfig($ini['linux']),
'BSD' => self::applyConfig($ini['freebsd']),
default => null,
};
}
public static function putenv(string $val): void
{
f_putenv($val);
self::$env_cache[] = $val;
}
private static function applyConfig(array $ini): void
{
foreach ($ini as $k => $v) {
if (getenv($k) === false) {
self::putenv($k . '=' . $v);
}
}
return $ini;
}
}

Some files were not shown because too many files have changed in this diff Show More