Compare commits

...

109 Commits
2.5.0 ... 2.5.2

Author SHA1 Message Date
Jerry Ma
a7adec1341 Add extension ev support (#703)
* Add extension ev support

* Sort config

* Correct ev build arg for windows

* Use static-php mirror of nasm

* Fix windows ev patch

* Oops
2025-04-19 20:39:48 +08:00
crazywhalecc
a236ee3ac3 Prepare for 2.5.2 2025-04-19 16:22:26 +08:00
DubbleClick
d7b9e5a7d4 better matching pattern just in case we forget x) 2025-04-19 16:17:12 +08:00
DubbleClick
3d1738b14b add mirror sources to savannah downloads 2025-04-19 16:14:09 +08:00
Jerry Ma
f0e634a4fa Merge pull request #673 from crazywhalecc/feat/xdebug-dynamic
add xdebug dynamic extension
2025-04-19 16:01:07 +08:00
crazywhalecc
0f5f60e477 Fix gnu static extension build test 2025-04-19 15:18:48 +08:00
crazywhalecc
9fe09f57f6 Add Chinese docs and additional option docs 2025-04-19 15:04:29 +08:00
crazywhalecc
cf24b88bc8 Add final full test 2025-04-19 14:43:51 +08:00
DubbleClick
d34fa0ba4e check for SPC_LIBC 2025-04-18 16:03:49 +07:00
DubbleClick
f40170ee6f update extension tests to support shared extensions 2025-04-18 15:43:54 +07:00
crazywhalecc
2da750d5f9 Fix phpunit, add skip extraction arg for SPCConfigUtil new builder 2025-04-18 14:50:58 +08:00
crazywhalecc
720e700701 Merge branch 'main' into feat/xdebug-dynamic 2025-04-18 14:43:34 +08:00
crazywhalecc
b452f7f32a Add default shared target 2025-04-18 14:38:42 +08:00
crazywhalecc
7bfb8d6f53 Use SPCConfigUtil to generate shared extension env 2025-04-18 14:38:22 +08:00
crazywhalecc
b06db1f920 Add more PHPUnit tests 2025-04-18 14:03:54 +08:00
crazywhalecc
61eafa48ff Add static extension filter 2025-04-18 12:18:20 +08:00
crazywhalecc
8b07b15f6c Merge branch 'main' into feat/xdebug-dynamic 2025-04-18 09:45:19 +08:00
Marc
5e67133495 Merge pull request #693 from crazywhalecc/fix/libheif-with-heic
explicitly state libheif -> --with-heic
2025-04-14 16:25:46 +07:00
Marc
30b740b7f0 Merge pull request #696 from crazywhalecc/remove-extension_dir
don't set EXTENSION_DIR by default
2025-04-14 14:35:09 +07:00
DubbleClick
7501ae4b4d don't set EXTENSION_DIR by default 2025-04-14 11:01:39 +07:00
DubbleClick
4391c30299 explicitly state libheif -> --with-heic 2025-04-12 09:49:00 +07:00
Marc
536641eadd Merge pull request #692
mimalloc v2.2.3 is bugged on musl
2025-04-12 09:32:02 +07:00
DubbleClick
21594cd4c0 mimalloc v2.2.3 is bugged on musl 2025-04-12 09:30:37 +07:00
Jerry Ma
d4b263bc9f Merge pull request #689 from crazywhalecc/fix/extract-source-only
Add extract source only mode for SourceManager
2025-03-31 21:56:51 +08:00
crazywhalecc
4e4eaed123 Add extract source only mode for SourceManager 2025-03-31 16:37:24 +08:00
Jerry Ma
610843398e Merge pull request #681 from crazywhalecc/fix/remove-libgomp
Remove openmp support for imagemagick
2025-03-31 16:17:05 +08:00
Jerry Ma
615e680b9b Update extension-notes.md 2025-03-31 16:15:12 +08:00
DubbleClick
4e67c63808 add note to sharedExtensionCheck 2025-03-31 00:10:27 +07:00
DubbleClick
f556f375ee add zlib... 2025-03-31 00:04:55 +07:00
DubbleClick
f21f833aed add phar?! 2025-03-30 23:58:33 +07:00
DubbleClick
0c6dd7a577 warning for building an extension as both static and shared 2025-03-30 23:50:49 +07:00
Marc
fc4872c5d6 Merge branch 'main' into feat/xdebug-dynamic 2025-03-30 23:39:30 +07:00
DubbleClick
3fe50e9ca3 let tests succeed 2025-03-30 23:37:22 +07:00
DubbleClick
a5e4d6a5ec xdebug and ffi compilable shared, updated notes 2025-03-30 23:36:23 +07:00
Marc
0524129b64 add notes to imagick 2025-03-30 23:34:52 +07:00
Marc
7ce13751a0 Merge pull request #677 from crazywhalecc/dynamic-ext-refactor
Dynamic extension build support for macOS and glibc Linux
2025-03-30 23:23:43 +07:00
Marc
e149ee0d70 Merge branch 'main' into fix/remove-libgomp 2025-03-30 23:05:17 +07:00
Jerry Ma
2f3c71e55a Downloader enhancement (#685)
* Quiet console output for non --debug mode

* Adjust console output and PHPDoc

* Allow locking different arch pre-built content

* Add install-pkg and pre-built test

* Fix typo

* Add debug console output for Downloader

* Add libc version for pre-built content name

* Separate musl-dist and non-musl-dist

* Add additional log output for pre-built finder

* Return default version for musl and musl-wrapper

* Test arm runner

* Re-enable musl version detect

* Add upx cmd for tests

* Remove comment

* Add SPC_DOCKER_DEBUG for gnu docker, remove classmap for alpine docker

* Add glibc build for CI

* Fix PHP warning in test-extensions.php

* Remove redundant suffix, add libc version suffix

* Fix redundant pre-built name calling

* Fix redundant pre-built name calling

* Fix CI wrong runner name

* Fix end of line space

* Full spell for SPC_DOWNLOAD
2025-03-31 00:01:11 +08:00
crazywhalecc
5c04638cb4 Full spell for SPC_DOWNLOAD 2025-03-30 23:27:43 +08:00
crazywhalecc
6dd6d807b6 Fix end of line space 2025-03-30 23:27:23 +08:00
crazywhalecc
237d39f09c Fix CI wrong runner name 2025-03-30 23:27:05 +08:00
DubbleClick
7a2f77193f add imagick extension note 2025-03-30 22:22:22 +07:00
DubbleClick
d21980170e bring back openmp for musl, add TODO to add it back in on glibc 2025-03-30 22:07:56 +07:00
crazywhalecc
7dec34bdfe Fix redundant pre-built name calling 2025-03-30 22:05:50 +08:00
crazywhalecc
62d619b6cd Fix redundant pre-built name calling 2025-03-30 22:05:20 +08:00
crazywhalecc
67afffeb96 Remove redundant suffix, add libc version suffix 2025-03-30 21:59:58 +08:00
crazywhalecc
c58ea0c3bd Fix PHP warning in test-extensions.php 2025-03-30 21:24:46 +08:00
crazywhalecc
acb8cea437 Add glibc build for CI 2025-03-30 21:14:27 +08:00
crazywhalecc
11f0957963 Add SPC_DOCKER_DEBUG for gnu docker, remove classmap for alpine docker 2025-03-30 21:03:40 +08:00
crazywhalecc
2d7c052fd9 Remove comment 2025-03-30 21:03:19 +08:00
crazywhalecc
23bd216cc7 Add upx cmd for tests 2025-03-30 21:03:11 +08:00
crazywhalecc
50cfc5899b Re-enable musl version detect 2025-03-30 20:54:32 +08:00
crazywhalecc
01d3cb4b11 Test arm runner 2025-03-30 20:53:41 +08:00
crazywhalecc
a940200164 Return default version for musl and musl-wrapper 2025-03-30 20:34:57 +08:00
crazywhalecc
4e5c0f0a48 Add additional log output for pre-built finder 2025-03-30 20:21:56 +08:00
crazywhalecc
8e5657eff0 Separate musl-dist and non-musl-dist 2025-03-30 20:20:04 +08:00
crazywhalecc
631a1b5864 Add libc version for pre-built content name 2025-03-30 20:16:41 +08:00
crazywhalecc
67d2ad5511 Add debug console output for Downloader 2025-03-30 19:21:29 +08:00
crazywhalecc
ab4d7fae7d Fix typo 2025-03-30 19:16:01 +08:00
crazywhalecc
0e4a3f5e2b Add install-pkg and pre-built test 2025-03-30 19:09:32 +08:00
crazywhalecc
87c0535624 Allow locking different arch pre-built content 2025-03-30 16:53:14 +08:00
crazywhalecc
5648681ecc Adjust console output and PHPDoc 2025-03-30 15:17:09 +08:00
crazywhalecc
2c0bb1f7ba Quiet console output for non --debug mode 2025-03-30 14:31:29 +08:00
crazywhalecc
16d82212dd Add full tests for imagick extension 2025-03-30 14:03:06 +08:00
crazywhalecc
6ea1d06460 Add bzip2 support for imagick 2025-03-30 14:02:44 +08:00
crazywhalecc
936413a6d9 Define HAVE_OMP_PAUSE_RESOURCE_ALL to 0, add additional file system func 2025-03-30 14:01:31 +08:00
crazywhalecc
88ce2eafab Fix path bug 2025-03-29 23:29:45 +08:00
crazywhalecc
3915c8410b Destroy imagick config for disabling openmp 2025-03-29 23:15:55 +08:00
crazywhalecc
4115e42dc6 Remove openmp support for imagemagick 2025-03-29 22:51:12 +08:00
DubbleClick
48f257f85a unixconfigurearg needs to know if currently building shared or static 2025-03-27 11:12:19 +07:00
DubbleClick
acdec64144 build static and shared at the same time 2025-03-27 09:36:46 +07:00
crazywhalecc
0beb97648a Fix phpstan 2025-03-26 12:41:13 +08:00
crazywhalecc
5564559192 Change --with-shared to --build-shared 2025-03-26 12:39:55 +08:00
crazywhalecc
8cb93bc1fe Add more exception and log 2025-03-26 12:39:15 +08:00
crazywhalecc
ae23b721b3 Fix shared extension does not build bug 2025-03-26 12:38:59 +08:00
crazywhalecc
df06a4bb2c Fix shared extension does not build bug 2025-03-26 12:38:53 +08:00
crazywhalecc
f37110605e Remove dev build target 2025-03-25 16:13:41 +08:00
crazywhalecc
8459754692 Change to --enable-shared --disable-static 2025-03-25 13:29:55 +08:00
crazywhalecc
fc08e5cf23 Remove brotli support for shared build 2025-03-25 00:17:15 +08:00
crazywhalecc
6dec44bdc3 sort-config 2025-03-25 00:17:01 +08:00
crazywhalecc
8cd69b2b70 Support zero extension build result 2025-03-25 00:05:46 +08:00
crazywhalecc
625a03e799 phpstan fix 2025-03-24 23:51:24 +08:00
crazywhalecc
aa4d4db11f Refactor, supports shared extension build now ! 2025-03-24 23:50:12 +08:00
crazywhalecc
76c353e790 Add SPC_DOCKER_DEBUG=yes option for docker build 2025-03-24 22:39:45 +08:00
crazywhalecc
8909b62dc4 Some prerequisites for refactor 2025-03-24 19:25:38 +08:00
crazywhalecc
371a588396 Merge branch 'main' into feat/xdebug-dynamic 2025-03-24 19:11:09 +08:00
tricker
ee54b6d347 Add pgsql extension for Windows <#664> (#665)
* Add pgsql extension for Windows <#664>

* Add pgsql to windows test

* Added pdo_pgsql for windows, added missing header files

* Adjust some configure args and deps

---------

Co-authored-by: crazywhalecc <jesse2061@outlook.com>
2025-03-24 12:47:00 +08:00
tricker
3ba215c35c enable PDO_ODBC and ODBC extension statically (#661)
* enable PDO_ODBC and ODBC extension statically

* fix sorting of ext.json

* add odbc and pdo_odbc extension to tests

* Add full tests, remove pdo_odbc from bulk

* Remove windows support for docs

* Add ODBC and PDO_ODBC extension

* Revert curl static lib

* Add full tests

* Add iconv for macOS

* Add tests

* Fix linux pdo_odbc patch

* Sort config

---------

Co-authored-by: crazywhalecc <jesse2061@outlook.com>
2025-03-23 23:26:36 +08:00
Jerry Ma
161a3924d2 Fix windows micro logo changer bug (illegal realpath) (#675) 2025-03-23 22:55:25 +08:00
Jerry Ma
7b6fae6d92 Fix windows ssl bug for curl (#674) 2025-03-23 22:33:26 +08:00
Marc
71b52e58b2 Merge branch 'main' into feat/xdebug-dynamic 2025-03-23 10:13:56 +01:00
Marc
d0a66ab16b Merge pull request #663 from crazywhalecc/feat/mimalloc
Add mimalloc support for macOS and Linux
2025-03-23 10:12:08 +01:00
DubbleClick
9d75265e25 fix crash on windoof 2025-03-23 16:11:03 +07:00
DubbleClick
744e066d5f Merge remote-tracking branch 'origin/main' into feat/mimalloc 2025-03-23 15:38:31 +07:00
DubbleClick
1791b443bc add xdebug dynamic extension 2025-03-23 15:35:25 +07:00
Marc
e850df505c Merge pull request #671 from crazywhalecc/fix/phpize
fix phpize using wrong paths
2025-03-23 07:14:23 +01:00
DubbleClick
918223e7da fix phpize using wrong paths 2025-03-23 12:40:50 +07:00
Marc Henderkes
1552d992df fix typo in gettext 2025-03-21 07:41:19 +01:00
Marc
f0a895691b Merge pull request #670 from crazywhalecc/fix/gettext-typo
fix typo in gettext
2025-03-21 07:38:48 +01:00
Marc Henderkes
1f7c805da4 fix typo in gettext 2025-03-21 07:38:14 +01:00
crazywhalecc
1ad33556e9 Add full tests 2025-03-20 16:11:45 +08:00
crazywhalecc
6b5e83b98e Sync Chinese docs 2025-03-20 16:06:33 +08:00
Marc Henderkes
6fb9c2df3b test macos 2025-03-20 08:46:54 +01:00
Marc Henderkes
1b29803ed2 docs 2025-03-20 08:00:35 +01:00
Marc Henderkes
3477857584 mimalloc on mac (untested) 2025-03-20 07:41:13 +01:00
Marc Henderkes
0ce2c894e9 juggle mimalloc.o to the beginning 2025-03-20 07:27:38 +01:00
Marc Henderkes
92470a35da replace allocator with mimalloc (works for cli/fpm, embed needs to be tested) 2025-03-20 06:22:11 +01:00
Marc Henderkes
6447fec028 mimalloc WIP 2025-03-20 04:36:46 +01:00
Jerry Ma
0bc143cac3 Fix windows curl build >= 8.3 (#660)
* Test windows curl

* Test windows curl

* Fix windows curl build for PHP 8.3 and 8.4
2025-03-20 09:22:10 +08:00
93 changed files with 1361 additions and 283 deletions

View File

@@ -6,10 +6,13 @@ on:
os: os:
required: true required: true
description: Build target OS description: Build target OS
default: 'linux-x86_64'
type: choice type: choice
options: options:
- 'linux-x86_64' - 'linux-x86_64'
- 'linux-aarch64' - 'linux-aarch64'
- 'linux-x86_64-glibc'
- 'linux-aarch64-glibc'
- 'macos-x86_64' - 'macos-x86_64'
- 'macos-aarch64' - 'macos-aarch64'
php-version: php-version:
@@ -22,7 +25,6 @@ on:
- '8.3' - '8.3'
- '8.2' - '8.2'
- '8.1' - '8.1'
- '8.0'
extensions: extensions:
description: Extensions to build (comma separated) description: Extensions to build (comma separated)
required: true required: true
@@ -77,9 +79,19 @@ jobs:
RUNS_ON="ubuntu-latest" RUNS_ON="ubuntu-latest"
;; ;;
linux-aarch64) linux-aarch64)
DOWN_CMD="SPC_USE_ARCH=aarch64 ./bin/spc-alpine-docker download" DOWN_CMD="./bin/spc-alpine-docker download"
BUILD_CMD="SPC_USE_ARCH=aarch64 ./bin/spc-alpine-docker build" BUILD_CMD="./bin/spc-alpine-docker build"
RUNS_ON="ubuntu-latest" RUNS_ON="ubuntu-24.04-arm"
;;
linux-x86_64-glibc)
DOWN_CMD="./bin/spc-gnu-docker download"
BUILD_CMD="./bin/spc-gnu-docker build"
RUNS_ON="ubuntu-22.04"
;;
linux-aarch64-glibc)
DOWN_CMD="./bin/spc-gnu-docker download"
BUILD_CMD="./bin/spc-gnu-docker build"
RUNS_ON="ubuntu-22.04-arm"
;; ;;
macos-x86_64) macos-x86_64)
DOWN_CMD="composer update --no-dev --classmap-authoritative && ./bin/spc doctor --auto-fix && ./bin/spc download" DOWN_CMD="composer update --no-dev --classmap-authoritative && ./bin/spc doctor --auto-fix && ./bin/spc download"

View File

