Merge branch 'main' into frankenphp/mbed

This commit is contained in:
Marc 2025-10-24 16:50:28 +02:00 committed by GitHub
commit 8649068159
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 1844 additions and 1385 deletions

View File

@ -136,12 +136,12 @@ jobs:
macos-x86_64) macos-x86_64)
DOWN_CMD="composer update --no-dev --classmap-authoritative && ./bin/spc doctor --auto-fix && ./bin/spc download" DOWN_CMD="composer update --no-dev --classmap-authoritative && ./bin/spc doctor --auto-fix && ./bin/spc download"
BUILD_CMD="./bin/spc build" BUILD_CMD="./bin/spc build"
RUNS_ON="macos-13" RUNS_ON="macos-15-intel"
;; ;;
macos-aarch64) macos-aarch64)
DOWN_CMD="composer update --no-dev --classmap-authoritative && ./bin/spc doctor --auto-fix && ./bin/spc download" DOWN_CMD="composer update --no-dev --classmap-authoritative && ./bin/spc doctor --auto-fix && ./bin/spc download"
BUILD_CMD="./bin/spc build" BUILD_CMD="./bin/spc build"
RUNS_ON="macos-14" RUNS_ON="macos-15"
;; ;;
esac esac
DOWN_CMD="$DOWN_CMD --with-php=${{ inputs.php-version }} --for-extensions=${{ inputs.extensions }} --ignore-cache-sources=php-src" DOWN_CMD="$DOWN_CMD --with-php=${{ inputs.php-version }} --for-extensions=${{ inputs.extensions }} --ignore-cache-sources=php-src"

View File

@ -85,9 +85,9 @@ jobs:
- "8.4" - "8.4"
operating-system: operating-system:
- "ubuntu-latest" - "ubuntu-latest"
#- "macos-13" #- "macos-15-intel"
#- "debian-arm64-self-hosted" #- "debian-arm64-self-hosted"
- "macos-14" - "macos-15"
steps: steps:
- name: "Checkout" - name: "Checkout"
@ -99,11 +99,11 @@ jobs:
OS="" OS=""
if [ "${{ matrix.operating-system }}" = "ubuntu-latest" ]; then if [ "${{ matrix.operating-system }}" = "ubuntu-latest" ]; then
OS="linux-x86_64" OS="linux-x86_64"
elif [ "${{ matrix.operating-system }}" = "macos-13" ]; then elif [ "${{ matrix.operating-system }}" = "macos-15-intel" ]; then
OS="macos-x86_64" OS="macos-x86_64"
elif [ "${{ matrix.operating-system }}" = "debian-arm64-self-hosted" ]; then elif [ "${{ matrix.operating-system }}" = "debian-arm64-self-hosted" ]; then
OS="linux-aarch64" OS="linux-aarch64"
elif [ "${{ matrix.operating-system }}" = "macos-14" ]; then elif [ "${{ matrix.operating-system }}" = "macos-15" ]; then
OS="macos-aarch64" OS="macos-aarch64"
fi fi
echo "OS=$OS" >> $GITHUB_ENV echo "OS=$OS" >> $GITHUB_ENV

View File

@ -27,7 +27,7 @@ jobs:
os: "ubuntu-latest" os: "ubuntu-latest"
filename: "spc-linux-x86_64.tar.gz" filename: "spc-linux-x86_64.tar.gz"
- name: "macos-x86_64" - name: "macos-x86_64"
os: "macos-13" os: "macos-15-intel"
filename: "spc-macos-x86_64.tar.gz" filename: "spc-macos-x86_64.tar.gz"
- name: "linux-aarch64" - name: "linux-aarch64"
os: "ubuntu-latest" os: "ubuntu-latest"
@ -147,11 +147,11 @@ jobs:
- name: "linux-x86_64" - name: "linux-x86_64"
os: "ubuntu-latest" os: "ubuntu-latest"
- name: "macos-x86_64" - name: "macos-x86_64"
os: "macos-13" os: "macos-15-intel"
- name: "linux-aarch64" - name: "linux-aarch64"
os: "ubuntu-24.04-arm" os: "ubuntu-24.04-arm"
- name: "macos-aarch64" - name: "macos-aarch64"
os: "macos-latest" os: "macos-15"
- name: "windows-x64" - name: "windows-x64"
os: "windows-latest" os: "windows-latest"
steps: steps:

View File

@ -1,13 +1,9 @@
name: Tests name: Tests
on: on:
push:
branches:
- main
paths:
- 'src/globals/test-extensions.php'
pull_request: pull_request:
branches: [ "main" ] branches: [ "main" ]
types: [ opened, synchronize, reopened ]
paths: paths:
- 'src/**' - 'src/**'
- 'config/**' - 'config/**'

3
.gitignore vendored
View File

