Compare commits

...

90 Commits
2.5.1 ... 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
Marc
71b52e58b2 Merge branch 'main' into feat/xdebug-dynamic 2025-03-23 10:13:56 +01:00
DubbleClick
9d75265e25 fix crash on windoof 2025-03-23 16:11:03 +07:00
DubbleClick
1791b443bc add xdebug dynamic extension 2025-03-23 15:35:25 +07:00
87 changed files with 1152 additions and 282 deletions

View File

@@ -6,10 +6,13 @@ on:
os:
required: true
description: Build target OS
default: 'linux-x86_64'
type: choice
options:
- 'linux-x86_64'
- 'linux-aarch64'
- 'linux-x86_64-glibc'
- 'linux-aarch64-glibc'
- 'macos-x86_64'
- 'macos-aarch64'
php-version:
@@ -22,7 +25,6 @@ on:
- '8.3'
- '8.2'
- '8.1'
- '8.0'
extensions:
description: Extensions to build (comma separated)
required: true
@@ -77,9 +79,19 @@ jobs:
RUNS_ON="ubuntu-latest"
;;
linux-aarch64)
DOWN_CMD="SPC_USE_ARCH=aarch64 ./bin/spc-alpine-docker download"
BUILD_CMD="SPC_USE_ARCH=aarch64 ./bin/spc-alpine-docker build"
RUNS_ON="ubuntu-latest"
DOWN_CMD="./bin/spc-alpine-docker download"
BUILD_CMD="./bin/spc-alpine-docker build"
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)
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
- 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"
if: matrix.os == 'windows-latest'
if: ${{ startsWith(matrix.os, 'windows-') }}
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
- name: "Prepare UPX for Linux"
if: matrix.os == 'ubunut-latest'
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
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
- name: "Run Build Tests (download)"
@@ -197,5 +197,5 @@ jobs:
run: php src/globals/test-extensions.php build_cmd ${{ matrix.os }} ${{ matrix.php }}
- 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 }}

View File

@@ -95,7 +95,7 @@ WORKDIR /app
ADD ./src /app/src
COPY ./composer.* /app/
ADD ./bin /app/bin
RUN composer install --no-dev --classmap-authoritative
RUN composer install --no-dev
EOF
fi
@@ -122,6 +122,20 @@ MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/pkgroot:/app/pkgroot"
# shellcheck disable=SC2086
# shellcheck disable=SC2090
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
else
$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
if [ $(id -u) -ne 0 ]; 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"
# shellcheck disable=SC2039
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/spc /app/bin/spc
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 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=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.
; 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.
; 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:
; 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
; Alpine: /usr/lib/php{PHP_VERSION}/modules
; where {PHP_VERSION} is 84 for php 8.4
EXTENSION_DIR=
; EXTENSION_DIR=
[windows]
; php-sdk-binary-tools path
@@ -98,9 +97,9 @@ SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}"
; LIBS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lpthread -lm"
; 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
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS=""
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm"
; EXTRA_LDFLAGS_PROGRAM for `make` php
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
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}"
; 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
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-lresolv"
; embed type for php, static (libphp.a) or shared (libphp.dylib)

View File

@@ -92,6 +92,11 @@
},
"type": "wip"
},
"ev": {
"type": "external",
"source": "ev",
"arg-type-windows": "with"
},
"event": {
"support": {
"Windows": "wip",
@@ -119,6 +124,10 @@
"Linux": "partial",
"BSD": "wip"
},
"target": [
"static",
"shared"
],
"notes": true,
"arg-type": "custom",
"type": "builtin",
@@ -253,6 +262,7 @@
"Windows": "wip",
"BSD": "wip"
},
"notes": true,
"type": "external",
"source": "ext-imagick",
"arg-type": "custom",
@@ -519,17 +529,20 @@
},
"pdo_pgsql": {
"support": {
"Windows": "wip",
"BSD": "wip"
},
"type": "builtin",
"arg-type": "with-prefix",
"arg-type-windows": "custom",
"ext-depends": [
"pdo",
"pgsql"
],
"lib-depends": [
"lib-depends-unix": [
"postgresql"
],
"lib-depends-windows": [
"postgresql-win"
]
},
"pdo_sqlite": {
@@ -560,14 +573,16 @@
},
"pgsql": {
"support": {
"Windows": "wip",
"BSD": "wip"
},
"notes": true,
"type": "builtin",
"arg-type": "custom",
"lib-depends": [
"lib-depends-unix": [
"postgresql"
],
"lib-depends-windows": [
"postgresql-win"
]
},
"phar": {
@@ -766,6 +781,9 @@
"Windows": "no",
"BSD": "wip"
},
"target": [
"static"
],
"notes": true,
"type": "external",
"source": "swoole",
@@ -913,12 +931,16 @@
]
},
"xdebug": {
"type": "builtin",
"type": "external",
"source": "xdebug",
"target": [
"shared"
],
"support": {
"Windows": "wip",
"BSD": "no",
"Darwin": "no",
"Linux": "no"
"Darwin": "partial",
"Linux": "partial"
},
"notes": true
},
@@ -1034,6 +1056,9 @@
"support": {
"BSD": "wip"
},
"target": [
"static"
],
"type": "builtin",
"arg-type": "with-prefix",
"arg-type-windows": "enable",