@@ -176,18 +176,18 @@ jobs:
run: composer update -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist run: composer update -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: "Run Build Tests (doctor)" - name: "Run Build Tests (doctor)"
run: bin/spc doctor --auto-fix --debug run: php src/globals/test-extensions.php doctor_cmd ${{ matrix.os }} ${{ matrix.php }}
- name: "Prepare UPX for Windows" - name: "Prepare UPX for Windows"
if: matrix.os == 'windows-latest' if: ${{ startsWith(matrix.os, 'windows-') }}
run: | run: |
bin/spc install-pkg upx php src/globals/test-extensions.php install_upx_cmd ${{ matrix.os }} ${{ matrix.php }}
echo "UPX_CMD=$(php src/globals/test-extensions.php upx)" >> $env:GITHUB_ENV echo "UPX_CMD=$(php src/globals/test-extensions.php upx)" >> $env:GITHUB_ENV
- name: "Prepare UPX for Linux" - name: "Prepare UPX for Linux"
if: matrix.os == 'ubunut-latest' if: ${{ startsWith(matrix.os, 'ubuntu-') }}
run: | run: |
bin/spc install-pkg upx php src/globals/test-extensions.php install_upx_cmd ${{ matrix.os }} ${{ matrix.php }}
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)"
@@ -197,5 +197,5 @@ jobs:
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 }}
- name: "Run Build Tests (build - embed for non-windows)" - name: "Run Build Tests (build - embed for non-windows)"
if: matrix.os != 'windows-latest' if: ${{ !startsWith(matrix.os, 'windows-') }}
run: php src/globals/test-extensions.php build_embed_cmd ${{ matrix.os }} ${{ matrix.php }} run: php src/globals/test-extensions.php build_embed_cmd ${{ matrix.os }} ${{ matrix.php }}

View File

@@ -95,7 +95,7 @@ WORKDIR /app
ADD ./src /app/src ADD ./src /app/src
COPY ./composer.* /app/ COPY ./composer.* /app/
ADD ./bin /app/bin ADD ./bin /app/bin
RUN composer install --no-dev --classmap-authoritative RUN composer install --no-dev
EOF EOF
fi fi
@@ -122,6 +122,20 @@ MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/pkgroot:/app/pkgroot"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
# shellcheck disable=SC2090 # shellcheck disable=SC2090
if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then
echo "* Debug mode enabled, run docker in interactive mode."
echo "* You can use 'exit' to exit the docker container."
echo "* You can use 'bin/spc' like normal builds."
echo "*"
echo "* Mounted directories:"
echo "* ./config: $(pwd)/config"
echo "* ./src: $(pwd)/src"
echo "* ./buildroot: $(pwd)/buildroot"
echo "* ./source: $(pwd)/source"
echo "* ./dist: $(pwd)/dist"
echo "* ./downloads: $(pwd)/downloads"
echo "* ./pkgroot: $(pwd)/pkgroot"
echo "*"
$DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-v2 $DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-v2
else else
$DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-v2 bin/spc $@ $DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-v2 bin/spc $@

View File

@@ -12,7 +12,7 @@ DOCKER_EXECUTABLE="docker"
# shellcheck disable=SC2046 # shellcheck disable=SC2046
if [ $(id -u) -ne 0 ]; then if [ $(id -u) -ne 0 ]; then
if ! docker info > /dev/null 2>&1; then if ! docker info > /dev/null 2>&1; then
if [ "$SPC_USE_SUDO" != "yes" ]; then if [ "$SPC_USE_SUDO" != "yes" ] && [ "$SPC_DOCKER_DEBUG" != "yes" ]; then
echo "Docker command requires sudo" echo "Docker command requires sudo"
# shellcheck disable=SC2039 # shellcheck disable=SC2039
echo -n 'To use sudo to run docker, run "export SPC_USE_SUDO=yes" and run command again' echo -n 'To use sudo to run docker, run "export SPC_USE_SUDO=yes" and run command again'
@@ -86,7 +86,7 @@ COPY ./composer.* /app/
ADD ./bin/setup-runtime /app/bin/setup-runtime 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 --classmap-authoritative RUN /app/bin/php /app/bin/composer install --no-dev
ENV PATH="/app/bin:/cmake/bin:$PATH" ENV PATH="/app/bin:/cmake/bin:$PATH"
ENV SPC_LIBC=glibc ENV SPC_LIBC=glibc
@@ -145,4 +145,22 @@ echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm -lresolv -lutil -lrt"'
# shellcheck disable=SC2086 # shellcheck disable=SC2086
# shellcheck disable=SC2090 # shellcheck disable=SC2090
$DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH bin/spc $@ if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then
echo "* Debug mode enabled, run docker in interactive mode."
echo "* You can use 'exit' to exit the docker container."
echo "* You can use 'bin/spc' like normal builds."
echo "*"
echo "* Mounted directories:"
echo "* ./config: $(pwd)/config"
echo "* ./src: $(pwd)/src"
echo "* ./buildroot: $(pwd)/buildroot"
echo "* ./source: $(pwd)/source"
echo "* ./dist: $(pwd)/dist"
echo "* ./downloads: $(pwd)/downloads"
echo "* ./pkgroot: $(pwd)/pkgroot"
echo "*"
$DOCKER_EXECUTABLE run --rm -it --privileged $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH
else
$DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT="$(pwd)" --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH bin/spc $@
fi

View File

@@ -28,7 +28,6 @@
; PATH: static-php-cli will add `$BUILD_BIN_PATH` to PATH. ; PATH: static-php-cli will add `$BUILD_BIN_PATH` to PATH.
; PKG_CONFIG: static-php-cli will set `$BUILD_BIN_PATH/pkg-config` to PKG_CONFIG. ; PKG_CONFIG: static-php-cli will set `$BUILD_BIN_PATH/pkg-config` to PKG_CONFIG.
; PKG_CONFIG_PATH: static-php-cli will set `$BUILD_LIB_PATH/pkgconfig` to PKG_CONFIG_PATH. ; PKG_CONFIG_PATH: static-php-cli will set `$BUILD_LIB_PATH/pkgconfig` to PKG_CONFIG_PATH.
; SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS: the default optimization CFLAGS for compiling php. (if --no-strip option is set: `-g -O0`, else: `-g -Os`)
; ;
; * These vars are only be defined in LinuxBuilder and cannot be changed anywhere: ; * These vars are only be defined in LinuxBuilder and cannot be changed anywhere:
; SPC_LINUX_DEFAULT_CC: the default compiler for linux. (For alpine linux: `gcc`, default: `$GNU_ARCH-linux-musl-gcc`) ; SPC_LINUX_DEFAULT_CC: the default compiler for linux. (For alpine linux: `gcc`, default: `$GNU_ARCH-linux-musl-gcc`)
@@ -50,7 +49,7 @@ SPC_SKIP_DOCTOR_CHECK_ITEMS=""
; RHEL: /usr/lib64/php/modules ; RHEL: /usr/lib64/php/modules
; Alpine: /usr/lib/php{PHP_VERSION}/modules ; Alpine: /usr/lib/php{PHP_VERSION}/modules
; where {PHP_VERSION} is 84 for php 8.4 ; where {PHP_VERSION} is 84 for php 8.4
EXTENSION_DIR= ; EXTENSION_DIR=
[windows] [windows]
; php-sdk-binary-tools path ; php-sdk-binary-tools path
@@ -98,9 +97,9 @@ 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="${SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS} -fno-ident -fPIE -fPIC" SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie -Os -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -fno-ident -fPIE -fPIC"
; EXTRA_LIBS for `make` php ; EXTRA_LIBS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="" SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm"
; EXTRA_LDFLAGS_PROGRAM for `make` php ; EXTRA_LDFLAGS_PROGRAM for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-all-static -Wl,-O1 -pie" SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-all-static -Wl,-O1 -pie"
@@ -132,7 +131,7 @@ SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS="-I${BUILD_INCLUDE_PATH}"
; LDFLAGS for configuring php ; LDFLAGS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}" SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}"
; EXTRA_CFLAGS for `make` php ; EXTRA_CFLAGS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS}" SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie -Os -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
; EXTRA_LIBS for `make` php ; EXTRA_LIBS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-lresolv" SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-lresolv"
; embed type for php, static (libphp.a) or shared (libphp.dylib) ; embed type for php, static (libphp.a) or shared (libphp.dylib)

View File

@@ -92,6 +92,11 @@
}, },
"type": "wip" "type": "wip"
}, },
"ev": {
"type": "external",
"source": "ev",
"arg-type-windows": "with"
},
"event": { "event": {
"support": { "support": {
"Windows": "wip", "Windows": "wip",
@@ -119,6 +124,10 @@
"Linux": "partial", "Linux": "partial",
"BSD": "wip" "BSD": "wip"
}, },
"target": [
"static",
"shared"
],
"notes": true, "notes": true,
"arg-type": "custom", "arg-type": "custom",
"type": "builtin", "type": "builtin",
@@ -253,6 +262,7 @@
"Windows": "wip", "Windows": "wip",
"BSD": "wip" "BSD": "wip"
}, },
"notes": true,
"type": "external", "type": "external",
"source": "ext-imagick", "source": "ext-imagick",
"arg-type": "custom", "arg-type": "custom",
@@ -426,6 +436,17 @@
}, },
"notes": true "notes": true
}, },
"odbc": {
"support": {
"BSD": "wip",
"Windows": "wip"
},
"type": "builtin",
"arg-type-unix": "custom",
"lib-depends-unix": [
"unixodbc"
]
},
"opcache": { "opcache": {
"type": "builtin", "type": "builtin",
"arg-type-unix": "custom" "arg-type-unix": "custom"
@@ -492,19 +513,36 @@
"mysqlnd" "mysqlnd"
] ]
}, },
"pdo_odbc": {
"support": {
"BSD": "wip"
},
"type": "builtin",
"arg-type": "custom",
"lib-depends-unix": [
"unixodbc"
],
"ext-depends": [
"pdo",
"odbc"
]
},
"pdo_pgsql": { "pdo_pgsql": {
"support": { "support": {
"Windows": "wip",
"BSD": "wip" "BSD": "wip"
}, },
"type": "builtin", "type": "builtin",
"arg-type": "with-prefix", "arg-type": "with-prefix",
"arg-type-windows": "custom",
"ext-depends": [ "ext-depends": [
"pdo", "pdo",
"pgsql" "pgsql"
], ],
"lib-depends": [ "lib-depends-unix": [
"postgresql" "postgresql"
],
"lib-depends-windows": [
"postgresql-win"
] ]
}, },
"pdo_sqlite": { "pdo_sqlite": {
@@ -535,14 +573,16 @@
}, },
"pgsql": { "pgsql": {
"support": { "support": {
"Windows": "wip",
"BSD": "wip" "BSD": "wip"
}, },
"notes": true, "notes": true,
"type": "builtin", "type": "builtin",
"arg-type": "custom", "arg-type": "custom",
"lib-depends": [ "lib-depends-unix": [
"postgresql" "postgresql"
],
"lib-depends-windows": [
"postgresql-win"
] ]
}, },
"phar": { "phar": {
@@ -741,6 +781,9 @@
"Windows": "no", "Windows": "no",
"BSD": "wip" "BSD": "wip"
}, },
"target": [
"static"
],
"notes": true, "notes": true,
"type": "external", "type": "external",
"source": "swoole", "source": "swoole",
@@ -888,12 +931,16 @@
] ]
}, },
"xdebug": { "xdebug": {
"type": "builtin", "type": "external",
"source": "xdebug",
"target": [
"shared"
],
"support": { "support": {
"Windows": "wip", "Windows": "wip",
"BSD": "no", "BSD": "no",
"Darwin": "no", "Darwin": "partial",
"Linux": "no" "Linux": "partial"
}, },
"notes": true "notes": true
}, },
@@ -1009,6 +1056,9 @@
"support": { "support": {
"BSD": "wip" "BSD": "wip"
}, },
"target": [
"static"
],
"type": "builtin", "type": "builtin",
"arg-type": "with-prefix", "arg-type": "with-prefix",
"arg-type-windows": "enable", "arg-type-windows": "enable",

View File