@ -22,6 +22,9 @@ docker/source/
# default package root directory # default package root directory
/pkgroot/** /pkgroot/**
# Windows PHP SDK binary tools
/php-sdk-binary-tools/**
# default pack:lib and release directory # default pack:lib and release directory
/dist/** /dist/**
packlib_files.txt packlib_files.txt

View File

@ -5,310 +5,168 @@
[![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases) [![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml) [![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE) [![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
[![Extensions](https://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)](https://static-php.dev/zh/guide/extensions.html)
**static-php-cli**是一个用于静态编译、构建 PHP 解释器的工具,支持众多流行扩展。 **static-php-cli** 是一个用于构建静态、独立 PHP 运行时的强大工具,支持众多流行扩展。
目前 static-php-cli 支持 `cli``fpm``embed``micro``frankenphp` SAPI。
**static-php-cli**也支持将 PHP 代码和 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 解释器,无需任何依赖 **单文件独立 php-cli**
- :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:**
<img width="700" alt="out1" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/01a2e60f-13b0-4242-a645-f7afa4936396"> <img width="700" alt="out1" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/01a2e60f-13b0-4242-a645-f7afa4936396">
**使用 phpmicro 打包 PHP 代码:** **使用 phpmicro 将 PHP 代码与 PHP 解释器结合:**
<img width="700" alt="out2" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/46b7128d-fb72-4169-957e-48564c3ff3e2"> <img width="700" alt="out2" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/46b7128d-fb72-4169-957e-48564c3ff3e2">
## 文档 ## 快速开始
目前 README 编写了基本用法。有关 static-php-cli 所有的功能,请点击这里查看文档:<https://static-php.dev> ### 1. 下载 spc 二进制文件
## 直接下载
如果你不想自行编译 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。
使用以下命令从自托管服务器下载:
```bash ```bash
# Download from self-hosted nightly builds (sync with main branch) # Linux x86_64
# For Linux x86_64
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-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 curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
# macOS x86_64 (Intel) # macOS x86_64 (Intel)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64 curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple) # macOS aarch64 (Apple)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64 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 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 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 ```bash
# clone 仓库即可 ./spc craft
git clone https://github.com/crazywhalecc/static-php-cli.git
# 输出完整控制台日志
./spc craft --debug
``` ```
如果您的系统上尚未安装 php我们建议你使用内置的 setup-runtime 自动安装 PHP 和 Composer。 ### 3. 静态 PHP 使用
```bash 现在您可以将 static-php-cli 构建的二进制文件复制到另一台机器上,无需依赖即可运行:
cd static-php-cli
chmod +x bin/setup-runtime ```
# it will download static php (from self-hosted server) and composer (from getcomposer) # php-cli
bin/setup-runtime buildroot/bin/php -v
# initialize composer deps
bin/composer install # phpmicro
# chmod echo '<?php echo "Hello world!\n";' > a.php
chmod +x bin/spc ./spc micro:combine a.php -O my-app
bin/spc --version ./my-app
# php-fpm
buildroot/bin/php-fpm -v
``` ```
### 开始构建 PHP ## 文档
下面是使用 static-php-cli 的基础用法: 当前 README 包含基本用法。有关 static-php-cli 的所有功能,
请访问 <https://static-php.dev>
> 如果你使用的是打包好的 `spc` 二进制,你需要将下列命令的 `./bin/spc` 替换为 `./spc` ## 直接下载
```bash 如果您不想构建或想先测试,可以从 [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-bulk.yml) 下载示例预编译工件,或从自托管服务器下载。
# 检查环境依赖,并根据尝试自动安装缺失的编译工具
./bin/spc doctor --auto-fix
# 输出目标项目依赖的扩展列表 以下是几个具有不同扩展组合的预编译静态 PHP 二进制文件,
./bin/spc dump-extensions /path/to/your/project --format=text 您可以根据需要直接下载。
# 拉取所有依赖库 | 组合名称 | 扩展数量 | 系统 | 备注 |
./bin/spc download --all |----------------------------------------------------------------------|----------------------------------------------------------------------------|--------------|--------------------|
# 只拉取编译指定扩展需要的所有依赖(推荐) | [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 |
./bin/spc download --for-extensions="openssl,pcntl,mbstring,pdo_sqlite" | [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 组合 |
./bin/spc download --for-extensions="openssl,curl,mbstring,mbregex" --prefer-pre-built | [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 |
# 下载编译不同版本的 PHP (--with-php=x.y 或 --with-php=x.y.z推荐 8.1 ~ 8.3) | [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 |
./bin/spc download --for-extensions="openssl,curl,mbstring" --with-php=8.1 | [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 > Linux 和 Windows 支持对二进制文件进行 UPX 压缩,可以将二进制文件大小减少 30% 到 50%。
./bin/spc build "bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl" --build-cli --build-micro > macOS 不支持 UPX 压缩,因此 mac 的预构建二进制文件大小较大。
# 编译线程安全版本 (--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
```
其中,目前支持构建 climicrofpm 和 embed使用以下参数的一个或多个来指定编译的 SAPI ### 在线构建(使用 GitHub Actions
- `--build-cli`:构建 cli 二进制 上方直接下载的二进制不能满足需求时,可使用 GitHub Action 可以轻松构建静态编译的 PHP
- `--build-micro`:构建 phpmicro 自执行二进制 同时自行定义要编译的扩展。
- `--build-fpm`:构建 fpm
- `--build-embed`:构建 embedlibphp
- `--build-cgi`: 构建 cgi不推荐
- `--build-all`:构建所有
如果出现了任何错误,可以使用 `--debug` 参数来展示完整的输出日志,以供排查错误: 1. Fork 本项目。
2. 进入项目的 Actions 并选择 `CI`
3. 选择 `Run workflow`,填入您要编译的 PHP 版本、目标类型和扩展列表。(扩展用逗号分隔,例如 `bcmath,curl,mbstring`
4. 等待一段时间后,进入相应的任务并获取 `Artifacts`
```bash 如果您启用 `debug`,构建时将输出所有日志,包括编译日志,以便故障排除。
./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 "<?php echo 'Hello world' . PHP_EOL;" > 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)。
## 贡献 ## 贡献
如果缺少你需要的扩展,可发起 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** 服务器。
**特别赞助商**: **特别感谢以下赞助商**
<a href="https://beyondco.de/"><img src="/docs/public/images/beyondcode-seeklogo.png" width="300" alt="Beyond Code Logo" /></a> <a href="https://beyondco.de/"><img src="/docs/public/images/beyondcode-seeklogo.png" width="300" alt="Beyond Code Logo" /></a>
<a href="https://nativephp.com/"><img src="/docs/public/images/nativephp-logo.svg" width="300" alt="NativePHP Logo" /></a> <a href="https://nativephp.com/"><img src="/docs/public/images/nativephp-logo.svg" width="300" alt="NativePHP Logo" /></a>
## 开源协议 ## 开源许可证
本项目采用 MIT License 许可开源,下面是类似的项目: 本项目本身基于 MIT 许可证,
一些新添加的扩展和依赖可能来自其他项目,
这些代码文件的头部也会给出额外的许可证和作者说明。
这些是类似的项目:
- [dixyes/lwmbs](https://github.com/dixyes/lwmbs) - [dixyes/lwmbs](https://github.com/dixyes/lwmbs)
- [swoole/swoole-cli](https://github.com/swoole/swoole-cli) - [swoole/swoole-cli](https://github.com/swoole/swoole-cli)
项目使用了 [dixyes/lwmbs](https://github.com/dixyes/lwmbs) 的一些代码,例如 Windows 静态构建目标和 libiconv 支持。 本项目使用了 [dixyes/lwmbs](https://github.com/dixyes/lwmbs) 的一些代码,例如 Windows 静态构建目标和 libiconv 支持。
lwmbs 使用 [Mulan PSL 2](http://license.coscl.org.cn/MulanPSL2) 许可进行分发。对应文件有关于作者和许可的特殊说明,除此之外,均使用 MIT 授权许可 lwmbs 基于 [Mulan PSL 2](http://license.coscl.org.cn/MulanPSL2) 许可证。
因本项目的特殊性,使用项目编译过程中会使用很多其他开源项目,例如 curl、protobuf 等,它们都有各自的开源协议。 由于本项目的特殊性,
请在编译完成后,使用命令 `bin/spc dump-license` 导出项目使用项目的开源协议,并遵守对应项目的 LICENSE。 项目编译过程中会使用许多其他开源项目,如 curl 和 protobuf
它们都有自己的开源许可证。
请在编译后使用 `bin/spc dump-license` 命令导出项目中使用的开源许可证,
并遵守相应项目的 LICENSE。

329
README.md
View File

@ -5,29 +5,22 @@
[![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases) [![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml) [![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE) [![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
[![Extensions](https://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)](https://static-php.dev/en/guide/extensions.html)
**static-php-cli** is a powerful tool designed for building static, standalone PHP runtime **static-php-cli** is a powerful tool designed for building static, standalone PHP runtime
with popular extensions. with popular extensions.
Static PHP built by **static-php-cli** supports `cli`, `fpm`, `embed`, `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 ## Features
static-php-cli (you can call it `spc`) has a lot of features: - :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
- :handbag: Build single-file php executable, without any dependencies - :hamburger:Build **[phpmicro](https://github.com/dixyes/phpmicro)** self-extracting executables (combines PHP binary and source code into one file)
- :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 with auto-fix capabilities
- :pill: Automatic build environment checker (Doctor module)
- :zap: `Linux`, `macOS`, `FreeBSD`, `Windows` support - :zap: `Linux`, `macOS`, `FreeBSD`, `Windows` support
- :wrench: Configurable source code patches - :wrench: Configurable source code patching
- :books: Build dependency management - :books: Intelligent dependency management
- 📦 Provide `spc` own standalone executable (built by spc and [box](https://github.com/box-project/box)) - 📦 Self-contained `spc` executable (built with [box](https://github.com/box-project/box))
- :fire: Support many popular [extensions](https://static-php.dev/en/guide/extensions.html) - :fire: Support 100+ popular [extensions](https://static-php.dev/en/guide/extensions.html)
- :floppy_disk: UPX integration (significantly reduces binary size) - :floppy_disk: UPX compression support (reduces binary size by 30-50%)
**Single-file standalone php-cli:** **Single-file standalone php-cli:**
@ -37,6 +30,72 @@ static-php-cli (you can call it `spc`) has a lot of features:
<img width="700" alt="out2" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/46b7128d-fb72-4169-957e-48564c3ff3e2"> <img width="700" alt="out2" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/46b7128d-fb72-4169-957e-48564c3ff3e2">
## 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 '<?php echo "Hello world!\n";' > a.php
./spc micro:combine a.php -O my-app
./my-app
# php-fpm
buildroot/bin/php-fpm -v
```
## Documentation ## Documentation
The current README contains basic usage. For all the features of static-php-cli, 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 | | [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 | | [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 | | [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-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 | | [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%. > 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. > 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) ### 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. and at the same time define the extensions to be compiled by yourself.
1. Fork me. 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. 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 "<?php echo 'Hello world' . PHP_EOL;" > 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 ## Contribution
If the extension you need is missing, you can create an issue. If the extension you need is missing, you can create an issue.

View File

@ -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)""/dist:/app/dist"
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/downloads:/app/downloads" MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/downloads:/app/downloads"
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/pkgroot:/app/pkgroot" MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/pkgroot:/app/pkgroot"
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/log:/app/log"
if [ -f "$(pwd)/craft.yml" ]; then if [ -f "$(pwd)/craft.yml" ]; then
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/craft.yml:/app/craft.yml" MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/craft.yml:/app/craft.yml"
fi fi

View File

@ -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)""/dist:/app/dist"
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/downloads:/app/downloads" MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/downloads:/app/downloads"
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/pkgroot:/app/pkgroot" MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/pkgroot:/app/pkgroot"
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/log:/app/log"
if [ -f "$(pwd)/craft.yml" ]; then if [ -f "$(pwd)/craft.yml" ]; then
MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/craft.yml:/app/craft.yml" MOUNT_LIST="$MOUNT_LIST -v ""$(pwd)""/craft.yml:/app/craft.yml"
fi fi

289
composer.lock generated
View File

@ -8,7 +8,7 @@
"packages": [ "packages": [
{ {
"name": "illuminate/collections", "name": "illuminate/collections",
"version": "v11.45.3", "version": "v11.46.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/collections.git", "url": "https://github.com/illuminate/collections.git",
@ -64,7 +64,7 @@
}, },
{ {
"name": "illuminate/conditionable", "name": "illuminate/conditionable",
"version": "v11.45.3", "version": "v11.46.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/conditionable.git", "url": "https://github.com/illuminate/conditionable.git",
@ -110,7 +110,7 @@
}, },
{ {
"name": "illuminate/contracts", "name": "illuminate/contracts",
"version": "v11.45.3", "version": "v11.46.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/contracts.git", "url": "https://github.com/illuminate/contracts.git",
@ -158,7 +158,7 @@
}, },
{ {
"name": "illuminate/macroable", "name": "illuminate/macroable",
"version": "v11.45.3", "version": "v11.46.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/illuminate/macroable.git", "url": "https://github.com/illuminate/macroable.git",
@ -416,16 +416,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v7.3.3", "version": "v7.3.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -490,7 +490,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v7.3.3" "source": "https://github.com/symfony/console/tree/v7.3.4"
}, },
"funding": [ "funding": [
{ {
@ -510,7 +510,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-08-25T06:35:40+00:00" "time": "2025-09-22T15:31:00+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
@ -916,16 +916,16 @@
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v7.3.3", "version": "v7.3.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1" "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", "url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1", "reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -957,7 +957,7 @@
"description": "Executes commands in sub-processes", "description": "Executes commands in sub-processes",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/process/tree/v7.3.3" "source": "https://github.com/symfony/process/tree/v7.3.4"
}, },
"funding": [ "funding": [
{ {
@ -977,7 +977,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-08-18T09:42:54+00:00" "time": "2025-09-11T10:12:26+00:00"
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
@ -1064,16 +1064,16 @@
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v7.3.3", "version": "v7.3.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" "reference": "f96476035142921000338bad71e5247fbc138872"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", "reference": "f96476035142921000338bad71e5247fbc138872",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1088,7 +1088,6 @@
}, },
"require-dev": { "require-dev": {
"symfony/emoji": "^7.1", "symfony/emoji": "^7.1",
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3.0", "symfony/translation-contracts": "^2.5|^3.0",
@ -1131,7 +1130,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v7.3.3" "source": "https://github.com/symfony/string/tree/v7.3.4"
}, },
"funding": [ "funding": [
{ {
@ -1151,7 +1150,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-08-25T06:35:40+00:00" "time": "2025-09-11T14:36:48+00:00"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
@ -2861,16 +2860,16 @@
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v3.87.1", "version": "v3.89.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "2f5170365e2a422d0c5421f9c8818b2c078105f6" "reference": "4dd6768cb7558440d27d18f54909eee417317ce9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2f5170365e2a422d0c5421f9c8818b2c078105f6", "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/4dd6768cb7558440d27d18f54909eee417317ce9",
"reference": "2f5170365e2a422d0c5421f9c8818b2c078105f6", "reference": "4dd6768cb7558440d27d18f54909eee417317ce9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2885,7 +2884,6 @@
"php": "^7.4 || ^8.0", "php": "^7.4 || ^8.0",
"react/child-process": "^0.6.6", "react/child-process": "^0.6.6",
"react/event-loop": "^1.5", "react/event-loop": "^1.5",
"react/promise": "^3.3",
"react/socket": "^1.16", "react/socket": "^1.16",
"react/stream": "^1.4", "react/stream": "^1.4",
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0",
@ -2897,12 +2895,13 @@
"symfony/polyfill-mbstring": "^1.33", "symfony/polyfill-mbstring": "^1.33",
"symfony/polyfill-php80": "^1.33", "symfony/polyfill-php80": "^1.33",
"symfony/polyfill-php81": "^1.33", "symfony/polyfill-php81": "^1.33",
"symfony/polyfill-php84": "^1.33",
"symfony/process": "^5.4.47 || ^6.4.24 || ^7.2", "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2",
"symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0" "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0"
}, },
"require-dev": { "require-dev": {
"facile-it/paraunit": "^1.3.1 || ^2.7", "facile-it/paraunit": "^1.3.1 || ^2.7",
"infection/infection": "^0.29.14", "infection/infection": "^0.31.0",
"justinrainbow/json-schema": "^6.5", "justinrainbow/json-schema": "^6.5",
"keradus/cli-executor": "^2.2", "keradus/cli-executor": "^2.2",
"mikey179/vfsstream": "^1.6.12", "mikey179/vfsstream": "^1.6.12",
@ -2910,7 +2909,6 @@
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
"phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34", "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/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2",
"symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2" "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2"
}, },
@ -2953,7 +2951,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", "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": [ "funding": [
{ {
@ -2961,20 +2959,20 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-09-02T15:27:36+00:00" "time": "2025-10-18T19:30:16+00:00"
}, },
{ {
"name": "humbug/box", "name": "humbug/box",
"version": "4.6.6", "version": "4.6.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/box-project/box.git", "url": "https://github.com/box-project/box.git",
"reference": "09646041cb2e0963ab6ca109e1b366337617a0f2" "reference": "05d205d99ddb72f3729658a0115db02cfc08912e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/box-project/box/zipball/09646041cb2e0963ab6ca109e1b366337617a0f2", "url": "https://api.github.com/repos/box-project/box/zipball/05d205d99ddb72f3729658a0115db02cfc08912e",
"reference": "09646041cb2e0963ab6ca109e1b366337617a0f2", "reference": "05d205d99ddb72f3729658a0115db02cfc08912e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2988,13 +2986,13 @@
"fidry/console": "^0.6.0", "fidry/console": "^0.6.0",
"fidry/filesystem": "^1.2.1", "fidry/filesystem": "^1.2.1",
"humbug/php-scoper": "^0.18.14", "humbug/php-scoper": "^0.18.14",
"justinrainbow/json-schema": "^5.2.12", "justinrainbow/json-schema": "^6.2.0",
"nikic/iter": "^2.2", "nikic/iter": "^2.2",
"php": "^8.2", "php": "^8.2",
"phpdocumentor/reflection-docblock": "^5.4", "phpdocumentor/reflection-docblock": "^5.4",
"phpdocumentor/type-resolver": "^1.7", "phpdocumentor/type-resolver": "^1.7",
"psr/log": "^3.0", "psr/log": "^3.0",
"sebastian/diff": "^5.0", "sebastian/diff": "^5.0 || ^6.0 || ^7.0",
"seld/jsonlint": "^1.10.2", "seld/jsonlint": "^1.10.2",
"seld/phar-utils": "^1.2", "seld/phar-utils": "^1.2",
"symfony/finder": "^6.4.0 || ^7.0.0", "symfony/finder": "^6.4.0 || ^7.0.0",
@ -3005,6 +3003,9 @@
"thecodingmachine/safe": "^2.5 || ^3.0", "thecodingmachine/safe": "^2.5 || ^3.0",
"webmozart/assert": "^1.11" "webmozart/assert": "^1.11"
}, },
"conflict": {
"marc-mabe/php-enum": "<4.4"
},
"replace": { "replace": {
"symfony/polyfill-php80": "*", "symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*", "symfony/polyfill-php81": "*",
@ -3070,22 +3071,22 @@
], ],
"support": { "support": {
"issues": "https://github.com/box-project/box/issues", "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", "name": "humbug/php-scoper",
"version": "0.18.17", "version": "0.18.18",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/humbug/php-scoper.git", "url": "https://github.com/humbug/php-scoper.git",
"reference": "0a2556c7c23776a61cf22689e2f24298ba00e33a" "reference": "dd55d01a937602c9473cfbe0ecab9521cb9740aa"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/humbug/php-scoper/zipball/0a2556c7c23776a61cf22689e2f24298ba00e33a", "url": "https://api.github.com/repos/humbug/php-scoper/zipball/dd55d01a937602c9473cfbe0ecab9521cb9740aa",
"reference": "0a2556c7c23776a61cf22689e2f24298ba00e33a", "reference": "dd55d01a937602c9473cfbe0ecab9521cb9740aa",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3154,9 +3155,9 @@
"description": "Prefixes all PHP namespaces in a file or directory.", "description": "Prefixes all PHP namespaces in a file or directory.",
"support": { "support": {
"issues": "https://github.com/humbug/php-scoper/issues", "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", "name": "jetbrains/phpstorm-stubs",
@ -3207,30 +3208,40 @@
}, },
{ {
"name": "justinrainbow/json-schema", "name": "justinrainbow/json-schema",
"version": "5.3.0", "version": "6.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/jsonrainbow/json-schema.git", "url": "https://github.com/jsonrainbow/json-schema.git",
"reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8" "reference": "68ba7677532803cc0c5900dd5a4d730537f2b2f3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/68ba7677532803cc0c5900dd5a4d730537f2b2f3",
"reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", "reference": "68ba7677532803cc0c5900dd5a4d730537f2b2f3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.1" "ext-json": "*",
"marc-mabe/php-enum": "^4.0",
"php": "^7.2 || ^8.0"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", "friendsofphp/php-cs-fixer": "3.3.0",
"json-schema/json-schema-test-suite": "1.2.0", "json-schema/json-schema-test-suite": "^23.2",
"phpunit/phpunit": "^4.8.35" "marc-mabe/php-enum-phpstan": "^2.0",
"phpspec/prophecy": "^1.19",
"phpstan/phpstan": "^1.12",
"phpunit/phpunit": "^8.5"
}, },
"bin": [ "bin": [
"bin/validate-json" "bin/validate-json"
], ],
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.x-dev"
}
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"JsonSchema\\": "src/JsonSchema/" "JsonSchema\\": "src/JsonSchema/"
@ -3259,16 +3270,16 @@
} }
], ],
"description": "A library to validate a json schema.", "description": "A library to validate a json schema.",
"homepage": "https://github.com/justinrainbow/json-schema", "homepage": "https://github.com/jsonrainbow/json-schema",
"keywords": [ "keywords": [
"json", "json",
"schema" "schema"
], ],
"support": { "support": {
"issues": "https://github.com/jsonrainbow/json-schema/issues", "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", "name": "kelunik/certificate",
@ -3502,6 +3513,79 @@
], ],
"time": "2024-12-08T08:18:47+00:00" "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", "name": "myclabs/deep-copy",
"version": "1.13.4", "version": "1.13.4",
@ -4228,16 +4312,11 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.12.28", "version": "1.12.32",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9"
},
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8",
"reference": "fcf8b71aeab4e1a1131d1783cef97b23a51b87a9", "reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4282,7 +4361,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2025-07-17T17:15:39+00:00" "time": "2025-09-30T10:16:31+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@ -4607,16 +4686,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "10.5.53", "version": "10.5.58",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "32768472ebfb6969e6c7399f1c7b09009723f653" "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/32768472ebfb6969e6c7399f1c7b09009723f653", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e24fb46da450d8e6a5788670513c1af1424f16ca",
"reference": "32768472ebfb6969e6c7399f1c7b09009723f653", "reference": "e24fb46da450d8e6a5788670513c1af1424f16ca",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4637,10 +4716,10 @@
"phpunit/php-timer": "^6.0.0", "phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1", "sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0", "sebastian/code-unit": "^2.0.0",
"sebastian/comparator": "^5.0.3", "sebastian/comparator": "^5.0.4",
"sebastian/diff": "^5.1.1", "sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0", "sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.2", "sebastian/exporter": "^5.1.4",
"sebastian/global-state": "^6.0.2", "sebastian/global-state": "^6.0.2",
"sebastian/object-enumerator": "^5.0.0", "sebastian/object-enumerator": "^5.0.0",
"sebastian/recursion-context": "^5.0.1", "sebastian/recursion-context": "^5.0.1",
@ -4688,7 +4767,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "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": [ "funding": [
{ {
@ -4712,7 +4791,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-08-20T14:40:06+00:00" "time": "2025-09-28T12:04:46+00:00"
}, },
{ {
"name": "psr/event-dispatcher", "name": "psr/event-dispatcher",
@ -5640,16 +5719,16 @@
}, },
{ {
"name": "sebastian/comparator", "name": "sebastian/comparator",
"version": "5.0.3", "version": "5.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git", "url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5705,15 +5784,27 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues", "issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy", "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": [ "funding": [
{ {
"url": "https://github.com/sebastianbergmann", "url": "https://github.com/sebastianbergmann",
"type": "github" "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", "name": "sebastian/complexity",
@ -5906,16 +5997,16 @@
}, },
{ {
"name": "sebastian/exporter", "name": "sebastian/exporter",
"version": "5.1.2", "version": "5.1.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git", "url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "955288482d97c19a372d3f31006ab3f37da47adf" "reference": "0735b90f4da94969541dac1da743446e276defa6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6",
"reference": "955288482d97c19a372d3f31006ab3f37da47adf", "reference": "0735b90f4da94969541dac1da743446e276defa6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5924,7 +6015,7 @@
"sebastian/recursion-context": "^5.0" "sebastian/recursion-context": "^5.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^10.0" "phpunit/phpunit": "^10.5"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@ -5972,15 +6063,27 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues", "issues": "https://github.com/sebastianbergmann/exporter/issues",
"security": "https://github.com/sebastianbergmann/exporter/security/policy", "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": [ "funding": [
{ {
"url": "https://github.com/sebastianbergmann", "url": "https://github.com/sebastianbergmann",
"type": "github" "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", "name": "sebastian/global-state",
@ -7108,16 +7211,16 @@
}, },
{ {
"name": "symfony/var-dumper", "name": "symfony/var-dumper",
"version": "v7.3.3", "version": "v7.3.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/var-dumper.git", "url": "https://github.com/symfony/var-dumper.git",
"reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f" "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
"reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -7171,7 +7274,7 @@
"dump" "dump"
], ],
"support": { "support": {
"source": "https://github.com/symfony/var-dumper/tree/v7.3.3" "source": "https://github.com/symfony/var-dumper/tree/v7.3.4"
}, },
"funding": [ "funding": [
{ {
@ -7191,7 +7294,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-08-13T11:49:31+00:00" "time": "2025-09-11T10:12:26+00:00"
}, },
{ {
"name": "thecodingmachine/safe", "name": "thecodingmachine/safe",

View File

@ -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_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_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_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] [global]
; Build concurrency for make -jN, default is CPU_COUNT, this value are used in every libs. ; 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" SPC_CMD_PREFIX_PHP_BUILDCONF="./buildconf --force"
; configure command ; configure command
SPC_CMD_PREFIX_PHP_CONFIGURE="./configure --prefix= --with-valgrind=no --disable-shared --enable-static --disable-all --disable-phpdbg --with-pic" 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 *** ; *** default build vars for building php ***
; embed type for php, static (libphp.a) or shared (libphp.so) ; 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" SPC_CMD_PREFIX_PHP_BUILDCONF="./buildconf --force"
; configure command ; configure command
SPC_CMD_PREFIX_PHP_CONFIGURE="./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-phpdbg" 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 *** ; *** default build vars for building php ***
; embed type for php, static (libphp.a) or shared (libphp.dylib) ; embed type for php, static (libphp.a) or shared (libphp.dylib)

View File

@ -341,6 +341,7 @@
"ext-depends": [ "ext-depends": [
"xml" "xml"
], ],
"build-with-php": true,
"target": [ "target": [
"static" "static"
] ]
@ -414,9 +415,17 @@
"libmemcached", "libmemcached",
"fastlz" "fastlz"
], ],
"lib-suggests": [
"zstd"
],
"ext-depends": [ "ext-depends": [
"session", "session",
"zlib" "zlib"
],
"ext-suggests": [
"igbinary",
"msgpack",
"session"
] ]
}, },
"mongodb": { "mongodb": {
@ -445,7 +454,7 @@
"type": "external", "type": "external",
"source": "msgpack", "source": "msgpack",
"arg-type-unix": "with", "arg-type-unix": "with",
"arg-type-win": "enable", "arg-type-windows": "enable",
"ext-depends": [ "ext-depends": [
"session" "session"
] ]
@ -453,6 +462,7 @@
"mysqli": { "mysqli": {
"type": "builtin", "type": "builtin",
"arg-type": "with", "arg-type": "with",
"build-with-php": true,
"ext-depends": [ "ext-depends": [
"mysqlnd" "mysqlnd"
] ]
@ -460,6 +470,7 @@
"mysqlnd": { "mysqlnd": {
"type": "builtin", "type": "builtin",
"arg-type-windows": "with", "arg-type-windows": "with",
"build-with-php": true,
"lib-depends": [ "lib-depends": [
"zlib" "zlib"
] ]
@ -679,7 +690,7 @@
"type": "builtin", "type": "builtin",
"arg-type": "with-path", "arg-type": "with-path",
"lib-depends": [ "lib-depends": [
"readline" "libedit"
], ],
"target": [ "target": [
"static" "static"
@ -751,11 +762,9 @@
}, },
"type": "builtin", "type": "builtin",
"arg-type": "custom", "arg-type": "custom",
"lib-depends": [ "ext-depends": [
"libxml2" "libxml",
], "session"
"ext-depends-windows": [
"xml"
] ]
}, },
"sockets": { "sockets": {
@ -791,6 +800,7 @@
"type": "builtin", "type": "builtin",
"arg-type": "with-path", "arg-type": "with-path",
"arg-type-windows": "with", "arg-type-windows": "with",
"build-with-php": true,
"lib-depends": [ "lib-depends": [
"sqlite" "sqlite"
] ]
@ -879,6 +889,22 @@
"mysqli" "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": { "swoole-hook-pgsql": {
"support": { "support": {
"Windows": "no", "Windows": "no",
@ -908,22 +934,6 @@
"swoole" "swoole"
] ]
}, },
"swoole-hook-odbc": {
"support": {
"Windows": "no",
"BSD": "wip"
},
"notes": true,
"type": "addon",
"arg-type": "none",
"ext-depends": [
"pdo",
"swoole"
],
"lib-depends": [
"unixodbc"
]
},
"swow": { "swow": {
"support": { "support": {
"BSD": "wip" "BSD": "wip"
@ -1146,8 +1156,9 @@
"support": { "support": {
"BSD": "wip" "BSD": "wip"
}, },
"type": "builtin", "type": "external",
"arg-type": "with-path", "source": "ext-zip",
"arg-type": "custom",
"arg-type-windows": "enable", "arg-type-windows": "enable",
"lib-depends-unix": [ "lib-depends-unix": [
"libzip" "libzip"
@ -1170,6 +1181,7 @@
"lib-depends": [ "lib-depends": [
"zlib" "zlib"
], ],
"build-with-php": true,
"target": [ "target": [
"static" "static"
] ]

View File

@ -10,17 +10,19 @@
"micro", "micro",
"frankenphp" "frankenphp"
], ],
"lib-depends-macos": [
"lib-base",
"micro",
"libxml2"
],
"lib-suggests-linux": [ "lib-suggests-linux": [
"libacl", "libacl",
"brotli", "brotli",
"watcher" "watcher"
], ],
"lib-suggests-unix": [ "lib-suggests-macos": [
"brotli", "brotli",
"watcher" "watcher"
],
"lib-depends-macos": [
"libxml2"
] ]
}, },
"micro": { "micro": {
@ -201,7 +203,7 @@
"openssl", "openssl",
"libcares" "libcares"
], ],
"provide-pre-built": true, "cpp-library": true,
"frameworks": [ "frameworks": [
"CoreFoundation" "CoreFoundation"
] ]
@ -229,6 +231,7 @@
}, },
"imagemagick": { "imagemagick": {
"source": "imagemagick", "source": "imagemagick",
"cpp-library": true,
"pkg-configs": [ "pkg-configs": [
"Magick++-7.Q16HDRI", "Magick++-7.Q16HDRI",
"MagickCore-7.Q16HDRI", "MagickCore-7.Q16HDRI",
@ -342,6 +345,15 @@
], ],
"cpp-library": true "cpp-library": true
}, },
"libedit": {
"source": "libedit",
"static-libs-unix": [
"libedit.a"
],
"lib-depends": [
"ncurses"
]
},
"libevent": { "libevent": {
"source": "libevent", "source": "libevent",
"static-libs-unix": [ "static-libs-unix": [
@ -451,6 +463,7 @@
}, },
"libmemcached": { "libmemcached": {
"source": "libmemcached", "source": "libmemcached",
"cpp-library": true,
"static-libs-unix": [ "static-libs-unix": [
"libmemcached.a", "libmemcached.a",
"libmemcachedprotocol.a", "libmemcachedprotocol.a",
@ -547,6 +560,21 @@
"zstd" "zstd"
] ]
}, },
"liburing": {
"source": "liburing",
"pkg-configs": [
"liburing",
"liburing-ffi"
],
"static-libs-linux": [
"liburing.a",
"liburing-ffi.a"
],
"headers-linux": [
"liburing/",
"liburing.h"
]
},
"libuuid": { "libuuid": {
"source": "libuuid", "source": "libuuid",
"static-libs-unix": [ "static-libs-unix": [
@ -595,7 +623,6 @@
], ],
"lib-suggests-unix": [ "lib-suggests-unix": [
"xz", "xz",
"icu",
"zlib" "zlib"
], ],
"lib-depends-windows": [ "lib-depends-windows": [
@ -769,7 +796,7 @@
"libxml2", "libxml2",
"openssl", "openssl",
"zlib", "zlib",
"readline" "libedit"
], ],
"lib-suggests": [ "lib-suggests": [
"icu", "icu",
@ -821,6 +848,7 @@
}, },
"snappy": { "snappy": {
"source": "snappy", "source": "snappy",
"cpp-library": true,
"static-libs-unix": [ "static-libs-unix": [
"libsnappy.a" "libsnappy.a"
], ],
@ -866,6 +894,7 @@
}, },
"watcher": { "watcher": {
"source": "watcher", "source": "watcher",
"cpp-library": true,
"static-libs-unix": [ "static-libs-unix": [
"libwatcher-c.a" "libwatcher-c.a"
], ],

View File

@ -23,8 +23,8 @@
"type": "url", "type": "url",
"url": "https://dl.static-php.dev/static-php-cli/deps/nasm/nasm-2.16.01-win64.zip", "url": "https://dl.static-php.dev/static-php-cli/deps/nasm/nasm-2.16.01-win64.zip",
"extract-files": { "extract-files": {
"nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe", "nasm.exe": "{php_sdk_path}/bin/nasm.exe",
"nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe" "ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"
} }
}, },
"pkg-config-aarch64-linux": { "pkg-config-aarch64-linux": {
@ -84,7 +84,7 @@
"repo": "upx/upx", "repo": "upx/upx",
"match": "upx.+-win64\\.zip", "match": "upx.+-win64\\.zip",
"extract-files": { "extract-files": {
"upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe" "upx.exe": "{pkg_root_path}/bin/upx.exe"
} }
}, },
"zig-aarch64-linux": { "zig-aarch64-linux": {

View File

@ -47,7 +47,7 @@
"provide-pre-built": true, "provide-pre-built": true,
"license": { "license": {
"type": "file", "type": "file",
"path": "doc/COPYING" "path": "doc/COPYING.LGPL"
} }
}, },
"brotli": { "brotli": {
@ -263,6 +263,15 @@
"path": "LICENSE" "path": "LICENSE"
} }
}, },
"ext-zip": {
"type": "url",
"url": "https://pecl.php.net/get/zip",
"filename": "ext-zip.tgz",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"ext-zstd": { "ext-zstd": {
"type": "git", "type": "git",
"path": "php-src/ext/zstd", "path": "php-src/ext/zstd",
@ -297,16 +306,17 @@
"regex": "/href=\"(?<file>gettext-(?<version>[^\"]+)\\.tar\\.xz)\"/", "regex": "/href=\"(?<file>gettext-(?<version>[^\"]+)\\.tar\\.xz)\"/",
"license": { "license": {
"type": "file", "type": "file",
"path": "COPYING" "path": "gettext-runtime/intl/COPYING.LIB"
} }
}, },
"gmp": { "gmp": {
"type": "url", "type": "filelist",
"url": "https://dl.static-php.dev/static-php-cli/deps/gmp/gmp-6.3.0.tar.xz", "url": "https://gmplib.org/download/gmp/",
"regex": "/href=\"(?<file>gmp-(?<version>[^\"]+)\\.tar\\.xz)\"/",
"provide-pre-built": true, "provide-pre-built": true,
"alt": { "alt": {
"type": "ghtagtar", "type": "url",
"repo": "alisw/GMP" "url": "https://dl.static-php.dev/static-php-cli/deps/gmp/gmp-6.3.0.tar.xz"
}, },
"license": { "license": {
"type": "text", "type": "text",
@ -324,7 +334,7 @@
}, },
"grpc": { "grpc": {
"type": "git", "type": "git",
"rev": "v1.68.x", "rev": "v1.75.x",
"url": "https://github.com/grpc/grpc.git", "url": "https://github.com/grpc/grpc.git",
"provide-pre-built": true, "provide-pre-built": true,
"license": { "license": {
@ -440,7 +450,7 @@
"provide-pre-built": true, "provide-pre-built": true,
"license": { "license": {
"type": "file", "type": "file",
"path": "doc/COPYING" "path": "doc/COPYING.LGPL"
} }
}, },
"libaom": { "libaom": {
@ -499,6 +509,16 @@
"path": "COPYING" "path": "COPYING"
} }
}, },
"libedit": {
"type": "filelist",
"url": "https://thrysoee.dk/editline/",
"regex": "/href=\"(?<file>libedit-(?<version>[^\"]+)\\.tar\\.gz)\"/",
"provide-pre-built": true,
"license": {
"type": "file",
"path": "COPYING"
}
},
"libevent": { "libevent": {
"type": "ghrel", "type": "ghrel",
"repo": "libevent/libevent", "repo": "libevent/libevent",
@ -547,7 +567,7 @@
"provide-pre-built": true, "provide-pre-built": true,
"license": { "license": {
"type": "file", "type": "file",
"path": "COPYING" "path": "COPYING.LIB"
} }
}, },
"libiconv-win": { "libiconv-win": {
@ -660,6 +680,15 @@
"path": "LICENSE.md" "path": "LICENSE.md"
} }
}, },
"liburing": {
"type": "ghtar",
"repo": "axboe/liburing",
"prefer-stable": true,
"license": {
"type": "file",
"path": "COPYING"
}
},
"libuuid": { "libuuid": {
"type": "git", "type": "git",
"url": "https://github.com/static-php/libuuid.git", "url": "https://github.com/static-php/libuuid.git",
@ -890,7 +919,7 @@
"postgresql": { "postgresql": {
"type": "ghtagtar", "type": "ghtagtar",
"repo": "postgres/postgres", "repo": "postgres/postgres",
"match": "REL_16_\\d+", "match": "REL_18_\\d+",
"license": { "license": {
"type": "file", "type": "file",
"path": "COPYRIGHT" "path": "COPYRIGHT"
@ -981,7 +1010,6 @@
}, },
"snappy": { "snappy": {
"type": "git", "type": "git",
"repo": "google/snappy",
"rev": "main", "rev": "main",
"url": "https://github.com/google/snappy", "url": "https://github.com/google/snappy",
"license": { "license": {
@ -990,9 +1018,8 @@
} }
}, },
"spx": { "spx": {
"type": "git", "type": "pie",
"rev": "master", "repo": "noisebynorthwest/php-spx",
"url": "https://github.com/static-php/php-spx.git",
"path": "php-src/ext/spx", "path": "php-src/ext/spx",
"license": { "license": {
"type": "file", "type": "file",
@ -1146,14 +1173,5 @@
"type": "file", "type": "file",
"path": "LICENSE" "path": "LICENSE"
} }
},
"liburing": {
"type": "ghtar",
"repo": "axboe/liburing",
"prefer-stable": true,
"license": {
"type": "file",
"path": "COPYING"
}
} }
} }

View File

@ -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: 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`. - `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. - `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. - `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. 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 ## Download type - ghrel
ghrel will download files from Assets uploaded in GitHub Release. ghrel will download files from Assets uploaded in GitHub Release.

View File

@ -21,19 +21,30 @@ The following is the architecture support situation, where :gear: represents sup
| Windows | :gear: :computer: | | | Windows | :gear: :computer: | |
| FreeBSD | :computer: | :computer: | | FreeBSD | :computer: | :computer: |
Among them, Linux is currently only tested on Ubuntu, Debian, and Alpine distributions, Current supported PHP versions for compilation:
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.
There are two architectures for macOS: `x86_64` and `Arm`, but binaries compiled on one architecture cannot be directly used on the other architecture. > :warning: Partial support, there may be issues with new beta versions and old versions.
Rosetta 2 cannot guarantee that programs compiled with `Arm` architecture can fully run on `x86_64` environment. >
> :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. ## PHP Support Versions
Simply select the earlier version when downloading.
However, due to some extensions and special components that have stopped supporting earlier versions of PHP, 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.
static-php-cli will not explicitly support earlier versions. 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. We recommend that you compile the latest PHP version possible for a better experience.

View File

@ -30,6 +30,7 @@ static-php-cli 的下载资源模块是一个主要的功能,它包含了所
这里最主要的字段是 `type`,目前它支持的类型有: 这里最主要的字段是 `type`,目前它支持的类型有:
- `url`: 直接使用 URL 下载,例如:`https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz` - `url`: 直接使用 URL 下载,例如:`https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz`
- `pie`: 使用 PIEPHP Installer for Extensions标准从 Packagist 下载 PHP 扩展。
- `ghrel`: 使用 GitHub Release API 下载,即从 GitHub 项目发布的最新版本中上传的附件下载。 - `ghrel`: 使用 GitHub Release API 下载,即从 GitHub 项目发布的最新版本中上传的附件下载。
- `ghtar`: 使用 GitHub Release API 下载,与 `ghrel` 不同的是,`ghtar` 是从项目的最新 Release 中找 `source code (tar.gz)` 下载的。 - `ghtar`: 使用 GitHub Release API 下载,与 `ghrel` 不同的是,`ghtar` 是从项目的最新 Release 中找 `source code (tar.gz)` 下载的。
- `ghtagtar`: 使用 GitHub Release API 下载,与 `ghtar` 相比,`ghtagtar` 可以从 `tags` 列表找最新的,并下载 `tar.gz` 格式的源码(因为有些项目只使用了 `tag` 发布版本)。 - `ghtagtar`: 使用 GitHub Release API 下载,与 `ghtar` 相比,`ghtagtar` 可以从 `tags` 列表找最新的,并下载 `tar.gz` 格式的源码(因为有些项目只使用了 `tag` 发布版本)。
@ -77,6 +78,36 @@ url 类型的资源指的是从 URL 直接下载文件。
} }
``` ```
## 下载类型 - pie
PIEPHP 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
ghrel 会从 GitHub Release 中上传的 Assets 下载文件。首先使用 GitHub Release API 获取最新版本,然后根据正则匹配方式下载相应的文件。 ghrel 会从 GitHub Release 中上传的 Assets 下载文件。首先使用 GitHub Release API 获取最新版本,然后根据正则匹配方式下载相应的文件。

View File

@ -19,16 +19,30 @@ static-php-cli 是一个用于构建静态编译的 PHP 二进制的工具,目
| Windows | :gear: :computer: | | | Windows | :gear: :computer: | |
| FreeBSD | :computer: | :computer: | | FreeBSD | :computer: | :computer: |
其中Linux 目前仅在 Ubuntu、Debian、Alpine 发行版测试通过,其他发行版未进行测试,不能保证编译成功。 当前支持编译的 PHP 版本:
对于未经过测试的发行版,可以使用 Docker 等方式本地编译,避免环境导致的问题。
macOS 下支持 x86_64 和 Arm 两种架构,但在其中一个架构上编译的二进制无法直接在另一个架构上使用。 > :warning: 部分支持,对于新的测试版和旧版本可能存在问题。
Rosetta 2 不能保证 Arm 架构编译的程序可以完全运行在 x86_64 环境下。 >
> :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 支持版本 ## 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 停止了支持,所以 static-php-cli 不会明确支持早期版本。
我们推荐你编译尽可能新的 PHP 版本,以获得更好的体验。 我们推荐你编译尽可能新的 PHP 版本,以获得更好的体验。

View File

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

View File

@ -10,6 +10,7 @@ use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\LockFile; use SPC\store\LockFile;
use SPC\store\pkg\GoXcaddy;
use SPC\store\SourceManager; use SPC\store\SourceManager;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
use SPC\util\AttributeMapper; use SPC\util\AttributeMapper;
@ -127,27 +128,6 @@ abstract class BuilderBase
return array_filter($this->exts, fn ($ext) => $ext->isBuildStatic()); 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. * Set libs only mode.
* *
@ -507,8 +487,7 @@ abstract class BuilderBase
throw new WrongUsageException('FrankenPHP SAPI is only available on Linux and macOS!'); throw new WrongUsageException('FrankenPHP SAPI is only available on Linux and macOS!');
} }
// frankenphp needs package go-xcaddy installed // frankenphp needs package go-xcaddy installed
$pkg_dir = PKG_ROOT_PATH . '/go-xcaddy-' . arch2gnu(php_uname('m')) . '-' . osfamily2shortname(); if (!GoXcaddy::isInstalled()) {
if (!file_exists("{$pkg_dir}/bin/go") || !file_exists("{$pkg_dir}/bin/xcaddy")) {
global $argv; global $argv;
throw new WrongUsageException("FrankenPHP SAPI requires the go-xcaddy package, please install it first: {$argv[0]} install-pkg go-xcaddy"); throw new WrongUsageException("FrankenPHP SAPI requires the go-xcaddy package, please install it first: {$argv[0]} install-pkg go-xcaddy");
} }

View File

@ -10,8 +10,6 @@ use SPC\exception\ValidationException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\toolchain\ToolchainManager;
use SPC\toolchain\ZigToolchain;
use SPC\util\SPCConfigUtil; use SPC\util\SPCConfigUtil;
use SPC\util\SPCTarget; use SPC\util\SPCTarget;
@ -223,12 +221,24 @@ class Extension
public function patchBeforeSharedMake(): bool public function patchBeforeSharedMake(): bool
{ {
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], array_map(fn ($l) => $l->getName(), $this->builder->getLibs())); $config = (new SPCConfigUtil($this->builder))->config([$this->getName()], array_map(fn ($l) => $l->getName(), $this->builder->getLibs()));
[$staticLibs] = $this->splitLibsIntoStaticAndShared($config['libs']); [$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']);
FileSystem::replaceFileRegex( $lstdcpp = str_contains($sharedLibs, '-l:libstdc++.a') ? '-l:libstdc++.a' : null;
$this->source_dir . '/Makefile', $lstdcpp ??= str_contains($sharedLibs, '-lstdc++') ? '-lstdc++' : '';
'/^(.*_SHARED_LIBADD\s*=.*)$/m',
'$1 ' . trim($staticLibs) $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')) { if ($objs = getenv('SPC_EXTRA_RUNTIME_OBJECTS')) {
FileSystem::replaceFileRegex( FileSystem::replaceFileRegex(
$this->source_dir . '/Makefile', $this->source_dir . '/Makefile',
@ -295,7 +305,7 @@ class Extension
// Run compile check if build target is cli // 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 // If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php
$sharedExtensions = $this->getSharedExtensionLoadString(); $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) { if ($ret !== 0) {
throw new ValidationException( throw new ValidationException(
"extension {$this->getName()} failed compile check: php-cli returned {$ret}", "extension {$this->getName()} failed compile check: php-cli returned {$ret}",
@ -325,7 +335,7 @@ class Extension
{ {
// Run compile check if build target is cli // 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 // 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) { if ($ret !== 0) {
throw new ValidationException("extension {$this->getName()} failed compile check: php-cli returned {$ret}", validation_module: "Extension {$this->getName()} sanity check"); 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 public function buildUnixShared(): void
{ {
$config = (new SPCConfigUtil($this->builder))->config( $env = $this->getSharedExtensionEnv();
[$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++';
}
if ($this->patchBeforeSharedPhpize()) { if ($this->patchBeforeSharedPhpize()) {
logger()->info("Extension [{$this->getName()}] patched before shared phpize"); logger()->info("Extension [{$this->getName()}] patched before shared phpize");
} }
@ -436,13 +427,15 @@ class Extension
logger()->info("Extension [{$this->getName()}] patched before shared configure"); logger()->info("Extension [{$this->getName()}] patched before shared configure");
} }
$phpvars = getenv('SPC_EXTRA_PHP_VARS') ?: '';
shell()->cd($this->source_dir) shell()->cd($this->source_dir)
->setEnv($env) ->setEnv($env)
->appendEnv($this->getExtraEnv()) ->appendEnv($this->getExtraEnv())
->exec( ->exec(
'./configure ' . $this->getUnixConfigureArg(true) . './configure ' . $this->getUnixConfigureArg(true) .
' --with-php-config=' . BUILD_BIN_PATH . '/php-config ' . ' --with-php-config=' . BUILD_BIN_PATH . '/php-config ' .
'--enable-shared --disable-static' "--enable-shared --disable-static {$phpvars}"
); );
if ($this->patchBeforeSharedMake()) { if ($this->patchBeforeSharedMake()) {
@ -493,6 +486,30 @@ class Extension
return $this->build_static; 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 protected function addLibraryDependency(string $name, bool $optional = false): void
{ {
$depLib = $this->builder->getLib($name); $depLib = $this->builder->getLib($name);
@ -565,12 +582,12 @@ class Extension
$added = 0; $added = 0;
foreach ($ret as $depName => $dep) { foreach ($ret as $depName => $dep) {
foreach ($dep->getDependencies(true) as $depdepName => $depdep) { foreach ($dep->getDependencies(true) as $depdepName => $depdep) {
if (!in_array($depdepName, array_keys($deps), true)) { if (!array_key_exists($depdepName, $deps)) {
$deps[$depdepName] = $depdep; $deps[$depdepName] = $depdep;
++$added; ++$added;
} }
} }
if (!in_array($depName, array_keys($deps), true)) { if (!array_key_exists($depName, $deps)) {
$deps[$depName] = $dep; $deps[$depName] = $dep;
} }
} }

View File

@ -12,7 +12,7 @@ class dba extends Extension
{ {
public function getUnixConfigureArg(bool $shared = false): string 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; return '--enable-dba' . ($shared ? '=shared' : '') . $qdbm;
} }

View File

@ -15,7 +15,11 @@ class gettext extends Extension
public function patchBeforeBuildconf(): bool public function patchBeforeBuildconf(): bool
{ {
if ($this->builder instanceof MacOSBuilder) { 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; return true;
} }

View File

@ -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'); GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . ' -Wno-strict-prototypes');
return true; return true;
} }
protected function getSharedExtensionEnv(): array
{
$env = parent::getSharedExtensionEnv();
$env['CPPFLAGS'] = $env['CXXFLAGS'] . ' -Wno-attributes';
return $env;
}
} }

View File

@ -17,6 +17,10 @@ class memcached extends Extension
'--with-libmemcached-dir=' . BUILD_ROOT_PATH . ' ' . '--with-libmemcached-dir=' . BUILD_ROOT_PATH . ' ' .
'--disable-memcached-sasl ' . '--disable-memcached-sasl ' .
'--enable-memcached-json ' . '--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'; '--with-system-fastlz';
} }
} }

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\extension; namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\exception\ValidationException;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\CustomExt; use SPC\util\CustomExt;
@ -23,12 +24,7 @@ class readline extends Extension
public function getUnixConfigureArg(bool $shared = false): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$enable = '--without-libedit --with-readline=' . BUILD_ROOT_PATH; return '--with-libedit --without-readline';
if ($this->builder->getPHPVersionID() < 84000) {
// the check uses `char rl_pending_input()` instead of `extern int rl_pending_input`, which makes LTO fail
$enable .= ' ac_cv_lib_readline_rl_pending_input=yes';
}
return $enable;
} }
public function buildUnixShared(): void public function buildUnixShared(): void
@ -39,4 +35,13 @@ class readline extends Extension
} }
parent::buildUnixShared(); 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));
}
}
} }

View File

@ -6,6 +6,8 @@ namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\toolchain\ToolchainManager;
use SPC\toolchain\ZigToolchain;
use SPC\util\CustomExt; use SPC\util\CustomExt;
#[CustomExt('simdjson')] #[CustomExt('simdjson')]
@ -17,7 +19,7 @@ class simdjson extends Extension
FileSystem::replaceFileRegex( FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/ext/simdjson/config.m4', SOURCE_PATH . '/php-src/ext/simdjson/config.m4',
'/php_version=(`.*`)$/m', '/php_version=(`.*`)$/m',
'php_version=' . strval($php_ver) 'php_version=' . $php_ver
); );
FileSystem::replaceFileStr( FileSystem::replaceFileStr(
SOURCE_PATH . '/php-src/ext/simdjson/config.m4', SOURCE_PATH . '/php-src/ext/simdjson/config.m4',
@ -31,4 +33,18 @@ class simdjson extends Extension
); );
return true; 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;
}
} }

View File

@ -13,7 +13,7 @@ class spx extends Extension
{ {
public function getUnixConfigureArg(bool $shared = false): string public function getUnixConfigureArg(bool $shared = false): string
{ {
$arg = '--enable-spx' . ($shared ? '=shared' : ''); $arg = '--enable-SPX' . ($shared ? '=shared' : '');
if ($this->builder->getLib('zlib') !== null) { if ($this->builder->getLib('zlib') !== null) {
$arg .= ' --with-zlib-dir=' . BUILD_ROOT_PATH; $arg .= ' --with-zlib-dir=' . BUILD_ROOT_PATH;
} }
@ -29,4 +29,20 @@ class spx extends Extension
); );
return true; 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;
}
} }

View File

@ -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-pgsql') ? ' --enable-swoole-pgsql' : ' --disable-swoole-pgsql';
$arg .= $this->builder->getExt('swoole-hook-mysql') ? ' --enable-mysqlnd' : ' --disable-mysqlnd'; $arg .= $this->builder->getExt('swoole-hook-mysql') ? ' --enable-mysqlnd' : ' --disable-mysqlnd';
$arg .= $this->builder->getExt('swoole-hook-sqlite') ? ' --enable-swoole-sqlite' : ' --disable-swoole-sqlite'; $arg .= $this->builder->getExt('swoole-hook-sqlite') ? ' --enable-swoole-sqlite' : ' --disable-swoole-sqlite';
if ($this->builder->getExt('swoole-hook-odbc')) { if ($this->builder->getExt('swoole-hook-odbc')) {
$config = (new SPCConfigUtil($this->builder, ['libs_only_deps' => true]))->config([], ['unixodbc']); $config = (new SPCConfigUtil($this->builder, ['libs_only_deps' => true]))->config([], ['unixodbc']);
$arg .= ' --with-swoole-odbc=unixODBC,' . BUILD_ROOT_PATH . ' SWOOLE_ODBC_LIBS="' . $config['libs'] . '"'; $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') { if (SPCTarget::getTargetOS() === 'Darwin') {
$arg .= ' ac_cv_lib_pthread_pthread_barrier_init=no'; $arg .= ' ac_cv_lib_pthread_pthread_barrier_init=no';
} }

View File

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

View File

@ -7,6 +7,7 @@ namespace SPC\builder\linux;
use SPC\builder\unix\UnixBuilderBase; use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\PatchException; use SPC\exception\PatchException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
use SPC\util\GlobalEnvManager; use SPC\util\GlobalEnvManager;
@ -95,6 +96,8 @@ class LinuxBuilder extends UnixBuilderBase
// 'LIBS' => SPCTarget::getRuntimeLibs(), // do not pass static libraries here yet, they may contain polyfills for libc functions! // 'LIBS' => 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'; $embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
if ($embed_type !== 'static' && SPCTarget::isStatic()) { if ($embed_type !== 'static' && SPCTarget::isStatic()) {
throw new WrongUsageException( throw new WrongUsageException(
@ -103,22 +106,22 @@ class LinuxBuilder extends UnixBuilderBase
); );
} }
shell()->cd(SOURCE_PATH . '/php-src') $this->seekPhpSrcLogFileOnException(fn () => shell()->cd(SOURCE_PATH . '/php-src')->exec(
->exec( $php_configure_env . ' ' .
$php_configure_env . ' ' . getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' . ($enableCli ? '--enable-cli ' : '--disable-cli ') .
($enableCli ? '--enable-cli ' : '--disable-cli ') . ($enableFpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') .
($enableFpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') . ($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') . ($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') . ($enableCgi ? '--enable-cgi ' : '--disable-cgi ') .
($enableCgi ? '--enable-cgi ' : '--disable-cgi ') . $config_file_path .
$config_file_path . $config_file_scan_dir .
$config_file_scan_dir . $json_74 .
$json_74 . $zts .
$zts . $maxExecutionTimers .
$maxExecutionTimers . $phpvars . ' ' .
$this->makeStaticExtensionArgs() . ' ' $this->makeStaticExtensionArgs() . ' '
); ));
$this->emitPatchPoint('before-php-make'); $this->emitPatchPoint('before-php-make');
SourcePatcher::patchBeforeMake($this); SourcePatcher::patchBeforeMake($this);
@ -148,16 +151,22 @@ class LinuxBuilder extends UnixBuilderBase
} }
$this->buildEmbed(); $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) { if ($enableFrankenphp) {
logger()->info('building frankenphp'); logger()->info('building frankenphp');
$this->buildFrankenphp(); $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) public function testPHP(int $build_target = BUILD_TARGET_NONE)
@ -171,11 +180,19 @@ class LinuxBuilder extends UnixBuilderBase
*/ */
protected function buildCli(): void 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()); $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') shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile') ->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)) { if (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-unneeded php'); 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 protected function buildCgi(): void
{ {
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars()); $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') shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile') ->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)) { if (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/cgi')->exec('strip --strip-unneeded php-cgi'); 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 // patch fake cli for micro
$vars['EXTRA_CFLAGS'] .= $enable_fake_cli; $vars['EXTRA_CFLAGS'] .= $enable_fake_cli;
$vars = SystemUtil::makeEnvVarString($vars); $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') shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} micro"); ->exec("make {$concurrency} {$vars} micro");
$this->processMicroUPX(); $this->processMicroUPX();
@ -247,10 +264,10 @@ class LinuxBuilder extends UnixBuilderBase
protected function buildFpm(): void protected function buildFpm(): void
{ {
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars()); $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') shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile') ->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)) { if (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-unneeded php-fpm'); 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 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()); $vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
shell()->cd(SOURCE_PATH . '/php-src') shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec('sed -i "s|^EXTENSION_DIR = .*|EXTENSION_DIR = /' . basename(BUILD_MODULES_PATH) . '|" 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') ?: ''; $ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: '';
$libDir = BUILD_LIB_PATH; $libDir = BUILD_LIB_PATH;
@ -327,7 +349,7 @@ class LinuxBuilder extends UnixBuilderBase
$target = "{$libDir}/{$realLibName}"; $target = "{$libDir}/{$realLibName}";
if (file_exists($target)) { if (file_exists($target)) {
[, $output] = shell()->execWithResult('readelf -d ' . escapeshellarg($target)); [, $output] = shell()->execWithResult('readelf -d ' . escapeshellarg($target));
$output = join("\n", $output); $output = implode("\n", $output);
if (preg_match('/SONAME.*\[(.+)]/', $output, $sonameMatch)) { if (preg_match('/SONAME.*\[(.+)]/', $output, $sonameMatch)) {
$currentSoname = $sonameMatch[1]; $currentSoname = $sonameMatch[1];
if ($currentSoname !== basename($target)) { 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')); $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' : ''; $static = SPCTarget::isStatic() ? '-all-static' : '';
$lib = BUILD_LIB_PATH; $lib = BUILD_LIB_PATH;
return [ return array_filter([
'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), 'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'),
'EXTRA_LIBS' => $config['libs'], 'EXTRA_LIBS' => $config['libs'],
'EXTRA_LDFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), 'EXTRA_LDFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'),
'EXTRA_LDFLAGS_PROGRAM' => "-L{$lib} {$static} -pie", 'EXTRA_LDFLAGS_PROGRAM' => "-L{$lib} {$static} -pie",
]; ]);
} }
/** /**

View File

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

View File

@ -4,9 +4,6 @@ declare(strict_types=1);
namespace SPC\builder\linux\library; namespace SPC\builder\linux\library;
/**
* gmp is a template library class for unix
*/
class readline extends LinuxLibraryBase class readline extends LinuxLibraryBase
{ {
use \SPC\builder\unix\library\readline; use \SPC\builder\unix\library\readline;

View File

@ -7,6 +7,7 @@ namespace SPC\builder\macos;
use SPC\builder\macos\library\MacOSLibraryBase; use SPC\builder\macos\library\MacOSLibraryBase;
use SPC\builder\unix\UnixBuilderBase; use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
use SPC\util\GlobalEnvManager; use SPC\util\GlobalEnvManager;
@ -118,9 +119,8 @@ class MacOSBuilder extends UnixBuilderBase
} }
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static'; $embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
shell()->cd(SOURCE_PATH . '/php-src') $this->seekPhpSrcLogFileOnException(fn () => shell()->cd(SOURCE_PATH . '/php-src')->exec(
->exec( getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
($enableCli ? '--enable-cli ' : '--disable-cli ') . ($enableCli ? '--enable-cli ' : '--disable-cli ') .
($enableFpm ? '--enable-fpm ' : '--disable-fpm ') . ($enableFpm ? '--enable-fpm ' : '--disable-fpm ') .
($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') . ($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
@ -132,7 +132,7 @@ class MacOSBuilder extends UnixBuilderBase
$zts . $zts .
$this->makeStaticExtensionArgs() . ' ' . $this->makeStaticExtensionArgs() . ' ' .
$envs_build_php $envs_build_php
); ));
$this->emitPatchPoint('before-php-make'); $this->emitPatchPoint('before-php-make');
SourcePatcher::patchBeforeMake($this); SourcePatcher::patchBeforeMake($this);
@ -162,15 +162,15 @@ class MacOSBuilder extends UnixBuilderBase
} }
$this->buildEmbed(); $this->buildEmbed();
} }
if ($enableFrankenphp) {
logger()->info('building frankenphp');
$this->buildFrankenphp();
}
$shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared')))); $shared_extensions = array_map('trim', array_filter(explode(',', $this->getOption('build-shared'))));
if (!empty($shared_extensions)) { if (!empty($shared_extensions)) {
logger()->info('Building shared extensions ...'); logger()->info('Building shared extensions ...');
$this->buildSharedExts(); $this->buildSharedExts();
} }
if ($enableFrankenphp) {
logger()->info('building frankenphp');
$this->buildFrankenphp();
}
} }
public function testPHP(int $build_target = BUILD_TARGET_NONE) public function testPHP(int $build_target = BUILD_TARGET_NONE)
@ -187,8 +187,8 @@ class MacOSBuilder extends UnixBuilderBase
$vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars()); $vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$shell = shell()->cd(SOURCE_PATH . '/php-src'); $shell = shell()->cd(SOURCE_PATH . '/php-src');
$SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make'; $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
$shell->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} cli"); $shell->exec("make {$concurrency} {$vars} cli");
if (!$this->getOption('no-strip', false)) { if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/cli/php')->exec('strip -S sapi/cli/php'); $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()); $vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$shell = shell()->cd(SOURCE_PATH . '/php-src'); $shell = shell()->cd(SOURCE_PATH . '/php-src');
$SPC_CMD_PREFIX_PHP_MAKE = getenv('SPC_CMD_PREFIX_PHP_MAKE') ?: 'make'; $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
$shell->exec("{$SPC_CMD_PREFIX_PHP_MAKE} {$vars} cgi"); $shell->exec("make {$concurrency} {$vars} cgi");
if (!$this->getOption('no-strip', false)) { if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/cgi/php-cgi')->exec('strip -S sapi/cgi/php-cgi'); $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'); $shell = shell()->cd(SOURCE_PATH . '/php-src');
// build // 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 // strip
if (!$this->getOption('no-strip', false)) { if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/micro/micro.sfx')->exec('strip -S sapi/micro/micro.sfx'); $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()); $vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$shell = shell()->cd(SOURCE_PATH . '/php-src'); $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)) { if (!$this->getOption('no-strip', false)) {
$shell->exec('dsymutil -f sapi/fpm/php-fpm')->exec('strip -S sapi/fpm/php-fpm'); $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 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()); $vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars());
$concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : '';
shell()->cd(SOURCE_PATH . '/php-src') 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') { if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') {
$AR = getenv('AR') ?: 'ar'; $AR = getenv('AR') ?: 'ar';
@ -280,10 +287,10 @@ class MacOSBuilder extends UnixBuilderBase
private function getMakeExtraVars(): array 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')); $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_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'),
'EXTRA_LDFLAGS_PROGRAM' => '-L' . BUILD_LIB_PATH, 'EXTRA_LDFLAGS_PROGRAM' => '-L' . BUILD_LIB_PATH,
'EXTRA_LIBS' => $config['libs'], 'EXTRA_LIBS' => $config['libs'],
]; ]);
} }
} }

