diff --git a/.github/workflows/build-unix.yml b/.github/workflows/build-unix.yml
index 16ad76e0..0166bfa0 100644
--- a/.github/workflows/build-unix.yml
+++ b/.github/workflows/build-unix.yml
@@ -136,12 +136,12 @@ jobs:
macos-x86_64)
DOWN_CMD="composer update --no-dev --classmap-authoritative && ./bin/spc doctor --auto-fix && ./bin/spc download"
BUILD_CMD="./bin/spc build"
- RUNS_ON="macos-13"
+ RUNS_ON="macos-15-intel"
;;
macos-aarch64)
DOWN_CMD="composer update --no-dev --classmap-authoritative && ./bin/spc doctor --auto-fix && ./bin/spc download"
BUILD_CMD="./bin/spc build"
- RUNS_ON="macos-14"
+ RUNS_ON="macos-15"
;;
esac
DOWN_CMD="$DOWN_CMD --with-php=${{ inputs.php-version }} --for-extensions=${{ inputs.extensions }} --ignore-cache-sources=php-src"
diff --git a/.github/workflows/ext-matrix-tests.yml b/.github/workflows/ext-matrix-tests.yml
index 84d3b518..e93e9145 100644
--- a/.github/workflows/ext-matrix-tests.yml
+++ b/.github/workflows/ext-matrix-tests.yml
@@ -85,9 +85,9 @@ jobs:
- "8.4"
operating-system:
- "ubuntu-latest"
- #- "macos-13"
+ #- "macos-15-intel"
#- "debian-arm64-self-hosted"
- - "macos-14"
+ - "macos-15"
steps:
- name: "Checkout"
@@ -99,11 +99,11 @@ jobs:
OS=""
if [ "${{ matrix.operating-system }}" = "ubuntu-latest" ]; then
OS="linux-x86_64"
- elif [ "${{ matrix.operating-system }}" = "macos-13" ]; then
+ elif [ "${{ matrix.operating-system }}" = "macos-15-intel" ]; then
OS="macos-x86_64"
elif [ "${{ matrix.operating-system }}" = "debian-arm64-self-hosted" ]; then
OS="linux-aarch64"
- elif [ "${{ matrix.operating-system }}" = "macos-14" ]; then
+ elif [ "${{ matrix.operating-system }}" = "macos-15" ]; then
OS="macos-aarch64"
fi
echo "OS=$OS" >> $GITHUB_ENV
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 61004bd3..aaeee1ff 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -27,7 +27,7 @@ jobs:
os: "ubuntu-latest"
filename: "spc-linux-x86_64.tar.gz"
- name: "macos-x86_64"
- os: "macos-13"
+ os: "macos-15-intel"
filename: "spc-macos-x86_64.tar.gz"
- name: "linux-aarch64"
os: "ubuntu-latest"
@@ -147,11 +147,11 @@ jobs:
- name: "linux-x86_64"
os: "ubuntu-latest"
- name: "macos-x86_64"
- os: "macos-13"
+ os: "macos-15-intel"
- name: "linux-aarch64"
os: "ubuntu-24.04-arm"
- name: "macos-aarch64"
- os: "macos-latest"
+ os: "macos-15"
- name: "windows-x64"
os: "windows-latest"
steps:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 8accd62f..fa9e646f 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,13 +1,9 @@
name: Tests
on:
- push:
- branches:
- - main
- paths:
- - 'src/globals/test-extensions.php'
pull_request:
branches: [ "main" ]
+ types: [ opened, synchronize, reopened ]
paths:
- 'src/**'
- 'config/**'
diff --git a/.gitignore b/.gitignore
index 4bf41b55..0d2bd554 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,9 @@ docker/source/
# default package root directory
/pkgroot/**
+# Windows PHP SDK binary tools
+/php-sdk-binary-tools/**
+
# default pack:lib and release directory
/dist/**
packlib_files.txt
diff --git a/README-zh.md b/README-zh.md
index b159a5c3..d8d1b396 100755
--- a/README-zh.md
+++ b/README-zh.md
@@ -5,310 +5,168 @@
[](https://github.com/crazywhalecc/static-php-cli/releases)
[](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
-[](https://static-php.dev/zh/guide/extensions.html)
-**static-php-cli**是一个用于静态编译、构建 PHP 解释器的工具,支持众多流行扩展。
-
-目前 static-php-cli 支持 `cli`、`fpm`、`embed`、`micro` 和 `frankenphp` SAPI。
-
-**static-php-cli**也支持将 PHP 代码和 PHP 运行时打包为一个文件并运行。
+**static-php-cli** 是一个用于构建静态、独立 PHP 运行时的强大工具,支持众多流行扩展。
## 特性
-static-php-cli(简称 `spc`)有许多特性:
+- :elephant: **支持多 PHP 版本** - 支持 PHP 8.1, 8.2, 8.3, 8.4, 8.5
+- :handbag: **单文件 PHP 可执行文件** - 构建零依赖的独立 PHP
+- :hamburger: **phpmicro 集成** - 构建 **[phpmicro](https://github.com/dixyes/phpmicro)** 自解压可执行文件(将 PHP 二进制文件和源代码合并为一个文件)
+- :pill: **智能环境检查器** - 自动构建环境检查器,具备自动修复功能
+- :zap: **跨平台支持** - 支持 Linux、macOS、FreeBSD 和 Windows
+- :wrench: **可配置补丁** - 可自定义的源代码补丁系统
+- :books: **智能依赖管理** - 自动处理构建依赖
+- 📦 **自包含工具** - 提供使用 [box](https://github.com/box-project/box) 构建的 `spc` 可执行文件
+- :fire: **广泛的扩展支持** - 支持 75+ 流行 [扩展](https://static-php.dev/zh/guide/extensions.html)
+- :floppy_disk: **UPX 压缩** - 减小二进制文件大小 30-50%(仅 Linux/Windows)
-- :handbag: 构建独立的单文件 PHP 解释器,无需任何依赖
-- :hamburger: 构建 **[phpmicro](https://github.com/dixyes/phpmicro)** 自执行二进制(将 PHP 代码和 PHP 解释器打包为一个文件)
-- :pill: 提供一键检查和修复编译环境的 Doctor 模块
-- :zap: 支持多个系统:`Linux`、`macOS`、`FreeBSD`、`Windows`
-- :wrench: 高度自定义的代码 patch 功能
-- :books: 自带编译依赖管理
-- 📦 提供由自身编译的独立 `spc` 二进制(使用 spc 和 [box](https://github.com/box-project/box) 构建)
-- :fire: 支持大量 [扩展](https://static-php.dev/zh/guide/extensions.html)
-- :floppy_disk: 整合 UPX 工具(减小二进制文件体积)
-
-**静态 php-cli:**
+**单文件独立 php-cli:**
-**使用 phpmicro 打包 PHP 代码:**
+**使用 phpmicro 将 PHP 代码与 PHP 解释器结合:**
-## 文档
+## 快速开始
-目前 README 编写了基本用法。有关 static-php-cli 所有的功能,请点击这里查看文档:。
-
-## 直接下载
-
-如果你不想自行编译 PHP,可以从本项目现有的示例 Action 下载 Artifact,也可以从自托管的服务器下载。
-
-| 组合名称 | 组合扩展数 | 系统 | 备注 |
-|---------------------------------------------------------------------|----------------------------------------------------------------------------|-------------|--------------|
-| [common](https://dl.static-php.dev/static-php-cli/common/) | [30+](https://dl.static-php.dev/static-php-cli/common/README.txt) | Linux/macOS | 体积为 7.5MB 左右 |
-| [bulk](https://dl.static-php.dev/static-php-cli/bulk/) | [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) | Linux/macOS | 体积为 25MB 左右 |
-| [minimal](https://dl.static-php.dev/static-php-cli/minimal/) | [5](https://dl.static-php.dev/static-php-cli/minimal/README.txt) | Linux/macOS | 体积为 3MB 左右 |
-| [spc-min](https://dl.static-php.dev/static-php-cli/windows/spc-min) | [5](https://dl.static-php.dev/static-php-cli/windows/spc-min/README.txt) | Windows | 体积为 3MB 左右 |
-| [spc-max](https://dl.static-php.dev/static-php-cli/windows/spc-max) | [40+](https://dl.static-php.dev/static-php-cli/windows/spc-max/README.txt) | Windows | 体积为 8.5MB 左右 |
-
-> Linux 和 Windows 默认启用了 UPX 压缩,可减小 30~50% 的 PHP 二进制体积。
-> macOS 当前不支持 UPX,所以上述预编译的 macOS 版本体积可能较大。
-
-## 使用 static-php-cli 构建 PHP
-
-### 编译环境需求
-
-- PHP >= 8.4(这是 spc 自身需要的版本,不是支持的构建版本)
-- 扩展:`mbstring,tokenizer,phar`
-- 系统安装了 `curl` 和 `git`
-
-是的,本项目采用 PHP 编写,编译前需要一个 PHP 环境,比较滑稽。
-但本项目默认可通过自身构建的 micro 和 static-php 二进制运行,其他只需要包含上面提到的扩展和 PHP 版本大于等于 8.1 即可。
-
-下面是架构支持情况,:octocat: 代表支持 GitHub Action 构建,:computer: 代表支持本地构建,空 代表暂不支持。
-
-| | x86_64 | aarch64 |
-|---------|----------------------|----------------------|
-| macOS | :octocat: :computer: | :octocat: :computer: |
-| Linux | :octocat: :computer: | :octocat: :computer: |
-| Windows | :octocat: :computer: | |
-| FreeBSD | :computer: | :computer: |
-
-当前支持编译的 PHP 版本:
-
-> :warning: 部分支持,对于新的测试版和旧版本可能存在问题。
->
-> :heavy_check_mark: 支持
->
-> :x: 不支持
-
-| PHP Version | Status | Comment |
-|-------------|--------------------|---------------------------------------------------------|
-| 7.2 | :x: | |
-| 7.3 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
-| 7.4 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
-| 8.0 | :warning: | PHP 官方已停止 8.0 的维护,我们不再处理 8.0 相关的 backport 支持 |
-| 8.1 | :heavy_check_mark: | PHP 官方仅对 8.1 提供安全更新,在 8.5 发布后我们不再处理 8.1 相关的 backport 支持 |
-| 8.2 | :heavy_check_mark: | |
-| 8.3 | :heavy_check_mark: | |
-| 8.4 | :heavy_check_mark: | |
-| 8.5 (alpha) | :warning: | PHP 8.5 目前处于 alpha 阶段 |
-
-> 这个表格的支持状态是 static-php-cli 对构建对应版本的支持情况,不是 PHP 官方对该版本的支持情况。
-
-### 支持的扩展
-
-请先根据下方扩展列表选择你要编译的扩展。
-
-- [扩展支持列表](https://static-php.dev/zh/guide/extensions.html)
-- [编译命令生成器](https://static-php.dev/zh/guide/cli-generator.html)
-
-> 如果这里没有你需要的扩展,可以提交 Issue。
-
-### 在线构建(使用 GitHub Actions)
-
-使用 GitHub Action 可以方便地构建一个静态编译的 PHP,同时可以自行定义要编译的扩展。
-
-1. Fork 本项目。
-2. 进入项目的 Actions,选择 CI。
-3. 选择 `Run workflow`,填入你要编译的 PHP 版本、目标类型、扩展列表。(扩展列表使用英文逗号分割,例如 `bcmath,curl,mbstring`)
-4. 等待大约一段时间后,进入对应的任务中,获取 `Artifacts`。
-
-如果你选择了 `debug`,则会在构建时输出所有日志,包括编译的日志,以供排查错误。
-
-### 本地构建(使用 spc 二进制,推荐)
-
-该项目提供了 static-php-cli 的二进制文件:`spc`。
-您可以使用 `spc` 二进制文件,无需安装任何运行时(用起来就像 golang 程序)。
-目前,`spc` 二进制文件提供的平台有 Linux 和 macOS。
-
-使用以下命令从自托管服务器下载:
+### 1. 下载 spc 二进制文件
```bash
-# Download from self-hosted nightly builds (sync with main branch)
-# For Linux x86_64
+# Linux x86_64
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
-# For Linux aarch64
+# Linux aarch64
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
# macOS x86_64 (Intel)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
-# Windows (x86_64, win10 build 17063 or later)
+# Windows (x86_64, win10 build 17063 或更高版本,请先安装 VS2022)
curl.exe -fsSL -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
+```
-# Add execute perm (Linux and macOS only)
+对于 macOS 和 Linux,请先添加执行权限:
+
+```bash
chmod +x ./spc
-
-# Run (Linux and macOS)
-./spc --version
-# Run (Windows powershell)
-.\spc.exe --version
```
-自托管 `spc` 由 GitHub Actions 构建,你也可以从 Actions 直接下载:[此处](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml)。
+### 2. 构建静态 PHP
-### 本地构建(使用 git 源码)
+首先,创建一个 `craft.yml` 文件,并从 [扩展列表](https://static-php.dev/zh/guide/extensions.html) 或 [命令生成器](https://static-php.dev/zh/guide/cli-generator.html) 中指定要包含的扩展:
-如果你需要修改 static-php-cli 源码,或者使用 spc 二进制构建有问题,你可以使用 git 源码下载 static-php-cli。
+```yml
+# PHP 版本支持:8.1, 8.2, 8.3, 8.4, 8.5
+php-version: 8.4
+# 在此处放置您的扩展列表
+extensions: "apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,gd,iconv,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,simplexml,sockets,sodium,sqlite3,tokenizer,xml,xmlreader,xmlwriter,xsl,zip,zlib"
+sapi:
+ - cli
+ - micro
+ - fpm
+download-options:
+ prefer-pre-built: true
+```
+
+运行命令:
```bash
-# clone 仓库即可
-git clone https://github.com/crazywhalecc/static-php-cli.git
+./spc craft
+
+# 输出完整控制台日志
+./spc craft --debug
```
-如果您的系统上尚未安装 php,我们建议你使用内置的 setup-runtime 自动安装 PHP 和 Composer。
+### 3. 静态 PHP 使用
-```bash
-cd static-php-cli
-chmod +x bin/setup-runtime
-# it will download static php (from self-hosted server) and composer (from getcomposer)
-bin/setup-runtime
-# initialize composer deps
-bin/composer install
-# chmod
-chmod +x bin/spc
-bin/spc --version
+现在您可以将 static-php-cli 构建的二进制文件复制到另一台机器上,无需依赖即可运行:
+
+```
+# php-cli
+buildroot/bin/php -v
+
+# phpmicro
+echo ' a.php
+./spc micro:combine a.php -O my-app
+./my-app
+
+# php-fpm
+buildroot/bin/php-fpm -v
```
-### 开始构建 PHP
+## 文档
-下面是使用 static-php-cli 的基础用法:
+当前 README 包含基本用法。有关 static-php-cli 的所有功能,
+请访问 。
-> 如果你使用的是打包好的 `spc` 二进制,你需要将下列命令的 `./bin/spc` 替换为 `./spc`。
+## 直接下载
-```bash
-# 检查环境依赖,并根据尝试自动安装缺失的编译工具
-./bin/spc doctor --auto-fix
+如果您不想构建或想先测试,可以从 [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-bulk.yml) 下载示例预编译工件,或从自托管服务器下载。
-# 输出目标项目依赖的扩展列表
-./bin/spc dump-extensions /path/to/your/project --format=text
+以下是几个具有不同扩展组合的预编译静态 PHP 二进制文件,
+您可以根据需要直接下载。
-# 拉取所有依赖库
-./bin/spc download --all
-# 只拉取编译指定扩展需要的所有依赖(推荐)
-./bin/spc download --for-extensions="openssl,pcntl,mbstring,pdo_sqlite"
-# 下载依赖时,优先下载有预编译的库(节省编译依赖的时间)
-./bin/spc download --for-extensions="openssl,curl,mbstring,mbregex" --prefer-pre-built
-# 下载编译不同版本的 PHP (--with-php=x.y 或 --with-php=x.y.z,推荐 8.1 ~ 8.3)
-./bin/spc download --for-extensions="openssl,curl,mbstring" --with-php=8.1
+| 组合名称 | 扩展数量 | 系统 | 备注 |
+|----------------------------------------------------------------------|----------------------------------------------------------------------------|--------------|--------------------|
+| [common](https://dl.static-php.dev/static-php-cli/common/) | [30+](https://dl.static-php.dev/static-php-cli/common/README.txt) | Linux, macOS | 二进制文件大小约为 7.5MB |
+| [bulk](https://dl.static-php.dev/static-php-cli/bulk/) | [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) | Linux, macOS | 二进制文件大小约为 25MB |
+| [gnu-bulk](https://dl.static-php.dev/static-php-cli/gnu-bulk/) | [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) | Linux, macOS | 使用 glibc 的 bulk 组合 |
+| [minimal](https://dl.static-php.dev/static-php-cli/minimal/) | [5](https://dl.static-php.dev/static-php-cli/minimal/README.txt) | Linux, macOS | 二进制文件大小约为 3MB |
+| [spc-min](https://dl.static-php.dev/static-php-cli/windows/spc-min/) | [5](https://dl.static-php.dev/static-php-cli/windows/spc-min/README.txt) | Windows | 二进制文件大小约为 3MB |
+| [spc-max](https://dl.static-php.dev/static-php-cli/windows/spc-max/) | [40+](https://dl.static-php.dev/static-php-cli/windows/spc-max/README.txt) | Windows | 二进制文件大小约为 8.5MB |
-# 构建包含 bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl 扩展的 php-cli 和 micro.sfx
-./bin/spc build "bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl" --build-cli --build-micro
-# 编译线程安全版本 (--enable-zts)
-./bin/spc build "curl,phar" --enable-zts --build-cli
-# 编译后使用 UPX 减小可执行文件体积 (仅 Linux、Windows 可用) (至少压缩至原来的 30~50%)
-./bin/spc build "curl,phar" --enable-zts --build-cli --with-upx-pack
-```
+> Linux 和 Windows 支持对二进制文件进行 UPX 压缩,可以将二进制文件大小减少 30% 到 50%。
+> macOS 不支持 UPX 压缩,因此 mac 的预构建二进制文件大小较大。
-其中,目前支持构建 cli,micro,fpm 和 embed,使用以下参数的一个或多个来指定编译的 SAPI:
+### 在线构建(使用 GitHub Actions)
-- `--build-cli`:构建 cli 二进制
-- `--build-micro`:构建 phpmicro 自执行二进制
-- `--build-fpm`:构建 fpm
-- `--build-embed`:构建 embed(libphp)
-- `--build-cgi`: 构建 cgi(不推荐)
-- `--build-all`:构建所有
+上方直接下载的二进制不能满足需求时,可使用 GitHub Action 可以轻松构建静态编译的 PHP,
+同时自行定义要编译的扩展。
-如果出现了任何错误,可以使用 `--debug` 参数来展示完整的输出日志,以供排查错误:
+1. Fork 本项目。
+2. 进入项目的 Actions 并选择 `CI`。
+3. 选择 `Run workflow`,填入您要编译的 PHP 版本、目标类型和扩展列表。(扩展用逗号分隔,例如 `bcmath,curl,mbstring`)
+4. 等待一段时间后,进入相应的任务并获取 `Artifacts`。
-```bash
-./bin/spc build "openssl,pcntl,mbstring" --debug --build-all
-./bin/spc download --all --debug
-```
-
-## 不同 SAPI 的使用
-
-### 使用 cli
-
-> php-cli 是一个静态的二进制文件,类似 Go、Rust 语言编译后的单个可移植的二进制文件。
-
-采用参数 `--build-cli` 或`--build-all` 参数时,最后编译结果会输出一个 `./php` 的二进制文件,此文件可分发、可直接使用。
-该文件编译后会存放在 `buildroot/bin/` 目录中,名称为 `php`,拷贝出来即可。
-
-```bash
-cd buildroot/bin/
-./php -v # 检查版本
-./php -m # 检查编译的扩展
-./php your_code.php # 运行代码
-./php your_project.phar # 运行打包为 phar 单文件的项目
-```
-
-### 使用 micro
-
-> phpmicro 是一个提供自执行二进制 PHP 的项目,本项目依赖 phpmicro 进行编译自执行二进制。详见 [dixyes/phpmicro](https://github.com/dixyes/phpmicro)。
-
-采用项目参数 `--build-micro` 或 `--build-all` 时,最后编译结果会输出一个 `./micro.sfx` 的文件,此文件需要配合你的 PHP 源码使用。
-该文件编译后会存放在 `buildroot/bin/` 目录中,拷贝出来即可。
-
-使用时应准备好你的项目源码文件,可以是单个 PHP 文件,也可以是 Phar 文件。
-
-```bash
-echo " code.php
-cat micro.sfx code.php > single-app && chmod +x single-app
-./single-app
-```
-
-如果打包 PHAR 文件,仅需把 code.php 更换为 phar 文件路径即可。
-你可以使用 [box-project/box](https://github.com/box-project/box) 将你的 CLI 项目打包为 Phar,
-然后将它与 phpmicro 结合,生成独立可执行的二进制文件。
-
-```bash
-# 使用 static-php-cli 生成的 micro.sfx 结合,也可以直接使用 cat 命令结合它们
-bin/spc micro:combine my-app.phar
-cat buildroot/bin/micro.sfx my-app.phar > my-app && chmod +x my-app
-
-# 使用 micro:combine 结合可以将 INI 选项注入到二进制中
-bin/spc micro:combine my-app.phar -I "memory_limit=4G" -I "disable_functions=system" --output my-app-2
-```
-
-> 有些情况下的 phar 文件或 PHP 项目可能无法在 micro 环境下运行。
-
-### 使用 fpm
-
-采用项目参数 `--build-fpm` 或 `--build-all` 时,最后编译结果会输出一个 `./php-fpm` 的文件。
-该文件存放在 `buildroot/bin/` 目录,拷贝出来即可使用。
-
-在正常的 Linux 发行版和 macOS 系统中,安装 php-fpm 后包管理会自动生成默认的 fpm 配置文件。
-因为 php-fpm 必须指定配置文件才可启动,本项目编译的 php-fpm 不会带任何配置文件,所以需自行编写 `php-fpm.conf` 和 `pool.conf` 配置文件。
-
-指定 `php-fpm.conf` 可以使用命令参数 `-y`,例如:`./php-fpm -y php-fpm.conf`。
-
-### 使用 embed
-
-采用项目参数 `--build-embed` 或 `--build-all` 时,最后编译结果会输出一个 `libphp.a`、`php-config` 以及一系列头文件,存放在 `buildroot/`,你可以在你的其他代码中引入它们。
-
-如果你知道 [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed),你应该知道如何使用它。对于有可能编译用到引入其他库的问题,你可以使用 `buildroot/bin/php-config` 来获取编译时的配置。
-
-另外,有关如何使用此功能的高级示例,请查看[如何使用它构建 FrankenPHP 的静态版本](https://github.com/php/frankenphp/blob/main/docs/static.md)。
+如果您启用 `debug`,构建时将输出所有日志,包括编译日志,以便故障排除。
## 贡献
-如果缺少你需要的扩展,可发起 Issue。如果你对本项目较熟悉,也欢迎为本项目发起 Pull Request。
+如果您需要的扩展缺失,可以创建 issue。
+如果您熟悉本项目,也欢迎发起 pull request。
-另外,添加新扩展的贡献方式,可以参考下方 `进阶`。
+如果您想贡献文档,请直接编辑 `docs/` 目录。
-如果你想贡献文档内容,请直接修改 `docs/` 目录。
+现在有一个 [static-php](https://github.com/static-php) 组织,用于存储与项目相关的仓库。
## 赞助本项目
-你可以在 [我的个人赞助页](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md) 支持我和我的项目。你捐赠的一部分将会被用于维护 **static-php.dev** 服务器。
+您可以从 [GitHub Sponsor](https://github.com/crazywhalecc) 赞助我或我的项目。您捐赠的一部分将用于维护 **static-php.dev** 服务器。
-**特别赞助商**:
+**特别感谢以下赞助商**:
-## 开源协议
+## 开源许可证
-本项目采用 MIT License 许可开源,下面是类似的项目:
+本项目本身基于 MIT 许可证,
+一些新添加的扩展和依赖可能来自其他项目,
+这些代码文件的头部也会给出额外的许可证和作者说明。
+
+这些是类似的项目:
- [dixyes/lwmbs](https://github.com/dixyes/lwmbs)
- [swoole/swoole-cli](https://github.com/swoole/swoole-cli)
-该项目使用了 [dixyes/lwmbs](https://github.com/dixyes/lwmbs) 中的一些代码,例如 Windows 静态构建目标和 libiconv 库支持。
-lwmbs 使用 [Mulan PSL 2](http://license.coscl.org.cn/MulanPSL2) 许可进行分发。对应文件有关于作者和许可的特殊说明,除此之外,均使用 MIT 授权许可。
+本项目使用了 [dixyes/lwmbs](https://github.com/dixyes/lwmbs) 的一些代码,例如 Windows 静态构建目标和 libiconv 支持。
+lwmbs 基于 [Mulan PSL 2](http://license.coscl.org.cn/MulanPSL2) 许可证。
-因本项目的特殊性,使用项目编译过程中会使用很多其他开源项目,例如 curl、protobuf 等,它们都有各自的开源协议。
-请在编译完成后,使用命令 `bin/spc dump-license` 导出项目使用项目的开源协议,并遵守对应项目的 LICENSE。
+由于本项目的特殊性,
+项目编译过程中会使用许多其他开源项目,如 curl 和 protobuf,
+它们都有自己的开源许可证。
+
+请在编译后使用 `bin/spc dump-license` 命令导出项目中使用的开源许可证,
+并遵守相应项目的 LICENSE。
diff --git a/README.md b/README.md
index e405edf4..3f3bfbf1 100755
--- a/README.md
+++ b/README.md
@@ -5,29 +5,22 @@
[](https://github.com/crazywhalecc/static-php-cli/releases)
[](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
-[](https://static-php.dev/en/guide/extensions.html)
**static-php-cli** is a powerful tool designed for building static, standalone PHP runtime
with popular extensions.
-Static PHP built by **static-php-cli** supports `cli`, `fpm`, `embed`, `micro` and `frankenphp` SAPI.
-
-**static-php-cli** also has the ability to package PHP projects
-along with the PHP interpreter into one single executable file.
-
## Features
-static-php-cli (you can call it `spc`) has a lot of features:
-
-- :handbag: Build single-file php executable, without any dependencies
-- :hamburger: Build **[phpmicro](https://github.com/dixyes/phpmicro)** self-extracted executable (glue php binary and php source code into one file)
-- :pill: Automatic build environment checker (Doctor module)
+- :elephant: Support multiple PHP versions - PHP 8.1, 8.2, 8.3, 8.4, 8.5
+- :handbag: Build single-file PHP executable with zero dependencies
+- :hamburger:Build **[phpmicro](https://github.com/dixyes/phpmicro)** self-extracting executables (combines PHP binary and source code into one file)
+- :pill: Automatic build environment checker with auto-fix capabilities
- :zap: `Linux`, `macOS`, `FreeBSD`, `Windows` support
-- :wrench: Configurable source code patches
-- :books: Build dependency management
-- 📦 Provide `spc` own standalone executable (built by spc and [box](https://github.com/box-project/box))
-- :fire: Support many popular [extensions](https://static-php.dev/en/guide/extensions.html)
-- :floppy_disk: UPX integration (significantly reduces binary size)
+- :wrench: Configurable source code patching
+- :books: Intelligent dependency management
+- 📦 Self-contained `spc` executable (built with [box](https://github.com/box-project/box))
+- :fire: Support 100+ popular [extensions](https://static-php.dev/en/guide/extensions.html)
+- :floppy_disk: UPX compression support (reduces binary size by 30-50%)
**Single-file standalone php-cli:**
@@ -37,6 +30,72 @@ static-php-cli (you can call it `spc`) has a lot of features:
+## Quickstart
+
+### 1. Download spc binary
+
+```bash
+# For Linux x86_64
+curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
+# For Linux aarch64
+curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
+# macOS x86_64 (Intel)
+curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
+# macOS aarch64 (Apple)
+curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
+# Windows (x86_64, win10 build 17063 or later, please install VS2022 first)
+curl.exe -fsSL -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
+```
+
+For macOS and Linux, add execute permission first:
+
+```bash
+chmod +x ./spc
+```
+
+### 2. Build Static PHP
+
+First, create a `craft.yml` file and specify which extensions you want to include from [extension list](https://static-php.dev/en/guide/extensions.html) or [command generator](https://static-php.dev/en/guide/cli-generator.html):
+
+```yml
+# PHP version support: 8.1, 8.2, 8.3, 8.4, 8.5
+php-version: 8.4
+# Put your extension list here
+extensions: "apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,gd,iconv,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,simplexml,sockets,sodium,sqlite3,tokenizer,xml,xmlreader,xmlwriter,xsl,zip,zlib"
+sapi:
+ - cli
+ - micro
+ - fpm
+download-options:
+ prefer-pre-built: true
+```
+
+Run command:
+
+```bash
+./spc craft
+
+# Output full console log
+./spc craft --debug
+```
+
+### 3. Static PHP usage
+
+Now you can copy binaries built by static-php-cli to another machine and run with no dependencies:
+
+```
+# php-cli
+buildroot/bin/php -v
+
+# phpmicro
+echo ' a.php
+./spc micro:combine a.php -O my-app
+./my-app
+
+# php-fpm
+buildroot/bin/php-fpm -v
+```
+
## Documentation
The current README contains basic usage. For all the features of static-php-cli,
@@ -53,6 +112,7 @@ which can be downloaded directly according to your needs.
|----------------------------------------------------------------------|----------------------------------------------------------------------------|--------------|--------------------------------|
| [common](https://dl.static-php.dev/static-php-cli/common/) | [30+](https://dl.static-php.dev/static-php-cli/common/README.txt) | Linux, macOS | The binary size is about 7.5MB |
| [bulk](https://dl.static-php.dev/static-php-cli/bulk/) | [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) | Linux, macOS | The binary size is about 25MB |
+| [gnu-bulk](https://dl.static-php.dev/static-php-cli/gnu-bulk/) | [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) | Linux, macOS | Using shared glibc |
| [minimal](https://dl.static-php.dev/static-php-cli/minimal/) | [5](https://dl.static-php.dev/static-php-cli/minimal/README.txt) | Linux, macOS | The binary size is about 3MB |
| [spc-min](https://dl.static-php.dev/static-php-cli/windows/spc-min/) | [5](https://dl.static-php.dev/static-php-cli/windows/spc-min/README.txt) | Windows | The binary size is about 3MB |
| [spc-max](https://dl.static-php.dev/static-php-cli/windows/spc-max/) | [40+](https://dl.static-php.dev/static-php-cli/windows/spc-max/README.txt) | Windows | The binary size is about 8.5MB |
@@ -60,64 +120,10 @@ which can be downloaded directly according to your needs.
> Linux and Windows supports UPX compression for binaries, which can reduce the size of the binary by 30% to 50%.
> macOS does not support UPX compression, so the size of the pre-built binaries for mac is larger.
-## Build
-
-### Compilation Requirements
-
-You can say I made a PHP builder written in PHP, pretty funny.
-But static-php-cli runtime only requires an environment above PHP 8.1 and extensions mentioned below.
-
-- PHP >= 8.4 (This is the version required by spc itself, not the build version)
-- Extension: `mbstring,tokenizer,phar`
-- Supported OS with `curl` and `git` installed
-
-Here is the supported OS and arch, where :octocat: represents support for GitHub Action builds,
-:computer: represents support for local manual builds, and blank represents not currently supported.
-
-| | x86_64 | aarch64 |
-|---------|----------------------|----------------------|
-| macOS | :octocat: :computer: | :octocat: :computer: |
-| Linux | :octocat: :computer: | :octocat: :computer: |
-| Windows | :octocat: :computer: | |
-| FreeBSD | :computer: | :computer: |
-
-Currently supported PHP versions for compilation:
-
-> :warning: Partial support, there may be issues with newer test versions or older versions.
->
-> :heavy_check_mark: supported
->
-> :x: not supported
-
-| PHP Version | Status | Comment |
-|-------------|--------------------|----------------------------------------------------------------------------------------------------|
-| 7.2 | :x: | |
-| 7.3 | :x: | phpmicro and some extensions not supported on 7.x |
-| 7.4 | :x: | phpmicro and some extensions not supported on 7.x |
-| 8.0 | :warning: | PHP official has stopped maintenance of 8.0, we no longer provide backport support for version 8.0 |
-| 8.1 | :heavy_check_mark: | PHP official has security fixes only, we no longer provide backport support when 8.5 released |
-| 8.2 | :heavy_check_mark: | |
-| 8.3 | :heavy_check_mark: | |
-| 8.4 | :heavy_check_mark: | |
-| 8.5 (alpha) | :warning: | PHP 8.5 is in alpha |
-
-> This table shows the support status for static-php-cli in building the corresponding version,
-> not the official PHP support status for that version.
-
-### Supported Extensions
-
-Please first select the extension you want to compile based on the extension list below.
-
-- [Supported Extension List](https://static-php.dev/en/guide/extensions.html)
-- [Command Generator](https://static-php.dev/en/guide/cli-generator.html)
-
-> If an extension you need is missing, you can submit an issue.
-
-Here is the current planned roadmap for extension support: [#152](https://github.com/crazywhalecc/static-php-cli/issues/152) .
-
### Build Online (using GitHub Actions)
-Use GitHub Action to easily build a statically compiled PHP,
+When the above direct download binaries cannot meet your needs,
+you can use GitHub Action to easily build a statically compiled PHP,
and at the same time define the extensions to be compiled by yourself.
1. Fork me.
@@ -127,185 +133,6 @@ and at the same time define the extensions to be compiled by yourself.
If you enable `debug`, all logs will be output at build time, including compiled logs, for troubleshooting.
-### Build Locally (using SPC binary, recommended)
-
-This project provides a binary file of static-php-cli: `spc`.
-You can use `spc` binary instead of installing any runtime like golang app.
-Currently, the platforms supported by `spc` binary are Linux and macOS.
-
-Download from self-hosted nightly builds using commands below:
-
-```bash
-# Download from self-hosted nightly builds (sync with main branch)
-# For Linux x86_64
-curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
-# For Linux aarch64
-curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
-# macOS x86_64 (Intel)
-curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
-# macOS aarch64 (Apple)
-curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
-# Windows (x86_64, win10 build 17063 or later)
-curl.exe -fsSL -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
-
-# Add execute perm (Linux and macOS only)
-chmod +x ./spc
-
-# Run (Linux and macOS)
-./spc --version
-# Run (Windows powershell)
-.\spc.exe --version
-```
-
-Self-hosted `spc` is built by GitHub Actions, you can also download from Actions artifacts [here](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml).
-
-### Build Locally (using git source)
-
-If you need to modify the static-php-cli source code, or have problems using the spc binary build,
-you can download static-php-cli using the git source code.
-
-```bash
-# just clone me!
-git clone https://github.com/crazywhalecc/static-php-cli.git
-```
-
-If you have not installed php on your system, we recommend that you use the built-in setup-runtime to install PHP and Composer automatically.
-
-```bash
-cd static-php-cli
-chmod +x bin/setup-runtime
-# it will download static php (from self-hosted server) and composer (from getcomposer)
-bin/setup-runtime
-# initialize composer deps
-bin/composer install
-# chmod
-chmod +x bin/spc
-bin/spc --version
-```
-
-### Start Building PHP
-
-Basic usage for building php with some extensions:
-
-> If you are using the packaged standalone `spc` binary, you need to replace `bin/spc` with `./spc` or `.\spc.exe` in the following commands.
-
-```bash
-# Check system tool dependencies, auto-fix them if possible
-./bin/spc doctor --auto-fix
-
-# fetch all libraries
-./bin/spc download --all
-# dump a list of extensions required by your project
-./bin/spc dump-extensions /path/to/your/project --format=text
-# only fetch necessary sources by needed extensions (recommended)
-./bin/spc download --for-extensions="openssl,pcntl,mbstring,pdo_sqlite"
-# download pre-built libraries first (save time for compiling dependencies)
-./bin/spc download --for-extensions="openssl,curl,mbstring,mbregex" --prefer-pre-built
-# download different PHP version (--with-php=x.y or --with-php=x.y.z, recommend 8.3 ~ 8.4)
-./bin/spc download --for-extensions="openssl,curl,mbstring" --with-php=8.1
-
-# with bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl extension, build both CLI and phpmicro SAPI
-./bin/spc build "bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl" --build-cli --build-micro
-# build thread-safe (ZTS) version (--enable-zts)
-./bin/spc build "curl,phar" --enable-zts --build-cli
-# build, pack executable with UPX (linux and windows only) (reduce binary size for 30~50%)
-./bin/spc build "curl,phar" --enable-zts --build-cli --with-upx-pack
-```
-
-Now we support `cli`, `micro`, `fpm` and `embed` SAPI. You can use one or more of the following parameters to specify the compiled SAPI:
-
-- `--build-cli`: build static cli executable
-- `--build-micro`: build static phpmicro self-extracted executable
-- `--build-fpm`: build static fpm binary
-- `--build-embed`: build embed (libphp)
-- `--build-cgi`: build cgi binary (not recommended)
-- `--build-all`: build all
-
-If anything goes wrong, use `--debug` option to display full terminal output:
-
-```bash
-./bin/spc build "openssl,pcntl,mbstring" --debug --build-all
-./bin/spc download --all --debug
-```
-
-## Different SAPI Usage
-
-### Use cli
-
-> php-cli is a single static binary, you can use it like normal php installed on your system.
-
-When using the parameter `--build-cli` or `--build-all`,
-the final compilation result will output a binary file named `./php`,
-which can be distributed and used directly.
-This file will be located in the directory `buildroot/bin/`, copy it out for use.
-
-```bash
-cd buildroot/bin/
-./php -v # check version
-./php -m # check extensions
-./php your_code.php # run your php code
-./php your_project.phar # run your phar (project archive)
-```
-
-### Use micro
-
-> phpmicro is a SelF-extracted eXecutable SAPI module,
-> provided by [phpmicro](https://github.com/dixyes/phpmicro) project.
-> But this project is using a [fork](https://github.com/static-php/phpmicro) of phpmicro, because we need to add some features to it.
-> It can put php runtime and your source code together.
-
-When using the parameter `--build-all` or `--build-micro`,
-the final compilation result will output a file named `./micro.sfx`,
-which needs to be used with your PHP source code like `code.php`.
-This file will be located in the path `buildroot/bin/micro.sfx`, simply copy it out for use.
-
-Prepare your project source code, which can be a single PHP file or a Phar file, for use.
-
-```bash
-echo " code.php
-cat micro.sfx code.php > single-app && chmod +x single-app
-./single-app
-```
-
-If you package a PHAR file, just replace `code.php` with the phar file path.
-You can use [box-project/box](https://github.com/box-project/box) to package your CLI project as Phar,
-It is then combined with phpmicro to produce a standalone executable binary.
-
-```bash
-# Use the micro.sfx generated by static-php-cli to combine,
-bin/spc micro:combine my-app.phar
-# or you can directly use the cat command to combine them.
-cat buildroot/bin/micro.sfx my-app.phar > my-app && chmod +x my-app
-
-# Use micro:combine combination to inject INI options into the binary.
-bin/spc micro:combine my-app.phar -I "memory_limit=4G" -I "disable_functions=system" --output my-app-2
-```
-
-> In some cases, PHAR files may not run in a micro environment. Overall, micro is not production ready.
-
-### Use fpm
-
-When using the parameter `--build-all` or `--build-fpm`,
-the final compilation result will output a file named `./php-fpm`,
-This file will be located in the path `buildroot/bin/`, simply copy it out for use.
-
-In common Linux distributions and macOS systems, the package manager will automatically generate a default fpm configuration file after installing php-fpm.
-Because php-fpm must specify a configuration file before running, the php-fpm compiled by this project will not have any configuration files, so you need to write `php-fpm.conf` and `pool.conf` configuration files yourself.
-
-Specifying `php-fpm.conf` can use the command parameter `-y`, for example: `./php-fpm -y php-fpm.conf`.
-
-### Use embed
-
-When using the project parameters `--build-embed` or `--build-all`,
-the final compilation result will output a `libphp.a`, `php-config` and a series of header files,
-stored in `buildroot/`. You can introduce them in your other projects.
-
-If you know [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed), you should know how to use it.
-You may require the introduction of other libraries during compilation,
-you can use `buildroot/bin/php-config` to obtain the compile-time configuration.
-
-For an advanced example of how to use this feature, take a look at [how to use it to build a static version of FrankenPHP](https://github.com/php/frankenphp/blob/main/docs/static.md).
-
## Contribution
If the extension you need is missing, you can create an issue.
diff --git a/bin/spc-alpine-docker b/bin/spc-alpine-docker
index 3a58547f..6365233c 100755
--- a/bin/spc-alpine-docker
+++ b/bin/spc-alpine-docker
@@ -150,6 +150,7 @@ 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)""/log:/app/log"
if [ -f "$(pwd)/craft.yml" ]; then
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/craft.yml:/app/craft.yml"
fi
diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker
index 97c9a089..7fcf5d41 100755
--- a/bin/spc-gnu-docker
+++ b/bin/spc-gnu-docker
@@ -158,6 +158,7 @@ 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)""/log:/app/log"
if [ -f "$(pwd)/craft.yml" ]; then
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/craft.yml:/app/craft.yml"
fi
diff --git a/composer.lock b/composer.lock
index 069a340f..4b28fc61 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8,7 +8,7 @@
"packages": [
{
"name": "illuminate/collections",
- "version": "v11.45.3",
+ "version": "v11.46.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/collections.git",
@@ -64,7 +64,7 @@
},
{
"name": "illuminate/conditionable",
- "version": "v11.45.3",
+ "version": "v11.46.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/conditionable.git",
@@ -110,7 +110,7 @@
},
{
"name": "illuminate/contracts",
- "version": "v11.45.3",
+ "version": "v11.46.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/contracts.git",
@@ -158,7 +158,7 @@
},
{
"name": "illuminate/macroable",
- "version": "v11.45.3",
+ "version": "v11.46.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/macroable.git",
@@ -416,16 +416,16 @@
},
{
"name": "symfony/console",
- "version": "v7.3.3",
+ "version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
+ "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
- "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
+ "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
+ "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
"shasum": ""
},
"require": {
@@ -490,7 +490,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.3.3"
+ "source": "https://github.com/symfony/console/tree/v7.3.4"
},
"funding": [
{
@@ -510,7 +510,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-25T06:35:40+00:00"
+ "time": "2025-09-22T15:31:00+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -916,16 +916,16 @@
},
{
"name": "symfony/process",
- "version": "v7.3.3",
+ "version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "32241012d521e2e8a9d713adb0812bb773b907f1"
+ "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1",
- "reference": "32241012d521e2e8a9d713adb0812bb773b907f1",
+ "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
+ "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
"shasum": ""
},
"require": {
@@ -957,7 +957,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.3.3"
+ "source": "https://github.com/symfony/process/tree/v7.3.4"
},
"funding": [
{
@@ -977,7 +977,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-18T09:42:54+00:00"
+ "time": "2025-09-11T10:12:26+00:00"
},
{
"name": "symfony/service-contracts",
@@ -1064,16 +1064,16 @@
},
{
"name": "symfony/string",
- "version": "v7.3.3",
+ "version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
+ "reference": "f96476035142921000338bad71e5247fbc138872"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
- "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
+ "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
+ "reference": "f96476035142921000338bad71e5247fbc138872",
"shasum": ""
},
"require": {
@@ -1088,7 +1088,6 @@
},
"require-dev": {
"symfony/emoji": "^7.1",
- "symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3.0",
@@ -1131,7 +1130,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.3.3"
+ "source": "https://github.com/symfony/string/tree/v7.3.4"
},
"funding": [
{
@@ -1151,7 +1150,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-25T06:35:40+00:00"
+ "time": "2025-09-11T14:36:48+00:00"
},
{
"name": "symfony/yaml",
@@ -2861,16 +2860,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
- "version": "v3.87.1",
+ "version": "v3.89.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
- "reference": "2f5170365e2a422d0c5421f9c8818b2c078105f6"
+ "reference": "4dd6768cb7558440d27d18f54909eee417317ce9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2f5170365e2a422d0c5421f9c8818b2c078105f6",
- "reference": "2f5170365e2a422d0c5421f9c8818b2c078105f6",
+ "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/4dd6768cb7558440d27d18f54909eee417317ce9",
+ "reference": "4dd6768cb7558440d27d18f54909eee417317ce9",
"shasum": ""
},
"require": {
@@ -2885,7 +2884,6 @@
"php": "^7.4 || ^8.0",
"react/child-process": "^0.6.6",
"react/event-loop": "^1.5",
- "react/promise": "^3.3",
"react/socket": "^1.16",
"react/stream": "^1.4",
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0",
@@ -2897,12 +2895,13 @@
"symfony/polyfill-mbstring": "^1.33",
"symfony/polyfill-php80": "^1.33",
"symfony/polyfill-php81": "^1.33",
+ "symfony/polyfill-php84": "^1.33",
"symfony/process": "^5.4.47 || ^6.4.24 || ^7.2",
"symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0"
},
"require-dev": {
"facile-it/paraunit": "^1.3.1 || ^2.7",
- "infection/infection": "^0.29.14",
+ "infection/infection": "^0.31.0",
"justinrainbow/json-schema": "^6.5",
"keradus/cli-executor": "^2.2",
"mikey179/vfsstream": "^1.6.12",
@@ -2910,7 +2909,6 @@
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
"phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34",
- "symfony/polyfill-php84": "^1.33",
"symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2",
"symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2"
},
@@ -2953,7 +2951,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
- "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.87.1"
+ "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.89.0"
},
"funding": [
{
@@ -2961,20 +2959,20 @@
"type": "github"
}
],
- "time": "2025-09-02T15:27:36+00:00"
+ "time": "2025-10-18T19:30:16+00:00"
},
{
"name": "humbug/box",
- "version": "4.6.6",
+ "version": "4.6.8",
"source": {
"type": "git",
"url": "https://github.com/box-project/box.git",
- "reference": "09646041cb2e0963ab6ca109e1b366337617a0f2"
+ "reference": "05d205d99ddb72f3729658a0115db02cfc08912e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/box-project/box/zipball/09646041cb2e0963ab6ca109e1b366337617a0f2",
- "reference": "09646041cb2e0963ab6ca109e1b366337617a0f2",
+ "url": "https://api.github.com/repos/box-project/box/zipball/05d205d99ddb72f3729658a0115db02cfc08912e",
+ "reference": "05d205d99ddb72f3729658a0115db02cfc08912e",
"shasum": ""
},
"require": {
@@ -2988,13 +2986,13 @@
"fidry/console": "^0.6.0",
"fidry/filesystem": "^1.2.1",
"humbug/php-scoper": "^0.18.14",
- "justinrainbow/json-schema": "^5.2.12",
+ "justinrainbow/json-schema": "^6.2.0",
"nikic/iter": "^2.2",
"php": "^8.2",
"phpdocumentor/reflection-docblock": "^5.4",
"phpdocumentor/type-resolver": "^1.7",
"psr/log": "^3.0",
- "sebastian/diff": "^5.0",
+ "sebastian/diff": "^5.0 || ^6.0 || ^7.0",
"seld/jsonlint": "^1.10.2",
"seld/phar-utils": "^1.2",
"symfony/finder": "^6.4.0 || ^7.0.0",
@@ -3005,6 +3003,9 @@
"thecodingmachine/safe": "^2.5 || ^3.0",
"webmozart/assert": "^1.11"
},
+ "conflict": {
+ "marc-mabe/php-enum": "<4.4"
+ },
"replace": {
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*",
@@ -3070,22 +3071,22 @@
],
"support": {
"issues": "https://github.com/box-project/box/issues",
- "source": "https://github.com/box-project/box/tree/4.6.6"
+ "source": "https://github.com/box-project/box/tree/4.6.8"
},
- "time": "2025-03-02T18:20:45+00:00"
+ "time": "2025-10-13T17:13:17+00:00"
},
{
"name": "humbug/php-scoper",
- "version": "0.18.17",
+ "version": "0.18.18",
"source": {
"type": "git",
"url": "https://github.com/humbug/php-scoper.git",
- "reference": "0a2556c7c23776a61cf22689e2f24298ba00e33a"
+ "reference": "dd55d01a937602c9473cfbe0ecab9521cb9740aa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/humbug/php-scoper/zipball/0a2556c7c23776a61cf22689e2f24298ba00e33a",
- "reference": "0a2556c7c23776a61cf22689e2f24298ba00e33a",
+ "url": "https://api.github.com/repos/humbug/php-scoper/zipball/dd55d01a937602c9473cfbe0ecab9521cb9740aa",
+ "reference": "dd55d01a937602c9473cfbe0ecab9521cb9740aa",
"shasum": ""
},
"require": {
@@ -3154,9 +3155,9 @@
"description": "Prefixes all PHP namespaces in a file or directory.",
"support": {
"issues": "https://github.com/humbug/php-scoper/issues",
- "source": "https://github.com/humbug/php-scoper/tree/0.18.17"
+ "source": "https://github.com/humbug/php-scoper/tree/0.18.18"
},
- "time": "2025-02-19T22:50:39+00:00"
+ "time": "2025-10-15T15:29:47+00:00"
},
{
"name": "jetbrains/phpstorm-stubs",
@@ -3207,30 +3208,40 @@
},
{
"name": "justinrainbow/json-schema",
- "version": "5.3.0",
+ "version": "6.6.0",
"source": {
"type": "git",
"url": "https://github.com/jsonrainbow/json-schema.git",
- "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8"
+ "reference": "68ba7677532803cc0c5900dd5a4d730537f2b2f3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8",
- "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8",
+ "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/68ba7677532803cc0c5900dd5a4d730537f2b2f3",
+ "reference": "68ba7677532803cc0c5900dd5a4d730537f2b2f3",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "ext-json": "*",
+ "marc-mabe/php-enum": "^4.0",
+ "php": "^7.2 || ^8.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1",
- "json-schema/json-schema-test-suite": "1.2.0",
- "phpunit/phpunit": "^4.8.35"
+ "friendsofphp/php-cs-fixer": "3.3.0",
+ "json-schema/json-schema-test-suite": "^23.2",
+ "marc-mabe/php-enum-phpstan": "^2.0",
+ "phpspec/prophecy": "^1.19",
+ "phpstan/phpstan": "^1.12",
+ "phpunit/phpunit": "^8.5"
},
"bin": [
"bin/validate-json"
],
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.x-dev"
+ }
+ },
"autoload": {
"psr-4": {
"JsonSchema\\": "src/JsonSchema/"
@@ -3259,16 +3270,16 @@
}
],
"description": "A library to validate a json schema.",
- "homepage": "https://github.com/justinrainbow/json-schema",
+ "homepage": "https://github.com/jsonrainbow/json-schema",
"keywords": [
"json",
"schema"
],
"support": {
"issues": "https://github.com/jsonrainbow/json-schema/issues",
- "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0"
+ "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.0"
},
- "time": "2024-07-06T21:00:26+00:00"
+ "time": "2025-10-10T11:34:09+00:00"
},
{
"name": "kelunik/certificate",
@@ -3502,6 +3513,79 @@
],
"time": "2024-12-08T08:18:47+00:00"
},
+ {
+ "name": "marc-mabe/php-enum",
+ "version": "v4.7.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/marc-mabe/php-enum.git",
+ "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef",
+ "reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef",
+ "shasum": ""
+ },
+ "require": {
+ "ext-reflection": "*",
+ "php": "^7.1 | ^8.0"
+ },
+ "require-dev": {
+ "phpbench/phpbench": "^0.16.10 || ^1.0.4",
+ "phpstan/phpstan": "^1.3.1",
+ "phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11",
+ "vimeo/psalm": "^4.17.0 | ^5.26.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-3.x": "3.2-dev",
+ "dev-master": "4.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "MabeEnum\\": "src/"
+ },
+ "classmap": [
+ "stubs/Stringable.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Marc Bennewitz",
+ "email": "dev@mabe.berlin",
+ "homepage": "https://mabe.berlin/",
+ "role": "Lead"
+ }
+ ],
+ "description": "Simple and fast implementation of enumerations with native PHP",
+ "homepage": "https://github.com/marc-mabe/php-enum",
+ "keywords": [
+ "enum",
+ "enum-map",
+ "enum-set",
+ "enumeration",
+ "enumerator",
+ "enummap",
+ "enumset",
+ "map",
+ "set",
+ "type",
+ "type-hint",
+ "typehint"
+ ],
+ "support": {
+ "issues": "https://github.com/marc-mabe/php-enum/issues",
+ "source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2"
+ },
+ "time": "2025-09-14T11:18:39+00:00"
+ },
{
"name": "myclabs/deep-copy",
"version": "1.13.4",
@@ -4228,16 +4312,11 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.12.28",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpstan.git",
- "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9"
- },
+ "version": "1.12.32",
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fcf8b71aeab4e1a1131d1783cef97b23a51b87a9",
- "reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8",
+ "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8",
"shasum": ""
},
"require": {
@@ -4282,7 +4361,7 @@
"type": "github"
}
],
- "time": "2025-07-17T17:15:39+00:00"
+ "time": "2025-09-30T10:16:31+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -4607,16 +4686,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "10.5.53",
+ "version": "10.5.58",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "32768472ebfb6969e6c7399f1c7b09009723f653"
+ "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/32768472ebfb6969e6c7399f1c7b09009723f653",
- "reference": "32768472ebfb6969e6c7399f1c7b09009723f653",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca",
+ "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca",
"shasum": ""
},
"require": {
@@ -4637,10 +4716,10 @@
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
- "sebastian/comparator": "^5.0.3",
+ "sebastian/comparator": "^5.0.4",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
- "sebastian/exporter": "^5.1.2",
+ "sebastian/exporter": "^5.1.4",
"sebastian/global-state": "^6.0.2",
"sebastian/object-enumerator": "^5.0.0",
"sebastian/recursion-context": "^5.0.1",
@@ -4688,7 +4767,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.53"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.58"
},
"funding": [
{
@@ -4712,7 +4791,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-20T14:40:06+00:00"
+ "time": "2025-09-28T12:04:46+00:00"
},
{
"name": "psr/event-dispatcher",
@@ -5640,16 +5719,16 @@
},
{
"name": "sebastian/comparator",
- "version": "5.0.3",
+ "version": "5.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
+ "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
- "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
+ "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"shasum": ""
},
"require": {
@@ -5705,15 +5784,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
- "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
+ "type": "tidelift"
}
],
- "time": "2024-10-18T14:56:07+00:00"
+ "time": "2025-09-07T05:25:07+00:00"
},
{
"name": "sebastian/complexity",
@@ -5906,16 +5997,16 @@
},
{
"name": "sebastian/exporter",
- "version": "5.1.2",
+ "version": "5.1.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "955288482d97c19a372d3f31006ab3f37da47adf"
+ "reference": "0735b90f4da94969541dac1da743446e276defa6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
- "reference": "955288482d97c19a372d3f31006ab3f37da47adf",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6",
+ "reference": "0735b90f4da94969541dac1da743446e276defa6",
"shasum": ""
},
"require": {
@@ -5924,7 +6015,7 @@
"sebastian/recursion-context": "^5.0"
},
"require-dev": {
- "phpunit/phpunit": "^10.0"
+ "phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
@@ -5972,15 +6063,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"security": "https://github.com/sebastianbergmann/exporter/security/policy",
- "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter",
+ "type": "tidelift"
}
],
- "time": "2024-03-02T07:17:12+00:00"
+ "time": "2025-09-24T06:09:11+00:00"
},
{
"name": "sebastian/global-state",
@@ -7108,16 +7211,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v7.3.3",
+ "version": "v7.3.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f"
+ "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f",
- "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
+ "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
"shasum": ""
},
"require": {
@@ -7171,7 +7274,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.3.3"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.3.4"
},
"funding": [
{
@@ -7191,7 +7294,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-13T11:49:31+00:00"
+ "time": "2025-09-11T10:12:26+00:00"
},
{
"name": "thecodingmachine/safe",
diff --git a/config/env.ini b/config/env.ini
index 8b5a58d4..7d770426 100644
--- a/config/env.ini
+++ b/config/env.ini
@@ -34,6 +34,7 @@
; SPC_LINUX_DEFAULT_CC: the default compiler for linux. (For alpine linux: `gcc`, default: `$GNU_ARCH-linux-musl-gcc`)
; SPC_LINUX_DEFAULT_CXX: the default c++ compiler for linux. (For alpine linux: `g++`, default: `$GNU_ARCH-linux-musl-g++`)
; SPC_LINUX_DEFAULT_AR: the default archiver for linux. (For alpine linux: `ar`, default: `$GNU_ARCH-linux-musl-ar`)
+; SPC_EXTRA_PHP_VARS: the extra vars for building php, used in `configure` and `make` command.
[global]
; Build concurrency for make -jN, default is CPU_COUNT, this value are used in every libs.
@@ -104,8 +105,6 @@ SPC_MICRO_PATCHES=cli_checks,disable_huge_page
SPC_CMD_PREFIX_PHP_BUILDCONF="./buildconf --force"
; configure command
SPC_CMD_PREFIX_PHP_CONFIGURE="./configure --prefix= --with-valgrind=no --disable-shared --enable-static --disable-all --disable-phpdbg --with-pic"
-; make command
-SPC_CMD_PREFIX_PHP_MAKE="make -j${SPC_CONCURRENCY}"
; *** default build vars for building php ***
; embed type for php, static (libphp.a) or shared (libphp.so)
@@ -138,8 +137,6 @@ SPC_MICRO_PATCHES=cli_checks,macos_iconv
SPC_CMD_PREFIX_PHP_BUILDCONF="./buildconf --force"
; configure command
SPC_CMD_PREFIX_PHP_CONFIGURE="./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-phpdbg"
-; make command
-SPC_CMD_PREFIX_PHP_MAKE="make -j${SPC_CONCURRENCY}"
; *** default build vars for building php ***
; embed type for php, static (libphp.a) or shared (libphp.dylib)
diff --git a/config/ext.json b/config/ext.json
index 8e02b118..e5aa0ce3 100644
--- a/config/ext.json
+++ b/config/ext.json
@@ -341,6 +341,7 @@
"ext-depends": [
"xml"
],
+ "build-with-php": true,
"target": [
"static"
]
@@ -414,9 +415,17 @@
"libmemcached",
"fastlz"
],
+ "lib-suggests": [
+ "zstd"
+ ],
"ext-depends": [
"session",
"zlib"
+ ],
+ "ext-suggests": [
+ "igbinary",
+ "msgpack",
+ "session"
]
},
"mongodb": {
@@ -445,7 +454,7 @@
"type": "external",
"source": "msgpack",
"arg-type-unix": "with",
- "arg-type-win": "enable",
+ "arg-type-windows": "enable",
"ext-depends": [
"session"
]
@@ -453,6 +462,7 @@
"mysqli": {
"type": "builtin",
"arg-type": "with",
+ "build-with-php": true,
"ext-depends": [
"mysqlnd"
]
@@ -460,6 +470,7 @@
"mysqlnd": {
"type": "builtin",
"arg-type-windows": "with",
+ "build-with-php": true,
"lib-depends": [
"zlib"
]
@@ -679,7 +690,7 @@
"type": "builtin",
"arg-type": "with-path",
"lib-depends": [
- "readline"
+ "libedit"
],
"target": [
"static"
@@ -751,11 +762,9 @@
},
"type": "builtin",
"arg-type": "custom",
- "lib-depends": [
- "libxml2"
- ],
- "ext-depends-windows": [
- "xml"
+ "ext-depends": [
+ "libxml",
+ "session"
]
},
"sockets": {
@@ -791,6 +800,7 @@
"type": "builtin",
"arg-type": "with-path",
"arg-type-windows": "with",
+ "build-with-php": true,
"lib-depends": [
"sqlite"
]
@@ -879,6 +889,22 @@
"mysqli"
]
},
+ "swoole-hook-odbc": {
+ "support": {
+ "Windows": "no",
+ "BSD": "wip"
+ },
+ "notes": true,
+ "type": "addon",
+ "arg-type": "none",
+ "ext-depends": [
+ "pdo",
+ "swoole"
+ ],
+ "lib-depends": [
+ "unixodbc"
+ ]
+ },
"swoole-hook-pgsql": {
"support": {
"Windows": "no",
@@ -908,22 +934,6 @@
"swoole"
]
},
- "swoole-hook-odbc": {
- "support": {
- "Windows": "no",
- "BSD": "wip"
- },
- "notes": true,
- "type": "addon",
- "arg-type": "none",
- "ext-depends": [
- "pdo",
- "swoole"
- ],
- "lib-depends": [
- "unixodbc"
- ]
- },
"swow": {
"support": {
"BSD": "wip"
@@ -1146,8 +1156,9 @@
"support": {
"BSD": "wip"
},
- "type": "builtin",
- "arg-type": "with-path",
+ "type": "external",
+ "source": "ext-zip",
+ "arg-type": "custom",
"arg-type-windows": "enable",
"lib-depends-unix": [
"libzip"
@@ -1170,6 +1181,7 @@
"lib-depends": [
"zlib"
],
+ "build-with-php": true,
"target": [
"static"
]
diff --git a/config/lib.json b/config/lib.json
index eccda304..09779cf9 100644
--- a/config/lib.json
+++ b/config/lib.json
@@ -10,17 +10,19 @@
"micro",
"frankenphp"
],
+ "lib-depends-macos": [
+ "lib-base",
+ "micro",
+ "libxml2"
+ ],
"lib-suggests-linux": [
"libacl",
"brotli",
"watcher"
],
- "lib-suggests-unix": [
+ "lib-suggests-macos": [
"brotli",
"watcher"
- ],
- "lib-depends-macos": [
- "libxml2"
]
},
"micro": {
@@ -201,7 +203,7 @@
"openssl",
"libcares"
],
- "provide-pre-built": true,
+ "cpp-library": true,
"frameworks": [
"CoreFoundation"
]
@@ -229,6 +231,7 @@
},
"imagemagick": {
"source": "imagemagick",
+ "cpp-library": true,
"pkg-configs": [
"Magick++-7.Q16HDRI",
"MagickCore-7.Q16HDRI",
@@ -342,6 +345,15 @@
],
"cpp-library": true
},
+ "libedit": {
+ "source": "libedit",
+ "static-libs-unix": [
+ "libedit.a"
+ ],
+ "lib-depends": [
+ "ncurses"
+ ]
+ },
"libevent": {
"source": "libevent",
"static-libs-unix": [
@@ -451,6 +463,7 @@
},
"libmemcached": {
"source": "libmemcached",
+ "cpp-library": true,
"static-libs-unix": [
"libmemcached.a",
"libmemcachedprotocol.a",
@@ -547,6 +560,21 @@
"zstd"
]
},
+ "liburing": {
+ "source": "liburing",
+ "pkg-configs": [
+ "liburing",
+ "liburing-ffi"
+ ],
+ "static-libs-linux": [
+ "liburing.a",
+ "liburing-ffi.a"
+ ],
+ "headers-linux": [
+ "liburing/",
+ "liburing.h"
+ ]
+ },
"libuuid": {
"source": "libuuid",
"static-libs-unix": [
@@ -595,7 +623,6 @@
],
"lib-suggests-unix": [
"xz",
- "icu",
"zlib"
],
"lib-depends-windows": [
@@ -769,7 +796,7 @@
"libxml2",
"openssl",
"zlib",
- "readline"
+ "libedit"
],
"lib-suggests": [
"icu",
@@ -821,6 +848,7 @@
},
"snappy": {
"source": "snappy",
+ "cpp-library": true,
"static-libs-unix": [
"libsnappy.a"
],
@@ -866,6 +894,7 @@
},
"watcher": {
"source": "watcher",
+ "cpp-library": true,
"static-libs-unix": [
"libwatcher-c.a"
],
diff --git a/config/pkg.json b/config/pkg.json
index 00e83595..d3b4fb90 100644
--- a/config/pkg.json
+++ b/config/pkg.json
@@ -23,8 +23,8 @@
"type": "url",
"url": "https://dl.static-php.dev/static-php-cli/deps/nasm/nasm-2.16.01-win64.zip",
"extract-files": {
- "nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe",
- "nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"
+ "nasm.exe": "{php_sdk_path}/bin/nasm.exe",
+ "ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"
}
},
"pkg-config-aarch64-linux": {
@@ -84,7 +84,7 @@
"repo": "upx/upx",
"match": "upx.+-win64\\.zip",
"extract-files": {
- "upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe"
+ "upx.exe": "{pkg_root_path}/bin/upx.exe"
}
},
"zig-aarch64-linux": {
diff --git a/config/source.json b/config/source.json
index f020ae06..77e5a5cf 100644
--- a/config/source.json
+++ b/config/source.json
@@ -47,7 +47,7 @@
"provide-pre-built": true,
"license": {
"type": "file",
- "path": "doc/COPYING"
+ "path": "doc/COPYING.LGPL"
}
},
"brotli": {
@@ -263,6 +263,15 @@
"path": "LICENSE"
}
},
+ "ext-zip": {
+ "type": "url",
+ "url": "https://pecl.php.net/get/zip",
+ "filename": "ext-zip.tgz",
+ "license": {
+ "type": "file",
+ "path": "LICENSE"
+ }
+ },
"ext-zstd": {
"type": "git",
"path": "php-src/ext/zstd",
@@ -297,16 +306,17 @@
"regex": "/href=\"(?gettext-(?[^\"]+)\\.tar\\.xz)\"/",
"license": {
"type": "file",
- "path": "COPYING"
+ "path": "gettext-runtime/intl/COPYING.LIB"
}
},
"gmp": {
- "type": "url",
- "url": "https://dl.static-php.dev/static-php-cli/deps/gmp/gmp-6.3.0.tar.xz",
+ "type": "filelist",
+ "url": "https://gmplib.org/download/gmp/",
+ "regex": "/href=\"(?gmp-(?[^\"]+)\\.tar\\.xz)\"/",
"provide-pre-built": true,
"alt": {
- "type": "ghtagtar",
- "repo": "alisw/GMP"
+ "type": "url",
+ "url": "https://dl.static-php.dev/static-php-cli/deps/gmp/gmp-6.3.0.tar.xz"
},
"license": {
"type": "text",
@@ -324,7 +334,7 @@
},
"grpc": {
"type": "git",
- "rev": "v1.68.x",
+ "rev": "v1.75.x",
"url": "https://github.com/grpc/grpc.git",
"provide-pre-built": true,
"license": {
@@ -440,7 +450,7 @@
"provide-pre-built": true,
"license": {
"type": "file",
- "path": "doc/COPYING"
+ "path": "doc/COPYING.LGPL"
}
},
"libaom": {
@@ -499,6 +509,16 @@
"path": "COPYING"
}
},
+ "libedit": {
+ "type": "filelist",
+ "url": "https://thrysoee.dk/editline/",
+ "regex": "/href=\"(?libedit-(?[^\"]+)\\.tar\\.gz)\"/",
+ "provide-pre-built": true,
+ "license": {
+ "type": "file",
+ "path": "COPYING"
+ }
+ },
"libevent": {
"type": "ghrel",
"repo": "libevent/libevent",
@@ -547,7 +567,7 @@
"provide-pre-built": true,
"license": {
"type": "file",
- "path": "COPYING"
+ "path": "COPYING.LIB"
}
},
"libiconv-win": {
@@ -660,6 +680,15 @@
"path": "LICENSE.md"
}
},
+ "liburing": {
+ "type": "ghtar",
+ "repo": "axboe/liburing",
+ "prefer-stable": true,
+ "license": {
+ "type": "file",
+ "path": "COPYING"
+ }
+ },
"libuuid": {
"type": "git",
"url": "https://github.com/static-php/libuuid.git",
@@ -890,7 +919,7 @@
"postgresql": {
"type": "ghtagtar",
"repo": "postgres/postgres",
- "match": "REL_16_\\d+",
+ "match": "REL_18_\\d+",
"license": {
"type": "file",
"path": "COPYRIGHT"
@@ -981,7 +1010,6 @@
},
"snappy": {
"type": "git",
- "repo": "google/snappy",
"rev": "main",
"url": "https://github.com/google/snappy",
"license": {
@@ -990,9 +1018,8 @@
}
},
"spx": {
- "type": "git",
- "rev": "master",
- "url": "https://github.com/static-php/php-spx.git",
+ "type": "pie",
+ "repo": "noisebynorthwest/php-spx",
"path": "php-src/ext/spx",
"license": {
"type": "file",
@@ -1146,14 +1173,5 @@
"type": "file",
"path": "LICENSE"
}
- },
- "liburing": {
- "type": "ghtar",
- "repo": "axboe/liburing",
- "prefer-stable": true,
- "license": {
- "type": "file",
- "path": "COPYING"
- }
}
}
diff --git a/docs/en/develop/source-module.md b/docs/en/develop/source-module.md
index 4b1b1fc0..51c3ba3c 100644
--- a/docs/en/develop/source-module.md
+++ b/docs/en/develop/source-module.md
@@ -36,6 +36,7 @@ The following is the source download configuration corresponding to the `libeven
The most important field here is `type`. Currently, the types it supports are:
- `url`: Directly use URL to download, for example: `https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz`.
+- `pie`: Download PHP extensions from Packagist using the PIE (PHP Installer for Extensions) standard.
- `ghrel`: Use the GitHub Release API to download, download the artifacts uploaded from the latest version released by maintainers.
- `ghtar`: Use the GitHub Release API to download.
Different from `ghrel`, `ghtar` is downloaded from the `source code (tar.gz)` in the latest Release of the project.
@@ -89,6 +90,37 @@ Example (download the imagick extension and extract it to the extension storage
}
```
+## Download type - pie
+
+PIE (PHP Installer for Extensions) type sources refer to downloading PHP extensions from Packagist that follow the PIE standard.
+This method automatically fetches extension information from the Packagist repository and downloads the appropriate distribution file.
+
+The parameters included are:
+
+- `repo`: The Packagist vendor/package name, such as `vendor/package-name`
+
+Example (download a PHP extension from Packagist using PIE):
+
+```json
+{
+ "ext-example": {
+ "type": "pie",
+ "repo": "vendor/example-extension",
+ "path": "php-src/ext/example",
+ "license": {
+ "type": "file",
+ "path": "LICENSE"
+ }
+ }
+}
+```
+
+::: tip
+The PIE download type will automatically detect the extension information from Packagist metadata,
+including the download URL, version, and distribution type.
+The extension must be marked as `type: php-ext` or contain `php-ext` metadata in its Packagist package definition.
+:::
+
## Download type - ghrel
ghrel will download files from Assets uploaded in GitHub Release.
diff --git a/docs/en/guide/index.md b/docs/en/guide/index.md
index 253562b5..54e7840c 100644
--- a/docs/en/guide/index.md
+++ b/docs/en/guide/index.md
@@ -21,19 +21,30 @@ The following is the architecture support situation, where :gear: represents sup
| Windows | :gear: :computer: | |
| FreeBSD | :computer: | :computer: |
-Among them, Linux is currently only tested on Ubuntu, Debian, and Alpine distributions,
-and other distributions have not been tested, which cannot guarantee successful compilation.
-For untested distributions, local compilation can be done using methods such as Docker to avoid environmental issues.
+Current supported PHP versions for compilation:
-There are two architectures for macOS: `x86_64` and `Arm`, but binaries compiled on one architecture cannot be directly used on the other architecture.
-Rosetta 2 cannot guarantee that programs compiled with `Arm` architecture can fully run on `x86_64` environment.
+> :warning: Partial support, there may be issues with new beta versions and old versions.
+>
+> :heavy_check_mark: Supported
+>
+> :x: Not supported
-Windows currently only supports the x86_64 architecture, and does not support 32-bit x86 or arm64 architecture.
+| PHP Version | Status | Comment |
+|-------------|--------------------|-------------------------------------------------------------------------------------------------------------------------|
+| 7.2 | :x: | |
+| 7.3 | :x: | phpmicro and many extensions do not support 7.3, 7.4 versions |
+| 7.4 | :x: | phpmicro and many extensions do not support 7.3, 7.4 versions |
+| 8.0 | :warning: | PHP official has stopped maintaining 8.0, we no longer handle 8.0 related backport support |
+| 8.1 | :warning: | PHP official only provides security updates for 8.1, we no longer handle 8.1 related backport support after 8.5 release |
+| 8.2 | :heavy_check_mark: | |
+| 8.3 | :heavy_check_mark: | |
+| 8.4 | :heavy_check_mark: | |
+| 8.5 (beta) | :warning: | PHP 8.5 is currently in beta stage |
-## Supported PHP Version
+> This table shows the support status of static-php-cli for building corresponding versions, not the PHP official support status for that version.
-Currently, static php cli supports PHP versions 8.1 to 8.5, and theoretically supports PHP 8.0 and earlier versions.
-Simply select the earlier version when downloading.
-However, due to some extensions and special components that have stopped supporting earlier versions of PHP,
-static-php-cli will not explicitly support earlier versions.
+## PHP Support Versions
+
+Currently, static-php-cli supports PHP versions 8.2 ~ 8.5, and theoretically supports PHP 8.1 and earlier versions, just select the earlier version when downloading.
+However, due to some extensions and special components that have stopped supporting earlier versions of PHP, static-php-cli will not explicitly support earlier versions.
We recommend that you compile the latest PHP version possible for a better experience.
diff --git a/docs/zh/develop/source-module.md b/docs/zh/develop/source-module.md
index 00feb3c4..769ffa08 100644
--- a/docs/zh/develop/source-module.md
+++ b/docs/zh/develop/source-module.md
@@ -30,6 +30,7 @@ static-php-cli 的下载资源模块是一个主要的功能,它包含了所
这里最主要的字段是 `type`,目前它支持的类型有:
- `url`: 直接使用 URL 下载,例如:`https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz`。
+- `pie`: 使用 PIE(PHP Installer for Extensions)标准从 Packagist 下载 PHP 扩展。
- `ghrel`: 使用 GitHub Release API 下载,即从 GitHub 项目发布的最新版本中上传的附件下载。
- `ghtar`: 使用 GitHub Release API 下载,与 `ghrel` 不同的是,`ghtar` 是从项目的最新 Release 中找 `source code (tar.gz)` 下载的。
- `ghtagtar`: 使用 GitHub Release API 下载,与 `ghtar` 相比,`ghtagtar` 可以从 `tags` 列表找最新的,并下载 `tar.gz` 格式的源码(因为有些项目只使用了 `tag` 发布版本)。
@@ -77,6 +78,36 @@ url 类型的资源指的是从 URL 直接下载文件。
}
```
+## 下载类型 - pie
+
+PIE(PHP Installer for Extensions)类型的资源是从 Packagist 下载遵循 PIE 标准的 PHP 扩展。
+该方法会自动从 Packagist 仓库获取扩展信息,并下载相应的分发文件。
+
+包含的参数有:
+
+- `repo`: Packagist 的 vendor/package 名称,如 `vendor/package-name`
+
+例子(使用 PIE 从 Packagist 下载 PHP 扩展):
+
+```json
+{
+ "ext-example": {
+ "type": "pie",
+ "repo": "vendor/example-extension",
+ "path": "php-src/ext/example",
+ "license": {
+ "type": "file",
+ "path": "LICENSE"
+ }
+ }
+}
+```
+
+::: tip
+PIE 下载类型会自动从 Packagist 元数据中检测扩展信息,包括下载 URL、版本和分发类型。
+扩展必须在其 Packagist 包定义中标记为 `type: php-ext` 或包含 `php-ext` 元数据。
+:::
+
## 下载类型 - ghrel
ghrel 会从 GitHub Release 中上传的 Assets 下载文件。首先使用 GitHub Release API 获取最新版本,然后根据正则匹配方式下载相应的文件。
diff --git a/docs/zh/guide/index.md b/docs/zh/guide/index.md
index 318c40b8..8e5727c2 100644
--- a/docs/zh/guide/index.md
+++ b/docs/zh/guide/index.md
@@ -19,16 +19,30 @@ static-php-cli 是一个用于构建静态编译的 PHP 二进制的工具,目
| Windows | :gear: :computer: | |
| FreeBSD | :computer: | :computer: |
-其中,Linux 目前仅在 Ubuntu、Debian、Alpine 发行版测试通过,其他发行版未进行测试,不能保证编译成功。
-对于未经过测试的发行版,可以使用 Docker 等方式本地编译,避免环境导致的问题。
+当前支持编译的 PHP 版本:
-macOS 下支持 x86_64 和 Arm 两种架构,但在其中一个架构上编译的二进制无法直接在另一个架构上使用。
-Rosetta 2 不能保证 Arm 架构编译的程序可以完全运行在 x86_64 环境下。
+> :warning: 部分支持,对于新的测试版和旧版本可能存在问题。
+>
+> :heavy_check_mark: 支持
+>
+> :x: 不支持
-Windows 目前只支持 x86_64 架构,不支持 32 位 x86、不支持 arm64 架构。
+| PHP Version | Status | Comment |
+|-------------|--------------------|---------------------------------------------------------|
+| 7.2 | :x: | |
+| 7.3 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
+| 7.4 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
+| 8.0 | :warning: | PHP 官方已停止 8.0 的维护,我们不再处理 8.0 相关的 backport 支持 |
+| 8.1 | :warning: | PHP 官方仅对 8.1 提供安全更新,在 8.5 发布后我们不再处理 8.1 相关的 backport 支持 |
+| 8.2 | :heavy_check_mark: | |
+| 8.3 | :heavy_check_mark: | |
+| 8.4 | :heavy_check_mark: | |
+| 8.5 (beta) | :warning: | PHP 8.5 目前处于 beta 阶段 |
+
+> 这个表格的支持状态是 static-php-cli 对构建对应版本的支持情况,不是 PHP 官方对该版本的支持情况。
## PHP 支持版本
-目前,static-php-cli 对 PHP 8.1 ~ 8.5 版本是支持的,对于 PHP 8.0 及更早版本理论上支持,只需下载时选择早期版本即可。
+目前,static-php-cli 对 PHP 8.2 ~ 8.5 版本是支持的,对于 PHP 8.1 及更早版本理论上支持,只需下载时选择早期版本即可。
但由于部分扩展和特殊组件已对早期版本的 PHP 停止了支持,所以 static-php-cli 不会明确支持早期版本。
我们推荐你编译尽可能新的 PHP 版本,以获得更好的体验。
diff --git a/src/SPC/ConsoleApplication.php b/src/SPC/ConsoleApplication.php
index c60ca4b0..ba2d38e0 100644
--- a/src/SPC/ConsoleApplication.php
+++ b/src/SPC/ConsoleApplication.php
@@ -34,7 +34,7 @@ use Symfony\Component\Console\Application;
*/
final class ConsoleApplication extends Application
{
- public const string VERSION = '2.7.3';
+ public const string VERSION = '2.7.5';
public function __construct()
{
diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php
index b410aab3..fdba936d 100644
--- a/src/SPC/builder/BuilderBase.php
+++ b/src/SPC/builder/BuilderBase.php
@@ -10,6 +10,7 @@ use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\LockFile;
+use SPC\store\pkg\GoXcaddy;
use SPC\store\SourceManager;
use SPC\store\SourcePatcher;
use SPC\util\AttributeMapper;
@@ -127,27 +128,6 @@ abstract class BuilderBase
return array_filter($this->exts, fn ($ext) => $ext->isBuildStatic());
}
- /**
- * Check if there is a cpp extensions or libraries.
- */
- public function hasCpp(): bool
- {
- // judge cpp-extension
- $exts = array_keys($this->getExts(false));
- foreach ($exts as $ext) {
- if (Config::getExt($ext, 'cpp-extension', false) === true) {
- return true;
- }
- }
- $libs = array_keys($this->getLibs());
- foreach ($libs as $lib) {
- if (Config::getLib($lib, 'cpp-library', false) === true) {
- return true;
- }
- }
- return false;
- }
-
/**
* Set libs only mode.
*
@@ -507,8 +487,7 @@ abstract class BuilderBase
throw new WrongUsageException('FrankenPHP SAPI is only available on Linux and macOS!');
}
// frankenphp needs package go-xcaddy installed
- $pkg_dir = PKG_ROOT_PATH . '/go-xcaddy-' . arch2gnu(php_uname('m')) . '-' . osfamily2shortname();
- if (!file_exists("{$pkg_dir}/bin/go") || !file_exists("{$pkg_dir}/bin/xcaddy")) {
+ if (!GoXcaddy::isInstalled()) {
global $argv;
throw new WrongUsageException("FrankenPHP SAPI requires the go-xcaddy package, please install it first: {$argv[0]} install-pkg go-xcaddy");
}
diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php
index 71bbf9d4..5b6a3329 100644
--- a/src/SPC/builder/Extension.php
+++ b/src/SPC/builder/Extension.php
@@ -10,8 +10,6 @@ use SPC\exception\ValidationException;
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;
@@ -223,12 +221,24 @@ class Extension
public function patchBeforeSharedMake(): bool
{
$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)
- );
+ [$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']);
+ $lstdcpp = str_contains($sharedLibs, '-l:libstdc++.a') ? '-l:libstdc++.a' : null;
+ $lstdcpp ??= str_contains($sharedLibs, '-lstdc++') ? '-lstdc++' : '';
+
+ $makefileContent = file_get_contents($this->source_dir . '/Makefile');
+ if (preg_match('/^(.*_SHARED_LIBADD\s*=\s*)(.*)$/m', $makefileContent, $matches)) {
+ $prefix = $matches[1];
+ $currentLibs = trim($matches[2]);
+ $newLibs = trim("{$currentLibs} {$staticLibs} {$lstdcpp}");
+ $deduplicatedLibs = deduplicate_flags($newLibs);
+
+ FileSystem::replaceFileRegex(
+ $this->source_dir . '/Makefile',
+ '/^(.*_SHARED_LIBADD\s*=.*)$/m',
+ $prefix . $deduplicatedLibs
+ );
+ }
+
if ($objs = getenv('SPC_EXTRA_RUNTIME_OBJECTS')) {
FileSystem::replaceFileRegex(
$this->source_dir . '/Makefile',
@@ -295,7 +305,7 @@ class Extension
// Run compile check if build target is cli
// If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php
$sharedExtensions = $this->getSharedExtensionLoadString();
- [$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"');
+ [$ret] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"');
if ($ret !== 0) {
throw new ValidationException(
"extension {$this->getName()} failed compile check: php-cli returned {$ret}",
@@ -325,7 +335,7 @@ class Extension
{
// Run compile check if build target is cli
// If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php
- [$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe -n --ri "' . $this->getDistName() . '"', false);
+ [$ret] = cmd()->execWithResult(BUILD_BIN_PATH . '/php.exe -n --ri "' . $this->getDistName() . '"', false);
if ($ret !== 0) {
throw new ValidationException("extension {$this->getName()} failed compile check: php-cli returned {$ret}", validation_module: "Extension {$this->getName()} sanity check");
}
@@ -402,26 +412,7 @@ class Extension
*/
public function buildUnixShared(): void
{
- $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' => 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++';
- }
-
+ $env = $this->getSharedExtensionEnv();
if ($this->patchBeforeSharedPhpize()) {
logger()->info("Extension [{$this->getName()}] patched before shared phpize");
}
@@ -436,13 +427,15 @@ class Extension
logger()->info("Extension [{$this->getName()}] patched before shared configure");
}
+ $phpvars = getenv('SPC_EXTRA_PHP_VARS') ?: '';
+
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'
+ "--enable-shared --disable-static {$phpvars}"
);
if ($this->patchBeforeSharedMake()) {
@@ -493,6 +486,30 @@ class Extension
return $this->build_static;
}
+ /**
+ * Returns the environment variables a shared extension needs to be built.
+ * CFLAGS, CXXFLAGS, LDFLAGS and so on.
+ */
+ protected function getSharedExtensionEnv(): array
+ {
+ $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 ';
+ return [
+ 'CFLAGS' => $config['cflags'],
+ 'CXXFLAGS' => $config['cflags'],
+ 'LDFLAGS' => $config['ldflags'],
+ 'LIBS' => clean_spaces("{$preStatic} {$staticLibs} {$postStatic} {$sharedLibs}"),
+ 'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
+ ];
+ }
+
protected function addLibraryDependency(string $name, bool $optional = false): void
{
$depLib = $this->builder->getLib($name);
@@ -565,12 +582,12 @@ class Extension
$added = 0;
foreach ($ret as $depName => $dep) {
foreach ($dep->getDependencies(true) as $depdepName => $depdep) {
- if (!in_array($depdepName, array_keys($deps), true)) {
+ if (!array_key_exists($depdepName, $deps)) {
$deps[$depdepName] = $depdep;
++$added;
}
}
- if (!in_array($depName, array_keys($deps), true)) {
+ if (!array_key_exists($depName, $deps)) {
$deps[$depName] = $dep;
}
}
diff --git a/src/SPC/builder/extension/dba.php b/src/SPC/builder/extension/dba.php
index abda5651..d1f8cda3 100644
--- a/src/SPC/builder/extension/dba.php
+++ b/src/SPC/builder/extension/dba.php
@@ -12,7 +12,7 @@ class dba extends Extension
{
public function getUnixConfigureArg(bool $shared = false): string
{
- $qdbm = $this->builder->getLib('qdbm') ? (' --with-qdbm=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH) : '';
+ $qdbm = $this->builder->getLib('qdbm') ? (' --with-qdbm=' . BUILD_ROOT_PATH) : '';
return '--enable-dba' . ($shared ? '=shared' : '') . $qdbm;
}
diff --git a/src/SPC/builder/extension/gettext.php b/src/SPC/builder/extension/gettext.php
index dc4f58aa..303cc389 100644
--- a/src/SPC/builder/extension/gettext.php
+++ b/src/SPC/builder/extension/gettext.php
@@ -15,7 +15,11 @@ class gettext extends Extension
public function patchBeforeBuildconf(): bool
{
if ($this->builder instanceof MacOSBuilder) {
- FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/ext/gettext/config.m4', 'AC_CHECK_LIB($GETTEXT_CHECK_IN_LIB', 'AC_CHECK_LIB(intl');
+ FileSystem::replaceFileStr(
+ SOURCE_PATH . '/php-src/ext/gettext/config.m4',
+ ['AC_CHECK_LIB($GETTEXT_CHECK_IN_LIB', 'AC_CHECK_LIB([$GETTEXT_CHECK_IN_LIB'],
+ ['AC_CHECK_LIB(intl', 'AC_CHECK_LIB([intl'] // new php versions use a bracket
+ );
}
return true;
}
diff --git a/src/SPC/builder/extension/grpc.php b/src/SPC/builder/extension/grpc.php
index 61af6c04..7388eaff 100644
--- a/src/SPC/builder/extension/grpc.php
+++ b/src/SPC/builder/extension/grpc.php
@@ -56,4 +56,11 @@ class grpc extends Extension
GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . ' -Wno-strict-prototypes');
return true;
}
+
+ protected function getSharedExtensionEnv(): array
+ {
+ $env = parent::getSharedExtensionEnv();
+ $env['CPPFLAGS'] = $env['CXXFLAGS'] . ' -Wno-attributes';
+ return $env;
+ }
}
diff --git a/src/SPC/builder/extension/memcached.php b/src/SPC/builder/extension/memcached.php
index a1b88ac6..983b5b64 100644
--- a/src/SPC/builder/extension/memcached.php
+++ b/src/SPC/builder/extension/memcached.php
@@ -17,6 +17,10 @@ class memcached extends Extension
'--with-libmemcached-dir=' . BUILD_ROOT_PATH . ' ' .
'--disable-memcached-sasl ' .
'--enable-memcached-json ' .
+ ($this->builder->getLib('zstd') ? '--with-zstd ' : '') .
+ ($this->builder->getExt('igbinary') ? '--enable-memcached-igbinary ' : '') .
+ ($this->builder->getExt('session') ? '--enable-memcached-session ' : '') .
+ ($this->builder->getExt('msgpack') ? '--enable-memcached-msgpack ' : '') .
'--with-system-fastlz';
}
}
diff --git a/src/SPC/builder/extension/readline.php b/src/SPC/builder/extension/readline.php
index a8ee48aa..035d7b43 100644
--- a/src/SPC/builder/extension/readline.php
+++ b/src/SPC/builder/extension/readline.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
+use SPC\exception\ValidationException;
use SPC\store\FileSystem;
use SPC\util\CustomExt;
@@ -23,12 +24,7 @@ class readline extends Extension
public function getUnixConfigureArg(bool $shared = false): string
{
- $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;
+ return '--with-libedit --without-readline';
}
public function buildUnixShared(): void
@@ -39,4 +35,13 @@ class readline extends Extension
}
parent::buildUnixShared();
}
+
+ public function runCliCheckUnix(): void
+ {
+ parent::runCliCheckUnix();
+ [$ret, $out] = shell()->execWithResult('printf "exit\n" | ' . BUILD_BIN_PATH . '/php -a');
+ if ($ret !== 0 || !str_contains(implode("\n", $out), 'Interactive shell')) {
+ throw new ValidationException("readline extension failed sanity check. Code: {$ret}, output: " . implode("\n", $out));
+ }
+ }
}
diff --git a/src/SPC/builder/extension/simdjson.php b/src/SPC/builder/extension/simdjson.php
index 71796ebe..914fd674 100644
--- a/src/SPC/builder/extension/simdjson.php
+++ b/src/SPC/builder/extension/simdjson.php
@@ -6,6 +6,8 @@ namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\store\FileSystem;
+use SPC\toolchain\ToolchainManager;
+use SPC\toolchain\ZigToolchain;
use SPC\util\CustomExt;
#[CustomExt('simdjson')]
@@ -17,7 +19,7 @@ class simdjson extends Extension
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/ext/simdjson/config.m4',
'/php_version=(`.*`)$/m',
- 'php_version=' . strval($php_ver)
+ 'php_version=' . $php_ver
);
FileSystem::replaceFileStr(
SOURCE_PATH . '/php-src/ext/simdjson/config.m4',
@@ -31,4 +33,18 @@ class simdjson extends Extension
);
return true;
}
+
+ public function getSharedExtensionEnv(): array
+ {
+ $env = parent::getSharedExtensionEnv();
+ if (ToolchainManager::getToolchainClass() === ZigToolchain::class) {
+ $extra = getenv('SPC_COMPILER_EXTRA');
+ if (!str_contains((string) $extra, '-lstdc++')) {
+ f_putenv('SPC_COMPILER_EXTRA=' . clean_spaces($extra . ' -lstdc++'));
+ }
+ $env['CFLAGS'] .= ' -Xclang -target-feature -Xclang +evex512';
+ $env['CXXFLAGS'] .= ' -Xclang -target-feature -Xclang +evex512';
+ }
+ return $env;
+ }
}
diff --git a/src/SPC/builder/extension/spx.php b/src/SPC/builder/extension/spx.php
index f5e736d5..a5a8ad71 100644
--- a/src/SPC/builder/extension/spx.php
+++ b/src/SPC/builder/extension/spx.php
@@ -13,7 +13,7 @@ class spx extends Extension
{
public function getUnixConfigureArg(bool $shared = false): string
{
- $arg = '--enable-spx' . ($shared ? '=shared' : '');
+ $arg = '--enable-SPX' . ($shared ? '=shared' : '');
if ($this->builder->getLib('zlib') !== null) {
$arg .= ' --with-zlib-dir=' . BUILD_ROOT_PATH;
}
@@ -29,4 +29,20 @@ class spx extends Extension
);
return true;
}
+
+ public function patchBeforeBuildconf(): bool
+ {
+ FileSystem::replaceFileStr(
+ $this->source_dir . '/config.m4',
+ 'CFLAGS="$CFLAGS -Werror -Wall -O3 -pthread -std=gnu90"',
+ 'CFLAGS="$CFLAGS -pthread"'
+ );
+ FileSystem::replaceFileStr(
+ $this->source_dir . '/src/php_spx.h',
+ "extern zend_module_entry spx_module_entry;\n",
+ "extern zend_module_entry spx_module_entry;;\n#define phpext_spx_ptr &spx_module_entry\n"
+ );
+ FileSystem::copy($this->source_dir . '/src/php_spx.h', $this->source_dir . '/php_spx.h');
+ return true;
+ }
}
diff --git a/src/SPC/builder/extension/swoole.php b/src/SPC/builder/extension/swoole.php
index a4a531cf..7ff4b331 100644
--- a/src/SPC/builder/extension/swoole.php
+++ b/src/SPC/builder/extension/swoole.php
@@ -69,12 +69,15 @@ class swoole extends Extension
$arg .= $this->builder->getExt('swoole-hook-pgsql') ? ' --enable-swoole-pgsql' : ' --disable-swoole-pgsql';
$arg .= $this->builder->getExt('swoole-hook-mysql') ? ' --enable-mysqlnd' : ' --disable-mysqlnd';
$arg .= $this->builder->getExt('swoole-hook-sqlite') ? ' --enable-swoole-sqlite' : ' --disable-swoole-sqlite';
-
if ($this->builder->getExt('swoole-hook-odbc')) {
$config = (new SPCConfigUtil($this->builder, ['libs_only_deps' => true]))->config([], ['unixodbc']);
$arg .= ' --with-swoole-odbc=unixODBC,' . BUILD_ROOT_PATH . ' SWOOLE_ODBC_LIBS="' . $config['libs'] . '"';
}
+ if ($this->getExtVersion() >= '6.1.0') {
+ $arg .= ' --enable-swoole-stdext';
+ }
+
if (SPCTarget::getTargetOS() === 'Darwin') {
$arg .= ' ac_cv_lib_pthread_pthread_barrier_init=no';
}
diff --git a/src/SPC/builder/extension/zip.php b/src/SPC/builder/extension/zip.php
new file mode 100644
index 00000000..eab5e40a
--- /dev/null
+++ b/src/SPC/builder/extension/zip.php
@@ -0,0 +1,17 @@
+ SPCTarget::getRuntimeLibs(), // do not pass static libraries here yet, they may contain polyfills for libc functions!
]);
+ $phpvars = getenv('SPC_EXTRA_PHP_VARS') ?: '';
+
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
if ($embed_type !== 'static' && SPCTarget::isStatic()) {
throw new WrongUsageException(
@@ -103,22 +106,22 @@ class LinuxBuilder extends UnixBuilderBase
);
}
- 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 ') .
- ($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
- ($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
- ($enableCgi ? '--enable-cgi ' : '--disable-cgi ') .
- $config_file_path .
- $config_file_scan_dir .
- $json_74 .
- $zts .
- $maxExecutionTimers .
- $this->makeStaticExtensionArgs() . ' '
- );
+ $this->seekPhpSrcLogFileOnException(fn () => 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 ') .
+ ($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
+ ($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
+ ($enableCgi ? '--enable-cgi ' : '--disable-cgi ') .
+ $config_file_path .
+ $config_file_scan_dir .
+ $json_74 .
+ $zts .
+ $maxExecutionTimers .
+ $phpvars . ' ' .
+ $this->makeStaticExtensionArgs() . ' '
+ ));
$this->emitPatchPoint('before-php-make');
SourcePatcher::patchBeforeMake($this);
@@ -148,16 +151,22 @@ class LinuxBuilder extends UnixBuilderBase
}
$this->buildEmbed();
}
- // build dynamic extensions if needed, must happen before building FrankenPHP to make sure we export all necessary, undefined symbols
- $shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared'))));
- if (!empty($shared_extensions)) {
- logger()->info('Building shared extensions ...');
- $this->buildSharedExts();
- }
if ($enableFrankenphp) {
logger()->info('building frankenphp');
$this->buildFrankenphp();
}
+ $shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared'))));
+ if (!empty($shared_extensions)) {
+ if (SPCTarget::isStatic()) {
+ throw new WrongUsageException(
+ "You're building against musl libc statically (the default on Linux), but you're trying to build shared extensions.\n" .
+ 'Static musl libc does not implement `dlopen`, so your php binary is not able to load shared extensions.' . "\n" .
+ 'Either use SPC_LIBC=glibc to link against glibc on a glibc OS, or use SPC_TARGET="native-native-musl -dynamic" to link against musl libc dynamically using `zig cc`.'
+ );
+ }
+ logger()->info('Building shared extensions...');
+ $this->buildSharedExts();
+ }
}
public function testPHP(int $build_target = BUILD_TARGET_NONE)
@@ -171,11 +180,19 @@ class LinuxBuilder extends UnixBuilderBase
*/
protected function buildCli(): void
{
+ if ($this->getExt('readline') && SPCTarget::isStatic()) {
+ SourcePatcher::patchFile('musl_static_readline.patch', SOURCE_PATH . '/php-src');
+ }
+
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
- $SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
- ->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} cli");
+ ->exec("make {$concurrency} {$vars} cli");
+
+ if ($this->getExt('readline') && SPCTarget::isStatic()) {
+ SourcePatcher::patchFile('musl_static_readline.patch', SOURCE_PATH . '/php-src', true);
+ }
if (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-unneeded php');
@@ -191,10 +208,10 @@ class LinuxBuilder extends UnixBuilderBase
protected function buildCgi(): void
{
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
- $SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
- ->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} cgi");
+ ->exec("make {$concurrency} {$vars} cgi");
if (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/cgi')->exec('strip --strip-unneeded php-cgi');
@@ -226,11 +243,11 @@ class LinuxBuilder extends UnixBuilderBase
// patch fake cli for micro
$vars['EXTRA_CFLAGS'] .= $enable_fake_cli;
$vars = SystemUtil::makeEnvVarString($vars);
- $SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
- ->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} micro");
+ ->exec("make {$concurrency} {$vars} micro");
$this->processMicroUPX();
@@ -247,10 +264,10 @@ class LinuxBuilder extends UnixBuilderBase
protected function buildFpm(): void
{
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
- $SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
- ->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} fpm");
+ ->exec("make {$concurrency} {$vars} fpm");
if (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-unneeded php-fpm');
@@ -267,12 +284,17 @@ class LinuxBuilder extends UnixBuilderBase
*/
protected function buildEmbed(): void
{
+ $sharedExts = array_filter($this->exts, static fn ($ext) => $ext->isBuildShared());
+ $sharedExts = array_filter($sharedExts, static function ($ext) {
+ return Config::getExt($ext->getName(), 'build-with-php') === true;
+ });
+ $install_modules = $sharedExts ? 'install-modules' : '';
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
-
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec('sed -i "s|^EXTENSION_DIR = .*|EXTENSION_DIR = /' . basename(BUILD_MODULES_PATH) . '|" Makefile')
- ->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install");
+ ->exec("make {$concurrency} INSTALL_ROOT=" . BUILD_ROOT_PATH . " {$vars} install-sapi {$install_modules} install-build install-headers install-programs");
$ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: '';
$libDir = BUILD_LIB_PATH;
@@ -327,7 +349,7 @@ class LinuxBuilder extends UnixBuilderBase
$target = "{$libDir}/{$realLibName}";
if (file_exists($target)) {
[, $output] = shell()->execWithResult('readelf -d ' . escapeshellarg($target));
- $output = join("\n", $output);
+ $output = implode("\n", $output);
if (preg_match('/SONAME.*\[(.+)]/', $output, $sonameMatch)) {
$currentSoname = $sonameMatch[1];
if ($currentSoname !== basename($target)) {
@@ -361,12 +383,12 @@ class LinuxBuilder extends UnixBuilderBase
$config = (new SPCConfigUtil($this, ['libs_only_deps' => true, 'absolute_libs' => true]))->config($this->ext_list, $this->lib_list, $this->getOption('with-suggested-exts'), $this->getOption('with-suggested-libs'));
$static = SPCTarget::isStatic() ? '-all-static' : '';
$lib = BUILD_LIB_PATH;
- return [
+ return array_filter([
'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",
- ];
+ ]);
}
/**
diff --git a/src/SPC/builder/linux/library/libedit.php b/src/SPC/builder/linux/library/libedit.php
new file mode 100644
index 00000000..ef869212
--- /dev/null
+++ b/src/SPC/builder/linux/library/libedit.php
@@ -0,0 +1,12 @@
+cd(SOURCE_PATH . '/php-src')
- ->exec(
- getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
+ $this->seekPhpSrcLogFileOnException(fn () => shell()->cd(SOURCE_PATH . '/php-src')->exec(
+ getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
($enableCli ? '--enable-cli ' : '--disable-cli ') .
($enableFpm ? '--enable-fpm ' : '--disable-fpm ') .
($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
@@ -132,7 +132,7 @@ class MacOSBuilder extends UnixBuilderBase
$zts .
$this->makeStaticExtensionArgs() . ' ' .
$envs_build_php
- );
+ ));
$this->emitPatchPoint('before-php-make');
SourcePatcher::patchBeforeMake($this);
@@ -162,15 +162,15 @@ class MacOSBuilder extends UnixBuilderBase
}
$this->buildEmbed();
}
+ if ($enableFrankenphp) {
+ logger()->info('building frankenphp');
+ $this->buildFrankenphp();
+ }
$shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared'))));
if (!empty($shared_extensions)) {
logger()->info('Building shared extensions ...');
$this->buildSharedExts();
}
- if ($enableFrankenphp) {
- logger()->info('building frankenphp');
- $this->buildFrankenphp();
- }
}
public function testPHP(int $build_target = BUILD_TARGET_NONE)
@@ -187,8 +187,8 @@ class MacOSBuilder extends UnixBuilderBase
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$shell = shell()->cd(SOURCE_PATH . '/php-src');
- $SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
- $shell->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} cli");
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
+ $shell->exec("make {$concurrency} {$vars} cli");
if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/cli/php')->exec('strip -S sapi/cli/php');
}
@@ -200,8 +200,8 @@ class MacOSBuilder extends UnixBuilderBase
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$shell = shell()->cd(SOURCE_PATH . '/php-src');
- $SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make';
- $shell->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} cgi");
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
+ $shell->exec("make {$concurrency} {$vars} cgi");
if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/cgi/php-cgi')->exec('strip -S sapi/cgi/php-cgi');
}
@@ -230,7 +230,8 @@ class MacOSBuilder extends UnixBuilderBase
$shell = shell()->cd(SOURCE_PATH . '/php-src');
// build
- $shell->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . " {$vars} micro");
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
+ $shell->exec("make {$concurrency} {$vars} micro");
// strip
if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/micro/micro.sfx')->exec('strip -S sapi/micro/micro.sfx');
@@ -251,7 +252,8 @@ class MacOSBuilder extends UnixBuilderBase
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$shell = shell()->cd(SOURCE_PATH . '/php-src');
- $shell->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . " {$vars} fpm");
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
+ $shell->exec("make {$concurrency} {$vars} fpm");
if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/fpm/php-fpm')->exec('strip -S sapi/fpm/php-fpm');
}
@@ -263,10 +265,15 @@ class MacOSBuilder extends UnixBuilderBase
*/
protected function buildEmbed(): void
{
+ $sharedExts = array_filter($this->exts, static fn ($ext) => $ext->isBuildShared());
+ $sharedExts = array_filter($sharedExts, static function ($ext) {
+ return Config::getExt($ext->getName(), 'build-with-php') === true;
+ });
+ $install_modules = $sharedExts ? 'install-modules' : '';
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
-
+ $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
shell()->cd(SOURCE_PATH . '/php-src')
- ->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install");
+ ->exec("make {$concurrency} INSTALL_ROOT=" . BUILD_ROOT_PATH . " {$vars} install-sapi {$install_modules} install-build install-headers install-programs");
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') {
$AR = getenv('AR') ?: 'ar';
@@ -280,10 +287,10 @@ class MacOSBuilder extends UnixBuilderBase
private function getMakeExtraVars(): array
{
$config = (new SPCConfigUtil($this, ['libs_only_deps' => true]))->config($this->ext_list, $this->lib_list, $this->getOption('with-suggested-exts'), $this->getOption('with-suggested-libs'));
- return [
+ return array_filter([
'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'),
'EXTRA_LDFLAGS_PROGRAM' => '-L' . BUILD_LIB_PATH,
'EXTRA_LIBS' => $config['libs'],
- ];
+ ]);
}
}
diff --git a/src/SPC/builder/macos/library/libedit.php b/src/SPC/builder/macos/library/libedit.php
new file mode 100644
index 00000000..18d2c3d1
--- /dev/null
+++ b/src/SPC/builder/macos/library/libedit.php
@@ -0,0 +1,12 @@
+processFrankenphpApp();
$nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : '';
$nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : '';
@@ -339,11 +340,11 @@ abstract class UnixBuilderBase extends BuilderBase
}
}
$debugFlags = $this->getOption('no-strip') ? '-w -s ' : '';
- $extLdFlags = "-extldflags '-pie{$dynamic_exports}'";
+ $extLdFlags = "-extldflags '-pie{$dynamic_exports} {$this->arch_ld_flags}'";
$muslTags = '';
$staticFlags = '';
if (SPCTarget::isStatic()) {
- $extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000{$dynamic_exports}'";
+ $extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000{$dynamic_exports} {$this->arch_ld_flags}'";
$muslTags = 'static_build,';
$staticFlags = '-static-pie';
}
@@ -351,7 +352,6 @@ abstract class UnixBuilderBase extends BuilderBase
$config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list);
$cflags = "{$this->arch_c_flags} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS');
$libs = $config['libs'];
- $libs .= PHP_OS_FAMILY === 'Linux' ? ' -lrt' : '';
// Go's gcc driver doesn't automatically link against -lgcov or -lrt. Ugly, but necessary fix.
if ((str_contains((string) getenv('SPC_DEFAULT_C_FLAGS'), '-fprofile') ||
str_contains((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), '-fprofile')) &&
@@ -359,7 +359,7 @@ abstract class UnixBuilderBase extends BuilderBase
$cflags .= ' -Wno-error=missing-profile';
$libs .= ' -lgcov';
}
- $env = [
+ $env = [...[
'CGO_ENABLED' => '1',
'CGO_CFLAGS' => clean_spaces($cflags),
'CGO_LDFLAGS' => "{$this->arch_ld_flags} {$staticFlags} {$config['ldflags']} {$libs}",
@@ -369,12 +369,7 @@ abstract class UnixBuilderBase extends BuilderBase
"v{$frankenPhpVersion} PHP {$libphpVersion} Caddy'\\\" " .
"-tags={$muslTags}nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}",
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
- ];
- foreach (GoXcaddy::getEnvironment() as $key => $value) {
- if ($key !== 'PATH') {
- $env[$key] = $value;
- }
- }
+ ], ...GoXcaddy::getEnvironment()];
shell()->cd(BUILD_BIN_PATH)
->setEnv($env)
->exec("xcaddy build --output frankenphp {$xcaddyModules}");
@@ -387,4 +382,20 @@ abstract class UnixBuilderBase extends BuilderBase
}
}
}
+
+ /**
+ * Seek php-src/config.log when building PHP, add it to exception.
+ */
+ protected function seekPhpSrcLogFileOnException(callable $callback): void
+ {
+ try {
+ $callback();
+ } catch (SPCException $e) {
+ if (file_exists(SOURCE_PATH . '/php-src/config.log')) {
+ $e->addExtraLogFile('php-src config.log', 'php-src.config.log');
+ copy(SOURCE_PATH . '/php-src/config.log', SPC_LOGS_DIR . '/php-src.config.log');
+ }
+ throw $e;
+ }
+ }
}
diff --git a/src/SPC/builder/unix/library/attr.php b/src/SPC/builder/unix/library/attr.php
index b33cf987..67ac5feb 100644
--- a/src/SPC/builder/unix/library/attr.php
+++ b/src/SPC/builder/unix/library/attr.php
@@ -17,7 +17,7 @@ trait attr
->exec('libtoolize --force --copy')
->exec('./autogen.sh || autoreconf -if')
->configure('--disable-nls')
- ->make();
+ ->make('install-attributes_h install-data install-libattr_h install-libLTLIBRARIES install-pkgincludeHEADERS install-pkgconfDATA', with_install: false);
$this->patchPkgconfPrefix(['libattr.pc'], PKGCONF_PATCH_PREFIX);
}
}
diff --git a/src/SPC/builder/unix/library/gettext.php b/src/SPC/builder/unix/library/gettext.php
index f0fdae54..332e25c9 100644
--- a/src/SPC/builder/unix/library/gettext.php
+++ b/src/SPC/builder/unix/library/gettext.php
@@ -31,7 +31,7 @@ trait gettext
$autoconf->addConfigureArgs('--disable-threads');
}
- $autoconf->configure()->make();
+ $autoconf->configure()->make(dir: $this->getSourceDir() . '/gettext-runtime/intl');
$this->patchLaDependencyPrefix();
}
}
diff --git a/src/SPC/builder/unix/library/imagemagick.php b/src/SPC/builder/unix/library/imagemagick.php
index 2d312549..42064e2d 100644
--- a/src/SPC/builder/unix/library/imagemagick.php
+++ b/src/SPC/builder/unix/library/imagemagick.php
@@ -12,6 +12,11 @@ trait imagemagick
{
protected function build(): void
{
+ $original_ldflags = $this->builder->arch_ld_flags;
+ if (str_contains($this->builder->arch_ld_flags, '-Wl,--as-needed')) {
+ $this->builder->arch_ld_flags = str_replace('-Wl,--as-needed', '', $original_ldflags);
+ }
+
$ac = UnixAutoconfExecutor::create($this)
->optionalLib('libzip', ...ac_with_args('zip'))
->optionalLib('libjpeg', ...ac_with_args('jpeg'))
@@ -32,7 +37,7 @@ trait imagemagick
);
// special: linux-static target needs `-static`
- $ldflags = SPCTarget::isStatic() ? ('-static -ldl') : '-ldl';
+ $ldflags = SPCTarget::isStatic() ? '-static -ldl' : '-ldl';
// special: macOS needs -iconv
$libs = SPCTarget::getTargetOS() === 'Darwin' ? '-liconv' : '';
@@ -45,6 +50,8 @@ trait imagemagick
$ac->configure()->make();
+ $this->builder->arch_ld_flags = $original_ldflags;
+
$filelist = [
'ImageMagick.pc',
'ImageMagick-7.Q16HDRI.pc',
diff --git a/src/SPC/builder/unix/library/libacl.php b/src/SPC/builder/unix/library/libacl.php
index 223e09fc..8c5d699d 100644
--- a/src/SPC/builder/unix/library/libacl.php
+++ b/src/SPC/builder/unix/library/libacl.php
@@ -26,7 +26,7 @@ trait libacl
->exec('libtoolize --force --copy')
->exec('./autogen.sh || autoreconf -if')
->configure('--disable-nls', '--disable-tests')
- ->make();
+ ->make('install-acl_h install-libacl_h install-data install-libLTLIBRARIES install-pkgincludeHEADERS install-sysincludeHEADERS install-pkgconfDATA', with_install: false);
$this->patchPkgconfPrefix(['libacl.pc'], PKGCONF_PATCH_PREFIX);
}
}
diff --git a/src/SPC/builder/unix/library/libedit.php b/src/SPC/builder/unix/library/libedit.php
new file mode 100644
index 00000000..2de9077f
--- /dev/null
+++ b/src/SPC/builder/unix/library/libedit.php
@@ -0,0 +1,30 @@
+source_dir . '/src/sys.h',
+ '|//#define\s+strl|',
+ '#define strl'
+ );
+ return true;
+ }
+
+ protected function build(): void
+ {
+ UnixAutoconfExecutor::create($this)
+ ->appendEnv(['CFLAGS' => '-D__STDC_ISO_10646__=201103L'])
+ ->configure()
+ ->make();
+ $this->patchPkgconfPrefix(['libedit.pc']);
+ }
+}
diff --git a/src/SPC/builder/unix/library/libiconv.php b/src/SPC/builder/unix/library/libiconv.php
index fd68e309..5ccc9484 100644
--- a/src/SPC/builder/unix/library/libiconv.php
+++ b/src/SPC/builder/unix/library/libiconv.php
@@ -10,7 +10,13 @@ trait libiconv
{
protected function build(): void
{
- UnixAutoconfExecutor::create($this)->configure('--enable-extra-encodings')->make();
+ UnixAutoconfExecutor::create($this)
+ ->configure(
+ '--enable-extra-encodings',
+ '--enable-year2038',
+ )
+ ->make('install-lib', with_install: false)
+ ->make('install-lib', with_install: false, dir: $this->getSourceDir() . '/libcharset');
$this->patchLaDependencyPrefix();
}
}
diff --git a/src/SPC/builder/unix/library/libxml2.php b/src/SPC/builder/unix/library/libxml2.php
index 87fbab10..7152c0c4 100644
--- a/src/SPC/builder/unix/library/libxml2.php
+++ b/src/SPC/builder/unix/library/libxml2.php
@@ -20,17 +20,17 @@ trait libxml2
"-DZLIB_INCLUDE_DIR={$this->getIncludeDir()}",
'-DLIBXML2_WITH_ZLIB=OFF',
)
- ->optionalLib('icu', ...cmake_boolean_args('LIBXML2_WITH_ICU'))
->optionalLib('xz', ...cmake_boolean_args('LIBXML2_WITH_LZMA'))
->addConfigureArgs(
'-DLIBXML2_WITH_ICONV=ON',
+ '-DLIBXML2_WITH_ICU=OFF', // optional, but discouraged: https://gitlab.gnome.org/GNOME/libxml2/-/blob/master/README.md
'-DLIBXML2_WITH_PYTHON=OFF',
'-DLIBXML2_WITH_PROGRAMS=OFF',
'-DLIBXML2_WITH_TESTS=OFF',
);
if ($this instanceof LinuxLibraryBase) {
- $cmake->addConfigureArgs('-DIconv_IS_BUILD_IN=OFF');
+ $cmake->addConfigureArgs('-DIconv_IS_BUILT_IN=OFF');
}
$cmake->build();
diff --git a/src/SPC/builder/unix/library/libzip.php b/src/SPC/builder/unix/library/libzip.php
index 931bfcc3..ad0befea 100644
--- a/src/SPC/builder/unix/library/libzip.php
+++ b/src/SPC/builder/unix/library/libzip.php
@@ -22,6 +22,7 @@ trait libzip
'-DBUILD_EXAMPLES=OFF',
'-DBUILD_REGRESS=OFF',
'-DBUILD_TOOLS=OFF',
+ '-DBUILD_OSSFUZZ=OFF',
)
->build();
$this->patchPkgconfPrefix(['libzip.pc'], PKGCONF_PATCH_PREFIX);
diff --git a/src/SPC/builder/unix/library/ncurses.php b/src/SPC/builder/unix/library/ncurses.php
index 3899e559..27725c3d 100644
--- a/src/SPC/builder/unix/library/ncurses.php
+++ b/src/SPC/builder/unix/library/ncurses.php
@@ -38,7 +38,7 @@ trait ncurses
->make();
$final = FileSystem::scanDirFiles(BUILD_BIN_PATH, relative: true);
// Remove the new files
- $new_files = array_diff($final, $filelist);
+ $new_files = array_diff($final, $filelist ?: []);
foreach ($new_files as $file) {
@unlink(BUILD_BIN_PATH . '/' . $file);
}
diff --git a/src/SPC/builder/unix/library/postgresql.php b/src/SPC/builder/unix/library/postgresql.php
index 3bc6835d..67935d14 100644
--- a/src/SPC/builder/unix/library/postgresql.php
+++ b/src/SPC/builder/unix/library/postgresql.php
@@ -4,93 +4,83 @@ declare(strict_types=1);
namespace SPC\builder\unix\library;
-use SPC\builder\linux\library\LinuxLibraryBase;
-use SPC\exception\BuildFailureException;
use SPC\exception\FileSystemException;
use SPC\store\FileSystem;
+use SPC\util\PkgConfigUtil;
+use SPC\util\SPCConfigUtil;
use SPC\util\SPCTarget;
trait postgresql
{
+ public function patchBeforeBuild(): bool
+ {
+ // fix aarch64 build on glibc 2.17 (e.g. CentOS 7)
+ if (SPCTarget::getLibcVersion() === '2.17' && GNU_ARCH === 'aarch64') {
+ try {
+ FileSystem::replaceFileStr("{$this->source_dir}/src/port/pg_popcount_aarch64.c", 'HWCAP_SVE', '0');
+ FileSystem::replaceFileStr(
+ "{$this->source_dir}/src/port/pg_crc32c_armv8_choose.c",
+ '#if defined(__linux__) && !defined(__aarch64__) && !defined(HWCAP2_CRC32)',
+ '#if defined(__linux__) && !defined(HWCAP_CRC32)'
+ );
+ } catch (FileSystemException) {
+ // allow file not-existence to make it compatible with old and new version
+ }
+ }
+ // skip the test on platforms where libpq infrastructure may be provided by statically-linked libraries
+ FileSystem::replaceFileStr("{$this->source_dir}/src/interfaces/libpq/Makefile", 'invokes exit\'; exit 1;', 'invokes exit\';');
+ // disable shared libs build
+ FileSystem::replaceFileStr(
+ "{$this->source_dir}/src/Makefile.shlib",
+ [
+ '$(LINK.shared) -o $@ $(OBJS) $(LDFLAGS) $(LDFLAGS_SL) $(SHLIB_LINK)',
+ '$(INSTALL_SHLIB) $< \'$(DESTDIR)$(pkglibdir)/$(shlib)\'',
+ '$(INSTALL_SHLIB) $< \'$(DESTDIR)$(libdir)/$(shlib)\'',
+ '$(INSTALL_SHLIB) $< \'$(DESTDIR)$(bindir)/$(shlib)\'',
+ ],
+ ''
+ );
+ return true;
+ }
+
protected function build(): void
{
- $builddir = BUILD_ROOT_PATH;
- $envs = '';
- $packages = 'zlib openssl readline libxml-2.0';
- $optional_packages = [
- 'zstd' => 'libzstd',
- 'ldap' => 'ldap',
- 'libxslt' => 'libxslt',
- 'icu' => 'icu-i18n',
+ $libs = array_map(fn ($x) => $x->getName(), $this->getDependencies());
+ $spc = new SPCConfigUtil($this->getBuilder(), ['no_php' => true, 'libs_only_deps' => true]);
+ $config = $spc->config(libraries: $libs, include_suggest_lib: $this->builder->getOption('with-suggested-libs'));
+
+ $env_vars = [
+ 'CFLAGS' => $config['cflags'],
+ 'CPPFLAGS' => '-DPIC',
+ 'LDFLAGS' => $config['ldflags'],
+ 'LIBS' => $config['libs'],
];
- $error_exec_cnt = 0;
- foreach ($optional_packages as $lib => $pkg) {
- if ($this->getBuilder()->getLib($lib)) {
- $packages .= ' ' . $pkg;
- $output = shell()->execWithResult("pkg-config --static {$pkg}");
- $error_exec_cnt += $output[0] === 0 ? 0 : 1;
- logger()->info(var_export($output[1], true));
- }
- }
-
- $output = shell()->execWithResult("pkg-config --cflags-only-I --static {$packages}");
- $error_exec_cnt += $output[0] === 0 ? 0 : 1;
- $macos_15_bug_cflags = PHP_OS_FAMILY === 'Darwin' ? ' -Wno-unguarded-availability-new' : '';
- $cflags = '';
- if (!empty($output[1][0])) {
- $cflags = $output[1][0];
- $envs .= ' CPPFLAGS="-DPIC"';
- $cflags = "{$cflags} -fno-ident{$macos_15_bug_cflags}";
- }
- $output = shell()->execWithResult("pkg-config --libs-only-L --static {$packages}");
- $error_exec_cnt += $output[0] === 0 ? 0 : 1;
- if (!empty($output[1][0])) {
- $ldflags = $output[1][0];
- $envs .= SPCTarget::isStatic() ? " LDFLAGS=\"{$ldflags} -static\" " : " LDFLAGS=\"{$ldflags}\" ";
- }
- $output = shell()->execWithResult("pkg-config --libs-only-l --static {$packages}");
- $error_exec_cnt += $output[0] === 0 ? 0 : 1;
- if (!empty($output[1][0])) {
- $libs = $output[1][0];
- $libcpp = '';
- if ($this->builder->getLib('icu')) {
- $libcpp = $this instanceof LinuxLibraryBase ? ' -lstdc++' : ' -lc++';
- }
- $envs .= " LIBS=\"{$libs}{$libcpp}\" ";
- }
- if ($error_exec_cnt > 0) {
- throw new BuildFailureException('Failed to get pkg-config information!');
+ if ($ldLibraryPath = getenv('SPC_LD_LIBRARY_PATH')) {
+ $env_vars['LD_LIBRARY_PATH'] = $ldLibraryPath;
}
FileSystem::resetDir($this->source_dir . '/build');
- $version = $this->getVersion();
- // 16.1 workaround
- if (version_compare($version, '16.1') >= 0) {
- # 有静态链接配置 参考文件: src/interfaces/libpq/Makefile
- shell()->cd($this->source_dir . '/build')
- ->exec('sed -i.backup "s/invokes exit\'; exit 1;/invokes exit\';/" ../src/interfaces/libpq/Makefile')
- ->exec('sed -i.backup "278 s/^/# /" ../src/Makefile.shlib')
- ->exec('sed -i.backup "402 s/^/# /" ../src/Makefile.shlib');
- } else {
- throw new BuildFailureException('Unsupported version for postgresql: ' . $version . ' !');
- }
+ // php source relies on the non-private encoding functions in libpgcommon.a
+ FileSystem::replaceFileStr(
+ "{$this->source_dir}/src/common/Makefile",
+ '$(OBJS_FRONTEND): CPPFLAGS += -DUSE_PRIVATE_ENCODING_FUNCS',
+ '$(OBJS_FRONTEND): CPPFLAGS += -UUSE_PRIVATE_ENCODING_FUNCS -DFRONTEND',
+ );
// configure
- shell()->cd($this->source_dir . '/build')->initializeEnv($this)
- ->appendEnv(['CFLAGS' => $cflags])
+ $shell = shell()->cd("{$this->source_dir}/build")->initializeEnv($this)
+ ->appendEnv($env_vars)
->exec(
- "{$envs} ../configure " .
- "--prefix={$builddir} " .
- ($this->builder->getOption('enable-zts') ? '--enable-thread-safety ' : '--disable-thread-safety ') .
+ '../configure ' .
+ "--prefix={$this->getBuildRootPath()} " .
'--enable-coverage=no ' .
'--with-ssl=openssl ' .
'--with-readline ' .
'--with-libxml ' .
($this->builder->getLib('icu') ? '--with-icu ' : '--without-icu ') .
($this->builder->getLib('ldap') ? '--with-ldap ' : '--without-ldap ') .
- // '--without-ldap ' .
($this->builder->getLib('libxslt') ? '--with-libxslt ' : '--without-libxslt ') .
($this->builder->getLib('zstd') ? '--with-zstd ' : '--without-zstd ') .
'--without-lz4 ' .
@@ -99,32 +89,29 @@ trait postgresql
'--without-pam ' .
'--without-bonjour ' .
'--without-tcl '
- )
- ->exec($envs . ' make -C src/bin/pg_config install')
- ->exec($envs . ' make -C src/include install')
- ->exec($envs . ' make -C src/common install')
- ->exec($envs . ' make -C src/port install')
- ->exec($envs . ' make -C src/interfaces/libpq install');
+ );
+
+ // patch ldap lib
+ if ($this->builder->getLib('ldap')) {
+ $libs = PkgConfigUtil::getLibsArray('ldap');
+ $libs = clean_spaces(implode(' ', $libs));
+ FileSystem::replaceFileStr($this->source_dir . '/build/config.status', '-lldap', $libs);
+ FileSystem::replaceFileStr($this->source_dir . '/build/src/Makefile.global', '-lldap', $libs);
+ }
+
+ $shell
+ ->exec('make -C src/bin/pg_config install')
+ ->exec('make -C src/include install')
+ ->exec('make -C src/common install')
+ ->exec('make -C src/port install')
+ ->exec('make -C src/interfaces/libpq install');
// remove dynamic libs
shell()->cd($this->source_dir . '/build')
- ->exec("rm -rf {$builddir}/lib/*.so.*")
- ->exec("rm -rf {$builddir}/lib/*.so")
- ->exec("rm -rf {$builddir}/lib/*.dylib");
+ ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.so.*")
+ ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.so")
+ ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.dylib");
- FileSystem::replaceFileStr(BUILD_LIB_PATH . '/pkgconfig/libpq.pc', '-lldap', '-lldap -llber');
- }
-
- private function getVersion(): string
- {
- try {
- $file = FileSystem::readFile($this->source_dir . '/meson.build');
- if (preg_match("/^\\s+version:\\s?'(.*)'/m", $file, $match)) {
- return $match[1];
- }
- return 'unknown';
- } catch (FileSystemException) {
- return 'unknown';
- }
+ FileSystem::replaceFileStr("{$this->getLibDir()}/pkgconfig/libpq.pc", '-lldap', '-lldap -llber');
}
}
diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php
index f8ab384b..c5c60d26 100644
--- a/src/SPC/command/BuildPHPCommand.php
+++ b/src/SPC/command/BuildPHPCommand.php
@@ -223,11 +223,9 @@ class BuildPHPCommand extends BuildCommand
// ---------- When using bin/spc-alpine-docker, the build root path is different from the host system ----------
$build_root_path = BUILD_ROOT_PATH;
- $cwd = getcwd();
$fixed = '';
+ $build_root_path = get_display_path($build_root_path);
if (!empty(getenv('SPC_FIX_DEPLOY_ROOT'))) {
- str_replace($cwd, '', $build_root_path);
- $build_root_path = getenv('SPC_FIX_DEPLOY_ROOT') . '/' . basename($build_root_path);
$fixed = ' (host system)';
}
if (($rule & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
diff --git a/src/SPC/command/CraftCommand.php b/src/SPC/command/CraftCommand.php
index 27b3c38f..6c40133d 100644
--- a/src/SPC/command/CraftCommand.php
+++ b/src/SPC/command/CraftCommand.php
@@ -5,6 +5,8 @@ declare(strict_types=1);
namespace SPC\command;
use SPC\exception\ValidationException;
+use SPC\store\pkg\GoXcaddy;
+use SPC\store\pkg\Zig;
use SPC\toolchain\ToolchainManager;
use SPC\toolchain\ZigToolchain;
use SPC\util\ConfigValidator;
@@ -63,7 +65,7 @@ class CraftCommand extends BuildCommand
}
}
// install go and xcaddy for frankenphp
- if (in_array('frankenphp', $craft['sapi'])) {
+ if (in_array('frankenphp', $craft['sapi']) && !GoXcaddy::isInstalled()) {
$retcode = $this->runCommand('install-pkg', 'go-xcaddy');
if ($retcode !== 0) {
$this->output->writeln('craft go-xcaddy failed');
@@ -71,7 +73,7 @@ class CraftCommand extends BuildCommand
}
}
// install zig if requested
- if (ToolchainManager::getToolchainClass() === ZigToolchain::class) {
+ if (ToolchainManager::getToolchainClass() === ZigToolchain::class && !Zig::isInstalled()) {
$retcode = $this->runCommand('install-pkg', 'zig');
if ($retcode !== 0) {
$this->output->writeln('craft zig failed');
diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php
index b9bae6a5..00bcc194 100644
--- a/src/SPC/command/DownloadCommand.php
+++ b/src/SPC/command/DownloadCommand.php
@@ -106,7 +106,7 @@ class DownloadCommand extends BaseCommand
}
// retry
- $retry = intval($this->getOption('retry'));
+ $retry = (int) $this->getOption('retry');
f_putenv('SPC_DOWNLOAD_RETRIES=' . $retry);
// Use shallow-clone can reduce git resource download
@@ -265,7 +265,7 @@ class DownloadCommand extends BaseCommand
f_passthru((PHP_OS_FAMILY === 'Windows' ? 'rmdir /s /q ' : 'rm -rf ') . DOWNLOAD_PATH);
}
// unzip command check
- if (PHP_OS_FAMILY !== 'Windows' && !$this->findCommand('unzip')) {
+ if (PHP_OS_FAMILY !== 'Windows' && !self::findCommand('unzip')) {
$this->output->writeln('Missing unzip command, you need to install it first !');
$this->output->writeln('You can use "bin/spc doctor" command to check and install required tools');
return static::FAILURE;
diff --git a/src/SPC/doctor/item/LinuxToolCheckList.php b/src/SPC/doctor/item/LinuxToolCheckList.php
index c3611dcc..a23ad6d5 100644
--- a/src/SPC/doctor/item/LinuxToolCheckList.php
+++ b/src/SPC/doctor/item/LinuxToolCheckList.php
@@ -28,7 +28,7 @@ class LinuxToolCheckList
public const TOOLS_DEBIAN = [
'make', 'bison', 're2c', 'flex',
'git', 'autoconf', 'automake', 'autopoint',
- 'tar', 'unzip', 'gzip',
+ 'tar', 'unzip', 'gzip', 'gcc', 'g++',
'bzip2', 'cmake', 'patch',
'xz', 'libtoolize', 'which',
'patchelf',
@@ -37,7 +37,7 @@ class LinuxToolCheckList
public const TOOLS_RHEL = [
'perl', 'make', 'bison', 're2c', 'flex',
'git', 'autoconf', 'automake',
- 'tar', 'unzip', 'gzip', 'gcc',
+ 'tar', 'unzip', 'gzip', 'gcc', 'g++',
'bzip2', 'cmake', 'patch', 'which',
'xz', 'libtool', 'gettext-devel',
'patchelf',
@@ -53,7 +53,8 @@ class LinuxToolCheckList
'base-devel' => 'automake',
'gettext-devel' => 'gettextize',
'gettext-dev' => 'gettextize',
- 'perl-IPC-Cmd' => '/usr/share/doc/perl-IPC-Cmd',
+ 'perl-IPC-Cmd' => '/usr/share/perl5/vendor_perl/IPC/Cmd.pm',
+ 'perl-Time-Piece' => '/usr/lib64/perl5/Time/Piece.pm',
];
/** @noinspection PhpUnused */
@@ -65,7 +66,7 @@ class LinuxToolCheckList
$required = match ($distro['dist']) {
'alpine' => self::TOOLS_ALPINE,
'redhat' => self::TOOLS_RHEL,
- 'centos' => array_merge(self::TOOLS_RHEL, ['perl-IPC-Cmd']),
+ 'centos' => array_merge(self::TOOLS_RHEL, ['perl-IPC-Cmd', 'perl-Time-Piece']),
'arch' => self::TOOLS_ARCH,
default => self::TOOLS_DEBIAN,
};
@@ -81,14 +82,14 @@ class LinuxToolCheckList
return CheckResult::ok();
}
- #[AsCheckItem('if cmake version >= 3.18', limit_os: 'Linux')]
+ #[AsCheckItem('if cmake version >= 3.22', limit_os: 'Linux')]
public function checkCMakeVersion(): ?CheckResult
{
$ver = get_cmake_version();
if ($ver === null) {
return CheckResult::fail('Failed to get cmake version');
}
- if (version_compare($ver, '3.18.0') < 0) {
+ if (version_compare($ver, '3.22.0') < 0) {
return CheckResult::fail('cmake version is too low (' . $ver . '), please update it manually!');
}
return CheckResult::ok($ver);
diff --git a/src/SPC/exception/ExceptionHandler.php b/src/SPC/exception/ExceptionHandler.php
index 44b82b06..d784be71 100644
--- a/src/SPC/exception/ExceptionHandler.php
+++ b/src/SPC/exception/ExceptionHandler.php
@@ -137,13 +137,18 @@ class ExceptionHandler
self::logError("\n----------------------------------------\n");
- self::logError('⚠ The ' . ConsoleColor::cyan('console output log') . ConsoleColor::red(' is saved in ') . ConsoleColor::none(SPC_OUTPUT_LOG));
+ // convert log file path if in docker
+ $spc_log_convert = get_display_path(SPC_OUTPUT_LOG);
+ $shell_log_convert = get_display_path(SPC_SHELL_LOG);
+ $spc_logs_dir_convert = get_display_path(SPC_LOGS_DIR);
+
+ self::logError('⚠ The ' . ConsoleColor::cyan('console output log') . ConsoleColor::red(' is saved in ') . ConsoleColor::none($spc_log_convert));
if (file_exists(SPC_SHELL_LOG)) {
- self::logError('⚠ The ' . ConsoleColor::cyan('shell output log') . ConsoleColor::red(' is saved in ') . ConsoleColor::none(SPC_SHELL_LOG));
+ self::logError('⚠ The ' . ConsoleColor::cyan('shell output log') . ConsoleColor::red(' is saved in ') . ConsoleColor::none($shell_log_convert));
}
if ($e->getExtraLogFiles() !== []) {
foreach ($e->getExtraLogFiles() as $key => $file) {
- self::logError("⚠ Log file [{$key}] is saved in: " . ConsoleColor::none(SPC_LOGS_DIR . "/{$file}"));
+ self::logError("⚠ Log file [{$key}] is saved in: " . ConsoleColor::none("{$spc_logs_dir_convert}/{$file}"));
}
}
if (!defined('DEBUG_MODE')) {
diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php
index d659253e..63bec807 100644
--- a/src/SPC/store/Downloader.php
+++ b/src/SPC/store/Downloader.php
@@ -16,6 +16,42 @@ use SPC\util\SPCTarget;
*/
class Downloader
{
+ /**
+ * Get latest version from PIE config (Packagist)
+ *
+ * @param string $name Source name
+ * @param array $source Source meta info: [repo]
+ * @return array [url, filename]
+ */
+ public static function getPIEInfo(string $name, array $source): array
+ {
+ $packagist_url = "https://repo.packagist.org/p2/{$source['repo']}.json";
+ logger()->debug("Fetching {$name} source from packagist index: {$packagist_url}");
+ $data = json_decode(self::curlExec(
+ url: $packagist_url,
+ retries: self::getRetryAttempts()
+ ), true);
+ if (!isset($data['packages'][$source['repo']]) || !is_array($data['packages'][$source['repo']])) {
+ throw new DownloaderException("failed to find {$name} repo info from packagist");
+ }
+ // get the first version
+ $first = $data['packages'][$source['repo']][0] ?? [];
+ // check 'type' => 'php-ext' or contains 'php-ext' key
+ if (!isset($first['php-ext'])) {
+ throw new DownloaderException("failed to find {$name} php-ext info from packagist, maybe not a php extension package");
+ }
+ // get download link from dist
+ $dist_url = $first['dist']['url'] ?? null;
+ $dist_type = $first['dist']['type'] ?? null;
+ if (!$dist_url || !$dist_type) {
+ throw new DownloaderException("failed to find {$name} dist info from packagist");
+ }
+ $name = str_replace('/', '_', $source['repo']);
+ $version = $first['version'] ?? 'unknown';
+ // file name use: $name-$version.$dist_type
+ return [$dist_url, "{$name}-{$version}.{$dist_type}"];
+ }
+
/**
* Get latest version from BitBucket tag
*
@@ -65,19 +101,19 @@ class Downloader
url: "https://api.github.com/repos/{$source['repo']}/{$type}",
hooks: [[CurlHook::class, 'setupGithubToken']],
retries: self::getRetryAttempts()
- ), true);
+ ), true, 512, JSON_THROW_ON_ERROR);
$url = null;
- for ($i = 0; $i < count($data); ++$i) {
- if (($data[$i]['prerelease'] ?? false) === true && ($source['prefer-stable'] ?? false)) {
+ foreach ($data as $rel) {
+ if (($rel['prerelease'] ?? false) === true && ($source['prefer-stable'] ?? false)) {
continue;
}
if (!($source['match'] ?? null)) {
- $url = $data[$i]['tarball_url'] ?? null;
+ $url = $rel['tarball_url'] ?? null;
break;
}
- if (preg_match('|' . $source['match'] . '|', $data[$i]['tarball_url'])) {
- $url = $data[$i]['tarball_url'];
+ if (preg_match('|' . $source['match'] . '|', $rel['tarball_url'])) {
+ $url = $rel['tarball_url'];
break;
}
}
@@ -232,7 +268,8 @@ class Downloader
$quiet = !defined('DEBUG_MODE') ? '-q --quiet' : '';
$git = SPC_GIT_EXEC;
$shallow = defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '';
- $recursive = ($submodules === null) ? '--recursive' : '';
+ $recursive = ($submodules === null && defined('GIT_SHALLOW_CLONE')) ? '--recursive --shallow-submodules' : null;
+ $recursive ??= $submodules === null ? '--recursive' : '';
try {
self::registerCancelEvent(function () use ($download_path) {
@@ -243,8 +280,9 @@ class Downloader
});
f_passthru("{$git} clone {$quiet} --config core.autocrlf=false --branch \"{$branch}\" {$shallow} {$recursive} \"{$url}\" \"{$download_path}\"");
if ($submodules !== null) {
+ $depth_flag = defined('GIT_SHALLOW_CLONE') ? '--depth 1' : '';
foreach ($submodules as $submodule) {
- f_passthru("cd \"{$download_path}\" && {$git} submodule update --init " . escapeshellarg($submodule));
+ f_passthru("cd \"{$download_path}\" && {$git} submodule update --init {$depth_flag} " . escapeshellarg($submodule));
}
}
} catch (SPCException $e) {
@@ -315,91 +353,14 @@ class Downloader
if (self::isAlreadyDownloaded($name, $force, SPC_DOWNLOAD_PACKAGE)) {
return;
}
-
- try {
- switch ($pkg['type']) {
- case 'bitbuckettag': // BitBucket Tag
- [$url, $filename] = self::getLatestBitbucketTag($name, $pkg);
- self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE);
- break;
- case 'ghtar': // GitHub Release (tar)
- [$url, $filename] = self::getLatestGithubTarball($name, $pkg);
- self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE, hooks: [[CurlHook::class, 'setupGithubToken']]);
- break;
- case 'ghtagtar': // GitHub Tag (tar)
- [$url, $filename] = self::getLatestGithubTarball($name, $pkg, 'tags');
- self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE, hooks: [[CurlHook::class, 'setupGithubToken']]);
- break;
- case 'ghrel': // GitHub Release (uploaded)
- [$url, $filename] = self::getLatestGithubRelease($name, $pkg);
- self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE, ['Accept: application/octet-stream'], [[CurlHook::class, 'setupGithubToken']]);
- break;
- case 'filelist': // Basic File List (regex based crawler)
- [$url, $filename] = self::getFromFileList($name, $pkg);
- self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE);
- break;
- case 'url': // Direct download URL
- $url = $pkg['url'];
- $filename = $pkg['filename'] ?? basename($pkg['url']);
- self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWNLOAD_PACKAGE);
- break;
- case 'git': // Git repo
- self::downloadGit(
- $name,
- $pkg['url'],
- $pkg['rev'],
- $pkg['submodules'] ?? null,
- $pkg['extract'] ?? null,
- self::getRetryAttempts(),
- SPC_DOWNLOAD_PRE_BUILT
- );
- break;
- case 'local':
- // Local directory, do nothing, just lock it
- logger()->debug("Locking local source {$name}");
- LockFile::lockSource($name, [
- 'source_type' => SPC_SOURCE_LOCAL,
- 'dirname' => $pkg['dirname'],
- 'move_path' => $pkg['extract'] ?? null,
- 'lock_as' => SPC_DOWNLOAD_PACKAGE,
- ]);
- break;
- case 'custom': // Custom download method, like API-based download or other
- $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg');
- if (isset($pkg['func']) && is_callable($pkg['func'])) {
- $pkg['name'] = $name;
- $pkg['func']($force, $pkg, SPC_DOWNLOAD_PACKAGE);
- break;
- }
- foreach ($classes as $class) {
- if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) {
- $cls = new $class();
- if (in_array($name, $cls->getSupportName())) {
- (new $class())->fetch($name, $force, $pkg);
- break;
- }
- }
- }
- break;
- default:
- throw new DownloaderException('unknown source type: ' . $pkg['type']);
- }
- } catch (\Throwable $e) {
- // Because sometimes files downloaded through the command line are not automatically deleted after a failure.
- // Here we need to manually delete the file if it is detected to exist.
- if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) {
- logger()->warning('Deleting download file: ' . $filename);
- unlink(DOWNLOAD_PATH . '/' . $filename);
- }
- throw new DownloaderException('Download failed! ' . $e->getMessage());
- }
+ self::downloadByType($pkg['type'], $name, $pkg, $force, SPC_DOWNLOAD_PACKAGE);
}
/**
* Download source
*
* @param string $name source name
- * @param null|array{
+ * @param null|array{
* type: string,
* repo: ?string,
* url: ?string,
@@ -414,7 +375,7 @@ class Downloader
* path: ?string,
* text: ?string
* }
- * } $source source meta info: [type, path, rev, url, filename, regex, license]
+ * } $source source meta info: [type, path, rev, url, filename, regex, license]
* @param bool $force Whether to force download (default: false)
* @param int $download_as Lock source type (default: SPC_LOCK_SOURCE)
*/
@@ -437,80 +398,7 @@ class Downloader
return;
}
- try {
- switch ($source['type']) {
- case 'bitbuckettag': // BitBucket Tag
- [$url, $filename] = self::getLatestBitbucketTag($name, $source);
- self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
- break;
- case 'ghtar': // GitHub Release (tar)
- [$url, $filename] = self::getLatestGithubTarball($name, $source);
- self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]);
- break;
- case 'ghtagtar': // GitHub Tag (tar)
- [$url, $filename] = self::getLatestGithubTarball($name, $source, 'tags');
- self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]);
- break;
- case 'ghrel': // GitHub Release (uploaded)
- [$url, $filename] = self::getLatestGithubRelease($name, $source);
- self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as, ['Accept: application/octet-stream'], [[CurlHook::class, 'setupGithubToken']]);
- break;
- case 'filelist': // Basic File List (regex based crawler)
- [$url, $filename] = self::getFromFileList($name, $source);
- self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
- break;
- case 'url': // Direct download URL
- $url = $source['url'];
- $filename = $source['filename'] ?? basename($source['url']);
- self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
- break;
- case 'git': // Git repo
- self::downloadGit(
- $name,
- $source['url'],
- $source['rev'],
- $source['submodules'] ?? null,
- $source['path'] ?? null,
- self::getRetryAttempts(),
- $download_as
- );
- break;
- case 'local':
- // Local directory, do nothing, just lock it
- logger()->debug("Locking local source {$name}");
- LockFile::lockSource($name, [
- 'source_type' => SPC_SOURCE_LOCAL,
- 'dirname' => $source['dirname'],
- 'move_path' => $source['extract'] ?? null,
- 'lock_as' => $download_as,
- ]);
- break;
- case 'custom': // Custom download method, like API-based download or other
- if (isset($source['func']) && is_callable($source['func'])) {
- $source['name'] = $name;
- $source['func']($force, $source, $download_as);
- break;
- }
- $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source');
- foreach ($classes as $class) {
- if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
- (new $class())->fetch($force, $source, $download_as);
- break;
- }
- }
- break;
- default:
- throw new DownloaderException('unknown source type: ' . $source['type']);
- }
- } catch (\Throwable $e) {
- // Because sometimes files downloaded through the command line are not automatically deleted after a failure.
- // Here we need to manually delete the file if it is detected to exist.
- if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) {
- logger()->warning('Deleting download file: ' . $filename);
- unlink(DOWNLOAD_PATH . '/' . $filename);
- }
- throw new DownloaderException('Download failed! ' . $e->getMessage());
- }
+ self::downloadByType($source['type'], $name, $source, $force, $download_as);
}
/**
@@ -711,4 +599,109 @@ class Downloader
}
return false;
}
+
+ /**
+ * Download by type.
+ *
+ * @param string $type Types
+ * @param string $name Download item name
+ * @param array{
+ * url?: string,
+ * repo?: string,
+ * rev?: string,
+ * path?: string,
+ * filename?: string,
+ * dirname?: string,
+ * match?: string,
+ * prefer-stable?: bool,
+ * extract?: string,
+ * submodules?: array,
+ * provide-pre-built?: bool,
+ * func?: ?callable,
+ * license?: array
+ * } $conf Download item config
+ * @param bool $force Force download
+ * @param int $download_as Lock source type
+ */
+ private static function downloadByType(string $type, string $name, array $conf, bool $force, int $download_as): void
+ {
+ try {
+ switch ($type) {
+ case 'pie': // Packagist
+ [$url, $filename] = self::getPIEInfo($name, $conf);
+ self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]);
+ break;
+ case 'bitbuckettag': // BitBucket Tag
+ [$url, $filename] = self::getLatestBitbucketTag($name, $conf);
+ self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as);
+ break;
+ case 'ghtar': // GitHub Release (tar)
+ [$url, $filename] = self::getLatestGithubTarball($name, $conf);
+ self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]);
+ break;
+ case 'ghtagtar': // GitHub Tag (tar)
+ [$url, $filename] = self::getLatestGithubTarball($name, $conf, 'tags');
+ self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as, hooks: [[CurlHook::class, 'setupGithubToken']]);
+ break;
+ case 'ghrel': // GitHub Release (uploaded)
+ [$url, $filename] = self::getLatestGithubRelease($name, $conf);
+ self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as, ['Accept: application/octet-stream'], [[CurlHook::class, 'setupGithubToken']]);
+ break;
+ case 'filelist': // Basic File List (regex based crawler)
+ [$url, $filename] = self::getFromFileList($name, $conf);
+ self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as);
+ break;
+ case 'url': // Direct download URL
+ $url = $conf['url'];
+ $filename = $conf['filename'] ?? basename($conf['url']);
+ self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as);
+ break;
+ case 'git': // Git repo
+ self::downloadGit($name, $conf['url'], $conf['rev'], $conf['submodules'] ?? null, $conf['path'] ?? $conf['extract'] ?? null, self::getRetryAttempts(), $download_as);
+ break;
+ case 'local': // Local directory, do nothing, just lock it
+ LockFile::lockSource($name, [
+ 'source_type' => SPC_SOURCE_LOCAL,
+ 'dirname' => $conf['dirname'],
+ 'move_path' => $conf['path'] ?? $conf['extract'] ?? null,
+ 'lock_as' => $download_as,
+ ]);
+ break;
+ case 'custom': // Custom download method, like API-based download or other
+ if (isset($conf['func'])) {
+ $conf['name'] = $name;
+ $conf['func']($force, $conf, $download_as);
+ break;
+ }
+ $classes = [
+ ...FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source'),
+ ...FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg'),
+ ];
+ foreach ($classes as $class) {
+ if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
+ (new $class())->fetch($force, $conf, $download_as);
+ break;
+ }
+ if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) {
+ $cls = new $class();
+ if (in_array($name, $cls->getSupportName())) {
+ (new $class())->fetch($name, $force, $conf);
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ throw new DownloaderException("Unknown download type: {$type}");
+ }
+ } catch (\Throwable $e) {
+ // Because sometimes files downloaded through the command line are not automatically deleted after a failure.
+ // Here we need to manually delete the file if it is detected to exist.
+ if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) {
+ logger()->warning("Deleting download file: {$filename}");
+ unlink(DOWNLOAD_PATH . '/' . $filename);
+ }
+ throw new DownloaderException("Download failed: {$e->getMessage()}");
+ }
+ }
}
diff --git a/src/SPC/store/FileSystem.php b/src/SPC/store/FileSystem.php
index a40f5f01..f6ce371e 100644
--- a/src/SPC/store/FileSystem.php
+++ b/src/SPC/store/FileSystem.php
@@ -274,7 +274,7 @@ class FileSystem
public static function convertWinPathToMinGW(string $path): string
{
if (preg_match('/^[A-Za-z]:/', $path)) {
- $path = '/' . strtolower(substr($path, 0, 1)) . '/' . str_replace('\\', '/', substr($path, 2));
+ $path = '/' . strtolower($path[0]) . '/' . str_replace('\\', '/', substr($path, 2));
}
return $path;
}
@@ -314,8 +314,13 @@ class FileSystem
$sub_file = self::convertPath($dir . '/' . $v);
if (is_dir($sub_file) && $recursive) {
# 如果是 目录 且 递推 , 则递推添加下级文件
- $list = array_merge($list, self::scanDirFiles($sub_file, $recursive, $relative));
- } elseif (is_file($sub_file) || is_dir($sub_file) && !$recursive && $include_dir) {
+ $sub_list = self::scanDirFiles($sub_file, $recursive, $relative);
+ if (is_array($sub_list)) {
+ foreach ($sub_list as $item) {
+ $list[] = $item;
+ }
+ }
+ } elseif (is_file($sub_file) || (is_dir($sub_file) && !$recursive && $include_dir)) {
# 如果是 文件 或 (是 目录 且 不递推 且 包含目录)
if (is_string($relative) && mb_strpos($sub_file, $relative) === 0) {
$list[] = ltrim(mb_substr($sub_file, mb_strlen($relative)), '/\\');
@@ -440,7 +445,7 @@ class FileSystem
public static function writeFile(string $path, mixed $content, ...$args): bool|int|string
{
$dir = pathinfo(self::convertPath($path), PATHINFO_DIRNAME);
- if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
+ if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {
throw new FileSystemException('Write file failed, cannot create parent directory: ' . $dir);
}
return file_put_contents($path, $content, ...$args);
@@ -579,7 +584,7 @@ class FileSystem
'tar', 'xz', 'txz' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
'tgz', 'gz' => f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1"),
'bz2' => f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1"),
- 'zip' => f_passthru("unzip {$filename} -d {$target}"),
+ 'zip' => self::unzipWithStrip($filename, $target),
default => throw new FileSystemException('unknown archive format: ' . $filename),
};
} elseif (PHP_OS_FAMILY === 'Windows') {
@@ -594,7 +599,7 @@ class FileSystem
match (self::extname($filename)) {
'tar' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
'xz', 'txz', 'gz', 'tgz', 'bz2' => cmd()->execWithResult("\"{$_7z}\" x -so {$filename} | tar -f - -x -C \"{$target}\" --strip-components 1"),
- 'zip' => f_passthru("\"{$_7z}\" x {$filename} -o{$target} -y"),
+ 'zip' => self::unzipWithStrip($filename, $target),
default => throw new FileSystemException("unknown archive format: {$filename}"),
};
}
@@ -630,7 +635,7 @@ class FileSystem
private static function extractWithType(string $source_type, string $filename, string $extract_path): void
{
- logger()->debug('Extracting source [' . $source_type . ']: ' . $filename);
+ logger()->debug("Extracting source [{$source_type}]: {$filename}");
/* @phpstan-ignore-next-line */
match ($source_type) {
SPC_SOURCE_ARCHIVE => self::extractArchive($filename, $extract_path),
@@ -639,4 +644,107 @@ class FileSystem
SPC_SOURCE_LOCAL => symlink(self::convertPath($filename), $extract_path),
};
}
+
+ /**
+ * Move file or directory, handling cross-device scenarios
+ * Uses rename() if possible, falls back to copy+delete for cross-device moves
+ *
+ * @param string $source Source path
+ * @param string $dest Destination path
+ */
+ private static function moveFileOrDir(string $source, string $dest): void
+ {
+ $source = self::convertPath($source);
+ $dest = self::convertPath($dest);
+
+ // Try rename first (fast, atomic)
+ if (@rename($source, $dest)) {
+ return;
+ }
+
+ if (is_dir($source)) {
+ self::copyDir($source, $dest);
+ self::removeDir($source);
+ } else {
+ if (!copy($source, $dest)) {
+ throw new FileSystemException("Failed to copy file from {$source} to {$dest}");
+ }
+ if (!unlink($source)) {
+ throw new FileSystemException("Failed to remove source file: {$source}");
+ }
+ }
+ }
+
+ /**
+ * Unzip file with stripping top-level directory
+ */
+ private static function unzipWithStrip(string $zip_file, string $extract_path): void
+ {
+ $temp_dir = self::convertPath(sys_get_temp_dir() . '/spc_unzip_' . bin2hex(random_bytes(16)));
+ $zip_file = self::convertPath($zip_file);
+ $extract_path = self::convertPath($extract_path);
+
+ // extract to temp dir
+ self::createDir($temp_dir);
+
+ if (PHP_OS_FAMILY === 'Windows') {
+ $mute = defined('DEBUG_MODE') ? '' : ' > NUL';
+ // use php-sdk-binary-tools/bin/7za.exe
+ $_7z = self::convertPath(getenv('PHP_SDK_PATH') . '/bin/7za.exe');
+ f_passthru("\"{$_7z}\" x {$zip_file} -o{$temp_dir} -y{$mute}");
+ } else {
+ $mute = defined('DEBUG_MODE') ? '' : ' > /dev/null';
+ f_passthru("unzip \"{$zip_file}\" -d \"{$temp_dir}\"{$mute}");
+ }
+ // scan first level dirs (relative, not recursive, include dirs)
+ $contents = self::scanDirFiles($temp_dir, false, true, true);
+ if ($contents === false) {
+ throw new FileSystemException('Cannot scan unzip temp dir: ' . $temp_dir);
+ }
+ // if extract path already exists, remove it
+ if (is_dir($extract_path)) {
+ self::removeDir($extract_path);
+ }
+ // if only one dir, move its contents to extract_path
+ $subdir = self::convertPath("{$temp_dir}/{$contents[0]}");
+ if (count($contents) === 1 && is_dir($subdir)) {
+ self::moveFileOrDir($subdir, $extract_path);
+ } else {
+ // else, if it contains only one dir, strip dir and copy other files
+ $dircount = 0;
+ $dir = [];
+ $top_files = [];
+ foreach ($contents as $item) {
+ if (is_dir(self::convertPath("{$temp_dir}/{$item}"))) {
+ ++$dircount;
+ $dir[] = $item;
+ } else {
+ $top_files[] = $item;
+ }
+ }
+ // extract dir contents to extract_path
+ self::createDir($extract_path);
+ // extract move dir
+ if ($dircount === 1) {
+ $sub_contents = self::scanDirFiles("{$temp_dir}/{$dir[0]}", false, true, true);
+ if ($sub_contents === false) {
+ throw new FileSystemException("Cannot scan unzip temp sub-dir: {$dir[0]}");
+ }
+ foreach ($sub_contents as $sub_item) {
+ self::moveFileOrDir(self::convertPath("{$temp_dir}/{$dir[0]}/{$sub_item}"), self::convertPath("{$extract_path}/{$sub_item}"));
+ }
+ } else {
+ foreach ($dir as $item) {
+ self::moveFileOrDir(self::convertPath("{$temp_dir}/{$item}"), self::convertPath("{$extract_path}/{$item}"));
+ }
+ }
+ // move top-level files to extract_path
+ foreach ($top_files as $top_file) {
+ self::moveFileOrDir(self::convertPath("{$temp_dir}/{$top_file}"), self::convertPath("{$extract_path}/{$top_file}"));
+ }
+ }
+
+ // Clean up temp directory
+ self::removeDir($temp_dir);
+ }
}
diff --git a/src/SPC/store/LockFile.php b/src/SPC/store/LockFile.php
index cab5b9da..88ecd6cb 100644
--- a/src/SPC/store/LockFile.php
+++ b/src/SPC/store/LockFile.php
@@ -155,8 +155,8 @@ class LockFile
* @param string $name Source name
* @param array{
* source_type: string,
- * dirname: ?string,
- * filename: ?string,
+ * dirname?: ?string,
+ * filename?: ?string,
* move_path: ?string,
* lock_as: int
* } $data Source data
diff --git a/src/SPC/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php
index c08204f5..c7738e92 100644
--- a/src/SPC/store/SourcePatcher.php
+++ b/src/SPC/store/SourcePatcher.php
@@ -44,7 +44,7 @@ class SourcePatcher
}
foreach ($builder->getLibs() as $lib) {
if ($lib->patchBeforeBuildconf() === true) {
- logger()->info("Library [{$lib->getName()}]patched before buildconf");
+ logger()->info("Library [{$lib->getName()}] patched before buildconf");
}
}
// patch windows php 8.1 bug
diff --git a/src/SPC/store/pkg/CustomPackage.php b/src/SPC/store/pkg/CustomPackage.php
index 093f65d0..eb972d74 100644
--- a/src/SPC/store/pkg/CustomPackage.php
+++ b/src/SPC/store/pkg/CustomPackage.php
@@ -30,10 +30,14 @@ abstract class CustomPackage
/**
* Get the environment variables this package needs to be usable.
- * PATH needs to be appended, rather than replaced.
*/
abstract public static function getEnvironment(): array;
+ /**
+ * Get the PATH required to use this package.
+ */
+ abstract public static function getPath(): ?string;
+
abstract public static function isInstalled(): bool;
/**
diff --git a/src/SPC/store/pkg/GoXcaddy.php b/src/SPC/store/pkg/GoXcaddy.php
index 13041a6f..0c1c6f8c 100644
--- a/src/SPC/store/pkg/GoXcaddy.php
+++ b/src/SPC/store/pkg/GoXcaddy.php
@@ -13,18 +13,7 @@ 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}";
-
+ $folder = PKG_ROOT_PATH . '/go-xcaddy';
return is_dir($folder) && is_file("{$folder}/bin/go") && is_file("{$folder}/bin/xcaddy");
}
@@ -59,7 +48,7 @@ class GoXcaddy extends CustomPackage
'macos' => 'darwin',
default => throw new \InvalidArgumentException('Unsupported OS: ' . $name),
};
- $go_version = '1.24.4';
+ $go_version = '1.25.0';
$config = [
'type' => 'url',
'url' => "https://go.dev/dl/go{$go_version}.{$os}-{$arch}.tar.gz",
@@ -70,15 +59,15 @@ class GoXcaddy extends CustomPackage
public function extract(string $name): void
{
$pkgroot = PKG_ROOT_PATH;
- $go_exec = "{$pkgroot}/{$name}/bin/go";
- $xcaddy_exec = "{$pkgroot}/{$name}/bin/xcaddy";
+ $go_exec = "{$pkgroot}/go-xcaddy/bin/go";
+ $xcaddy_exec = "{$pkgroot}/go-xcaddy/bin/xcaddy";
if (file_exists($go_exec) && file_exists($xcaddy_exec)) {
return;
}
$lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true);
$source_type = $lock[$name]['source_type'];
$filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']);
- $extract = $lock[$name]['move_path'] ?? "{$pkgroot}/{$name}";
+ $extract = $lock[$name]['move_path'] ?? "{$pkgroot}/go-xcaddy";
FileSystem::extractPackage($name, $source_type, $filename, $extract);
@@ -91,9 +80,9 @@ class GoXcaddy extends CustomPackage
// install xcaddy without using musl tools, xcaddy build requires dynamic linking
shell()
->appendEnv([
- 'PATH' => "{$pkgroot}/{$name}/bin:" . $sanitizedPath,
- 'GOROOT' => "{$pkgroot}/{$name}",
- 'GOBIN' => "{$pkgroot}/{$name}/bin",
+ 'PATH' => "{$pkgroot}/go-xcaddy/bin:" . $sanitizedPath,
+ 'GOROOT' => "{$pkgroot}/go-xcaddy",
+ 'GOBIN' => "{$pkgroot}/go-xcaddy/bin",
'GOPATH' => "{$pkgroot}/go",
])
->exec('CC=cc go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest');
@@ -101,22 +90,17 @@ class GoXcaddy extends CustomPackage
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}";
+ $packageName = 'go-xcaddy';
$pkgroot = PKG_ROOT_PATH;
-
return [
- 'PATH' => "{$pkgroot}/{$packageName}/bin",
'GOROOT' => "{$pkgroot}/{$packageName}",
'GOBIN' => "{$pkgroot}/{$packageName}/bin",
'GOPATH' => "{$pkgroot}/go",
];
}
+
+ public static function getPath(): ?string
+ {
+ return PKG_ROOT_PATH . '/go-xcaddy/bin';
+ }
}
diff --git a/src/SPC/store/pkg/Zig.php b/src/SPC/store/pkg/Zig.php
index 3ac90c6d..e9865dbe 100644
--- a/src/SPC/store/pkg/Zig.php
+++ b/src/SPC/store/pkg/Zig.php
@@ -103,7 +103,7 @@ class Zig extends CustomPackage
public function extract(string $name): void
{
$pkgroot = PKG_ROOT_PATH;
- $zig_bin_dir = "{$pkgroot}/{$name}";
+ $zig_bin_dir = "{$pkgroot}/zig";
$files = ['zig', 'zig-cc', 'zig-c++', 'zig-ar', 'zig-ld.lld', 'zig-ranlib', 'zig-objcopy'];
$all_exist = true;
@@ -120,7 +120,7 @@ class Zig extends CustomPackage
$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}";
+ $extract = "{$pkgroot}/zig";
FileSystem::extractPackage($name, $source_type, $filename, $extract);
@@ -129,34 +129,12 @@ class Zig extends CustomPackage
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,
- ];
+ return [];
}
- private static function getPath(): string
+ public 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}";
+ return PKG_ROOT_PATH . '/zig';
}
private function createZigCcScript(string $bin_dir): void
diff --git a/src/SPC/store/scripts/zig-cc.sh b/src/SPC/store/scripts/zig-cc.sh
index 388a3655..6b340bc1 100644
--- a/src/SPC/store/scripts/zig-cc.sh
+++ b/src/SPC/store/scripts/zig-cc.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
-BUILDROOT_ABS="$(realpath "$SCRIPT_DIR/../../buildroot/include" 2>/dev/null || true)"
+BUILDROOT_ABS="$(realpath "$SCRIPT_DIR/../../../buildroot/include" 2>/dev/null || true)"
PARSED_ARGS=()
while [[ $# -gt 0 ]]; do
@@ -19,9 +19,15 @@ while [[ $# -gt 0 ]]; do
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
+ -march=*|-mcpu=*)
OPT_NAME="${1%%=*}"
OPT_VALUE="${1#*=}"
+ # Skip armv8- flags entirely as Zig doesn't support them
+ if [[ "$OPT_VALUE" == armv8-* ]]; then
+ shift
+ continue
+ fi
+ # replace -march=x86-64 with -march=x86_64
OPT_VALUE="${OPT_VALUE//-/_}"
PARSED_ARGS+=("${OPT_NAME}=${OPT_VALUE}")
shift
diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php
index c33e172f..1c0be85f 100644
--- a/src/SPC/store/source/PhpSource.php
+++ b/src/SPC/store/source/PhpSource.php
@@ -16,11 +16,11 @@ class PhpSource extends CustomSourceBase
{
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4';
if ($major === '8.5') {
- Downloader::downloadSource('php-src', ['type' => 'url', 'url' => 'https://downloads.php.net/~edorian/php-8.5.0beta1.tar.xz'], $force);
+ Downloader::downloadSource('php-src', ['type' => 'url', 'url' => 'https://downloads.php.net/~daniels/php-8.5.0RC3.tar.xz'], $force);
} elseif ($major === 'git') {
Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force);
} else {
- Downloader::downloadSource('php-src', self::getLatestPHPInfo($major), $force);
+ Downloader::downloadSource('php-src', $this->getLatestPHPInfo($major), $force);
}
}
@@ -33,7 +33,7 @@ class PhpSource extends CustomSourceBase
// 查找最新的小版本号
$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') ?: 0
), true);
if (!isset($info['version'])) {
throw new DownloaderException("Version {$major_version} not found.");
diff --git a/src/SPC/toolchain/ZigToolchain.php b/src/SPC/toolchain/ZigToolchain.php
index 3a929f2b..bb423db2 100644
--- a/src/SPC/toolchain/ZigToolchain.php
+++ b/src/SPC/toolchain/ZigToolchain.php
@@ -42,10 +42,10 @@ class ZigToolchain implements ToolchainInterface
public function afterInit(): void
{
- if (!is_dir(Zig::getEnvironment()['PATH'])) {
+ if (!Zig::isInstalled()) {
throw new EnvironmentException('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']);
+ GlobalEnvManager::addPathIfNotExists(Zig::getPath());
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') ?: '';
@@ -64,6 +64,11 @@ class ZigToolchain implements ToolchainInterface
$extra_libs = trim($extra_libs . ' -lunwind');
GlobalEnvManager::putenv("SPC_EXTRA_LIBS={$extra_libs}");
}
+ $cflags = getenv('SPC_DEFAULT_C_FLAGS') ?: getenv('CFLAGS') ?: '';
+ $has_avx512 = str_contains($cflags, '-mavx512') || str_contains($cflags, '-march=x86-64-v4');
+ if (!$has_avx512) {
+ GlobalEnvManager::putenv('SPC_EXTRA_PHP_VARS=php_cv_have_avx512=no php_cv_have_avx512vbmi=no');
+ }
}
public function getCompilerInfo(): ?string
diff --git a/src/SPC/util/ConfigValidator.php b/src/SPC/util/ConfigValidator.php
index 58dedea6..445c6242 100644
--- a/src/SPC/util/ConfigValidator.php
+++ b/src/SPC/util/ConfigValidator.php
@@ -11,6 +11,150 @@ use Symfony\Component\Yaml\Yaml;
class ConfigValidator
{
+ /**
+ * Global field type definitions
+ * Maps field names to their expected types and validation rules
+ * Note: This only includes fields used in config files (source.json, lib.json, ext.json, pkg.json, pre-built.json)
+ */
+ private const array FIELD_TYPES = [
+ // String fields
+ 'url' => 'string', // url
+ 'regex' => 'string', // regex pattern
+ 'rev' => 'string', // revision/branch
+ 'repo' => 'string', // repository name
+ 'match' => 'string', // match pattern (aaa*bbb)
+ 'filename' => 'string', // filename
+ 'path' => 'string', // copy path
+ 'extract' => 'string', // copy path (alias of path)
+ 'dirname' => 'string', // directory name for local source
+ 'source' => 'string', // the source name that this item uses
+ 'match-pattern-linux' => 'string', // pre-built match pattern for linux
+ 'match-pattern-macos' => 'string', // pre-built match pattern for macos
+ 'match-pattern-windows' => 'string', // pre-built match pattern for windows
+
+ // Boolean fields
+ 'prefer-stable' => 'bool', // prefer stable releases
+ 'provide-pre-built' => 'bool', // provide pre-built binaries
+ 'notes' => 'bool', // whether to show notes in docs
+ 'cpp-library' => 'bool', // whether this is a C++ library
+ 'cpp-extension' => 'bool', // whether this is a C++ extension
+ 'build-with-php' => 'bool', // whether if this extension can be built to shared with PHP source together
+ 'zend-extension' => 'bool', // whether this is a zend extension
+ 'unix-only' => 'bool', // whether this extension is only for unix-like systems
+
+ // Array fields
+ 'submodules' => 'array', // git submodules list (for git source type)
+ 'lib-depends' => 'list',
+ 'lib-suggests' => 'list',
+ 'ext-depends' => 'list',
+ 'ext-suggests' => 'list',
+ 'static-libs' => 'list',
+ 'pkg-configs' => 'list', // required pkg-config files without suffix (e.g. [libwebp])
+ 'headers' => 'list', // required header files
+ 'bin' => 'list', // required binary files
+ 'frameworks' => 'list', // shared library frameworks (macOS)
+
+ // Object/assoc array fields
+ 'support' => 'object', // extension OS support docs
+ 'extract-files' => 'object', // pkg.json extract files mapping with match pattern
+ 'alt' => 'object|bool', // alternative source/package
+ 'license' => 'object|array', // license information
+ 'target' => 'array', // extension build targets (default: [static], alternate: [shared] or both)
+
+ // Special/mixed fields
+ 'func' => 'callable', // custom download function for custom source/package type
+ 'type' => 'string', // type field (validated separately)
+ ];
+
+ /**
+ * Source/Package download type validation rules
+ * Maps type names to [required_props, optional_props]
+ */
+ private const array SOURCE_TYPE_FIELDS = [
+ 'filelist' => [['url', 'regex'], []],
+ 'git' => [['url', 'rev'], ['path', 'extract', 'submodules']],
+ 'ghtagtar' => [['repo'], ['path', 'extract', 'prefer-stable', 'match']],
+ 'ghtar' => [['repo'], ['path', 'extract', 'prefer-stable', 'match']],
+ 'ghrel' => [['repo', 'match'], ['path', 'extract', 'prefer-stable']],
+ 'url' => [['url'], ['filename', 'path', 'extract']],
+ 'bitbuckettag' => [['repo'], ['path', 'extract']],
+ 'local' => [['dirname'], ['path', 'extract']],
+ 'pie' => [['repo'], ['path']],
+ 'custom' => [[], ['func']],
+ ];
+
+ /**
+ * Source.json specific fields [field_name => required]
+ * Note: 'type' is validated separately in validateSourceTypeConfig
+ * Field types are defined in FIELD_TYPES constant
+ */
+ private const array SOURCE_FIELDS = [
+ 'type' => true, // source type (must be SOURCE_TYPE_FIELDS key)
+ 'provide-pre-built' => false, // whether to provide pre-built binaries
+ 'alt' => false, // alternative source configuration
+ 'license' => false, // license information for source
+ // ... other fields are validated based on source type
+ ];
+
+ /**
+ * Lib.json specific fields [field_name => required]
+ * Field types are defined in FIELD_TYPES constant
+ */
+ private const array LIB_FIELDS = [
+ 'type' => false, // lib type (lib/package/target/root)
+ 'source' => false, // the source name that this lib uses
+ 'lib-depends' => false, // required libraries
+ 'lib-suggests' => false, // suggested libraries
+ 'static-libs' => false, // Generated static libraries
+ 'pkg-configs' => false, // Generated pkg-config files
+ 'cpp-library' => false, // whether this is a C++ library
+ 'headers' => false, // Generated header files
+ 'bin' => false, // Generated binary files
+ 'frameworks' => false, // Used shared library frameworks (macOS)
+ ];
+
+ /**
+ * Ext.json specific fields [field_name => required]
+ * Field types are defined in FIELD_TYPES constant
+ */
+ private const array EXT_FIELDS = [
+ 'type' => true, // extension type (builtin/external/addon/wip)
+ 'source' => false, // the source name that this extension uses
+ 'support' => false, // extension OS support docs
+ 'notes' => false, // whether to show notes in docs
+ 'cpp-extension' => false, // whether this is a C++ extension
+ 'build-with-php' => false, // whether if this extension can be built to shared with PHP source together
+ 'target' => false, // extension build targets (default: [static], alternate: [shared] or both)
+ 'lib-depends' => false,
+ 'lib-suggests' => false,
+ 'ext-depends' => false,
+ 'ext-suggests' => false,
+ 'frameworks' => false,
+ 'zend-extension' => false, // whether this is a zend extension
+ 'unix-only' => false, // whether this extension is only for unix-like systems
+ ];
+
+ /**
+ * Pkg.json specific fields [field_name => required]
+ * Field types are defined in FIELD_TYPES constant
+ */
+ private const array PKG_FIELDS = [
+ 'type' => true, // package type (same as source type)
+ 'extract-files' => false, // files to extract mapping (source pattern => target path)
+ ];
+
+ /**
+ * Pre-built.json specific fields [field_name => required]
+ * Field types are defined in FIELD_TYPES constant
+ */
+ private const array PRE_BUILT_FIELDS = [
+ 'repo' => true, // repository name for pre-built binaries
+ 'prefer-stable' => false, // prefer stable releases
+ 'match-pattern-linux' => false, // pre-built match pattern for linux
+ 'match-pattern-macos' => false, // pre-built match pattern for macos
+ 'match-pattern-windows' => false, // pre-built match pattern for windows
+ ];
+
/**
* Validate source.json
*
@@ -22,33 +166,20 @@ class ConfigValidator
// Validate basic source type configuration
self::validateSourceTypeConfig($src, $name, 'source');
- // Check source-specific fields
+ // Validate all source-specific fields using unified method
+ self::validateConfigFields($src, $name, 'source', self::SOURCE_FIELDS);
+
+ // Check for unknown fields
+ self::validateAllowedFields($src, $name, 'source', self::SOURCE_FIELDS);
+
// check if alt is valid
- if (isset($src['alt'])) {
- if (!is_assoc_array($src['alt']) && !is_bool($src['alt'])) {
- throw new ValidationException("source {$name} alt must be object or boolean");
- }
- if (is_assoc_array($src['alt'])) {
- // validate alt source recursively
- self::validateSource([$name . '_alt' => $src['alt']]);
- }
- }
-
- // check if provide-pre-built is boolean
- if (isset($src['provide-pre-built']) && !is_bool($src['provide-pre-built'])) {
- throw new ValidationException("source {$name} provide-pre-built must be boolean");
- }
-
- // check if prefer-stable is boolean
- if (isset($src['prefer-stable']) && !is_bool($src['prefer-stable'])) {
- throw new ValidationException("source {$name} prefer-stable must be boolean");
+ if (isset($src['alt']) && is_assoc_array($src['alt'])) {
+ // validate alt source recursively
+ self::validateSource([$name . '_alt' => $src['alt']]);
}
// check if license is valid
if (isset($src['license'])) {
- if (!is_array($src['license'])) {
- throw new ValidationException("source {$name} license must be an object or array");
- }
if (is_assoc_array($src['license'])) {
self::checkSingleLicense($src['license'], $name);
} elseif (is_list_array($src['license'])) {
@@ -58,8 +189,6 @@ class ConfigValidator
}
self::checkSingleLicense($license, $name);
}
- } else {
- throw new ValidationException("source {$name} license must be an object or array");
}
}
}
@@ -71,9 +200,8 @@ class ConfigValidator
if (!is_array($data)) {
throw new ValidationException('lib.json is broken');
}
- // check each lib
+
foreach ($data as $name => $lib) {
- // check if lib is an assoc array
if (!is_assoc_array($lib)) {
throw new ValidationException("lib {$name} is not an object");
}
@@ -89,36 +217,22 @@ class ConfigValidator
if (isset($lib['source']) && !empty($source_data) && !isset($source_data[$lib['source']])) {
throw new ValidationException("lib {$name} assigns an invalid source: {$lib['source']}");
}
- // check if source is string
- if (isset($lib['source']) && !is_string($lib['source'])) {
- throw new ValidationException("lib {$name} source must be string");
- }
- // check if [lib-depends|lib-suggests|static-libs|headers|bin][-windows|-unix|-macos|-linux] are valid list array
+
+ // Validate basic fields using unified method
+ self::validateConfigFields($lib, $name, 'lib', self::LIB_FIELDS);
+
+ // Validate list array fields with suffixes
$suffixes = ['', '-windows', '-unix', '-macos', '-linux'];
- foreach ($suffixes as $suffix) {
- if (isset($lib['lib-depends' . $suffix]) && !is_list_array($lib['lib-depends' . $suffix])) {
- throw new ValidationException("lib {$name} lib-depends must be a list");
- }
- if (isset($lib['lib-suggests' . $suffix]) && !is_list_array($lib['lib-suggests' . $suffix])) {
- throw new ValidationException("lib {$name} lib-suggests must be a list");
- }
- if (isset($lib['static-libs' . $suffix]) && !is_list_array($lib['static-libs' . $suffix])) {
- throw new ValidationException("lib {$name} static-libs must be a list");
- }
- if (isset($lib['pkg-configs' . $suffix]) && !is_list_array($lib['pkg-configs' . $suffix])) {
- throw new ValidationException("lib {$name} pkg-configs must be a list");
- }
- if (isset($lib['headers' . $suffix]) && !is_list_array($lib['headers' . $suffix])) {
- throw new ValidationException("lib {$name} headers must be a list");
- }
- if (isset($lib['bin' . $suffix]) && !is_list_array($lib['bin' . $suffix])) {
- throw new ValidationException("lib {$name} bin must be a list");
- }
- }
- // check if frameworks is a list array
- if (isset($lib['frameworks']) && !is_list_array($lib['frameworks'])) {
- throw new ValidationException("lib {$name} frameworks must be a list");
+ $fields = ['lib-depends', 'lib-suggests', 'static-libs', 'pkg-configs', 'headers', 'bin'];
+ self::validateListArrayFields($lib, $name, 'lib', $fields, $suffixes);
+
+ // Validate frameworks (special case without suffix)
+ if (isset($lib['frameworks'])) {
+ self::validateFieldType('frameworks', $lib['frameworks'], $name, 'lib');
}
+
+ // Check for unknown fields
+ self::validateAllowedFields($lib, $name, 'lib', self::LIB_FIELDS);
}
}
@@ -127,61 +241,34 @@ class ConfigValidator
if (!is_array($data)) {
throw new ValidationException('ext.json is broken');
}
- // check each extension
+
foreach ($data as $name => $ext) {
- // check if ext is an assoc array
if (!is_assoc_array($ext)) {
throw new ValidationException("ext {$name} is not an object");
}
- // check if ext has valid type
+
if (!in_array($ext['type'] ?? '', ['builtin', 'external', 'addon', 'wip'])) {
throw new ValidationException("ext {$name} type is invalid");
}
- // check if external ext has source
+
+ // Check source field requirement
if (($ext['type'] ?? '') === 'external' && !isset($ext['source'])) {
throw new ValidationException("ext {$name} does not assign any source");
}
- // check if source is string
- if (isset($ext['source']) && !is_string($ext['source'])) {
- throw new ValidationException("ext {$name} source must be string");
- }
- // check if support is valid
- if (isset($ext['support']) && !is_assoc_array($ext['support'])) {
- throw new ValidationException("ext {$name} support must be an object");
- }
- // check if notes is boolean
- if (isset($ext['notes']) && !is_bool($ext['notes'])) {
- throw new ValidationException("ext {$name} notes must be boolean");
- }
- // check if [lib-depends|lib-suggests|ext-depends][-windows|-unix|-macos|-linux] are valid list array
+
+ // Validate basic fields using unified method
+ self::validateConfigFields($ext, $name, 'ext', self::EXT_FIELDS);
+
+ // Validate list array fields with suffixes
$suffixes = ['', '-windows', '-unix', '-macos', '-linux'];
- foreach ($suffixes as $suffix) {
- if (isset($ext['lib-depends' . $suffix]) && !is_list_array($ext['lib-depends' . $suffix])) {
- throw new ValidationException("ext {$name} lib-depends must be a list");
- }
- if (isset($ext['lib-suggests' . $suffix]) && !is_list_array($ext['lib-suggests' . $suffix])) {
- throw new ValidationException("ext {$name} lib-suggests must be a list");
- }
- if (isset($ext['ext-depends' . $suffix]) && !is_list_array($ext['ext-depends' . $suffix])) {
- throw new ValidationException("ext {$name} ext-depends must be a list");
- }
- }
- // check if arg-type is valid
- if (isset($ext['arg-type'])) {
- $valid_arg_types = ['enable', 'with', 'with-path', 'custom', 'none', 'enable-path'];
- if (!in_array($ext['arg-type'], $valid_arg_types)) {
- throw new ValidationException("ext {$name} arg-type is invalid");
- }
- }
- // check if arg-type with suffix is valid
- foreach ($suffixes as $suffix) {
- if (isset($ext['arg-type' . $suffix])) {
- $valid_arg_types = ['enable', 'with', 'with-path', 'custom', 'none', 'enable-path'];
- if (!in_array($ext['arg-type' . $suffix], $valid_arg_types)) {
- throw new ValidationException("ext {$name} arg-type{$suffix} is invalid");
- }
- }
- }
+ $fields = ['lib-depends', 'lib-suggests', 'ext-depends', 'ext-suggests'];
+ self::validateListArrayFields($ext, $name, 'ext', $fields, $suffixes);
+
+ // Validate arg-type fields
+ self::validateArgTypeFields($ext, $name, $suffixes);
+
+ // Check for unknown fields
+ self::validateAllowedFields($ext, $name, 'ext', self::EXT_FIELDS);
}
}
@@ -200,12 +287,11 @@ class ConfigValidator
// Validate basic source type configuration (reuse from source validation)
self::validateSourceTypeConfig($pkg, $name, 'pkg');
- // Check pkg-specific fields
- // check if extract-files is valid
+ // Validate all pkg-specific fields using unified method
+ self::validateConfigFields($pkg, $name, 'pkg', self::PKG_FIELDS);
+
+ // Validate extract-files content (object validation is done by validateFieldType)
if (isset($pkg['extract-files'])) {
- if (!is_assoc_array($pkg['extract-files'])) {
- throw new ValidationException("pkg {$name} extract-files must be an object");
- }
// check each extract file mapping
foreach ($pkg['extract-files'] as $source => $target) {
if (!is_string($source) || !is_string($target)) {
@@ -213,6 +299,9 @@ class ConfigValidator
}
}
}
+
+ // Check for unknown fields
+ self::validateAllowedFields($pkg, $name, 'pkg', self::PKG_FIELDS);
}
}
@@ -227,18 +316,11 @@ class ConfigValidator
throw new ValidationException('pre-built.json is broken');
}
- // Check required fields
- if (!isset($data['repo'])) {
- throw new ValidationException('pre-built.json must have [repo] field');
- }
- if (!is_string($data['repo'])) {
- throw new ValidationException('pre-built.json [repo] must be string');
- }
+ // Validate all fields using unified method
+ self::validateConfigFields($data, 'pre-built', 'pre-built', self::PRE_BUILT_FIELDS);
- // Check optional prefer-stable field
- if (isset($data['prefer-stable']) && !is_bool($data['prefer-stable'])) {
- throw new ValidationException('pre-built.json [prefer-stable] must be boolean');
- }
+ // Check for unknown fields
+ self::validateAllowedFields($data, 'pre-built', 'pre-built', self::PRE_BUILT_FIELDS);
// Check match pattern fields (at least one must exist)
$pattern_fields = ['match-pattern-linux', 'match-pattern-macos', 'match-pattern-windows'];
@@ -247,9 +329,6 @@ class ConfigValidator
foreach ($pattern_fields as $field) {
if (isset($data[$field])) {
$has_pattern = true;
- if (!is_string($data[$field])) {
- throw new ValidationException("pre-built.json [{$field}] must be string");
- }
// Validate pattern contains required placeholders
if (!str_contains($data[$field], '{name}')) {
throw new ValidationException("pre-built.json [{$field}] must contain {name} placeholder");
@@ -403,6 +482,52 @@ class ConfigValidator
return $craft;
}
+ /**
+ * Validate a field based on its global type definition
+ *
+ * @param string $field Field name
+ * @param mixed $value Field value
+ * @param string $name Item name (for error messages)
+ * @param string $type Item type (for error messages)
+ * @return bool Returns true if validation passes
+ */
+ private static function validateFieldType(string $field, mixed $value, string $name, string $type): bool
+ {
+ // Check if field exists in FIELD_TYPES
+ if (!isset(self::FIELD_TYPES[$field])) {
+ // Try to strip suffix and check base field name
+ $suffixes = ['-windows', '-unix', '-macos', '-linux'];
+ $base_field = $field;
+ foreach ($suffixes as $suffix) {
+ if (str_ends_with($field, $suffix)) {
+ $base_field = substr($field, 0, -strlen($suffix));
+ break;
+ }
+ }
+
+ if (!isset(self::FIELD_TYPES[$base_field])) {
+ // Unknown field is not allowed - strict validation
+ throw new ValidationException("{$type} {$name} has unknown field [{$field}]");
+ }
+
+ // Use base field type for validation
+ $expected_type = self::FIELD_TYPES[$base_field];
+ } else {
+ $expected_type = self::FIELD_TYPES[$field];
+ }
+
+ return match ($expected_type) {
+ 'string' => is_string($value) ?: throw new ValidationException("{$type} {$name} [{$field}] must be string"),
+ 'bool' => is_bool($value) ?: throw new ValidationException("{$type} {$name} [{$field}] must be boolean"),
+ 'array' => is_array($value) ?: throw new ValidationException("{$type} {$name} [{$field}] must be array"),
+ 'list' => is_list_array($value) ?: throw new ValidationException("{$type} {$name} [{$field}] must be a list"),
+ 'object' => is_assoc_array($value) ?: throw new ValidationException("{$type} {$name} [{$field}] must be an object"),
+ 'object|bool' => (is_assoc_array($value) || is_bool($value)) ?: throw new ValidationException("{$type} {$name} [{$field}] must be object or boolean"),
+ 'object|array' => is_array($value) ?: throw new ValidationException("{$type} {$name} [{$field}] must be an object or array"),
+ 'callable' => true, // Skip validation for callable
+ };
+ }
+
private static function checkSingleLicense(array $license, string $name): void
{
if (!is_assoc_array($license)) {
@@ -414,9 +539,6 @@ class ConfigValidator
if (!in_array($license['type'], ['file', 'text'])) {
throw new ValidationException("source {$name} license type is invalid");
}
- if (!in_array($license['type'], ['file', 'text'])) {
- throw new ValidationException("source {$name} license type is invalid");
- }
if ($license['type'] === 'file' && !isset($license['path'])) {
throw new ValidationException("source {$name} license file must have path");
}
@@ -440,68 +562,127 @@ class ConfigValidator
if (!is_string($item['type'])) {
throw new ValidationException("{$config_type} {$name} type prop must be string");
}
- if (!in_array($item['type'], ['filelist', 'git', 'ghtagtar', 'ghtar', 'ghrel', 'url', 'custom'])) {
+
+ if (!isset(self::SOURCE_TYPE_FIELDS[$item['type']])) {
throw new ValidationException("{$config_type} {$name} type [{$item['type']}] is invalid");
}
- // Validate type-specific requirements
- switch ($item['type']) {
- case 'filelist':
- if (!isset($item['url'], $item['regex'])) {
- throw new ValidationException("{$config_type} {$name} needs [url] and [regex] props");
+ [$required, $optional] = self::SOURCE_TYPE_FIELDS[$item['type']];
+
+ // Check required fields exist
+ foreach ($required as $prop) {
+ if (!isset($item[$prop])) {
+ $props = implode('] and [', $required);
+ throw new ValidationException("{$config_type} {$name} needs [{$props}] props");
+ }
+ }
+
+ // Validate field types using global field type definitions
+ foreach (array_merge($required, $optional) as $prop) {
+ if (isset($item[$prop])) {
+ self::validateFieldType($prop, $item[$prop], $name, $config_type);
+ }
+ }
+ }
+
+ /**
+ * Validate that fields with suffixes are list arrays
+ */
+ private static function validateListArrayFields(array $item, string $name, string $type, array $fields, array $suffixes): void
+ {
+ foreach ($fields as $field) {
+ foreach ($suffixes as $suffix) {
+ $key = $field . $suffix;
+ if (isset($item[$key])) {
+ self::validateFieldType($key, $item[$key], $name, $type);
}
- if (!is_string($item['url']) || !is_string($item['regex'])) {
- throw new ValidationException("{$config_type} {$name} [url] and [regex] must be string");
+ }
+ }
+ }
+
+ /**
+ * Validate arg-type fields with suffixes
+ */
+ private static function validateArgTypeFields(array $item, string $name, array $suffixes): void
+ {
+ $valid_arg_types = ['enable', 'with', 'with-path', 'custom', 'none', 'enable-path'];
+
+ foreach (array_merge([''], $suffixes) as $suffix) {
+ $key = 'arg-type' . $suffix;
+ if (isset($item[$key]) && !in_array($item[$key], $valid_arg_types)) {
+ throw new ValidationException("ext {$name} {$key} is invalid");
+ }
+ }
+ }
+
+ /**
+ * Unified method to validate config fields based on field definitions
+ *
+ * @param array $item Item data to validate
+ * @param string $name Item name for error messages
+ * @param string $type Config type (source, lib, ext, pkg, pre-built)
+ * @param array $field_definitions Field definitions [field_name => required (bool)]
+ */
+ private static function validateConfigFields(array $item, string $name, string $type, array $field_definitions): void
+ {
+ foreach ($field_definitions as $field => $required) {
+ if ($required && !isset($item[$field])) {
+ throw new ValidationException("{$type} {$name} must have [{$field}] field");
+ }
+
+ if (isset($item[$field])) {
+ self::validateFieldType($field, $item[$field], $name, $type);
+ }
+ }
+ }
+
+ /**
+ * Validate that item only contains allowed fields
+ * This method checks for unknown fields based on the config type
+ *
+ * @param array $item Item data to validate
+ * @param string $name Item name for error messages
+ * @param string $type Config type (source, lib, ext, pkg, pre-built)
+ * @param array $field_definitions Field definitions [field_name => required (bool)]
+ */
+ private static function validateAllowedFields(array $item, string $name, string $type, array $field_definitions): void
+ {
+ // For source and pkg types, we need to check SOURCE_TYPE_FIELDS as well
+ $allowed_fields = array_keys($field_definitions);
+
+ // For source/pkg, add allowed fields from SOURCE_TYPE_FIELDS based on the type
+ if (in_array($type, ['source', 'pkg']) && isset($item['type'], self::SOURCE_TYPE_FIELDS[$item['type']])) {
+ [$required, $optional] = self::SOURCE_TYPE_FIELDS[$item['type']];
+ $allowed_fields = array_merge($allowed_fields, $required, $optional);
+ }
+
+ // For lib and ext types, add fields with suffixes
+ if (in_array($type, ['lib', 'ext'])) {
+ $suffixes = ['-windows', '-unix', '-macos', '-linux'];
+ $base_fields = ['lib-depends', 'lib-suggests', 'static-libs', 'pkg-configs', 'headers', 'bin'];
+ if ($type === 'ext') {
+ $base_fields = ['lib-depends', 'lib-suggests', 'ext-depends', 'ext-suggests'];
+ // Add arg-type fields
+ foreach (array_merge([''], $suffixes) as $suffix) {
+ $allowed_fields[] = 'arg-type' . $suffix;
}
- break;
- case 'git':
- if (!isset($item['url'], $item['rev'])) {
- throw new ValidationException("{$config_type} {$name} needs [url] and [rev] props");
+ }
+ foreach ($base_fields as $field) {
+ foreach ($suffixes as $suffix) {
+ $allowed_fields[] = $field . $suffix;
}
- if (!is_string($item['url']) || !is_string($item['rev'])) {
- throw new ValidationException("{$config_type} {$name} [url] and [rev] must be string");
- }
- if (isset($item['path']) && !is_string($item['path'])) {
- throw new ValidationException("{$config_type} {$name} [path] must be string");
- }
- break;
- case 'ghtagtar':
- case 'ghtar':
- if (!isset($item['repo'])) {
- throw new ValidationException("{$config_type} {$name} needs [repo] prop");
- }
- if (!is_string($item['repo'])) {
- throw new ValidationException("{$config_type} {$name} [repo] must be string");
- }
- if (isset($item['path']) && !is_string($item['path'])) {
- throw new ValidationException("{$config_type} {$name} [path] must be string");
- }
- break;
- case 'ghrel':
- if (!isset($item['repo'], $item['match'])) {
- throw new ValidationException("{$config_type} {$name} needs [repo] and [match] props");
- }
- if (!is_string($item['repo']) || !is_string($item['match'])) {
- throw new ValidationException("{$config_type} {$name} [repo] and [match] must be string");
- }
- break;
- case 'url':
- if (!isset($item['url'])) {
- throw new ValidationException("{$config_type} {$name} needs [url] prop");
- }
- if (!is_string($item['url'])) {
- throw new ValidationException("{$config_type} {$name} [url] must be string");
- }
- if (isset($item['filename']) && !is_string($item['filename'])) {
- throw new ValidationException("{$config_type} {$name} [filename] must be string");
- }
- if (isset($item['path']) && !is_string($item['path'])) {
- throw new ValidationException("{$config_type} {$name} [path] must be string");
- }
- break;
- case 'custom':
- // custom type has no specific requirements
- break;
+ }
+ // frameworks is lib-only
+ if ($type === 'lib') {
+ $allowed_fields[] = 'frameworks';
+ }
+ }
+
+ // Check each field in item
+ foreach (array_keys($item) as $field) {
+ if (!in_array($field, $allowed_fields)) {
+ throw new ValidationException("{$type} {$name} has unknown field [{$field}]");
+ }
}
}
}
diff --git a/src/SPC/util/GlobalEnvManager.php b/src/SPC/util/GlobalEnvManager.php
index 9f74d2df..4ce3384d 100644
--- a/src/SPC/util/GlobalEnvManager.php
+++ b/src/SPC/util/GlobalEnvManager.php
@@ -40,7 +40,12 @@ class GlobalEnvManager
if (is_unix()) {
self::addPathIfNotExists(BUILD_BIN_PATH);
self::addPathIfNotExists(PKG_ROOT_PATH . '/bin');
- self::putenv('PKG_CONFIG_PATH=' . BUILD_LIB_PATH . '/pkgconfig');
+ $pkgConfigPath = getenv('PKG_CONFIG_PATH');
+ if ($pkgConfigPath !== false) {
+ self::putenv('PKG_CONFIG_PATH=' . BUILD_LIB_PATH . "/pkgconfig:{$pkgConfigPath}");
+ } else {
+ self::putenv('PKG_CONFIG_PATH=' . BUILD_LIB_PATH . '/pkgconfig');
+ }
}
$ini = self::readIniFile();
diff --git a/src/SPC/util/SPCConfigUtil.php b/src/SPC/util/SPCConfigUtil.php
index d19cc858..f321ca10 100644
--- a/src/SPC/util/SPCConfigUtil.php
+++ b/src/SPC/util/SPCConfigUtil.php
@@ -6,6 +6,7 @@ namespace SPC\util;
use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider;
+use SPC\builder\Extension;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use Symfony\Component\Console\Input\ArgvInput;
@@ -87,7 +88,7 @@ class SPCConfigUtil
if (SPCTarget::getTargetOS() === 'Darwin') {
$libs .= " {$this->getFrameworksString($extensions)}";
}
- if ($this->builder->hasCpp()) {
+ if ($this->hasCpp($extensions, $libraries)) {
$libcpp = SPCTarget::getTargetOS() === 'Darwin' ? '-lc++' : '-lstdc++';
$libs = str_replace($libcpp, '', $libs) . " {$libcpp}";
}
@@ -123,6 +124,27 @@ class SPCConfigUtil
];
}
+ private function hasCpp(array $extensions, array $libraries): bool
+ {
+ // judge cpp-extension
+ $builderExtNames = array_keys($this->builder->getExts(false));
+ $exts = array_unique([...$builderExtNames, ...$extensions]);
+
+ foreach ($exts as $ext) {
+ if (Config::getExt($ext, 'cpp-extension', false) === true) {
+ return true;
+ }
+ }
+ $builderLibNames = array_keys($this->builder->getLibs());
+ $libs = array_unique([...$builderLibNames, ...$libraries]);
+ foreach ($libs as $lib) {
+ if (Config::getLib($lib, 'cpp-library', false) === true) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private function getIncludesString(array $libraries): string
{
$base = BUILD_INCLUDE_PATH;
diff --git a/src/SPC/util/executor/UnixAutoconfExecutor.php b/src/SPC/util/executor/UnixAutoconfExecutor.php
index 075d7fd3..f196cb1c 100644
--- a/src/SPC/util/executor/UnixAutoconfExecutor.php
+++ b/src/SPC/util/executor/UnixAutoconfExecutor.php
@@ -50,18 +50,22 @@ class UnixAutoconfExecutor extends Executor
* @param bool $with_clean Whether to clean before building
* @param array $after_env_vars Environment variables postfix
*/
- public function make(string $target = '', false|string $with_install = 'install', bool $with_clean = true, array $after_env_vars = []): static
+ public function make(string $target = '', false|string $with_install = 'install', bool $with_clean = true, array $after_env_vars = [], ?string $dir = null): static
{
- return $this->seekLogFileOnException(function () use ($target, $with_install, $with_clean, $after_env_vars) {
+ return $this->seekLogFileOnException(function () use ($target, $with_install, $with_clean, $after_env_vars, $dir) {
+ $shell = $this->shell;
+ if ($dir) {
+ $shell = $shell->cd($dir);
+ }
if ($with_clean) {
- $this->shell->exec('make clean');
+ $shell->exec('make clean');
}
$after_env_vars_str = $after_env_vars !== [] ? shell()->setEnv($after_env_vars)->getEnvString() : '';
- $this->shell->exec("make -j{$this->library->getBuilder()->concurrency} {$target} {$after_env_vars_str}");
+ $shell->exec("make -j{$this->library->getBuilder()->concurrency} {$target} {$after_env_vars_str}");
if ($with_install !== false) {
- $this->shell->exec("make {$with_install}");
+ $shell->exec("make {$with_install}");
}
- return $this->shell;
+ return $shell;
});
}
diff --git a/src/SPC/util/executor/UnixCMakeExecutor.php b/src/SPC/util/executor/UnixCMakeExecutor.php
index 3e29addf..eceab901 100644
--- a/src/SPC/util/executor/UnixCMakeExecutor.php
+++ b/src/SPC/util/executor/UnixCMakeExecutor.php
@@ -205,7 +205,7 @@ SET(CMAKE_INSTALL_PREFIX "{$root}")
SET(CMAKE_INSTALL_LIBDIR "lib")
set(PKG_CONFIG_EXECUTABLE "{$pkgConfigExecutable}")
-list(APPEND PKG_CONFIG_EXECUTABLE "--static")
+set(PKG_CONFIG_ARGN "--static" CACHE STRING "Extra arguments for pkg-config" FORCE)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
diff --git a/src/SPC/util/shell/UnixShell.php b/src/SPC/util/shell/UnixShell.php
index 75a0be3d..2fdc1b07 100644
--- a/src/SPC/util/shell/UnixShell.php
+++ b/src/SPC/util/shell/UnixShell.php
@@ -8,6 +8,7 @@ use SPC\builder\freebsd\library\BSDLibraryBase;
use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\builder\macos\library\MacOSLibraryBase;
use SPC\exception\SPCInternalException;
+use SPC\util\SPCTarget;
use ZM\Logger\ConsoleColor;
/**
@@ -28,6 +29,7 @@ class UnixShell extends Shell
public function exec(string $cmd): static
{
+ $cmd = clean_spaces($cmd);
/* @phpstan-ignore-next-line */
logger()->info(ConsoleColor::yellow('[EXEC] ') . ConsoleColor::green($cmd));
$original_command = $cmd;
@@ -48,7 +50,7 @@ class UnixShell extends Shell
'CFLAGS' => $library->getLibExtraCFlags(),
'CXXFLAGS' => $library->getLibExtraCXXFlags(),
'LDFLAGS' => $library->getLibExtraLdFlags(),
- 'LIBS' => $library->getLibExtraLibs(),
+ 'LIBS' => $library->getLibExtraLibs() . SPCTarget::getRuntimeLibs(),
]);
return $this;
}
diff --git a/src/globals/ext-tests/gettext.php b/src/globals/ext-tests/gettext.php
index f34f487a..b5bc901e 100644
--- a/src/globals/ext-tests/gettext.php
+++ b/src/globals/ext-tests/gettext.php
@@ -4,20 +4,40 @@ declare(strict_types=1);
assert(function_exists('gettext'));
assert(function_exists('bindtextdomain'));
+assert(function_exists('bind_textdomain_codeset'));
assert(function_exists('textdomain'));
-if (!is_dir('locale/en_US/LC_MESSAGES/')) {
- mkdir('locale/en_US/LC_MESSAGES/', 0755, true);
+foreach (['en_US', 'en_GB'] as $lc) {
+ $dir = "locale/{$lc}/LC_MESSAGES";
+ if (!is_dir($dir)) {
+ mkdir($dir, 0755, true);
+ }
+ $mo = '3hIElQAAAAACAAAAHAAAACwAAAAFAAAAPAAAAAAAAABQAAAABgAAAFEAAAAXAQAAWAAAAAcAAABwAQAAAQAAAAAAAAAAAAAAAgAAAAAAAAAA56S65L6LAFByb2plY3QtSWQtVmVyc2lvbjogUEFDS0FHRSBWRVJTSU9OClJlcG9ydC1Nc2dpZC1CdWdzLVRvOiAKUE8tUmV2aXNpb24tRGF0ZTogWUVBUi1NTy1EQSBITzpNSytaT05FCkxhc3QtVHJhbnNsYXRvcjogRlVMTCBOQU1FIDxFTUFJTEBBRERSRVNTPgpMYW5ndWFnZS1UZWFtOiBMQU5HVUFHRSA8TExAbGkub3JnPgpMYW5ndWFnZTogCk1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD1VVEYtOApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA4Yml0CgBFeGFtcGxlAA==';
+ $path = "{$dir}/test.mo";
+ if (!file_exists($path)) {
+ file_put_contents($path, base64_decode($mo));
+ }
}
-if (!file_exists('locale/en_US/LC_MESSAGES/test.mo')) {
- $mo = '3hIElQAAAAACAAAAHAAAACwAAAAFAAAAPAAAAAAAAABQAAAABgAAAFEAAAAXAQAAWAAAAAcAAABwAQAAAQAAAAAAAAAAAAAAAgAAAAAAAAAA56S65L6LAFByb2plY3QtSWQtVmVyc2lvbjogUEFDS0FHRSBWRVJTSU9OClJlcG9ydC1Nc2dpZC1CdWdzLVRvOiAKUE8tUmV2aXNpb24tRGF0ZTogWUVBUi1NTy1EQSBITzpNSStaT05FCkxhc3QtVHJhbnNsYXRvcjogRlVMTCBOQU1FIDxFTUFJTEBBRERSRVNTPgpMYW5ndWFnZS1UZWFtOiBMQU5HVUFHRSA8TExAbGkub3JnPgpMYW5ndWFnZTogCk1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD1VVEYtOApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA4Yml0CgBFeGFtcGxlAA==';
- file_put_contents('locale/en_US/LC_MESSAGES/test.mo', base64_decode($mo));
-}
-putenv('LANG=en_US');
-assert(setlocale(LC_ALL, 'en_US.utf-8') === 'en_US.utf-8');
+
+// Probe for an available English locale
+$candidates = [
+ 'en_US.UTF-8', 'en_US.utf8', 'en_US.utf-8', 'en_US',
+ 'en_GB.UTF-8', 'en_GB.utf8', 'en_GB.utf-8', 'en_GB',
+ 'English_United States.65001', 'English_United States.1252',
+ 'English_United Kingdom.65001', 'English_United Kingdom.1252',
+];
+
+$locale = setlocale(LC_ALL, $candidates);
+assert($locale !== false);
+
+putenv('LC_ALL=' . $locale);
+putenv('LANG=' . $locale);
+putenv('LANGUAGE=' . (stripos($locale, 'US') !== false ? 'en_US:en_GB' : 'en_GB:en_US'));
$domain = 'test';
bindtextdomain($domain, 'locale/');
+bind_textdomain_codeset($domain, 'UTF-8');
textdomain($domain);
-assert(gettext(json_decode('"\u793a\u4f8b"', true)) === 'Example');
+$src = json_decode('"\u793a\u4f8b"', true);
+assert(gettext($src) === 'Example');
diff --git a/src/globals/functions.php b/src/globals/functions.php
index 71127960..c8c6a8d0 100644
--- a/src/globals/functions.php
+++ b/src/globals/functions.php
@@ -245,6 +245,23 @@ function clean_spaces(string $string): string
return trim(preg_replace('/\s+/', ' ', $string));
}
+/**
+ * Deduplicate flags in a string. Only the last occurence of each flag will be kept.
+ * E.g. `-lintl -lstdc++ -lphp -lstdc++` becomes `-lintl -lphp -lstdc++`
+ *
+ * @param string $flags the string containing flags to deduplicate
+ * @return string the deduplicated string with no duplicate flags
+ */
+function deduplicate_flags(string $flags): string
+{
+ $tokens = preg_split('/\s+/', trim($flags));
+
+ // Reverse, unique, reverse back - keeps last occurrence of duplicates
+ $deduplicated = array_reverse(array_unique(array_reverse($tokens)));
+
+ return implode(' ', $deduplicated);
+}
+
/**
* Register a callback function to handle keyboard interrupts (Ctrl+C).
*
@@ -283,3 +300,20 @@ function strip_ansi_colors(string $text): string
// Including color codes, cursor control, clear screen and other control sequences
return preg_replace('/\e\[[0-9;]*[a-zA-Z]/', '', $text);
}
+
+/**
+ * Convert to a real path for display purposes, used in docker volumes.
+ */
+function get_display_path(string $path): string
+{
+ $deploy_root = getenv('SPC_FIX_DEPLOY_ROOT');
+ if ($deploy_root === false) {
+ return $path;
+ }
+ $cwd = WORKING_DIR;
+ // replace build root with deploy root, only if path starts with build root
+ if (str_starts_with($path, $cwd)) {
+ return $deploy_root . substr($path, strlen($cwd));
+ }
+ throw new WrongUsageException("Cannot convert path: {$path}");
+}
diff --git a/src/globals/patch/musl_static_readline.patch b/src/globals/patch/musl_static_readline.patch
new file mode 100644
index 00000000..19611621
--- /dev/null
+++ b/src/globals/patch/musl_static_readline.patch
@@ -0,0 +1,26 @@
+diff --git a/ext/readline/readline_cli.c b/ext/readline/readline_cli.c
+index 31212999..d80a21c3 100644
+--- a/ext/readline/readline_cli.c
++++ b/ext/readline/readline_cli.c
+@@ -739,8 +739,8 @@ typedef cli_shell_callbacks_t *(__cdecl *get_cli_shell_callbacks)(void);
+ } while(0)
+
+ #else
+-/*
+ #ifdef COMPILE_DL_READLINE
++/*
+ This dlsym() is always used as even the CGI SAPI is linked against "CLI"-only
+ extensions. If that is being changed dlsym() should only be used when building
+ this extension sharedto offer compatibility.
+@@ -754,9 +754,9 @@ this extension sharedto offer compatibility.
+ (cb) = get_callbacks(); \
+ } \
+ } while(0)
+-/*#else
++#else
+ #define GET_SHELL_CB(cb) (cb) = php_cli_get_shell_callbacks()
+-#endif*/
++#endif
+ #endif
+
+ PHP_MINIT_FUNCTION(cli_readline)
diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php
index 764cfc7d..5aef4bd8 100644
--- a/src/globals/test-extensions.php
+++ b/src/globals/test-extensions.php
@@ -13,29 +13,28 @@ declare(strict_types=1);
// test php version (8.1 ~ 8.4 available, multiple for matrix)
$test_php_version = [
- // '8.1',
+ '8.1',
// '8.2',
// '8.3',
- '8.4',
- // '8.5',
+ // '8.4',
+ '8.5',
// 'git',
];
-// test os (macos-13, macos-14, macos-15, ubuntu-latest, windows-latest are available)
+// test os (macos-15-intel, macos-15, ubuntu-latest, windows-latest are available)
$test_os = [
- 'macos-13', // bin/spc for x86_64
- // 'macos-14', // bin/spc for arm64
+ 'macos-15-intel', // bin/spc for x86_64
'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-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
+ 'ubuntu-24.04-arm', // bin/spc for arm64
// 'windows-latest', // .\bin\spc.ps1
];
// whether enable thread safe
-$zts = true;
+$zts = false;
$no_strip = false;
@@ -50,13 +49,13 @@ $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' => 'bcmath',
+ 'Linux', 'Darwin' => 'gettext',
'Windows' => 'bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,iconv,xml,mbstring,mbregex,mysqlnd,openssl,pdo,pdo_mysql,pdo_sqlite,phar,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip',
};
// If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`).
$shared_extensions = match (PHP_OS_FAMILY) {
- 'Linux' => 'zip',
+ 'Linux' => '',
'Darwin' => '',
'Windows' => '',
};
@@ -156,17 +155,13 @@ if ($shared_extensions) {
switch ($argv[2] ?? null) {
case 'ubuntu-22.04':
case 'ubuntu-22.04-arm':
+ case 'macos-15':
+ case 'macos-15-intel':
$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':
- $shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
- $no_strip = true;
- break;
default:
$shared_cmd = '';
break;
diff --git a/tests/SPC/builder/BuilderTest.php b/tests/SPC/builder/BuilderTest.php
index d28bd4d3..b4b6258f 100644
--- a/tests/SPC/builder/BuilderTest.php
+++ b/tests/SPC/builder/BuilderTest.php
@@ -62,12 +62,6 @@ class BuilderTest extends TestCase
$this->assertInstanceOf(Extension::class, $this->builder->getExt('mbregex'));
}
- public function testHasCpp()
- {
- // mbregex doesn't have cpp
- $this->assertFalse($this->builder->hasCpp());
- }
-
public function testMakeExtensionArgs()
{
$this->assertStringContainsString('--enable-mbstring', $this->builder->makeStaticExtensionArgs());
diff --git a/tests/SPC/util/ConfigValidatorTest.php b/tests/SPC/util/ConfigValidatorTest.php
index f132636f..aba611a4 100644
--- a/tests/SPC/util/ConfigValidatorTest.php
+++ b/tests/SPC/util/ConfigValidatorTest.php
@@ -50,7 +50,6 @@ class ConfigValidatorTest extends TestCase
'filename' => 'test.tar.gz',
'path' => 'test/path',
'provide-pre-built' => true,
- 'prefer-stable' => false,
'license' => [
'type' => 'file',
'path' => 'LICENSE',
diff --git a/tests/SPC/util/SPCConfigUtilTest.php b/tests/SPC/util/SPCConfigUtilTest.php
index 92353824..c4b1427a 100644
--- a/tests/SPC/util/SPCConfigUtilTest.php
+++ b/tests/SPC/util/SPCConfigUtilTest.php
@@ -44,6 +44,9 @@ class SPCConfigUtilTest extends TestCase
public function testConfig(): void
{
+ if (PHP_OS_FAMILY !== 'Linux') {
+ $this->markTestSkipped('SPCConfigUtil tests are only applicable on Linux.');
+ }
// normal
$result = (new SPCConfigUtil())->config(['bcmath']);
$this->assertStringContainsString(BUILD_ROOT_PATH . '/include', $result['cflags']);