Compare commits

...

203 Commits

Author SHA1 Message Date
DubbleClick
2ef64e4597 add libjxl #755 2025-07-20 00:11:29 +07:00
DubbleClick
6c18862fd1 fixes 2025-07-19 23:54:38 +07:00
Marc
6c109c52f6 Update src/SPC/builder/unix/library/jbig.php
Co-authored-by: Jerry Ma <jesse2061@outlook.com>
2025-07-19 17:56:54 +07:00
DubbleClick
c7762a5b65 Revert "only uncomment that line temporarily"
This reverts commit d07fadde76.
2025-07-18 20:44:04 +07:00
DubbleClick
d07fadde76 only uncomment that line temporarily 2025-07-18 20:30:51 +07:00
DubbleClick
ac7a09edd1 oops 2025-07-18 14:52:39 +07:00
Marc
f30f095f00 Merge branch 'main' into tiff 2025-07-18 14:47:59 +07:00
DubbleClick
d91a66ee8d test 2025-07-18 14:47:05 +07:00
DubbleClick
7db7ed97ee sort 2025-07-18 14:41:10 +07:00
DubbleClick
6b9d49eb71 fix lerc build (needs -lstdc++) 2025-07-18 14:39:17 +07:00
DubbleClick
7c9e8e3e40 add jbig 2025-07-18 14:04:55 +07:00
DubbleClick
5586f627d3 add lerc 2025-07-18 13:51:33 +07:00
Marc
e4921087d9 Merge pull request #823 from crazywhalecc/fix/curl-patch
Fix curl build for non-windows
2025-07-18 12:37:38 +07:00
crazywhalecc
005b78413c Fix curl build for non-windows 2025-07-18 13:23:46 +08:00
Jerry Ma
3d45890a2d Merge pull request #821 from crazywhalecc/fix/windows-curl-sspi
Fix/windows curl sspi
2025-07-17 13:58:31 +08:00
Jerry Ma
bb5eb64b28 Merge pull request #802 from crazywhalecc/feat/safe-pack
Check lib-suggest before packing libs
2025-07-17 13:34:15 +08:00
crazywhalecc
e6a050de82 Use SPC_EXTRA_LIBS instead of SPC_WINDOWS_EXEC_LIBS 2025-07-17 13:14:18 +08:00
crazywhalecc
7e7eb844a7 cs-fix 2025-07-17 12:26:30 +08:00
crazywhalecc
e56c628bec Add windows tests 2025-07-17 12:23:03 +08:00
crazywhalecc
c604f88685 Disallow alt for php-src 2025-07-17 12:21:50 +08:00
crazywhalecc
83db186972 Fix curl static build for Windows 2025-07-17 12:21:13 +08:00
Jerry Ma
9af3b745a2 Merge pull request #792 from crazywhalecc/fix/ext-imap-extraction
Skip re-extract ext-imap source
2025-07-10 21:39:34 +08:00
crazywhalecc
a5cd5fcb31 Fix test and bump docker image version 2025-07-10 20:20:29 +08:00
crazywhalecc
ec153fa6ff Workaround for swoole with alpine 3.21 2025-07-10 20:10:54 +08:00
crazywhalecc
dd72b32559 Merge 2025-07-10 12:59:27 +08:00
Marc
a5d26d6517 Merge pull request #813 from crazywhalecc/fix-native
fix g++ not found error
2025-07-01 22:12:09 +07:00
DubbleClick
e5ea32e9c0 fix in case it's not set 2025-07-01 17:57:36 +07:00
DubbleClick
5bc4504e37 fix g++ not found error 2025-07-01 17:53:35 +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
crazywhalecc
e1e48922de Use isStatic instead of isStaticTarget 2025-06-29 22:49:48 +08: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
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
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
crazywhalecc
c23c5ae614 Disable openmp for imagemagick 2025-06-28 23:08:17 +08:00
crazywhalecc
454b5a77ad Add SPC_LIBC mapping to SPC_TARGET 2025-06-28 22:59:02 +08: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
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
crazywhalecc
5681722c09 Fix global env manager, add in-ini variable parsing 2025-06-25 15:32:18 +08: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
crazywhalecc
f6d25153c6 Allow deleting pre-built or source only for del-download 2025-06-22 16:04:50 +08:00
crazywhalecc
1409d1b47b Check lib-suggest before packing libs 2025-06-22 13:52:53 +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
107 changed files with 2037 additions and 508 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 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 a modification is not involved, please skip it directly.
- [ ] If you modified `*.php`, run `composer cs-fix` at local machine. - If you modified `*.php` or `*.json`, run them locally to ensure your changes are valid:
- [ ] If it's an extension or dependency update, make sure adding related extensions in `src/global/test-extensions.php`. - [ ] `PHP_CS_FIXER_IGNORE_ENV=1 composer cs-fix`
- [ ] If you changed the behavior of static-php-cli, update docs in `./docs/`. - [ ] `composer analyse`
- [ ] If you updated `config/xxx.json` content, run `bin/spc dev:sort-config xxx`. - [ ] `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: on:
workflow_dispatch: workflow_dispatch:
pull_request: push:
branches: [ "main" ]
paths:
- '.github/workflows/ext-matrix-tests.yml'
jobs: jobs:
test: test:
name: "${{ matrix.extension }} (PHP ${{ matrix.php-version }} on ${{ matrix.operating-system }})" name: "${{ matrix.extension }} (PHP ${{ matrix.php-version }} on ${{ matrix.operating-system }})"
runs-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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:

View File

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

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
return (new PhpCsFixer\Config()) return (new PhpCsFixer\Config())
->setRiskyAllowed(true) ->setRiskyAllowed(true)
->setUnsupportedPhpVersionAllowed(true)
->setRules([ ->setRules([
'@PSR12' => true, '@PSR12' => true,
'@Symfony' => true, '@Symfony' => true,

View File

@@ -9,7 +9,7 @@
**static-php-cli**是一个用于静态编译、构建 PHP 解释器的工具,支持众多流行扩展。 **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 运行时打包为一个文件并运行。 **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` 来获取编译时的配置。 如果你知道 [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** 服务器。 你可以在 [我的个人赞助页](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 许可开源,下面是类似的项目: 本项目采用 MIT License 许可开源,下面是类似的项目:

View File

@@ -10,7 +10,7 @@
**static-php-cli** is a powerful tool designed for building static, standalone PHP runtime **static-php-cli** is a powerful tool designed for building static, standalone PHP runtime
with popular extensions. 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 **static-php-cli** also has the ability to package PHP projects
along with the PHP interpreter into one single executable file. 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 may require the introduction of other libraries during compilation,
you can use `buildroot/bin/php-config` to obtain the compile-time configuration. 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 ## 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. 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 ## Open-Source License
This project itself is based on MIT 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 # This file is using docker to run commands
SPC_DOCKER_VERSION=v3 SPC_DOCKER_VERSION=v5
# Detect docker can run # Detect docker can run
if ! which docker >/dev/null; then if ! which docker >/dev/null; then
echo "Docker is not installed, please install docker first !" echo "Docker is not installed, please install docker first !"
exit 1 exit 1
fi fi
DOCKER_EXECUTABLE="docker" DOCKER_EXECUTABLE="docker"
# shellcheck disable=SC2046 # shellcheck disable=SC2046
@@ -22,27 +24,48 @@ if [ $(id -u) -ne 0 ]; then
fi fi
fi fi
# Convert uname to gnu arch
CURRENT_ARCH=$(uname -m)
# to check if qemu-docker run if [ "$CURRENT_ARCH" = "arm64" ]; then
if [ "$SPC_USE_ARCH" = "" ]; then CURRENT_ARCH=aarch64
SPC_USE_ARCH=x86_64
fi fi
if [ -z "$SPC_USE_ARCH" ]; then
SPC_USE_ARCH=$CURRENT_ARCH
fi
# parse SPC_USE_ARCH
case $SPC_USE_ARCH in case $SPC_USE_ARCH in
x86_64) x86_64|amd64)
ALPINE_FROM=alpine:edge 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) aarch64|arm64)
ALPINE_FROM=multiarch/alpine:aarch64-edge SPC_USE_ARCH=aarch64
# shellcheck disable=SC2039 if [ "$CURRENT_ARCH" != "aarch64" ]; then
echo -e "\e[033m* Using different arch needs to setup qemu-static for docker !\e[0m" PLATFORM_ARG="--platform linux/arm64"
$DOCKER_EXECUTABLE run --rm --privileged multiarch/qemu-user-static:register --reset > /dev/null ALPINE_FROM=multiarch/alpine:aarch64-edge
fi
;; ;;
*) *)
echo "Current arch is not supported to run in docker: $SPC_USE_ARCH" echo "Current arch is not supported to run in docker: $SPC_USE_ARCH"
exit 1 exit 1
;; ;;
esac esac
# if ALPINE_FROM is not set, use alpine:3.21
if [ -z "$ALPINE_FROM" ]; then
ALPINE_FROM=alpine:3.21
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:3.21
fi
if [ "$SPC_USE_MIRROR" = "yes" ]; then 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" 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 # Detect docker env is setup
if ! $DOCKER_EXECUTABLE images | grep -q cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION; then if ! $DOCKER_EXECUTABLE images | grep -q cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION; then
echo "Docker container does not exist. Building docker image ..." 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 FROM $ALPINE_FROM
$SPC_USE_MIRROR $SPC_USE_MIRROR
RUN apk update; \ RUN apk update; \
@@ -84,7 +107,8 @@ RUN apk update; \
wget \ wget \
xz \ xz \
gettext-dev \ 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 && \ 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 chmod +x /usr/local/bin/php
@@ -147,7 +171,7 @@ if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then
echo "* ./pkgroot: $(pwd)/pkgroot" echo "* ./pkgroot: $(pwd)/pkgroot"
echo "*" echo "*"
set -ex 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 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 fi

View File