View File

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

View File

@ -4,9 +4,6 @@ declare(strict_types=1);
namespace SPC\builder\macos\library; namespace SPC\builder\macos\library;
/**
* gmp is a template library class for unix
*/
class readline extends MacOSLibraryBase class readline extends MacOSLibraryBase
{ {
use \SPC\builder\unix\library\readline; use \SPC\builder\unix\library\readline;

View File

@ -6,6 +6,7 @@ namespace SPC\builder\unix;
use SPC\builder\BuilderBase; use SPC\builder\BuilderBase;
use SPC\builder\linux\SystemUtil as LinuxSystemUtil; use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
use SPC\exception\SPCException;
use SPC\exception\SPCInternalException; use SPC\exception\SPCInternalException;
use SPC\exception\ValidationException; use SPC\exception\ValidationException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
@ -312,7 +313,7 @@ abstract class UnixBuilderBase extends BuilderBase
protected function buildFrankenphp(): void protected function buildFrankenphp(): void
{ {
GlobalEnvManager::addPathIfNotExists(GoXcaddy::getEnvironment()['PATH']); GlobalEnvManager::addPathIfNotExists(GoXcaddy::getPath());
$this->processFrankenphpApp(); $this->processFrankenphpApp();
$nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : '';
$nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : '';
@ -339,11 +340,11 @@ abstract class UnixBuilderBase extends BuilderBase
} }
} }
$debugFlags = $this->getOption('no-strip') ? '-w -s ' : ''; $debugFlags = $this->getOption('no-strip') ? '-w -s ' : '';
$extLdFlags = "-extldflags '-pie{$dynamic_exports}'"; $extLdFlags = "-extldflags '-pie{$dynamic_exports} {$this->arch_ld_flags}'";
$muslTags = ''; $muslTags = '';
$staticFlags = ''; $staticFlags = '';
if (SPCTarget::isStatic()) { 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,'; $muslTags = 'static_build,';
$staticFlags = '-static-pie'; $staticFlags = '-static-pie';
} }
@ -351,7 +352,6 @@ abstract class UnixBuilderBase extends BuilderBase
$config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list); $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'); $cflags = "{$this->arch_c_flags} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS');
$libs = $config['libs']; $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. // 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') || if ((str_contains((string) getenv('SPC_DEFAULT_C_FLAGS'), '-fprofile') ||
str_contains((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), '-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'; $cflags .= ' -Wno-error=missing-profile';
$libs .= ' -lgcov'; $libs .= ' -lgcov';
} }
$env = [ $env = [...[
'CGO_ENABLED' => '1', 'CGO_ENABLED' => '1',
'CGO_CFLAGS' => clean_spaces($cflags), 'CGO_CFLAGS' => clean_spaces($cflags),
'CGO_LDFLAGS' => "{$this->arch_ld_flags} {$staticFlags} {$config['ldflags']} {$libs}", 'CGO_LDFLAGS' => "{$this->arch_ld_flags} {$staticFlags} {$config['ldflags']} {$libs}",
@ -369,12 +369,7 @@ abstract class UnixBuilderBase extends BuilderBase
"v{$frankenPhpVersion} PHP {$libphpVersion} Caddy'\\\" " . "v{$frankenPhpVersion} PHP {$libphpVersion} Caddy'\\\" " .
"-tags={$muslTags}nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", "-tags={$muslTags}nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}",
'LD_LIBRARY_PATH' => BUILD_LIB_PATH, 'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
]; ], ...GoXcaddy::getEnvironment()];
foreach (GoXcaddy::getEnvironment() as $key => $value) {
if ($key !== 'PATH') {
$env[$key] = $value;
}
}
shell()->cd(BUILD_BIN_PATH) shell()->cd(BUILD_BIN_PATH)
->setEnv($env) ->setEnv($env)
->exec("xcaddy build --output frankenphp {$xcaddyModules}"); ->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;
}
}
} }