@@ -68,7 +68,7 @@
"libcurl.a" "libcurl.a"
], ],
"static-libs-windows": [ "static-libs-windows": [
"libcurl.lib" "libcurl_a.lib"
], ],
"headers": [ "headers": [
"curl" "curl"
@@ -78,7 +78,6 @@
"zlib" "zlib"
], ],
"lib-depends-windows": [ "lib-depends-windows": [
"openssl",
"zlib", "zlib",
"libssh2", "libssh2",
"nghttp2" "nghttp2"
@@ -211,12 +210,12 @@
"libwebp", "libwebp",
"freetype", "freetype",
"libtiff", "libtiff",
"libheif" "libheif",
"bzip2"
], ],
"lib-suggests": [ "lib-suggests": [
"zstd", "zstd",
"xz", "xz",
"bzip2",
"libzip", "libzip",
"libxml2" "libxml2"
] ]
@@ -282,8 +281,7 @@
"headers-unix": [ "headers-unix": [
"ares.h", "ares.h",
"ares_dns.h", "ares_dns.h",
"ares_nameser.h", "ares_nameser.h"
"ares_rules.h"
] ]
}, },
"libde265": { "libde265": {
@@ -589,6 +587,12 @@
"openssl" "openssl"
] ]
}, },
"mimalloc": {
"source": "mimalloc",
"static-libs-unix": [
"mimalloc.o"
]
},
"ncurses": { "ncurses": {
"source": "ncurses", "source": "ncurses",
"static-libs-unix": [ "static-libs-unix": [
@@ -666,6 +670,14 @@
"zstd" "zstd"
] ]
}, },
"postgresql-win": {
"source": "postgresql-win",
"static-libs": [
"libpq.lib",
"libpgport.lib",
"libpgcommon.lib"
]
},
"pthreads4w": { "pthreads4w": {
"source": "pthreads4w", "source": "pthreads4w",
"static-libs-windows": [ "static-libs-windows": [

View File

@@ -9,7 +9,7 @@
}, },
"nasm-x86_64-win": { "nasm-x86_64-win": {
"type": "url", "type": "url",
"url": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip", "url": "https://dl.static-php.dev/static-php-cli/deps/nasm/nasm-2.16.01-win64.zip",
"extract-files": { "extract-files": {
"nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe", "nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe",
"nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe" "nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"

View File

@@ -1,6 +1,6 @@
{ {
"repo": "static-php/static-php-cli-hosted", "repo": "static-php/static-php-cli-hosted",
"prefer-stable": true, "prefer-stable": true,
"match-pattern": "{name}-{arch}-{os}.txz", "match-pattern-linux": "{name}-{arch}-{os}-{libc}-{libcver}.txz",
"suffix": "txz" "match-pattern": "{name}-{arch}-{os}.txz"
} }

View File

@@ -37,9 +37,12 @@
} }
}, },
"attr": { "attr": {
"type": "git", "alt": {
"rev": "v2.5.2", "type": "url",
"url": "https://git.savannah.nongnu.org/git/attr.git", "url": "https://mirror.souseiseki.middlendian.com/nongnu/attr/attr-2.5.2.tar.gz"
},
"type": "url",
"url": "https://download.savannah.nongnu.org/releases/attr/attr-2.5.2.tar.gz",
"provide-pre-built": true, "provide-pre-built": true,
"license": { "license": {
"type": "file", "type": "file",
@@ -89,6 +92,16 @@
"path": "LICENSE" "path": "LICENSE"
} }
}, },
"ev": {
"type": "url",
"url": "https://pecl.php.net/get/ev",
"path": "php-src/ext/ev",
"filename": "ev.tgz",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"ext-ds": { "ext-ds": {
"type": "url", "type": "url",
"url": "https://pecl.php.net/get/ds", "url": "https://pecl.php.net/get/ds",
@@ -335,9 +348,12 @@
} }
}, },
"libacl": { "libacl": {
"type": "git", "alt": {
"rev": "v2.3.2", "type": "url",
"url": "https://git.savannah.nongnu.org/git/acl.git", "url": "https://mirror.souseiseki.middlendian.com/nongnu/acl/acl-2.3.2.tar.gz"
},
"type": "url",
"url": "https://download.savannah.nongnu.org/releases/acl/acl-2.3.2.tar.gz",
"provide-pre-built": true, "provide-pre-built": true,
"license": { "license": {
"type": "file", "type": "file",
@@ -636,6 +652,16 @@
"path": "LICENSE" "path": "LICENSE"
} }
}, },
"mimalloc": {
"type": "ghtagtar",
"repo": "microsoft/mimalloc",
"match": "v2\\.\\d\\.[^3].*",
"provide-pre-built": false,
"license": {
"type": "file",
"path": "LICENSE"
}
},
"mongodb": { "mongodb": {
"type": "ghrel", "type": "ghrel",
"repo": "mongodb/mongo-php-driver", "repo": "mongodb/mongo-php-driver",
@@ -751,6 +777,14 @@
"path": "COPYRIGHT" "path": "COPYRIGHT"
} }
}, },
"postgresql-win": {
"type": "url",
"url": "https://get.enterprisedb.com/postgresql/postgresql-16.8-1-windows-x64-binaries.zip",
"license": {
"type": "text",
"text": "PostgreSQL Database Management System\n(also known as Postgres, formerly as Postgres95)\n\nPortions Copyright (c) 1996-2025, The PostgreSQL Global Development Group\n\nPortions Copyright (c) 1994, The Regents of the University of California\n\nPermission to use, copy, modify, and distribute this software and its\ndocumentation for any purpose, without fee, and without a written\nagreement is hereby granted, provided that the above copyright notice\nand this paragraph and the following two paragraphs appear in all\ncopies.\n\nIN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY\nFOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,\nINCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS\nDOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF\nTHE POSSIBILITY OF SUCH DAMAGE.\n\nTHE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,\nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS\nON AN \"AS IS\" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS\nTO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
}
},
"protobuf": { "protobuf": {
"type": "url", "type": "url",
"url": "https://pecl.php.net/get/protobuf", "url": "https://pecl.php.net/get/protobuf",
@@ -895,6 +929,15 @@
"path": "COPYING" "path": "COPYING"
} }
}, },
"xdebug": {
"type": "url",
"url": "https://pecl.php.net/get/xdebug",
"filename": "xdebug.tgz",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"xhprof": { "xhprof": {
"type": "url", "type": "url",
"url": "https://pecl.php.net/get/xhprof", "url": "https://pecl.php.net/get/xhprof",

View File

@@ -48,6 +48,10 @@ This extension contains an implementation of the coroutine environment for `pdo_
1. Only PHP 8.0 ~ 8.4 is supported. 1. Only PHP 8.0 ~ 8.4 is supported.
## imagick
1. The imagick extension currently only has openmp support on musl libc. This means that multithreading is disabled on glibc or other operating systems. The extension is still fully functional.
## imap ## imap
1. Kerberos is not supported 1. Kerberos is not supported
@@ -76,11 +80,9 @@ and this extension cannot be compiled into php by static linking, so it cannot b
## xdebug ## xdebug
1. Xdebug is a Zend extension. The functions of Xdebug depend on PHP's Zend engine and underlying code. 1. Xdebug is only buildable as a shared extension. On Linux, you need to use static-php-cli with SPC_LIBC=glibc.
If you want to statically compile it into PHP, you may need a huge amount of patch code, which is not feasible. 2. When using Linux/glibc or macOS, you can compile Xdebug as a shared extension using --build-shared="xdebug".
2. The macOS platform can compile an xdebug extension under PHP compiled on the same platform, 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`.
extract the `xdebug.so` file, and then use the `--no-strip` parameter in static-php-cli to retain the debug symbol table and add the `ffi` extension.
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`.
## xml ## xml
@@ -123,8 +125,8 @@ For details on the solution, see [FAQ - Unable to use ssl](../faq/#unable-to-use
## ffi ## ffi
1. Due to the limitation of Linux system, you cannot use it to load other `so` extensions in the purely static compiled state (spc defaults to pure static compilation). 1. Due to the limitation of musl libc's static linkage, you cannot use ffi because dynamic libraries cannot be loaded.
Linux supports loading so extensions only if they are non-statically compiled. If you need to use the ffi extension, see [Compile PHP with GNU libc](./build-with-glibc). If you need to use the ffi extension, see [Compile PHP with GNU libc](./build-with-glibc).
2. macOS supports the ffi extension, but errors will occur when some kernels do not contain debugging symbols. 2. macOS supports the ffi extension, but errors will occur when some kernels do not contain debugging symbols.
3. Windows x64 supports the ffi extension. 3. Windows x64 supports the ffi extension.
@@ -149,3 +151,9 @@ Parallel is only supported on PHP 8.0 ZTS and above.
1. The [SPX extension](https://github.com/NoiseByNorthwest/php-spx) only supports NTS mode. 1. The [SPX extension](https://github.com/NoiseByNorthwest/php-spx) only supports NTS mode.
2. SPX does not support Windows, and the official repository does not support static compilation. static-php-cli uses a [modified version](https://github.com/static-php/php-spx). 2. SPX does not support Windows, and the official repository does not support static compilation. static-php-cli uses a [modified version](https://github.com/static-php/php-spx).
## mimalloc
1. This is not technically an extension, but a library.
2. Building with `--with-libs="mimalloc"` on Linux or macOS will override the default allocator.
3. This is experimental for now, but is recommended in threaded environments.

View File

@@ -314,6 +314,7 @@ You can try to use the following commands:
- `--with-suggested-exts`: Add `ext-suggests` as dependencies when compiling - `--with-suggested-exts`: Add `ext-suggests` as dependencies when compiling
- `--with-suggested-libs`: Add `lib-suggests` as dependencies when compiling - `--with-suggested-libs`: Add `lib-suggests` as dependencies when compiling
- `--with-upx-pack`: Use UPX to reduce the size of the binary file after compilation (you need to use `bin/spc install-pkg upx` to install upx first) - `--with-upx-pack`: Use UPX to reduce the size of the binary file after compilation (you need to use `bin/spc install-pkg upx` to install upx first)
- `--build-shared=XXX,YYY`: compile the specified extension into a shared library (the default is to compile into a static library)
For hardcoding INI options, it works for cli, micro, embed sapi. Here is a simple example where we preset a larger `memory_limit` and disable the `system` function: For hardcoding INI options, it works for cli, micro, embed sapi. Here is a simple example where we preset a larger `memory_limit` and disable the `system` function:

View File

@@ -45,6 +45,10 @@ swoole-hook-sqlite 与 `pdo_sqlite` 扩展冲突。如需使用 Swoole 和 `pdo_
1. swow 仅支持 PHP 8.0 ~ 8.4 版本。 1. swow 仅支持 PHP 8.0 ~ 8.4 版本。
## imagick
imagick 扩展目前仅在 musl libc 上支持 OpenMPlibgomp。使用 glibc 方式构建的 imagick 扩展无法支持多线程特性。
## imap ## imap
1. 该扩展目前不支持 Kerberos。 1. 该扩展目前不支持 Kerberos。
@@ -70,9 +74,9 @@ bin/spc build gd --with-libs=freetype,libjpeg,libavif,libwebp --build-cli
## xdebug ## xdebug
1. Xdebug 是一个 Zend 扩展Xdebug 的功能依赖于 PHP 的 Zend 引擎和底层代码,如果要将其静态编译到 PHP 中,可能需要巨量的 patch 代码,这是不可行的 1. Xdebug 只能作为共享扩展进行构建。在 Linux 上,您需要使用 static-php-cli 并设置 SPC_LIBC=glibc
2. macOS 平台可以通过在相同平台编译的 PHP 下编译一个 xdebug 扩展,并提取其中的 `xdebug.so` 文件,再在 static-php-cli 中使用 `--no-strip` 参数保留调试符号表,同时加入 `ffi` 扩展。 2. 使用 Linux/glibc 或 macOS 时,您可以使用 `--build-shared=xdebug` 将 Xdebug 编译为共享扩展。
编译的 `./php` 二进制可以通过指定 INI 配置运行,例如`./php -d 'zend_extension=xdebug.so' your-code.php` 编译`./php` 二进制文件可以通过指定 INI 文件进行配置运行,例如 `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`
## xml ## xml
@@ -113,9 +117,10 @@ pgsql 16.2 修复了这个 Bug现在正常工作了。
## ffi ## ffi
1. 因为 Linux 系统的限制纯静态编译的状态下spc 默认编译结果为纯静态)无法使用它加载其他 `so` 扩展。Linux 支持加载 so 扩展的前提是非静态编译。如果你需要使用 ffi 扩展,请参见 [编译 GNU libc 的 PHP](./build-with-glibc) 1. 由于 musl libc 静态链接的限制,无法加载动态库,因此无法使用 ffi
2. macOS 支持 ffi 扩展,但是部分内核下不包含调试符号时会出现错误 如果您需要使用 ffi 扩展,请参阅 [使用 GNU libc 编译 PHP](./build-with-glibc)
3. Windows 支持 ffi 扩展。 2. macOS 支持 ffi 扩展,但某些内核不包含调试符号时会出现错误
3. Windows x64 支持 ffi 扩展。
## xhprof ## xhprof
@@ -136,3 +141,9 @@ parallel 扩展只支持 PHP 8.0 及以上版本,并只支持 ZTS 构建(`--
1. [SPX 扩展](https://github.com/NoiseByNorthwest/php-spx) 只支持非线程模式。 1. [SPX 扩展](https://github.com/NoiseByNorthwest/php-spx) 只支持非线程模式。
2. SPX 目前不支持 Windows且官方仓库也不支持静态编译static-php-cli 使用了 [修改版本](https://github.com/static-php/php-spx)。 2. SPX 目前不支持 Windows且官方仓库也不支持静态编译static-php-cli 使用了 [修改版本](https://github.com/static-php/php-spx)。
## mimalloc
1. 从技术上讲,这不是扩展,而是一个库。
2. 在 Linux 或 macOS 上使用 `--with-libs="mimalloc"` 进行构建将覆盖默认分配器。
3. 目前,这还处于实验阶段,但建议在线程环境中使用。

View File

@@ -272,6 +272,7 @@ bin/spc build mysqlnd,pdo_mysql --build-all --debug
- `--with-suggested-exts`: 编译时将 `ext-suggests` 也作为编译依赖加入 - `--with-suggested-exts`: 编译时将 `ext-suggests` 也作为编译依赖加入
- `--with-suggested-libs`: 编译时将 `lib-suggests` 也作为编译依赖加入 - `--with-suggested-libs`: 编译时将 `lib-suggests` 也作为编译依赖加入
- `--with-upx-pack`: 编译后使用 UPX 减小二进制文件体积(需先使用 `bin/spc install-pkg upx` 安装 upx - `--with-upx-pack`: 编译后使用 UPX 减小二进制文件体积(需先使用 `bin/spc install-pkg upx` 安装 upx
- `--build-shared=XXX,YYY`: 编译时将指定的扩展编译为共享库(默认编译为静态库)
硬编码 INI 选项适用于 cli、micro、embed。有关硬编码 INI 选项,下面是一个简单的例子,我们预设一个更大的 `memory_limit`,并且禁用 `system` 函数: 硬编码 INI 选项适用于 cli、micro、embed。有关硬编码 INI 选项,下面是一个简单的例子,我们预设一个更大的 `memory_limit`,并且禁用 `system` 函数:

View File

@@ -32,7 +32,7 @@ use Symfony\Component\Console\Application;
*/ */
final class ConsoleApplication extends Application final class ConsoleApplication extends Application
{ {
public const VERSION = '2.5.0'; public const VERSION = '2.5.2';
public function __construct() public function __construct()
{ {

View File

@@ -122,9 +122,12 @@ abstract class BuilderBase
* *
* @return Extension[] * @return Extension[]
*/ */
public function getExts(): array public function getExts(bool $including_shared = true): array
{ {
return $this->exts; if ($including_shared) {
return $this->exts;
}
return array_filter($this->exts, fn ($ext) => !$ext->isBuildShared());
} }
/** /**
@@ -136,7 +139,7 @@ abstract class BuilderBase
public function hasCpp(): bool public function hasCpp(): bool
{ {
// judge cpp-extension // judge cpp-extension
$exts = array_keys($this->getExts()); $exts = array_keys($this->getExts(false));
foreach ($exts as $ext) { foreach ($exts as $ext) {
if (Config::getExt($ext, 'cpp-extension', false) === true) { if (Config::getExt($ext, 'cpp-extension', false) === true) {
return true; return true;
@@ -170,23 +173,46 @@ abstract class BuilderBase
* @throws \Throwable|WrongUsageException * @throws \Throwable|WrongUsageException
* @internal * @internal
*/ */
public function proveExts(array $extensions, bool $skip_check_deps = false): void public function proveExts(array $static_extensions, array $shared_extensions = [], bool $skip_check_deps = false, bool $skip_extract = false): void
{ {
CustomExt::loadCustomExt(); CustomExt::loadCustomExt();
$this->emitPatchPoint('before-php-extract'); // judge ext
SourceManager::initSource(sources: ['php-src']); foreach ($static_extensions as $ext) {
$this->emitPatchPoint('after-php-extract'); // if extension does not support static build, throw exception
if ($this->getPHPVersionID() >= 80000) { if (!in_array('static', Config::getExtTarget($ext))) {
$this->emitPatchPoint('before-micro-extract'); throw new WrongUsageException('Extension [' . $ext . '] does not support static build!');
SourceManager::initSource(sources: ['micro']); }
$this->emitPatchPoint('after-micro-extract');
} }
$this->emitPatchPoint('before-exts-extract'); foreach ($shared_extensions as $ext) {
SourceManager::initSource(exts: $extensions); // if extension does not support shared build, throw exception
$this->emitPatchPoint('after-exts-extract'); if (!in_array('shared', Config::getExtTarget($ext)) && !in_array($ext, $shared_extensions)) {
foreach ($extensions as $extension) { throw new WrongUsageException('Extension [' . $ext . '] does not support shared build!');
}
}
if (!$skip_extract) {
$this->emitPatchPoint('before-php-extract');
SourceManager::initSource(sources: ['php-src'], source_only: true);
$this->emitPatchPoint('after-php-extract');
if ($this->getPHPVersionID() >= 80000) {
$this->emitPatchPoint('before-micro-extract');
SourceManager::initSource(sources: ['micro'], source_only: true);
$this->emitPatchPoint('after-micro-extract');
}
$this->emitPatchPoint('before-exts-extract');
SourceManager::initSource(exts: [...$static_extensions, ...$shared_extensions]);
$this->emitPatchPoint('after-exts-extract');
}
foreach ([...$static_extensions, ...$shared_extensions] as $extension) {
$class = CustomExt::getExtClass($extension); $class = CustomExt::getExtClass($extension);
/** @var Extension $ext */
$ext = new $class($extension, $this); $ext = new $class($extension, $this);
if (in_array($extension, $static_extensions)) {
$ext->setBuildStatic();
}
if (in_array($extension, $shared_extensions)) {
$ext->setBuildShared();
}
$this->addExt($ext); $this->addExt($ext);
} }
@@ -194,10 +220,10 @@ abstract class BuilderBase
return; return;
} }
foreach ($this->exts as $ext) { foreach ($this->getExts() as $ext) {
$ext->checkDependency(); $ext->checkDependency();
} }
$this->ext_list = $extensions; $this->ext_list = [...$static_extensions, ...$shared_extensions];
} }
/** /**
@@ -207,6 +233,17 @@ abstract class BuilderBase
*/ */
abstract public function buildPHP(int $build_target = BUILD_TARGET_NONE); abstract public function buildPHP(int $build_target = BUILD_TARGET_NONE);
public function buildSharedExts(): void
{
foreach ($this->getExts() as $ext) {
if (!$ext->isBuildShared()) {
continue;
}
logger()->info('Building extension [' . $ext->getName() . '] as shared extension (' . $ext->getName() . '.so)');
$ext->buildShared();
}
}
/** /**
* Generate extension enable arguments for configure. * Generate extension enable arguments for configure.
* e.g. --enable-mbstring * e.g. --enable-mbstring
@@ -214,10 +251,10 @@ abstract class BuilderBase
* @throws FileSystemException * @throws FileSystemException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
public function makeExtensionArgs(): string public function makeStaticExtensionArgs(): string
{ {
$ret = []; $ret = [];
foreach ($this->exts as $ext) { foreach ($this->getExts(false) as $ext) {
logger()->info($ext->getName() . ' is using ' . $ext->getConfigureArg()); logger()->info($ext->getName() . ' is using ' . $ext->getConfigureArg());
$ret[] = trim($ext->getConfigureArg()); $ret[] = trim($ext->getConfigureArg());
} }
@@ -396,7 +433,7 @@ abstract class BuilderBase
foreach ($this->libs as $lib) { foreach ($this->libs as $lib) {
$lib->validate(); $lib->validate();
} }
foreach ($this->exts as $ext) { foreach ($this->getExts() as $ext) {
$ext->validate(); $ext->validate();
} }
} }
@@ -441,7 +478,7 @@ abstract class BuilderBase
{ {
$php = "<?php\n\necho '[micro-test-start]' . PHP_EOL;\n"; $php = "<?php\n\necho '[micro-test-start]' . PHP_EOL;\n";
foreach ($this->getExts() as $ext) { foreach ($this->getExts(false) as $ext) {
$ext_name = $ext->getDistName(); $ext_name = $ext->getDistName();
if (!empty($ext_name)) { if (!empty($ext_name)) {
$php .= "echo 'Running micro with {$ext_name} test' . PHP_EOL;\n"; $php .= "echo 'Running micro with {$ext_name} test' . PHP_EOL;\n";

View File

@@ -9,11 +9,18 @@ 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\util\SPCConfigUtil;
class Extension class Extension
{ {
protected array $dependencies = []; protected array $dependencies = [];
protected bool $build_shared = false;
protected bool $build_static = false;
protected string $source_dir;
/** /**
* @throws FileSystemException * @throws FileSystemException
* @throws RuntimeException * @throws RuntimeException
@@ -30,6 +37,18 @@ class Extension
if (PHP_OS_FAMILY === 'Windows' && $unix_only) { if (PHP_OS_FAMILY === 'Windows' && $unix_only) {
throw new RuntimeException("{$ext_type} extension {$name} is not supported on Windows platform"); throw new RuntimeException("{$ext_type} extension {$name} is not supported on Windows platform");
} }
// set source_dir for builtin
if ($ext_type === 'builtin') {
$this->source_dir = SOURCE_PATH . '/php-src/ext/' . $this->name;
} else {
$source = Config::getExt($this->name, 'source');
if ($source === null) {
throw new RuntimeException("{$ext_type} extension {$name} source not found");
}
$source_path = Config::getSource($source)['path'] ?? null;
$source_path = $source_path === null ? SOURCE_PATH . '/' . $source : SOURCE_PATH . '/' . $source_path;
$this->source_dir = $source_path;
}
} }
/** /**
@@ -132,7 +151,7 @@ class Extension
// Windows is not supported yet // Windows is not supported yet
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return ''; return '';
} }
@@ -167,6 +186,21 @@ class Extension
return false; return false;
} }
/**
* Run shared extension check when cli is enabled
* @throws RuntimeException
*/
public function runSharedExtensionCheckUnix(): void
{
[$ret] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n -d "extension=' . BUILD_LIB_PATH . '/' . $this->getName() . '.so" --ri ' . $this->getName());
if ($ret !== 0) {
throw new RuntimeException($this->getName() . '.so failed to load');
}
if ($this->isBuildStatic()) {
logger()->warning($this->getName() . '.so test succeeded, but has little significance since it is also compiled in statically.');
}
}
/** /**
* @throws RuntimeException * @throws RuntimeException
*/ */
@@ -231,6 +265,53 @@ class Extension
// do nothing, just throw wrong usage exception if not valid // do nothing, just throw wrong usage exception if not valid
} }
/**
* Build shared extension
*
* @throws WrongUsageException
* @throws RuntimeException
*/
public function buildShared(): void
{
match (PHP_OS_FAMILY) {
'Darwin', 'Linux' => $this->buildUnixShared(),
default => throw new WrongUsageException(PHP_OS_FAMILY . ' build shared extensions is not supported yet'),
};
}
/**
* Build shared extension for Unix
*
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
* @throws \ReflectionException
* @throws \Throwable
*/
public function buildUnixShared(): void
{
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()]);
$env = [
'CFLAGS' => $config['cflags'],
'LDFLAGS' => $config['ldflags'],
'LIBS' => $config['libs'],
];
// prepare configure args
shell()->cd($this->source_dir)
->setEnv($env)
->execWithEnv(BUILD_BIN_PATH . '/phpize')
->execWithEnv('./configure ' . $this->getUnixConfigureArg(true) . ' --with-php-config=' . BUILD_BIN_PATH . '/php-config --enable-shared --disable-static')
->execWithEnv('make clean')
->execWithEnv('make -j' . $this->builder->concurrency);
// copy shared library
copy($this->source_dir . '/modules/' . $this->getDistName() . '.so', BUILD_LIB_PATH . '/' . $this->getDistName() . '.so');
// check shared extension with php-cli
if (file_exists(BUILD_BIN_PATH . '/php')) {
$this->runSharedExtensionCheckUnix();
}
}
/** /**
* Get current extension version * Get current extension version
* *
@@ -241,6 +322,32 @@ class Extension
return null; return null;
} }
public function setBuildStatic(): void
{
if (!in_array('static', Config::getExtTarget($this->name))) {
throw new WrongUsageException("Extension [{$this->name}] does not support static build!");
}
$this->build_static = true;
}
public function setBuildShared(): void
{
if (!in_array('shared', Config::getExtTarget($this->name))) {
throw new WrongUsageException("Extension [{$this->name}] does not support shared build!");
}
$this->build_shared = true;
}
public function isBuildShared(): bool
{
return $this->build_shared;
}
public function isBuildStatic(): bool
{
return $this->build_static;
}
/** /**
* @throws RuntimeException * @throws RuntimeException
*/ */

View File

@@ -8,6 +8,7 @@ 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\Downloader;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourceManager; use SPC\store\SourceManager;
@@ -45,8 +46,9 @@ abstract class LibraryBase
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; $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
if (isset($lock[$source]) && ($lock[$source]['lock_as'] ?? SPC_LOCK_SOURCE) === SPC_LOCK_PRE_BUILT) { $pre_built_name = Downloader::getPreBuiltLockName($source);
return $this->tryInstall($lock[$source]['filename'], $force); if (isset($lock[$pre_built_name]) && ($lock[$pre_built_name]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) {
return $this->tryInstall($lock[$pre_built_name]['filename'], $force);
} }
return $this->tryBuild($force); return $this->tryBuild($force);
} }
@@ -220,7 +222,7 @@ abstract class LibraryBase
// extract first if not exists // extract first if not exists
if (!is_dir($this->source_dir)) { if (!is_dir($this->source_dir)) {
$this->getBuilder()->emitPatchPoint('before-library[ ' . static::NAME . ']-extract'); $this->getBuilder()->emitPatchPoint('before-library[ ' . static::NAME . ']-extract');
SourceManager::initSource(libs: [static::NAME]); SourceManager::initSource(libs: [static::NAME], source_only: true);
$this->getBuilder()->emitPatchPoint('after-library[ ' . static::NAME . ']-extract'); $this->getBuilder()->emitPatchPoint('after-library[ ' . static::NAME . ']-extract');
} }

View File

@@ -23,7 +23,7 @@ class amqp extends Extension
return false; return false;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--with-amqp --with-librabbitmq-dir=' . BUILD_ROOT_PATH; return '--with-amqp --with-librabbitmq-dir=' . BUILD_ROOT_PATH;
} }

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('dba')] #[CustomExt('dba')]
class dba extends Extension class dba extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$qdbm = $this->builder->getLib('qdbm') ? (' --with-qdbm=' . BUILD_ROOT_PATH) : ''; $qdbm = $this->builder->getLib('qdbm') ? (' --with-qdbm=' . BUILD_ROOT_PATH) : '';
return '--enable-dba' . $qdbm; return '--enable-dba' . $qdbm;

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('enchant')] #[CustomExt('enchant')]
class enchant extends Extension class enchant extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$glibs = [ $glibs = [
'/Users/jerry/project/git-project/static-php-cli/buildroot/lib/libgio-2.0.a', '/Users/jerry/project/git-project/static-php-cli/buildroot/lib/libgio-2.0.a',

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\exception\FileSystemException;
use SPC\store\FileSystem;
use SPC\util\CustomExt;
#[CustomExt('ev')]
class ev extends Extension
{
/**
* @throws FileSystemException
*/
public function patchBeforeBuildconf(): bool
{
/*
* replace EXTENSION('ev', php_ev_sources, true, ' /DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
* to EXTENSION('ev', php_ev_sources, PHP_EV_SHARED, ' /DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
*/
FileSystem::replaceFileLineContainsString(
$this->source_dir . '/config.w32',
'EXTENSION(\'ev\'',
" EXTENSION('ev', php_ev_sources, PHP_EV_SHARED, ' /DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');"
);
return true;
}
}

View File

@@ -13,7 +13,7 @@ use SPC\util\CustomExt;
#[CustomExt('event')] #[CustomExt('event')]
class event extends Extension class event extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = '--with-event-core --with-event-extra --with-event-libevent-dir=' . BUILD_ROOT_PATH; $arg = '--with-event-core --with-event-extra --with-event-libevent-dir=' . BUILD_ROOT_PATH;
if ($this->builder->getLib('openssl')) { if ($this->builder->getLib('openssl')) {

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('ffi')] #[CustomExt('ffi')]
class ffi extends Extension class ffi extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--with-ffi --enable-zend-signals'; return '--with-ffi --enable-zend-signals';
} }

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('gd')] #[CustomExt('gd')]
class gd extends Extension class gd extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = '--enable-gd'; $arg = '--enable-gd';
$arg .= $this->builder->getLib('freetype') ? ' --with-freetype' : ''; $arg .= $this->builder->getLib('freetype') ? ' --with-freetype' : '';

View File

@@ -30,7 +30,7 @@ class glfw extends Extension
return true; return true;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--enable-glfw --with-glfw-dir=' . BUILD_ROOT_PATH; return '--enable-glfw --with-glfw-dir=' . BUILD_ROOT_PATH;
} }

View File

@@ -44,7 +44,7 @@ class grpc extends Extension
return true; return true;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--enable-grpc=' . BUILD_ROOT_PATH . '/grpc GRPC_LIB_SUBDIR=' . BUILD_LIB_PATH; return '--enable-grpc=' . BUILD_ROOT_PATH . '/grpc GRPC_LIB_SUBDIR=' . BUILD_LIB_PATH;
} }

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace SPC\builder\extension; namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\builder\linux\LinuxBuilder;
use SPC\util\CustomExt; use SPC\util\CustomExt;
#[CustomExt('imagick')] #[CustomExt('imagick')]
@@ -13,17 +12,18 @@ class imagick extends Extension
{ {
public function patchBeforeMake(): bool public function patchBeforeMake(): bool
{ {
// imagick may call omp_pause_all which requires -lgomp if (getenv('SPC_LIBC') !== 'musl') {
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: ''; return false;
if ($this->builder instanceof LinuxBuilder) {
$extra_libs .= (empty($extra_libs) ? '' : ' ') . '-lgomp ';
} }
// imagick with calls omp_pause_all which requires -lgomp, on non-musl we build imagick without openmp
$extra_libs = trim(getenv('SPC_EXTRA_LIBS') . ' -lgomp');
f_putenv('SPC_EXTRA_LIBS=' . $extra_libs); f_putenv('SPC_EXTRA_LIBS=' . $extra_libs);
return true; return true;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--with-imagick=' . BUILD_ROOT_PATH; $disable_omp = getenv('SPC_LIBC') === 'musl' ? '' : ' ac_cv_func_omp_pause_resource_all=no';
return '--with-imagick=' . BUILD_ROOT_PATH . $disable_omp;
} }
} }

View File

@@ -33,7 +33,7 @@ class imap extends Extension
} }
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = '--with-imap=' . BUILD_ROOT_PATH; $arg = '--with-imap=' . BUILD_ROOT_PATH;
if ($this->builder->getLib('openssl') !== null) { if ($this->builder->getLib('openssl') !== null) {

View File

@@ -12,7 +12,7 @@ use SPC\util\CustomExt;
#[CustomExt('memcache')] #[CustomExt('memcache')]
class memcache extends Extension class memcache extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--enable-memcache --with-zlib-dir=' . BUILD_ROOT_PATH; return '--enable-memcache --with-zlib-dir=' . BUILD_ROOT_PATH;
} }

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('memcached')] #[CustomExt('memcached')]
class memcached extends Extension class memcached extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$rootdir = BUILD_ROOT_PATH; $rootdir = BUILD_ROOT_PATH;
$zlib_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : "--with-zlib-dir={$rootdir}"; $zlib_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : "--with-zlib-dir={$rootdir}";

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('mongodb')] #[CustomExt('mongodb')]
class mongodb extends Extension class mongodb extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = ' --enable-mongodb '; $arg = ' --enable-mongodb ';
$arg .= ' --with-mongodb-system-libs=no --with-mongodb-client-side-encryption=no '; $arg .= ' --with-mongodb-system-libs=no --with-mongodb-client-side-encryption=no ';

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\util\CustomExt;
#[CustomExt('odbc')]
class odbc extends Extension
{
public function getUnixConfigureArg(bool $shared = false): string
{
return '--with-unixODBC=' . BUILD_ROOT_PATH;
}
}

View File

@@ -42,7 +42,7 @@ class opcache extends Extension
return file_put_contents(SOURCE_PATH . '/php-src/.opcache_patched', '1') !== false; return file_put_contents(SOURCE_PATH . '/php-src/.opcache_patched', '1') !== false;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--enable-opcache'; return '--enable-opcache';
} }

View File

@@ -23,7 +23,7 @@ class openssl extends Extension
return false; return false;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$openssl_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : ' --with-openssl-dir=' . BUILD_ROOT_PATH; $openssl_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : ' --with-openssl-dir=' . BUILD_ROOT_PATH;
return '--with-openssl=' . BUILD_ROOT_PATH . $openssl_dir; return '--with-openssl=' . BUILD_ROOT_PATH . $openssl_dir;

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\store\FileSystem;
use SPC\util\CustomExt;
#[CustomExt('pdo_odbc')]
class pdo_odbc extends Extension
{
public function patchBeforeBuildconf(): bool
{
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/ext/pdo_odbc/config.m4', 'PDO_ODBC_LDFLAGS="$pdo_odbc_def_ldflags', 'PDO_ODBC_LDFLAGS="-liconv $pdo_odbc_def_ldflags');
return true;
}
public function getUnixConfigureArg(bool $shared = false): string
{
return '--with-pdo-odbc=unixODBC,' . BUILD_ROOT_PATH;
}
public function getWindowsConfigureArg(): string
{
return '--with-pdo-odbc';
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\util\CustomExt;
#[CustomExt('pdo_pgsql')]
class pdo_pgsql extends Extension
{
public function getWindowsConfigureArg(): string
{
return '--with-pdo-pgsql=yes';
}
}

View File

@@ -33,11 +33,23 @@ class pgsql extends Extension
* @throws WrongUsageException * @throws WrongUsageException
* @throws RuntimeException * @throws RuntimeException
*/ */
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
if ($this->builder->getPHPVersionID() >= 80400) { if ($this->builder->getPHPVersionID() >= 80400) {
return '--with-pgsql PGSQL_CFLAGS=-I' . BUILD_INCLUDE_PATH . ' PGSQL_LIBS="-L' . BUILD_LIB_PATH . ' -lpq -lpgport -lpgcommon"'; return '--with-pgsql PGSQL_CFLAGS=-I' . BUILD_INCLUDE_PATH . ' PGSQL_LIBS="-L' . BUILD_LIB_PATH . ' -lpq -lpgport -lpgcommon"';
} }
return '--with-pgsql=' . BUILD_ROOT_PATH; return '--with-pgsql=' . BUILD_ROOT_PATH;
} }
/**
* @throws WrongUsageException
* @throws RuntimeException
*/
public function getWindowsConfigureArg(): string
{
if ($this->builder->getPHPVersionID() >= 80400) {
return '--with-pgsql';
}
return '--with-pgsql=' . BUILD_ROOT_PATH;
}
} }

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('redis')] #[CustomExt('redis')]
class redis extends Extension class redis extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = '--enable-redis'; $arg = '--enable-redis';
$arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session'; $arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session';

View File

@@ -26,7 +26,7 @@ class snappy extends Extension
return true; return true;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--enable-snappy --with-snappy-includedir="' . BUILD_ROOT_PATH . '"'; return '--enable-snappy --with-snappy-includedir="' . BUILD_ROOT_PATH . '"';
} }

View File

@@ -21,7 +21,7 @@ class spx extends Extension
} }
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = '--enable-spx'; $arg = '--enable-spx';
if ($this->builder->getExt('zlib') === null) { if ($this->builder->getExt('zlib') === null) {

View File

@@ -35,7 +35,7 @@ class swoole extends Extension
return null; return null;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
// enable swoole // enable swoole
$arg = '--enable-swoole'; $arg = '--enable-swoole';
@@ -49,7 +49,9 @@ class swoole extends Extension
// additional feature: c-ares, brotli, nghttp2 (can be disabled, but we enable it by default in config to support full network feature) // additional feature: c-ares, brotli, nghttp2 (can be disabled, but we enable it by default in config to support full network feature)
$arg .= $this->builder->getLib('libcares') ? ' --enable-cares' : ''; $arg .= $this->builder->getLib('libcares') ? ' --enable-cares' : '';
$arg .= $this->builder->getLib('brotli') ? (' --with-brotli-dir=' . BUILD_ROOT_PATH) : ''; if (!$shared) {
$arg .= $this->builder->getLib('brotli') ? (' --enable-brotli --with-brotli-dir=' . BUILD_ROOT_PATH) : '';
}
$arg .= $this->builder->getLib('nghttp2') ? (' --with-nghttp2-dir=' . BUILD_ROOT_PATH) : ''; $arg .= $this->builder->getLib('nghttp2') ? (' --with-nghttp2-dir=' . BUILD_ROOT_PATH) : '';
// additional feature: swoole-pgsql, it should depend on lib [postgresql], but it will lack of CFLAGS etc. // additional feature: swoole-pgsql, it should depend on lib [postgresql], but it will lack of CFLAGS etc.

View File

@@ -16,7 +16,7 @@ class swoole_hook_mysql extends Extension
return 'swoole'; return 'swoole';
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
// pdo_mysql doesn't need to be disabled // pdo_mysql doesn't need to be disabled
// enable swoole-hook-mysql will enable mysqli, pdo, pdo_mysql, we don't need to add any additional options // enable swoole-hook-mysql will enable mysqli, pdo, pdo_mysql, we don't need to add any additional options

View File

@@ -25,7 +25,7 @@ class swoole_hook_pgsql extends Extension
} }
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
// enable swoole pgsql hook // enable swoole pgsql hook
return '--enable-swoole-pgsql'; return '--enable-swoole-pgsql';

View File

@@ -25,7 +25,7 @@ class swoole_hook_sqlite extends Extension
} }
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
// enable swoole pgsql hook // enable swoole pgsql hook
return '--enable-swoole-sqlite'; return '--enable-swoole-sqlite';

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\exception\RuntimeException;
use SPC\util\CustomExt;
#[CustomExt('xdebug')]
class xdebug extends Extension
{
public function runSharedExtensionCheckUnix(): void
{
[$ret] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n -d "zend_extension=' . BUILD_LIB_PATH . '/xdebug.so" --ri xdebug');
if ($ret !== 0) {
throw new RuntimeException('xdebug.so failed to load.');
}
}
}

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('xlswriter')] #[CustomExt('xlswriter')]
class xlswriter extends Extension class xlswriter extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = '--with-xlswriter --enable-reader'; $arg = '--with-xlswriter --enable-reader';
if ($this->builder->getLib('openssl')) { if ($this->builder->getLib('openssl')) {

View File

@@ -20,7 +20,7 @@ class xml extends Extension
/** /**
* @throws RuntimeException * @throws RuntimeException
*/ */
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = match ($this->name) { $arg = match ($this->name) {
'xml' => '--enable-xml', 'xml' => '--enable-xml',

View File

@@ -19,7 +19,7 @@ class yac extends Extension
return true; return true;
} }
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--enable-yac --enable-igbinary --enable-json'; return '--enable-yac --enable-igbinary --enable-json';
} }

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('zlib')] #[CustomExt('zlib')]
class zlib extends Extension class zlib extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$zlib_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : ' --with-zlib-dir=' . BUILD_ROOT_PATH; $zlib_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : ' --with-zlib-dir=' . BUILD_ROOT_PATH;
return '--with-zlib' . $zlib_dir; return '--with-zlib' . $zlib_dir;

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('zstd')] #[CustomExt('zstd')]
class zstd extends Extension class zstd extends Extension
{ {
public function getUnixConfigureArg(): string public function getUnixConfigureArg(bool $shared = false): string
{ {
return '--enable-zstd --with-libzstd="' . BUILD_ROOT_PATH . '"'; return '--enable-zstd --with-libzstd="' . BUILD_ROOT_PATH . '"';
} }

View File

@@ -118,7 +118,7 @@ class BSDBuilder extends UnixBuilderBase
$config_file_scan_dir . $config_file_scan_dir .
$json_74 . $json_74 .
$zts . $zts .
$this->makeExtensionArgs() $this->makeStaticExtensionArgs()
); );
$this->emitPatchPoint('before-php-make'); $this->emitPatchPoint('before-php-make');

View File

@@ -150,12 +150,13 @@ class LinuxBuilder extends UnixBuilderBase
$enable_micro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; $enable_micro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enable_embed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; $enable_embed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
// prepare build php envs // prepare build php envs
$envs_build_php = SystemUtil::makeEnvVarString([ $envs_build_php = SystemUtil::makeEnvVarString([
'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'), 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'),
'CPPFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS'), 'CPPFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS'),
'LDFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS'), 'LDFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS'),
'LIBS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_LIBS'), 'LIBS' => $mimallocLibs . getenv('SPC_CMD_VAR_PHP_CONFIGURE_LIBS'),
]); ]);
// process micro upx patch if micro sapi enabled // process micro upx patch if micro sapi enabled
@@ -181,7 +182,7 @@ class LinuxBuilder extends UnixBuilderBase
$json_74 . $json_74 .
$zts . $zts .
$maxExecutionTimers . $maxExecutionTimers .
$this->makeExtensionArgs() . $this->makeStaticExtensionArgs() .
' ' . $envs_build_php . ' ' ' ' . $envs_build_php . ' '
); );
@@ -310,7 +311,7 @@ class LinuxBuilder extends UnixBuilderBase
shell()->cd(SOURCE_PATH . '/php-src') shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install"); ->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/php-config', 'prefix=""', 'prefix="' . BUILD_ROOT_PATH . '"'); $this->patchPhpScripts();
} }
private function getMakeExtraVars(): array private function getMakeExtraVars(): array

View File

@@ -182,4 +182,39 @@ class SystemUtil
'arch', 'manjaro', 'arch', 'manjaro',
]; ];
} }
/**
* Get libc version string from ldd
*/
public static function getLibcVersionIfExists(): ?string
{
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'glibc') {
$result = shell()->execWithResult('ldd --version', false);
if ($result[0] !== 0) {
return null;
}
// get first line
$first_line = $result[1][0];
// match ldd version: "ldd (some useless text) 2.17" match 2.17
$pattern = '/ldd\s+\(.*?\)\s+(\d+\.\d+)/';
if (preg_match($pattern, $first_line, $matches)) {
return $matches[1];
}
return null;
}
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') {
if (self::isMuslDist()) {
$result = shell()->execWithResult('ldd 2>&1', false);
} else {
$result = shell()->execWithResult('/usr/local/musl/lib/libc.so 2>&1', false);
}
// Match Version * line
// match ldd version: "Version 1.2.3" match 1.2.3
$pattern = '/Version\s+(\d+\.\d+\.\d+)/';
if (preg_match($pattern, $result[1][1] ?? '', $matches)) {
return $matches[1];
}
}
return null;
}
} }

View File

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

View File

@@ -148,10 +148,12 @@ class MacOSBuilder extends UnixBuilderBase
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; $enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
// prepare build php envs // prepare build php envs
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
$envs_build_php = SystemUtil::makeEnvVarString([ $envs_build_php = SystemUtil::makeEnvVarString([
'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'), 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'),
'CPPFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS'), 'CPPFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS'),
'LDFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS'), 'LDFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS'),
'LIBS' => $mimallocLibs . getenv('SPC_CMD_VAR_PHP_CONFIGURE_LIBS'),
]); ]);
if ($this->getLib('postgresql')) { if ($this->getLib('postgresql')) {
@@ -174,7 +176,7 @@ class MacOSBuilder extends UnixBuilderBase
$config_file_scan_dir . $config_file_scan_dir .
$json_74 . $json_74 .
$zts . $zts .
$this->makeExtensionArgs() . ' ' . $this->makeStaticExtensionArgs() . ' ' .
$envs_build_php $envs_build_php
); );
@@ -298,6 +300,7 @@ class MacOSBuilder extends UnixBuilderBase
->exec('rm ' . BUILD_ROOT_PATH . '/lib/libphp.a') ->exec('rm ' . BUILD_ROOT_PATH . '/lib/libphp.a')
->exec('ar rcs ' . BUILD_ROOT_PATH . '/lib/libphp.a *.o') ->exec('ar rcs ' . BUILD_ROOT_PATH . '/lib/libphp.a *.o')
->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o'); ->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o');
$this->patchPhpScripts();
} }
private function getMakeExtraVars(): array private function getMakeExtraVars(): array

View File

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

View File

@@ -146,7 +146,7 @@ abstract class UnixBuilderBase extends BuilderBase
throw new RuntimeException("cli failed sanity check: ret[{$ret}]. out[{$raw_output}]"); throw new RuntimeException("cli failed sanity check: ret[{$ret}]. out[{$raw_output}]");
} }
foreach ($this->exts as $ext) { foreach ($this->getExts(false) as $ext) {
logger()->debug('testing ext: ' . $ext->getName()); logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckUnix(); $ext->runCliCheckUnix();
} }
@@ -238,4 +238,29 @@ abstract class UnixBuilderBase extends BuilderBase
logger()->info('cleaning up'); logger()->info('cleaning up');
shell()->cd(SOURCE_PATH . '/php-src')->exec('make clean'); shell()->cd(SOURCE_PATH . '/php-src')->exec('make clean');
} }
/**
* Patch phpize and php-config if needed
* @throws FileSystemException
*/
protected function patchPhpScripts(): void
{
// patch phpize
if (file_exists(BUILD_BIN_PATH . '/phpize')) {
logger()->debug('Patching phpize prefix');
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#');
}
// patch php-config
if (file_exists(BUILD_BIN_PATH . '/php-config')) {
logger()->debug('Patching php-config prefix and libs order');
$php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config');
$php_config_str = str_replace('prefix=""', 'prefix="' . BUILD_ROOT_PATH . '"', $php_config_str);
// move mimalloc to the beginning of libs
$php_config_str = preg_replace('/(libs=")(.*?)\s*(' . preg_quote(BUILD_LIB_PATH, '/') . '\/mimalloc\.o)\s*(.*?)"/', '$1$3 $2 $4"', $php_config_str);
// move lstdc++ to the end of libs
$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);
}
}
} }

View File

@@ -17,13 +17,17 @@ trait gettext
$ldflags = $this->builder->getOption('enable-zts') ? '-lpthread' : ''; $ldflags = $this->builder->getOption('enable-zts') ? '-lpthread' : '';
shell()->cd($this->source_dir) shell()->cd($this->source_dir)
->setEnv(['CFLAGS' => "{$this->getLibExtraCFlags()} {$cflags}", 'LDFLAGS' => $this->getLibExtraLdFlags() ?: $ldflags, 'LIBS' => $this->getLibExtraLibs()]) ->setEnv([
'CFLAGS' => "{$this->getLibExtraCFlags()} {$cflags}",
'LDFLAGS' => $this->getLibExtraLdFlags() ?: $ldflags,
'LIBS' => $this->getLibExtraLibs(),
])
->execWithEnv( ->execWithEnv(
'./configure ' . './configure ' .
'--enable-static ' . '--enable-static ' .
'--disable-shared ' . '--disable-shared ' .
'--disable-java ' . '--disable-java ' .
'--disable-c+ ' . '--disable-c++ ' .
$zts . $zts .
$extra . $extra .
'--with-included-gettext ' . '--with-included-gettext ' .

View File

@@ -18,8 +18,9 @@ trait imagemagick
*/ */
protected function build(): void protected function build(): void
{ {
// TODO: imagemagick build with bzip2 failed with bugs, we need to fix it in the future // TODO: glibc rh 10 toolset's libgomp.a was built without -fPIC -fPIE so we can't use openmp without depending on libgomp.so
$extra = '--without-jxl --without-x --enable-openmp --without-bzlib '; $openmp = getenv('SPC_LIBC') === 'musl' ? '--enable-openmp' : '--disable-openmp';
$extra = "--without-jxl --without-x {$openmp} ";
$required_libs = ''; $required_libs = '';
$optional_libs = [ $optional_libs = [
'libzip' => 'zip', 'libzip' => 'zip',
@@ -27,10 +28,12 @@ trait imagemagick
'libpng' => 'png', 'libpng' => 'png',
'libwebp' => 'webp', 'libwebp' => 'webp',
'libxml2' => 'xml', 'libxml2' => 'xml',
'libheif' => 'heic',
'zlib' => 'zlib', 'zlib' => 'zlib',
'xz' => 'lzma', 'xz' => 'lzma',
'zstd' => 'zstd', 'zstd' => 'zstd',
'freetype' => 'freetype', 'freetype' => 'freetype',
'bzip2' => 'bzlib',
]; ];
foreach ($optional_libs as $lib => $option) { foreach ($optional_libs as $lib => $option) {
$extra .= $this->builder->getLib($lib) ? "--with-{$option} " : "--without-{$option} "; $extra .= $this->builder->getLib($lib) ? "--with-{$option} " : "--without-{$option} ";

View File

@@ -26,9 +26,9 @@ trait libcares
{ {
shell()->cd($this->source_dir) shell()->cd($this->source_dir)
->setEnv(['CFLAGS' => $this->getLibExtraCFlags(), 'LDFLAGS' => $this->getLibExtraLdFlags(), 'LIBS' => $this->getLibExtraLibs()]) ->setEnv(['CFLAGS' => $this->getLibExtraCFlags(), 'LDFLAGS' => $this->getLibExtraLdFlags(), 'LIBS' => $this->getLibExtraLibs()])
->execWithEnv('./configure --prefix= --enable-static --disable-shared --disable-tests') ->execWithEnv('./configure --prefix= --enable-static --disable-shared --disable-tests --with-pic')
->execWithEnv("make -j {$this->builder->concurrency}") ->execWithEnv("make -j {$this->builder->concurrency}")
->exec('make install DESTDIR=' . BUILD_ROOT_PATH); ->execWithEnv('make install DESTDIR=' . BUILD_ROOT_PATH);
$this->patchPkgconfPrefix(['libcares.pc'], PKGCONF_PATCH_PREFIX); $this->patchPkgconfPrefix(['libcares.pc'], PKGCONF_PATCH_PREFIX);
} }

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
trait mimalloc
{
/**
* @throws RuntimeException
* @throws FileSystemException
*/
protected function build(): void
{
$args = '';
if (getenv('SPC_LIBC') === 'musl') {
$args .= '-DMI_LIBC_MUSL=ON ';
}
$args .= '-DMI_BUILD_SHARED=OFF ';
$args .= '-DMI_INSTALL_TOPLEVEL=ON ';
FileSystem::resetDir($this->source_dir . '/build');
shell()->cd($this->source_dir . '/build')
->execWithEnv(
'cmake ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DCMAKE_BUILD_TYPE=Release ' .
$args .
'..'
)
->execWithEnv("make -j{$this->builder->concurrency}")
->execWithEnv('make install');
}
}

View File

@@ -95,8 +95,9 @@ class WindowsBuilder extends BuilderBase
if (($logo = $this->getOption('with-micro-logo')) !== null) { if (($logo = $this->getOption('with-micro-logo')) !== null) {
// realpath // realpath
$logo = realpath($logo); // $logo = realpath($logo);
$micro_logo = '--enable-micro-logo=' . $logo . ' '; $micro_logo = '--enable-micro-logo=' . $logo . ' ';
copy($logo, SOURCE_PATH . '\php-src\\' . $logo);
} else { } else {
$micro_logo = ''; $micro_logo = '';
} }
@@ -118,7 +119,7 @@ class WindowsBuilder extends BuilderBase
($enableMicro ? ('--enable-micro=yes ' . $micro_logo . $micro_w32) : '--enable-micro=no ') . ($enableMicro ? ('--enable-micro=yes ' . $micro_logo . $micro_w32) : '--enable-micro=no ') .
($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') . ($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') .
$config_file_scan_dir . $config_file_scan_dir .
"{$this->makeExtensionArgs()} " . "{$this->makeStaticExtensionArgs()} " .
$zts . $zts .
'"' '"'
); );
@@ -285,7 +286,7 @@ class WindowsBuilder extends BuilderBase
throw new RuntimeException('cli failed sanity check'); throw new RuntimeException('cli failed sanity check');
} }
foreach ($this->exts as $ext) { foreach ($this->getExts(false) as $ext) {
logger()->debug('testing ext: ' . $ext->getName()); logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckWindows(); $ext->runCliCheckWindows();
} }

View File

@@ -37,6 +37,8 @@ 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
'-DCURL_USE_SCHANNEL=ON ' . // use Schannel instead of OpenSSL
'-DCURL_USE_OPENSSL=OFF ' . // disable openssl due to certificate issue
'-DCURL_ENABLE_SSL=ON ' . '-DCURL_ENABLE_SSL=ON ' .
'-DUSE_NGHTTP2=ON ' . // enable nghttp2 '-DUSE_NGHTTP2=ON ' . // enable nghttp2
'-DCURL_USE_LIBSSH2=ON ' . // enable libssh2 '-DCURL_USE_LIBSSH2=ON ' . // enable libssh2
@@ -48,5 +50,7 @@ class curl extends WindowsLibraryBase
$this->builder->makeSimpleWrapper('cmake'), $this->builder->makeSimpleWrapper('cmake'),
"--build cmakebuild --config Release --target install -j{$this->builder->concurrency}" "--build cmakebuild --config Release --target install -j{$this->builder->concurrency}"
); );
// move libcurl.lib to libcurl_a.lib
rename(BUILD_LIB_PATH . '\libcurl.lib', BUILD_LIB_PATH . '\libcurl_a.lib');
} }
} }

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
class postgresql_win extends WindowsLibraryBase
{
public const NAME = 'postgresql-win';
protected function build(): void
{
copy($this->source_dir . '\pgsql\lib\libpq.lib', BUILD_LIB_PATH . '\libpq.lib');
copy($this->source_dir . '\pgsql\lib\libpgport.lib', BUILD_LIB_PATH . '\libpgport.lib');
copy($this->source_dir . '\pgsql\lib\libpgcommon.lib', BUILD_LIB_PATH . '\libpgcommon.lib');
// create libpq folder in buildroot/includes/libpq
if (!file_exists(BUILD_INCLUDE_PATH . '\libpq')) {
mkdir(BUILD_INCLUDE_PATH . '\libpq');
}
$headerFiles = ['libpq-fe.h', 'postgres_ext.h', 'pg_config_ext.h', 'libpq\libpq-fs.h'];
foreach ($headerFiles as $header) {
copy($this->source_dir . '\pgsql\include\\' . $header, BUILD_INCLUDE_PATH . '\\' . $header);
}
}
}

View File

@@ -165,7 +165,7 @@ abstract class BaseCommand extends Command
return SPC_EXTENSION_ALIAS[$lower]; return SPC_EXTENSION_ALIAS[$lower];
} }
return $lower; return $lower;
}, is_array($ext_list) ? $ext_list : explode(',', $ext_list)); }, is_array($ext_list) ? $ext_list : array_filter(explode(',', $ext_list)));
// filter internals // filter internals
return array_values(array_filter($ls, function ($x) { return array_values(array_filter($ls, function ($x) {

View File

@@ -27,6 +27,7 @@ class BuildPHPCommand extends BuildCommand
$this->addArgument('extensions', InputArgument::REQUIRED, 'The extensions will be compiled, comma separated'); $this->addArgument('extensions', InputArgument::REQUIRED, 'The extensions will be compiled, comma separated');
$this->addOption('with-libs', null, InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', ''); $this->addOption('with-libs', null, InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', '');
$this->addOption('build-shared', 'D', InputOption::VALUE_REQUIRED, 'Shared extensions to build, comma separated', '');
$this->addOption('build-micro', null, null, 'Build micro SAPI'); $this->addOption('build-micro', null, null, 'Build micro SAPI');
$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)');
@@ -52,13 +53,31 @@ class BuildPHPCommand extends BuildCommand
// transform string to array // transform string to array
$libraries = array_map('trim', array_filter(explode(',', $this->getOption('with-libs')))); $libraries = array_map('trim', array_filter(explode(',', $this->getOption('with-libs'))));
// transform string to array // transform string to array
$extensions = $this->parseExtensionList($this->getArgument('extensions')); $shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared'))));
// transform string to array
$static_extensions = $this->parseExtensionList($this->getArgument('extensions'));
// parse rule with options // parse rule with options
$rule = $this->parseRules(); $rule = $this->parseRules($shared_extensions);
// check dynamic extension build env
// macOS must use --no-strip option
if (!empty($shared_extensions) && PHP_OS_FAMILY === 'Darwin' && !$this->getOption('no-strip')) {
$this->output->writeln('MacOS does not support dynamic extension loading with stripped binary, please use --no-strip option!');
return static::FAILURE;
}
// linux must build with glibc
if (!empty($shared_extensions) && PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') !== 'glibc') {
$this->output->writeln('Linux does not support dynamic extension loading with musl-libc full-static build, please build with glibc!');
return static::FAILURE;
}
$static_and_shared = array_intersect($static_extensions, $shared_extensions);
if (!empty($static_and_shared)) {
$this->output->writeln('<comment>Building extensions [' . implode(',', $static_and_shared) . '] as both static and shared\, tests may not be accurate or fail.</comment>');
}
if ($rule === BUILD_TARGET_NONE) { if ($rule === BUILD_TARGET_NONE) {
$this->output->writeln('<error>Please add at least one build target!</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\tBuild php-cli SAPI</comment>");
$this->output->writeln("<comment>\t--build-micro\tBuild phpmicro SAPI</comment>"); $this->output->writeln("<comment>\t--build-micro\tBuild phpmicro SAPI</comment>");
$this->output->writeln("<comment>\t--build-fpm\tBuild php-fpm SAPI</comment>"); $this->output->writeln("<comment>\t--build-fpm\tBuild php-fpm SAPI</comment>");
@@ -107,18 +126,26 @@ class BuildPHPCommand extends BuildCommand
$builder = BuilderProvider::makeBuilderByInput($this->input); $builder = BuilderProvider::makeBuilderByInput($this->input);
$include_suggest_ext = $this->getOption('with-suggested-exts'); $include_suggest_ext = $this->getOption('with-suggested-exts');
$include_suggest_lib = $this->getOption('with-suggested-libs'); $include_suggest_lib = $this->getOption('with-suggested-libs');
[$extensions, $libraries, $not_included] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib); [$extensions, $libraries, $not_included] = DependencyUtil::getExtsAndLibs(array_merge($static_extensions, $shared_extensions), $libraries, $include_suggest_ext, $include_suggest_lib);
$display_libs = array_filter($libraries, fn ($lib) => in_array(Config::getLib($lib, 'type', 'lib'), ['lib', 'package'])); $display_libs = array_filter($libraries, fn ($lib) => in_array(Config::getLib($lib, 'type', 'lib'), ['lib', 'package']));
$display_extensions = array_map(fn ($ext) => in_array($ext, $shared_extensions) ? "*{$ext}" : $ext, $extensions);
// separate static and shared extensions from $extensions
// filter rule: including shared extensions if they are in $static_extensions or $shared_extensions
$static_extensions = array_filter($extensions, fn ($ext) => !in_array($ext, $shared_extensions) || in_array($ext, $static_extensions));
// 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 SAPI' => $builder->getBuildTypeName($rule), 'Build SAPI' => $builder->getBuildTypeName($rule),
'Extensions (' . count($extensions) . ')' => implode(',', $extensions), 'Extensions (' . count($extensions) . ')' => implode(',', $display_extensions),
'Libraries (' . count($libraries) . ')' => implode(',', $display_libs), 'Libraries (' . count($libraries) . ')' => implode(',', $display_libs),
'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes', 'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes',
'Enable ZTS' => $builder->getOption('enable-zts') ? 'yes' : 'no', 'Enable ZTS' => $builder->getOption('enable-zts') ? 'yes' : 'no',
]; ];
if (!empty($shared_extensions) || ($rule & BUILD_TARGET_EMBED)) {
$indent_texts['Build Dev'] = 'yes';
}
if (!empty($this->input->getOption('with-config-file-path'))) { if (!empty($this->input->getOption('with-config-file-path'))) {
$indent_texts['Config File Path'] = $this->input->getOption('with-config-file-path'); $indent_texts['Config File Path'] = $this->input->getOption('with-config-file-path');
} }
@@ -152,8 +179,8 @@ class BuildPHPCommand extends BuildCommand
// compile libraries // compile libraries
$builder->proveLibs($libraries); $builder->proveLibs($libraries);
// check extensions // check extensions
$builder->proveExts($extensions); $builder->proveExts($static_extensions, $shared_extensions);
// validate libs and exts // validate libs and extensions
$builder->validateLibsAndExts(); $builder->validateLibsAndExts();
// clean builds and sources // clean builds and sources
@@ -183,6 +210,12 @@ class BuildPHPCommand extends BuildCommand
// start to build // start to build
$builder->buildPHP($rule); $builder->buildPHP($rule);
// build dynamic extensions if needed
if (!empty($shared_extensions)) {
logger()->info('Building shared extensions ...');
$builder->buildSharedExts();
}
// compile stopwatch :P // compile stopwatch :P
$time = round(microtime(true) - START_TIME, 3); $time = round(microtime(true) - START_TIME, 3);
logger()->info(''); logger()->info('');
@@ -211,6 +244,12 @@ class BuildPHPCommand extends BuildCommand
$path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm"); $path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm");
logger()->info("Static php-fpm binary path{$fixed}: {$path}"); logger()->info("Static php-fpm binary path{$fixed}: {$path}");
} }
if (!empty($shared_extensions)) {
foreach ($shared_extensions as $ext) {
$path = FileSystem::convertPath("{$build_root_path}/lib/{$ext}.so");
logger()->info("Shared extension [{$ext}] path{$fixed}: {$path}");
}
}
// export metadata // export metadata
file_put_contents(BUILD_ROOT_PATH . '/build-extensions.json', json_encode($extensions, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); file_put_contents(BUILD_ROOT_PATH . '/build-extensions.json', json_encode($extensions, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
@@ -239,13 +278,13 @@ class BuildPHPCommand extends BuildCommand
/** /**
* Parse build options to rule int. * Parse build options to rule int.
*/ */
private function parseRules(): int private function parseRules(array $shared_extensions = []): int
{ {
$rule = BUILD_TARGET_NONE; $rule = BUILD_TARGET_NONE;
$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') ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-embed') || !empty($shared_extensions) ? 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

@@ -7,6 +7,7 @@ namespace SPC\command;
use SPC\exception\DownloaderException; use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Downloader;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
@@ -47,30 +48,35 @@ class DeleteDownloadCommand extends BaseCommand
$chosen_sources = $sources; $chosen_sources = $sources;
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$deleted_sources = [];
foreach ($chosen_sources as $source) { foreach ($chosen_sources as $source) {
$source = trim($source); $source = trim($source);
if (!isset($lock[$source])) { foreach ([$source, Downloader::getPreBuiltLockName($source)] as $name) {
logger()->warning("Source/Package [{$source}] not locked or not downloaded, skipped."); if (isset($lock[$name])) {
continue; $deleted_sources[] = $name;
}
} }
}
foreach ($deleted_sources as $lock_name) {
// remove download file/dir if exists // remove download file/dir if exists
if ($lock[$source]['source_type'] === 'archive') { if ($lock[$lock_name]['source_type'] === 'archive') {
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$source]['filename']))) { if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']))) {
logger()->info('Deleting file ' . $path); logger()->info('Deleting file ' . $path);
unlink($path); unlink($path);
} else { } else {
logger()->warning("Source/Package [{$source}] 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[$source]['dirname']))) { if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname']))) {
logger()->info('Deleting dir ' . $path); logger()->info('Deleting dir ' . $path);
FileSystem::removeDir($path); FileSystem::removeDir($path);
} else { } else {
logger()->warning("Source/Package [{$source}] directory not found, skip deleting dir."); logger()->warning("Source/Package [{$lock_name}] directory not found, skip deleting dir.");
} }
} }
// remove locked sources // remove locked sources
unset($lock[$source]); unset($lock[$lock_name]);
} }
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); 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!');

View File

@@ -4,6 +4,7 @@ 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;
@@ -212,7 +213,7 @@ class DownloadCommand extends BaseCommand
if (isset($config['filename'])) { if (isset($config['filename'])) {
$new_config['filename'] = $config['filename']; $new_config['filename'] = $config['filename'];
} }
logger()->info("Fetching source {$source} from custom url [{$ni}/{$cnt}]"); logger()->info("[{$ni}/{$cnt}] Downloading source {$source} from custom url: {$new_config['url']}");
Downloader::downloadSource($source, $new_config, true); Downloader::downloadSource($source, $new_config, true);
} elseif (isset($custom_gits[$source])) { } elseif (isset($custom_gits[$source])) {
$config = Config::getSource($source); $config = Config::getSource($source);
@@ -224,23 +225,30 @@ class DownloadCommand extends BaseCommand
if (isset($config['path'])) { if (isset($config['path'])) {
$new_config['path'] = $config['path']; $new_config['path'] = $config['path'];
} }
logger()->info("Fetching source {$source} from custom git [{$ni}/{$cnt}]"); logger()->info("[{$ni}/{$cnt}] Downloading source {$source} from custom git: {$new_config['url']}");
Downloader::downloadSource($source, $new_config, true); Downloader::downloadSource($source, $new_config, true);
} else { } else {
$config = Config::getSource($source); $config = Config::getSource($source);
// Prefer pre-built, we need to search pre-built library // Prefer pre-built, we need to search pre-built library
if ($this->getOption('prefer-pre-built') && ($config['provide-pre-built'] ?? false) === true) { if ($this->getOption('prefer-pre-built') && ($config['provide-pre-built'] ?? false) === true) {
// We need to replace pattern // We need to replace pattern
$find = str_replace(['{name}', '{arch}', '{os}'], [$source, arch2gnu(php_uname('m')), strtolower(PHP_OS_FAMILY)], Config::getPreBuilt('match-pattern')); $replace = [
'{name}' => $source,
'{arch}' => arch2gnu(php_uname('m')),
'{os}' => strtolower(PHP_OS_FAMILY),
'{libc}' => getenv('SPC_LIBC') ?: 'default',
'{libcver}' => PHP_OS_FAMILY === 'Linux' ? (SystemUtil::getLibcVersionIfExists() ?? 'default') : 'default',
];
$find = str_replace(array_keys($replace), array_values($replace), Config::getPreBuilt('match-pattern'));
// find filename in asset list // find filename in asset list
if (($url = $this->findPreBuilt($pre_built_libs, $find)) !== null) { if (($url = $this->findPreBuilt($pre_built_libs, $find)) !== null) {
logger()->info("Fetching pre-built content {$source} [{$ni}/{$cnt}]"); logger()->info("[{$ni}/{$cnt}] Downloading pre-built content {$source}");
Downloader::downloadSource($source, ['type' => 'url', 'url' => $url], $force_all || in_array($source, $force_list), SPC_LOCK_PRE_BUILT); Downloader::downloadSource($source, ['type' => 'url', 'url' => $url], $force_all || in_array($source, $force_list), SPC_DOWNLOAD_PRE_BUILT);
continue; continue;
} }
logger()->warning("Pre-built content not found for {$source}, fallback to source download"); logger()->warning("Pre-built content not found for {$source}, fallback to source download");
} }
logger()->info("Fetching source {$source} [{$ni}/{$cnt}]"); logger()->info("[{$ni}/{$cnt}] Downloading source {$source}");
Downloader::downloadSource($source, $config, $force_all || in_array($source, $force_list)); Downloader::downloadSource($source, $config, $force_all || in_array($source, $force_list));
} }
} }
@@ -352,6 +360,7 @@ class DownloadCommand extends BaseCommand
*/ */
private function findPreBuilt(array $assets, string $filename): ?string private function findPreBuilt(array $assets, string $filename): ?string
{ {
logger()->debug("Finding pre-built asset {$filename}");
foreach ($assets as $asset) { foreach ($assets as $asset) {
if ($asset['name'] === $filename) { if ($asset['name'] === $filename) {
return $asset['browser_download_url']; return $asset['browser_download_url'];

View File

@@ -20,6 +20,7 @@ class ExtractCommand extends BaseCommand
public function configure(): void public function configure(): void
{ {
$this->addArgument('sources', InputArgument::REQUIRED, 'The sources will be compiled, comma separated'); $this->addArgument('sources', InputArgument::REQUIRED, 'The sources will be compiled, comma separated');
$this->addOption('source-only', null, null, 'Only check the source exist, do not check the lib and ext');
} }
/** /**
@@ -34,7 +35,7 @@ class ExtractCommand extends BaseCommand
$this->output->writeln('<error>sources cannot be empty, at least contain one !</error>'); $this->output->writeln('<error>sources cannot be empty, at least contain one !</error>');
return static::FAILURE; return static::FAILURE;
} }
SourceManager::initSource(sources: $sources); SourceManager::initSource(sources: $sources, source_only: $this->getOption('source-only'));
logger()->info('Extract done !'); logger()->info('Extract done !');
return static::SUCCESS; return static::SUCCESS;
} }

View File

@@ -37,7 +37,7 @@ class SPCConfigCommand extends BuildCommand
$include_suggest_ext = $this->getOption('with-suggested-exts'); $include_suggest_ext = $this->getOption('with-suggested-exts');
$include_suggest_lib = $this->getOption('with-suggested-libs'); $include_suggest_lib = $this->getOption('with-suggested-libs');
$util = new SPCConfigUtil(null, $this->input); $util = new SPCConfigUtil();
$config = $util->config($extensions, $libraries, $include_suggest_ext, $include_suggest_lib); $config = $util->config($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
if ($this->getOption('includes')) { if ($this->getOption('includes')) {

View File

@@ -6,7 +6,6 @@ namespace SPC\command\dev;
use SPC\builder\BuilderProvider; use SPC\builder\BuilderProvider;
use SPC\command\BaseCommand; use SPC\command\BaseCommand;
use SPC\store\Config;
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;
@@ -31,8 +30,7 @@ class ExtVerCommand extends BaseCommand
// Get lib object // Get lib object
$builder = BuilderProvider::makeBuilderByInput($this->input); $builder = BuilderProvider::makeBuilderByInput($this->input);
$ext_conf = Config::getExt($this->getArgument('extension')); $builder->proveExts([$this->getArgument('extension')], [], true);
$builder->proveExts([$this->getArgument('extension')], true);
// Check whether lib is extracted // Check whether lib is extracted
// if (!is_dir(SOURCE_PATH . '/' . $this->getArgument('library'))) { // if (!is_dir(SOURCE_PATH . '/' . $this->getArgument('library'))) {

View File

@@ -6,6 +6,7 @@ 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;
@@ -23,6 +24,7 @@ class PackLibCommand extends BuildCommand
public function configure(): void public function configure(): void
{ {
$this->addArgument('library', InputArgument::REQUIRED, 'The library will be compiled'); $this->addArgument('library', InputArgument::REQUIRED, 'The library will be compiled');
$this->addOption('show-libc-ver', null, null);
} }
public function handle(): int public function handle(): int
@@ -47,7 +49,7 @@ class PackLibCommand extends BuildCommand
// Get lock info // Get lock info
$lock = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; $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_LOCK_SOURCE) === SPC_LOCK_PRE_BUILT) { if (!isset($lock[$source]) || ($lock[$source]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === 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;
} }
@@ -69,7 +71,16 @@ class PackLibCommand extends BuildCommand
// write list to packlib_files.txt // write list to packlib_files.txt
FileSystem::writeFile(WORKING_DIR . '/packlib_files.txt', implode("\n", $increase_files)); FileSystem::writeFile(WORKING_DIR . '/packlib_files.txt', implode("\n", $increase_files));
// pack // pack
$filename = WORKING_DIR . '/dist/' . $lib->getName() . '-' . arch2gnu(php_uname('m')) . '-' . strtolower(PHP_OS_FAMILY) . '.' . Config::getPreBuilt('suffix'); $filename = Config::getPreBuilt('match-pattern');
$replace = [
'{name}' => $lib->getName(),
'{arch}' => arch2gnu(php_uname('m')),
'{os}' => strtolower(PHP_OS_FAMILY),
'{libc}' => getenv('SPC_LIBC') ?: 'default',
'{libcver}' => PHP_OS_FAMILY === 'Linux' ? (SystemUtil::getLibcVersionIfExists() ?? 'default') : 'default',
];
$filename = str_replace(array_keys($replace), array_values($replace), $filename);
$filename = WORKING_DIR . '/dist/' . $filename;
f_passthru('tar -czf ' . $filename . ' -T ' . WORKING_DIR . '/packlib_files.txt'); f_passthru('tar -czf ' . $filename . ' -T ' . WORKING_DIR . '/packlib_files.txt');
logger()->info('Pack library ' . $lib->getName() . ' to ' . $filename . ' complete.'); logger()->info('Pack library ' . $lib->getName() . ' to ' . $filename . ' complete.');
} }

View File

@@ -22,11 +22,30 @@ class Config
public static ?array $pre_built = null; public static ?array $pre_built = null;
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public static function getPreBuilt(string $name): mixed public static function getPreBuilt(string $name): mixed
{ {
if (self::$pre_built === null) { if (self::$pre_built === null) {
self::$pre_built = FileSystem::loadConfigArray('pre-built'); self::$pre_built = FileSystem::loadConfigArray('pre-built');
} }
$supported_sys_based = ['match-pattern', 'prefer-stable', 'repo'];
if (in_array($name, $supported_sys_based)) {
$m_key = match (PHP_OS_FAMILY) {
'Windows' => ['-windows', '-win', ''],
'Darwin' => ['-macos', '-unix', ''],
'Linux' => ['-linux', '-unix', ''],
'BSD' => ['-freebsd', '-bsd', '-unix', ''],
default => throw new WrongUsageException('OS ' . PHP_OS_FAMILY . ' is not supported'),
};
foreach ($m_key as $v) {
if (isset(self::$pre_built["{$name}{$v}"])) {
return self::$pre_built["{$name}{$v}"];
}
}
}
return self::$pre_built[$name] ?? null; return self::$pre_built[$name] ?? null;
} }
@@ -106,6 +125,21 @@ class Config
return self::$lib; return self::$lib;
} }
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public static function getExtTarget(string $name): ?array
{
if (self::$ext === null) {
self::$ext = FileSystem::loadConfigArray('ext');
}
if (!isset(self::$ext[$name])) {
throw new WrongUsageException('ext [' . $name . '] is not supported yet');
}
return self::$ext[$name]['target'] ?? ['static', 'shared'];
}
/** /**
* @throws FileSystemException * @throws FileSystemException
* @throws WrongUsageException * @throws WrongUsageException

View File

@@ -4,6 +4,7 @@ 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;
@@ -69,14 +70,19 @@ class Downloader
retry: self::getRetryTime() retry: self::getRetryTime()
), true); ), true);
if (($source['prefer-stable'] ?? false) === false) { $url = null;
$url = $data[0]['tarball_url']; for ($i = 0; $i < count($data); ++$i) {
} else { if (($data[$i]['prerelease'] ?? false) === true && ($source['prefer-stable'] ?? false)) {
$id = 0; continue;
while ($data[$id]['prerelease'] === true) { }
++$id; if (!($source['match'] ?? null)) {
$url = $data[$i]['tarball_url'] ?? null;
break;
}
if (preg_match('|' . $source['match'] . '|', $data[$i]['tarball_url'])) {
$url = $data[$i]['tarball_url'];
break;
} }
$url = $data[$id]['tarball_url'] ?? null;
} }
if (!$url) { if (!$url) {
throw new DownloaderException("failed to find {$name} source"); throw new DownloaderException("failed to find {$name} source");
@@ -119,6 +125,7 @@ class Downloader
if (($source['prefer-stable'] ?? false) === true && $release['prerelease'] === true) { if (($source['prefer-stable'] ?? false) === true && $release['prerelease'] === true) {
continue; continue;
} }
logger()->debug("Found {$release['name']} releases assets");
if (!$match_result) { if (!$match_result) {
return $release['assets']; return $release['assets'];
} }
@@ -184,7 +191,7 @@ class Downloader
* @throws RuntimeException * @throws RuntimeException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null, int $lock_as = SPC_LOCK_SOURCE): void public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null, int $download_as = SPC_DOWNLOAD_SOURCE): void
{ {
logger()->debug("Downloading {$url}"); logger()->debug("Downloading {$url}");
$cancel_func = function () use ($filename) { $cancel_func = function () use ($filename) {
@@ -197,12 +204,23 @@ class Downloader
self::curlDown(url: $url, path: FileSystem::convertPath(DOWNLOAD_PATH . "/{$filename}"), retry: self::getRetryTime()); self::curlDown(url: $url, path: FileSystem::convertPath(DOWNLOAD_PATH . "/{$filename}"), retry: self::getRetryTime());
self::unregisterCancelEvent(); self::unregisterCancelEvent();
logger()->debug("Locking {$filename}"); logger()->debug("Locking {$filename}");
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $lock_as]); if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
$name = self::getPreBuiltLockName($name);
}
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
} }
/** /**
* Try to lock source. * 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 * @throws FileSystemException
*/ */
public static function lockSource(string $name, array $data): void public static function lockSource(string $name, array $data): void
@@ -223,7 +241,7 @@ class Downloader
* @throws RuntimeException * @throws RuntimeException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null, int $retry = 0, int $lock_as = SPC_LOCK_SOURCE): void public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null, int $retry = 0, int $lock_as = SPC_DOWNLOAD_SOURCE): void
{ {
$download_path = FileSystem::convertPath(DOWNLOAD_PATH . "/{$name}"); $download_path = FileSystem::convertPath(DOWNLOAD_PATH . "/{$name}");
if (file_exists($download_path)) { if (file_exists($download_path)) {
@@ -241,6 +259,7 @@ class Downloader
self::registerCancelEvent($cancel_func); self::registerCancelEvent($cancel_func);
f_passthru( f_passthru(
SPC_GIT_EXEC . ' clone' . $check . SPC_GIT_EXEC . ' clone' . $check .
(defined('DEBUG_MODE') ? '' : ' --quiet') .
' --config core.autocrlf=false ' . ' --config core.autocrlf=false ' .
"--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\"" "--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\""
); );
@@ -278,8 +297,22 @@ class Downloader
} }
/** /**
* @param string $name Package name
* @param null|array{
* type: string,
* repo: ?string,
* url: ?string,
* rev: ?string,
* path: ?string,
* filename: ?string,
* match: ?string,
* prefer-stable: ?bool,
* extract-files: ?array<string, string>
* } $pkg Package config
* @param bool $force Download all the time even if it exists
* @throws DownloaderException * @throws DownloaderException
* @throws FileSystemException * @throws FileSystemException
* @throws WrongUsageException
*/ */
public static function downloadPackage(string $name, ?array $pkg = null, bool $force = false): void public static function downloadPackage(string $name, ?array $pkg = null, bool $force = false): void
{ {
@@ -296,50 +329,36 @@ class Downloader
FileSystem::createDir(DOWNLOAD_PATH); FileSystem::createDir(DOWNLOAD_PATH);
} }
// load lock file if (self::isAlreadyDownloaded($name, $force, SPC_DOWNLOAD_PACKAGE)) {
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) { return;
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading
if (isset($lock[$name]) && !$force) {
if ($lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename'])) {
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['filename']);
return;
}
if ($lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])) {
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['dirname']);
return;
}
} }
try { try {
switch ($pkg['type']) { switch ($pkg['type']) {
case 'bitbuckettag': // BitBucket Tag case 'bitbuckettag': // BitBucket Tag
[$url, $filename] = self::getLatestBitbucketTag($name, $pkg); [$url, $filename] = self::getLatestBitbucketTag($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT); self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE);
break; break;
case 'ghtar': // GitHub Release (tar) case 'ghtar': // GitHub Release (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $pkg); [$url, $filename] = self::getLatestGithubTarball($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT); self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE);
break; break;
case 'ghtagtar': // GitHub Tag (tar) case 'ghtagtar': // GitHub Tag (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $pkg, 'tags'); [$url, $filename] = self::getLatestGithubTarball($name, $pkg, 'tags');
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT); self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE);
break; break;
case 'ghrel': // GitHub Release (uploaded) case 'ghrel': // GitHub Release (uploaded)
[$url, $filename] = self::getLatestGithubRelease($name, $pkg); [$url, $filename] = self::getLatestGithubRelease($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT); self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE);
break; break;
case 'filelist': // Basic File List (regex based crawler) case 'filelist': // Basic File List (regex based crawler)
[$url, $filename] = self::getFromFileList($name, $pkg); [$url, $filename] = self::getFromFileList($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT); self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE);
break; break;
case 'url': // Direct download URL case 'url': // Direct download URL
$url = $pkg['url']; $url = $pkg['url'];
$filename = $pkg['filename'] ?? basename($pkg['url']); $filename = $pkg['filename'] ?? basename($pkg['url']);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT); self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE);
break; break;
case 'git': // Git repo case 'git': // Git repo
self::downloadGit( self::downloadGit(
@@ -348,7 +367,7 @@ class Downloader
$pkg['rev'], $pkg['rev'],
$pkg['extract'] ?? null, $pkg['extract'] ?? null,
self::getRetryTime(), self::getRetryTime(),
SPC_LOCK_PRE_BUILT SPC_DOWNLOAD_PRE_BUILT
); );
break; break;
case 'custom': // Custom download method, like API-based download or other case 'custom': // Custom download method, like API-based download or other
@@ -377,15 +396,30 @@ class Downloader
/** /**
* Download source by name and meta. * Download source by name and meta.
* *
* @param string $name source name * @param string $name source name
* @param null|array $source source meta info: [type, path, rev, url, filename, regex, license] * @param null|array{
* @param bool $force Whether to force download (default: false) * type: string,
* @param int $lock_as Lock source type (default: SPC_LOCK_SOURCE) * repo: ?string,
* url: ?string,
* rev: ?string,
* path: ?string,
* filename: ?string,
* match: ?string,
* prefer-stable: ?bool,
* provide-pre-built: ?bool,
* license: array{
* type: string,
* path: ?string,
* text: ?string
* }
* } $source source meta info: [type, path, rev, url, filename, regex, license]
* @param bool $force Whether to force download (default: false)
* @param int $download_as Lock source type (default: SPC_LOCK_SOURCE)
* @throws DownloaderException * @throws DownloaderException
* @throws FileSystemException * @throws FileSystemException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
public static function downloadSource(string $name, ?array $source = null, bool $force = false, int $lock_as = SPC_LOCK_SOURCE): void public static function downloadSource(string $name, ?array $source = null, bool $force = false, int $download_as = SPC_DOWNLOAD_SOURCE): void
{ {
if ($source === null) { if ($source === null) {
$source = Config::getSource($name); $source = Config::getSource($name);
@@ -401,49 +435,36 @@ class Downloader
} }
// load lock file // load lock file
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) { if (self::isAlreadyDownloaded($name, $force, $download_as)) {
$lock = []; return;
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading
if (isset($lock[$name]) && !$force && ($lock[$name]['lock_as'] ?? SPC_LOCK_SOURCE) === $lock_as) {
if ($lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename'])) {
logger()->notice("source [{$name}] already downloaded: " . $lock[$name]['filename']);
return;
}
if ($lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])) {
logger()->notice("source [{$name}] already downloaded: " . $lock[$name]['dirname']);
return;
}
} }
try { try {
switch ($source['type']) { switch ($source['type']) {
case 'bitbuckettag': // BitBucket Tag case 'bitbuckettag': // BitBucket Tag
[$url, $filename] = self::getLatestBitbucketTag($name, $source); [$url, $filename] = self::getLatestBitbucketTag($name, $source);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as); self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break; break;
case 'ghtar': // GitHub Release (tar) case 'ghtar': // GitHub Release (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $source); [$url, $filename] = self::getLatestGithubTarball($name, $source);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as); self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break; break;
case 'ghtagtar': // GitHub Tag (tar) case 'ghtagtar': // GitHub Tag (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $source, 'tags'); [$url, $filename] = self::getLatestGithubTarball($name, $source, 'tags');
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as); self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break; break;
case 'ghrel': // GitHub Release (uploaded) case 'ghrel': // GitHub Release (uploaded)
[$url, $filename] = self::getLatestGithubRelease($name, $source); [$url, $filename] = self::getLatestGithubRelease($name, $source);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as); self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break; break;
case 'filelist': // Basic File List (regex based crawler) case 'filelist': // Basic File List (regex based crawler)
[$url, $filename] = self::getFromFileList($name, $source); [$url, $filename] = self::getFromFileList($name, $source);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as); self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break; break;
case 'url': // Direct download URL case 'url': // Direct download URL
$url = $source['url']; $url = $source['url'];
$filename = $source['filename'] ?? basename($source['url']); $filename = $source['filename'] ?? basename($source['url']);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as); self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break; break;
case 'git': // Git repo case 'git': // Git repo
self::downloadGit( self::downloadGit(
@@ -452,14 +473,14 @@ class Downloader
$source['rev'], $source['rev'],
$source['path'] ?? null, $source['path'] ?? null,
self::getRetryTime(), self::getRetryTime(),
$lock_as $download_as
); );
break; 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/source', 'SPC\store\source');
foreach ($classes as $class) { foreach ($classes as $class) {
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) { if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
(new $class())->fetch($force, $source, $lock_as); (new $class())->fetch($force, $source, $download_as);
break; break;
} }
} }
@@ -574,6 +595,11 @@ class Downloader
} }
} }
public static function getPreBuiltLockName(string $source): string
{
return "{$source}-" . PHP_OS_FAMILY . '-' . getenv('GNU_ARCH') . '-' . (getenv('SPC_LIBC') ?: 'default') . '-' . (SystemUtil::getLibcVersionIfExists() ?? 'default');
}
/** /**
* Register CTRL+C event for different OS. * Register CTRL+C event for different OS.
* *
@@ -606,4 +632,39 @@ class Downloader
{ {
return intval(getenv('SPC_RETRY_TIME') ? getenv('SPC_RETRY_TIME') : 0); return intval(getenv('SPC_RETRY_TIME') ? getenv('SPC_RETRY_TIME') : 0);
} }
/**
* @throws FileSystemException
*/
private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool
{
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading for source mode
if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && isset($lock[$name])) {
if (
$lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename']) ||
$lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])
) {
logger()->notice("Source [{$name}] already downloaded: " . ($lock[$name]['filename'] ?? $lock[$name]['dirname']));
return true;
}
}
// If lock file exists for current arch and glibc target, skip downloading
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && isset($lock[$lock_name = self::getPreBuiltLockName($name)])) {
// lock name with env
if (
$lock[$lock_name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']) ||
$lock[$lock_name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname'])
) {
logger()->notice("Pre-built content [{$name}] already downloaded: " . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname']));
return true;
}
}
return false;
}
} }

View File

@@ -461,6 +461,23 @@ class FileSystem
} }
} }
/**
* @throws FileSystemException
*/
public static function replaceFileLineContainsString(string $file, string $find, string $line): false|int
{
$lines = file($file);
if ($lines === false) {
throw new FileSystemException('Cannot read file: ' . $file);
}
foreach ($lines as $key => $value) {
if (str_contains($value, $find)) {
$lines[$key] = $line . PHP_EOL;
}
}
return file_put_contents($file, implode('', $lines));
}
/** /**
* @throws RuntimeException * @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException

View File

@@ -15,7 +15,7 @@ class SourceManager
* @throws FileSystemException * @throws FileSystemException
* @throws RuntimeException * @throws RuntimeException
*/ */
public static function initSource(?array $sources = null, ?array $libs = null, ?array $exts = null): 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')) { 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 ?'); throw new WrongUsageException('Download lock file "downloads/.lock.json" not found, maybe you need to download sources first ?');
@@ -54,15 +54,22 @@ class SourceManager
if (Config::getSource($source) === null) { if (Config::getSource($source) === null) {
throw new WrongUsageException("Source [{$source}] does not exist, please check the name and correct it !"); throw new WrongUsageException("Source [{$source}] does not exist, please check the name and correct it !");
} }
if (!isset($lock[$source])) { // check source downloaded
throw new WrongUsageException('Source [' . $source . '] not downloaded or not locked, you should download it first !'); $pre_built_name = Downloader::getPreBuiltLockName($source);
if ($source_only || !isset($lock[$pre_built_name])) {
if (!isset($lock[$source])) {
throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !");
}
$lock_name = $source;
} else {
$lock_name = $pre_built_name;
} }
// check source dir exist // check source dir exist
$check = $lock[$source]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$source]['move_path']); $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[$source]['filename'] ?? $lock[$source]['dirname']), $lock[$source]['move_path']); FileSystem::extractSource($source, DOWNLOAD_PATH . '/' . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname']), $lock[$lock_name]['move_path']);
} else { } else {
logger()->debug('Source [' . $source . '] already extracted in ' . $check . ', skip !'); logger()->debug('Source [' . $source . '] already extracted in ' . $check . ', skip !');
} }

View File

@@ -43,7 +43,7 @@ class SourcePatcher
*/ */
public static function patchBeforeBuildconf(BuilderBase $builder): void public static function patchBeforeBuildconf(BuilderBase $builder): void
{ {
foreach ($builder->getExts() as $ext) { foreach ($builder->getExts(false) 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');
} }
@@ -86,7 +86,7 @@ class SourcePatcher
*/ */
public static function patchBeforeConfigure(BuilderBase $builder): void public static function patchBeforeConfigure(BuilderBase $builder): void
{ {
foreach ($builder->getExts() as $ext) { foreach ($builder->getExts(false) 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');
} }
@@ -253,7 +253,7 @@ class SourcePatcher
// } // }
// call extension patch before make // call extension patch before make
foreach ($builder->getExts() 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');
} }

View File

@@ -8,5 +8,5 @@ abstract class CustomSourceBase
{ {
public const NAME = 'unknown'; public const NAME = 'unknown';
abstract public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_LOCK_SOURCE): void; abstract public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void;
} }

View File

@@ -17,7 +17,7 @@ class PhpSource extends CustomSourceBase
* @throws DownloaderException * @throws DownloaderException
* @throws FileSystemException * @throws FileSystemException
*/ */
public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_LOCK_SOURCE): void public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void
{ {
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.3'; $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.3';
Downloader::downloadSource('php-src', self::getLatestPHPInfo($major), $force); Downloader::downloadSource('php-src', self::getLatestPHPInfo($major), $force);

View File

@@ -16,7 +16,7 @@ class PostgreSQLSource extends CustomSourceBase
* @throws DownloaderException * @throws DownloaderException
* @throws FileSystemException * @throws FileSystemException
*/ */
public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_LOCK_SOURCE): void public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void
{ {
Downloader::downloadSource('postgresql', self::getLatestInfo(), $force); Downloader::downloadSource('postgresql', self::getLatestInfo(), $force);
} }

View File

@@ -40,9 +40,6 @@ class GlobalEnvManager
self::putenv('PATH=' . BUILD_ROOT_PATH . '/bin:' . getenv('PATH')); self::putenv('PATH=' . BUILD_ROOT_PATH . '/bin:' . getenv('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');
if ($builder instanceof BuilderBase) {
self::putenv('SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS=' . ($builder->getOption('no-strip') ? '-g -O0' : '-g -fstack-protector-strong -fpic -fpie -Os -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64'));
}
} }
// Define env vars for linux // Define env vars for linux

View File

@@ -7,26 +7,51 @@ namespace SPC\util;
use SPC\builder\BuilderBase; use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider; use SPC\builder\BuilderProvider;
use SPC\builder\macos\MacOSBuilder; use SPC\builder\macos\MacOSBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
class SPCConfigUtil class SPCConfigUtil
{ {
public function __construct(private ?BuilderBase $builder = null, ?InputInterface $input = null) private ?BuilderBase $builder = null;
public function __construct(?BuilderBase $builder = null)
{ {
if ($builder === null) { if ($builder !== null) {
$this->builder = BuilderProvider::makeBuilderByInput($input ?? new ArgvInput()); $this->builder = $builder; // BuilderProvider::makeBuilderByInput($input ?? new ArgvInput());
} }
} }
/**
* Generate configuration for building PHP extensions.
*
* @param array $extensions Extension name list
* @param array $libraries Additional library name list
* @param bool $include_suggest_ext Include suggested extensions
* @param bool $include_suggest_lib Include suggested libraries
* @return array{
* cflags: string,
* ldflags: string,
* libs: string
* }
* @throws \ReflectionException
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
* @throws \Throwable
*/
public function config(array $extensions = [], array $libraries = [], bool $include_suggest_ext = false, bool $include_suggest_lib = false): array public function config(array $extensions = [], array $libraries = [], bool $include_suggest_ext = false, bool $include_suggest_lib = false): array
{ {
[$extensions, $libraries] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib); [$extensions, $libraries] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
ob_start(); ob_start();
$this->builder->proveLibs($libraries); if ($this->builder === null) {
$this->builder->proveExts($extensions); $this->builder = BuilderProvider::makeBuilderByInput(new ArgvInput());
$this->builder->proveLibs($libraries);
$this->builder->proveExts($extensions, skip_extract: true);
}
ob_get_clean(); ob_get_clean();
$ldflags = $this->getLdflagsString(); $ldflags = $this->getLdflagsString();
$libs = $this->getLibsString($libraries); $libs = $this->getLibsString($libraries);
@@ -42,10 +67,14 @@ class SPCConfigUtil
if ($this->builder->hasCpp()) { if ($this->builder->hasCpp()) {
$libs .= $this->builder instanceof MacOSBuilder ? ' -lc++' : ' -lstdc++'; $libs .= $this->builder instanceof MacOSBuilder ? ' -lc++' : ' -lstdc++';
} }
// mimalloc must come first
if (str_contains($libs, BUILD_LIB_PATH . '/mimalloc.o')) {
$libs = BUILD_LIB_PATH . '/mimalloc.o ' . str_replace(BUILD_LIB_PATH . '/mimalloc.o', '', $libs);
}
return [ return [
'cflags' => $cflags, 'cflags' => trim(getenv('CFLAGS') . ' ' . $cflags),
'ldflags' => $ldflags, 'ldflags' => trim(getenv('LDFLAGS') . ' ' . $ldflags),
'libs' => $libs, 'libs' => trim(getenv('LIBS') . ' ' . $libs),
]; ];
} }
@@ -86,8 +115,8 @@ class SPCConfigUtil
} }
} }
} }
// patch: imagick (imagemagick wrapper) for linux needs -lgomp // patch: imagick (imagemagick wrapper) for linux needs libgomp
if (in_array('imagemagick', $libraries) && PHP_OS_FAMILY === 'Linux') { if (in_array('imagemagick', $libraries) && PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') {
$short_name[] = '-lgomp'; $short_name[] = '-lgomp';
} }
return implode(' ', $short_name); return implode(' ', $short_name);

View File

@@ -41,8 +41,9 @@ const SPC_EXTENSION_ALIAS = [
]; ];
// spc lock type // spc lock type
const SPC_LOCK_SOURCE = 1; // lock source const SPC_DOWNLOAD_SOURCE = 1; // lock source
const SPC_LOCK_PRE_BUILT = 2; // lock pre-built const SPC_DOWNLOAD_PRE_BUILT = 2; // lock pre-built
const SPC_DOWNLOAD_PACKAGE = 3; // lock as package
// file replace strategy // file replace strategy
const REPLACE_FILE_STR = 1; const REPLACE_FILE_STR = 1;

View File

@@ -3,3 +3,16 @@
declare(strict_types=1); declare(strict_types=1);
assert(function_exists('curl_init')); assert(function_exists('curl_init'));
assert(function_exists('curl_setopt'));
assert(function_exists('curl_exec'));
assert(function_exists('curl_close'));
$curl_version = curl_version();
if (stripos($curl_version['ssl_version'], 'schannel') !== false) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'https://example.com/');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HEADER, 0);
$data = curl_exec($curl);
curl_close($curl);
assert($data !== false);
}

View File

@@ -6,15 +6,15 @@ declare(strict_types=1);
/** /**
* This is GitHub Actions automatic test extension args generator. * This is GitHub Actions automatic test extension args generator.
* You can edit $extensions, $with_libs and $base_combination. * You can edit $test_php_version, $test_os, $zts, $no_strip, $upx, $prefer_pre_built, $extensions, $with_libs and $base_combination.
*/ */
// --------------------------------- edit area --------------------------------- // --------------------------------- edit area ---------------------------------
// test php version // test php version (8.1 ~ 8.4 available, multiple for matrix)
$test_php_version = [ $test_php_version = [
// '8.1', '8.1',
// '8.2', '8.2',
'8.3', '8.3',
'8.4', '8.4',
]; ];
@@ -24,11 +24,15 @@ $test_os = [
'macos-13', 'macos-13',
'macos-14', 'macos-14',
'ubuntu-latest', 'ubuntu-latest',
// 'windows-latest', 'ubuntu-22.04',
'ubuntu-24.04',
'ubuntu-22.04-arm',
'ubuntu-24.04-arm',
'windows-latest',
]; ];
// whether enable thread safe // whether enable thread safe
$zts = false; $zts = true;
$no_strip = false; $no_strip = false;
@@ -40,13 +44,19 @@ $prefer_pre_built = false;
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`). // If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
$extensions = match (PHP_OS_FAMILY) { $extensions = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'gd', 'Linux', 'Darwin' => 'ev',
'Windows' => 'bcmath', 'Windows' => 'ev',
};
// If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`).
$shared_extensions = match (PHP_OS_FAMILY) {
'Linux' => 'xdebug',
'Windows', 'Darwin' => '',
}; };
// If you want to test lib-suggests feature with extension, add them below (comma separated, example `libwebp,libavif`). // If you want to test lib-suggests feature with extension, add them below (comma separated, example `libwebp,libavif`).
$with_libs = match (PHP_OS_FAMILY) { $with_libs = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'freetype', 'Linux', 'Darwin' => '',
'Windows' => '', 'Windows' => '',
}; };
@@ -85,6 +95,7 @@ if (!isset($argv[1])) {
$trim_value = "\r\n \t,"; $trim_value = "\r\n \t,";
$final_extensions = trim(trim($extensions, $trim_value) . ',' . _getCombination($base_combination), $trim_value); $final_extensions = trim(trim($extensions, $trim_value) . ',' . _getCombination($base_combination), $trim_value);
$download_extensions = trim($final_extensions . ',' . $shared_extensions, $trim_value);
$final_libs = trim($with_libs, $trim_value); $final_libs = trim($with_libs, $trim_value);
if (PHP_OS_FAMILY === 'Windows') { if (PHP_OS_FAMILY === 'Windows') {
@@ -105,7 +116,7 @@ function quote2(string $param): string
// generate download command // generate download command
if ($argv[1] === 'download_cmd') { if ($argv[1] === 'download_cmd') {
$down_cmd = 'download '; $down_cmd = 'download ';
$down_cmd .= '--for-extensions=' . quote2($final_extensions) . ' '; $down_cmd .= '--for-extensions=' . quote2($download_extensions) . ' ';
$down_cmd .= '--for-libs=' . quote2($final_libs) . ' '; $down_cmd .= '--for-libs=' . quote2($final_libs) . ' ';
$down_cmd .= '--with-php=' . quote2($argv[3]) . ' '; $down_cmd .= '--with-php=' . quote2($argv[3]) . ' ';
$down_cmd .= '--ignore-cache-sources=php-src '; $down_cmd .= '--ignore-cache-sources=php-src ';
@@ -115,10 +126,46 @@ if ($argv[1] === 'download_cmd') {
$down_cmd .= $prefer_pre_built ? '--prefer-pre-built ' : ''; $down_cmd .= $prefer_pre_built ? '--prefer-pre-built ' : '';
} }
if ($argv[1] === 'doctor_cmd') {
$doctor_cmd = 'doctor --auto-fix --debug';
}
if ($argv[1] === 'install_upx_cmd') {
$install_upx_cmd = 'install-pkg upx';
}
$prefix = match ($argv[2] ?? null) {
'windows-latest', 'windows-2022', 'windows-2019', 'windows-2025' => 'powershell.exe -file .\bin\spc.ps1 ',
'ubuntu-latest' => 'bin/spc-alpine-docker ',
'ubuntu-24.04', 'ubuntu-24.04-arm' => './bin/spc ',
'ubuntu-22.04', 'ubuntu-22.04-arm' => 'bin/spc-gnu-docker ',
default => 'bin/spc ',
};
// shared_extension build
if ($shared_extensions) {
switch ($argv[2] ?? null) {
case 'ubuntu-22.04':
case 'ubuntu-22.04-arm':
$shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
break;
case 'macos-13':
case 'macos-14':
$shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
$no_strip = true;
break;
default:
$shared_cmd = '';
break;
}
} else {
$shared_cmd = '';
}
// generate build command // generate build command
if ($argv[1] === 'build_cmd' || $argv[1] === 'build_embed_cmd') { if ($argv[1] === 'build_cmd' || $argv[1] === 'build_embed_cmd') {
$build_cmd = 'build '; $build_cmd = 'build ';
$build_cmd .= quote2($final_extensions) . ' '; $build_cmd .= quote2($final_extensions) . ' ';
$build_cmd .= $shared_cmd;
$build_cmd .= $zts ? '--enable-zts ' : ''; $build_cmd .= $zts ? '--enable-zts ' : '';
$build_cmd .= $no_strip ? '--no-strip ' : ''; $build_cmd .= $no_strip ? '--no-strip ' : '';
$build_cmd .= $upx ? '--with-upx-pack ' : ''; $build_cmd .= $upx ? '--with-upx-pack ' : '';
@@ -139,32 +186,32 @@ echo match ($argv[1]) {
'upx' => $upx ? '--with-upx-pack' : '', 'upx' => $upx ? '--with-upx-pack' : '',
'prefer_pre_built' => $prefer_pre_built ? '--prefer-pre-built' : '', 'prefer_pre_built' => $prefer_pre_built ? '--prefer-pre-built' : '',
'download_cmd' => $down_cmd, 'download_cmd' => $down_cmd,
'install_upx_cmd' => $install_upx_cmd,
'doctor_cmd' => $doctor_cmd,
'build_cmd' => $build_cmd, 'build_cmd' => $build_cmd,
'build_embed_cmd' => $build_cmd, 'build_embed_cmd' => $build_cmd,
default => '', default => '',
}; };
if ($argv[1] === 'download_cmd') { switch ($argv[1] ?? null) {
if (str_starts_with($argv[2], 'windows-')) { case 'download_cmd':
passthru('powershell.exe -file .\bin\spc.ps1 ' . $down_cmd, $retcode); passthru($prefix . $down_cmd, $retcode);
} else { break;
passthru('./bin/spc ' . $down_cmd, $retcode); case 'build_cmd':
} passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode);
} elseif ($argv[1] === 'build_cmd') { break;
if (str_starts_with($argv[2], 'windows-')) { case 'build_embed_cmd':
passthru('powershell.exe -file .\bin\spc.ps1 ' . $build_cmd . ' --build-cli --build-micro', $retcode); passthru($prefix . $build_cmd . (str_starts_with($argv[2], 'windows-') ? ' --build-cli' : ' --build-embed'), $retcode);
} else { break;
passthru('./bin/spc ' . $build_cmd . ' --build-cli --build-micro', $retcode); case 'doctor_cmd':
} passthru($prefix . $doctor_cmd, $retcode);
} elseif ($argv[1] === 'build_embed_cmd') { break;
if (str_starts_with($argv[2], 'windows-')) { case 'install_upx_cmd':
// windows does not accept embed SAPI passthru($prefix . $install_upx_cmd, $retcode);
passthru('powershell.exe -file .\bin\spc.ps1 ' . $build_cmd . ' --build-cli', $retcode); break;
} else { default:
passthru('./bin/spc ' . $build_cmd . ' --build-embed', $retcode); $retcode = 0;
} break;
} else {
$retcode = 0;
} }
exit($retcode); exit($retcode);

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace SPC\Tests;
use PHPUnit\Framework\TestCase;
/**
* @internal
*/
class GlobalDefinesTest extends TestCase
{
public function testGlobalDefines(): void
{
require __DIR__ . '/../../src/globals/defines.php';
$this->assertTrue(defined('WORKING_DIR'));
}
public function testInternalEnv(): void
{
require __DIR__ . '/../../src/globals/internal-env.php';
$this->assertTrue(defined('GNU_ARCH'));
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace SPC\Tests;
use PHPUnit\Framework\TestCase;
use SPC\exception\InterruptException;
/**
* @internal
*/
class GlobalFunctionsTest extends TestCase
{
public function testMatchPattern(): void
{
$this->assertEquals('abc', match_pattern('a*c', 'abc'));
$this->assertFalse(match_pattern('a*c', 'abcd'));
}
public function testFExec(): void
{
$this->assertEquals('abc', f_exec('echo abc', $out, $ret));
$this->assertEquals(0, $ret);
$this->assertEquals(['abc'], $out);
}
public function testPatchPointInterrupt(): void
{
$except = patch_point_interrupt(0);
$this->assertInstanceOf(InterruptException::class, $except);
}
}

View File

@@ -72,7 +72,7 @@ class BuilderTest extends TestCase
public function testMakeExtensionArgs() public function testMakeExtensionArgs()
{ {
$this->assertStringContainsString('--enable-mbstring', $this->builder->makeExtensionArgs()); $this->assertStringContainsString('--enable-mbstring', $this->builder->makeStaticExtensionArgs());
} }
public function testIsLibsOnly() public function testIsLibsOnly()

View File

@@ -137,6 +137,41 @@ class ConfigValidatorTest extends TestCase
} catch (ValidationException) { } catch (ValidationException) {
$this->assertTrue(true); $this->assertTrue(true);
} }
// lib.json is broken by not assoc array
try {
ConfigValidator::validateLibs(['lib1', 'lib2'], ['source1' => [], 'source2' => []]);
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
// lib.json lib is not one of "lib", "package", "root", "target"
try {
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'type' => 'not one of']], ['source1' => [], 'source2' => []]);
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
// lib.json lib if it is "lib" or "package", it must have "source"
try {
ConfigValidator::validateLibs(['lib1' => ['type' => 'lib']], ['source1' => [], 'source2' => []]);
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
// lib.json static-libs must be a list
try {
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'static-libs-windows' => 'not list']], ['source1' => [], 'source2' => []]);
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
// lib.json frameworks must be a list
try {
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'frameworks' => 'not list']], ['source1' => [], 'source2' => []]);
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
// source must be string // source must be string
try { try {
ConfigValidator::validateLibs(['lib1' => ['source' => true]], ['source1' => [], 'source2' => []]); ConfigValidator::validateLibs(['lib1' => ['source' => true]], ['source1' => [], 'source2' => []]);
@@ -169,4 +204,11 @@ class ConfigValidatorTest extends TestCase
$this->expectException(ValidationException::class); $this->expectException(ValidationException::class);
ConfigValidator::validateExts(null); ConfigValidator::validateExts(null);
} }
public function testValidatePkgs(): void
{
ConfigValidator::validatePkgs([]);
$this->expectException(ValidationException::class);
ConfigValidator::validatePkgs(null);
}
} }