@@ -1,5 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e
# This file is using docker to run commands
SPC_DOCKER_VERSION=v4
# Detect docker can run # Detect docker can run
if ! which docker >/dev/null; then if ! which docker >/dev/null; then
echo "Docker is not installed, please install docker first !" echo "Docker is not installed, please install docker first !"
@@ -19,35 +24,47 @@ if [ $(id -u) -ne 0 ]; then
fi fi
fi fi
# Convert uname to gnu arch
CURRENT_ARCH=$(uname -m)
# to check if qemu-docker run if [ "$CURRENT_ARCH" = "arm64" ]; then
if [ "$SPC_USE_ARCH" = "" ]; then CURRENT_ARCH=aarch64
SPC_USE_ARCH=current
fi fi
if [ -z "$SPC_USE_ARCH" ]; then
SPC_USE_ARCH=$CURRENT_ARCH
fi
# parse SPC_USE_ARCH
case $SPC_USE_ARCH in case $SPC_USE_ARCH in
current) x86_64|amd64)
BASE_ARCH=$(uname -m) SPC_USE_ARCH=x86_64
if [ "$BASE_ARCH" = "arm64" ]; then SPC_USE_ARCH_DOCKER=amd64
BASE_ARCH=aarch64 if [ "$CURRENT_ARCH" != "x86_64" ]; then
PLATFORM_ARG="--platform linux/amd64"
fi fi
;; ;;
aarch64) aarch64|arm64)
BASE_ARCH=aarch64 SPC_USE_ARCH=aarch64
# shellcheck disable=SC2039 SPC_USE_ARCH_DOCKER=arm64
echo -e "\e[033m* Using different arch needs to setup qemu-static for docker !\e[0m" if [ "$CURRENT_ARCH" != "aarch64" ]; then
$DOCKER_EXECUTABLE run --rm --privileged multiarch/qemu-user-static:register --reset > /dev/null PLATFORM_ARG="--platform linux/arm64"
fi
;; ;;
*) *)
echo "Current arch is not supported to run in docker: $SPC_USE_ARCH" echo "Current arch is not supported to run in docker: $SPC_USE_ARCH"
exit 1 exit 1
;; ;;
esac 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 # 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 ..." 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 FROM centos:7
RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/*.repo && \ 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 && \ 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 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-rh.repo ; \
sed -i 's|mirror.centos.org/centos|vault.centos.org/altarch|g' /etc/yum.repos.d/CentOS-SCLo-scl.repo ; \ sed -i 's|mirror.centos.org/centos|vault.centos.org/altarch|g' /etc/yum.repos.d/CentOS-SCLo-scl.repo ; \
else \ else \
@@ -74,7 +91,12 @@ RUN echo "source scl_source enable devtoolset-10" >> /etc/bashrc
RUN source /etc/bashrc RUN source /etc/bashrc
RUN yum install -y which 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 && \ mkdir /cmake && \
tar -xzf cmake.tgz -C /cmake --strip-components 1 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 ADD ./bin/spc /app/bin/spc
RUN /app/bin/setup-runtime RUN /app/bin/setup-runtime
RUN /app/bin/php /app/bin/composer install --no-dev RUN /app/bin/php /app/bin/composer install --no-dev
ENV PATH="/app/bin:/cmake/bin:$PATH"
ENV SPC_LIBC=glibc 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 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 && \ RUN curl -o make.tgz -fsSL https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && \
tar -zxvf make.tgz && \ 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 && \
make install && \ make install && \
ln -sf /usr/local/bin/automake /usr/bin/automake ln -sf /usr/local/bin/automake /usr/bin/automake
EOF EOF
fi fi
@@ -132,11 +153,7 @@ if [ -f "$(pwd)/craft.yml" ]; then
fi fi
# Apply env in temp env file # Apply env in temp env file
echo 'CC=/opt/rh/devtoolset-10/root/usr/bin/gcc' > /tmp/spc-gnu-docker.env echo 'SPC_DEFAULT_C_FLAGS=-fPIC' > /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_LIBC=glibc' >> /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_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 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 "* ./pkgroot: $(pwd)/pkgroot"
echo "*" echo "*"
set -ex 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 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 fi

View File

@@ -42,6 +42,9 @@ SPC_CONCURRENCY=${CPU_COUNT}
SPC_SKIP_PHP_VERSION_CHECK="no" 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") ; 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="" 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 ; 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 ; only useful for builds targeting not pure-static linking
; default paths ; default paths
@@ -52,30 +55,45 @@ SPC_SKIP_DOCTOR_CHECK_ITEMS=""
; EXTENSION_DIR= ; EXTENSION_DIR=
[windows] [windows]
; build target: win7-static
SPC_TARGET=native-windows
; php-sdk-binary-tools path ; php-sdk-binary-tools path
PHP_SDK_PATH="${WORKING_DIR}\php-sdk-binary-tools" PHP_SDK_PATH="${WORKING_DIR}\php-sdk-binary-tools"
; upx executable path ; upx executable path
UPX_EXEC="${PKG_ROOT_PATH}\bin\upx.exe" UPX_EXEC="${PKG_ROOT_PATH}\bin\upx.exe"
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches ; 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,cli_static SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime140,win32,zend_stream,cli_static
; Windows static linking system libs
SPC_EXTRA_LIBS=""
[linux] [linux]
; Linux can use different build toolchain, but the toolchain can not be changed in this file:
; - musl (default): used for general linux distros, can build `musl-static` target only.
; - zig (WIP): used for general linux distros, can build `musl` and `glibc` targets.
; - musl-native: used for alpine linux, can build `musl-static` and `musl`(WIP) target.
; - gnu-native (assume): used for general linux distros, can build `glibc` target only and have portability issues.
; build target:
; - musl-static (default): pure static linking, using musl-libc, can run on any linux distro.
; - musl: static linking with dynamic linking to musl-libc, can run on musl-based linux distro.
; - glibc: static linking with dynamic linking to glibc, can run on glibc-based linux distro.
; include PATH for musl libc. ; include PATH for musl libc.
SPC_LIBC=musl SPC_LIBC=musl
; compiler environments ; compiler environments
CC=${SPC_LINUX_DEFAULT_CC} CC=${SPC_LINUX_DEFAULT_CC}
CXX=${SPC_LINUX_DEFAULT_CXX} CXX=${SPC_LINUX_DEFAULT_CXX}
AR=${SPC_LINUX_DEFAULT_AR} 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 ; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
SPC_DEFAULT_C_FLAGS="-fpic -Os" SPC_DEFAULT_C_FLAGS="-fPIC -Os"
SPC_DEFAULT_CXX_FLAGS="-fpic -Os" SPC_DEFAULT_CXX_FLAGS="-fPIC -Os"
; extra libs for building php executable, used in `make` command for building php (this value may changed by extension build process, space separated) ; 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= SPC_EXTRA_LIBS=
; upx executable path ; upx executable path
UPX_EXEC=${PKG_ROOT_PATH}/bin/upx UPX_EXEC=${PKG_ROOT_PATH}/bin/upx
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches ; 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 *** ; *** default build command for building php ***
; buildconf command ; buildconf command
@@ -89,7 +107,7 @@ SPC_CMD_VAR_PHP_EMBED_TYPE="static"
; *** default build vars for building php *** ; *** default build vars for building php ***
; CFLAGS for configuring 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 ; CPPFLAGS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS="-I${BUILD_INCLUDE_PATH}" SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS="-I${BUILD_INCLUDE_PATH}"
; LDFLAGS for configuring php ; LDFLAGS for configuring php
@@ -97,7 +115,7 @@ SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}"
; LIBS for configuring php ; LIBS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lpthread -lm" SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lpthread -lm"
; EXTRA_CFLAGS for `make` php ; 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 ; EXTRA_LIBS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm" SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm"
; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so ; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so
@@ -106,6 +124,9 @@ SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS=""
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-all-static -Wl,-O1 -pie" SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-all-static -Wl,-O1 -pie"
[macos] [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 ; compiler environments
CC=clang CC=clang
CXX=clang++ CXX=clang++
@@ -115,7 +136,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) ; 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= SPC_EXTRA_LIBS=
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches ; 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 *** ; *** default build command for building php ***
; buildconf command ; buildconf command

View File

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

View File

@@ -207,6 +207,18 @@
"libicudata.a" "libicudata.a"
] ]
}, },
"icu-static-win": {
"source": "icu-static-win",
"static-libs-windows": [
"icudt.lib",
"icuin.lib",
"icuio.lib",
"icuuc.lib"
],
"headers-windows": [
"unicode"
]
},
"imagemagick": { "imagemagick": {
"source": "imagemagick", "source": "imagemagick",
"static-libs-unix": [ "static-libs-unix": [
@@ -228,7 +240,8 @@
"zstd", "zstd",
"xz", "xz",
"libzip", "libzip",
"libxml2" "libxml2",
"libjxl"
] ]
}, },
"imap": { "imap": {
@@ -240,6 +253,18 @@
"openssl" "openssl"
] ]
}, },
"jbig": {
"source": "jbig",
"static-libs-unix": [
"libjbig.a",
"libjbig85.a"
],
"headers": [
"jbig.h",
"jbig85.h",
"jbig_ar.h"
]
},
"ldap": { "ldap": {
"source": "ldap", "source": "ldap",
"static-libs-unix": [ "static-libs-unix": [
@@ -253,6 +278,13 @@
"libsodium" "libsodium"
] ]
}, },
"lerc": {
"source": "lerc",
"static-libs-unix": [
"libLerc.a"
],
"cpp-library": true
},
"libacl": { "libacl": {
"source": "libacl", "source": "libacl",
"static-libs-unix": [ "static-libs-unix": [
@@ -388,6 +420,20 @@
"zlib" "zlib"
] ]
}, },
"libjxl": {
"source": "libjxl",
"static-libs-unix": [
"libjxl.a",
"libjxl_dec.a"
],
"lib-depends": [
"brotli",
"libgif",
"libjpeg",
"libpng",
"libwebp"
]
},
"liblz4": { "liblz4": {
"source": "liblz4", "source": "liblz4",
"static-libs-unix": [ "static-libs-unix": [
@@ -466,6 +512,7 @@
"static-libs-windows": [ "static-libs-windows": [
"libssh2.lib" "libssh2.lib"
], ],
"provide-pre-built": true,
"headers": [ "headers": [
"libssh2.h", "libssh2.h",
"libssh2_publickey.h", "libssh2_publickey.h",
@@ -473,9 +520,6 @@
], ],
"lib-depends": [ "lib-depends": [
"openssl" "openssl"
],
"lib-suggests": [
"zlib"
] ]
}, },
"libtiff": { "libtiff": {
@@ -486,6 +530,13 @@
"lib-depends": [ "lib-depends": [
"zlib", "zlib",
"libjpeg" "libjpeg"
],
"lib-suggests-unix": [
"lerc",
"libwebp",
"jbig",
"xz",
"zstd"
] ]
}, },
"libuuid": { "libuuid": {
@@ -801,6 +852,15 @@
"libiconv" "libiconv"
] ]
}, },
"watcher": {
"source": "watcher",
"static-libs-unix": [
"libwatcher-c.a"
],
"headers": [
"wtr/watcher-c.h"
]
},
"xz": { "xz": {
"source": "xz", "source": "xz",
"static-libs-unix": [ "static-libs-unix": [

View File

@@ -42,5 +42,17 @@
"extract-files": { "extract-files": {
"upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe" "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"
} }
} }

View File

@@ -4,7 +4,8 @@
"license": { "license": {
"type": "file", "type": "file",
"path": "LICENSE" "path": "LICENSE"
} },
"alt": false
}, },
"amqp": { "amqp": {
"type": "url", "type": "url",
@@ -342,6 +343,14 @@
"path": "LICENSE" "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": { "igbinary": {
"type": "url", "type": "url",
"url": "https://pecl.php.net/get/igbinary", "url": "https://pecl.php.net/get/igbinary",
@@ -379,6 +388,15 @@
"path": "LICENSE" "path": "LICENSE"
} }
}, },
"jbig": {
"type": "url",
"url": "https://www.cl.cam.ac.uk/~mgk25/jbigkit/download/jbigkit-2.1.tar.gz",
"provide-pre-built": true,
"license": {
"type": "file",
"path": "COPYING"
}
},
"ldap": { "ldap": {
"type": "filelist", "type": "filelist",
"url": "https://www.openldap.org/software/download/OpenLDAP/openldap-release/", "url": "https://www.openldap.org/software/download/OpenLDAP/openldap-release/",
@@ -388,6 +406,16 @@
"path": "LICENSE" "path": "LICENSE"
} }
}, },
"lerc": {
"type": "ghtar",
"repo": "Esri/lerc",
"prefer-stable": true,
"provide-pre-built": true,
"license": {
"type": "file",
"path": "LICENSE"
}
},
"libacl": { "libacl": {
"alt": { "alt": {
"type": "url", "type": "url",
@@ -525,6 +553,14 @@
"path": "LICENSE.md" "path": "LICENSE.md"
} }
}, },
"libjxl": {
"type": "ghtar",
"repo": "libjxl/libjxl",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"liblz4": { "liblz4": {
"type": "ghrel", "type": "ghrel",
"repo": "lz4/lz4", "repo": "lz4/lz4",
@@ -597,7 +633,6 @@
"type": "filelist", "type": "filelist",
"url": "https://download.osgeo.org/libtiff/", "url": "https://download.osgeo.org/libtiff/",
"regex": "/href=\"(?<file>tiff-(?<version>[^\"]+)\\.tar\\.xz)\"/", "regex": "/href=\"(?<file>tiff-(?<version>[^\"]+)\\.tar\\.xz)\"/",
"provide-pre-built": true,
"license": { "license": {
"type": "file", "type": "file",
"path": "LICENSE.md" "path": "LICENSE.md"
@@ -990,6 +1025,15 @@
"path": "COPYING" "path": "COPYING"
} }
}, },
"watcher": {
"type": "ghtar",
"repo": "e-dant/watcher",
"prefer-stable": true,
"license": {
"type": "file",
"path": "license"
}
},
"xdebug": { "xdebug": {
"type": "url", "type": "url",
"url": "https://pecl.php.net/get/xdebug", "url": "https://pecl.php.net/get/xdebug",

View File

@@ -47,6 +47,10 @@ export default {
nav: [], nav: [],
socialLinks: [ socialLinks: [
{icon: 'github', link: 'https://github.com/crazywhalecc/static-php-cli'} {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 DefaultTheme from 'vitepress/theme'
import {inBrowser, useData} from "vitepress"; import {inBrowser, useData} from "vitepress";
import {watchEffect} from "vue"; import {watchEffect} from "vue";
import './style.css';
export default { export default {
...DefaultTheme, ...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) # 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 enable-micro-win32: false
# Build options for shared extensions (list or comma-separated are both accepted)
shared-extensions: [ ]
# Download options # Download options
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") # 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`. 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`. 1. Build shared extension `xxx.so` using: `--build-shared=XXX` option. e.g. `bin/spc build bcmath,zlib --build-shared=xdebug --build-cli`
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. 2. You will get `buildroot/modules/xdebug.so` and `buildroot/bin/php`.
3. Use the `phpize && ./configure && make` command to compile the extensions you want to use. 3. The `xdebug.so` file could be used for php that version and thread-safe are the same.
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
```
## Can it support Oracle database extension? ## Can it support Oracle database extension?

View File

@@ -82,7 +82,7 @@ and this extension cannot be compiled into php by static linking, so it cannot b
## xdebug ## 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. You need to use a build target other than `musl-static` for SPC_TARGET.
2. When using Linux/glibc or macOS, you can compile Xdebug as a shared extension using --build-shared="xdebug". 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`. 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`. - 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). - 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). - 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, 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. 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-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-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-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 - `--build-all`: build all above sapi
```bash ```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++`) - `--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 - `--with-clean`: clean up old make files before compiling PHP
- `--enable-zts`: Make compiled PHP thread-safe version (default is NTS version) - `--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-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-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) - `--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: Here is an example of installing the tool:
- Download and install UPX (Linux and Windows only): `bin/spc install-pkg upx` - 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 ## Command - del-download

View File

@@ -21,3 +21,16 @@ features:
- title: Dependency Management - title: Dependency Management
details: static-php-cli comes with dependency management and supports installation of different types of PHP extensions. 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 平台来说macOS 下的几乎所有二进制文件都无法真正纯静态链接,几乎所有二进制文件都会链接 macOS 的系统库:`/usr/lib/libresolv.9.dylib``/usr/lib/libSystem.B.dylib`
所以在 macOS 系统下,在特定的编译条件下可以使用静态编译的 php 二进制文件,可使用动态链接的扩展: 因此,在 macOS 上,您可以直接构建出使用静态编译的 PHP 二进制文件动态链接的扩展:
1. 使用 `--no-strip` 参数,将不会对二进制文件去除调试符号等信息,以供使用 `Xdebug` 等外部 Zend 扩展。 1. 使用 `--build-shared=XXX` 选项构建共享扩展 `xxx.so`。例如:`bin/spc build bcmath,zlib --build-shared=xdebug --build-cli`
2. 如果要编译某些 Zend 扩展,使用 Homebrew、MacPorts、源码编译的形式在所在的操作系统安装一个普通版本的 PHP 2. 您将获得 `buildroot/modules/xdebug.so``buildroot/bin/php`
3. 使用 `phpize && ./configure && make` 命令编译想要使用的扩展 3. `xdebug.so` 文件可用于版本和线程安全相同的 php
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
```
## 可以支持 Oracle 数据库扩展吗 ## 可以支持 Oracle 数据库扩展吗

View File

@@ -76,7 +76,7 @@ bin/spc build gd --with-libs=freetype,libjpeg,libavif,libwebp --build-cli
## xdebug ## xdebug
1. Xdebug 只能作为共享扩展进行构建。在 Linux 上,您需要使用 static-php-cli 并设置 SPC_LIBC=glibc 1. Xdebug 只能作为共享扩展进行构建。您需要使用除了 `musl-static` 外的其他 `SPC_TARGET` 构建目标
2. 使用 Linux/glibc 或 macOS 时,您可以使用 `--build-shared=xdebug` 将 Xdebug 编译为共享扩展。 2. 使用 Linux/glibc 或 macOS 时,您可以使用 `--build-shared=xdebug` 将 Xdebug 编译为共享扩展。
编译后的 `./php` 二进制文件可以通过指定 INI 文件进行配置和运行,例如 `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php` 编译后的 `./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` - fpm: 构建结果为 `buildroot/bin/php-fpm`
- micro: 构建结果为 `buildroot/bin/micro.sfx`,如需进一步与 PHP 代码打包,请查看 [打包 micro 二进制](./manual-build#命令-micro-combine-打包-micro-二进制)。 - micro: 构建结果为 `buildroot/bin/micro.sfx`,如需进一步与 PHP 代码打包,请查看 [打包 micro 二进制](./manual-build#命令-micro-combine-打包-micro-二进制)。
- embed: 参见 [embed 使用](./manual-build#embed-使用)。 - embed: 参见 [embed 使用](./manual-build#embed-使用)。
- frankenphp: 构建结果为 `buildroot/bin/frankenphp`
如果中途构建出错,你可以使用 `--debug` 参数查看详细的错误信息,或者使用 `--with-clean` 参数清除旧的编译结果,重新编译。 如果中途构建出错,你可以使用 `--debug` 参数查看详细的错误信息,或者使用 `--with-clean` 参数清除旧的编译结果,重新编译。
@@ -250,6 +251,7 @@ bin/spc doctor --auto-fix
- `--build-fpm`: 构建一个 fpm sapiphp-fpm用于和其他传统的 fpm 架构的软件如 nginx 配合使用) - `--build-fpm`: 构建一个 fpm sapiphp-fpm用于和其他传统的 fpm 架构的软件如 nginx 配合使用)
- `--build-micro`: 构建一个 micro sapi用于构建一个包含 PHP 代码的独立可执行二进制) - `--build-micro`: 构建一个 micro sapi用于构建一个包含 PHP 代码的独立可执行二进制)
- `--build-embed`: 构建一个 embed sapi用于嵌入到其他 C 语言程序中) - `--build-embed`: 构建一个 embed sapi用于嵌入到其他 C 语言程序中)
- `--build-frankenphp`: 构建一个 [frankenphp](https://github.com/php/frankenphp) 二进制
- `--build-all`: 构建以上所有 sapi - `--build-all`: 构建以上所有 sapi
```bash ```bash
@@ -294,7 +296,7 @@ bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
- `--cxx=XXX`: 指定 C++ 语言编译器的执行命令Linux 默认 `g++`macOS 默认 `clang++` - `--cxx=XXX`: 指定 C++ 语言编译器的执行命令Linux 默认 `g++`macOS 默认 `clang++`
- `--with-clean`: 编译 PHP 前先清理旧的 make 产生的文件 - `--with-clean`: 编译 PHP 前先清理旧的 make 产生的文件
- `--enable-zts`: 让编译的 PHP 为线程安全版本(默认为 NTS 版本) - `--enable-zts`: 让编译的 PHP 为线程安全版本(默认为 NTS 版本)
- `--no-strip`: 编译 PHP 库后不运行 `strip` 裁剪二进制文件缩小体积(不裁剪的 macOS 二进制文件可使用动态链接的第三方扩展) - `--no-strip`: 编译 PHP 库后不运行 `strip` 裁剪二进制文件缩小体积
- `--with-libs=XXX,YYY`: 编译 PHP 前先编译指定的依赖库,激活部分扩展的可选功能(例如 gd 库的 libavif 等) - `--with-libs=XXX,YYY`: 编译 PHP 前先编译指定的依赖库,激活部分扩展的可选功能(例如 gd 库的 libavif 等)
- `--with-config-file-path=XXX` 查找 `php.ini` 的路径(在 [这里](../faq/index.html#php-ini-的路径是什么) 查看默认路径) - `--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-的路径是什么) 查看默认路径) - `--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` - 下载安装 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 - 删除已下载的资源 ## 命令 del-download - 删除已下载的资源

View File

@@ -2,4 +2,8 @@
<phpunit <phpunit
bootstrap="tests/bootstrap.php" 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\CraftCommand;
use SPC\command\DeleteDownloadCommand; use SPC\command\DeleteDownloadCommand;
use SPC\command\dev\AllExtCommand; use SPC\command\dev\AllExtCommand;
use SPC\command\dev\EnvCommand;
use SPC\command\dev\ExtVerCommand; use SPC\command\dev\ExtVerCommand;
use SPC\command\dev\GenerateExtDepDocsCommand; use SPC\command\dev\GenerateExtDepDocsCommand;
use SPC\command\dev\GenerateExtDocCommand; use SPC\command\dev\GenerateExtDocCommand;
@@ -33,7 +34,7 @@ use Symfony\Component\Console\Application;
*/ */
final class ConsoleApplication extends Application final class ConsoleApplication extends Application
{ {
public const VERSION = '2.6.0'; public const VERSION = '2.6.1';
public function __construct() public function __construct()
{ {
@@ -70,6 +71,7 @@ final class ConsoleApplication extends Application
new GenerateExtDepDocsCommand(), new GenerateExtDepDocsCommand(),
new GenerateLibDepDocsCommand(), new GenerateLibDepDocsCommand(),
new PackLibCommand(), new PackLibCommand(),
new EnvCommand(),
] ]
); );
} }