View File

@ -17,7 +17,7 @@ trait attr
->exec('libtoolize --force --copy') ->exec('libtoolize --force --copy')
->exec('./autogen.sh || autoreconf -if') ->exec('./autogen.sh || autoreconf -if')
->configure('--disable-nls') ->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); $this->patchPkgconfPrefix(['libattr.pc'], PKGCONF_PATCH_PREFIX);
} }
} }

View File

@ -31,7 +31,7 @@ trait gettext
$autoconf->addConfigureArgs('--disable-threads'); $autoconf->addConfigureArgs('--disable-threads');
} }
$autoconf->configure()->make(); $autoconf->configure()->make(dir: $this->getSourceDir() . '/gettext-runtime/intl');
$this->patchLaDependencyPrefix(); $this->patchLaDependencyPrefix();
} }
} }

View File

@ -12,6 +12,11 @@ trait imagemagick
{ {
protected function build(): void 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) $ac = UnixAutoconfExecutor::create($this)
->optionalLib('libzip', ...ac_with_args('zip')) ->optionalLib('libzip', ...ac_with_args('zip'))
->optionalLib('libjpeg', ...ac_with_args('jpeg')) ->optionalLib('libjpeg', ...ac_with_args('jpeg'))
@ -32,7 +37,7 @@ trait imagemagick
); );
// special: linux-static target needs `-static` // special: linux-static target needs `-static`
$ldflags = SPCTarget::isStatic() ? ('-static -ldl') : '-ldl'; $ldflags = SPCTarget::isStatic() ? '-static -ldl' : '-ldl';
// special: macOS needs -iconv // special: macOS needs -iconv
$libs = SPCTarget::getTargetOS() === 'Darwin' ? '-liconv' : ''; $libs = SPCTarget::getTargetOS() === 'Darwin' ? '-liconv' : '';
@ -45,6 +50,8 @@ trait imagemagick
$ac->configure()->make(); $ac->configure()->make();
$this->builder->arch_ld_flags = $original_ldflags;
$filelist = [ $filelist = [
'ImageMagick.pc', 'ImageMagick.pc',
'ImageMagick-7.Q16HDRI.pc', 'ImageMagick-7.Q16HDRI.pc',

View File

@ -26,7 +26,7 @@ trait libacl
->exec('libtoolize --force --copy') ->exec('libtoolize --force --copy')
->exec('./autogen.sh || autoreconf -if') ->exec('./autogen.sh || autoreconf -if')
->configure('--disable-nls', '--disable-tests') ->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); $this->patchPkgconfPrefix(['libacl.pc'], PKGCONF_PATCH_PREFIX);
} }
} }

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\store\FileSystem;
use SPC\util\executor\UnixAutoconfExecutor;
trait libedit
{
public function patchBeforeBuild(): bool
{
FileSystem::replaceFileRegex(
$this->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']);
}
}

View File

@ -10,7 +10,13 @@ trait libiconv
{ {
protected function build(): void 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(); $this->patchLaDependencyPrefix();
} }
} }

