diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 13441a81f..fb78849c7 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,7 +8,7 @@ > If a modification is not involved, please skip it directly. - If you modified `*.php` or `*.json`, run them locally to ensure your changes are valid: - - [ ] `PHP_CS_FIXER_IGNORE_ENV=1 composer cs-fix` + - [ ] `composer cs-fix` - [ ] `composer analyse` - [ ] `composer test` - [ ] `bin/spc dev:sort-config` diff --git a/.github/workflows/build-unix.yml b/.github/workflows/build-unix.yml index 31b13c61c..16ad76e06 100644 --- a/.github/workflows/build-unix.yml +++ b/.github/workflows/build-unix.yml @@ -198,9 +198,9 @@ jobs: run: ${{ needs.define-build.outputs.download }} - name: "Build PHP" run: ${{ needs.define-build.outputs.build }} - #- name: Setup tmate session - # if: ${{ failure() }} - # uses: mxschmitt/action-tmate@v3 +# - name: Setup tmate session +# if: ${{ failure() }} +# uses: mxschmitt/action-tmate@v3 # Upload cli executable - if: ${{ inputs.build-cli == true }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d02be5ef0..a360e54a8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: tools: pecl, composer, php-cs-fixer - name: Run PHP-CS-Fixer fix - run: PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix --dry-run --diff --ansi + run: php-cs-fixer fix --dry-run --diff --ansi phpstan: runs-on: ubuntu-latest @@ -55,6 +55,8 @@ jobs: extensions: curl, openssl, mbstring ini-values: memory_limit=-1 tools: composer + env: + phpts: zts - name: "Cache Composer packages" id: composer-cache @@ -135,7 +137,7 @@ jobs: build: name: "Build PHP Test (PHP ${{ matrix.php }} ${{ matrix.os }})" runs-on: ${{ matrix.os }} - needs: define-matrix + needs: [define-matrix, php-cs-fixer, phpstan, phpunit] timeout-minutes: 120 strategy: matrix: diff --git a/.gitignore b/.gitignore index a95eeaacd..ccdb7866f 100644 --- a/.gitignore +++ b/.gitignore @@ -34,9 +34,6 @@ packlib_files.txt /bin/* !/bin/spc* !/bin/setup-runtime* -!/bin/spc-alpine-docker -!/bin/php-cs-fixer-wrapper -!/bin/build-static-frankenphp # exclude windows build tools /php-sdk-binary-tools/ diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 55fca81a3..d1c30090e 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -70,4 +70,5 @@ ]) ->setFinder( PhpCsFixer\Finder::create()->in([__DIR__ . '/src', __DIR__ . '/tests/SPC']) - ); + ) + ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()); diff --git a/bin/build-static-frankenphp b/bin/build-static-frankenphp deleted file mode 100755 index 10a693a78..000000000 --- a/bin/build-static-frankenphp +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env bash - -# This file is using docker to run commands -set -e - -# Detect docker can run -if ! which docker >/dev/null; then - echo "Docker is not installed, please install docker first !" - exit 1 -fi -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 - 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' - exit 1 - fi - DOCKER_EXECUTABLE="sudo docker" - fi -fi - - - -# to check if qemu-docker run -if [ "$SPC_USE_ARCH" = "" ]; then - SPC_USE_ARCH=current -fi -case $SPC_USE_ARCH in -current) - BASE_ARCH=$(uname -m) - if [ "$BASE_ARCH" = "arm64" ]; then - BASE_ARCH=aarch64 - GO_ARCH=arm64 - else - GO_ARCH=amd64 - fi - ;; -aarch64) - BASE_ARCH=aarch64 - GO_ARCH=arm64 - # shellcheck disable=SC2039 - echo -e "\e[033m* Using different arch needs to setup qemu-static for docker !\e[0m" - $DOCKER_EXECUTABLE run --rm --privileged multiarch/qemu-user-static:register --reset > /dev/null - ;; -*) - echo "Current arch is not supported to run in docker: $SPC_USE_ARCH" - exit 1 - ;; -esac - -# Detect docker env is setup -if ! $DOCKER_EXECUTABLE images | grep -q cwcc-frankenphp-gnu-$SPC_USE_ARCH; then - echo "Docker container does not exist. Building docker image ..." - $DOCKER_EXECUTABLE build -t cwcc-frankenphp-gnu-$SPC_USE_ARCH -f- . <> /etc/bashrc -RUN source /etc/bashrc - -RUN curl -o cmake.tgz -fsSL https://github.com/Kitware/CMake/releases/download/v3.31.4/cmake-3.31.4-linux-$BASE_ARCH.tar.gz && \ - mkdir /cmake && \ - tar -xzf cmake.tgz -C /cmake --strip-components 1 - -WORKDIR /app -ADD ./src /app/src -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 -ENV PATH="/app/bin:/cmake/bin:/usr/local/go/bin:$PATH" -ENV SPC_LIBC=glibc - -ADD ./config/env.ini /app/config/env.ini -RUN bin/spc doctor --auto-fix --debug - -RUN curl -o make.tgz -fsSL https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && \ - tar -zxvf make.tgz && \ - cd make-4.4 && \ - ./configure && \ - make && \ - make install && \ - ln -sf /usr/local/bin/make /usr/bin/make - -RUN curl -o automake.tgz -fsSL https://ftp.gnu.org/gnu/automake/automake-1.17.tar.xz && \ - tar -xvf automake.tgz && \ - cd automake-1.17 && \ - ./configure && \ - make && \ - make install && \ - ln -sf /usr/local/bin/automake /usr/bin/automake - -RUN git clone https://github.com/static-php/gnu-frankenphp --depth=1 /frankenphp -WORKDIR /frankenphp - -RUN curl -o go.tgz -fsSL https://go.dev/dl/go1.24.1.linux-$GO_ARCH.tar.gz && \ - rm -rf /usr/local/go && tar -C /usr/local -xzf go.tgz -EOF -fi - -# Check if in ci (local terminal can execute with -it) -if [ -t 0 ]; then - INTERACT=-it -else - INTERACT='' -fi - -# Mounting volumes -MOUNT_LIST="" -# shellcheck disable=SC2089 -MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/config:/app/config" -MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/src:/app/src" -MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/buildroot:/app/buildroot" -MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/source:/app/source" -MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/dist:/app/dist" -MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/downloads:/app/downloads" -MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/pkgroot:/app/pkgroot" -MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/frankenphp-dist:/frankenphp/dist" - -# Apply env in temp env file -echo 'CC=/opt/rh/devtoolset-10/root/usr/bin/gcc' > /tmp/spc-gnu-docker.env -echo 'CXX=/opt/rh/devtoolset-10/root/usr/bin/g++' >> /tmp/spc-gnu-docker.env -echo 'AR=/opt/rh/devtoolset-10/root/usr/bin/ar' >> /tmp/spc-gnu-docker.env -echo 'LD=/opt/rh/devtoolset-10/root/usr/bin/ld' >> /tmp/spc-gnu-docker.env -echo 'SPC_DEFAULT_C_FLAGS=-fPIE -fPIC' >> /tmp/spc-gnu-docker.env -echo 'SPC_LIBC=glibc' >> /tmp/spc-gnu-docker.env -echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-Wl,-O1 -pie"' >> /tmp/spc-gnu-docker.env -echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm -lresolv -lutil -lrt"' >> /tmp/spc-gnu-docker.env - -# Run docker -# shellcheck disable=SC2068 -# 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-frankenphp-gnu-$SPC_USE_ARCH ./build-static.sh diff --git a/bin/php-cs-fixer-wrapper b/bin/php-cs-fixer-wrapper deleted file mode 100755 index 8a32dbe4c..000000000 --- a/bin/php-cs-fixer-wrapper +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -# get parent dir, and run the php-cs-fixer -PHP_CS_FIXER_IGNORE_ENV=1 "$(dirname "$0")/../vendor/bin/php-cs-fixer" "$@" diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index 1a9e7e032..1c0331349 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -83,7 +83,8 @@ RUN if [ "$SPC_USE_ARCH" = "aarch64" ]; then \ sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/*.repo ; \ fi RUN sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/*.repo && \ - sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/*.repo + sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/*.repo && \ + sed -i 's|http://|https://|g' /etc/yum.repos.d/*.repo RUN yum update -y && \ yum install -y devtoolset-10-gcc-* devtoolset-10-libatomic-devel @@ -155,8 +156,6 @@ fi # Apply env in temp env file echo 'SPC_DEFAULT_C_FLAGS=-fPIC' > /tmp/spc-gnu-docker.env echo 'SPC_LIBC=glibc' >> /tmp/spc-gnu-docker.env -echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-Wl,-O1 -pie"' >> /tmp/spc-gnu-docker.env -echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm -lresolv -lutil -lrt"' >> /tmp/spc-gnu-docker.env # Environment variable passthrough ENV_LIST="" diff --git a/config/env.ini b/config/env.ini index c4c27c507..465f72d5b 100644 --- a/config/env.ini +++ b/config/env.ini @@ -62,23 +62,26 @@ PHP_SDK_PATH="${WORKING_DIR}\php-sdk-binary-tools" UPX_EXEC="${PKG_ROOT_PATH}\bin\upx.exe" ; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime140,win32,zend_stream,cli_static -; Windows static linking system libs -SPC_EXTRA_LIBS="" [linux] -; Linux can use different build toolchain, but the toolchain can not be changed in this file: -; - musl (default): used for general linux distros, can build `musl-static` target only. -; - zig (WIP): used for general linux distros, can build `musl` and `glibc` targets. -; - musl-native: used for alpine linux, can build `musl-static` and `musl`(WIP) target. -; - gnu-native (assume): used for general linux distros, can build `glibc` target only and have portability issues. +; Linux can use different build toolchains. +; - musl (default, when SPC_LIBC=musl): used for general linux distros, can build `musl` (statically linked) only. +; - zig (will become default): usable on all Linux distros, can build `-musl`, `arch-linux-musl -dynamic` and `arch-linux-gnu` targets. Can specify version such as `x86_64-linux-gnu.2.17`. +; - musl-native: used for alpine linux, can build `musl` and `musl -dynamic` target. +; - gnu-native: used for general linux distros, can build gnu target for the installed glibc version only. -; build target: -; - musl-static (default): pure static linking, using musl-libc, can run on any linux distro. -; - musl: static linking with dynamic linking to musl-libc, can run on musl-based linux distro. -; - glibc: static linking with dynamic linking to glibc, can run on glibc-based linux distro. - -; include PATH for musl libc. +; LEGACY option to specify the target SPC_LIBC=musl + +; Recommended: specify your target here. Zig toolchain will be used. +; examples: +; `native-native-gnu` - links against glibc, current OS version +; `native-native-gnu.2.17` - links against glibc, version 2.17 +; `native-native` - links against system libc dynamically +; `native-native-musl` - links against musl libc statically +; `native-native-musl -dynamic` - links against musl libc dynamically +; SPC_TARGET= + ; compiler environments CC=${SPC_LINUX_DEFAULT_CC} CXX=${SPC_LINUX_DEFAULT_CXX} @@ -87,8 +90,7 @@ LD=${SPC_LINUX_DEFAULT_LD} ; default compiler flags, used in CMake toolchain file, openssl and pkg-config build SPC_DEFAULT_C_FLAGS="-fPIC -Os" SPC_DEFAULT_CXX_FLAGS="-fPIC -Os" -; extra libs for building php executable, used in `make` command for building php (this value may changed by extension build process, space separated) -SPC_EXTRA_LIBS="" +SPC_DEFAULT_LD_FLAGS="" ; upx executable path UPX_EXEC=${PKG_ROOT_PATH}/bin/upx ; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches @@ -107,16 +109,10 @@ SPC_CMD_PREFIX_PHP_MAKE="make -j${SPC_CONCURRENCY}" SPC_CMD_VAR_PHP_EMBED_TYPE="static" ; CFLAGS for configuring php SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS="${SPC_DEFAULT_C_FLAGS} -fPIE" -; CPPFLAGS for configuring php -SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS="-I${BUILD_INCLUDE_PATH}" -; LDFLAGS for configuring php -SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}" -; LIBS for configuring php -SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lpthread -lm" ; EXTRA_CFLAGS for `make` php -SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_DEFAULT_C_FLAGS} -g -fstack-protector-strong -fno-ident -fPIE" -; EXTRA_LIBS for `make` php -SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm" +SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fno-ident -fPIE ${SPC_DEFAULT_C_FLAGS}" +; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so +SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS="" [macos] ; build target: macho or macho (possibly we could support macho-universal in the future) @@ -128,8 +124,7 @@ CXX=clang++ ; default compiler flags, used in CMake toolchain file, openssl and pkg-config build SPC_DEFAULT_C_FLAGS="--target=${MAC_ARCH}-apple-darwin -Os" SPC_DEFAULT_CXX_FLAGS="--target=${MAC_ARCH}-apple-darwin -Os" -; extra libs for building php executable, used in `make` command for building php (this value may changed by extension build process, space separated) -SPC_EXTRA_LIBS="" +SPC_DEFAULT_LD_FLAGS="" ; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches SPC_MICRO_PATCHES=cli_checks,macos_iconv @@ -146,14 +141,8 @@ SPC_CMD_PREFIX_PHP_MAKE="make -j${SPC_CONCURRENCY}" SPC_CMD_VAR_PHP_EMBED_TYPE="static" ; CFLAGS for configuring php SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS="${SPC_DEFAULT_C_FLAGS} -Werror=unknown-warning-option" -; CPPFLAGS for configuring php -SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS="-I${BUILD_INCLUDE_PATH}" -; LDFLAGS for configuring php -SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}" ; EXTRA_CFLAGS for `make` php -SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_DEFAULT_C_FLAGS} -g -fstack-protector-strong -fpic -fpie" -; EXTRA_LIBS for `make` php -SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-lresolv" +SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie ${SPC_DEFAULT_C_FLAGS}" [freebsd] ; compiler environments diff --git a/config/ext.json b/config/ext.json index 61897bb5a..2cf81b180 100644 --- a/config/ext.json +++ b/config/ext.json @@ -495,6 +495,7 @@ "type": "builtin", "arg-type": "custom", "arg-type-windows": "with", + "build-with-php": true, "lib-depends": [ "openssl", "zlib" diff --git a/config/lib.json b/config/lib.json index 13f886ca5..d12950744 100644 --- a/config/lib.json +++ b/config/lib.json @@ -655,7 +655,7 @@ "mimalloc": { "source": "mimalloc", "static-libs-unix": [ - "mimalloc.o" + "libmimalloc.a" ] }, "ncurses": { @@ -738,8 +738,8 @@ "openssl": { "source": "openssl", "static-libs-unix": [ - "libcrypto.a", - "libssl.a" + "libssl.a", + "libcrypto.a" ], "static-libs-windows": [ "libssl.lib", diff --git a/config/pkg.json b/config/pkg.json index e0762cac7..080958b4f 100644 --- a/config/pkg.json +++ b/config/pkg.json @@ -54,5 +54,20 @@ }, "go-xcaddy-aarch64-macos": { "type": "custom" + }, + "zig-x86_64-linux": { + "type": "custom" + }, + "zig-aarch64-linux": { + "type": "custom" + }, + "zig-x86_64-macos": { + "type": "custom" + }, + "zig-aarch64-macos": { + "type": "custom" + }, + "zig-x86_64-win": { + "type": "custom" } } diff --git a/config/source.json b/config/source.json index 36fcace0a..0fc9bca75 100644 --- a/config/source.json +++ b/config/source.json @@ -996,33 +996,28 @@ }, "swoole": { "path": "php-src/ext/swoole", - "type": "git", - "rev": "master", - "url": "https://github.com/swoole/swoole-src.git", + "type": "ghtar", + "repo": "swoole/swoole-src", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" - }, - "alt": { - "type": "ghtar", - "repo": "swoole/swoole-src", - "prefer-stable": true } }, "swow": { - "type": "git", "path": "php-src/ext/swow-src", - "rev": "ci", - "url": "https://github.com/swow/swow", + "type": "ghtar", + "repo": "swow/swow", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" } }, "tidy": { - "type": "url", - "url": "https://github.com/htacg/tidy-html5/archive/refs/tags/5.8.0.tar.gz", - "filename": "tidy-html5.tgz", + "type": "ghtar", + "repo": "htacg/tidy-html5", + "prefer-stable": true, "license": { "type": "file", "path": "README/LICENSE.md" diff --git a/docs/en/guide/extension-notes.md b/docs/en/guide/extension-notes.md index 5a022b590..1fa65d0e0 100644 --- a/docs/en/guide/extension-notes.md +++ b/docs/en/guide/extension-notes.md @@ -48,17 +48,17 @@ This extension contains an implementation of the coroutine environment for `pdo_ ## swow -1. Only PHP 8.0 ~ 8.4 is supported. +1. Only PHP 8.0+ is supported. ## imagick -1. The imagick extension currently only has openmp support on musl libc. This means that multithreading is disabled on glibc or other operating systems. The extension is still fully functional. +1. OpenMP support is disabled, this is recommended by the maintainers and also the case system packages. ## imap 1. Kerberos is not supported -2. ext-imap is not thread safe due to the underlying c-client. It's not possible to use it in --enable-zts builds. -3. Because the extension may be dropped from php, we recommend you look for an alternative implementation, such as [Webklex/php-imap](https://github.com/Webklex/php-imap) +2. ext-imap is not thread safe due to the underlying c-client. It's not possible to use it in `--enable-zts` builds. +3. The extension was dropped from php 8.4, we recommend you look for an alternative implementation, such as [Webklex/php-imap](https://github.com/Webklex/php-imap) ## gd @@ -82,7 +82,7 @@ and this extension cannot be compiled into php by static linking, so it cannot b ## xdebug -1. Xdebug is only buildable as a shared extension. You need to use a build target other than `musl-static` for SPC_TARGET. +1. Xdebug is only buildable as a shared extension. On Linux, you'll need to use a SPC_TARGET like `native-native -dynamic` or `native-native-gnu`. 2. When using Linux/glibc or macOS, you can compile Xdebug as a shared extension using --build-shared="xdebug". The compiled `./php` binary can be configured and run by specifying the INI, eg `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`. @@ -122,8 +122,8 @@ For details on the solution, see [FAQ - Unable to use ssl](../faq/#unable-to-use ## password-argon2 -1. password-argon2 is not a standard extension, it is an additional algorithm for the `password_hash` function. -2. On Linux systems, `password-argon2` dependency `libargon2` conflicts with the `libsodium` library. +1. password-argon2 is not a standard extension. The algorithm `PASSWORD_ARGON2ID` for the `password_hash` function needs libsodium or libargon2 to work. +2. using password-argon2 enables multithread support for this. ## ffi diff --git a/docs/zh/guide/extension-notes.md b/docs/zh/guide/extension-notes.md index ad787dd95..40744ffaf 100644 --- a/docs/zh/guide/extension-notes.md +++ b/docs/zh/guide/extension-notes.md @@ -45,17 +45,17 @@ swoole-hook-sqlite 与 `pdo_sqlite` 扩展冲突。如需使用 Swoole 和 `pdo_ ## swow -1. swow 仅支持 PHP 8.0 ~ 8.4 版本。 +1. swow 仅支持 PHP 8.0+ 版本。 ## imagick -imagick 扩展目前仅在 musl libc 上支持 OpenMP(libgomp)。使用 glibc 方式构建的 imagick 扩展无法支持多线程特性。 +1. OpenMP 支持已被禁用,这是维护者推荐的做法,系统软件包也是如此配置。 ## imap 1. 该扩展目前不支持 Kerberos。 2. 由于底层的 c-client、ext-imap 不是线程安全的。 无法在 `--enable-zts` 构建中使用它。 -3. 由于该扩展可能会从未来的 PHP 中删除,因此我们建议您寻找替代实现,例如 [Webklex/php-imap](https://github.com/Webklex/php-imap)。 +3. 该扩展已在 PHP 8.4 中被移除,因此我们建议您寻找替代实现,例如 [Webklex/php-imap](https://github.com/Webklex/php-imap)。 ## gd @@ -114,8 +114,8 @@ pgsql 16.2 修复了这个 Bug,现在正常工作了。 ## password-argon2 -1. password-argon2不是一个标准的扩展,它是 `password_hash` 函数的额外算法。 -2. 在Linux系统,password-argon2 的依赖库 `libargon2` 与 `libsodium` 库冲突。 +1. password-argon2不是一个标准的扩展。`password_hash` 函数的 `PASSWORD_ARGON2ID` 算法需要 libsodium 或 libargon2 才能工作。 +2. 使用 password-argon2 可以为此启用多线程支持。 ## ffi diff --git a/src/SPC/ConsoleApplication.php b/src/SPC/ConsoleApplication.php index a24eada9a..43c838eae 100644 --- a/src/SPC/ConsoleApplication.php +++ b/src/SPC/ConsoleApplication.php @@ -34,7 +34,7 @@ */ final class ConsoleApplication extends Application { - public const VERSION = '2.6.1'; + public const VERSION = '2.7.0'; public function __construct() { diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index 3c3acddad..becd0f437 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -264,6 +264,7 @@ public function buildSharedExts(): void } } file_put_contents(BUILD_BIN_PATH . '/php-config', implode('', $lines)); + FileSystem::replaceFileStr(BUILD_LIB_PATH . '/php/build/phpize.m4', 'test "[$]$1" = "no" && $1=yes', '# test "[$]$1" = "no" && $1=yes'); FileSystem::createDir(BUILD_MODULES_PATH); try { foreach ($this->getExts() as $ext) { @@ -277,6 +278,7 @@ public function buildSharedExts(): void throw $e; } FileSystem::replaceFileLineContainsString(BUILD_BIN_PATH . '/php-config', 'extension_dir=', $extension_dir_line); + FileSystem::replaceFileStr(BUILD_LIB_PATH . '/php/build/phpize.m4', '# test "[$]$1" = "no" && $1=yes', 'test "[$]$1" = "no" && $1=yes'); } /** diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index 585ecfc77..90f2e27e0 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -9,7 +9,10 @@ use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\FileSystem; +use SPC\toolchain\ToolchainManager; +use SPC\toolchain\ZigToolchain; use SPC\util\SPCConfigUtil; +use SPC\util\SPCTarget; class Extension { @@ -187,6 +190,14 @@ public function patchBeforeConfigure(): bool */ public function patchBeforeMake(): bool { + if (SPCTarget::getTargetOS() === 'Linux' && $this->isBuildShared() && ($objs = getenv('SPC_EXTRA_RUNTIME_OBJECTS'))) { + FileSystem::replaceFileRegex( + SOURCE_PATH . '/php-src/Makefile', + "/^(shared_objects_{$this->getName()}\\s*=.*)$/m", + "$1 {$objs}", + ); + return true; + } return false; } @@ -217,7 +228,21 @@ public function patchBeforeSharedConfigure(): bool */ public function patchBeforeSharedMake(): bool { - return false; + $config = (new SPCConfigUtil($this->builder))->config([$this->getName()], array_map(fn ($l) => $l->getName(), $this->builder->getLibs())); + [$staticLibs] = $this->splitLibsIntoStaticAndShared($config['libs']); + FileSystem::replaceFileRegex( + $this->source_dir . '/Makefile', + '/^(.*_SHARED_LIBADD\s*=.*)$/m', + '$1 ' . trim($staticLibs) + ); + if ($objs = getenv('SPC_EXTRA_RUNTIME_OBJECTS')) { + FileSystem::replaceFileRegex( + $this->source_dir . '/Makefile', + "/^(shared_objects_{$this->getName()}\\s*=.*)$/m", + "$1 {$objs}", + ); + } + return true; } /** @@ -251,7 +276,7 @@ public function getSharedExtensionLoadString(): string $ret = ''; foreach ($order as $ext) { - if ($ext instanceof Extension && $ext->isBuildShared()) { + if ($ext instanceof self && $ext->isBuildShared()) { if (Config::getExt($ext->getName(), 'zend-extension', false) === true) { $ret .= " -d \"zend_extension={$ext->getName()}\""; } else { @@ -294,9 +319,7 @@ public function runCliCheckUnix(): void [$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' -r "' . trim($test) . '"'); if ($ret !== 0) { - if ($this->builder->getOption('debug')) { - var_dump($out); - } + var_dump($out); throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check'); } } @@ -384,20 +407,25 @@ public function buildShared(): void */ public function buildUnixShared(): void { - $config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true); - [$staticLibString, $sharedLibString] = $this->getStaticAndSharedLibs(); - - // macOS ld64 doesn't understand these, while Linux and BSD do - // use them to make sure that all symbols are picked up, even if a library has already been visited before - $preStatic = PHP_OS_FAMILY !== 'Darwin' ? '-Wl,-Bstatic -Wl,--start-group ' : ''; - $postStatic = PHP_OS_FAMILY !== 'Darwin' ? ' -Wl,--end-group -Wl,-Bdynamic ' : ' '; + $config = (new SPCConfigUtil($this->builder))->config( + [$this->getName()], + array_map(fn ($l) => $l->getName(), $this->getLibraryDependencies(recursive: true)), + $this->builder->getOption('with-suggested-exts'), + $this->builder->getOption('with-suggested-libs'), + ); + [$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']); + $preStatic = PHP_OS_FAMILY === 'Darwin' ? '' : '-Wl,--start-group '; + $postStatic = PHP_OS_FAMILY === 'Darwin' ? '' : ' -Wl,--end-group '; $env = [ 'CFLAGS' => $config['cflags'], 'CXXFLAGS' => $config['cflags'], 'LDFLAGS' => $config['ldflags'], - 'LIBS' => $preStatic . $staticLibString . $postStatic . $sharedLibString, + 'LIBS' => clean_spaces("{$preStatic} {$staticLibs} {$postStatic} {$sharedLibs}"), 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, ]; + if (ToolchainManager::getToolchainClass() === ZigToolchain::class && SPCTarget::getTargetOS() === 'Linux') { + $env['SPC_COMPILER_EXTRA'] = '-lstdc++'; + } if ($this->patchBeforeSharedPhpize()) { logger()->info("Extension [{$this->getName()}] patched before shared phpize"); @@ -406,6 +434,7 @@ public function buildUnixShared(): void // prepare configure args shell()->cd($this->source_dir) ->setEnv($env) + ->appendEnv($this->getExtraEnv()) ->exec(BUILD_BIN_PATH . '/phpize'); if ($this->patchBeforeSharedConfigure()) { @@ -414,25 +443,20 @@ public function buildUnixShared(): void shell()->cd($this->source_dir) ->setEnv($env) + ->appendEnv($this->getExtraEnv()) ->exec( './configure ' . $this->getUnixConfigureArg(true) . ' --with-php-config=' . BUILD_BIN_PATH . '/php-config ' . '--enable-shared --disable-static' ); - // some extensions don't define their dependencies well, this patch is only needed for a few - FileSystem::replaceFileRegex( - $this->source_dir . '/Makefile', - '/^(.*_SHARED_LIBADD\s*=.*)$/m', - '$1 ' . $staticLibString - ); - if ($this->patchBeforeSharedMake()) { logger()->info("Extension [{$this->getName()}] patched before shared make"); } shell()->cd($this->source_dir) ->setEnv($env) + ->appendEnv($this->getExtraEnv()) ->exec('make clean') ->exec('make -j' . $this->builder->concurrency) ->exec('make install'); @@ -506,38 +530,35 @@ protected function addExtensionDependency(string $name, bool $optional = false): } } + protected function getExtraEnv(): array + { + return []; + } + /** - * Get required static and shared libraries as a pair of strings in format -l{libname} -l{libname2} + * Splits a given string of library flags into static and shared libraries. * - * @return array [staticLibString, sharedLibString] + * @param string $allLibs A space-separated string of library flags (e.g., -lxyz). + * @return array an array containing two elements: the first is a space-separated string + * of static library flags, and the second is a space-separated string + * of shared library flags */ - protected function getStaticAndSharedLibs(): array + protected function splitLibsIntoStaticAndShared(string $allLibs): array { - $config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true); - $sharedLibString = ''; $staticLibString = ''; - $staticLibs = $this->getLibFilesString(); - $staticLibs = str_replace([BUILD_LIB_PATH . '/lib', '.a'], ['-l', ''], $staticLibs); - $staticLibs = explode('-l', $staticLibs . ' ' . $config['libs']); - foreach ($staticLibs as $lib) { - $lib = trim($lib); - if ($lib === '') { - continue; + $sharedLibString = ''; + $libs = explode(' ', $allLibs); + foreach ($libs as $lib) { + $staticLib = BUILD_LIB_PATH . '/lib' . str_replace('-l', '', $lib) . '.a'; + if (str_starts_with($lib, BUILD_LIB_PATH . '/lib') && str_ends_with($lib, '.a')) { + $staticLib = $lib; } - $static_lib = 'lib' . $lib . '.a'; - if (file_exists(BUILD_LIB_PATH . '/' . $static_lib) && !str_contains($static_lib, 'libphp')) { - if (!str_contains($staticLibString, '-l' . $lib . ' ')) { - $staticLibString .= '-l' . $lib . ' '; - } - } elseif (!str_contains($sharedLibString, '-l' . $lib . ' ')) { - $sharedLibString .= '-l' . $lib . ' '; + if ($lib === '-lphp' || !file_exists($staticLib)) { + $sharedLibString .= " {$lib}"; + } else { + $staticLibString .= " {$lib}"; } } - // move -lstdc++ to static libraries because centos 7 the shared libstdc++ is incomplete - if (str_contains((string) getenv('PATH'), 'rh/devtoolset-10')) { - $staticLibString .= ' -lstdc++'; - $sharedLibString = str_replace('-lstdc++', '', $sharedLibString); - } return [trim($staticLibString), trim($sharedLibString)]; } diff --git a/src/SPC/builder/extension/amqp.php b/src/SPC/builder/extension/amqp.php index 7e0ea2dec..d4df55044 100644 --- a/src/SPC/builder/extension/amqp.php +++ b/src/SPC/builder/extension/amqp.php @@ -13,6 +13,7 @@ class amqp extends Extension { public function patchBeforeMake(): bool { + $patched = parent::patchBeforeMake(); if (PHP_OS_FAMILY === 'Windows') { FileSystem::replaceFileRegex(BUILD_INCLUDE_PATH . '\amqp.h', '/^#warning.*/m', ''); FileSystem::replaceFileRegex(BUILD_INCLUDE_PATH . '\amqp_framing.h', '/^#warning.*/m', ''); @@ -20,7 +21,7 @@ public function patchBeforeMake(): bool FileSystem::replaceFileRegex(BUILD_INCLUDE_PATH . '\amqp_tcp_socket.h', '/^#warning.*/m', ''); return true; } - return false; + return $patched; } public function getUnixConfigureArg(bool $shared = false): string diff --git a/src/SPC/builder/extension/curl.php b/src/SPC/builder/extension/curl.php index 9ac3fd9b5..a9079830d 100644 --- a/src/SPC/builder/extension/curl.php +++ b/src/SPC/builder/extension/curl.php @@ -60,13 +60,14 @@ public function patchBeforeConfigure(): bool public function patchBeforeMake(): bool { + $patched = parent::patchBeforeMake(); $extra_libs = getenv('SPC_EXTRA_LIBS'); if ($this->builder instanceof WindowsBuilder && !str_contains($extra_libs, 'secur32.lib')) { $extra_libs .= ' secur32.lib'; putenv('SPC_EXTRA_LIBS=' . trim($extra_libs)); return true; } - return false; + return $patched; } public function patchBeforeSharedConfigure(): bool diff --git a/src/SPC/builder/extension/event.php b/src/SPC/builder/extension/event.php index 95d9aba60..d2d063b22 100644 --- a/src/SPC/builder/extension/event.php +++ b/src/SPC/builder/extension/event.php @@ -41,10 +41,12 @@ public function patchBeforeConfigure(): bool */ public function patchBeforeMake(): bool { + $patched = parent::patchBeforeMake(); // Prevent event extension compile error on macOS if ($this->builder instanceof MacOSBuilder) { FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_OPENPTY 1$/m', ''); + return true; } - return true; + return $patched; } } diff --git a/src/SPC/builder/extension/grpc.php b/src/SPC/builder/extension/grpc.php index 6ddfb0085..bda360045 100644 --- a/src/SPC/builder/extension/grpc.php +++ b/src/SPC/builder/extension/grpc.php @@ -50,6 +50,7 @@ public function patchBeforeConfigure(): bool public function patchBeforeMake(): bool { + parent::patchBeforeMake(); // add -Wno-strict-prototypes GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . ' -Wno-strict-prototypes'); return true; diff --git a/src/SPC/builder/extension/imagick.php b/src/SPC/builder/extension/imagick.php index 514e79b32..cd5542cf4 100644 --- a/src/SPC/builder/extension/imagick.php +++ b/src/SPC/builder/extension/imagick.php @@ -15,4 +15,14 @@ public function getUnixConfigureArg(bool $shared = false): string $disable_omp = ' ac_cv_func_omp_pause_resource_all=no'; return '--with-imagick=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $disable_omp; } + + protected function splitLibsIntoStaticAndShared(string $allLibs): array + { + [$static, $shared] = parent::splitLibsIntoStaticAndShared($allLibs); + if (str_contains(getenv('PATH'), 'rh/devtoolset-10')) { + $static .= ' -l:libstdc++.a'; + $shared = str_replace('-lstdc++', '', $shared); + } + return [clean_spaces($static), clean_spaces($shared)]; + } } diff --git a/src/SPC/builder/extension/imap.php b/src/SPC/builder/extension/imap.php index f36ffb1e3..81bb31648 100644 --- a/src/SPC/builder/extension/imap.php +++ b/src/SPC/builder/extension/imap.php @@ -45,8 +45,9 @@ public function getUnixConfigureArg(bool $shared = false): string public function patchBeforeMake(): bool { + $patched = parent::patchBeforeMake(); if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::isMuslDist()) { - return false; + return $patched; } $extra_libs = trim(getenv('SPC_EXTRA_LIBS') . ' -lcrypt'); f_putenv('SPC_EXTRA_LIBS=' . $extra_libs); diff --git a/src/SPC/builder/extension/openssl.php b/src/SPC/builder/extension/openssl.php index fc4831f30..3ef4998f2 100644 --- a/src/SPC/builder/extension/openssl.php +++ b/src/SPC/builder/extension/openssl.php @@ -12,6 +12,7 @@ class openssl extends Extension { public function patchBeforeMake(): bool { + $patched = parent::patchBeforeMake(); // patch openssl3 with php8.0 bug if ($this->builder->getPHPVersionID() < 80100) { $openssl_c = file_get_contents(SOURCE_PATH . '/php-src/ext/openssl/openssl.c'); @@ -20,7 +21,7 @@ public function patchBeforeMake(): bool return true; } - return false; + return $patched; } public function getUnixConfigureArg(bool $shared = false): string diff --git a/src/SPC/builder/extension/opentelemetry.php b/src/SPC/builder/extension/opentelemetry.php index 4b0829e01..4b3c5aaaf 100644 --- a/src/SPC/builder/extension/opentelemetry.php +++ b/src/SPC/builder/extension/opentelemetry.php @@ -34,6 +34,7 @@ public function patchBeforeBuildconf(): bool public function patchBeforeMake(): bool { + parent::patchBeforeMake(); // add -Wno-strict-prototypes GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . ' -Wno-strict-prototypes'); return true; diff --git a/src/SPC/builder/extension/password_argon2.php b/src/SPC/builder/extension/password_argon2.php index fd45ca7e8..4a7fd9d07 100644 --- a/src/SPC/builder/extension/password_argon2.php +++ b/src/SPC/builder/extension/password_argon2.php @@ -23,4 +23,23 @@ public function runCliCheckUnix(): void throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check'); } } + + public function patchBeforeMake(): bool + { + $patched = parent::patchBeforeMake(); + if ($this->builder->getLib('libsodium') !== null) { + $extraLibs = getenv('SPC_EXTRA_LIBS'); + if ($extraLibs !== false) { + $extraLibs = str_replace( + [BUILD_LIB_PATH . '/libargon2.a', BUILD_LIB_PATH . '/libsodium.a'], + ['', BUILD_LIB_PATH . '/libargon2.a ' . BUILD_LIB_PATH . '/libsodium.a'], + $extraLibs, + ); + $extraLibs = trim(preg_replace('/\s+/', ' ', $extraLibs)); // normalize spacing + f_putenv('SPC_EXTRA_LIBS=' . $extraLibs); + return true; + } + } + return $patched; + } } diff --git a/src/SPC/builder/extension/pgsql.php b/src/SPC/builder/extension/pgsql.php index a70c1fb72..e1788cfaa 100644 --- a/src/SPC/builder/extension/pgsql.php +++ b/src/SPC/builder/extension/pgsql.php @@ -57,4 +57,11 @@ public function getWindowsConfigureArg(bool $shared = false): string } return '--with-pgsql=' . BUILD_ROOT_PATH; } + + protected function getExtraEnv(): array + { + return [ + 'CFLAGS' => '-Wno-int-conversion', + ]; + } } diff --git a/src/SPC/builder/extension/rdkafka.php b/src/SPC/builder/extension/rdkafka.php index cb1792c5f..575ab3092 100644 --- a/src/SPC/builder/extension/rdkafka.php +++ b/src/SPC/builder/extension/rdkafka.php @@ -20,6 +20,7 @@ public function patchBeforeBuildconf(): bool public function patchBeforeMake(): bool { + parent::patchBeforeMake(); // when compiling rdkafka with inline builds, it shows some errors, I don't know why. FileSystem::replaceFileStr( SOURCE_PATH . '/php-src/ext/rdkafka/rdkafka.c', diff --git a/src/SPC/builder/extension/readline.php b/src/SPC/builder/extension/readline.php index dc4d15f7c..939484daf 100644 --- a/src/SPC/builder/extension/readline.php +++ b/src/SPC/builder/extension/readline.php @@ -27,7 +27,12 @@ public function patchBeforeConfigure(): bool public function getUnixConfigureArg(bool $shared = false): string { - return '--without-libedit --with-readline=' . BUILD_ROOT_PATH; + $enable = '--without-libedit --with-readline=' . BUILD_ROOT_PATH; + if ($this->builder->getPHPVersionID() < 84000) { + // the check uses `char rl_pending_input()` instead of `extern int rl_pending_input`, which makes LTO fail + $enable .= ' ac_cv_lib_readline_rl_pending_input=yes'; + } + return $enable; } public function buildUnixShared(): void diff --git a/src/SPC/builder/extension/redis.php b/src/SPC/builder/extension/redis.php index 75158ad70..952b004e6 100644 --- a/src/SPC/builder/extension/redis.php +++ b/src/SPC/builder/extension/redis.php @@ -13,8 +13,13 @@ class redis extends Extension public function getUnixConfigureArg(bool $shared = false): string { $arg = '--enable-redis'; - $arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session'; - $arg .= $this->builder->getExt('igbinary') ? ' --enable-redis-igbinary' : ' --disable-redis-igbinary'; + if ($this->isBuildStatic()) { + $arg .= $this->builder->getExt('session')?->isBuildStatic() ? ' --enable-redis-session' : ' --disable-redis-session'; + $arg .= $this->builder->getExt('igbinary')?->isBuildStatic() ? ' --enable-redis-igbinary' : ' --disable-redis-igbinary'; + } else { + $arg .= $this->builder->getExt('session') ? ' --enable-redis-session' : ' --disable-redis-session'; + $arg .= $this->builder->getExt('igbinary') ? ' --enable-redis-igbinary' : ' --disable-redis-igbinary'; + } if ($this->builder->getLib('zstd')) { $arg .= ' --enable-redis-zstd --with-libzstd="' . BUILD_ROOT_PATH . '"'; } diff --git a/src/SPC/builder/extension/swoole.php b/src/SPC/builder/extension/swoole.php index 6ce006b97..00085e8bf 100644 --- a/src/SPC/builder/extension/swoole.php +++ b/src/SPC/builder/extension/swoole.php @@ -14,13 +14,18 @@ class swoole extends Extension { public function patchBeforeMake(): bool { + $patched = parent::patchBeforeMake(); if ($this->builder instanceof MacOSBuilder) { // Fix swoole with event extension conflict bug $util_path = shell()->execWithResult('xcrun --show-sdk-path', false)[1][0] . '/usr/include/util.h'; - FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/ext/swoole/thirdparty/php/standard/proc_open.cc', 'include ', 'include "' . $util_path . '"'); + FileSystem::replaceFileStr( + "{$this->source_dir}/thirdparty/php/standard/proc_open.cc", + 'include ', + 'include "' . $util_path . '"', + ); return true; } - return false; + return $patched; } public function getExtVersion(): ?string diff --git a/src/SPC/builder/extension/uv.php b/src/SPC/builder/extension/uv.php index 8c54e007c..d803a7506 100644 --- a/src/SPC/builder/extension/uv.php +++ b/src/SPC/builder/extension/uv.php @@ -20,6 +20,7 @@ public function validate(): void public function patchBeforeSharedMake(): bool { + parent::patchBeforeSharedMake(); if (PHP_OS_FAMILY !== 'Linux' || arch2gnu(php_uname('m')) !== 'aarch64') { return false; } diff --git a/src/SPC/builder/extension/xlswriter.php b/src/SPC/builder/extension/xlswriter.php index 878168adf..24d32d947 100644 --- a/src/SPC/builder/extension/xlswriter.php +++ b/src/SPC/builder/extension/xlswriter.php @@ -27,16 +27,17 @@ public function getWindowsConfigureArg(bool $shared = false): string public function patchBeforeMake(): bool { + $patched = parent::patchBeforeMake(); if (PHP_OS_FAMILY === 'Windows') { // fix windows build with openssl extension duplicate symbol bug SourcePatcher::patchFile('spc_fix_xlswriter_win32.patch', $this->source_dir); $content = file_get_contents($this->source_dir . '/library/libxlsxwriter/src/theme.c'); $bom = pack('CCC', 0xEF, 0xBB, 0xBF); - if (substr($content, 0, 3) !== $bom) { + if (!str_starts_with($content, $bom)) { file_put_contents($this->source_dir . '/library/libxlsxwriter/src/theme.c', $bom . $content); } return true; } - return false; + return $patched; } } diff --git a/src/SPC/builder/freebsd/BSDBuilder.php b/src/SPC/builder/freebsd/BSDBuilder.php index 3e9cc80bd..ba04675ce 100644 --- a/src/SPC/builder/freebsd/BSDBuilder.php +++ b/src/SPC/builder/freebsd/BSDBuilder.php @@ -198,7 +198,7 @@ protected function buildMicro(): void ->exec("make -j{$this->concurrency} {$vars} micro"); if (!$this->getOption('no-strip', false)) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/micro')->exec('strip --strip-all micro.sfx'); + shell()->cd(SOURCE_PATH . '/php-src/sapi/micro')->exec('strip --strip-unneeded micro.sfx'); } $this->deployBinary(BUILD_TARGET_MICRO); diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 85cda2a1b..28edeb223 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -31,17 +31,11 @@ public function __construct(array $options = []) GlobalEnvManager::afterInit(); // concurrency - $this->concurrency = intval(getenv('SPC_CONCURRENCY')); + $this->concurrency = (int) getenv('SPC_CONCURRENCY'); // cflags $this->arch_c_flags = getenv('SPC_DEFAULT_C_FLAGS'); $this->arch_cxx_flags = getenv('SPC_DEFAULT_CXX_FLAGS'); - - // cross-compiling is not supported yet - /*if (php_uname('m') !== $this->arch) { - $this->cross_compile_prefix = SystemUtil::getCrossCompilePrefix($this->cc, $this->arch); - logger()->info('using cross compile prefix: ' . $this->cross_compile_prefix); - $this->configure_env .= " CROSS_COMPILE='{$this->cross_compile_prefix}'"; - }*/ + $this->arch_ld_flags = getenv('SPC_DEFAULT_LD_FLAGS'); // create pkgconfig and include dir (some libs cannot create them automatically) f_mkdir(BUILD_LIB_PATH . '/pkgconfig', recursive: true); @@ -80,7 +74,9 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void $zts = ''; } $disable_jit = $this->getOption('disable-opcache-jit', false) ? '--disable-opcache-jit ' : ''; - + if (!$disable_jit && $this->getExt('opcache')) { + f_putenv('SPC_COMPILER_EXTRA=-fno-sanitize=undefined'); + } $config_file_path = $this->getOption('with-config-file-path', false) ? ('--with-config-file-path=' . $this->getOption('with-config-file-path') . ' ') : ''; $config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ? @@ -92,27 +88,25 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void $enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; $enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP; - $mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : ''; // prepare build php envs - $envs_build_php = SystemUtil::makeEnvVarString([ + $php_configure_env = SystemUtil::makeEnvVarString([ 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'), - 'CPPFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS'), + 'CPPFLAGS' => '-I' . BUILD_INCLUDE_PATH, 'LDFLAGS' => '-L' . BUILD_LIB_PATH, - 'LIBS' => $mimallocLibs . getenv('SPC_CMD_VAR_PHP_CONFIGURE_LIBS'), + // 'LIBS' => SPCTarget::getRuntimeLibs(), // do not pass static libraries here yet, they may contain polyfills for libc functions! ]); - // process micro upx patch if micro sapi enabled - if ($enableMicro) { - if (version_compare($this->getMicroVersion(), '0.2.0') < 0) { - // for phpmicro 0.1.x - $this->processMicroUPXLegacy(); - } - // micro latest needs do strip and upx pack later (strip, upx, cut binary manually supported) + $embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static'; + if ($embed_type !== 'static' && SPCTarget::isStatic()) { + throw new WrongUsageException( + 'Linux does not support loading shared libraries when linking libc statically. ' . + 'Change SPC_CMD_VAR_PHP_EMBED_TYPE to static.' + ); } - $embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static'; shell()->cd(SOURCE_PATH . '/php-src') ->exec( + $php_configure_env . ' ' . getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' . ($enableCli ? '--enable-cli ' : '--disable-cli ') . ($enableFpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') . @@ -124,8 +118,7 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void $json_74 . $zts . $maxExecutionTimers . - $this->makeStaticExtensionArgs() . - ' ' . $envs_build_php . ' ' + $this->makeStaticExtensionArgs() . ' ' ); $this->emitPatchPoint('before-php-make'); @@ -178,12 +171,12 @@ protected function buildCli(): void ->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} cli"); + if (!$this->getOption('no-strip', false)) { + shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-unneeded php'); + } if ($this->getOption('with-upx-pack')) { shell()->cd(SOURCE_PATH . '/php-src/sapi/cli') - ->exec('strip --strip-all php') ->exec(getenv('UPX_EXEC') . ' --best php'); - } elseif (!$this->getOption('no-strip', false)) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-all php'); } $this->deployBinary(BUILD_TARGET_CLI); @@ -241,12 +234,12 @@ protected function buildFpm(): void ->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} fpm"); + if (!$this->getOption('no-strip', false)) { + shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-unneeded php-fpm'); + } if ($this->getOption('with-upx-pack')) { shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm') - ->exec('strip --strip-all php-fpm') ->exec(getenv('UPX_EXEC') . ' --best php-fpm'); - } elseif (!$this->getOption('no-strip', false)) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-all php-fpm'); } $this->deployBinary(BUILD_TARGET_FPM); } @@ -266,34 +259,79 @@ protected function buildEmbed(): void ->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install"); $ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: ''; + $libDir = BUILD_LIB_PATH; + $modulesDir = BUILD_MODULES_PATH; + $libphpSo = "{$libDir}/libphp.so"; + $realLibName = 'libphp.so'; + $cwd = getcwd(); + if (preg_match('/-release\s+(\S+)/', $ldflags, $matches)) { $release = $matches[1]; - $realLibName = 'libphp-' . $release . '.so'; - $realLib = BUILD_LIB_PATH . '/' . $realLibName; - rename(BUILD_LIB_PATH . '/libphp.so', $realLib); - $cwd = getcwd(); - chdir(BUILD_LIB_PATH); - symlink($realLibName, 'libphp.so'); - chdir(BUILD_MODULES_PATH); - foreach ($this->getExts() as $ext) { - if (!$ext->isBuildShared()) { - continue; + $realLibName = "libphp-{$release}.so"; + $libphpRelease = "{$libDir}/{$realLibName}"; + if (!file_exists($libphpRelease) && file_exists($libphpSo)) { + rename($libphpSo, $libphpRelease); + } + if (file_exists($libphpRelease)) { + chdir($libDir); + if (file_exists($libphpSo)) { + unlink($libphpSo); } - $name = $ext->getName(); - $versioned = "{$name}-{$release}.so"; - $unversioned = "{$name}.so"; - if (is_file(BUILD_MODULES_PATH . "/{$versioned}")) { - rename(BUILD_MODULES_PATH . "/{$versioned}", BUILD_MODULES_PATH . "/{$unversioned}"); - shell()->cd(BUILD_MODULES_PATH) - ->exec(sprintf( + symlink($realLibName, 'libphp.so'); + shell()->exec(sprintf( + 'patchelf --set-soname %s %s', + escapeshellarg($realLibName), + escapeshellarg($libphpRelease) + )); + } + if (is_dir($modulesDir)) { + chdir($modulesDir); + foreach ($this->getExts() as $ext) { + if (!$ext->isBuildShared()) { + continue; + } + $name = $ext->getName(); + $versioned = "{$name}-{$release}.so"; + $unversioned = "{$name}.so"; + $src = "{$modulesDir}/{$versioned}"; + $dst = "{$modulesDir}/{$unversioned}"; + if (is_file($src)) { + rename($src, $dst); + shell()->exec(sprintf( 'patchelf --set-soname %s %s', escapeshellarg($unversioned), - escapeshellarg($unversioned) + escapeshellarg($dst) )); + } } } chdir($cwd); } + + $target = "{$libDir}/{$realLibName}"; + if (file_exists($target)) { + [, $output] = shell()->execWithResult('readelf -d ' . escapeshellarg($target)); + $output = join("\n", $output); + if (preg_match('/SONAME.*\[(.+)]/', $output, $sonameMatch)) { + $currentSoname = $sonameMatch[1]; + if ($currentSoname !== basename($target)) { + shell()->exec(sprintf( + 'patchelf --set-soname %s %s', + escapeshellarg(basename($target)), + escapeshellarg($target) + )); + } + } + } + + if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') { + $AR = getenv('AR') ?: 'ar'; + f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a'); + } + + if (!$this->getOption('no-strip', false) && file_exists(BUILD_LIB_PATH . '/' . $realLibName)) { + shell()->cd(BUILD_LIB_PATH)->exec("strip --strip-unneeded {$realLibName}"); + } $this->patchPhpScripts(); } @@ -305,58 +343,22 @@ private function getMakeExtraVars(): array return [ 'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), 'EXTRA_LIBS' => $config['libs'], + 'EXTRA_LDFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), 'EXTRA_LDFLAGS_PROGRAM' => "-L{$lib} {$static} -pie", ]; } /** - * Apply option --no-strip and --with-upx-pack for micro sapi (only for phpmicro 0.1.x) + * Strip micro.sfx for Linux. + * The micro.sfx does not support UPX directly, but we can remove UPX-info segment to adapt. + * This will also make micro.sfx with upx-packed more like a malware fore antivirus :( * - * @throws FileSystemException + * @throws RuntimeException */ - private function processMicroUPXLegacy(): void - { - // upx pack and strip for micro - // but always restore Makefile.frag.bak first - if (file_exists(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag.bak')) { - copy(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag.bak', SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag'); - } - if ($this->getOption('with-upx-pack', false)) { - // judge $(MAKE) micro_2s_objs SFX_FILESIZE=`$(STAT_SIZE) $(SAPI_MICRO_PATH)` count - // if 2, replace src/globals/extra/micro-triple-Makefile.frag file content - if (substr_count(FileSystem::readFile(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag'), '$(MAKE) micro_2s_objs SFX_FILESIZE=`$(STAT_SIZE) $(SAPI_MICRO_PATH)`') === 2) { - // bak first - copy(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag', SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag.bak'); - // replace Makefile.frag content - FileSystem::writeFile(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag', FileSystem::readFile(ROOT_DIR . '/src/globals/extra/micro-triple-Makefile.frag')); - } - // with upx pack always need strip - FileSystem::replaceFileRegex( - SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag', - '/POST_MICRO_BUILD_COMMANDS=.*/', - 'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH) && ' . getenv('UPX_EXEC') . ' --best \$(SAPI_MICRO_PATH)', - ); - } elseif (!$this->getOption('no-strip', false)) { - // not-no-strip means strip (default behavior) - FileSystem::replaceFileRegex( - SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag', - '/POST_MICRO_BUILD_COMMANDS=.*/', - 'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH)', - ); - } else { - // just no strip - FileSystem::replaceFileRegex( - SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag', - '/POST_MICRO_BUILD_COMMANDS=.*/', - 'POST_MICRO_BUILD_COMMANDS=true', - ); - } - } - private function processMicroUPX(): void { if (version_compare($this->getMicroVersion(), '0.2.0') >= 0 && !$this->getOption('no-strip', false)) { - shell()->exec('strip --strip-all ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx'); + shell()->exec('strip --strip-unneeded ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx'); if ($this->getOption('with-upx-pack')) { // strip first diff --git a/src/SPC/builder/linux/SystemUtil.php b/src/SPC/builder/linux/SystemUtil.php index 0f6d35781..056af090d 100644 --- a/src/SPC/builder/linux/SystemUtil.php +++ b/src/SPC/builder/linux/SystemUtil.php @@ -5,7 +5,6 @@ namespace SPC\builder\linux; use SPC\builder\traits\UnixSystemUtilTrait; -use SPC\exception\RuntimeException; class SystemUtil { @@ -75,37 +74,6 @@ public static function getCpuCount(): int return $ncpu; } - /** - * @throws RuntimeException - */ - public static function getCCType(string $cc): string - { - return match (true) { - str_ends_with($cc, 'c++'), str_ends_with($cc, 'cc'), str_ends_with($cc, 'g++'), str_ends_with($cc, 'gcc') => 'gcc', - $cc === 'clang++', $cc === 'clang', str_starts_with($cc, 'musl-clang') => 'clang', - default => throw new RuntimeException("unknown cc type: {$cc}"), - }; - } - - /** - * @throws RuntimeException - * @noinspection PhpUnused - */ - public static function getCrossCompilePrefix(string $cc, string $arch): string - { - return match (static::getCCType($cc)) { - // guessing clang toolchains - 'clang' => match ($arch) { - 'x86_64' => 'x86_64-linux-gnu-', - 'arm64', 'aarch64' => 'aarch64-linux-gnu-', - default => throw new RuntimeException('unsupported arch: ' . $arch), - }, - // remove gcc postfix - 'gcc' => str_replace('-cc', '', str_replace('-gcc', '', $cc)) . '-', - default => throw new RuntimeException('unsupported cc'), - }; - } - public static function findStaticLib(string $name): ?array { $paths = getenv('LIBPATH'); @@ -188,7 +156,7 @@ public static function getSupportedDistros(): array /** * Get libc version string from ldd */ - public static function getLibcVersionIfExists(string $libc): ?string + public static function getLibcVersionIfExists(?string $libc = null): ?string { if (self::$libc_version !== null) { return self::$libc_version; @@ -211,8 +179,11 @@ public static function getLibcVersionIfExists(string $libc): ?string if ($libc === 'musl') { if (self::isMuslDist()) { $result = shell()->execWithResult('ldd 2>&1', false); - } else { + } elseif (is_file('/usr/local/musl/lib/libc.so')) { $result = shell()->execWithResult('/usr/local/musl/lib/libc.so 2>&1', false); + } else { + $arch = php_uname('m'); + $result = shell()->execWithResult("/lib/ld-musl-{$arch}.so.1 2>&1", false); } // Match Version * line // match ldd version: "Version 1.2.3" match 1.2.3 diff --git a/src/SPC/builder/linux/library/libffi.php b/src/SPC/builder/linux/library/libffi.php index ecb3e1422..e47d3d232 100644 --- a/src/SPC/builder/linux/library/libffi.php +++ b/src/SPC/builder/linux/library/libffi.php @@ -18,14 +18,8 @@ class libffi extends LinuxLibraryBase */ public function build(): void { - $arch = getenv('SPC_ARCH'); UnixAutoconfExecutor::create($this) - ->configure( - "--host={$arch}-unknown-linux", - "--target={$arch}-unknown-linux", - "--libdir={$this->getLibDir()}" - ) - ->make(); + ->configure()->make(); if (is_file(BUILD_ROOT_PATH . '/lib64/libffi.a')) { copy(BUILD_ROOT_PATH . '/lib64/libffi.a', BUILD_ROOT_PATH . '/lib/libffi.a'); diff --git a/src/SPC/builder/linux/library/libmemcached.php b/src/SPC/builder/linux/library/libmemcached.php index fb09b52ab..d1e35bfc9 100644 --- a/src/SPC/builder/linux/library/libmemcached.php +++ b/src/SPC/builder/linux/library/libmemcached.php @@ -12,6 +12,8 @@ class libmemcached extends LinuxLibraryBase public function build(): void { - UnixCMakeExecutor::create($this)->build(); + UnixCMakeExecutor::create($this) + ->addConfigureArgs('-DCMAKE_INSTALL_RPATH=""') + ->build(); } } diff --git a/src/SPC/builder/linux/library/openssl.php b/src/SPC/builder/linux/library/openssl.php index 51633ac23..161182cba 100644 --- a/src/SPC/builder/linux/library/openssl.php +++ b/src/SPC/builder/linux/library/openssl.php @@ -21,7 +21,6 @@ namespace SPC\builder\linux\library; -use SPC\builder\linux\SystemUtil; use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; @@ -62,8 +61,6 @@ public function build(): void $ex_lib = trim($ex_lib); - $clang_postfix = SystemUtil::getCCType(getenv('CC')) === 'clang' ? '-clang' : ''; - shell()->cd($this->source_dir)->initializeEnv($this) ->exec( "{$env} ./Configure no-shared {$extra} " . @@ -71,8 +68,9 @@ public function build(): void '--libdir=' . BUILD_LIB_PATH . ' ' . '--openssldir=/etc/ssl ' . "{$zlib_extra}" . + 'enable-pie ' . 'no-legacy ' . - "linux-{$arch}{$clang_postfix}" + "linux-{$arch}" ) ->exec('make clean') ->exec("make -j{$this->builder->concurrency} CNF_EX_LIBS=\"{$ex_lib}\"") diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index 9b131b9d6..bd152cb7d 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -38,6 +38,7 @@ public function __construct(array $options = []) // cflags $this->arch_c_flags = getenv('SPC_DEFAULT_C_FLAGS'); $this->arch_cxx_flags = getenv('SPC_DEFAULT_CXX_FLAGS'); + $this->arch_ld_flags = getenv('SPC_DEFAULT_LD_FLAGS'); // create pkgconfig and include dir (some libs cannot create them automatically) f_mkdir(BUILD_LIB_PATH . '/pkgconfig', recursive: true); @@ -112,12 +113,10 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void $enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP; // prepare build php envs - $mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : ''; $envs_build_php = SystemUtil::makeEnvVarString([ 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'), - 'CPPFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS'), + 'CPPFLAGS' => '-I' . BUILD_INCLUDE_PATH, 'LDFLAGS' => '-L' . BUILD_LIB_PATH, - 'LIBS' => $mimallocLibs . getenv('SPC_CMD_VAR_PHP_CONFIGURE_LIBS'), ]); if ($this->getLib('postgresql')) { @@ -269,8 +268,8 @@ protected function buildEmbed(): void ->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install"); if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') { - shell()->cd(SOURCE_PATH . '/php-src') - ->exec('ar -t ' . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 ar d " . BUILD_LIB_PATH . '/libphp.a'); + $AR = getenv('AR') ?: 'ar'; + f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a'); } $this->patchPhpScripts(); } diff --git a/src/SPC/builder/traits/UnixLibraryTrait.php b/src/SPC/builder/traits/UnixLibraryTrait.php index 1d69f82ae..c45d45b22 100644 --- a/src/SPC/builder/traits/UnixLibraryTrait.php +++ b/src/SPC/builder/traits/UnixLibraryTrait.php @@ -92,27 +92,31 @@ public function getLibExtraCFlags(): string { $env = getenv($this->getSnakeCaseName() . '_CFLAGS') ?: ''; if (!str_contains($env, $this->builder->arch_c_flags)) { - $env .= $this->builder->arch_c_flags; + $env .= ' ' . $this->builder->arch_c_flags; } - return $env; + return trim($env); } - public function getLibExtraLdFlags(): string + public function getLibExtraCXXFlags(): string { - return getenv($this->getSnakeCaseName() . '_LDFLAGS') ?: ''; + $env = getenv($this->getSnakeCaseName() . '_CXXFLAGS') ?: ''; + if (!str_contains($env, $this->builder->arch_cxx_flags)) { + $env .= ' ' . $this->builder->arch_cxx_flags; + } + return trim($env); } - public function getLibExtraLibs(): string + public function getLibExtraLdFlags(): string { - return getenv($this->getSnakeCaseName() . '_LIBS') ?: ''; + $env = getenv($this->getSnakeCaseName() . '_LDFLAGS') ?: ''; + if (!str_contains($env, $this->builder->arch_ld_flags)) { + $env .= ' ' . $this->builder->arch_ld_flags; + } + return trim($env); } - public function getLibExtraCXXFlags(): string + public function getLibExtraLibs(): string { - $env = getenv($this->getSnakeCaseName() . '_CXXFLAGS') ?: ''; - if (!str_contains($env, $this->builder->arch_cxx_flags)) { - $env .= $this->builder->arch_cxx_flags; - } - return $env; + return getenv($this->getSnakeCaseName() . '_LIBS') ?: ''; } } diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 27f9c01f4..b69aba2eb 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -12,7 +12,9 @@ use SPC\store\CurlHook; use SPC\store\Downloader; use SPC\store\FileSystem; +use SPC\store\pkg\GoXcaddy; use SPC\util\DependencyUtil; +use SPC\util\GlobalEnvManager; use SPC\util\SPCConfigUtil; use SPC\util\SPCTarget; @@ -24,6 +26,9 @@ abstract class UnixBuilderBase extends BuilderBase /** @var string C++ flags */ public string $arch_cxx_flags; + /** @var string LD flags */ + public string $arch_ld_flags; + public function proveLibs(array $sorted_libraries): void { // search all supported libs @@ -122,11 +127,7 @@ protected function sanityCheck(int $build_target): void if (SPCTarget::isStatic()) { $lens .= ' -static'; } - [$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens); - if ($ret !== 0) { - throw new RuntimeException('embed failed sanity check: build failed. Error message: ' . implode("\n", $out)); - } - // if someone changed to --enable-embed=shared, we need to add LD_LIBRARY_PATH + // if someone changed to EMBED_TYPE=shared, we need to add LD_LIBRARY_PATH if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { if (PHP_OS_FAMILY === 'Darwin') { $ext_path = 'DYLD_LIBRARY_PATH=' . BUILD_LIB_PATH . ':$DYLD_LIBRARY_PATH '; @@ -211,7 +212,6 @@ protected function patchPhpScripts(): void 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#'); - FileSystem::replaceFileStr(BUILD_LIB_PATH . '/php/build/phpize.m4', 'test "[$]$1" = "no" && $1=yes', '# test "[$]$1" = "no" && $1=yes'); } // patch php-config if (file_exists(BUILD_BIN_PATH . '/php-config')) { @@ -237,18 +237,6 @@ protected function patchPhpScripts(): void */ protected function buildFrankenphp(): void { - $os = match (PHP_OS_FAMILY) { - 'Linux' => 'linux', - 'Windows' => 'win', - 'Darwin' => 'macos', - 'BSD' => 'freebsd', - default => throw new RuntimeException('Unsupported OS: ' . PHP_OS_FAMILY), - }; - $arch = arch2gnu(php_uname('m')); - - // define executables for go and xcaddy - $xcaddy_exec = PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin/xcaddy"; - $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); @@ -261,30 +249,30 @@ protected function buildFrankenphp(): void $xcaddyModules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddyModules); } $lrt = PHP_OS_FAMILY === 'Linux' ? '-lrt' : ''; - $releaseInfo = json_decode(Downloader::curlExec('https://api.github.com/repos/php/frankenphp/releases/latest', retries: 3, hooks: [[CurlHook::class, 'setupGithubToken']]), true); + $releaseInfo = json_decode(Downloader::curlExec( + 'https://api.github.com/repos/php/frankenphp/releases/latest', + hooks: [[CurlHook::class, 'setupGithubToken']], + ), true); $frankenPhpVersion = $releaseInfo['tag_name']; $libphpVersion = $this->getPHPVersion(); if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { $libphpVersion = preg_replace('/\.\d$/', '', $libphpVersion); } - $debugFlags = $this->getOption('no-strip') ? "'-w -s' " : ''; + $debugFlags = $this->getOption('no-strip') ? '-w -s ' : ''; $extLdFlags = "-extldflags '-pie'"; $muslTags = ''; + $staticFlags = ''; if (SPCTarget::isStatic()) { $extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'"; $muslTags = 'static_build,'; + $staticFlags = '-static-pie'; } - $config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list, with_dependencies: true); - + $config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list); $env = [ - 'PATH' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin:" . getenv('PATH'), - 'GOROOT' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}", - 'GOBIN' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin", - 'GOPATH' => PKG_ROOT_PATH . '/go', 'CGO_ENABLED' => '1', 'CGO_CFLAGS' => $config['cflags'], - 'CGO_LDFLAGS' => "{$config['ldflags']} {$config['libs']} {$lrt}", + 'CGO_LDFLAGS' => "{$staticFlags} {$config['ldflags']} {$config['libs']} {$lrt}", 'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' . '-ldflags \"-linkmode=external ' . $extLdFlags . ' ' . $debugFlags . '-X \'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ' . @@ -292,8 +280,23 @@ protected function buildFrankenphp(): void "-tags={$muslTags}nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, ]; + foreach (GoXcaddy::getEnvironment() as $key => $value) { + if ($key === 'PATH') { + GlobalEnvManager::addPathIfNotExists($value); + } else { + $env[$key] = $value; + } + } shell()->cd(BUILD_BIN_PATH) ->setEnv($env) - ->exec("{$xcaddy_exec} build --output frankenphp {$xcaddyModules}"); + ->exec("xcaddy build --output frankenphp {$xcaddyModules}"); + + if (!$this->getOption('no-strip', false) && file_exists(BUILD_BIN_PATH . '/frankenphp')) { + if (PHP_OS_FAMILY === 'Linux') { + shell()->cd(BUILD_BIN_PATH)->exec('strip --strip-unneeded frankenphp'); + } else { // macOS doesn't understand strip-unneeded + shell()->cd(BUILD_BIN_PATH)->exec('strip -S frankenphp'); + } + } } } diff --git a/src/SPC/builder/unix/library/attr.php b/src/SPC/builder/unix/library/attr.php index 439acf38e..71fe2623d 100644 --- a/src/SPC/builder/unix/library/attr.php +++ b/src/SPC/builder/unix/library/attr.php @@ -15,6 +15,9 @@ trait attr protected function build(): void { UnixAutoconfExecutor::create($this) + ->appendEnv([ + 'CFLAGS' => '-Wno-int-conversion -Wno-implicit-function-declaration', + ]) ->exec('libtoolize --force --copy') ->exec('./autogen.sh || autoreconf -if') ->configure('--disable-nls') diff --git a/src/SPC/builder/unix/library/gettext.php b/src/SPC/builder/unix/library/gettext.php index 257fa3051..f0fdae547 100644 --- a/src/SPC/builder/unix/library/gettext.php +++ b/src/SPC/builder/unix/library/gettext.php @@ -31,7 +31,7 @@ protected function build(): void $autoconf->addConfigureArgs('--disable-threads'); } - $autoconf->configure()->make(with_clean: true); + $autoconf->configure()->make(); $this->patchLaDependencyPrefix(); } } diff --git a/src/SPC/builder/unix/library/imagemagick.php b/src/SPC/builder/unix/library/imagemagick.php index 3a6975af9..68bd88e9f 100644 --- a/src/SPC/builder/unix/library/imagemagick.php +++ b/src/SPC/builder/unix/library/imagemagick.php @@ -33,6 +33,7 @@ protected function build(): void ->optionalLib('freetype', ...ac_with_args('freetype')) ->optionalLib('bzip2', ...ac_with_args('bzlib')) ->optionalLib('libjxl', ...ac_with_args('jxl')) + ->optionalLib('jbig', ...ac_with_args('jbig')) ->addConfigureArgs( '--disable-openmp', '--without-x', diff --git a/src/SPC/builder/unix/library/ldap.php b/src/SPC/builder/unix/library/ldap.php index 18393e248..3ff72dcba 100644 --- a/src/SPC/builder/unix/library/ldap.php +++ b/src/SPC/builder/unix/library/ldap.php @@ -27,8 +27,10 @@ protected function build(): void '--disable-slapd', '--without-systemd', '--without-cyrus-sasl', + 'ac_cv_func_pthread_kill_other_threads_np=no' ) ->appendEnv([ + 'CFLAGS' => '-Wno-date-time', 'LDFLAGS' => "-L{$this->getLibDir()}", 'CPPFLAGS' => "-I{$this->getIncludeDir()}", ]) diff --git a/src/SPC/builder/unix/library/libaom.php b/src/SPC/builder/unix/library/libaom.php index ab5c22b8c..f2f50d85b 100644 --- a/src/SPC/builder/unix/library/libaom.php +++ b/src/SPC/builder/unix/library/libaom.php @@ -4,22 +4,24 @@ namespace SPC\builder\unix\library; -use SPC\exception\FileSystemException; -use SPC\exception\RuntimeException; +use SPC\toolchain\ToolchainManager; +use SPC\toolchain\ZigToolchain; use SPC\util\executor\UnixCMakeExecutor; trait libaom { - /** - * @throws RuntimeException - * @throws FileSystemException - */ protected function build(): void { + $extra = getenv('SPC_COMPILER_EXTRA'); + if (ToolchainManager::getToolchainClass() === ZigToolchain::class) { + $new = trim($extra . ' -D_GNU_SOURCE'); + f_putenv("SPC_COMPILER_EXTRA={$new}"); + } UnixCMakeExecutor::create($this) ->setBuildDir("{$this->source_dir}/builddir") ->addConfigureArgs('-DAOM_TARGET_CPU=generic') ->build(); + f_putenv("SPC_COMPILER_EXTRA={$extra}"); $this->patchPkgconfPrefix(['aom.pc']); } } diff --git a/src/SPC/builder/unix/library/libjxl.php b/src/SPC/builder/unix/library/libjxl.php index 871477b99..13f8481b1 100644 --- a/src/SPC/builder/unix/library/libjxl.php +++ b/src/SPC/builder/unix/library/libjxl.php @@ -4,6 +4,8 @@ namespace SPC\builder\unix\library; +use SPC\toolchain\ToolchainManager; +use SPC\toolchain\ZigToolchain; use SPC\util\executor\UnixCMakeExecutor; use SPC\util\SPCTarget; @@ -11,18 +13,31 @@ trait libjxl { protected function build(): void { - UnixCMakeExecutor::create($this) - ->addConfigureArgs('-DJPEGXL_ENABLE_TOOLS=OFF') - ->addConfigureArgs('-DJPEGXL_ENABLE_EXAMPLES=OFF') - ->addConfigureArgs('-DJPEGXL_ENABLE_MANPAGES=OFF') - ->addConfigureArgs('-DJPEGXL_ENABLE_BENCHMARK=OFF') - ->addConfigureArgs('-DJPEGXL_ENABLE_PLUGINS=OFF') - ->addConfigureArgs('-DJPEGXL_ENABLE_SJPOEG=ON') - ->addConfigureArgs('-DJPEGXL_ENABLE_JNI=OFF') - ->addConfigureArgs('-DJPEGXL_ENABLE_TRANSCODE_JPEG=ON') - ->addConfigureArgs('-DJPEGXL_STATIC=' . (SPCTarget::isStatic() ? 'ON' : 'OFF')) - ->addConfigureArgs('-DJPEGXL_FORCE_SYSTEM_BROTLI=ON') - ->addConfigureArgs('-DBUILD_TESTING=OFF') - ->build(); + $cmake = UnixCMakeExecutor::create($this) + ->addConfigureArgs( + '-DJPEGXL_ENABLE_TOOLS=OFF', + '-DJPEGXL_ENABLE_EXAMPLES=OFF', + '-DJPEGXL_ENABLE_MANPAGES=OFF', + '-DJPEGXL_ENABLE_BENCHMARK=OFF', + '-DJPEGXL_ENABLE_PLUGINS=OFF', + '-DJPEGXL_ENABLE_SJPEG=ON', + '-DJPEGXL_ENABLE_JNI=OFF', + '-DJPEGXL_ENABLE_TRANSCODE_JPEG=ON', + '-DJPEGXL_STATIC=' . (SPCTarget::isStatic() ? 'ON' : 'OFF'), + '-DJPEGXL_FORCE_SYSTEM_BROTLI=ON', + '-DBUILD_TESTING=OFF' + ); + + if (ToolchainManager::getToolchainClass() === ZigToolchain::class) { + $cmake->addConfigureArgs( + '-DCXX_MAVX512F_SUPPORTED:BOOL=FALSE', + '-DCXX_MAVX512DQ_SUPPORTED:BOOL=FALSE', + '-DCXX_MAVX512CD_SUPPORTED:BOOL=FALSE', + '-DCXX_MAVX512BW_SUPPORTED:BOOL=FALSE', + '-DCXX_MAVX512VL_SUPPORTED:BOOL=FALSE' + ); + } + + $cmake->build(); } } diff --git a/src/SPC/builder/unix/library/liblz4.php b/src/SPC/builder/unix/library/liblz4.php index 73108d953..2dc2b46fc 100644 --- a/src/SPC/builder/unix/library/liblz4.php +++ b/src/SPC/builder/unix/library/liblz4.php @@ -19,7 +19,11 @@ protected function build(): void { shell()->cd($this->source_dir)->initializeEnv($this) ->exec("make PREFIX='' clean") - ->exec("make -j{$this->builder->concurrency} PREFIX=''") + ->exec("make lib -j{$this->builder->concurrency} PREFIX=''"); + + FileSystem::replaceFileStr($this->source_dir . '/Makefile', '$(MAKE) -C $(PRGDIR) $@', ''); + + shell()->cd($this->source_dir) ->exec("make install PREFIX='' DESTDIR=" . BUILD_ROOT_PATH); $this->patchPkgconfPrefix(['liblz4.pc']); diff --git a/src/SPC/builder/unix/library/librdkafka.php b/src/SPC/builder/unix/library/librdkafka.php index 32f389e15..e2c3b46e9 100644 --- a/src/SPC/builder/unix/library/librdkafka.php +++ b/src/SPC/builder/unix/library/librdkafka.php @@ -6,10 +6,26 @@ use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; +use SPC\store\FileSystem; use SPC\util\executor\UnixAutoconfExecutor; trait librdkafka { + public function patchBeforeBuild(): bool + { + FileSystem::replaceFileStr( + $this->source_dir . '/lds-gen.py', + "funcs.append('rd_ut_coverage_check')", + '' + ); + FileSystem::replaceFileStr( + $this->source_dir . '/src/rd.h', + '#error "IOV_MAX not defined"', + "#define IOV_MAX 1024\n#define __GNU__" + ); + return true; + } + /** * @throws FileSystemException * @throws RuntimeException @@ -17,6 +33,7 @@ trait librdkafka protected function build(): void { UnixAutoconfExecutor::create($this) + ->appendEnv(['CFLAGS' => '-Wno-int-conversion -Wno-unused-but-set-variable -Wno-unused-variable']) ->optionalLib( 'zstd', function ($lib) { diff --git a/src/SPC/builder/unix/library/libxslt.php b/src/SPC/builder/unix/library/libxslt.php index 98ceba8db..1c584be20 100644 --- a/src/SPC/builder/unix/library/libxslt.php +++ b/src/SPC/builder/unix/library/libxslt.php @@ -35,18 +35,19 @@ protected function build(): void '--without-debugger', "--with-libxml-prefix={$this->getBuildRootPath()}", ); - if (getenv('SPC_LINUX_DEFAULT_LD_LIBRARY_PATH') && getenv('SPC_LINUX_DEFAULT_LIBRARY_PATH')) { + if (getenv('SPC_LD_LIBRARY_PATH') && getenv('SPC_LIBRARY_PATH')) { $ac->appendEnv([ - 'LD_LIBRARY_PATH' => getenv('SPC_LINUX_DEFAULT_LD_LIBRARY_PATH'), - 'LIBRARY_PATH' => getenv('SPC_LINUX_DEFAULT_LIBRARY_PATH'), + 'LD_LIBRARY_PATH' => getenv('SPC_LD_LIBRARY_PATH'), + 'LIBRARY_PATH' => getenv('SPC_LIBRARY_PATH'), ]); } $ac->configure()->make(); $this->patchPkgconfPrefix(['libexslt.pc', 'libxslt.pc']); $this->patchLaDependencyPrefix(); + $AR = getenv('AR') ?: 'ar'; shell()->cd(BUILD_LIB_PATH) - ->exec("ar -t libxslt.a | grep '\\.a$' | xargs -n1 ar d libxslt.a") - ->exec("ar -t libexslt.a | grep '\\.a$' | xargs -n1 ar d libexslt.a"); + ->exec("{$AR} -t libxslt.a | grep '\\.a$' | xargs -n1 {$AR} d libxslt.a") + ->exec("{$AR} -t libexslt.a | grep '\\.a$' | xargs -n1 {$AR} d libexslt.a"); } } diff --git a/src/SPC/builder/unix/library/mimalloc.php b/src/SPC/builder/unix/library/mimalloc.php index 6d89e6dfc..d483ed397 100644 --- a/src/SPC/builder/unix/library/mimalloc.php +++ b/src/SPC/builder/unix/library/mimalloc.php @@ -14,6 +14,7 @@ protected function build(): void $cmake = UnixCMakeExecutor::create($this) ->addConfigureArgs( '-DMI_BUILD_SHARED=OFF', + '-DMI_BUILD_OBJECT=OFF', '-DMI_INSTALL_TOPLEVEL=ON', ); if (SPCTarget::getLibc() === 'musl') { diff --git a/src/SPC/builder/unix/library/ncurses.php b/src/SPC/builder/unix/library/ncurses.php index 14d2a6dc7..3899e5597 100644 --- a/src/SPC/builder/unix/library/ncurses.php +++ b/src/SPC/builder/unix/library/ncurses.php @@ -6,6 +6,7 @@ use SPC\store\FileSystem; use SPC\util\executor\UnixAutoconfExecutor; +use SPC\util\SPCTarget; trait ncurses { @@ -14,6 +15,9 @@ protected function build(): void $filelist = FileSystem::scanDirFiles(BUILD_BIN_PATH, relative: true); UnixAutoconfExecutor::create($this) + ->appendEnv([ + 'LDFLAGS' => SPCTarget::isStatic() ? '-static' : '', + ]) ->configure( '--enable-overwrite', '--with-curses-h', diff --git a/src/SPC/builder/unix/library/ngtcp2.php b/src/SPC/builder/unix/library/ngtcp2.php index 9c9919d8a..e2c4ed099 100644 --- a/src/SPC/builder/unix/library/ngtcp2.php +++ b/src/SPC/builder/unix/library/ngtcp2.php @@ -48,6 +48,7 @@ protected function build(): void // on macOS, the static library may contain other static libraries? // ld: archive member 'libssl.a' not a mach-o file in libngtcp2_crypto_ossl.a - shell()->cd(BUILD_LIB_PATH)->exec("ar -t libngtcp2_crypto_ossl.a | grep '\\.a$' | xargs -n1 ar d libngtcp2_crypto_ossl.a"); + $AR = getenv('AR') ?: 'ar'; + shell()->cd(BUILD_LIB_PATH)->exec("{$AR} -t libngtcp2_crypto_ossl.a | grep '\\.a$' | xargs -n1 {$AR} d libngtcp2_crypto_ossl.a"); } } diff --git a/src/SPC/builder/unix/library/pkgconfig.php b/src/SPC/builder/unix/library/pkgconfig.php index 1817ab0c9..e6f672e59 100644 --- a/src/SPC/builder/unix/library/pkgconfig.php +++ b/src/SPC/builder/unix/library/pkgconfig.php @@ -13,7 +13,7 @@ protected function build(): void { UnixAutoconfExecutor::create($this) ->appendEnv([ - 'CFLAGS' => PHP_OS_FAMILY !== 'Linux' ? '-Wimplicit-function-declaration -Wno-int-conversion' : '', + 'CFLAGS' => '-Wimplicit-function-declaration -Wno-int-conversion', 'LDFLAGS' => SPCTarget::isStatic() ? '--static' : '', ]) ->configure( diff --git a/src/SPC/builder/unix/library/watcher.php b/src/SPC/builder/unix/library/watcher.php index 2463cc4f8..84f8140e8 100644 --- a/src/SPC/builder/unix/library/watcher.php +++ b/src/SPC/builder/unix/library/watcher.php @@ -16,9 +16,13 @@ trait watcher */ protected function build(): void { + $cflags = $this->getLibExtraCXXFlags(); + if (stripos($cflags, '-fpic') === false) { + $cflags .= ' -fPIC'; + } + $ldflags = $this->getLibExtraLdFlags() ? ' ' . $this->getLibExtraLdFlags() : ''; shell()->cd($this->source_dir . '/watcher-c') - ->initializeEnv($this) - ->exec(getenv('CC') . ' -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra -fPIC') + ->exec(getenv('CXX') . " -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra {$cflags}{$ldflags}") ->exec(getenv('AR') . ' rcs libwatcher-c.a libwatcher-c.o'); copy($this->source_dir . '/watcher-c/libwatcher-c.a', BUILD_LIB_PATH . '/libwatcher-c.a'); diff --git a/src/SPC/builder/windows/WindowsBuilder.php b/src/SPC/builder/windows/WindowsBuilder.php index 4becc9265..fb49a0131 100644 --- a/src/SPC/builder/windows/WindowsBuilder.php +++ b/src/SPC/builder/windows/WindowsBuilder.php @@ -45,7 +45,7 @@ public function __construct(array $options = []) $this->zts = $this->getOption('enable-zts', false); // set concurrency - $this->concurrency = intval(getenv('SPC_CONCURRENCY')); + $this->concurrency = (int) getenv('SPC_CONCURRENCY'); // make cmake toolchain $this->cmake_toolchain_file = SystemUtil::makeCmakeToolchainFile(); diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 8bfa4aafe..e914c70d4 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -65,7 +65,7 @@ public function handle(): int // check dynamic extension build env // linux must build with glibc if (!empty($shared_extensions) && SPCTarget::isStatic()) { - $this->output->writeln('Linux does not support dynamic extension loading with musl-libc full-static build, please build with shared target!'); + $this->output->writeln('Linux does not support dynamic extension loading with fully static builds, please build with a shared C runtime target!'); return static::FAILURE; } $static_and_shared = array_intersect($static_extensions, $shared_extensions); diff --git a/src/SPC/command/CraftCommand.php b/src/SPC/command/CraftCommand.php index fbf3a5bed..5a40a25d3 100644 --- a/src/SPC/command/CraftCommand.php +++ b/src/SPC/command/CraftCommand.php @@ -5,6 +5,8 @@ namespace SPC\command; use SPC\exception\ValidationException; +use SPC\toolchain\ToolchainManager; +use SPC\toolchain\ZigToolchain; use SPC\util\ConfigValidator; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Process\Process; @@ -71,7 +73,16 @@ public function handle(): int $retcode = $this->runCommand('install-pkg', 'go-xcaddy'); if ($retcode !== 0) { $this->output->writeln('craft go-xcaddy failed'); - $this->log("craft go-xcaddy failed with code: {$retcode}", true); + $this->log("craft: spc install-pkg go-xcaddy failed with code: {$retcode}", true); + return static::FAILURE; + } + } + // install zig if requested + if (ToolchainManager::getToolchainClass() === ZigToolchain::class) { + $retcode = $this->runCommand('install-pkg', 'zig'); + if ($retcode !== 0) { + $this->output->writeln('craft zig failed'); + $this->log("craft: spc install-pkg zig failed with code: {$retcode}", true); return static::FAILURE; } } diff --git a/src/SPC/doctor/item/LinuxMuslCheck.php b/src/SPC/doctor/item/LinuxMuslCheck.php index a4e57eba2..48b33fedb 100644 --- a/src/SPC/doctor/item/LinuxMuslCheck.php +++ b/src/SPC/doctor/item/LinuxMuslCheck.php @@ -4,6 +4,7 @@ namespace SPC\doctor\item; +use SPC\builder\linux\SystemUtil; use SPC\doctor\AsCheckItem; use SPC\doctor\AsFixItem; use SPC\doctor\CheckResult; @@ -17,13 +18,15 @@ use SPC\store\PackageManager; use SPC\store\SourcePatcher; use SPC\toolchain\MuslToolchain; +use SPC\toolchain\ZigToolchain; #[OptionalCheck([self::class, 'optionalCheck'])] class LinuxMuslCheck { public static function optionalCheck(): bool { - return getenv('SPC_TOOLCHAIN') === MuslToolchain::class; + return getenv('SPC_TOOLCHAIN') === MuslToolchain::class || + (getenv('SPC_TOOLCHAIN') === ZigToolchain::class && !SystemUtil::isMuslDist()); } /** @noinspection PhpUnused */ @@ -31,7 +34,7 @@ public static function optionalCheck(): bool public function checkMusl(): CheckResult { $musl_wrapper_lib = sprintf('/lib/ld-musl-%s.so.1', php_uname('m')); - if (file_exists($musl_wrapper_lib) && file_exists('/usr/local/musl/lib/libc.a')) { + if (file_exists($musl_wrapper_lib) && (file_exists('/usr/local/musl/lib/libc.a') || getenv('SPC_TOOLCHAIN') === ZigToolchain::class)) { return CheckResult::ok(); } return CheckResult::fail('musl-wrapper is not installed on your system', 'fix-musl-wrapper'); @@ -40,6 +43,9 @@ public function checkMusl(): CheckResult #[AsCheckItem('if musl-cross-make is installed', limit_os: 'Linux', level: 799)] public function checkMuslCrossMake(): CheckResult { + if (getenv('SPC_TOOLCHAIN') === ZigToolchain::class && !SystemUtil::isMuslDist()) { + return CheckResult::ok(); + } $arch = arch2gnu(php_uname('m')); $cross_compile_lib = "/usr/local/musl/{$arch}-linux-musl/lib/libc.a"; $cross_compile_gcc = "/usr/local/musl/bin/{$arch}-linux-musl-gcc"; diff --git a/src/SPC/doctor/item/LinuxToolCheckList.php b/src/SPC/doctor/item/LinuxToolCheckList.php index 51e191bfa..b5c9cb034 100644 --- a/src/SPC/doctor/item/LinuxToolCheckList.php +++ b/src/SPC/doctor/item/LinuxToolCheckList.php @@ -69,7 +69,7 @@ public function checkCliTools(): ?CheckResult }; $missing = []; foreach ($required as $package) { - if ($this->findCommand(self::PROVIDED_COMMAND[$package] ?? $package) === null) { + if (self::findCommand(self::PROVIDED_COMMAND[$package] ?? $package) === null) { $missing[] = $package; } } diff --git a/src/SPC/doctor/item/ZigCheck.php b/src/SPC/doctor/item/ZigCheck.php new file mode 100644 index 000000000..186f43b74 --- /dev/null +++ b/src/SPC/doctor/item/ZigCheck.php @@ -0,0 +1,53 @@ + 'win', + 'Darwin' => 'macos', + 'BSD' => 'freebsd', + default => 'linux', + }; + PackageManager::installPackage("zig-{$arch}-{$os}"); + return Zig::isInstalled(); + } +} diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index edf963969..6d6c9f229 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -389,8 +389,8 @@ public static function downloadPackage(string $name, ?array $pkg = null, bool $f $cls = new $class(); if (in_array($name, $cls->getSupportName())) { (new $class())->fetch($name, $force, $pkg); + break; } - break; } } break; diff --git a/src/SPC/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php index 54f805b60..02a64624b 100644 --- a/src/SPC/store/SourcePatcher.php +++ b/src/SPC/store/SourcePatcher.php @@ -5,7 +5,6 @@ namespace SPC\store; use SPC\builder\BuilderBase; -use SPC\builder\linux\LinuxBuilder; use SPC\builder\linux\SystemUtil; use SPC\builder\unix\UnixBuilderBase; use SPC\exception\FileSystemException; @@ -17,21 +16,21 @@ class SourcePatcher { public static function init(): void { - // FileSystem::addSourceExtractHook('swow', [SourcePatcher::class, 'patchSwow']); - FileSystem::addSourceExtractHook('openssl', [SourcePatcher::class, 'patchOpenssl11Darwin']); - FileSystem::addSourceExtractHook('swoole', [SourcePatcher::class, 'patchSwoole']); - FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchPhpLibxml212']); - FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchGDWin32']); - FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchFfiCentos7FixO3strncmp']); - FileSystem::addSourceExtractHook('sqlsrv', [SourcePatcher::class, 'patchSQLSRVWin32']); - FileSystem::addSourceExtractHook('pdo_sqlsrv', [SourcePatcher::class, 'patchSQLSRVWin32']); - FileSystem::addSourceExtractHook('yaml', [SourcePatcher::class, 'patchYamlWin32']); - FileSystem::addSourceExtractHook('libyaml', [SourcePatcher::class, 'patchLibYaml']); - FileSystem::addSourceExtractHook('php-src', [SourcePatcher::class, 'patchImapLicense']); - FileSystem::addSourceExtractHook('ext-imagick', [SourcePatcher::class, 'patchImagickWith84']); - FileSystem::addSourceExtractHook('libaom', [SourcePatcher::class, 'patchLibaomForAlpine']); - FileSystem::addSourceExtractHook('attr', [SourcePatcher::class, 'patchAttrForAlpine']); - FileSystem::addSourceExtractHook('gmssl', [SourcePatcher::class, 'patchGMSSL']); + // FileSystem::addSourceExtractHook('swow', [__CLASS__, 'patchSwow']); + FileSystem::addSourceExtractHook('openssl', [__CLASS__, 'patchOpenssl11Darwin']); + FileSystem::addSourceExtractHook('swoole', [__CLASS__, 'patchSwoole']); + FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchPhpLibxml212']); + FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchGDWin32']); + FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchFfiCentos7FixO3strncmp']); + FileSystem::addSourceExtractHook('sqlsrv', [__CLASS__, 'patchSQLSRVWin32']); + FileSystem::addSourceExtractHook('pdo_sqlsrv', [__CLASS__, 'patchSQLSRVWin32']); + FileSystem::addSourceExtractHook('yaml', [__CLASS__, 'patchYamlWin32']); + FileSystem::addSourceExtractHook('libyaml', [__CLASS__, 'patchLibYaml']); + FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchImapLicense']); + FileSystem::addSourceExtractHook('ext-imagick', [__CLASS__, 'patchImagickWith84']); + FileSystem::addSourceExtractHook('libaom', [__CLASS__, 'patchLibaomForAlpine']); + FileSystem::addSourceExtractHook('attr', [__CLASS__, 'patchAttrForAlpine']); + FileSystem::addSourceExtractHook('gmssl', [__CLASS__, 'patchGMSSL']); } /** @@ -73,9 +72,9 @@ public static function patchBeforeBuildconf(BuilderBase $builder): void FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/build/php.m4', 'PKG_CHECK_MODULES(', 'PKG_CHECK_MODULES_STATIC('); if ($builder->getOption('enable-micro-win32')) { - SourcePatcher::patchMicroWin32(); + self::patchMicroWin32(); } else { - SourcePatcher::unpatchMicroWin32(); + self::unpatchMicroWin32(); } } @@ -233,6 +232,7 @@ public static function patchSwoole(): bool 'PHP_ADD_INCLUDE([$ext_srcdir])', "PHP_ADD_INCLUDE( [\$ext_srcdir] )\n PHP_ADD_INCLUDE([\$abs_srcdir/ext])" ); + // swoole 5.1.3 build fix // get swoole version first $file = SOURCE_PATH . '/php-src/ext/swoole/include/swoole_version.h'; @@ -246,6 +246,7 @@ public static function patchSwoole(): bool if ($version === '5.1.3') { self::patchFile('spc_fix_swoole_50513.patch', SOURCE_PATH . '/php-src/ext/swoole'); } + self::patchFile('swoole_fix_date_time.patch', SOURCE_PATH . '/php-src/ext/swoole'); return true; } @@ -254,14 +255,23 @@ public static function patchSwoole(): bool */ public static function patchBeforeMake(BuilderBase $builder): void { - // Try to fix debian environment build lack HAVE_STRLCAT problem - if ($builder instanceof LinuxBuilder) { - FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_STRLCPY 1$/m', ''); - FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_STRLCAT 1$/m', ''); - } if ($builder instanceof UnixBuilderBase) { FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'install-micro', ''); } + if (!SPCTarget::isStatic() && SPCTarget::getLibc() === 'musl') { + // we need to patch the symbol to global visibility, otherwise extensions with `initial-exec` TLS model will fail to load + FileSystem::replaceFileStr( + SOURCE_PATH . '/php-src/TSRM/TSRM.h', + '#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;', + '#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS __attribute__((visibility("default"))) void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;', + ); + } else { + FileSystem::replaceFileStr( + SOURCE_PATH . '/php-src/TSRM/TSRM.h', + '#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS __attribute__((visibility("default"))) void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;', + '#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;', + ); + } // no asan // if (strpos(file_get_contents(SOURCE_PATH . '/php-src/Makefile'), 'CFLAGS_CLEAN = -g') === false) { @@ -269,7 +279,7 @@ public static function patchBeforeMake(BuilderBase $builder): void // } // call extension patch before make - foreach ($builder->getExts(false) as $ext) { + foreach ($builder->getExts() as $ext) { if ($ext->patchBeforeMake() === true) { logger()->info("Extension [{$ext->getName()}] patched before make"); } @@ -452,7 +462,7 @@ public static function patchImagickWith84(): bool } $extnum = intval($match[1]); if ($extnum < 30800) { - SourcePatcher::patchFile('imagick_php84_before_30800.patch', SOURCE_PATH . '/php-src/ext/imagick'); + self::patchFile('imagick_php84_before_30800.patch', SOURCE_PATH . '/php-src/ext/imagick'); return true; } return false; @@ -470,14 +480,14 @@ public static function patchFfiCentos7FixO3strncmp(): bool if (preg_match('/PHP_VERSION_ID (\d+)/', $file, $match) !== 0 && intval($match[1]) < 80316) { return false; } - SourcePatcher::patchFile('ffi_centos7_fix_O3_strncmp.patch', SOURCE_PATH . '/php-src'); + self::patchFile('ffi_centos7_fix_O3_strncmp.patch', SOURCE_PATH . '/php-src'); return true; } public static function patchLibaomForAlpine(): bool { if (PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist()) { - SourcePatcher::patchFile('libaom_posix_implict.patch', SOURCE_PATH . '/libaom'); + self::patchFile('libaom_posix_implict.patch', SOURCE_PATH . '/libaom'); return true; } return false; @@ -486,7 +496,7 @@ public static function patchLibaomForAlpine(): bool public static function patchAttrForAlpine(): bool { if (PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist() || PHP_OS_FAMILY === 'Darwin') { - SourcePatcher::patchFile('attr_alpine_gethostname.patch', SOURCE_PATH . '/attr'); + self::patchFile('attr_alpine_gethostname.patch', SOURCE_PATH . '/attr'); return true; } return false; diff --git a/src/SPC/store/pkg/CustomPackage.php b/src/SPC/store/pkg/CustomPackage.php index 7e5c35326..093f65d02 100644 --- a/src/SPC/store/pkg/CustomPackage.php +++ b/src/SPC/store/pkg/CustomPackage.php @@ -28,14 +28,18 @@ abstract public function getSupportName(): array; */ abstract public function fetch(string $name, bool $force = false, ?array $config = null): void; + /** + * Get the environment variables this package needs to be usable. + * PATH needs to be appended, rather than replaced. + */ + abstract public static function getEnvironment(): array; + + abstract public static function isInstalled(): bool; + /** * Extract the downloaded package * - * @param string $name Package name - * @throws \RuntimeException If extraction is not implemented + * @param string $name Package name */ - public function extract(string $name): void - { - throw new \RuntimeException("Extract method not implemented for package: {$name}"); - } + abstract public function extract(string $name): void; } diff --git a/src/SPC/store/pkg/GoXcaddy.php b/src/SPC/store/pkg/GoXcaddy.php index 8dc1cf2d6..13041a6f2 100644 --- a/src/SPC/store/pkg/GoXcaddy.php +++ b/src/SPC/store/pkg/GoXcaddy.php @@ -4,12 +4,30 @@ namespace SPC\store\pkg; +use SPC\builder\linux\SystemUtil; use SPC\store\Downloader; use SPC\store\FileSystem; use SPC\store\LockFile; class GoXcaddy extends CustomPackage { + public static function isInstalled(): bool + { + $arch = arch2gnu(php_uname('m')); + $os = match (PHP_OS_FAMILY) { + 'Windows' => 'win', + 'Darwin' => 'macos', + 'BSD' => 'freebsd', + default => 'linux', + }; + + $packageName = "go-xcaddy-{$arch}-{$os}"; + $pkgroot = PKG_ROOT_PATH; + $folder = "{$pkgroot}/{$packageName}"; + + return is_dir($folder) && is_file("{$folder}/bin/go") && is_file("{$folder}/bin/xcaddy"); + } + public function getSupportName(): array { return [ @@ -25,6 +43,9 @@ public function fetch(string $name, bool $force = false, ?array $config = null): $pkgroot = PKG_ROOT_PATH; $go_exec = "{$pkgroot}/{$name}/bin/go"; $xcaddy_exec = "{$pkgroot}/{$name}/bin/xcaddy"; + if ($force) { + FileSystem::removeDir("{$pkgroot}/{$name}"); + } if (file_exists($go_exec) && file_exists($xcaddy_exec)) { return; } @@ -57,18 +78,45 @@ public function extract(string $name): void $lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true); $source_type = $lock[$name]['source_type']; $filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']); - $extract = $lock[$name]['move_path'] === null ? "{$pkgroot}/{$name}" : $lock[$name]['move_path']; + $extract = $lock[$name]['move_path'] ?? "{$pkgroot}/{$name}"; FileSystem::extractPackage($name, $source_type, $filename, $extract); - // install xcaddy + $sanitizedPath = getenv('PATH'); + if (PHP_OS_FAMILY === 'Linux' && !SystemUtil::isMuslDist()) { + $sanitizedPath = preg_replace('#(:?/?[^:]*musl[^:]*)#', '', $sanitizedPath); + $sanitizedPath = preg_replace('#^:|:$|::#', ':', $sanitizedPath); // clean up colons + } + + // install xcaddy without using musl tools, xcaddy build requires dynamic linking shell() ->appendEnv([ - 'PATH' => "{$pkgroot}/{$name}/bin:" . getenv('PATH'), + 'PATH' => "{$pkgroot}/{$name}/bin:" . $sanitizedPath, 'GOROOT' => "{$pkgroot}/{$name}", 'GOBIN' => "{$pkgroot}/{$name}/bin", 'GOPATH' => "{$pkgroot}/go", ]) - ->exec("{$go_exec} install github.com/caddyserver/xcaddy/cmd/xcaddy@latest"); + ->exec('CC=cc go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest'); + } + + public static function getEnvironment(): array + { + $arch = arch2gnu(php_uname('m')); + $os = match (PHP_OS_FAMILY) { + 'Windows' => 'win', + 'Darwin' => 'macos', + 'BSD' => 'freebsd', + default => 'linux', + }; + + $packageName = "go-xcaddy-{$arch}-{$os}"; + $pkgroot = PKG_ROOT_PATH; + + return [ + 'PATH' => "{$pkgroot}/{$packageName}/bin", + 'GOROOT' => "{$pkgroot}/{$packageName}", + 'GOBIN' => "{$pkgroot}/{$packageName}/bin", + 'GOPATH' => "{$pkgroot}/go", + ]; } } diff --git a/src/SPC/store/pkg/Zig.php b/src/SPC/store/pkg/Zig.php new file mode 100644 index 000000000..a28460891 --- /dev/null +++ b/src/SPC/store/pkg/Zig.php @@ -0,0 +1,185 @@ + "{$pkgroot}/{$name}/zig.exe", + default => "{$pkgroot}/{$name}/zig", + }; + + if ($force) { + FileSystem::removeDir("{$pkgroot}/{$name}"); + } + + if (file_exists($zig_exec)) { + return; + } + + $parts = explode('-', $name); + $arch = $parts[1]; + $os = $parts[2]; + + $zig_arch = match ($arch) { + 'x86_64', 'aarch64' => $arch, + default => throw new WrongUsageException('Unsupported architecture: ' . $arch), + }; + + $zig_os = match ($os) { + 'linux' => 'linux', + 'macos' => 'macos', + 'win' => 'windows', + default => throw new WrongUsageException('Unsupported OS: ' . $os), + }; + + $index_json = json_decode(Downloader::curlExec('https://ziglang.org/download/index.json', hooks: [[CurlHook::class, 'setupGithubToken']]), true); + + $latest_version = null; + foreach ($index_json as $version => $data) { + $latest_version = $version; + break; + } + + if (!$latest_version) { + throw new RuntimeException('Could not determine latest Zig version'); + } + + logger()->info("Installing Zig version {$latest_version}"); + + $platform_key = "{$zig_arch}-{$zig_os}"; + if (!isset($index_json[$latest_version][$platform_key])) { + throw new RuntimeException("No download available for {$platform_key} in Zig version {$latest_version}"); + } + + $download_info = $index_json[$latest_version][$platform_key]; + $url = $download_info['tarball']; + $filename = basename($url); + + $pkg = [ + 'type' => 'url', + 'url' => $url, + 'filename' => $filename, + ]; + + Downloader::downloadPackage($name, $pkg, $force); + } + + public function extract(string $name): void + { + $pkgroot = PKG_ROOT_PATH; + $zig_bin_dir = "{$pkgroot}/{$name}"; + + $files = ['zig', 'zig-cc', 'zig-c++', 'zig-ar', 'zig-ld.lld', 'zig-ranlib', 'zig-objcopy']; + $all_exist = true; + foreach ($files as $file) { + if (!file_exists("{$zig_bin_dir}/{$file}")) { + $all_exist = false; + break; + } + } + if ($all_exist) { + return; + } + + $lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true); + $source_type = $lock[$name]['source_type']; + $filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']); + $extract = "{$pkgroot}/{$name}"; + + FileSystem::extractPackage($name, $source_type, $filename, $extract); + + $this->createZigCcScript($zig_bin_dir); + } + + public static function getEnvironment(): array + { + $arch = arch2gnu(php_uname('m')); + $os = match (PHP_OS_FAMILY) { + 'Windows' => 'win', + 'Darwin' => 'macos', + 'BSD' => 'freebsd', + default => 'linux', + }; + + $packageName = "zig-{$arch}-{$os}"; + $path = PKG_ROOT_PATH . "/{$packageName}"; + + return [ + 'PATH' => $path, + ]; + } + + /** + * @throws WrongUsageException + */ + private static function getPath(): string + { + $arch = arch2gnu(php_uname('m')); + $os = match (PHP_OS_FAMILY) { + 'Windows' => 'win', + 'Darwin' => 'macos', + 'BSD' => 'freebsd', + default => 'linux', + }; + + $packageName = "zig-{$arch}-{$os}"; + return PKG_ROOT_PATH . "/{$packageName}"; + } + + private function createZigCcScript(string $bin_dir): void + { + $script_path = __DIR__ . '/../scripts/zig-cc.sh'; + $script_content = file_get_contents($script_path); + + file_put_contents("{$bin_dir}/zig-cc", $script_content); + chmod("{$bin_dir}/zig-cc", 0755); + + $script_content = str_replace('zig cc', 'zig c++', $script_content); + file_put_contents("{$bin_dir}/zig-c++", $script_content); + file_put_contents("{$bin_dir}/zig-ar", "#!/usr/bin/env bash\nexec zig ar $@"); + file_put_contents("{$bin_dir}/zig-ld.lld", "#!/usr/bin/env bash\nexec zig ld.lld $@"); + file_put_contents("{$bin_dir}/zig-ranlib", "#!/usr/bin/env bash\nexec zig ranlib $@"); + file_put_contents("{$bin_dir}/zig-objcopy", "#!/usr/bin/env bash\nexec zig objcopy $@"); + chmod("{$bin_dir}/zig-c++", 0755); + chmod("{$bin_dir}/zig-ar", 0755); + chmod("{$bin_dir}/zig-ld.lld", 0755); + chmod("{$bin_dir}/zig-ranlib", 0755); + chmod("{$bin_dir}/zig-objcopy", 0755); + } +} diff --git a/src/SPC/store/scripts/zig-cc.sh b/src/SPC/store/scripts/zig-cc.sh new file mode 100644 index 000000000..388a3655c --- /dev/null +++ b/src/SPC/store/scripts/zig-cc.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" +BUILDROOT_ABS="$(realpath "$SCRIPT_DIR/../../buildroot/include" 2>/dev/null || true)" +PARSED_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + -isystem) + shift + ARG="$1" + shift + ARG_ABS="$(realpath "$ARG" 2>/dev/null || true)" + [[ "$ARG_ABS" == "$BUILDROOT_ABS" ]] && PARSED_ARGS+=("-I$ARG") || PARSED_ARGS+=("-isystem" "$ARG") + ;; + -isystem*) + ARG="${1#-isystem}" + shift + ARG_ABS="$(realpath "$ARG" 2>/dev/null || true)" + [[ "$ARG_ABS" == "$BUILDROOT_ABS" ]] && PARSED_ARGS+=("-I$ARG") || PARSED_ARGS+=("-isystem$ARG") + ;; + -march=*|-mcpu=*) # replace -march=x86-64 with -march=x86_64 + OPT_NAME="${1%%=*}" + OPT_VALUE="${1#*=}" + OPT_VALUE="${OPT_VALUE//-/_}" + PARSED_ARGS+=("${OPT_NAME}=${OPT_VALUE}") + shift + ;; + *) + PARSED_ARGS+=("$1") + shift + ;; + esac +done + +[[ -n "$SPC_TARGET" ]] && TARGET="-target $SPC_TARGET" || TARGET="" + +if [[ "$SPC_TARGET" =~ \.[0-9]+\.[0-9]+ ]]; then + output=$(zig cc $TARGET $SPC_COMPILER_EXTRA "${PARSED_ARGS[@]}" 2>&1) + status=$? + + if [[ $status -eq 0 ]]; then + echo "$output" + exit 0 + fi + + if echo "$output" | grep -qE "version '.*' in target triple"; then + filtered_output=$(echo "$output" | grep -vE "version '.*' in target triple") + echo "$filtered_output" + exit 0 + fi +fi + +exec zig cc $TARGET $SPC_COMPILER_EXTRA "${PARSED_ARGS[@]}" diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php index 5b4fd0972..d6807cd1c 100644 --- a/src/SPC/store/source/PhpSource.php +++ b/src/SPC/store/source/PhpSource.php @@ -7,6 +7,7 @@ use JetBrains\PhpStorm\ArrayShape; use SPC\exception\DownloaderException; use SPC\exception\FileSystemException; +use SPC\exception\WrongUsageException; use SPC\store\Downloader; class PhpSource extends CustomSourceBase @@ -16,11 +17,12 @@ class PhpSource extends CustomSourceBase /** * @throws DownloaderException * @throws FileSystemException + * @throws WrongUsageException */ 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); + Downloader::downloadSource('php-src', $this->getLatestPHPInfo($major), $force); } /** @@ -34,7 +36,7 @@ public function getLatestPHPInfo(string $major_version): array // 查找最新的小版本号 $info = json_decode(Downloader::curlExec( url: "https://www.php.net/releases/index.php?json&version={$major_version}", - retries: intval(getenv('SPC_DOWNLOAD_RETRIES') ?: 0) + retries: (int) getenv('SPC_DOWNLOAD_RETRIES') ), true); if (!isset($info['version'])) { throw new DownloaderException("Version {$major_version} not found."); diff --git a/src/SPC/toolchain/ClangNativeToolchain.php b/src/SPC/toolchain/ClangNativeToolchain.php index fb0bea464..aef844d7d 100644 --- a/src/SPC/toolchain/ClangNativeToolchain.php +++ b/src/SPC/toolchain/ClangNativeToolchain.php @@ -10,6 +10,9 @@ use SPC\exception\WrongUsageException; use SPC\util\GlobalEnvManager; +/** + * Toolchain implementation for system clang compiler. + */ class ClangNativeToolchain implements ToolchainInterface { public function initEnv(): void @@ -20,6 +23,9 @@ public function initEnv(): void GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld'); } + /** + * @throws WrongUsageException + */ public function afterInit(): void { foreach (['CC', 'CXX', 'AR', 'LD'] as $env) { diff --git a/src/SPC/toolchain/GccNativeToolchain.php b/src/SPC/toolchain/GccNativeToolchain.php index 1e97b50e4..1ada28b54 100644 --- a/src/SPC/toolchain/GccNativeToolchain.php +++ b/src/SPC/toolchain/GccNativeToolchain.php @@ -17,7 +17,7 @@ public function initEnv(): void GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=gcc'); GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=g++'); GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=ar'); - GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld.gold'); + GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld'); } public function afterInit(): void diff --git a/src/SPC/toolchain/MuslToolchain.php b/src/SPC/toolchain/MuslToolchain.php index dd7955560..e996ef1fa 100644 --- a/src/SPC/toolchain/MuslToolchain.php +++ b/src/SPC/toolchain/MuslToolchain.php @@ -20,8 +20,8 @@ public function initEnv(): void GlobalEnvManager::addPathIfNotExists('/usr/local/musl/bin'); GlobalEnvManager::addPathIfNotExists("/usr/local/musl/{$arch}-linux-musl/bin"); - GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_LD_LIBRARY_PATH=/usr/local/musl/lib:/usr/local/musl/{$arch}-linux-musl/lib"); - GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_LIBRARY_PATH=/usr/local/musl/lib:/usr/local/musl/{$arch}-linux-musl/lib"); + GlobalEnvManager::putenv("SPC_LD_LIBRARY_PATH=/usr/local/musl/lib:/usr/local/musl/{$arch}-linux-musl/lib"); + GlobalEnvManager::putenv("SPC_LIBRARY_PATH=/usr/local/musl/lib:/usr/local/musl/{$arch}-linux-musl/lib"); } public function afterInit(): void @@ -29,7 +29,7 @@ public function afterInit(): void $arch = getenv('GNU_ARCH'); // append LD_LIBRARY_PATH to $configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE'); $configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE'); - $ld_library_path = getenv('SPC_LINUX_DEFAULT_LD_LIBRARY_PATH'); + $ld_library_path = getenv('SPC_LD_LIBRARY_PATH'); GlobalEnvManager::putenv("SPC_CMD_PREFIX_PHP_CONFIGURE=LD_LIBRARY_PATH=\"{$ld_library_path}\" {$configure}"); if (!file_exists("/usr/local/musl/{$arch}-linux-musl/lib/libc.a")) { diff --git a/src/SPC/toolchain/ToolchainManager.php b/src/SPC/toolchain/ToolchainManager.php index 76807bf22..0c92c58a9 100644 --- a/src/SPC/toolchain/ToolchainManager.php +++ b/src/SPC/toolchain/ToolchainManager.php @@ -7,43 +7,57 @@ use SPC\builder\linux\SystemUtil; use SPC\exception\WrongUsageException; use SPC\util\GlobalEnvManager; +use SPC\util\SPCTarget; class ToolchainManager { public const array OS_DEFAULT_TOOLCHAIN = [ - 'Linux' => MuslToolchain::class, // use musl toolchain by default, after zig pr merged, change this to ZigToolchain::class + 'Linux' => ZigToolchain::class, 'Windows' => MSVCToolchain::class, 'Darwin' => ClangNativeToolchain::class, 'BSD' => ClangNativeToolchain::class, ]; - /** - * @throws WrongUsageException - */ - public static function initToolchain(): void + public static function getToolchainClass(): string { + if ($tc = getenv('SPC_TOOLCHAIN')) { + return $tc; + } $libc = getenv('SPC_LIBC'); - if ($libc !== false) { - // uncomment this when zig pr is merged - // logger()->warning('SPC_LIBC is deprecated, please use SPC_TARGET instead.'); - $toolchain = match ($libc) { + if ($libc && !getenv('SPC_TARGET')) { + // trigger_error('Setting SPC_LIBC is deprecated, please use SPC_TARGET instead.', E_USER_DEPRECATED); + return match ($libc) { 'musl' => SystemUtil::isMuslDist() ? GccNativeToolchain::class : MuslToolchain::class, - 'glibc' => !SystemUtil::isMuslDist() ? GccNativeToolchain::class : throw new WrongUsageException('SPC_TARGET must be musl-static or musl for musl dist.'), + 'glibc' => !SystemUtil::isMuslDist() ? GccNativeToolchain::class : throw new WrongUsageException('SPC_LIBC must be musl for musl dist.'), default => throw new WrongUsageException('Unsupported SPC_LIBC value: ' . $libc), }; - } else { - $toolchain = self::OS_DEFAULT_TOOLCHAIN[PHP_OS_FAMILY]; } - $toolchainClass = $toolchain; + + return self::OS_DEFAULT_TOOLCHAIN[PHP_OS_FAMILY]; + } + + /** + * @throws WrongUsageException + */ + public static function initToolchain(): void + { + $toolchainClass = self::getToolchainClass(); /* @var ToolchainInterface $toolchainClass */ (new $toolchainClass())->initEnv(); - GlobalEnvManager::putenv("SPC_TOOLCHAIN={$toolchain}"); + GlobalEnvManager::putenv("SPC_TOOLCHAIN={$toolchainClass}"); } public static function afterInitToolchain(): void { if (!getenv('SPC_TOOLCHAIN')) { - throw new WrongUsageException('SPC_TOOLCHAIN not set'); + throw new WrongUsageException('SPC_TOOLCHAIN was not properly set. Please contact the developers.'); + } + $musl_wrapper_lib = sprintf('/lib/ld-musl-%s.so.1', php_uname('m')); + if (SPCTarget::getLibc() === 'musl' && !SPCTarget::isStatic() && !file_exists($musl_wrapper_lib)) { + throw new WrongUsageException('You are linking against musl libc dynamically, but musl libc is not installed. Please use `bin/spc doctor` to install it.'); + } + if (SPCTarget::getLibc() === 'glibc' && SystemUtil::isMuslDist()) { + throw new WrongUsageException('You are linking against glibc dynamically, which is only supported on glibc distros.'); } $toolchain = getenv('SPC_TOOLCHAIN'); /* @var ToolchainInterface $toolchain */ diff --git a/src/SPC/toolchain/ZigToolchain.php b/src/SPC/toolchain/ZigToolchain.php index 3922fecbb..613c0f979 100644 --- a/src/SPC/toolchain/ZigToolchain.php +++ b/src/SPC/toolchain/ZigToolchain.php @@ -4,9 +4,68 @@ namespace SPC\toolchain; +use SPC\exception\WrongUsageException; +use SPC\store\pkg\Zig; +use SPC\util\GlobalEnvManager; + class ZigToolchain implements ToolchainInterface { - public function initEnv(): void {} + public function initEnv(): void + { + // Set environment variables for zig toolchain + GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=zig-cc'); + GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=zig-c++'); + GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=zig-ar'); + GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=zig-ld.lld'); + + // Generate additional objects needed for zig toolchain + $paths = ['/usr/lib/gcc', '/usr/local/lib/gcc']; + $objects = ['crtbeginS.o', 'crtendS.o']; + $found = []; + + foreach ($objects as $obj) { + $located = null; + foreach ($paths as $base) { + $output = shell_exec("find {$base} -name {$obj} 2>/dev/null | grep -v '/32/' | head -n 1"); + $line = trim((string) $output); + if ($line !== '') { + $located = $line; + break; + } + } + if ($located) { + $found[] = $located; + } + } + GlobalEnvManager::putenv('SPC_EXTRA_RUNTIME_OBJECTS=' . implode(' ', $found)); + } - public function afterInit(): void {} + /** + * @throws WrongUsageException + */ + public function afterInit(): void + { + if (!is_dir(Zig::getEnvironment()['PATH'])) { + throw new WrongUsageException('You are building with zig, but zig is not installed, please install zig first. (You can use `doctor` command to install it)'); + } + GlobalEnvManager::addPathIfNotExists(Zig::getEnvironment()['PATH']); + f_passthru('ulimit -n 2048'); // zig opens extra file descriptors, so when a lot of extensions are built statically, 1024 is not enough + $cflags = getenv('SPC_DEFAULT_C_FLAGS') ?: ''; + $cxxflags = getenv('SPC_DEFAULT_CXX_FLAGS') ?: ''; + $extraCflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') ?: ''; + $cflags = trim($cflags . ' -Wno-date-time'); + $cxxflags = trim($cxxflags . ' -Wno-date-time'); + $extraCflags = trim($extraCflags . ' -Wno-date-time'); + GlobalEnvManager::putenv("SPC_DEFAULT_C_FLAGS={$cflags}"); + GlobalEnvManager::putenv("SPC_DEFAULT_CXX_FLAGS={$cxxflags}"); + GlobalEnvManager::putenv("SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS={$extraCflags}"); + GlobalEnvManager::putenv('RANLIB=zig-ranlib'); + GlobalEnvManager::putenv('OBJCOPY=zig-objcopy'); + $extra_libs = getenv('SPC_EXTRA_LIBS') ?: ''; + if (!str_contains($extra_libs, '-lunwind')) { + // Add unwind library if not already present + $extra_libs = trim($extra_libs . ' -lunwind'); + GlobalEnvManager::putenv("SPC_EXTRA_LIBS={$extra_libs}"); + } + } } diff --git a/src/SPC/util/GlobalEnvManager.php b/src/SPC/util/GlobalEnvManager.php index 23ae242dc..f5dafea25 100644 --- a/src/SPC/util/GlobalEnvManager.php +++ b/src/SPC/util/GlobalEnvManager.php @@ -15,6 +15,8 @@ class GlobalEnvManager { private static array $env_cache = []; + private static bool $initialized = false; + public static function getInitializedEnv(): array { return self::$env_cache; @@ -28,6 +30,9 @@ public static function getInitializedEnv(): array */ public static function init(): void { + if (self::$initialized) { + return; + } // Check pre-defined env vars exists if (getenv('BUILD_ROOT_PATH') === false) { throw new RuntimeException('You must include src/globals/internal-env.php before using GlobalEnvManager'); @@ -85,6 +90,7 @@ public static function init(): void self::putenv("{$k}={$v}"); } } + self::$initialized = true; } public static function putenv(string $val): void diff --git a/src/SPC/util/SPCConfigUtil.php b/src/SPC/util/SPCConfigUtil.php index 941cec2c7..08570008e 100644 --- a/src/SPC/util/SPCConfigUtil.php +++ b/src/SPC/util/SPCConfigUtil.php @@ -57,7 +57,7 @@ public function __construct(?BuilderBase $builder = null, array $options = []) * @throws WrongUsageException * @throws \Throwable */ - public function config(array $extensions = [], array $libraries = [], bool $include_suggest_ext = false, bool $include_suggest_lib = false, bool $with_dependencies = false): array + public function config(array $extensions = [], array $libraries = [], bool $include_suggest_ext = false, bool $include_suggest_lib = false): array { [$extensions, $libraries] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib); @@ -73,9 +73,9 @@ public function config(array $extensions = [], array $libraries = [], bool $incl $libs = $this->getLibsString($libraries, !$this->absolute_libs); // additional OS-specific libraries (e.g. macOS -lresolv) - $extra_env = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS'); - if (is_string($extra_env)) { - $libs .= ' ' . trim($extra_env, '"'); + // embed + if ($extra_libs = SPCTarget::getRuntimeLibs()) { + $libs .= " {$extra_libs}"; } $extra_env = getenv('SPC_EXTRA_LIBS'); if (is_string($extra_env) && !empty($extra_env)) { @@ -86,14 +86,21 @@ public function config(array $extensions = [], array $libraries = [], bool $incl $libs .= " {$this->getFrameworksString($extensions)}"; } if ($this->builder->hasCpp()) { - $libs .= SPCTarget::getTargetOS() === 'Darwin' ? ' -lc++' : ' -lstdc++'; + $libcpp = SPCTarget::getTargetOS() === 'Darwin' ? '-lc++' : '-lstdc++'; + if (!str_contains($libs, $libcpp)) { + $libs .= " {$libcpp}"; + } } if ($this->libs_only_deps) { + // mimalloc must come first + if ($this->builder->getLib('mimalloc') && file_exists(BUILD_LIB_PATH . '/libmimalloc.a')) { + $libs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace([BUILD_LIB_PATH . '/libmimalloc.a', '-lmimalloc'], ['', ''], $libs); + } return [ - 'cflags' => trim(getenv('CFLAGS') . ' ' . $cflags), - 'ldflags' => trim(getenv('LDFLAGS') . ' ' . $ldflags), - 'libs' => trim(getenv('LIBS') . ' ' . $libs), + 'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags), + 'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags), + 'libs' => clean_spaces(getenv('LIBS') . ' ' . $libs), ]; } @@ -105,14 +112,14 @@ public function config(array $extensions = [], array $libraries = [], bool $incl $allLibs = getenv('LIBS') . ' ' . $libs; // mimalloc must come first - if (str_contains($libs, BUILD_LIB_PATH . '/mimalloc.o')) { - $allLibs = BUILD_LIB_PATH . '/mimalloc.o ' . str_replace(BUILD_LIB_PATH . '/mimalloc.o', '', $allLibs); + if ($this->builder->getLib('mimalloc') && file_exists(BUILD_LIB_PATH . '/libmimalloc.a')) { + $allLibs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace([BUILD_LIB_PATH . '/libmimalloc.a', '-lmimalloc'], ['', ''], $allLibs); } return [ - 'cflags' => trim(getenv('CFLAGS') . ' ' . $cflags), - 'ldflags' => trim(getenv('LDFLAGS') . ' ' . $ldflags), - 'libs' => trim($allLibs), + 'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags), + 'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags), + 'libs' => clean_spaces($allLibs), ]; } @@ -179,7 +186,7 @@ private function getLibsString(array $libraries, bool $use_short_libs = true): s $lib_names = [...$lib_names, ...$pc_libs]; } // convert all static-libs to short names - $libs = Config::getLib($library, 'static-libs', []); + $libs = array_reverse(Config::getLib($library, 'static-libs', [])); foreach ($libs as $lib) { // check file existence if (!file_exists(BUILD_LIB_PATH . "/{$lib}")) { diff --git a/src/SPC/util/SPCTarget.php b/src/SPC/util/SPCTarget.php index b674379e1..d4164ceb6 100644 --- a/src/SPC/util/SPCTarget.php +++ b/src/SPC/util/SPCTarget.php @@ -5,7 +5,10 @@ namespace SPC\util; use SPC\builder\linux\SystemUtil; -use SPC\exception\WrongUsageException; +use SPC\toolchain\ClangNativeToolchain; +use SPC\toolchain\GccNativeToolchain; +use SPC\toolchain\MuslToolchain; +use SPC\toolchain\ToolchainManager; /** * SPC build target constants and toolchain initialization. @@ -13,23 +16,44 @@ */ class SPCTarget { - public const array LIBC_LIST = [ - 'musl', - 'glibc', - ]; + public const array LIBC_LIST = ['musl', 'glibc']; /** - * Returns whether the target is a full-static target. + * Returns whether we link the C runtime in statically. */ public static function isStatic(): bool { - $env = getenv('SPC_TARGET'); - $libc = getenv('SPC_LIBC'); + if (ToolchainManager::getToolchainClass() === MuslToolchain::class) { + return true; + } + if (ToolchainManager::getToolchainClass() === GccNativeToolchain::class) { + return PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist(); + } + if (ToolchainManager::getToolchainClass() === ClangNativeToolchain::class) { + return PHP_OS_FAMILY === 'Linux' && SystemUtil::isMuslDist(); + } // if SPC_LIBC is set, it means the target is static, remove it when 3.0 is released - if ($libc === 'musl') { + if ($target = getenv('SPC_TARGET')) { + if (str_contains($target, '-macos') || str_contains($target, '-native') && PHP_OS_FAMILY === 'Darwin') { + return false; + } + if (str_contains($target, '-gnu')) { + return false; + } + if (str_contains($target, '-dynamic')) { + return false; + } + if (str_contains($target, '-musl')) { + return true; + } + if (PHP_OS_FAMILY === 'Linux') { + return SystemUtil::isMuslDist(); + } + return true; + } + if (getenv('SPC_LIBC') === 'musl') { return true; } - // TODO: add zig target parser here return false; } @@ -38,29 +62,45 @@ public static function isStatic(): bool */ public static function getLibc(): ?string { - $env = getenv('SPC_TARGET'); + if ($target = getenv('SPC_TARGET')) { + if (str_contains($target, '-gnu')) { + return 'glibc'; + } + if (str_contains($target, '-musl')) { + return 'musl'; + } + if (PHP_OS_FAMILY === 'Linux') { + return SystemUtil::isMuslDist() ? 'musl' : 'glibc'; + } + } $libc = getenv('SPC_LIBC'); if ($libc !== false) { return $libc; } - // TODO: zig target parser + if (PHP_OS_FAMILY === 'Linux') { + return SystemUtil::isMuslDist() ? 'musl' : 'glibc'; + } return null; } + public static function getRuntimeLibs(): string + { + if (PHP_OS_FAMILY === 'Linux') { + return self::getLibc() === 'musl' ? '-ldl -lpthread -lm' : '-ldl -lrt -lpthread -lm -lresolv -lutil'; + } + if (PHP_OS_FAMILY === 'Darwin') { + return '-lresolv'; + } + return ''; + } + /** * Returns the libc version if set, for other OS, it will always return null. */ public static function getLibcVersion(): ?string { - $env = getenv('SPC_TARGET'); - $libc = getenv('SPC_LIBC'); - if ($libc !== false) { - // legacy method: get a version from system - return SystemUtil::getLibcVersionIfExists($libc); - } - // TODO: zig target parser - - return null; + $libc = self::getLibc(); + return SystemUtil::getLibcVersionIfExists($libc); } /** @@ -68,20 +108,16 @@ public static function getLibcVersion(): ?string * Currently, we only support native building. * * @return 'BSD'|'Darwin'|'Linux'|'Windows' - * @throws WrongUsageException */ public static function getTargetOS(): string { - $target = getenv('SPC_TARGET'); - if ($target === false || $target === '') { - return PHP_OS_FAMILY; - } - // TODO: zig target parser like below? + $target = (string) getenv('SPC_TARGET'); return match (true) { - str_contains($target, 'linux') => 'Linux', - str_contains($target, 'macos') => 'Darwin', - str_contains($target, 'windows') => 'Windows', - default => throw new WrongUsageException('Cannot parse target.'), + str_contains($target, '-linux') => 'Linux', + str_contains($target, '-macos') => 'Darwin', + str_contains($target, '-windows') => 'Windows', + str_contains($target, '-native') => PHP_OS_FAMILY, + default => PHP_OS_FAMILY, }; } } diff --git a/src/SPC/util/UnixShell.php b/src/SPC/util/UnixShell.php index ffe18910d..3e96e4bde 100644 --- a/src/SPC/util/UnixShell.php +++ b/src/SPC/util/UnixShell.php @@ -61,9 +61,9 @@ public function initializeEnv(BSDLibraryBase|LinuxLibraryBase|MacOSLibraryBase $ { $this->setEnv([ 'CFLAGS' => $library->getLibExtraCFlags(), + 'CXXFLAGS' => $library->getLibExtraCXXFlags(), 'LDFLAGS' => $library->getLibExtraLdFlags(), 'LIBS' => $library->getLibExtraLibs(), - 'CXXFLAGS' => $library->getLibExtraCXXFlags(), ]); return $this; } diff --git a/src/SPC/util/executor/UnixAutoconfExecutor.php b/src/SPC/util/executor/UnixAutoconfExecutor.php index 8f923b1f1..4543bd7bc 100644 --- a/src/SPC/util/executor/UnixAutoconfExecutor.php +++ b/src/SPC/util/executor/UnixAutoconfExecutor.php @@ -135,6 +135,7 @@ private function initShell(): void { $this->shell = shell()->cd($this->library->getSourceDir())->initializeEnv($this->library)->appendEnv([ 'CFLAGS' => "-I{$this->library->getIncludeDir()}", + 'CXXFLAGS' => "-I{$this->library->getIncludeDir()}", 'LDFLAGS' => "-L{$this->library->getLibDir()}", ]); } diff --git a/src/globals/common-tests/embed.c b/src/globals/common-tests/embed.c index 85c299219..38d8f39fc 100644 --- a/src/globals/common-tests/embed.c +++ b/src/globals/common-tests/embed.c @@ -8,10 +8,10 @@ int main(int argc,char **argv){ zend_stream_init_filename(&file_handle,"embed.php"); - if(php_execute_script(&file_handle) == FAILURE){ + if(!php_execute_script(&file_handle)){ php_printf("Failed to execute PHP script.\n"); } PHP_EMBED_END_BLOCK() return 0; -} \ No newline at end of file +} diff --git a/src/globals/functions.php b/src/globals/functions.php index b45d8c5ee..2fef0d7f7 100644 --- a/src/globals/functions.php +++ b/src/globals/functions.php @@ -242,3 +242,14 @@ function get_pack_replace(): array BUILD_ROOT_PATH => '@build_root_path@', ]; } + +/** + * Remove duplicate spaces from a string. + * + * @param string $string Input string that may contain unnecessary spaces (e.g., " -la -lb"). + * @return string The trimmed string with only single spaces (e.g., "-la -lb"). + */ +function clean_spaces(string $string): string +{ + return trim(preg_replace('/\s+/', ' ', $string)); +} diff --git a/src/globals/patch/swoole_fix_date_time.patch b/src/globals/patch/swoole_fix_date_time.patch new file mode 100644 index 000000000..97c391d16 --- /dev/null +++ b/src/globals/patch/swoole_fix_date_time.patch @@ -0,0 +1,19 @@ +--- a/config.m4 ++++ b/config.m4 +@@ -426,6 +426,7 @@ + AX_CHECK_COMPILE_FLAG(-Wlogical-op-parentheses, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wlogical-op-parentheses") + AX_CHECK_COMPILE_FLAG(-Wloop-analysis, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wloop-analysis") + AX_CHECK_COMPILE_FLAG(-Wuninitialized, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wuninitialized") ++ AX_CHECK_COMPILE_FLAG(-Wno-date-time, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-date-time") + AX_CHECK_COMPILE_FLAG(-Wno-missing-field-initializers, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-missing-field-initializers") + AX_CHECK_COMPILE_FLAG(-Wno-sign-compare, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-sign-compare") + AX_CHECK_COMPILE_FLAG(-Wno-unused-const-variable, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-unused-const-variable") +@@ -1307,7 +1308,7 @@ + + PHP_REQUIRE_CXX() + +- CXXFLAGS="$CXXFLAGS -Wall -Wno-unused-function -Wno-deprecated -Wno-deprecated-declarations" ++ CXXFLAGS="$CXXFLAGS -Wall -Wno-date-time -Wno-unused-function -Wno-deprecated -Wno-deprecated-declarations" + + if test "$SW_OS" = "CYGWIN" || test "$SW_OS" = "MINGW"; then + CXXFLAGS="$CXXFLAGS -std=gnu++14" diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index 4d1f7d2aa..f47340a03 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -21,19 +21,19 @@ // test os (macos-13, macos-14, macos-15, ubuntu-latest, windows-latest are available) $test_os = [ - // 'macos-13', // bin/spc for x86_64 + 'macos-13', // bin/spc for x86_64 // 'macos-14', // bin/spc for arm64 'macos-15', // bin/spc for arm64 - 'ubuntu-latest', // bin/spc-alpine-docker for x86_64 - // 'ubuntu-22.04', // bin/spc-gnu-docker for x86_64 - // 'ubuntu-24.04', // bin/spc for x86_64 + // 'ubuntu-latest', // bin/spc-alpine-docker for x86_64 + 'ubuntu-22.04', // bin/spc-gnu-docker for x86_64 + 'ubuntu-24.04', // bin/spc for x86_64 'ubuntu-22.04-arm', // bin/spc-gnu-docker for arm64 'ubuntu-24.04-arm', // bin/spc for arm64 // 'windows-latest', // .\bin\spc.ps1 ]; // whether enable thread safe -$zts = false; +$zts = true; $no_strip = false; @@ -41,15 +41,15 @@ $upx = false; // whether to test frankenphp build, only available for macos and linux -$frankenphp = false; +$frankenphp = true; // prefer downloading pre-built packages to speed up the build process $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' => 'grpc', - 'Windows' => 'curl', + 'Linux', 'Darwin' => 'ast,bcmath,calendar,ctype,dba,dom,exif,fileinfo,filter,libxml,mbregex,mbstring,pcntl,phar,posix,session,simplexml,sockets,sodium,tokenizer,xml,xmlreader,xmlwriter,zlib', + 'Windows' => 'intl', }; // If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`). @@ -60,7 +60,7 @@ }; // If you want to test lib-suggests for all extensions and libraries, set it to true. -$with_suggested_libs = false; +$with_suggested_libs = true; // If you want to test extra libs for extensions, add them below (comma separated, example `libwebp,libavif`). Unnecessary, when $with_suggested_libs is true. $with_libs = match (PHP_OS_FAMILY) { @@ -72,7 +72,7 @@ // You can use `common`, `bulk`, `minimal` or `none`. // note: combination is only available for *nix platform. Windows must use `none` combination $base_combination = match (PHP_OS_FAMILY) { - 'Linux', 'Darwin' => 'none', + 'Linux', 'Darwin' => 'minimal', 'Windows' => 'none', }; @@ -156,6 +156,9 @@ function quote2(string $param): string case 'ubuntu-22.04-arm': $shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' '; break; + case 'ubuntu-24.04': + case 'ubuntu-24.04-arm': + break; case 'macos-13': case 'macos-14': case 'macos-15': diff --git a/tests/SPC/builder/linux/SystemUtilTest.php b/tests/SPC/builder/linux/SystemUtilTest.php index 15552bcd3..01d555d81 100644 --- a/tests/SPC/builder/linux/SystemUtilTest.php +++ b/tests/SPC/builder/linux/SystemUtilTest.php @@ -43,16 +43,6 @@ public function testFindHeader() $this->assertIsArray(SystemUtil::findHeader('elf.h')); } - public function testGetCrossCompilePrefix() - { - $this->assertIsString(SystemUtil::getCrossCompilePrefix('gcc', 'x86_64')); - } - - public function testGetCCType() - { - $this->assertEquals('gcc', SystemUtil::getCCType('xjfoiewjfoewof-gcc')); - } - public function testGetSupportedDistros() { $this->assertIsArray(SystemUtil::getSupportedDistros()); diff --git a/tests/SPC/util/GlobalEnvManagerTest.php b/tests/SPC/util/GlobalEnvManagerTest.php index 28d60d34d..1caeaa19b 100644 --- a/tests/SPC/util/GlobalEnvManagerTest.php +++ b/tests/SPC/util/GlobalEnvManagerTest.php @@ -23,6 +23,10 @@ protected function setUp(): void 'SPC_TARGET' => getenv('SPC_TARGET'), 'SPC_LIBC' => getenv('SPC_LIBC'), ]; + // Temporarily set private GlobalEnvManager::$initialized to false (use reflection) + $reflection = new \ReflectionClass(GlobalEnvManager::class); + $property = $reflection->getProperty('initialized'); + $property->setValue(null, false); } protected function tearDown(): void @@ -35,6 +39,10 @@ protected function tearDown(): void putenv("{$key}={$value}"); } } + // Temporarily set private GlobalEnvManager::$initialized to false (use reflection) + $reflection = new \ReflectionClass(GlobalEnvManager::class); + $property = $reflection->getProperty('initialized'); + $property->setValue(null, true); } public function testGetInitializedEnv(): void diff --git a/tests/SPC/util/SPCConfigUtilTest.php b/tests/SPC/util/SPCConfigUtilTest.php index c37f01f38..7b8baa6c5 100644 --- a/tests/SPC/util/SPCConfigUtilTest.php +++ b/tests/SPC/util/SPCConfigUtilTest.php @@ -57,18 +57,18 @@ public function testConfig(): void $result = (new SPCConfigUtil())->config(['rar']); $this->assertStringContainsString(PHP_OS_FAMILY === 'Darwin' ? '-lc++' : '-lstdc++', $result['libs']); - // has mimalloc.o in lib dir + // has libmimalloc.a 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'); + if (file_exists(BUILD_LIB_PATH . '/libmimalloc.a')) { + $bak = file_get_contents(BUILD_LIB_PATH . '/libmimalloc.a'); + @unlink(BUILD_LIB_PATH . '/libmimalloc.a'); } - file_put_contents(BUILD_LIB_PATH . '/mimalloc.o', ''); + file_put_contents(BUILD_LIB_PATH . '/libmimalloc.a', ''); $result = (new SPCConfigUtil())->config(['bcmath'], ['mimalloc']); - $this->assertStringStartsWith(BUILD_LIB_PATH . '/mimalloc.o', $result['libs']); - @unlink(BUILD_LIB_PATH . '/mimalloc.o'); + $this->assertStringStartsWith(BUILD_LIB_PATH . '/libmimalloc.a', $result['libs']); + @unlink(BUILD_LIB_PATH . '/libmimalloc.a'); if (isset($bak)) { - file_put_contents(BUILD_LIB_PATH . '/mimalloc.o', $bak); + file_put_contents(BUILD_LIB_PATH . '/libmimalloc.a', $bak); } } } diff --git a/tests/SPC/util/SPCTargetTest.php b/tests/SPC/util/SPCTargetTest.php index dc003932a..665b4045b 100644 --- a/tests/SPC/util/SPCTargetTest.php +++ b/tests/SPC/util/SPCTargetTest.php @@ -4,7 +4,6 @@ namespace SPC\Tests\util; -use SPC\exception\WrongUsageException; use SPC\util\SPCTarget; /** @@ -35,17 +34,6 @@ protected function tearDown(): void } } - /** - * @dataProvider libcProvider - */ - public function testIsStatic(string $libc, bool $expected): void - { - putenv("SPC_LIBC={$libc}"); - - $result = SPCTarget::isStatic(); - $this->assertEquals($expected, $result); - } - /** * @dataProvider libcProvider */ @@ -85,19 +73,6 @@ public function testGetTargetOS(string $target, string $expected): void $this->assertEquals($expected, $result); } - /** - * @dataProvider invalidTargetProvider - */ - public function testGetTargetOSWithInvalidTarget(string $target): void - { - putenv("SPC_TARGET={$target}"); - - $this->expectException(WrongUsageException::class); - $this->expectExceptionMessage('Cannot parse target.'); - - SPCTarget::getTargetOS(); - } - public function testLibcListConstant(): void { $this->assertIsArray(SPCTarget::LIBC_LIST); @@ -117,22 +92,13 @@ public function libcProvider(): array public function targetOSProvider(): array { return [ - 'linux-target' => ['linux-x86_64', 'Linux'], - 'macos-target' => ['macos-x86_64', 'Darwin'], - 'windows-target' => ['windows-x86_64', 'Windows'], + 'linux-target' => ['native-linux', 'Linux'], + 'macos-target' => ['native-macos', 'Darwin'], + 'windows-target' => ['native-windows', 'Windows'], 'empty-target' => ['', PHP_OS_FAMILY], ]; } - public function invalidTargetProvider(): array - { - return [ - 'invalid-target' => ['invalid-target'], - 'unknown-target' => ['unknown-target'], - 'mixed-target' => ['mixed-target'], - ]; - } - private function assertIsStringOrNull($value): void { $this->assertTrue(is_string($value) || is_null($value), 'Value must be string or null');