View File

@@ -12,7 +12,9 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\LockFile;
use SPC\store\SourceManager; use SPC\store\SourceManager;
use SPC\store\SourcePatcher;
use SPC\util\CustomExt; use SPC\util\CustomExt;
abstract class BuilderBase abstract class BuilderBase
@@ -202,6 +204,8 @@ abstract class BuilderBase
$this->emitPatchPoint('before-exts-extract'); $this->emitPatchPoint('before-exts-extract');
SourceManager::initSource(exts: [...$static_extensions, ...$shared_extensions]); SourceManager::initSource(exts: [...$static_extensions, ...$shared_extensions]);
$this->emitPatchPoint('after-exts-extract'); $this->emitPatchPoint('after-exts-extract');
// patch micro
SourcePatcher::patchMicro();
} }
foreach ([...$static_extensions, ...$shared_extensions] as $extension) { foreach ([...$static_extensions, ...$shared_extensions] as $extension) {
@@ -262,17 +266,6 @@ abstract class BuilderBase
if (!$ext->isBuildShared()) { if (!$ext->isBuildShared()) {
continue; 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(); $ext->buildShared();
} }
} catch (RuntimeException $e) { } catch (RuntimeException $e) {
@@ -361,15 +354,11 @@ abstract class BuilderBase
public function getPHPVersionFromArchive(?string $file = null): false|string public function getPHPVersionFromArchive(?string $file = null): false|string
{ {
if ($file === null) { if ($file === null) {
$lock = file_exists(DOWNLOAD_PATH . '/.lock.json') ? file_get_contents(DOWNLOAD_PATH . '/.lock.json') : false; $lock = LockFile::get('php-src');
if ($lock === false) { if ($lock === null) {
return false;
}
$lock = json_decode($lock, true);
$file = $lock['php-src']['filename'] ?? null;
if ($file === null) {
return false; return false;
} }
$file = LockFile::getLockFullPath($lock);
} }
if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) { if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) {
return $match[1]; return $match[1];
@@ -415,6 +404,9 @@ abstract class BuilderBase
if (($type & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED) { if (($type & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED) {
$ls[] = 'embed'; $ls[] = 'embed';
} }
if (($type & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) {
$ls[] = 'frankenphp';
}
return implode(', ', $ls); 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. * Generate micro extension test php code.
*/ */

View File

@@ -10,6 +10,7 @@ use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\SPCConfigUtil; use SPC\util\SPCConfigUtil;
use SPC\util\SPCTarget;
class Extension class Extension
{ {
@@ -193,7 +194,7 @@ class Extension
* If you need to patch some code, overwrite this * If you need to patch some code, overwrite this
* return true if you patched something, false if not * return true if you patched something, false if not
*/ */
public function patchBeforeSharedBuild(): bool public function patchBeforeSharedPhpize(): bool
{ {
return false; return false;
} }
@@ -208,6 +209,16 @@ class Extension
return false; 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
{
return false;
}
/** /**
* @return string * @return string
* returns a command line string with all required shared extensions to load * returns a command line string with all required shared extensions to load
@@ -264,9 +275,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 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 // If check failed, throw RuntimeException
$sharedExtensions = $this->getSharedExtensionLoadString(); $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) { 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')) { if (file_exists(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php')) {
@@ -328,11 +342,21 @@ class Extension
*/ */
public function buildShared(): void public function buildShared(): void
{ {
logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)'); if (Config::getExt($this->getName(), 'type') === 'builtin' || Config::getExt($this->getName(), 'build-with-php') === true) {
if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) { if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) {
logger()->info('extension ' . $this->getName() . ' already built, skipping'); logger()->info('Shared extension [' . $this->getName() . '] was already built by php-src/configure (' . $this->getName() . '.so)');
return; 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) { foreach ($this->dependencies as $dependency) {
if (!$dependency instanceof Extension) { if (!$dependency instanceof Extension) {
continue; continue;
@@ -361,21 +385,30 @@ class Extension
{ {
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true); $config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
[$staticLibString, $sharedLibString] = $this->getStaticAndSharedLibs(); [$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,-Bstatic -Wl,--start-group ' : '';
$postStatic = PHP_OS_FAMILY !== 'Darwin' ? ' -Wl,--end-group -Wl,-Bdynamic ' : ' ';
$env = [ $env = [
'CFLAGS' => $config['cflags'], 'CFLAGS' => $config['cflags'],
'CXXFLAGS' => $config['cflags'], 'CXXFLAGS' => $config['cflags'],
'LDFLAGS' => $config['ldflags'], '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, 'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
]; ];
if ($this->patchBeforeSharedPhpize()) {
logger()->info("Extension [{$this->getName()}] patched before shared phpize");
}
// prepare configure args // prepare configure args
shell()->cd($this->source_dir) shell()->cd($this->source_dir)
->setEnv($env) ->setEnv($env)
->exec(BUILD_BIN_PATH . '/phpize'); ->exec(BUILD_BIN_PATH . '/phpize');
if ($this->patchBeforeSharedConfigure()) { 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) shell()->cd($this->source_dir)
@@ -386,12 +419,17 @@ class Extension
'--enable-shared --disable-static' '--enable-shared --disable-static'
); );
// some extensions don't define their dependencies well, this patch is only needed for a few
FileSystem::replaceFileRegex( FileSystem::replaceFileRegex(
$this->source_dir . '/Makefile', $this->source_dir . '/Makefile',
'/^(.*_SHARED_LIBADD\s*=.*)$/m', '/^(.*_SHARED_LIBADD\s*=.*)$/m',
'$1 ' . $staticLibString '$1 ' . $staticLibString
); );
if ($this->patchBeforeSharedMake()) {
logger()->info("Extension [{$this->getName()}] patched before shared make");
}
shell()->cd($this->source_dir) shell()->cd($this->source_dir)
->setEnv($env) ->setEnv($env)
->exec('make clean') ->exec('make clean')
@@ -472,7 +510,7 @@ class Extension
* *
* @return array [staticLibString, sharedLibString] * @return array [staticLibString, sharedLibString]
*/ */
private function getStaticAndSharedLibs(): array protected function getStaticAndSharedLibs(): array
{ {
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true); $config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
$sharedLibString = ''; $sharedLibString = '';
@@ -487,7 +525,7 @@ class Extension
continue; continue;
} }
$static_lib = 'lib' . $lib . '.a'; $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 . ' ')) { if (!str_contains($staticLibString, '-l' . $lib . ' ')) {
$staticLibString .= '-l' . $lib . ' '; $staticLibString .= '-l' . $lib . ' ';
} }
@@ -495,6 +533,11 @@ class Extension
$sharedLibString .= '-l' . $lib . ' '; $sharedLibString .= '-l' . $lib . ' ';
} }
} }
// move static libstdc++ to shared if we are on non-full-static build target
if (!SPCTarget::isStatic() && in_array(SPCTarget::getLibc(), SPCTarget::LIBC_LIST)) {
$staticLibString .= ' -lstdc++';
$sharedLibString = str_replace('-lstdc++', '', $sharedLibString);
}
return [trim($staticLibString), trim($sharedLibString)]; return [trim($staticLibString), trim($sharedLibString)];
} }

View File

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

View File

@@ -7,6 +7,7 @@ namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\builder\linux\LinuxBuilder; use SPC\builder\linux\LinuxBuilder;
use SPC\builder\macos\MacOSBuilder; use SPC\builder\macos\MacOSBuilder;
use SPC\builder\windows\WindowsBuilder;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\FileSystem; use SPC\store\FileSystem;
@@ -57,6 +58,17 @@ class curl extends Extension
return true; return true;
} }
public function patchBeforeMake(): bool
{
$extra_libs = getenv('SPC_EXTRA_LIBS');
if ($this->builder instanceof WindowsBuilder && !str_contains($extra_libs, 'secur32.lib')) {
$extra_libs .= ' secur32.lib';
putenv('SPC_EXTRA_LIBS=' . trim($extra_libs));
return true;
}
return false;
}
public function patchBeforeSharedConfigure(): bool public function patchBeforeSharedConfigure(): bool
{ {
$file = $this->source_dir . '/config.m4'; $file = $this->source_dir . '/config.m4';

View File

@@ -10,23 +10,9 @@ use SPC\util\CustomExt;
#[CustomExt('imagick')] #[CustomExt('imagick')]
class imagick extends Extension 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 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; return '--with-imagick=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $disable_omp;
} }
} }

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\extension; namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\builder\linux\SystemUtil;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\CustomExt; use SPC\util\CustomExt;
@@ -41,4 +42,14 @@ class imap extends Extension
} }
return $arg; return $arg;
} }
public function patchBeforeMake(): bool
{
if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::isMuslDist()) {
return false;
}
$extra_libs = trim(getenv('SPC_EXTRA_LIBS') . ' -lcrypt');
f_putenv('SPC_EXTRA_LIBS=' . $extra_libs);
return true;
}
} }

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\extension; namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\store\FileSystem;
use SPC\util\CustomExt; use SPC\util\CustomExt;
#[CustomExt('uv')] #[CustomExt('uv')]
@@ -16,4 +17,13 @@ class uv extends Extension
throw new \RuntimeException('The latest uv extension requires PHP 8.0 or later'); throw new \RuntimeException('The latest uv extension requires PHP 8.0 or later');
} }
} }
public function patchBeforeSharedMake(): bool
{
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 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; $enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; $enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; $enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
shell()->cd(SOURCE_PATH . '/php-src') shell()->cd(SOURCE_PATH . '/php-src')
->exec( ->exec(
@@ -143,6 +144,10 @@ class BSDBuilder extends UnixBuilderBase
} }
$this->buildEmbed(); $this->buildEmbed();
} }
if ($enableFrankenphp) {
logger()->info('building frankenphp');
$this->buildFrankenphp();
}
} }
public function testPHP(int $build_target = BUILD_TARGET_NONE) 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