View File

@ -20,17 +20,17 @@ trait libxml2
"-DZLIB_INCLUDE_DIR={$this->getIncludeDir()}", "-DZLIB_INCLUDE_DIR={$this->getIncludeDir()}",
'-DLIBXML2_WITH_ZLIB=OFF', '-DLIBXML2_WITH_ZLIB=OFF',
) )
->optionalLib('icu', ...cmake_boolean_args('LIBXML2_WITH_ICU'))
->optionalLib('xz', ...cmake_boolean_args('LIBXML2_WITH_LZMA')) ->optionalLib('xz', ...cmake_boolean_args('LIBXML2_WITH_LZMA'))
->addConfigureArgs( ->addConfigureArgs(
'-DLIBXML2_WITH_ICONV=ON', '-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_PYTHON=OFF',
'-DLIBXML2_WITH_PROGRAMS=OFF', '-DLIBXML2_WITH_PROGRAMS=OFF',
'-DLIBXML2_WITH_TESTS=OFF', '-DLIBXML2_WITH_TESTS=OFF',
); );
if ($this instanceof LinuxLibraryBase) { if ($this instanceof LinuxLibraryBase) {
$cmake->addConfigureArgs('-DIconv_IS_BUILD_IN=OFF'); $cmake->addConfigureArgs('-DIconv_IS_BUILT_IN=OFF');
} }
$cmake->build(); $cmake->build();