View File

@@ -210,12 +210,12 @@
"libwebp",
"freetype",
"libtiff",
"libheif"
"libheif",
"bzip2"
],
"lib-suggests": [
"zstd",
"xz",
"bzip2",
"libzip",
"libxml2"
]
@@ -281,8 +281,7 @@
"headers-unix": [
"ares.h",
"ares_dns.h",
"ares_nameser.h",
"ares_rules.h"
"ares_nameser.h"
]
},
"libde265": {
@@ -671,6 +670,14 @@
"zstd"
]
},
"postgresql-win": {
"source": "postgresql-win",
"static-libs": [
"libpq.lib",
"libpgport.lib",
"libpgcommon.lib"
]
},
"pthreads4w": {
"source": "pthreads4w",
"static-libs-windows": [

View File

@@ -9,7 +9,7 @@
},
"nasm-x86_64-win": {
"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": {
"nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.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",
"prefer-stable": true,
"match-pattern": "{name}-{arch}-{os}.txz",
"suffix": "txz"
}
"match-pattern-linux": "{name}-{arch}-{os}-{libc}-{libcver}.txz",
"match-pattern": "{name}-{arch}-{os}.txz"
}

View File

@@ -37,9 +37,12 @@
}
},
"attr": {
"type": "git",
"rev": "v2.5.2",
"url": "https://git.savannah.nongnu.org/git/attr.git",
"alt": {
"type": "url",
"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,
"license": {
"type": "file",
@@ -89,6 +92,16 @@
"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": {
"type": "url",
"url": "https://pecl.php.net/get/ds",
@@ -335,9 +348,12 @@
}
},
"libacl": {
"type": "git",
"rev": "v2.3.2",
"url": "https://git.savannah.nongnu.org/git/acl.git",
"alt": {
"type": "url",
"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,
"license": {
"type": "file",
@@ -639,7 +655,7 @@
"mimalloc": {
"type": "ghtagtar",
"repo": "microsoft/mimalloc",
"match": "v2.+",
"match": "v2\\.\\d\\.[^3].*",
"provide-pre-built": false,
"license": {
"type": "file",
@@ -761,6 +777,14 @@
"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": {
"type": "url",
"url": "https://pecl.php.net/get/protobuf",
@@ -905,6 +929,15 @@
"path": "COPYING"
}
},
"xdebug": {
"type": "url",
"url": "https://pecl.php.net/get/xdebug",
"filename": "xdebug.tgz",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"xhprof": {
"type": "url",
"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.
## 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
1. Kerberos is not supported
@@ -76,10 +80,9 @@ and this extension cannot be compiled into php by static linking, so it cannot b
## xdebug
1. Xdebug is only buildable as a shared extension. On Linux, you need to use static-php-cli with SPC_LIBC=glibc and then compile php-xdebug from source with the option `--with-php-config=/path/to/buildroot/bin/php-config`.
2. The macOS platform can compile an xdebug extension under PHP compiled on the same platform,
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`.
1. Xdebug is only buildable as a shared extension. On Linux, you need to use static-php-cli with SPC_LIBC=glibc.
2. When using Linux/glibc or macOS, you can compile Xdebug as a shared extension using --build-shared="xdebug".
The compiled `./php` binary can be configured and run by specifying the INI, eg `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`.
## xml
@@ -122,8 +125,8 @@ For details on the solution, see [FAQ - Unable to use ssl](../faq/#unable-to-use
## 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).
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).
1. Due to the limitation of musl libc's static linkage, you cannot use ffi because dynamic libraries cannot be loaded.
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.
3. Windows x64 supports the ffi extension.

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-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)
- `--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:

View File

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

View File

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

View File

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

View File

@@ -122,9 +122,12 @@ abstract class BuilderBase
*
* @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
{
// judge cpp-extension
$exts = array_keys($this->getExts());
$exts = array_keys($this->getExts(false));
foreach ($exts as $ext) {
if (Config::getExt($ext, 'cpp-extension', false) === true) {
return true;
@@ -170,23 +173,46 @@ abstract class BuilderBase
* @throws \Throwable|WrongUsageException
* @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();
$this->emitPatchPoint('before-php-extract');
SourceManager::initSource(sources: ['php-src']);
$this->emitPatchPoint('after-php-extract');
if ($this->getPHPVersionID() >= 80000) {
$this->emitPatchPoint('before-micro-extract');
SourceManager::initSource(sources: ['micro']);
$this->emitPatchPoint('after-micro-extract');
// judge ext
foreach ($static_extensions as $ext) {
// if extension does not support static build, throw exception
if (!in_array('static', Config::getExtTarget($ext))) {
throw new WrongUsageException('Extension [' . $ext . '] does not support static build!');
}
}
$this->emitPatchPoint('before-exts-extract');
SourceManager::initSource(exts: $extensions);
$this->emitPatchPoint('after-exts-extract');
foreach ($extensions as $extension) {
foreach ($shared_extensions as $ext) {
// if extension does not support shared build, throw exception
if (!in_array('shared', Config::getExtTarget($ext)) && !in_array($ext, $shared_extensions)) {
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);
/** @var Extension $ext */
$ext = new $class($extension, $this);
if (in_array($extension, $static_extensions)) {
$ext->setBuildStatic();
}
if (in_array($extension, $shared_extensions)) {
$ext->setBuildShared();
}
$this->addExt($ext);
}
@@ -194,10 +220,10 @@ abstract class BuilderBase
return;
}
foreach ($this->exts as $ext) {
foreach ($this->getExts() as $ext) {
$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);
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.
* e.g. --enable-mbstring
@@ -214,10 +251,10 @@ abstract class BuilderBase
* @throws FileSystemException
* @throws WrongUsageException
*/
public function makeExtensionArgs(): string
public function makeStaticExtensionArgs(): string
{
$ret = [];
foreach ($this->exts as $ext) {
foreach ($this->getExts(false) as $ext) {
logger()->info($ext->getName() . ' is using ' . $ext->getConfigureArg());
$ret[] = trim($ext->getConfigureArg());
}
@@ -396,7 +433,7 @@ abstract class BuilderBase
foreach ($this->libs as $lib) {
$lib->validate();
}
foreach ($this->exts as $ext) {
foreach ($this->getExts() as $ext) {
$ext->validate();
}
}
@@ -441,7 +478,7 @@ abstract class BuilderBase
{
$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();
if (!empty($ext_name)) {
$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\store\Config;
use SPC\store\FileSystem;
use SPC\util\SPCConfigUtil;
class Extension
{
protected array $dependencies = [];
protected bool $build_shared = false;
protected bool $build_static = false;
protected string $source_dir;
/**
* @throws FileSystemException
* @throws RuntimeException
@@ -30,6 +37,18 @@ class Extension
if (PHP_OS_FAMILY === 'Windows' && $unix_only) {
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
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '';
}
@@ -167,6 +186,21 @@ class Extension
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
*/
@@ -231,6 +265,53 @@ class Extension
// 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
*
@@ -241,6 +322,32 @@ class Extension
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
*/

View File

@@ -8,6 +8,7 @@ use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\SourceManager;
@@ -45,8 +46,9 @@ abstract class LibraryBase
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$source = Config::getLib(static::NAME, 'source');
// if source is locked as pre-built, we just tryInstall it
if (isset($lock[$source]) && ($lock[$source]['lock_as'] ?? SPC_LOCK_SOURCE) === SPC_LOCK_PRE_BUILT) {
return $this->tryInstall($lock[$source]['filename'], $force);
$pre_built_name = Downloader::getPreBuiltLockName($source);
if (isset($lock[$pre_built_name]) && ($lock[$pre_built_name]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) {
return $this->tryInstall($lock[$pre_built_name]['filename'], $force);
}
return $this->tryBuild($force);
}
@@ -220,7 +222,7 @@ abstract class LibraryBase
// extract first if not exists
if (!is_dir($this->source_dir)) {
$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');
}

View File

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

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('dba')]
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) : '';
return '--enable-dba' . $qdbm;

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('enchant')]
class enchant extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$glibs = [
'/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')]
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;
if ($this->builder->getLib('openssl')) {

View File

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

View File

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

View File

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

View File

@@ -44,7 +44,7 @@ class grpc extends Extension
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;
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\builder\linux\LinuxBuilder;
use SPC\util\CustomExt;
#[CustomExt('imagick')]
@@ -13,17 +12,18 @@ class imagick extends Extension
{
public function patchBeforeMake(): bool
{
// imagick may call omp_pause_all which requires -lgomp
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
if ($this->builder instanceof LinuxBuilder) {
$extra_libs .= (empty($extra_libs) ? '' : ' ') . '-lgomp ';
if (getenv('SPC_LIBC') !== 'musl') {
return false;
}
// 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);
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;
if ($this->builder->getLib('openssl') !== null) {

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('odbc')]
class odbc extends Extension
{
public function getUnixConfigureArg(): string
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;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-opcache';
}

View File

@@ -23,7 +23,7 @@ class openssl extends Extension
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;
return '--with-openssl=' . BUILD_ROOT_PATH . $openssl_dir;

View File

@@ -17,7 +17,7 @@ class pdo_odbc extends Extension
return true;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
return '--with-pdo-odbc=unixODBC,' . BUILD_ROOT_PATH;
}

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 RuntimeException
*/
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
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=' . 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')]
class redis extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = '--enable-redis';
$arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session';

View File

@@ -26,7 +26,7 @@ class snappy extends Extension
return true;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
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';
if ($this->builder->getExt('zlib') === null) {

View File

@@ -35,7 +35,7 @@ class swoole extends Extension
return null;
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
// 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)
$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) : '';
// 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';
}
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
// 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

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
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
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')]
class xlswriter extends Extension
{
public function getUnixConfigureArg(): string
public function getUnixConfigureArg(bool $shared = false): string
{
$arg = '--with-xlswriter --enable-reader';
if ($this->builder->getLib('openssl')) {

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('zlib')]
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;
return '--with-zlib' . $zlib_dir;

View File

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

View File

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

View File

@@ -182,7 +182,7 @@ class LinuxBuilder extends UnixBuilderBase
$json_74 .
$zts .
$maxExecutionTimers .
$this->makeExtensionArgs() .
$this->makeStaticExtensionArgs() .
' ' . $envs_build_php . ' '
);
@@ -311,15 +311,7 @@ class LinuxBuilder extends UnixBuilderBase
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#');
$php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config');
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);
$this->patchPhpScripts();
}
private function getMakeExtraVars(): array

View File

@@ -182,4 +182,39 @@ class SystemUtil
'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

@@ -176,7 +176,7 @@ class MacOSBuilder extends UnixBuilderBase
$config_file_scan_dir .
$json_74 .
$zts .
$this->makeExtensionArgs() . ' ' .
$this->makeStaticExtensionArgs() . ' ' .
$envs_build_php
);
@@ -300,13 +300,7 @@ class MacOSBuilder extends UnixBuilderBase
->exec('rm ' . BUILD_ROOT_PATH . '/lib/libphp.a')
->exec('ar rcs ' . BUILD_ROOT_PATH . '/lib/libphp.a *.o')
->exec('rm -Rf ' . BUILD_ROOT_PATH . '/lib/php-o');
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'");
FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#');
$php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config');
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);
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
$this->patchPhpScripts();
}
private function getMakeExtraVars(): array

View File

@@ -146,7 +146,7 @@ abstract class UnixBuilderBase extends BuilderBase
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());
$ext->runCliCheckUnix();
}
@@ -238,4 +238,29 @@ abstract class UnixBuilderBase extends BuilderBase
logger()->info('cleaning up');
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

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

View File

@@ -26,9 +26,9 @@ trait libcares
{
shell()->cd($this->source_dir)
->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}")
->exec('make install DESTDIR=' . BUILD_ROOT_PATH);
->execWithEnv('make install DESTDIR=' . BUILD_ROOT_PATH);
$this->patchPkgconfPrefix(['libcares.pc'], PKGCONF_PATCH_PREFIX);
}

View File

@@ -119,7 +119,7 @@ class WindowsBuilder extends BuilderBase
($enableMicro ? ('--enable-micro=yes ' . $micro_logo . $micro_w32) : '--enable-micro=no ') .
($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') .
$config_file_scan_dir .
"{$this->makeExtensionArgs()} " .
"{$this->makeStaticExtensionArgs()} " .
$zts .
'"'
);
@@ -286,7 +286,7 @@ class WindowsBuilder extends BuilderBase
throw new RuntimeException('cli failed sanity check');
}
foreach ($this->exts as $ext) {
foreach ($this->getExts(false) as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckWindows();
}

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 $lower;
}, is_array($ext_list) ? $ext_list : explode(',', $ext_list));
}, is_array($ext_list) ? $ext_list : array_filter(explode(',', $ext_list)));
// filter internals
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->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-cli', null, null, 'Build cli SAPI');
$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
$libraries = array_map('trim', array_filter(explode(',', $this->getOption('with-libs'))));
// 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
$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) {
$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-micro\tBuild phpmicro 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);
$include_suggest_ext = $this->getOption('with-suggested-exts');
$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_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
$indent_texts = [
'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')',
'Build SAPI' => $builder->getBuildTypeName($rule),
'Extensions (' . count($extensions) . ')' => implode(',', $extensions),
'Extensions (' . count($extensions) . ')' => implode(',', $display_extensions),
'Libraries (' . count($libraries) . ')' => implode(',', $display_libs),
'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes',
'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'))) {
$indent_texts['Config File Path'] = $this->input->getOption('with-config-file-path');
}
@@ -152,8 +179,8 @@ class BuildPHPCommand extends BuildCommand
// compile libraries
$builder->proveLibs($libraries);
// check extensions
$builder->proveExts($extensions);
// validate libs and exts
$builder->proveExts($static_extensions, $shared_extensions);
// validate libs and extensions
$builder->validateLibsAndExts();
// clean builds and sources
@@ -183,6 +210,12 @@ class BuildPHPCommand extends BuildCommand
// start to build
$builder->buildPHP($rule);
// build dynamic extensions if needed
if (!empty($shared_extensions)) {
logger()->info('Building shared extensions ...');
$builder->buildSharedExts();
}
// compile stopwatch :P
$time = round(microtime(true) - START_TIME, 3);
logger()->info('');
@@ -211,6 +244,12 @@ class BuildPHPCommand extends BuildCommand
$path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm");
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
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.
*/
private function parseRules(): int
private function parseRules(array $shared_extensions = []): int
{
$rule = 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-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);
return $rule;
}