View File

@@ -16,6 +16,12 @@ final class DependencyUtilTest extends TestCase
{ {
public function testGetExtLibsByDeps(): void public function testGetExtLibsByDeps(): void
{ {
// setup
$bak = [
'source' => Config::$source,
'lib' => Config::$lib,
'ext' => Config::$ext,
];
// example // example
Config::$source = [ Config::$source = [
'test1' => [ 'test1' => [
@@ -82,6 +88,10 @@ final class DependencyUtilTest extends TestCase
$this->assertTrue($b < $a); $this->assertTrue($b < $a);
$this->assertTrue($c < $a); $this->assertTrue($c < $a);
$this->assertTrue($c < $b); $this->assertTrue($c < $b);
// restore
Config::$source = $bak['source'];
Config::$lib = $bak['lib'];
Config::$ext = $bak['ext'];
} }
public function testNotExistExtException(): void public function testNotExistExtException(): void

View File

@@ -33,6 +33,10 @@ final class LicenseDumperTest extends TestCase
public function testDumpWithSingleLicense(): void public function testDumpWithSingleLicense(): void
{ {
$bak = [
'source' => Config::$source,
'lib' => Config::$lib,
];
Config::$lib = [ Config::$lib = [
'lib-base' => ['type' => 'root'], 'lib-base' => ['type' => 'root'],
'php' => ['type' => 'root'], 'php' => ['type' => 'root'],
@@ -54,10 +58,17 @@ final class LicenseDumperTest extends TestCase
$dumper->dump(self::DIRECTORY); $dumper->dump(self::DIRECTORY);
$this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_0.txt'); $this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_0.txt');
// restore
Config::$source = $bak['source'];
Config::$lib = $bak['lib'];
} }
public function testDumpWithMultipleLicenses(): void public function testDumpWithMultipleLicenses(): void
{ {
$bak = [
'source' => Config::$source,
'lib' => Config::$lib,
];
Config::$lib = [ Config::$lib = [
'lib-base' => ['type' => 'root'], 'lib-base' => ['type' => 'root'],
'php' => ['type' => 'root'], 'php' => ['type' => 'root'],
@@ -91,5 +102,9 @@ final class LicenseDumperTest extends TestCase
$this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_0.txt'); $this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_0.txt');
$this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_1.txt'); $this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_1.txt');
$this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_2.txt'); $this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_2.txt');
// restore
Config::$source = $bak['source'];
Config::$lib = $bak['lib'];
} }
} }

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace SPC\Tests\util;
use PHPUnit\Framework\TestCase;
use SPC\builder\BuilderProvider;
use SPC\exception\FileSystemException;
use SPC\store\FileSystem;
use SPC\util\SPCConfigUtil;
use Symfony\Component\Console\Input\ArgvInput;
/**
* @internal
*/
class SPCConfigUtilTest extends TestCase
{
/**
* @throws FileSystemException
*/
public static function setUpBeforeClass(): void
{
$testdir = WORKING_DIR . '/.configtest';
FileSystem::createDir($testdir);
FileSystem::writeFile($testdir . '/lib.json', file_get_contents(ROOT_DIR . '/config/lib.json'));
FileSystem::writeFile($testdir . '/ext.json', file_get_contents(ROOT_DIR . '/config/ext.json'));
FileSystem::writeFile($testdir . '/source.json', file_get_contents(ROOT_DIR . '/config/source.json'));
FileSystem::loadConfigArray('lib', $testdir);
FileSystem::loadConfigArray('ext', $testdir);
FileSystem::loadConfigArray('source', $testdir);
}
/**
* @throws FileSystemException
*/
public static function tearDownAfterClass(): void
{
FileSystem::removeDir(WORKING_DIR . '/.configtest');
}
public function testConstruct(): void
{
$this->assertInstanceOf(SPCConfigUtil::class, new SPCConfigUtil());
$this->assertInstanceOf(SPCConfigUtil::class, new SPCConfigUtil(BuilderProvider::makeBuilderByInput(new ArgvInput())));
}
public function testConfig(): void
{
// normal
$result = (new SPCConfigUtil())->config(['bcmath']);
$this->assertStringContainsString(BUILD_ROOT_PATH . '/include', $result['cflags']);
$this->assertStringContainsString(BUILD_ROOT_PATH . '/lib', $result['ldflags']);
$this->assertStringContainsString('-lphp', $result['libs']);
// has cpp
$result = (new SPCConfigUtil())->config(['swoole']);
$this->assertStringContainsString(PHP_OS_FAMILY === 'Darwin' ? '-lc++' : '-lstdc++', $result['libs']);
// has mimalloc.o in lib dir
// backup first
if (file_exists(BUILD_LIB_PATH . '/mimalloc.o')) {
$bak = file_get_contents(BUILD_LIB_PATH . '/mimalloc.o');
@unlink(BUILD_LIB_PATH . '/mimalloc.o');
}
file_put_contents(BUILD_LIB_PATH . '/mimalloc.o', '');
$result = (new SPCConfigUtil())->config(['bcmath'], ['mimalloc']);
$this->assertStringStartsWith(BUILD_LIB_PATH . '/mimalloc.o', $result['libs']);
@unlink(BUILD_LIB_PATH . '/mimalloc.o');
if (isset($bak)) {
file_put_contents(BUILD_LIB_PATH . '/mimalloc.o', $bak);
}
}
}