View File

@ -22,6 +22,7 @@ trait libzip
'-DBUILD_EXAMPLES=OFF', '-DBUILD_EXAMPLES=OFF',
'-DBUILD_REGRESS=OFF', '-DBUILD_REGRESS=OFF',
'-DBUILD_TOOLS=OFF', '-DBUILD_TOOLS=OFF',
'-DBUILD_OSSFUZZ=OFF',
) )
->build(); ->build();
$this->patchPkgconfPrefix(['libzip.pc'], PKGCONF_PATCH_PREFIX); $this->patchPkgconfPrefix(['libzip.pc'], PKGCONF_PATCH_PREFIX);

View File

@ -38,7 +38,7 @@ trait ncurses
->make(); ->make();
$final = FileSystem::scanDirFiles(BUILD_BIN_PATH, relative: true); $final = FileSystem::scanDirFiles(BUILD_BIN_PATH, relative: true);
// Remove the new files // Remove the new files
$new_files = array_diff($final, $filelist); $new_files = array_diff($final, $filelist ?: []);
foreach ($new_files as $file) { foreach ($new_files as $file) {
@unlink(BUILD_BIN_PATH . '/' . $file); @unlink(BUILD_BIN_PATH . '/' . $file);
} }

View File

@ -4,93 +4,83 @@ declare(strict_types=1);
namespace SPC\builder\unix\library; namespace SPC\builder\unix\library;
use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\exception\BuildFailureException;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\PkgConfigUtil;
use SPC\util\SPCConfigUtil;
use SPC\util\SPCTarget; use SPC\util\SPCTarget;
trait postgresql 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 protected function build(): void
{ {
$builddir = BUILD_ROOT_PATH; $libs = array_map(fn ($x) => $x->getName(), $this->getDependencies());
$envs = ''; $spc = new SPCConfigUtil($this->getBuilder(), ['no_php' => true, 'libs_only_deps' => true]);
$packages = 'zlib openssl readline libxml-2.0'; $config = $spc->config(libraries: $libs, include_suggest_lib: $this->builder->getOption('with-suggested-libs'));
$optional_packages = [
'zstd' => 'libzstd', $env_vars = [
'ldap' => 'ldap', 'CFLAGS' => $config['cflags'],
'libxslt' => 'libxslt', 'CPPFLAGS' => '-DPIC',
'icu' => 'icu-i18n', 'LDFLAGS' => $config['ldflags'],
'LIBS' => $config['libs'],
]; ];
$error_exec_cnt = 0;
foreach ($optional_packages as $lib => $pkg) { if ($ldLibraryPath = getenv('SPC_LD_LIBRARY_PATH')) {
if ($this->getBuilder()->getLib($lib)) { $env_vars['LD_LIBRARY_PATH'] = $ldLibraryPath;
$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!');
} }
FileSystem::resetDir($this->source_dir . '/build'); FileSystem::resetDir($this->source_dir . '/build');
$version = $this->getVersion(); // php source relies on the non-private encoding functions in libpgcommon.a
// 16.1 workaround FileSystem::replaceFileStr(
if (version_compare($version, '16.1') >= 0) { "{$this->source_dir}/src/common/Makefile",
# 有静态链接配置 参考文件: src/interfaces/libpq/Makefile '$(OBJS_FRONTEND): CPPFLAGS += -DUSE_PRIVATE_ENCODING_FUNCS',
shell()->cd($this->source_dir . '/build') '$(OBJS_FRONTEND): CPPFLAGS += -UUSE_PRIVATE_ENCODING_FUNCS -DFRONTEND',
->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 . ' !');
}
// configure // configure
shell()->cd($this->source_dir . '/build')->initializeEnv($this) $shell = shell()->cd("{$this->source_dir}/build")->initializeEnv($this)
->appendEnv(['CFLAGS' => $cflags]) ->appendEnv($env_vars)
->exec( ->exec(
"{$envs} ../configure " . '../configure ' .
"--prefix={$builddir} " . "--prefix={$this->getBuildRootPath()} " .
($this->builder->getOption('enable-zts') ? '--enable-thread-safety ' : '--disable-thread-safety ') .
'--enable-coverage=no ' . '--enable-coverage=no ' .
'--with-ssl=openssl ' . '--with-ssl=openssl ' .
'--with-readline ' . '--with-readline ' .
'--with-libxml ' . '--with-libxml ' .
($this->builder->getLib('icu') ? '--with-icu ' : '--without-icu ') . ($this->builder->getLib('icu') ? '--with-icu ' : '--without-icu ') .
($this->builder->getLib('ldap') ? '--with-ldap ' : '--without-ldap ') . ($this->builder->getLib('ldap') ? '--with-ldap ' : '--without-ldap ') .
// '--without-ldap ' .
($this->builder->getLib('libxslt') ? '--with-libxslt ' : '--without-libxslt ') . ($this->builder->getLib('libxslt') ? '--with-libxslt ' : '--without-libxslt ') .
($this->builder->getLib('zstd') ? '--with-zstd ' : '--without-zstd ') . ($this->builder->getLib('zstd') ? '--with-zstd ' : '--without-zstd ') .
'--without-lz4 ' . '--without-lz4 ' .
@ -99,32 +89,29 @@ trait postgresql
'--without-pam ' . '--without-pam ' .
'--without-bonjour ' . '--without-bonjour ' .
'--without-tcl ' '--without-tcl '
) );
->exec($envs . ' make -C src/bin/pg_config install')
->exec($envs . ' make -C src/include install') // patch ldap lib
->exec($envs . ' make -C src/common install') if ($this->builder->getLib('ldap')) {
->exec($envs . ' make -C src/port install') $libs = PkgConfigUtil::getLibsArray('ldap');
->exec($envs . ' make -C src/interfaces/libpq install'); $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 // remove dynamic libs
shell()->cd($this->source_dir . '/build') shell()->cd($this->source_dir . '/build')
->exec("rm -rf {$builddir}/lib/*.so.*") ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.so.*")
->exec("rm -rf {$builddir}/lib/*.so") ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.so")
->exec("rm -rf {$builddir}/lib/*.dylib"); ->exec("rm -rf {$this->getBuildRootPath()}/lib/*.dylib");
FileSystem::replaceFileStr(BUILD_LIB_PATH . '/pkgconfig/libpq.pc', '-lldap', '-lldap -llber'); FileSystem::replaceFileStr("{$this->getLibDir()}/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';
}
} }
} }

View File

@ -223,11 +223,9 @@ class BuildPHPCommand extends BuildCommand
// ---------- When using bin/spc-alpine-docker, the build root path is different from the host system ---------- // ---------- When using bin/spc-alpine-docker, the build root path is different from the host system ----------
$build_root_path = BUILD_ROOT_PATH; $build_root_path = BUILD_ROOT_PATH;
$cwd = getcwd();
$fixed = ''; $fixed = '';
$build_root_path = get_display_path($build_root_path);
if (!empty(getenv('SPC_FIX_DEPLOY_ROOT'))) { 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)'; $fixed = ' (host system)';
} }
if (($rule & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) { if (($rule & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {

View File

@ -5,6 +5,8 @@ declare(strict_types=1);
namespace SPC\command; namespace SPC\command;
use SPC\exception\ValidationException; use SPC\exception\ValidationException;
use SPC\store\pkg\GoXcaddy;
use SPC\store\pkg\Zig;
use SPC\toolchain\ToolchainManager; use SPC\toolchain\ToolchainManager;
use SPC\toolchain\ZigToolchain; use SPC\toolchain\ZigToolchain;
use SPC\util\ConfigValidator; use SPC\util\ConfigValidator;
@ -63,7 +65,7 @@ class CraftCommand extends BuildCommand
} }
} }
// install go and xcaddy for frankenphp // 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'); $retcode = $this->runCommand('install-pkg', 'go-xcaddy');
if ($retcode !== 0) { if ($retcode !== 0) {
$this->output->writeln('<error>craft go-xcaddy failed</error>'); $this->output->writeln('<error>craft go-xcaddy failed</error>');
@ -71,7 +73,7 @@ class CraftCommand extends BuildCommand
} }
} }
// install zig if requested // install zig if requested
if (ToolchainManager::getToolchainClass() === ZigToolchain::class) { if (ToolchainManager::getToolchainClass() === ZigToolchain::class && !Zig::isInstalled()) {
$retcode = $this->runCommand('install-pkg', 'zig'); $retcode = $this->runCommand('install-pkg', 'zig');
if ($retcode !== 0) { if ($retcode !== 0) {
$this->output->writeln('<error>craft zig failed</error>'); $this->output->writeln('<error>craft zig failed</error>');

View File

@ -106,7 +106,7 @@ class DownloadCommand extends BaseCommand
} }
// retry // retry
$retry = intval($this->getOption('retry')); $retry = (int) $this->getOption('retry');
f_putenv('SPC_DOWNLOAD_RETRIES=' . $retry); f_putenv('SPC_DOWNLOAD_RETRIES=' . $retry);
// Use shallow-clone can reduce git resource download // 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); f_passthru((PHP_OS_FAMILY === 'Windows' ? 'rmdir /s /q ' : 'rm -rf ') . DOWNLOAD_PATH);
} }
// unzip command check // 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('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'); $this->output->writeln('You can use "bin/spc doctor" command to check and install required tools');
return static::FAILURE; return static::FAILURE;

View File

@ -28,7 +28,7 @@ class LinuxToolCheckList
public const TOOLS_DEBIAN = [ public const TOOLS_DEBIAN = [
'make', 'bison', 're2c', 'flex', 'make', 'bison', 're2c', 'flex',
'git', 'autoconf', 'automake', 'autopoint', 'git', 'autoconf', 'automake', 'autopoint',
'tar', 'unzip', 'gzip', 'tar', 'unzip', 'gzip', 'gcc', 'g++',
'bzip2', 'cmake', 'patch', 'bzip2', 'cmake', 'patch',
'xz', 'libtoolize', 'which', 'xz', 'libtoolize', 'which',
'patchelf', 'patchelf',
@ -37,7 +37,7 @@ class LinuxToolCheckList
public const TOOLS_RHEL = [ public const TOOLS_RHEL = [
'perl', 'make', 'bison', 're2c', 'flex', 'perl', 'make', 'bison', 're2c', 'flex',
'git', 'autoconf', 'automake', 'git', 'autoconf', 'automake',
'tar', 'unzip', 'gzip', 'gcc', 'tar', 'unzip', 'gzip', 'gcc', 'g++',
'bzip2', 'cmake', 'patch', 'which', 'bzip2', 'cmake', 'patch', 'which',
'xz', 'libtool', 'gettext-devel', 'xz', 'libtool', 'gettext-devel',
'patchelf', 'patchelf',
@ -53,7 +53,8 @@ class LinuxToolCheckList
'base-devel' => 'automake', 'base-devel' => 'automake',
'gettext-devel' => 'gettextize', 'gettext-devel' => 'gettextize',
'gettext-dev' => '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 */ /** @noinspection PhpUnused */
@ -65,7 +66,7 @@ class LinuxToolCheckList
$required = match ($distro['dist']) { $required = match ($distro['dist']) {
'alpine' => self::TOOLS_ALPINE, 'alpine' => self::TOOLS_ALPINE,
'redhat' => self::TOOLS_RHEL, '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, 'arch' => self::TOOLS_ARCH,
default => self::TOOLS_DEBIAN, default => self::TOOLS_DEBIAN,
}; };
@ -81,14 +82,14 @@ class LinuxToolCheckList
return CheckResult::ok(); 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 public function checkCMakeVersion(): ?CheckResult
{ {
$ver = get_cmake_version(); $ver = get_cmake_version();
if ($ver === null) { if ($ver === null) {
return CheckResult::fail('Failed to get cmake version'); 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::fail('cmake version is too low (' . $ver . '), please update it manually!');
} }
return CheckResult::ok($ver); return CheckResult::ok($ver);

View File

@ -137,13 +137,18 @@ class ExceptionHandler
self::logError("\n----------------------------------------\n"); 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)) { 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() !== []) { if ($e->getExtraLogFiles() !== []) {
foreach ($e->getExtraLogFiles() as $key => $file) { 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')) { if (!defined('DEBUG_MODE')) {

View File

@ -16,6 +16,42 @@ use SPC\util\SPCTarget;
*/ */
class Downloader class Downloader
{ {
/**
* Get latest version from PIE config (Packagist)
*
* @param string $name Source name
* @param array $source Source meta info: [repo]
* @return array<int, string> [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 * Get latest version from BitBucket tag
* *
@ -65,19 +101,19 @@ class Downloader
url: "https://api.github.com/repos/{$source['repo']}/{$type}", url: "https://api.github.com/repos/{$source['repo']}/{$type}",
hooks: [[CurlHook::class, 'setupGithubToken']], hooks: [[CurlHook::class, 'setupGithubToken']],
retries: self::getRetryAttempts() retries: self::getRetryAttempts()
), true); ), true, 512, JSON_THROW_ON_ERROR);
$url = null; $url = null;
for ($i = 0; $i < count($data); ++$i) { foreach ($data as $rel) {
if (($data[$i]['prerelease'] ?? false) === true && ($source['prefer-stable'] ?? false)) { if (($rel['prerelease'] ?? false) === true && ($source['prefer-stable'] ?? false)) {
continue; continue;
} }
if (!($source['match'] ?? null)) { if (!($source['match'] ?? null)) {
$url = $data[$i]['tarball_url'] ?? null; $url = $rel['tarball_url'] ?? null;
break; break;
} }
if (preg_match('|' . $source['match'] . '|', $data[$i]['tarball_url'])) { if (preg_match('|' . $source['match'] . '|', $rel['tarball_url'])) {
$url = $data[$i]['tarball_url']; $url = $rel['tarball_url'];
break; break;
} }
} }
@ -232,7 +268,8 @@ class Downloader
$quiet = !defined('DEBUG_MODE') ? '-q --quiet' : ''; $quiet = !defined('DEBUG_MODE') ? '-q --quiet' : '';
$git = SPC_GIT_EXEC; $git = SPC_GIT_EXEC;
$shallow = defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : ''; $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 { try {
self::registerCancelEvent(function () use ($download_path) { 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}\""); f_passthru("{$git} clone {$quiet} --config core.autocrlf=false --branch \"{$branch}\" {$shallow} {$recursive} \"{$url}\" \"{$download_path}\"");
if ($submodules !== null) { if ($submodules !== null) {
$depth_flag = defined('GIT_SHALLOW_CLONE') ? '--depth 1' : '';
foreach ($submodules as $submodule) { 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) { } catch (SPCException $e) {
@ -315,91 +353,14 @@ class Downloader
if (self::isAlreadyDownloaded($name, $force, SPC_DOWNLOAD_PACKAGE)) { if (self::isAlreadyDownloaded($name, $force, SPC_DOWNLOAD_PACKAGE)) {
return; return;
} }
self::downloadByType($pkg['type'], $name, $pkg, $force, SPC_DOWNLOAD_PACKAGE);
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());
}
} }
/** /**
* Download source * Download source
* *
* @param string $name source name * @param string $name source name
* @param null|array{ * @param null|array{
* type: string, * type: string,
* repo: ?string, * repo: ?string,
* url: ?string, * url: ?string,
@ -414,7 +375,7 @@ class Downloader
* path: ?string, * path: ?string,
* text: ?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 bool $force Whether to force download (default: false)
* @param int $download_as Lock source type (default: SPC_LOCK_SOURCE) * @param int $download_as Lock source type (default: SPC_LOCK_SOURCE)
*/ */
@ -437,80 +398,7 @@ class Downloader
return; return;
} }
try { self::downloadByType($source['type'], $name, $source, $force, $download_as);
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());
}
} }
/** /**
@ -711,4 +599,109 @@ class Downloader
} }
return false; 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<string>,
* 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()}");
}
}
} }

View File

@ -274,7 +274,7 @@ class FileSystem
public static function convertWinPathToMinGW(string $path): string public static function convertWinPathToMinGW(string $path): string
{ {
if (preg_match('/^[A-Za-z]:/', $path)) { 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; return $path;
} }
@ -314,8 +314,13 @@ class FileSystem
$sub_file = self::convertPath($dir . '/' . $v); $sub_file = self::convertPath($dir . '/' . $v);
if (is_dir($sub_file) && $recursive) { if (is_dir($sub_file) && $recursive) {
# 如果是 目录 且 递推 , 则递推添加下级文件 # 如果是 目录 且 递推 , 则递推添加下级文件
$list = array_merge($list, self::scanDirFiles($sub_file, $recursive, $relative)); $sub_list = self::scanDirFiles($sub_file, $recursive, $relative);
} elseif (is_file($sub_file) || is_dir($sub_file) && !$recursive && $include_dir) { 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) { if (is_string($relative) && mb_strpos($sub_file, $relative) === 0) {
$list[] = ltrim(mb_substr($sub_file, mb_strlen($relative)), '/\\'); $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 public static function writeFile(string $path, mixed $content, ...$args): bool|int|string
{ {
$dir = pathinfo(self::convertPath($path), PATHINFO_DIRNAME); $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); throw new FileSystemException('Write file failed, cannot create parent directory: ' . $dir);
} }
return file_put_contents($path, $content, ...$args); 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"), '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"), 'tgz', 'gz' => f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1"),
'bz2' => f_passthru("tar -xjf {$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), default => throw new FileSystemException('unknown archive format: ' . $filename),
}; };
} elseif (PHP_OS_FAMILY === 'Windows') { } elseif (PHP_OS_FAMILY === 'Windows') {
@ -594,7 +599,7 @@ class FileSystem
match (self::extname($filename)) { match (self::extname($filename)) {
'tar' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"), '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"), '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}"), 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 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 */ /* @phpstan-ignore-next-line */
match ($source_type) { match ($source_type) {
SPC_SOURCE_ARCHIVE => self::extractArchive($filename, $extract_path), SPC_SOURCE_ARCHIVE => self::extractArchive($filename, $extract_path),
@ -639,4 +644,107 @@ class FileSystem
SPC_SOURCE_LOCAL => symlink(self::convertPath($filename), $extract_path), 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);
}
} }

View File

@ -155,8 +155,8 @@ class LockFile
* @param string $name Source name * @param string $name Source name
* @param array{ * @param array{
* source_type: string, * source_type: string,
* dirname: ?string, * dirname?: ?string,
* filename: ?string, * filename?: ?string,
* move_path: ?string, * move_path: ?string,
* lock_as: int * lock_as: int
* } $data Source data * } $data Source data

View File

@ -44,7 +44,7 @@ class SourcePatcher
} }
foreach ($builder->getLibs() as $lib) { foreach ($builder->getLibs() as $lib) {
if ($lib->patchBeforeBuildconf() === true) { if ($lib->patchBeforeBuildconf() === true) {
logger()->info("Library [{$lib->getName()}]patched before buildconf"); logger()->info("Library [{$lib->getName()}] patched before buildconf");
} }
} }
// patch windows php 8.1 bug // patch windows php 8.1 bug