@@ -25,23 +25,8 @@ class LinuxBuilder extends UnixBuilderBase
{ {
$this->options = $options; $this->options = $options;
// check musl-cross make installed if we use musl-cross-make GlobalEnvManager::init();
$arch = arch2gnu(php_uname('m')); GlobalEnvManager::afterInit();
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)');
}
}
// concurrency // concurrency
$this->concurrency = intval(getenv('SPC_CONCURRENCY')); $this->concurrency = intval(getenv('SPC_CONCURRENCY'));
@@ -110,10 +95,11 @@ class LinuxBuilder extends UnixBuilderBase
$config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ? $config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ?
('--with-config-file-scan-dir=' . $this->getOption('with-config-file-scan-dir') . ' ') : ''; ('--with-config-file-scan-dir=' . $this->getOption('with-config-file-scan-dir') . ' ') : '';
$enable_cli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI; $enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
$enable_fpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM; $enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enable_micro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; $enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enable_embed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; $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 ' : ''; $mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
// prepare build php envs // prepare build php envs
@@ -125,7 +111,7 @@ class LinuxBuilder extends UnixBuilderBase
]); ]);
// process micro upx patch if micro sapi enabled // process micro upx patch if micro sapi enabled
if ($enable_micro) { if ($enableMicro) {
if (version_compare($this->getMicroVersion(), '0.2.0') < 0) { if (version_compare($this->getMicroVersion(), '0.2.0') < 0) {
// for phpmicro 0.1.x // for phpmicro 0.1.x
$this->processMicroUPXLegacy(); $this->processMicroUPXLegacy();
@@ -137,10 +123,10 @@ class LinuxBuilder extends UnixBuilderBase
shell()->cd(SOURCE_PATH . '/php-src') shell()->cd(SOURCE_PATH . '/php-src')
->exec( ->exec(
getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' . getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
($enable_cli ? '--enable-cli ' : '--disable-cli ') . ($enableCli ? '--enable-cli ' : '--disable-cli ') .
($enable_fpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') . ($enableFpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') .
($enable_embed ? "--enable-embed={$embed_type} " : '--disable-embed ') . ($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
($enable_micro ? '--enable-micro=all-static ' : '--disable-micro ') . ($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
$config_file_path . $config_file_path .
$config_file_scan_dir . $config_file_scan_dir .
$disable_jit . $disable_jit .
@@ -156,25 +142,29 @@ class LinuxBuilder extends UnixBuilderBase
$this->cleanMake(); $this->cleanMake();
if ($enable_cli) { if ($enableCli) {
logger()->info('building cli'); logger()->info('building cli');
$this->buildCli(); $this->buildCli();
} }
if ($enable_fpm) { if ($enableFpm) {
logger()->info('building fpm'); logger()->info('building fpm');
$this->buildFpm(); $this->buildFpm();
} }
if ($enable_micro) { if ($enableMicro) {
logger()->info('building micro'); logger()->info('building micro');
$this->buildMicro(); $this->buildMicro();
} }
if ($enable_embed) { if ($enableEmbed) {
logger()->info('building embed'); logger()->info('building embed');
if ($enable_micro) { if ($enableMicro) {
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la'); FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la');
} }
$this->buildEmbed(); $this->buildEmbed();
} }
if ($enableFrankenphp) {
logger()->info('building frankenphp');
$this->buildFrankenphp();
}
} }
public function testPHP(int $build_target = BUILD_TARGET_NONE) public function testPHP(int $build_target = BUILD_TARGET_NONE)
@@ -293,6 +283,24 @@ class LinuxBuilder extends UnixBuilderBase
$cwd = getcwd(); $cwd = getcwd();
chdir(BUILD_LIB_PATH); chdir(BUILD_LIB_PATH);
symlink($realLibName, 'libphp.so'); symlink($realLibName, 'libphp.so');
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); chdir($cwd);
} }
$this->patchPhpScripts(); $this->patchPhpScripts();

View File

@@ -188,12 +188,12 @@ class SystemUtil
/** /**
* Get libc version string from ldd * Get libc version string from ldd
*/ */
public static function getLibcVersionIfExists(): ?string public static function getLibcVersionIfExists(string $libc): ?string
{ {
if (self::$libc_version !== null) { if (self::$libc_version !== null) {
return self::$libc_version; return self::$libc_version;
} }
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'glibc') { if ($libc === 'glibc') {
$result = shell()->execWithResult('ldd --version', false); $result = shell()->execWithResult('ldd --version', false);
if ($result[0] !== 0) { if ($result[0] !== 0) {
return null; return null;
@@ -208,7 +208,7 @@ class SystemUtil
} }
return null; return null;
} }
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { if ($libc === 'musl') {
if (self::isMuslDist()) { if (self::isMuslDist()) {
$result = shell()->execWithResult('ldd 2>&1', false); $result = shell()->execWithResult('ldd 2>&1', false);
} else { } else {

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\linux\library; namespace SPC\builder\linux\library;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\SPCTarget;
class icu extends LinuxLibraryBase 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"'; $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"'; $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) shell()->cd($this->source_dir . '/source')->initializeEnv($this)
->exec( ->exec(
"{$cppflags} {$cxxflags} {$ldflags} " . "{$cppflags} {$cxxflags} {$ldflags} " .

View File

@@ -7,6 +7,7 @@ namespace SPC\builder\linux\library;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\SPCTarget;
class imap extends LinuxLibraryBase class imap extends LinuxLibraryBase
{ {
@@ -34,6 +35,15 @@ class imap extends LinuxLibraryBase
return true; return true;
} }
public function patchPhpConfig(): bool
{
if (SPCTarget::getLibc() === 'glibc') {
FileSystem::replaceFileRegex(BUILD_BIN_PATH . '/php-config', '/^libs="(.*)"$/m', 'libs="$1 -lcrypt"');
return true;
}
return false;
}
/** /**
* @throws RuntimeException * @throws RuntimeException
*/ */
@@ -44,6 +54,8 @@ class imap extends LinuxLibraryBase
} else { } else {
$ssl_options = 'SSLTYPE=none'; $ssl_options = 'SSLTYPE=none';
} }
$libcVer = SPCTarget::getLibcVersion();
$extraLibs = $libcVer && version_compare($libcVer, '2.17', '<=') ? 'EXTRALDFLAGS="-ldl -lrt -lpthread"' : '';
shell()->cd($this->source_dir) shell()->cd($this->source_dir)
->exec('make clean') ->exec('make clean')
->exec('touch ip6') ->exec('touch ip6')
@@ -51,9 +63,7 @@ class imap extends LinuxLibraryBase
->exec('chmod +x tools/ua') ->exec('chmod +x tools/ua')
->exec('chmod +x src/osdep/unix/drivers') ->exec('chmod +x src/osdep/unix/drivers')
->exec('chmod +x src/osdep/unix/mkauths') ->exec('chmod +x src/osdep/unix/mkauths')
->exec( ->exec("yes | make slx {$ssl_options} EXTRACFLAGS='-fPIC -fpermissive' {$extraLibs}");
"yes | make slx {$ssl_options} EXTRACFLAGS='-fPIC -fpermissive'"
);
try { try {
shell() shell()
->exec("cp -rf {$this->source_dir}/c-client/c-client.a " . BUILD_LIB_PATH . '/libc-client.a') ->exec("cp -rf {$this->source_dir}/c-client/c-client.a " . BUILD_LIB_PATH . '/libc-client.a')

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

@@ -12,9 +12,12 @@ use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\CurlHook;
use SPC\store\Downloader;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\DependencyUtil; use SPC\util\DependencyUtil;
use SPC\util\SPCConfigUtil; use SPC\util\SPCConfigUtil;
use SPC\util\SPCTarget;
abstract class UnixBuilderBase extends BuilderBase abstract class UnixBuilderBase extends BuilderBase
{ {
@@ -198,7 +201,7 @@ abstract class UnixBuilderBase extends BuilderBase
$util = new SPCConfigUtil($this); $util = new SPCConfigUtil($this);
$config = $util->config($this->ext_list, $this->lib_list, $this->getOption('with-suggested-exts'), $this->getOption('with-suggested-libs')); $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']}"; $lens = "{$config['cflags']} {$config['ldflags']} {$config['libs']}";
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { if (SPCTarget::isStatic()) {
$lens .= ' -static'; $lens .= ' -static';
} }
[$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens); [$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens);
@@ -218,6 +221,21 @@ abstract class UnixBuilderBase extends BuilderBase
throw new RuntimeException('embed failed sanity check: run failed. Error message: ' . implode("\n", $output)); 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) . ']');
}
}
} }
/** /**
@@ -276,5 +294,76 @@ abstract class UnixBuilderBase extends BuilderBase
$php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str); $php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str);
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str); FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
} }
foreach ($this->getLibs() as $lib) {
if ($lib->patchPhpConfig()) {
logger()->debug("Library {$lib->getName()} patched php-config");
}
}
}
/**
* @throws WrongUsageException
* @throws RuntimeException
*/
protected function buildFrankenphp(): void
{
$os = match (PHP_OS_FAMILY) {
'Linux' => 'linux',
'Windows' => 'win',
'Darwin' => 'macos',
'BSD' => 'freebsd',
default => throw new RuntimeException('Unsupported OS: ' . PHP_OS_FAMILY),
};
$arch = arch2gnu(php_uname('m'));
// define executables for go and xcaddy
$xcaddy_exec = PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin/xcaddy";
$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', retries: 3, 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 = '';
if (SPCTarget::isStatic()) {
$extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'";
$muslTags = 'static_build,';
}
$config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list, with_dependencies: true);
$env = [
'PATH' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin:" . getenv('PATH'),
'GOROOT' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}",
'GOBIN' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin",
'GOPATH' => PKG_ROOT_PATH . '/go',
'CGO_ENABLED' => '1',
'CGO_CFLAGS' => $config['cflags'],
'CGO_LDFLAGS' => "{$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,
];
shell()->cd(BUILD_BIN_PATH)
->setEnv($env)
->exec("{$xcaddy_exec} build --output frankenphp {$xcaddyModules}");
} }
} }

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('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('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('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('ldap', ...cmake_boolean_args('CURL_DISABLE_LDAP', true))
->optionalLib('zstd', ...cmake_boolean_args('CURL_ZSTD')) ->optionalLib('zstd', ...cmake_boolean_args('CURL_ZSTD'))
->optionalLib('idn2', ...cmake_boolean_args('USE_LIBIDN2')) ->optionalLib('idn2', ...cmake_boolean_args('USE_LIBIDN2'))

View File

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

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
trait jbig
{
/**
* @throws FileSystemException
*/
public function patchBeforeBuild(): bool
{
FileSystem::replaceFileStr($this->source_dir . '/Makefile', 'CFLAGS = -O2 -W -Wno-unused-result', 'CFLAGS = -O2 -W -Wno-unused-result -fPIC');
return true;
}
/**
* @throws RuntimeException
*/
protected function build(): void
{
shell()->cd($this->source_dir)->initializeEnv($this)
->exec("make -j{$this->builder->concurrency} {$this->builder->getEnvString()} lib")
->exec('cp libjbig/libjbig.a ' . BUILD_LIB_PATH)
->exec('cp libjbig/libjbig85.a ' . BUILD_LIB_PATH)
->exec('cp libjbig/jbig.h ' . BUILD_INCLUDE_PATH)
->exec('cp libjbig/jbig85.h ' . BUILD_INCLUDE_PATH)
->exec('cp libjbig/jbig_ar.h ' . BUILD_INCLUDE_PATH);
}
}

View File

@@ -6,12 +6,13 @@ namespace SPC\builder\unix\library;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\executor\UnixAutoconfExecutor; use SPC\util\executor\UnixAutoconfExecutor;
use SPC\util\SPCTarget;
trait ldap trait ldap
{ {
public function patchBeforeBuild(): bool 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); FileSystem::replaceFileStr($this->source_dir . '/configure', '"-lssl -lcrypto', '"-lssl -lcrypto -lz ' . $extra);
return true; return true;
} }

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\util\executor\UnixCMakeExecutor;
trait lerc
{
/**
* @throws FileSystemException
* @throws RuntimeException
*/
protected function build(): void
{
UnixCMakeExecutor::create($this)
->build();
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\util\executor\UnixCMakeExecutor;
trait libjxl
{
protected function build(): void
{
UnixCMakeExecutor::create($this)
->addConfigureArgs('-DJPEGXL_ENABLE_TOOLS=OFF')
->addConfigureArgs('-DJPEGXL_ENABLE_EXAMPLES=OFF')
->addConfigureArgs('-DJPEGXL_ENABLE_MANPAGES=OFF')
->addConfigureArgs('-DJPEGXL_ENABLE_BENCHMARK=OFF')
->addConfigureArgs('-DJPEGXL_ENABLE_PLUGINS=OFF')
->addConfigureArgs('-DJPEGXL_ENABLE_SJPEG=OFF')
->addConfigureArgs('-DJPEGXL_STATIC=ON')
->addConfigureArgs('-DBUILD_TESTING=OFF')
->build();
}
}

View File

@@ -6,7 +6,9 @@ namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
use SPC\util\executor\UnixAutoconfExecutor; use SPC\util\executor\UnixAutoconfExecutor;
use SPC\util\SPCTarget;
trait libtiff trait libtiff
{ {
@@ -16,6 +18,9 @@ trait libtiff
*/ */
protected function build(): void protected function build(): void
{ {
$libcpp = SPCTarget::getTargetOS() === 'Darwin' ? '-lc++' : '-lstdc++';
FileSystem::replaceFileStr($this->source_dir . '/configure', '-lwebp', '-lwebp -lsharpyuv');
FileSystem::replaceFileStr($this->source_dir . '/configure', '-l"$lerc_lib_name"', '-l"$lerc_lib_name" ' . $libcpp);
UnixAutoconfExecutor::create($this) UnixAutoconfExecutor::create($this)
->configure( ->configure(
// zlib deps // zlib deps
@@ -24,17 +29,21 @@ trait libtiff
"--with-zlib-lib-dir={$this->getLibDir()}", "--with-zlib-lib-dir={$this->getLibDir()}",
// libjpeg deps // libjpeg deps
'--enable-jpeg', '--enable-jpeg',
'--disable-old-jpeg',
'--disable-jpeg12',
"--with-jpeg-include-dir={$this->getIncludeDir()}", "--with-jpeg-include-dir={$this->getIncludeDir()}",
"--with-jpeg-lib-dir={$this->getLibDir()}", "--with-jpeg-lib-dir={$this->getLibDir()}",
// We disabled lzma, zstd, webp, libdeflate by default to reduce the size of the binary '--disable-old-jpeg',
'--disable-lzma', '--disable-jpeg12',
'--disable-zstd',
'--disable-webp',
'--disable-libdeflate', '--disable-libdeflate',
'--disable-tools',
'--disable-contrib',
'--disable-cxx', '--disable-cxx',
'--without-x',
) )
->optionalLib('lerc', '--enable-lerc', '--disable-lerc')
->optionalLib('zstd', '--enable-zstd', '--disable-zstd')
->optionalLib('webp', '--enable-webp', '--disable-webp')
->optionalLib('xz', '--enable-lzma', '--disable-lzma')
->optionalLib('jbig', '--enable-jbig', '--disable-jbig')
->make(); ->make();
$this->patchPkgconfPrefix(['libtiff-4.pc']); $this->patchPkgconfPrefix(['libtiff-4.pc']);
} }

View File

@@ -33,7 +33,13 @@ trait libxslt
'--without-debugger', '--without-debugger',
"--with-libxml-prefix={$this->getBuildRootPath()}", "--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_LINUX_DEFAULT_LD_LIBRARY_PATH') && getenv('SPC_LINUX_DEFAULT_LIBRARY_PATH')) {
$ac->appendEnv([
'LD_LIBRARY_PATH' => getenv('SPC_LINUX_DEFAULT_LD_LIBRARY_PATH'),
'LIBRARY_PATH' => getenv('SPC_LINUX_DEFAULT_LIBRARY_PATH'),
]);
}
$ac->configure()->make();
$this->patchPkgconfPrefix(['libexslt.pc']); $this->patchPkgconfPrefix(['libexslt.pc']);
$this->patchLaDependencyPrefix(); $this->patchLaDependencyPrefix();

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\SPCTarget;
trait postgresql trait postgresql
{ {
@@ -50,7 +51,7 @@ trait postgresql
$error_exec_cnt += $output[0] === 0 ? 0 : 1; $error_exec_cnt += $output[0] === 0 ? 0 : 1;
if (!empty($output[1][0])) { if (!empty($output[1][0])) {
$ldflags = $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}"); $output = shell()->execWithResult("pkg-config --libs-only-l --static {$packages}");
$error_exec_cnt += $output[0] === 0 ? 0 : 1; $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('CC') . ' -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; $this->options = $options;
GlobalEnvManager::init($this); GlobalEnvManager::init();
GlobalEnvManager::afterInit();
// ---------- set necessary options ---------- // ---------- set necessary options ----------
// set sdk (require visual studio 16 or 17) // set sdk (require visual studio 16 or 17)
@@ -60,11 +61,6 @@ class WindowsBuilder extends BuilderBase
*/ */
public function buildPHP(int $build_target = BUILD_TARGET_NONE): void public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
{ {
// ---------- Update extra-libs ----------
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
$extra_libs .= (empty($extra_libs) ? '' : ' ') . implode(' ', $this->getAllStaticLibFiles());
f_putenv('SPC_EXTRA_LIBS=' . $extra_libs);
$enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI; $enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM; $enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; $enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
@@ -161,8 +157,10 @@ class WindowsBuilder extends BuilderBase
{ {
SourcePatcher::patchWindowsCLITarget(); SourcePatcher::patchWindowsCLITarget();
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
// add nmake wrapper // add nmake wrapper
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_cli_wrapper.bat', "nmake /nologo LIBS_CLI=\"{$this->getOption('extra-libs')} ws2_32.lib shell32.lib\" EXTRA_LD_FLAGS_PROGRAM= %*"); FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_cli_wrapper.bat', "nmake /nologo LIBS_CLI=\"ws2_32.lib shell32.lib {$extra_libs}\" EXTRA_LD_FLAGS_PROGRAM= %*");
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_cli_wrapper.bat --task-args php.exe"); cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_cli_wrapper.bat --task-args php.exe");
@@ -196,9 +194,11 @@ class WindowsBuilder extends BuilderBase
} }
FileSystem::writeFile(SOURCE_PATH . '\php-src\Makefile', $makefile); FileSystem::writeFile(SOURCE_PATH . '\php-src\Makefile', $makefile);
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
// add nmake wrapper // add nmake wrapper
$fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' /DPHP_MICRO_FAKE_CLI" ' : ''; $fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' /DPHP_MICRO_FAKE_CLI" ' : '';
$wrapper = "nmake /nologo LIBS_MICRO=\"{$this->getOption('extra-libs')} ws2_32.lib shell32.lib\" CFLAGS_MICRO=\"/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1{$fake_cli}\" %*"; $wrapper = "nmake /nologo LIBS_MICRO=\"ws2_32.lib shell32.lib {$extra_libs}\" CFLAGS_MICRO=\"/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1{$fake_cli}\" %*";
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_micro_wrapper.bat', $wrapper); FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_micro_wrapper.bat', $wrapper);
// phar patch for micro // phar patch for micro

View File

@@ -37,6 +37,7 @@ class curl extends WindowsLibraryBase
'-DBUILD_EXAMPLES=OFF ' . // disable examples '-DBUILD_EXAMPLES=OFF ' . // disable examples
'-DUSE_LIBIDN2=OFF ' . // disable libidn2 '-DUSE_LIBIDN2=OFF ' . // disable libidn2
'-DCURL_USE_LIBPSL=OFF ' . // disable libpsl '-DCURL_USE_LIBPSL=OFF ' . // disable libpsl
'-DUSE_WINDOWS_SSPI=ON ' . // use Schannel instead of OpenSSL
'-DCURL_USE_SCHANNEL=ON ' . // use Schannel instead of OpenSSL '-DCURL_USE_SCHANNEL=ON ' . // use Schannel instead of OpenSSL
'-DCURL_USE_OPENSSL=OFF ' . // disable openssl due to certificate issue '-DCURL_USE_OPENSSL=OFF ' . // disable openssl due to certificate issue
'-DCURL_ENABLE_SSL=ON ' . '-DCURL_ENABLE_SSL=ON ' .

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 // init GlobalEnv
if (!$this instanceof BuildCommand) { if (!$this instanceof BuildCommand) {
GlobalEnvManager::init(); GlobalEnvManager::init();
f_putenv('SPC_SKIP_TOOLCHAIN_CHECK=yes');
} }
if ($this->shouldExecute()) { if ($this->shouldExecute()) {
try { try {

View File

@@ -13,6 +13,7 @@ use SPC\store\SourcePatcher;
use SPC\util\DependencyUtil; use SPC\util\DependencyUtil;
use SPC\util\GlobalEnvManager; use SPC\util\GlobalEnvManager;
use SPC\util\LicenseDumper; use SPC\util\LicenseDumper;
use SPC\util\SPCTarget;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; 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-cli', null, null, 'Build cli SAPI');
$this->addOption('build-fpm', null, null, 'Build fpm SAPI (not available on Windows)'); $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-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('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('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-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'); $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); $rule = $this->parseRules($shared_extensions);
// check dynamic extension build env // 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 // linux must build with glibc
if (!empty($shared_extensions) && PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') !== 'glibc') { if (!empty($shared_extensions) && SPCTarget::isStatic()) {
$this->output->writeln('Linux does not support dynamic extension loading with musl-libc full-static build, please build with glibc!'); $this->output->writeln('Linux does not support dynamic extension loading with musl-libc full-static build, please build with shared target!');
return static::FAILURE; return static::FAILURE;
} }
$static_and_shared = array_intersect($static_extensions, $shared_extensions); $static_and_shared = array_intersect($static_extensions, $shared_extensions);
@@ -78,11 +75,12 @@ class BuildPHPCommand extends BuildCommand
if ($rule === BUILD_TARGET_NONE) { if ($rule === BUILD_TARGET_NONE) {
$this->output->writeln('<error>Please add at least one build SAPI!</error>'); $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-cli\t\tBuild php-cli SAPI</comment>");
$this->output->writeln("<comment>\t--build-micro\tBuild phpmicro SAPI</comment>"); $this->output->writeln("<comment>\t--build-micro\t\tBuild phpmicro SAPI</comment>");
$this->output->writeln("<comment>\t--build-fpm\tBuild php-fpm SAPI</comment>"); $this->output->writeln("<comment>\t--build-fpm\t\tBuild php-fpm SAPI</comment>");
$this->output->writeln("<comment>\t--build-embed\tBuild embed SAPI/libphp</comment>"); $this->output->writeln("<comment>\t--build-embed\t\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-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; return static::FAILURE;
} }
if ($rule === BUILD_TARGET_ALL) { if ($rule === BUILD_TARGET_ALL) {
@@ -136,6 +134,8 @@ class BuildPHPCommand extends BuildCommand
// print info // print info
$indent_texts = [ $indent_texts = [
'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')', 'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')',
'Build Target' => getenv('SPC_TARGET'),
'Build Toolchain' => getenv('SPC_TOOLCHAIN'),
'Build SAPI' => $builder->getBuildTypeName($rule), 'Build SAPI' => $builder->getBuildTypeName($rule),
'Static Extensions (' . count($static_extensions) . ')' => implode(',', $static_extensions), 'Static Extensions (' . count($static_extensions) . ')' => implode(',', $static_extensions),
'Shared Extensions (' . count($shared_extensions) . ')' => implode(',', $shared_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'])) { if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
$indent_texts['UPX Pack'] = 'enabled'; $indent_texts['UPX Pack'] = 'enabled';
} }
try {
$ver = $builder->getPHPVersion(); $ver = $builder->getPHPVersionFromArchive() ?: $builder->getPHPVersion();
$indent_texts['PHP Version'] = $ver; $indent_texts['PHP Version'] = $ver;
} catch (\Throwable) {
if (($ver = $builder->getPHPVersionFromArchive()) !== false) {
$indent_texts['PHP Version'] = $ver;
}
}
if (!empty($not_included)) { if (!empty($not_included)) {
$indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included); $indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included);
@@ -183,6 +178,9 @@ class BuildPHPCommand extends BuildCommand
// validate libs and extensions // validate libs and extensions
$builder->validateLibsAndExts(); $builder->validateLibsAndExts();
// check some things before building all the things
$builder->checkBeforeBuildPHP($rule);
// clean builds and sources // clean builds and sources
if ($this->input->getOption('with-clean')) { if ($this->input->getOption('with-clean')) {
logger()->info('Cleaning source and previous build dir...'); 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 // add static-php-cli.version to main.c, in order to debug php failure more easily
SourcePatcher::patchSPCVersionToPHP($this->getApplication()->getVersion()); SourcePatcher::patchSPCVersionToPHP($this->getApplication()->getVersion());
// clean old modules that may conflict with the new php build
FileSystem::removeDir(BUILD_MODULES_PATH);
// start to build // start to build
$builder->buildPHP($rule); $builder->buildPHP($rule);
SourcePatcher::patchBeforeSharedBuild($builder);
// build dynamic extensions if needed // build dynamic extensions if needed
if (!empty($shared_extensions)) { if (!empty($shared_extensions)) {
logger()->info('Building 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-cli') ? BUILD_TARGET_CLI : BUILD_TARGET_NONE);
$rule |= ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : 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-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); $rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE);
return $rule; return $rule;
} }

View File

@@ -10,7 +10,7 @@ use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
#[AsCommand('craft', 'Build static-php from craft.yml')] #[AsCommand('craft', 'Build static-php from craft.yml')]
class CraftCommand extends BaseCommand class CraftCommand extends BuildCommand
{ {
public function configure(): void public function configure(): void
{ {
@@ -49,7 +49,7 @@ class CraftCommand extends BaseCommand
} }
$static_extensions = implode(',', $craft['extensions']); $static_extensions = implode(',', $craft['extensions']);
$shared_extensions = implode(',', $craft['shared-extensions']); $shared_extensions = implode(',', $craft['shared-extensions'] ?? []);
$libs = implode(',', $craft['libs']); $libs = implode(',', $craft['libs']);
// init log // init log
@@ -66,6 +66,15 @@ class CraftCommand extends BaseCommand
return static::FAILURE; 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 go-xcaddy failed with code: {$retcode}", true);
return static::FAILURE;
}
}
// craft download // craft download
if ($craft['craft-options']['download']) { if ($craft['craft-options']['download']) {
$sharedAppend = $shared_extensions ? ',' . $shared_extensions : ''; $sharedAppend = $shared_extensions ? ',' . $shared_extensions : '';

View File

@@ -9,6 +9,7 @@ use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Downloader; use SPC\store\Downloader;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\LockFile;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; 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->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('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 public function initialize(InputInterface $input, OutputInterface $output): void
@@ -46,29 +49,30 @@ class DeleteDownloadCommand extends BaseCommand
return static::SUCCESS; return static::SUCCESS;
} }
$chosen_sources = $sources; $chosen_sources = $sources;
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$deleted_sources = []; $deleted_sources = [];
foreach ($chosen_sources as $source) { foreach ($chosen_sources as $source) {
$source = trim($source); $source = trim($source);
foreach ([$source, Downloader::getPreBuiltLockName($source)] as $name) { if (LockFile::get($source) && !$this->getOption('pre-built-only')) {
if (isset($lock[$name])) { $deleted_sources[] = $source;
$deleted_sources[] = $name; }
} if (LockFile::get(Downloader::getPreBuiltLockName($source)) && !$this->getOption('source-only')) {
$deleted_sources[] = Downloader::getPreBuiltLockName($source);
} }
} }
foreach ($deleted_sources as $lock_name) { foreach ($deleted_sources as $lock_name) {
$lock = LockFile::get($lock_name);
// remove download file/dir if exists // remove download file/dir if exists
if ($lock[$lock_name]['source_type'] === 'archive') { if ($lock['source_type'] === SPC_SOURCE_ARCHIVE) {
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']))) { if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['filename']))) {
logger()->info('Deleting file ' . $path); logger()->info('Deleting file ' . $path);
unlink($path); unlink($path);
} else { } else {
logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file."); logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file.");
} }
} else { } 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); logger()->info('Deleting dir ' . $path);
FileSystem::removeDir($path); FileSystem::removeDir($path);
} else { } else {
@@ -76,9 +80,8 @@ class DeleteDownloadCommand extends BaseCommand
} }
} }
// remove locked sources // 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!'); logger()->info('Delete success!');
return static::SUCCESS; return static::SUCCESS;
} catch (DownloaderException $e) { } catch (DownloaderException $e) {

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace SPC\command; namespace SPC\command;
use SPC\builder\linux\SystemUtil;
use SPC\builder\traits\UnixSystemUtilTrait; use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\DownloaderException; use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
@@ -12,7 +11,9 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\Downloader; use SPC\store\Downloader;
use SPC\store\LockFile;
use SPC\util\DependencyUtil; use SPC\util\DependencyUtil;
use SPC\util\SPCTarget;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
@@ -222,8 +223,8 @@ class DownloadCommand extends BaseCommand
'{name}' => $source, '{name}' => $source,
'{arch}' => arch2gnu(php_uname('m')), '{arch}' => arch2gnu(php_uname('m')),
'{os}' => strtolower(PHP_OS_FAMILY), '{os}' => strtolower(PHP_OS_FAMILY),
'{libc}' => getenv('SPC_LIBC') ?: 'default', '{libc}' => SPCTarget::getLibc() ?? 'default',
'{libcver}' => PHP_OS_FAMILY === 'Linux' ? (SystemUtil::getLibcVersionIfExists() ?? 'default') : 'default', '{libcver}' => SPCTarget::getLibcVersion() ?? 'default',
]; ];
$find = str_replace(array_keys($replace), array_values($replace), Config::getPreBuilt('match-pattern')); $find = str_replace(array_keys($replace), array_values($replace), Config::getPreBuilt('match-pattern'));
// find filename in asset list // find filename in asset list
@@ -244,11 +245,15 @@ class DownloadCommand extends BaseCommand
} }
// if download failed, we will try to download alternative sources // if download failed, we will try to download alternative sources
logger()->warning("Download failed: {$e->getMessage()}"); logger()->warning("Download failed: {$e->getMessage()}");
logger()->notice("Trying to download alternative sources for {$source}");
$alt_sources = Config::getSource($source)['alt'] ?? null; $alt_sources = Config::getSource($source)['alt'] ?? null;
if ($alt_sources === null) { if ($alt_sources === null) {
logger()->warning("No alternative sources found for {$source}, using default alternative source");
$alt_config = array_merge($config, $this->getDefaultAlternativeSource($source)); $alt_config = array_merge($config, $this->getDefaultAlternativeSource($source));
} elseif ($alt_sources === false) {
logger()->warning("No alternative sources found for {$source}, skipping alternative download");
throw $e;
} else { } else {
logger()->notice("Trying to download alternative sources for {$source}");
$alt_config = array_merge($config, $alt_sources); $alt_config = array_merge($config, $alt_sources);
} }
Downloader::downloadSource($source, $alt_config, $force_all || in_array($source, $force_list)); Downloader::downloadSource($source, $alt_config, $force_all || in_array($source, $force_list));
@@ -301,7 +306,7 @@ class DownloadCommand extends BaseCommand
throw new WrongUsageException('Windows currently does not support --from-zip !'); 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/"'); throw new RuntimeException('.lock.json not exist in "downloads/"');
} }
} catch (RuntimeException $e) { } catch (RuntimeException $e) {

View File

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

View File

@@ -7,6 +7,7 @@ namespace SPC\command;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\Downloader; use SPC\store\Downloader;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\LockFile;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
@@ -40,16 +41,9 @@ class SwitchPhpVersionCommand extends BaseCommand
} }
} }
// detect if downloads/.lock.json exists if (LockFile::isLockFileExists('php-src')) {
$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)) {
$this->output->writeln('<info>Removing old PHP source...</info>'); $this->output->writeln('<info>Removing old PHP source...</info>');
unlink($file); LockFile::put('php-src', null);
} }
// Download new PHP source // 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\BuilderProvider;
use SPC\builder\LibraryBase; use SPC\builder\LibraryBase;
use SPC\builder\linux\SystemUtil;
use SPC\command\BuildCommand; use SPC\command\BuildCommand;
use SPC\exception\ExceptionHandler; use SPC\exception\ExceptionHandler;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
@@ -14,7 +13,9 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\LockFile;
use SPC\util\DependencyUtil; use SPC\util\DependencyUtil;
use SPC\util\SPCTarget;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
@@ -41,15 +42,23 @@ class PackLibCommand extends BuildCommand
$builder->proveLibs($libraries); $builder->proveLibs($libraries);
$builder->validateLibsAndExts(); $builder->validateLibsAndExts();
// before pack, check if the dependency tree contains lib-suggests
foreach ($libraries as $lib) {
if (Config::getLib($lib, 'lib-suggests', []) !== []) {
logger()->critical("The library {$lib} has lib-suggests, packing [{$lib_name}] is not safe, abort !");
return static::FAILURE;
}
}
foreach ($builder->getLibs() as $lib) { foreach ($builder->getLibs() as $lib) {
if ($lib->getName() !== $lib_name) { if ($lib->getName() !== $lib_name) {
// other dependencies: install or build, both ok // other dependencies: install or build, both ok
$lib->setup(); $lib->setup();
} else { } else {
// Get lock info // Get lock info
$lock = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$source = Config::getLib($lib->getName(), 'source'); $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."); logger()->critical("The library {$lib->getName()} is downloaded as pre-built, we need to build it instead of installing pre-built.");
return static::FAILURE; return static::FAILURE;
} }
@@ -76,8 +85,8 @@ class PackLibCommand extends BuildCommand
'{name}' => $lib->getName(), '{name}' => $lib->getName(),
'{arch}' => arch2gnu(php_uname('m')), '{arch}' => arch2gnu(php_uname('m')),
'{os}' => strtolower(PHP_OS_FAMILY), '{os}' => strtolower(PHP_OS_FAMILY),
'{libc}' => getenv('SPC_LIBC') ?: 'default', '{libc}' => SPCTarget::getLibc() ?? 'default',
'{libcver}' => PHP_OS_FAMILY === 'Linux' ? (SystemUtil::getLibcVersionIfExists() ?? 'default') : 'default', '{libcver}' => SPCTarget::getLibcVersion() ?? 'default',
]; ];
// detect suffix, for proper tar option // detect suffix, for proper tar option
$tar_option = $this->getTarOptionFromSuffix(Config::getPreBuilt('match-pattern')); $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) { foreach (FileSystem::getClassesPsr4(__DIR__ . '/item', 'SPC\doctor\item') as $class) {
$ref = new \ReflectionClass($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 ($ref->getMethods() as $method) {
foreach ($method->getAttributes() as $a) { foreach ($method->getAttributes() as $a) {
if (is_a($a->getName(), AsCheckItem::class, true)) { 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; namespace SPC\doctor\item;
use SPC\builder\linux\SystemUtil;
use SPC\doctor\AsCheckItem; use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem; use SPC\doctor\AsFixItem;
use SPC\doctor\CheckResult; use SPC\doctor\CheckResult;
use SPC\doctor\OptionalCheck;
use SPC\exception\DownloaderException; use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
@@ -16,20 +16,20 @@ use SPC\store\Downloader;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\PackageManager; use SPC\store\PackageManager;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
use SPC\toolchain\MuslToolchain;
#[OptionalCheck([self::class, 'optionalCheck'])]
class LinuxMuslCheck class LinuxMuslCheck
{ {
public static function optionalCheck(): bool
{
return getenv('SPC_TOOLCHAIN') === MuslToolchain::class;
}
/** @noinspection PhpUnused */ /** @noinspection PhpUnused */
#[AsCheckItem('if musl-wrapper is installed', limit_os: 'Linux', level: 800)] #[AsCheckItem('if musl-wrapper is installed', limit_os: 'Linux', level: 800)]
public function checkMusl(): CheckResult 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')); $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')) { if (file_exists($musl_wrapper_lib) && file_exists('/usr/local/musl/lib/libc.a')) {
return CheckResult::ok(); return CheckResult::ok();
@@ -40,13 +40,6 @@ class LinuxMuslCheck
#[AsCheckItem('if musl-cross-make is installed', limit_os: 'Linux', level: 799)] #[AsCheckItem('if musl-cross-make is installed', limit_os: 'Linux', level: 799)]
public function checkMuslCrossMake(): CheckResult 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')); $arch = arch2gnu(php_uname('m'));
$cross_compile_lib = "/usr/local/musl/{$arch}-linux-musl/lib/libc.a"; $cross_compile_lib = "/usr/local/musl/{$arch}-linux-musl/lib/libc.a";
$cross_compile_gcc = "/usr/local/musl/bin/{$arch}-linux-musl-gcc"; $cross_compile_gcc = "/usr/local/musl/bin/{$arch}-linux-musl-gcc";
@@ -78,7 +71,7 @@ class LinuxMuslCheck
]; ];
logger()->info('Downloading ' . $musl_source['url']); logger()->info('Downloading ' . $musl_source['url']);
Downloader::downloadSource($musl_version_name, $musl_source); 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 // Apply CVE-2025-26519 patch
SourcePatcher::patchFile('musl-1.2.5_CVE-2025-26519_0001.patch', SOURCE_PATH . "/{$musl_version_name}"); 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', 'bzip2', 'cmake', 'gcc',
'g++', 'patch', 'binutils-gold', 'g++', 'patch', 'binutils-gold',
'libtoolize', 'which', 'libtoolize', 'which',
'patchelf',
]; ];
public const TOOLS_DEBIAN = [ public const TOOLS_DEBIAN = [
@@ -30,6 +31,7 @@ class LinuxToolCheckList
'tar', 'unzip', 'gzip', 'tar', 'unzip', 'gzip',
'bzip2', 'cmake', 'patch', 'bzip2', 'cmake', 'patch',
'xz', 'libtoolize', 'which', 'xz', 'libtoolize', 'which',
'patchelf',
]; ];
public const TOOLS_RHEL = [ public const TOOLS_RHEL = [
@@ -38,6 +40,7 @@ class LinuxToolCheckList
'tar', 'unzip', 'gzip', 'gcc', 'tar', 'unzip', 'gzip', 'gcc',
'bzip2', 'cmake', 'patch', 'which', 'bzip2', 'cmake', 'patch', 'which',
'xz', 'libtool', 'gettext-devel', 'xz', 'libtool', 'gettext-devel',
'perl', 'patchelf',
]; ];
public const TOOLS_ARCH = [ public const TOOLS_ARCH = [
@@ -48,6 +51,7 @@ class LinuxToolCheckList
'binutils-gold' => 'ld.gold', 'binutils-gold' => 'ld.gold',
'base-devel' => 'automake', 'base-devel' => 'automake',
'gettext-devel' => 'gettextize', 'gettext-devel' => 'gettextize',
'gettext-dev' => 'gettextize',
]; ];
/** @noinspection PhpUnused */ /** @noinspection PhpUnused */

View File

@@ -4,12 +4,13 @@ declare(strict_types=1);
namespace SPC\store; namespace SPC\store;
use SPC\builder\linux\SystemUtil;
use SPC\exception\DownloaderException; use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\pkg\CustomPackage;
use SPC\store\source\CustomSourceBase; use SPC\store\source\CustomSourceBase;
use SPC\util\SPCTarget;
/** /**
* Source Downloader. * Source Downloader.
@@ -208,31 +209,7 @@ class Downloader
if ($download_as === SPC_DOWNLOAD_PRE_BUILT) { if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
$name = self::getPreBuiltLockName($name); $name = self::getPreBuiltLockName($name);
} }
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]); LockFile::lockSource($name, ['source_type' => SPC_SOURCE_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));
} }
/** /**
@@ -278,7 +255,7 @@ class Downloader
} }
// Lock // Lock
logger()->debug("Locking git source {$name}"); 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,11 +348,24 @@ class Downloader
SPC_DOWNLOAD_PRE_BUILT SPC_DOWNLOAD_PRE_BUILT
); );
break; 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 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) { foreach ($classes as $class) {
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) { if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) {
(new $class())->fetch($force); $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 $download_as
); );
break; 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 case 'custom': // Custom download method, like API-based download or other
if (isset($source['func']) && is_callable($source['func'])) { if (isset($source['func']) && is_callable($source['func'])) {
$source['name'] = $name; $source['name'] = $name;
@@ -591,7 +591,12 @@ class Downloader
public static function getPreBuiltLockName(string $source): string 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 FileSystemException
* @throws WrongUsageException
*/ */
private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool
{ {
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) { // If the lock file exists, skip downloading for source mode
$lock = []; $lock_item = LockFile::get($name);
} else { if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && $lock_item !== null) {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
} logger()->notice("Source [{$name}] already downloaded: {$path}");
// 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']));
return true; return true;
} }
} }
// If lock file exists for current arch and glibc target, skip downloading $lock_name = self::getPreBuiltLockName($name);
$lock_item = LockFile::get($lock_name);
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && isset($lock[$lock_name = self::getPreBuiltLockName($name)])) { if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && $lock_item !== null) {
// lock name with env // lock name with env
if ( if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
$lock[$lock_name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']) || logger()->notice("Pre-built content [{$name}] already downloaded: {$path}");
$lock[$lock_name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname']) return true;
) { }
logger()->notice("Pre-built content [{$name}] already downloaded: " . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname'])); }
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; return true;
} }
} }

View File

@@ -142,7 +142,7 @@ class FileSystem
* @throws RuntimeException * @throws RuntimeException
* @throws FileSystemException * @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) { if ($extract_path !== null) {
// replace // replace
@@ -151,14 +151,15 @@ class FileSystem
} else { } else {
$extract_path = PKG_ROOT_PATH . '/' . $name; $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); $target = self::convertPath($extract_path);
if (!is_dir($dir = dirname($target))) { if (!is_dir($dir = dirname($target))) {
self::createDir($dir); self::createDir($dir);
} }
try { try {
self::extractArchive($filename, $target); // extract wrapper command
self::extractWithType($source_type, $filename, $extract_path);
} catch (RuntimeException $e) { } catch (RuntimeException $e) {
if (PHP_OS_FAMILY === 'Windows') { if (PHP_OS_FAMILY === 'Windows') {
f_passthru('rmdir /s /q ' . $target); f_passthru('rmdir /s /q ' . $target);
@@ -177,24 +178,23 @@ class FileSystem
* @throws FileSystemException * @throws FileSystemException
* @throws RuntimeException * @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 source hook is empty, load it
if (self::$_extract_hook === []) { if (self::$_extract_hook === []) {
SourcePatcher::init(); SourcePatcher::init();
} }
if ($move_path !== null) { $move_path = match ($move_path) {
$move_path = SOURCE_PATH . '/' . $move_path; null => SOURCE_PATH . '/' . $name,
} else { default => self::isRelativePath($move_path) ? (SOURCE_PATH . '/' . $move_path) : $move_path,
$move_path = SOURCE_PATH . "/{$name}"; };
}
$target = self::convertPath($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))) { if (!is_dir($dir = dirname($target))) {
self::createDir($dir); self::createDir($dir);
} }
try { try {
self::extractArchive($filename, $target); self::extractWithType($source_type, $filename, $move_path);
self::emitSourceExtractHook($name, $target); self::emitSourceExtractHook($name, $target);
} catch (RuntimeException $e) { } catch (RuntimeException $e) {
if (PHP_OS_FAMILY === 'Windows') { if (PHP_OS_FAMILY === 'Windows') {
@@ -484,11 +484,6 @@ class FileSystem
*/ */
private static function extractArchive(string $filename, string $target): void 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 // Create base dir
if (f_mkdir(directory: $target, recursive: true) !== true) { if (f_mkdir(directory: $target, recursive: true) !== true) {
throw new FileSystemException('create ' . $target . ' dir failed'); 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\FileSystemException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\pkg\CustomPackage;
class PackageManager class PackageManager
{ {
@@ -32,11 +33,27 @@ class PackageManager
// Download package // Download package
Downloader::downloadPackage($pkg_name, $config, $force); 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 // After download, read lock file name
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true); $lock = LockFile::get($pkg_name);
$filename = DOWNLOAD_PATH . '/' . ($lock[$pkg_name]['filename'] ?? $lock[$pkg_name]['dirname']); $source_type = $lock['source_type'];
$extract = $lock[$pkg_name]['move_path'] === null ? (PKG_ROOT_PATH . '/' . $pkg_name) : $lock[$pkg_name]['move_path']; $filename = LockFile::getLockFullPath($lock);
FileSystem::extractPackage($pkg_name, $filename, $extract); $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 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'])) { 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 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 = []; $sources_extracted = [];
// source check exist // source check exist
if (is_array($sources)) { if (is_array($sources)) {
@@ -56,8 +51,8 @@ class SourceManager
} }
// check source downloaded // check source downloaded
$pre_built_name = Downloader::getPreBuiltLockName($source); $pre_built_name = Downloader::getPreBuiltLockName($source);
if ($source_only || !isset($lock[$pre_built_name])) { if ($source_only || LockFile::get($pre_built_name) === null) {
if (!isset($lock[$source])) { if (LockFile::get($source) === null) {
throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !"); throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !");
} }
$lock_name = $source; $lock_name = $source;
@@ -65,14 +60,45 @@ class SourceManager
$lock_name = $pre_built_name; $lock_name = $pre_built_name;
} }
$lock_content = LockFile::get($lock_name);
// check source dir exist // 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)) { if (!is_dir($check)) {
logger()->debug('Extracting source [' . $source . '] to ' . $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']); $filename = LockFile::getLockFullPath($lock_content);
} else { FileSystem::extractSource($source, $lock_content['source_type'], $filename, $check);
logger()->debug('Source [' . $source . '] already extracted in ' . $check . ', skip !'); 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;
}
// ext imap was included in php < 8.4 which we should not extract,
// but since it's not simple to compare php version, for now we just skip it
if ($source === 'ext-imap') {
logger()->debug("Source [ext-imap] 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,13 +11,13 @@ use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\util\SPCTarget;
class SourcePatcher class SourcePatcher
{ {
public static function init(): void public static function init(): void
{ {
// FileSystem::addSourceExtractHook('swow', [SourcePatcher::class, 'patchSwow']); // FileSystem::addSourceExtractHook('swow', [SourcePatcher::class, 'patchSwow']);
FileSystem::addSourceExtractHook('micro', [SourcePatcher::class, 'patchMicro']);
FileSystem::addSourceExtractHook('openssl', [SourcePatcher::class, 'patchOpenssl11Darwin']); FileSystem::addSourceExtractHook('openssl', [SourcePatcher::class, 'patchOpenssl11Darwin']);
FileSystem::addSourceExtractHook('swoole', [SourcePatcher::class, 'patchSwoole']); FileSystem::addSourceExtractHook('swoole', [SourcePatcher::class, 'patchSwoole']);
FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchPhpLibxml212']); FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchPhpLibxml212']);
@@ -46,12 +46,12 @@ class SourcePatcher
{ {
foreach ($builder->getExts() as $ext) { foreach ($builder->getExts() as $ext) {
if ($ext->patchBeforeBuildconf() === true) { 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) { foreach ($builder->getLibs() as $lib) {
if ($lib->patchBeforeBuildconf() === true) { 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 // patch windows php 8.1 bug
@@ -79,15 +79,6 @@ class SourcePatcher
} }
} }
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');
}
}
}
/** /**
* Source patcher runner before configure * Source patcher runner before configure
* *
@@ -98,26 +89,23 @@ class SourcePatcher
{ {
foreach ($builder->getExts() as $ext) { foreach ($builder->getExts() as $ext) {
if ($ext->patchBeforeConfigure() === true) { 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) { foreach ($builder->getLibs() as $lib) {
if ($lib->patchBeforeConfigure() === true) { if ($lib->patchBeforeConfigure() === true) {
logger()->info('Library [' . $lib->getName() . '] patched before configure'); logger()->info("Library [{$lib->getName()}] patched before configure");
} }
} }
// patch capstone // patch capstone
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/configure', '/have_capstone="yes"/', 'have_capstone="no"'); 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 RuntimeException
* @throws FileSystemException * @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')) { if (!file_exists(SOURCE_PATH . '/php-src/sapi/micro/php_micro.c')) {
return false; return false;
@@ -164,11 +152,7 @@ class SourcePatcher
foreach ($patches as $patch) { foreach ($patches as $patch) {
logger()->info("Patching micro with {$patch}"); logger()->info("Patching micro with {$patch}");
$patchesStr = str_replace('/', DIRECTORY_SEPARATOR, $patch); self::patchFile(SOURCE_PATH . "/php-src/{$patch}", SOURCE_PATH . '/php-src');
f_passthru(
'cd ' . SOURCE_PATH . '/php-src && ' .
(PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . $patchesStr . ' | patch -p1 '
);
} }
return true; return true;
@@ -177,24 +161,32 @@ class SourcePatcher
/** /**
* Use existing patch file for patching * 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 string $cwd Working directory for patch command
* @param bool $reverse Reverse patches (default: False) * @param bool $reverse Reverse patches (default: False)
* @throws RuntimeException * @throws RuntimeException
*/ */
public static function patchFile(string $patch_name, string $cwd, bool $reverse = false): bool 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; return false;
} }
$patch_file = ROOT_DIR . "/src/globals/patch/{$patch_name}"; $patch_str = FileSystem::convertPath($patch_file);
$patch_str = str_replace('/', DIRECTORY_SEPARATOR, $patch_file); if (!file_exists($patch_str)) {
throw new RuntimeException("Patch file [{$patch_str}] does not exist");
}
// Copy patch from phar // Copy patch from phar
if (\Phar::running() !== '') { if (str_starts_with($patch_str, 'phar://')) {
file_put_contents(SOURCE_PATH . '/' . $patch_name, file_get_contents($patch_file)); $filename = pathinfo($patch_file, PATHINFO_BASENAME);
$patch_str = str_replace('/', DIRECTORY_SEPARATOR, SOURCE_PATH . '/' . $patch_name); file_put_contents(SOURCE_PATH . "/{$filename}", file_get_contents($patch_file));
$patch_str = FileSystem::convertPath(SOURCE_PATH . "/{$filename}");
} }
// detect // detect
@@ -279,14 +271,28 @@ class SourcePatcher
// call extension patch before make // call extension patch before make
foreach ($builder->getExts(false) as $ext) { foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeMake() === true) { 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) { foreach ($builder->getLibs() as $lib) {
if ($lib->patchBeforeMake() === true) { 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',
);
}
} }
/** /**
@@ -454,7 +460,7 @@ class SourcePatcher
public static function patchFfiCentos7FixO3strncmp(): bool 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; return false;
} }
if (!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) { if (!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) {

View File

@@ -0,0 +1,17 @@
<?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;
public function extract(string $name): void
{
throw new \RuntimeException("Extract method not implemented for package: {$name}");
}
}

View File

@@ -0,0 +1,74 @@
<?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 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 (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("{$go_exec} install github.com/caddyserver/xcaddy/cmd/xcaddy@latest");
}
}

View File

@@ -0,0 +1,38 @@
<?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
{
foreach (['CC', 'CXX', 'AR', 'LD'] as $env) {
$command = getenv($env);
if (!$command || is_file($command)) {
continue;
}
match (PHP_OS_FAMILY) {
'Linux' => LinuxSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."),
'Darwin' => MacOSSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."),
'BSD' => FreeBSDSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."),
default => throw new \RuntimeException(__CLASS__ . ' is not supported on ' . PHP_OS_FAMILY . '.'),
};
}
}
}

View File

@@ -0,0 +1,38 @@
<?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
{
foreach (['CC', 'CXX', 'AR', 'LD'] as $env) {
$command = getenv($env);
if (!$command || is_file($command)) {
continue;
}
match (PHP_OS_FAMILY) {
'Linux' => LinuxSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."),
'Darwin' => MacOSSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."),
'BSD' => FreeBSDSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."),
default => throw new \RuntimeException(__CLASS__ . ' 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_LINUX_DEFAULT_LD_LIBRARY_PATH=/usr/local/musl/lib:/usr/local/musl/{$arch}-linux-musl/lib");
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_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_LINUX_DEFAULT_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,52 @@
<?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' => MuslToolchain::class, // use musl toolchain by default, after zig pr merged, change this to ZigToolchain::class
'Windows' => MSVCToolchain::class,
'Darwin' => ClangNativeToolchain::class,
'BSD' => ClangNativeToolchain::class,
];
/**
* @throws WrongUsageException
*/
public static function initToolchain(): void
{
$libc = getenv('SPC_LIBC');
if ($libc !== false) {
// uncomment this when zig pr is merged
// logger()->warning('SPC_LIBC is deprecated, please use SPC_TARGET instead.');
$toolchain = match ($libc) {
'musl' => SystemUtil::isMuslDist() ? GccNativeToolchain::class : MuslToolchain::class,
'glibc' => !SystemUtil::isMuslDist() ? GccNativeToolchain::class : throw new WrongUsageException('SPC_TARGET must be musl-static or musl for musl dist.'),
default => throw new WrongUsageException('Unsupported SPC_LIBC value: ' . $libc),
};
} else {
$toolchain = self::OS_DEFAULT_TOOLCHAIN[PHP_OS_FAMILY];
}
$toolchainClass = $toolchain;
/* @var ToolchainInterface $toolchainClass */
(new $toolchainClass())->initEnv();
GlobalEnvManager::putenv("SPC_TOOLCHAIN={$toolchain}");
}
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,12 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
class ZigToolchain implements ToolchainInterface
{
public function initEnv(): void {}
public function afterInit(): void {}
}

View File

@@ -164,6 +164,12 @@ class ConfigValidator
if (is_string($craft['extensions'])) { if (is_string($craft['extensions'])) {
$craft['extensions'] = array_filter(array_map(fn ($x) => trim($x), explode(',', $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 // check libs
if (isset($craft['libs']) && is_string($craft['libs'])) { if (isset($craft['libs']) && is_string($craft['libs'])) {
$craft['libs'] = array_filter(array_map(fn ($x) => trim($x), explode(',', $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; namespace SPC\util;
use SPC\builder\BuilderBase;
use SPC\builder\linux\SystemUtil;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\toolchain\ToolchainManager;
/** /**
* Environment variable manager * Environment variable manager
@@ -24,11 +23,10 @@ class GlobalEnvManager
/** /**
* Initialize the environment variables * Initialize the environment variables
* *
* @param null|BuilderBase $builder Builder
* @throws RuntimeException * @throws RuntimeException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
public static function init(?BuilderBase $builder = null): void public static function init(): void
{ {
// Check pre-defined env vars exists // Check pre-defined env vars exists
if (getenv('BUILD_ROOT_PATH') === false) { if (getenv('BUILD_ROOT_PATH') === false) {
@@ -37,25 +35,89 @@ class GlobalEnvManager
// Define env vars for unix // Define env vars for unix
if (is_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=' . BUILD_BIN_PATH . '/pkg-config');
self::putenv('PKG_CONFIG_PATH=' . BUILD_ROOT_PATH . '/lib/pkgconfig'); self::putenv('PKG_CONFIG_PATH=' . BUILD_ROOT_PATH . '/lib/pkgconfig');
} }
// Define env vars for linux $ini = self::readIniFile();
if (PHP_OS_FAMILY === 'Linux') {
$arch = getenv('GNU_ARCH'); $default_put_list = [];
if (SystemUtil::isMuslDist()) { foreach ($ini['global'] as $k => $v) {
self::putenv('SPC_LINUX_DEFAULT_CC=gcc'); if (getenv($k) === false) {
self::putenv('SPC_LINUX_DEFAULT_CXX=g++'); $default_put_list[$k] = $v;
self::putenv('SPC_LINUX_DEFAULT_AR=ar'); self::putenv("{$k}={$v}");
} else { }
self::putenv("SPC_LINUX_DEFAULT_CC={$arch}-linux-musl-gcc"); }
self::putenv("SPC_LINUX_DEFAULT_CXX={$arch}-linux-musl-g++"); $os_ini = match (PHP_OS_FAMILY) {
self::putenv("SPC_LINUX_DEFAULT_AR={$arch}-linux-musl-ar"); '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: // Init env.ini file, read order:
// WORKING_DIR/config/env.ini // WORKING_DIR/config/env.ini
// ROOT_DIR/config/env.ini // ROOT_DIR/config/env.ini
@@ -97,28 +159,6 @@ class GlobalEnvManager
break; break;
} }
} }
self::applyConfig($ini['global']); return $ini;
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);
}
}
} }
} }

View File

@@ -55,7 +55,7 @@ class SPCConfigUtil
ob_get_clean(); ob_get_clean();
$ldflags = $this->getLdflagsString(); $ldflags = $this->getLdflagsString();
$libs = $this->getLibsString($libraries, $with_dependencies); $libs = $this->getLibsString($libraries, $with_dependencies);
if (PHP_OS_FAMILY === 'Darwin') { if (SPCTarget::getTargetOS() === 'Darwin') {
$libs .= " {$this->getFrameworksString($extensions)}"; $libs .= " {$this->getFrameworksString($extensions)}";
} }
$cflags = $this->getIncludesString(); $cflags = $this->getIncludesString();
@@ -145,9 +145,8 @@ class SPCConfigUtil
} }
} }
} }
// patch: imagick (imagemagick wrapper) for linux needs libgomp if (in_array('imap', $libraries) && SPCTarget::getLibc() === 'glibc') {
if (in_array('imagemagick', $libraries) && PHP_OS_FAMILY === 'Linux' && !(getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10'))) { $short_name[] = '-lcrypt';
$short_name[] = '-lgomp';
} }
return implode(' ', $short_name); return implode(' ', $short_name);
} }

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