View File

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

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace SPC\command;
use SPC\builder\linux\SystemUtil;
use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
@@ -212,7 +213,7 @@ class DownloadCommand extends BaseCommand
if (isset($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);
} elseif (isset($custom_gits[$source])) {
$config = Config::getSource($source);
@@ -224,23 +225,30 @@ class DownloadCommand extends BaseCommand
if (isset($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);
} else {
$config = Config::getSource($source);
// Prefer pre-built, we need to search pre-built library
if ($this->getOption('prefer-pre-built') && ($config['provide-pre-built'] ?? false) === true) {
// 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
if (($url = $this->findPreBuilt($pre_built_libs, $find)) !== null) {
logger()->info("Fetching pre-built content {$source} [{$ni}/{$cnt}]");
Downloader::downloadSource($source, ['type' => 'url', 'url' => $url], $force_all || in_array($source, $force_list), SPC_LOCK_PRE_BUILT);
logger()->info("[{$ni}/{$cnt}] Downloading pre-built content {$source}");
Downloader::downloadSource($source, ['type' => 'url', 'url' => $url], $force_all || in_array($source, $force_list), SPC_DOWNLOAD_PRE_BUILT);
continue;
}
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));
}
}
@@ -352,6 +360,7 @@ class DownloadCommand extends BaseCommand
*/
private function findPreBuilt(array $assets, string $filename): ?string
{
logger()->debug("Finding pre-built asset {$filename}");
foreach ($assets as $asset) {
if ($asset['name'] === $filename) {
return $asset['browser_download_url'];

View File

@@ -20,6 +20,7 @@ class ExtractCommand extends BaseCommand
public function configure(): void
{
$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>');
return static::FAILURE;
}
SourceManager::initSource(sources: $sources);
SourceManager::initSource(sources: $sources, source_only: $this->getOption('source-only'));
logger()->info('Extract done !');
return static::SUCCESS;
}

View File

@@ -37,7 +37,7 @@ class SPCConfigCommand extends BuildCommand
$include_suggest_ext = $this->getOption('with-suggested-exts');
$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);
if ($this->getOption('includes')) {

View File

@@ -6,7 +6,6 @@ namespace SPC\command\dev;
use SPC\builder\BuilderProvider;
use SPC\command\BaseCommand;
use SPC\store\Config;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -31,8 +30,7 @@ class ExtVerCommand extends BaseCommand
// Get lib object
$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
// 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\LibraryBase;
use SPC\builder\linux\SystemUtil;
use SPC\command\BuildCommand;
use SPC\exception\ExceptionHandler;
use SPC\exception\FileSystemException;
@@ -23,6 +24,7 @@ class PackLibCommand extends BuildCommand
public function configure(): void
{
$this->addArgument('library', InputArgument::REQUIRED, 'The library will be compiled');
$this->addOption('show-libc-ver', null, null);
}
public function handle(): int
@@ -47,7 +49,7 @@ class PackLibCommand extends BuildCommand
// Get lock info
$lock = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$source = Config::getLib($lib->getName(), 'source');
if (!isset($lock[$source]) || ($lock[$source]['lock_as'] ?? SPC_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.");
return static::FAILURE;
}
@@ -69,7 +71,16 @@ class PackLibCommand extends BuildCommand
// write list to packlib_files.txt
FileSystem::writeFile(WORKING_DIR . '/packlib_files.txt', implode("\n", $increase_files));
// 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');
logger()->info('Pack library ' . $lib->getName() . ' to ' . $filename . ' complete.');
}

View File

@@ -22,11 +22,30 @@ class Config
public static ?array $pre_built = null;
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public static function getPreBuilt(string $name): mixed
{
if (self::$pre_built === null) {
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;
}
@@ -106,6 +125,21 @@ class Config
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 WrongUsageException

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace SPC\store;
use SPC\builder\linux\SystemUtil;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
@@ -124,6 +125,7 @@ class Downloader
if (($source['prefer-stable'] ?? false) === true && $release['prerelease'] === true) {
continue;
}
logger()->debug("Found {$release['name']} releases assets");
if (!$match_result) {
return $release['assets'];
}
@@ -189,7 +191,7 @@ class Downloader
* @throws RuntimeException
* @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}");
$cancel_func = function () use ($filename) {
@@ -202,12 +204,23 @@ class Downloader
self::curlDown(url: $url, path: FileSystem::convertPath(DOWNLOAD_PATH . "/{$filename}"), retry: self::getRetryTime());
self::unregisterCancelEvent();
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.
*
* @param string $name Source name
* @param array{
* source_type: string,
* dirname: ?string,
* filename: ?string,
* move_path: ?string,
* lock_as: int
* } $data Source data
* @throws FileSystemException
*/
public static function lockSource(string $name, array $data): void
@@ -228,7 +241,7 @@ class Downloader
* @throws RuntimeException
* @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}");
if (file_exists($download_path)) {
@@ -246,6 +259,7 @@ class Downloader
self::registerCancelEvent($cancel_func);
f_passthru(
SPC_GIT_EXEC . ' clone' . $check .
(defined('DEBUG_MODE') ? '' : ' --quiet') .
' --config core.autocrlf=false ' .
"--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\""
);
@@ -283,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 FileSystemException
* @throws WrongUsageException
*/
public static function downloadPackage(string $name, ?array $pkg = null, bool $force = false): void
{
@@ -301,50 +329,36 @@ class Downloader
FileSystem::createDir(DOWNLOAD_PATH);
}
// load lock file
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
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;
}
if (self::isAlreadyDownloaded($name, $force, SPC_DOWNLOAD_PACKAGE)) {
return;
}
try {
switch ($pkg['type']) {
case 'bitbuckettag': // BitBucket Tag
[$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;
case 'ghtar': // GitHub Release (tar)
[$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;
case 'ghtagtar': // GitHub Tag (tar)
[$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;
case 'ghrel': // GitHub Release (uploaded)
[$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;
case 'filelist': // Basic File List (regex based crawler)
[$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;
case 'url': // Direct download URL
$url = $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;
case 'git': // Git repo
self::downloadGit(
@@ -353,7 +367,7 @@ class Downloader
$pkg['rev'],
$pkg['extract'] ?? null,
self::getRetryTime(),
SPC_LOCK_PRE_BUILT
SPC_DOWNLOAD_PRE_BUILT
);
break;
case 'custom': // Custom download method, like API-based download or other
@@ -382,15 +396,30 @@ class Downloader
/**
* Download source by name and meta.
*
* @param string $name source name
* @param null|array $source source meta info: [type, path, rev, url, filename, regex, license]
* @param bool $force Whether to force download (default: false)
* @param int $lock_as Lock source type (default: SPC_LOCK_SOURCE)
* @param string $name source name
* @param null|array{
* type: string,
* 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 FileSystemException
* @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) {
$source = Config::getSource($name);
@@ -406,49 +435,36 @@ class Downloader
}
// load lock file
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
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;
}
if (self::isAlreadyDownloaded($name, $force, $download_as)) {
return;
}
try {
switch ($source['type']) {
case 'bitbuckettag': // BitBucket Tag
[$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;
case 'ghtar': // GitHub Release (tar)
[$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;
case 'ghtagtar': // GitHub Tag (tar)
[$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;
case 'ghrel': // GitHub Release (uploaded)
[$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;
case 'filelist': // Basic File List (regex based crawler)
[$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;
case 'url': // Direct download URL
$url = $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;
case 'git': // Git repo
self::downloadGit(
@@ -457,14 +473,14 @@ class Downloader
$source['rev'],
$source['path'] ?? null,
self::getRetryTime(),
$lock_as
$download_as
);
break;
case 'custom': // Custom download method, like API-based download or other
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source');
foreach ($classes as $class) {
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;
}
}
@@ -579,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.
*
@@ -611,4 +632,39 @@ class Downloader
{
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 FileSystemException

View File

@@ -15,7 +15,7 @@ class SourceManager
* @throws FileSystemException
* @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')) {
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) {
throw new WrongUsageException("Source [{$source}] does not exist, please check the name and correct it !");
}
if (!isset($lock[$source])) {
throw new WrongUsageException('Source [' . $source . '] not downloaded or not locked, you should download it first !');
// check source downloaded
$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 = $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)) {
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 {
logger()->debug('Source [' . $source . '] already extracted in ' . $check . ', skip !');
}

View File

@@ -43,7 +43,7 @@ class SourcePatcher
*/
public static function patchBeforeBuildconf(BuilderBase $builder): void
{
foreach ($builder->getExts() as $ext) {
foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeBuildconf() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before buildconf');
}
@@ -86,7 +86,7 @@ class SourcePatcher
*/
public static function patchBeforeConfigure(BuilderBase $builder): void
{
foreach ($builder->getExts() as $ext) {
foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeConfigure() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before configure');
}
@@ -253,7 +253,7 @@ class SourcePatcher
// }
// call extension patch before make
foreach ($builder->getExts() as $ext) {
foreach ($builder->getExts(false) as $ext) {
if ($ext->patchBeforeMake() === true) {
logger()->info('Extension [' . $ext->getName() . '] patched before make');
}

View File

@@ -8,5 +8,5 @@ abstract class CustomSourceBase
{
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 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';
Downloader::downloadSource('php-src', self::getLatestPHPInfo($major), $force);

View File

@@ -16,7 +16,7 @@ class PostgreSQLSource extends CustomSourceBase
* @throws DownloaderException
* @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);
}

View File

@@ -40,9 +40,6 @@ class GlobalEnvManager
self::putenv('PATH=' . BUILD_ROOT_PATH . '/bin:' . getenv('PATH'));
self::putenv('PKG_CONFIG=' . BUILD_BIN_PATH . '/pkg-config');
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

View File

@@ -7,26 +7,51 @@ namespace SPC\util;
use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider;
use SPC\builder\macos\MacOSBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
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) {
$this->builder = BuilderProvider::makeBuilderByInput($input ?? new ArgvInput());
if ($builder !== null) {
$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
{
[$extensions, $libraries] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
ob_start();
$this->builder->proveLibs($libraries);
$this->builder->proveExts($extensions);
if ($this->builder === null) {
$this->builder = BuilderProvider::makeBuilderByInput(new ArgvInput());
$this->builder->proveLibs($libraries);
$this->builder->proveExts($extensions, skip_extract: true);
}
ob_get_clean();
$ldflags = $this->getLdflagsString();
$libs = $this->getLibsString($libraries);
@@ -47,9 +72,9 @@ class SPCConfigUtil
$libs = BUILD_LIB_PATH . '/mimalloc.o ' . str_replace(BUILD_LIB_PATH . '/mimalloc.o', '', $libs);
}
return [
'cflags' => $cflags,
'ldflags' => $ldflags,
'libs' => $libs,
'cflags' => trim(getenv('CFLAGS') . ' ' . $cflags),
'ldflags' => trim(getenv('LDFLAGS') . ' ' . $ldflags),
'libs' => trim(getenv('LIBS') . ' ' . $libs),
];
}
@@ -90,8 +115,8 @@ class SPCConfigUtil
}
}
}
// patch: imagick (imagemagick wrapper) for linux needs -lgomp
if (in_array('imagemagick', $libraries) && PHP_OS_FAMILY === 'Linux') {
// patch: imagick (imagemagick wrapper) for linux needs libgomp
if (in_array('imagemagick', $libraries) && PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') {
$short_name[] = '-lgomp';
}
return implode(' ', $short_name);

View File

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

View File

@@ -24,11 +24,15 @@ $test_os = [
'macos-13',
'macos-14',
'ubuntu-latest',
'ubuntu-22.04',
'ubuntu-24.04',
'ubuntu-22.04-arm',
'ubuntu-24.04-arm',
'windows-latest',
];
// whether enable thread safe
$zts = false;
$zts = true;
$no_strip = false;
@@ -40,8 +44,14 @@ $prefer_pre_built = false;
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
$extensions = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'odbc,pdo_odbc',
'Windows' => 'odbc,pdo_odbc',
'Linux', 'Darwin' => 'ev',
'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`).
@@ -85,6 +95,7 @@ if (!isset($argv[1])) {
$trim_value = "\r\n \t,";
$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);
if (PHP_OS_FAMILY === 'Windows') {
@@ -105,7 +116,7 @@ function quote2(string $param): string
// generate download command
if ($argv[1] === 'download_cmd') {
$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 .= '--with-php=' . quote2($argv[3]) . ' ';
$down_cmd .= '--ignore-cache-sources=php-src ';
@@ -115,10 +126,46 @@ if ($argv[1] === 'download_cmd') {
$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
if ($argv[1] === 'build_cmd' || $argv[1] === 'build_embed_cmd') {
$build_cmd = 'build ';
$build_cmd .= quote2($final_extensions) . ' ';
$build_cmd .= $shared_cmd;
$build_cmd .= $zts ? '--enable-zts ' : '';
$build_cmd .= $no_strip ? '--no-strip ' : '';
$build_cmd .= $upx ? '--with-upx-pack ' : '';
@@ -139,32 +186,32 @@ echo match ($argv[1]) {
'upx' => $upx ? '--with-upx-pack' : '',
'prefer_pre_built' => $prefer_pre_built ? '--prefer-pre-built' : '',
'download_cmd' => $down_cmd,
'install_upx_cmd' => $install_upx_cmd,
'doctor_cmd' => $doctor_cmd,
'build_cmd' => $build_cmd,
'build_embed_cmd' => $build_cmd,
default => '',
};
if ($argv[1] === 'download_cmd') {
if (str_starts_with($argv[2], 'windows-')) {
passthru('powershell.exe -file .\bin\spc.ps1 ' . $down_cmd, $retcode);
} else {
passthru('./bin/spc ' . $down_cmd, $retcode);
}
} elseif ($argv[1] === 'build_cmd') {
if (str_starts_with($argv[2], 'windows-')) {
passthru('powershell.exe -file .\bin\spc.ps1 ' . $build_cmd . ' --build-cli --build-micro', $retcode);
} else {
passthru('./bin/spc ' . $build_cmd . ' --build-cli --build-micro', $retcode);
}
} elseif ($argv[1] === 'build_embed_cmd') {
if (str_starts_with($argv[2], 'windows-')) {
// windows does not accept embed SAPI
passthru('powershell.exe -file .\bin\spc.ps1 ' . $build_cmd . ' --build-cli', $retcode);
} else {
passthru('./bin/spc ' . $build_cmd . ' --build-embed', $retcode);
}
} else {
$retcode = 0;
switch ($argv[1] ?? null) {
case 'download_cmd':
passthru($prefix . $down_cmd, $retcode);
break;
case 'build_cmd':
passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode);
break;
case 'build_embed_cmd':
passthru($prefix . $build_cmd . (str_starts_with($argv[2], 'windows-') ? ' --build-cli' : ' --build-embed'), $retcode);
break;
case 'doctor_cmd':
passthru($prefix . $doctor_cmd, $retcode);
break;
case 'install_upx_cmd':
passthru($prefix . $install_upx_cmd, $retcode);
break;
default:
$retcode = 0;
break;
}
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()
{
$this->assertStringContainsString('--enable-mbstring', $this->builder->makeExtensionArgs());
$this->assertStringContainsString('--enable-mbstring', $this->builder->makeStaticExtensionArgs());
}
public function testIsLibsOnly()

View File

@@ -137,6 +137,41 @@ class ConfigValidatorTest extends TestCase
} catch (ValidationException) {
$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
try {
ConfigValidator::validateLibs(['lib1' => ['source' => true]], ['source1' => [], 'source2' => []]);
@@ -169,4 +204,11 @@ class ConfigValidatorTest extends TestCase
$this->expectException(ValidationException::class);
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
{
// setup
$bak = [
'source' => Config::$source,
'lib' => Config::$lib,
'ext' => Config::$ext,
];
// example
Config::$source = [
'test1' => [
@@ -82,6 +88,10 @@ final class DependencyUtilTest extends TestCase
$this->assertTrue($b < $a);
$this->assertTrue($c < $a);
$this->assertTrue($c < $b);
// restore
Config::$source = $bak['source'];
Config::$lib = $bak['lib'];
Config::$ext = $bak['ext'];
}
public function testNotExistExtException(): void

View File

@@ -33,6 +33,10 @@ final class LicenseDumperTest extends TestCase
public function testDumpWithSingleLicense(): void
{
$bak = [
'source' => Config::$source,
'lib' => Config::$lib,
];
Config::$lib = [
'lib-base' => ['type' => 'root'],
'php' => ['type' => 'root'],
@@ -54,10 +58,17 @@ final class LicenseDumperTest extends TestCase
$dumper->dump(self::DIRECTORY);
$this->assertFileExists(self::DIRECTORY . '/lib_fake_lib_0.txt');
// restore
Config::$source = $bak['source'];
Config::$lib = $bak['lib'];
}
public function testDumpWithMultipleLicenses(): void
{
$bak = [
'source' => Config::$source,
'lib' => Config::$lib,
];
Config::$lib = [
'lib-base' => ['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_1.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);
}
}
}