View File

@ -30,10 +30,14 @@ abstract class CustomPackage
/** /**
* Get the environment variables this package needs to be usable. * Get the environment variables this package needs to be usable.
* PATH needs to be appended, rather than replaced.
*/ */
abstract public static function getEnvironment(): array; abstract public static function getEnvironment(): array;
/**
* Get the PATH required to use this package.
*/
abstract public static function getPath(): ?string;
abstract public static function isInstalled(): bool; abstract public static function isInstalled(): bool;
/** /**

View File

@ -13,18 +13,7 @@ class GoXcaddy extends CustomPackage
{ {
public static function isInstalled(): bool public static function isInstalled(): bool
{ {
$arch = arch2gnu(php_uname('m')); $folder = PKG_ROOT_PATH . '/go-xcaddy';
$os = match (PHP_OS_FAMILY) {
'Windows' => 'win',
'Darwin' => 'macos',
'BSD' => 'freebsd',
default => 'linux',
};
$packageName = "go-xcaddy-{$arch}-{$os}";
$pkgroot = PKG_ROOT_PATH;
$folder = "{$pkgroot}/{$packageName}";
return is_dir($folder) && is_file("{$folder}/bin/go") && is_file("{$folder}/bin/xcaddy"); return is_dir($folder) && is_file("{$folder}/bin/go") && is_file("{$folder}/bin/xcaddy");
} }
@ -59,7 +48,7 @@ class GoXcaddy extends CustomPackage
'macos' => 'darwin', 'macos' => 'darwin',
default => throw new \InvalidArgumentException('Unsupported OS: ' . $name), default => throw new \InvalidArgumentException('Unsupported OS: ' . $name),
}; };
$go_version = '1.24.4'; $go_version = '1.25.0';
$config = [ $config = [
'type' => 'url', 'type' => 'url',
'url' => "https://go.dev/dl/go{$go_version}.{$os}-{$arch}.tar.gz", '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 public function extract(string $name): void
{ {
$pkgroot = PKG_ROOT_PATH; $pkgroot = PKG_ROOT_PATH;
$go_exec = "{$pkgroot}/{$name}/bin/go"; $go_exec = "{$pkgroot}/go-xcaddy/bin/go";
$xcaddy_exec = "{$pkgroot}/{$name}/bin/xcaddy"; $xcaddy_exec = "{$pkgroot}/go-xcaddy/bin/xcaddy";
if (file_exists($go_exec) && file_exists($xcaddy_exec)) { if (file_exists($go_exec) && file_exists($xcaddy_exec)) {
return; return;
} }
$lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true); $lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true);
$source_type = $lock[$name]['source_type']; $source_type = $lock[$name]['source_type'];
$filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']); $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); 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 // install xcaddy without using musl tools, xcaddy build requires dynamic linking
shell() shell()
->appendEnv([ ->appendEnv([
'PATH' => "{$pkgroot}/{$name}/bin:" . $sanitizedPath, 'PATH' => "{$pkgroot}/go-xcaddy/bin:" . $sanitizedPath,
'GOROOT' => "{$pkgroot}/{$name}", 'GOROOT' => "{$pkgroot}/go-xcaddy",
'GOBIN' => "{$pkgroot}/{$name}/bin", 'GOBIN' => "{$pkgroot}/go-xcaddy/bin",
'GOPATH' => "{$pkgroot}/go", 'GOPATH' => "{$pkgroot}/go",
]) ])
->exec('CC=cc go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest'); ->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 public static function getEnvironment(): array
{ {
$arch = arch2gnu(php_uname('m')); $packageName = 'go-xcaddy';
$os = match (PHP_OS_FAMILY) {
'Windows' => 'win',
'Darwin' => 'macos',
'BSD' => 'freebsd',
default => 'linux',
};
$packageName = "go-xcaddy-{$arch}-{$os}";
$pkgroot = PKG_ROOT_PATH; $pkgroot = PKG_ROOT_PATH;
return [ return [
'PATH' => "{$pkgroot}/{$packageName}/bin",
'GOROOT' => "{$pkgroot}/{$packageName}", 'GOROOT' => "{$pkgroot}/{$packageName}",
'GOBIN' => "{$pkgroot}/{$packageName}/bin", 'GOBIN' => "{$pkgroot}/{$packageName}/bin",
'GOPATH' => "{$pkgroot}/go", 'GOPATH' => "{$pkgroot}/go",
]; ];
} }
public static function getPath(): ?string
{
return PKG_ROOT_PATH . '/go-xcaddy/bin';
}
} }

View File

@ -103,7 +103,7 @@ class Zig extends CustomPackage
public function extract(string $name): void public function extract(string $name): void
{ {
$pkgroot = PKG_ROOT_PATH; $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']; $files = ['zig', 'zig-cc', 'zig-c++', 'zig-ar', 'zig-ld.lld', 'zig-ranlib', 'zig-objcopy'];
$all_exist = true; $all_exist = true;
@ -120,7 +120,7 @@ class Zig extends CustomPackage
$lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true); $lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true);
$source_type = $lock[$name]['source_type']; $source_type = $lock[$name]['source_type'];
$filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']); $filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']);
$extract = "{$pkgroot}/{$name}"; $extract = "{$pkgroot}/zig";
FileSystem::extractPackage($name, $source_type, $filename, $extract); FileSystem::extractPackage($name, $source_type, $filename, $extract);
@ -129,34 +129,12 @@ class Zig extends CustomPackage
public static function getEnvironment(): array public static function getEnvironment(): array
{ {
$arch = arch2gnu(php_uname('m')); return [];
$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,
];
} }
private static function getPath(): string public static function getPath(): ?string
{ {
$arch = arch2gnu(php_uname('m')); return PKG_ROOT_PATH . '/zig';
$os = match (PHP_OS_FAMILY) {
'Windows' => 'win',
'Darwin' => 'macos',
'BSD' => 'freebsd',
default => 'linux',
};
$packageName = "zig-{$arch}-{$os}";
return PKG_ROOT_PATH . "/{$packageName}";
} }
private function createZigCcScript(string $bin_dir): void private function createZigCcScript(string $bin_dir): void

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")" 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=() PARSED_ARGS=()
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
@ -19,9 +19,15 @@ while [[ $# -gt 0 ]]; do
ARG_ABS="$(realpath "$ARG" 2>/dev/null || true)" ARG_ABS="$(realpath "$ARG" 2>/dev/null || true)"
[[ "$ARG_ABS" == "$BUILDROOT_ABS" ]] && PARSED_ARGS+=("-I$ARG") || PARSED_ARGS+=("-isystem$ARG") [[ "$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_NAME="${1%%=*}"
OPT_VALUE="${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//-/_}" OPT_VALUE="${OPT_VALUE//-/_}"
PARSED_ARGS+=("${OPT_NAME}=${OPT_VALUE}") PARSED_ARGS+=("${OPT_NAME}=${OPT_VALUE}")
shift shift

View File

@ -16,11 +16,11 @@ class PhpSource extends CustomSourceBase
{ {
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4'; $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4';
if ($major === '8.5') { 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') { } elseif ($major === 'git') {
Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force); Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force);
} else { } 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( $info = json_decode(Downloader::curlExec(
url: "https://www.php.net/releases/index.php?json&version={$major_version}", 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); ), true);
if (!isset($info['version'])) { if (!isset($info['version'])) {
throw new DownloaderException("Version {$major_version} not found."); throw new DownloaderException("Version {$major_version} not found.");

View File

@ -42,10 +42,10 @@ class ZigToolchain implements ToolchainInterface
public function afterInit(): void 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)'); 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 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') ?: ''; $cflags = getenv('SPC_DEFAULT_C_FLAGS') ?: '';
$cxxflags = getenv('SPC_DEFAULT_CXX_FLAGS') ?: ''; $cxxflags = getenv('SPC_DEFAULT_CXX_FLAGS') ?: '';
@ -64,6 +64,11 @@ class ZigToolchain implements ToolchainInterface
$extra_libs = trim($extra_libs . ' -lunwind'); $extra_libs = trim($extra_libs . ' -lunwind');
GlobalEnvManager::putenv("SPC_EXTRA_LIBS={$extra_libs}"); 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 public function getCompilerInfo(): ?string

View File

@ -11,6 +11,150 @@ use Symfony\Component\Yaml\Yaml;
class ConfigValidator 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 * Validate source.json
* *
@ -22,33 +166,20 @@ class ConfigValidator
// Validate basic source type configuration // Validate basic source type configuration
self::validateSourceTypeConfig($src, $name, 'source'); 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 // check if alt is valid
if (isset($src['alt'])) { if (isset($src['alt']) && is_assoc_array($src['alt'])) {
if (!is_assoc_array($src['alt']) && !is_bool($src['alt'])) { // validate alt source recursively
throw new ValidationException("source {$name} alt must be object or boolean"); self::validateSource([$name . '_alt' => $src['alt']]);
}
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");
} }
// check if license is valid // check if license is valid
if (isset($src['license'])) { 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'])) { if (is_assoc_array($src['license'])) {
self::checkSingleLicense($src['license'], $name); self::checkSingleLicense($src['license'], $name);
} elseif (is_list_array($src['license'])) { } elseif (is_list_array($src['license'])) {
@ -58,8 +189,6 @@ class ConfigValidator
} }
self::checkSingleLicense($license, $name); 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)) { if (!is_array($data)) {
throw new ValidationException('lib.json is broken'); throw new ValidationException('lib.json is broken');
} }
// check each lib
foreach ($data as $name => $lib) { foreach ($data as $name => $lib) {
// check if lib is an assoc array
if (!is_assoc_array($lib)) { if (!is_assoc_array($lib)) {
throw new ValidationException("lib {$name} is not an object"); 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']])) { if (isset($lib['source']) && !empty($source_data) && !isset($source_data[$lib['source']])) {
throw new ValidationException("lib {$name} assigns an invalid source: {$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'])) { // Validate basic fields using unified method
throw new ValidationException("lib {$name} source must be string"); self::validateConfigFields($lib, $name, 'lib', self::LIB_FIELDS);
}
// check if [lib-depends|lib-suggests|static-libs|headers|bin][-windows|-unix|-macos|-linux] are valid list array // Validate list array fields with suffixes
$suffixes = ['', '-windows', '-unix', '-macos', '-linux']; $suffixes = ['', '-windows', '-unix', '-macos', '-linux'];
foreach ($suffixes as $suffix) { $fields = ['lib-depends', 'lib-suggests', 'static-libs', 'pkg-configs', 'headers', 'bin'];
if (isset($lib['lib-depends' . $suffix]) && !is_list_array($lib['lib-depends' . $suffix])) { self::validateListArrayFields($lib, $name, 'lib', $fields, $suffixes);
throw new ValidationException("lib {$name} lib-depends must be a list");
} // Validate frameworks (special case without suffix)
if (isset($lib['lib-suggests' . $suffix]) && !is_list_array($lib['lib-suggests' . $suffix])) { if (isset($lib['frameworks'])) {
throw new ValidationException("lib {$name} lib-suggests must be a list"); self::validateFieldType('frameworks', $lib['frameworks'], $name, 'lib');
}
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");
} }
// Check for unknown fields
self::validateAllowedFields($lib, $name, 'lib', self::LIB_FIELDS);
} }
} }
@ -127,61 +241,34 @@ class ConfigValidator
if (!is_array($data)) { if (!is_array($data)) {
throw new ValidationException('ext.json is broken'); throw new ValidationException('ext.json is broken');
} }
// check each extension
foreach ($data as $name => $ext) { foreach ($data as $name => $ext) {
// check if ext is an assoc array
if (!is_assoc_array($ext)) { if (!is_assoc_array($ext)) {
throw new ValidationException("ext {$name} is not an object"); throw new ValidationException("ext {$name} is not an object");
} }
// check if ext has valid type
if (!in_array($ext['type'] ?? '', ['builtin', 'external', 'addon', 'wip'])) { if (!in_array($ext['type'] ?? '', ['builtin', 'external', 'addon', 'wip'])) {
throw new ValidationException("ext {$name} type is invalid"); 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'])) { if (($ext['type'] ?? '') === 'external' && !isset($ext['source'])) {
throw new ValidationException("ext {$name} does not assign any source"); throw new ValidationException("ext {$name} does not assign any source");
} }
// check if source is string
if (isset($ext['source']) && !is_string($ext['source'])) { // Validate basic fields using unified method
throw new ValidationException("ext {$name} source must be string"); self::validateConfigFields($ext, $name, 'ext', self::EXT_FIELDS);
}
// check if support is valid // Validate list array fields with suffixes
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
$suffixes = ['', '-windows', '-unix', '-macos', '-linux']; $suffixes = ['', '-windows', '-unix', '-macos', '-linux'];
foreach ($suffixes as $suffix) { $fields = ['lib-depends', 'lib-suggests', 'ext-depends', 'ext-suggests'];
if (isset($ext['lib-depends' . $suffix]) && !is_list_array($ext['lib-depends' . $suffix])) { self::validateListArrayFields($ext, $name, 'ext', $fields, $suffixes);
throw new ValidationException("ext {$name} lib-depends must be a list");
} // Validate arg-type fields
if (isset($ext['lib-suggests' . $suffix]) && !is_list_array($ext['lib-suggests' . $suffix])) { self::validateArgTypeFields($ext, $name, $suffixes);
throw new ValidationException("ext {$name} lib-suggests must be a list");
} // Check for unknown fields
if (isset($ext['ext-depends' . $suffix]) && !is_list_array($ext['ext-depends' . $suffix])) { self::validateAllowedFields($ext, $name, 'ext', self::EXT_FIELDS);
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");
}
}
}
} }
} }
@ -200,12 +287,11 @@ class ConfigValidator
// Validate basic source type configuration (reuse from source validation) // Validate basic source type configuration (reuse from source validation)
self::validateSourceTypeConfig($pkg, $name, 'pkg'); self::validateSourceTypeConfig($pkg, $name, 'pkg');
// Check pkg-specific fields // Validate all pkg-specific fields using unified method
// check if extract-files is valid self::validateConfigFields($pkg, $name, 'pkg', self::PKG_FIELDS);
// Validate extract-files content (object validation is done by validateFieldType)
if (isset($pkg['extract-files'])) { 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 // check each extract file mapping
foreach ($pkg['extract-files'] as $source => $target) { foreach ($pkg['extract-files'] as $source => $target) {
if (!is_string($source) || !is_string($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'); throw new ValidationException('pre-built.json is broken');
} }
// Check required fields // Validate all fields using unified method
if (!isset($data['repo'])) { self::validateConfigFields($data, 'pre-built', 'pre-built', self::PRE_BUILT_FIELDS);
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');
}
// Check optional prefer-stable field // Check for unknown fields
if (isset($data['prefer-stable']) && !is_bool($data['prefer-stable'])) { self::validateAllowedFields($data, 'pre-built', 'pre-built', self::PRE_BUILT_FIELDS);
throw new ValidationException('pre-built.json [prefer-stable] must be boolean');
}
// Check match pattern fields (at least one must exist) // Check match pattern fields (at least one must exist)
$pattern_fields = ['match-pattern-linux', 'match-pattern-macos', 'match-pattern-windows']; $pattern_fields = ['match-pattern-linux', 'match-pattern-macos', 'match-pattern-windows'];
@ -247,9 +329,6 @@ class ConfigValidator
foreach ($pattern_fields as $field) { foreach ($pattern_fields as $field) {
if (isset($data[$field])) { if (isset($data[$field])) {
$has_pattern = true; $has_pattern = true;
if (!is_string($data[$field])) {
throw new ValidationException("pre-built.json [{$field}] must be string");
}
// Validate pattern contains required placeholders // Validate pattern contains required placeholders
if (!str_contains($data[$field], '{name}')) { if (!str_contains($data[$field], '{name}')) {
throw new ValidationException("pre-built.json [{$field}] must contain {name} placeholder"); throw new ValidationException("pre-built.json [{$field}] must contain {name} placeholder");
@ -403,6 +482,52 @@ class ConfigValidator
return $craft; 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 private static function checkSingleLicense(array $license, string $name): void
{ {
if (!is_assoc_array($license)) { if (!is_assoc_array($license)) {
@ -414,9 +539,6 @@ class ConfigValidator
if (!in_array($license['type'], ['file', 'text'])) { if (!in_array($license['type'], ['file', 'text'])) {
throw new ValidationException("source {$name} license type is invalid"); 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'])) { if ($license['type'] === 'file' && !isset($license['path'])) {
throw new ValidationException("source {$name} license file must have path"); throw new ValidationException("source {$name} license file must have path");
} }
@ -440,68 +562,127 @@ class ConfigValidator
if (!is_string($item['type'])) { if (!is_string($item['type'])) {
throw new ValidationException("{$config_type} {$name} type prop must be string"); 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"); throw new ValidationException("{$config_type} {$name} type [{$item['type']}] is invalid");
} }
// Validate type-specific requirements [$required, $optional] = self::SOURCE_TYPE_FIELDS[$item['type']];
switch ($item['type']) {
case 'filelist': // Check required fields exist
if (!isset($item['url'], $item['regex'])) { foreach ($required as $prop) {
throw new ValidationException("{$config_type} {$name} needs [url] and [regex] props"); 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': foreach ($base_fields as $field) {
if (!isset($item['url'], $item['rev'])) { foreach ($suffixes as $suffix) {
throw new ValidationException("{$config_type} {$name} needs [url] and [rev] props"); $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"); // frameworks is lib-only
} if ($type === 'lib') {
if (isset($item['path']) && !is_string($item['path'])) { $allowed_fields[] = 'frameworks';
throw new ValidationException("{$config_type} {$name} [path] must be string"); }
} }
break;
case 'ghtagtar': // Check each field in item
case 'ghtar': foreach (array_keys($item) as $field) {
if (!isset($item['repo'])) { if (!in_array($field, $allowed_fields)) {
throw new ValidationException("{$config_type} {$name} needs [repo] prop"); throw new ValidationException("{$type} {$name} has unknown field [{$field}]");
} }
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;
} }
} }
} }

View File

@ -40,7 +40,12 @@ class GlobalEnvManager
if (is_unix()) { if (is_unix()) {
self::addPathIfNotExists(BUILD_BIN_PATH); self::addPathIfNotExists(BUILD_BIN_PATH);
self::addPathIfNotExists(PKG_ROOT_PATH . '/bin'); 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(); $ini = self::readIniFile();

View File

@ -6,6 +6,7 @@ namespace SPC\util;
use SPC\builder\BuilderBase; use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider; use SPC\builder\BuilderProvider;
use SPC\builder\Extension;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArgvInput;
@ -87,7 +88,7 @@ class SPCConfigUtil
if (SPCTarget::getTargetOS() === 'Darwin') { if (SPCTarget::getTargetOS() === 'Darwin') {
$libs .= " {$this->getFrameworksString($extensions)}"; $libs .= " {$this->getFrameworksString($extensions)}";
} }
if ($this->builder->hasCpp()) { if ($this->hasCpp($extensions, $libraries)) {
$libcpp = SPCTarget::getTargetOS() === 'Darwin' ? '-lc++' : '-lstdc++'; $libcpp = SPCTarget::getTargetOS() === 'Darwin' ? '-lc++' : '-lstdc++';
$libs = str_replace($libcpp, '', $libs) . " {$libcpp}"; $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 private function getIncludesString(array $libraries): string
{ {
$base = BUILD_INCLUDE_PATH; $base = BUILD_INCLUDE_PATH;

View File

@ -50,18 +50,22 @@ class UnixAutoconfExecutor extends Executor
* @param bool $with_clean Whether to clean before building * @param bool $with_clean Whether to clean before building
* @param array $after_env_vars Environment variables postfix * @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) { 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() : ''; $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) { if ($with_install !== false) {
$this->shell->exec("make {$with_install}"); $shell->exec("make {$with_install}");
} }
return $this->shell; return $shell;
}); });
} }

View File

@ -205,7 +205,7 @@ SET(CMAKE_INSTALL_PREFIX "{$root}")
SET(CMAKE_INSTALL_LIBDIR "lib") SET(CMAKE_INSTALL_LIBDIR "lib")
set(PKG_CONFIG_EXECUTABLE "{$pkgConfigExecutable}") 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_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

View File

@ -8,6 +8,7 @@ use SPC\builder\freebsd\library\BSDLibraryBase;
use SPC\builder\linux\library\LinuxLibraryBase; use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\builder\macos\library\MacOSLibraryBase; use SPC\builder\macos\library\MacOSLibraryBase;
use SPC\exception\SPCInternalException; use SPC\exception\SPCInternalException;
use SPC\util\SPCTarget;
use ZM\Logger\ConsoleColor; use ZM\Logger\ConsoleColor;
/** /**
@ -28,6 +29,7 @@ class UnixShell extends Shell
public function exec(string $cmd): static public function exec(string $cmd): static
{ {
$cmd = clean_spaces($cmd);
/* @phpstan-ignore-next-line */ /* @phpstan-ignore-next-line */
logger()->info(ConsoleColor::yellow('[EXEC] ') . ConsoleColor::green($cmd)); logger()->info(ConsoleColor::yellow('[EXEC] ') . ConsoleColor::green($cmd));
$original_command = $cmd; $original_command = $cmd;
@ -48,7 +50,7 @@ class UnixShell extends Shell
'CFLAGS' => $library->getLibExtraCFlags(), 'CFLAGS' => $library->getLibExtraCFlags(),
'CXXFLAGS' => $library->getLibExtraCXXFlags(), 'CXXFLAGS' => $library->getLibExtraCXXFlags(),
'LDFLAGS' => $library->getLibExtraLdFlags(), 'LDFLAGS' => $library->getLibExtraLdFlags(),
'LIBS' => $library->getLibExtraLibs(), 'LIBS' => $library->getLibExtraLibs() . SPCTarget::getRuntimeLibs(),
]); ]);
return $this; return $this;
} }

View File

@ -4,20 +4,40 @@ declare(strict_types=1);
assert(function_exists('gettext')); assert(function_exists('gettext'));
assert(function_exists('bindtextdomain')); assert(function_exists('bindtextdomain'));
assert(function_exists('bind_textdomain_codeset'));
assert(function_exists('textdomain')); assert(function_exists('textdomain'));
if (!is_dir('locale/en_US/LC_MESSAGES/')) { foreach (['en_US', 'en_GB'] as $lc) {
mkdir('locale/en_US/LC_MESSAGES/', 0755, true); $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=='; // Probe for an available English locale
file_put_contents('locale/en_US/LC_MESSAGES/test.mo', base64_decode($mo)); $candidates = [
} 'en_US.UTF-8', 'en_US.utf8', 'en_US.utf-8', 'en_US',
putenv('LANG=en_US'); 'en_GB.UTF-8', 'en_GB.utf8', 'en_GB.utf-8', 'en_GB',
assert(setlocale(LC_ALL, 'en_US.utf-8') === 'en_US.utf-8'); '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'; $domain = 'test';
bindtextdomain($domain, 'locale/'); bindtextdomain($domain, 'locale/');
bind_textdomain_codeset($domain, 'UTF-8');
textdomain($domain); textdomain($domain);
assert(gettext(json_decode('"\u793a\u4f8b"', true)) === 'Example'); $src = json_decode('"\u793a\u4f8b"', true);
assert(gettext($src) === 'Example');

View File

@ -245,6 +245,23 @@ function clean_spaces(string $string): string
return trim(preg_replace('/\s+/', ' ', $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). * 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 // Including color codes, cursor control, clear screen and other control sequences
return preg_replace('/\e\[[0-9;]*[a-zA-Z]/', '', $text); 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}");
}

View File

@ -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)

View File

@ -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.4 available, multiple for matrix)
$test_php_version = [ $test_php_version = [
// '8.1', '8.1',
// '8.2', // '8.2',
// '8.3', // '8.3',
'8.4', // '8.4',
// '8.5', '8.5',
// 'git', // '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 = [ $test_os = [
'macos-13', // bin/spc for x86_64 'macos-15-intel', // bin/spc for x86_64
// 'macos-14', // bin/spc for arm64
'macos-15', // bin/spc for arm64 'macos-15', // bin/spc for arm64
'ubuntu-latest', // bin/spc-alpine-docker for x86_64 'ubuntu-latest', // bin/spc-alpine-docker for x86_64
'ubuntu-22.04', // bin/spc-gnu-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-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 // 'windows-latest', // .\bin\spc.ps1
]; ];
// whether enable thread safe // whether enable thread safe
$zts = true; $zts = false;
$no_strip = 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`). // If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
$extensions = match (PHP_OS_FAMILY) { $extensions = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => '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', '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`). // If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`).
$shared_extensions = match (PHP_OS_FAMILY) { $shared_extensions = match (PHP_OS_FAMILY) {
'Linux' => 'zip', 'Linux' => '',
'Darwin' => '', 'Darwin' => '',
'Windows' => '', 'Windows' => '',
}; };
@ -156,17 +155,13 @@ if ($shared_extensions) {
switch ($argv[2] ?? null) { switch ($argv[2] ?? null) {
case 'ubuntu-22.04': case 'ubuntu-22.04':
case 'ubuntu-22.04-arm': case 'ubuntu-22.04-arm':
case 'macos-15':
case 'macos-15-intel':
$shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' '; $shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
break; break;
case 'ubuntu-24.04': case 'ubuntu-24.04':
case 'ubuntu-24.04-arm': case 'ubuntu-24.04-arm':
break; break;
case 'macos-13':
case 'macos-14':
case 'macos-15':
$shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
$no_strip = true;
break;
default: default:
$shared_cmd = ''; $shared_cmd = '';
break; break;

View File

@ -62,12 +62,6 @@ class BuilderTest extends TestCase
$this->assertInstanceOf(Extension::class, $this->builder->getExt('mbregex')); $this->assertInstanceOf(Extension::class, $this->builder->getExt('mbregex'));
} }
public function testHasCpp()
{
// mbregex doesn't have cpp
$this->assertFalse($this->builder->hasCpp());
}
public function testMakeExtensionArgs() public function testMakeExtensionArgs()
{ {
$this->assertStringContainsString('--enable-mbstring', $this->builder->makeStaticExtensionArgs()); $this->assertStringContainsString('--enable-mbstring', $this->builder->makeStaticExtensionArgs());

View File

@ -50,7 +50,6 @@ class ConfigValidatorTest extends TestCase
'filename' => 'test.tar.gz', 'filename' => 'test.tar.gz',
'path' => 'test/path', 'path' => 'test/path',
'provide-pre-built' => true, 'provide-pre-built' => true,
'prefer-stable' => false,
'license' => [ 'license' => [
'type' => 'file', 'type' => 'file',
'path' => 'LICENSE', 'path' => 'LICENSE',

View File

@ -44,6 +44,9 @@ class SPCConfigUtilTest extends TestCase
public function testConfig(): void public function testConfig(): void
{ {
if (PHP_OS_FAMILY !== 'Linux') {
$this->markTestSkipped('SPCConfigUtil tests are only applicable on Linux.');
}
// normal // normal
$result = (new SPCConfigUtil())->config(['bcmath']); $result = (new SPCConfigUtil())->config(['bcmath']);
$this->assertStringContainsString(BUILD_ROOT_PATH . '/include', $result['cflags']); $this->assertStringContainsString(BUILD_ROOT_PATH . '/include', $result['cflags']);