Compare commits

..

62 Commits

Author SHA1 Message Date
henderkes
3b8abda8c9 add libclang.cpu.a 2026-06-09 12:27:58 +07:00
henderkes
5747a5661e build shared extensions before frankenphp 2026-06-02 04:00:57 +00:00
henderkes
d52ba59edc fix SourcePatcher::patchHardcodedINI method signature 2026-05-30 09:08:49 +07:00
henderkes
2d5abd31c1 micro patch for cross-arch 32 bit builds 2026-05-29 23:15:53 +07:00
henderkes
69d0f9b8cc remove SPC_ARCH 2026-05-29 22:38:58 +07:00
henderkes
d846db2ef2 forward v2 fix 2026-05-26 00:51:16 +00:00
henderkes
f06891155c revert 2026-05-24 21:42:41 +07:00
henderkes
15deecd34f Merge remote-tracking branch 'origin/v3' into feat/pgo-v3 2026-05-24 20:26:50 +07:00
henderkes
fe4803cfaf run shared ext builds after frankenphp build 2026-05-24 12:15:40 +07:00
henderkes
80d81079db backport v2 passing correct libraries, not all 2026-05-24 12:15:04 +07:00
henderkes
6ab52a5181 cxxflags in spcconfigutil 2026-05-23 20:46:21 +07:00
henderkes
5bdcd3f562 dont pass all static exts to frankenphp build either 2026-05-23 20:02:24 +07:00
henderkes
203fed65d9 dont pass shared extension packages to SPCConfigUtil for building php (bleeds libpq polyfills into php configure, poisoning e.g. have_strlcat results) 2026-05-23 16:17:12 +07:00
henderkes
1ae989df59 simplify common zig code paths 2026-05-23 16:15:12 +07:00
henderkes
e7fb1e203f backport fixes from v2 2026-05-23 16:15:12 +07:00
Marc
713f8255af mongodb: export PHP_VERSION_ID for in-tree builds (#1156) 2026-05-23 07:35:24 +07:00
Luther Monson
3c24c92d61 mongodb: export PHP_VERSION_ID for in-tree builds
mongo-php-driver 2.3.3 added a config.m4 check that falls back to
php-config when PHP_VERSION_ID is unset in the shell env. In-tree
PHP source builds have no php-config, so configure fails with:

    checking PHP version... configure: error: php-config not found

Set PHP_VERSION_ID from main/php_version.h before configureForUnix
so the lookup short-circuits.
2026-05-22 10:03:43 -07:00
henderkes
d93fdd9707 use github tokens for package downloads 2026-05-22 23:06:25 +07:00
henderkes
c40d069b0c address #1155 2026-05-22 23:05:51 +07:00
henderkes
9d508f1d39 forward port v2 pgo changes 2026-05-22 15:42:51 +07:00
crazywhalecc
a23ad55fe2 Add preInstall stage 2026-05-22 14:03:26 +08:00
crazywhalecc
735f12648e Update discord invite link 2026-05-22 11:23:53 +08:00
Jerry Ma
6fe55a4d6b Merge branch 'v3' into feat/pgo-v3 2026-05-22 10:27:39 +08:00
Marc
f1525c0ca7 Merge branch 'v3' into feat/pgo-v3 2026-05-20 16:55:02 +07:00
henderkes
433043c0c8 fix macOS 2026-05-19 23:22:52 +07:00
henderkes
b38c7b274f install runtime rt after zig init 2026-05-19 22:55:01 +07:00
henderkes
efa7946c14 build in SOURCE_PATH 2026-05-19 20:44:47 +07:00
henderkes
7bb4a09a3c turn llvm-tools into a doctor check. llvm-runtime is a target now 2026-05-19 20:02:13 +07:00
crazywhalecc
3eca044895 Whoops 2026-05-18 12:12:37 +08:00
crazywhalecc
32da708f54 Change gettext-win base to 0.18 (master is 1.0 now) 2026-05-18 12:05:06 +08:00
crazywhalecc
f27ec773a1 Refactor clang runtime bits support for zig integration 2026-05-18 10:55:39 +08:00
crazywhalecc
ce70c0df6a Register artifacts dynamically if not already initialized 2026-05-18 10:54:32 +08:00
crazywhalecc
fdc75cb9fe Allow vendor mode loading default registry file 2026-05-18 10:54:17 +08:00
crazywhalecc
697040b918 Trim base namespace for registry 2026-05-18 10:53:55 +08:00
henderkes
19d1379f7d better cross compile compatibility 2026-05-17 19:52:22 +07:00
henderkes
07aae79cae forward port #1142 2026-05-16 19:16:24 +07:00
henderkes
4b19f4ec95 forward port #1138 2026-05-15 14:54:04 +07:00
henderkes
1707d21569 don't extract local sources 2026-05-15 14:30:58 +07:00
henderkes
70e717adb6 fix version reevaluating regression from v2 2026-05-15 14:05:33 +07:00
henderkes
a88e426623 clean cgo cache before rebuilding frankenphp (reports wrong version from cgo includes, even if they're updated) 2026-05-15 12:54:05 +07:00
henderkes
6fda358c90 --build-frankenphp didn't actually build frankenphp 2026-05-15 12:36:34 +07:00
henderkes
09de4c9c70 drop ldflags fron watcher-c 2026-05-15 11:05:22 +07:00
henderkes
db794bf27b strange shit 2026-05-12 13:18:54 +07:00
henderkes
b880ef7003 allow * stage to subscribe to everything 2026-05-12 12:45:09 +07:00
henderkes
9addbe2c7d oops 2026-05-12 11:41:25 +07:00
henderkes
445c0b36c9 don't add fno-sanitize=undefined for library builds 2026-05-12 11:30:46 +07:00
henderkes
4754faf43e also build curl exe on windows 2026-05-12 11:24:55 +07:00
henderkes
2415b7db35 we don't need this anymore 2026-05-12 11:22:04 +07:00
henderkes
814014e122 array 2026-05-12 11:19:57 +07:00
henderkes
defd50f459 wrong exception type 2026-05-12 11:17:31 +07:00
henderkes
57ef0423d5 fix phpunit failure 2026-05-12 10:57:49 +07:00
henderkes
8453f69eea fix BUILD_CC=cc workaround root cause (minijit trips zig default undefined behaviour sanitizer) 2026-05-12 10:52:23 +07:00
henderkes
c1c34d8c10 trust filesystem, not downloads 2026-05-12 10:38:33 +07:00
henderkes
270e2d6471 cant reset it because of the same reason x( 2026-05-12 10:07:21 +07:00
henderkes
4172508cb9 use {pkg_root_path} for packages, otherwise containers get confused with different pkg_root_path set 2026-05-12 09:54:29 +07:00
henderkes
a585359b28 reset registry 2026-05-11 21:36:17 +07:00
henderkes
bfaa7ebb3a Merge remote-tracking branch 'origin/v3' into feat/pgo-v3 2026-05-11 21:05:54 +07:00
henderkes
7e6e9d869e add pgo capabilities v3 style 2026-05-11 19:06:40 +07:00
henderkes
743934d1fe use resolved dependency tree instead of asking for with_suggests everywhere 2026-05-11 11:35:15 +07:00
henderkes
efdd2a74a5 update libraries to honour user flags 2026-05-11 10:05:33 +07:00
henderkes
6e3267273b better deduplicate_flags 2026-05-11 09:41:25 +07:00
henderkes
4f7694267b toolchain fixes forward port from v2-pgo 2026-05-10 19:27:36 +07:00
164 changed files with 3328 additions and 3553 deletions

24
.gitattributes vendored
View File

@@ -1,24 +0,0 @@
# Force LF line endings for all text files.
# Prevents git from auto-converting LF -> CRLF on Windows checkouts.
* text=auto eol=lf
# Explicit binary files — git won't touch line endings for these
*.phar binary
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.zip binary
*.gz binary
*.tgz binary
*.txz binary
*.xz binary
*.bz2 binary
*.7z binary
*.exe binary
*.dll binary
*.sfx binary
*.so binary
*.a binary
*.o binary

View File

@@ -38,9 +38,6 @@ jobs:
- name: "windows-x64" - name: "windows-x64"
os: "ubuntu-latest" os: "ubuntu-latest"
filename: "spc-windows-x64.exe" filename: "spc-windows-x64.exe"
permissions:
id-token: write
attestations: write
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: "actions/checkout@v5" uses: "actions/checkout@v5"
@@ -108,12 +105,6 @@ jobs:
fi fi
fi fi
- name: "Generate build provenance attestation"
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@v4
with:
subject-path: "${{ github.workspace }}/${{ matrix.operating-system.name == 'windows-x64' && 'spc.exe' || 'spc' }}"
- name: "Copy file" - name: "Copy file"
run: | run: |
if [ "${{ matrix.operating-system.name }}" != "windows-x64" ]; then if [ "${{ matrix.operating-system.name }}" != "windows-x64" ]; then

View File

@@ -1,68 +1,68 @@
name: Docs build test and auto deploy name: Docs build test and auto deploy
on: on:
pull_request: pull_request:
branches: [ "v3" ] branches: [ "v3" ]
types: [ opened, synchronize, reopened ] types: [ opened, synchronize, reopened ]
paths: paths:
- 'config/**.yml' - 'config/**.yml'
- 'docs/**' - 'docs/**'
- 'package.json' - 'package.json'
- 'yarn.lock' - 'yarn.lock'
- '.github/workflows/vitepress-deploy.yml' - '.github/workflows/vitepress-deploy.yml'
push: push:
branches: [ "v3" ] branches: [ "v3" ]
paths: paths:
- 'config/**.yml' - 'config/**.yml'
- 'docs/**' - 'docs/**'
- 'package.json' - 'package.json'
- 'yarn.lock' - 'yarn.lock'
- '.github/workflows/vitepress-deploy.yml' - '.github/workflows/vitepress-deploy.yml'
jobs: jobs:
build: build:
name: Deploy docs name: Deploy docs
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'crazywhalecc/static-php-cli' if: github.repository == 'crazywhalecc/static-php-cli'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
- run: npm install - run: npm install
- name: "Install PHP for official runners" - name: "Install PHP for official runners"
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
coverage: none coverage: none
tools: composer:v2 tools: composer:v2
php-version: 8.4 php-version: 8.4
ini-values: memory_limit=-1 ini-values: memory_limit=-1
extensions: curl, openssl, mbstring extensions: curl, openssl, mbstring
- name: "Get Composer Cache Directory" - name: "Get Composer Cache Directory"
id: composer-cache id: composer-cache
run: | run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: "Cache Composer dependencies" - name: "Cache Composer dependencies"
uses: "actions/cache@v4" uses: "actions/cache@v4"
with: with:
path: "${{ steps.composer-cache.outputs.dir }}" path: "${{ steps.composer-cache.outputs.dir }}"
key: "php-8.2-locked-composer-${{ hashFiles('**/composer.lock') }}" key: "php-8.2-locked-composer-${{ hashFiles('**/composer.lock') }}"
restore-keys: | restore-keys: |
php-8.2-locked-composer php-8.2-locked-composer
- name: "Install Locked Dependencies" - name: "Install Locked Dependencies"
run: "composer install --no-interaction --no-progress" run: "composer install --no-interaction --no-progress"
- name: Build - name: Build
run: npm run docs:build run: npm run docs:build
# Deploy to GitHub Pages only when the workflow is triggered by a push to the v3 branch # Deploy to GitHub Pages only when the workflow is triggered by a push to the v3 branch
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v3
if: github.event_name == 'push' && github.ref == 'refs/heads/v3' if: github.event_name == 'push' && github.ref == 'refs/heads/v3'
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/.vitepress/dist publish_dir: docs/.vitepress/dist

View File

@@ -1,156 +1,156 @@
# StaticPHP # StaticPHP
[![Chinese readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87%20%F0%9F%87%A8%F0%9F%87%B3-moccasin?style=flat-square)](README-zh.md) [![Chinese readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87%20%F0%9F%87%A8%F0%9F%87%B3-moccasin?style=flat-square)](README-zh.md)
[![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md) [![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md)
[![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)
[![Discord](https://img.shields.io/discord/nrSRbpMJ?label=Discord&logo=discord&style=flat-square)](https://discord.gg/xf6Rd4pEAk) [![Discord](https://img.shields.io/discord/nrSRbpMJ?label=Discord&logo=discord&style=flat-square)](https://discord.gg/xf6Rd4pEAk)
**StaticPHP** 是一个强大的工具,用于构建可移植的可执行文件,包括 PHP、扩展等。 **StaticPHP** 是一个强大的工具,用于构建可移植的可执行文件,包括 PHP、扩展等。
> [!IMPORTANT] > [!IMPORTANT]
> 我们正准备发布 **v3** 版本,届时项目名称将从 **static-php-cli** 更改为 **StaticPHP**。 > 我们正准备发布 **v3** 版本,届时项目名称将从 **static-php-cli** 更改为 **StaticPHP**。
> 此分支对应 v3 版本。v2 版本请查看 [v2 分支](https://github.com/crazywhalecc/static-php-cli/tree/main)。 > 此分支对应 v3 版本。v2 版本请查看 [v2 分支](https://github.com/crazywhalecc/static-php-cli/tree/main)。
> 请更新您的参考资料,并关注正式版发布。 > 请更新您的参考资料,并关注正式版发布。
## 特性 ## 特性
- :elephant: 支持多个 PHP 版本 - PHP 8.1, 8.2, 8.3, 8.4, 8.5 - :elephant: 支持多个 PHP 版本 - PHP 8.1, 8.2, 8.3, 8.4, 8.5
- :handbag: 构建零依赖的单文件 PHP 可执行程序 - :handbag: 构建零依赖的单文件 PHP 可执行程序
- :hamburger: 构建 **[phpmicro](https://github.com/static-php/phpmicro)** 自解压可执行文件(将 PHP 二进制和源码合并为单个文件) - :hamburger: 构建 **[phpmicro](https://github.com/static-php/phpmicro)** 自解压可执行文件(将 PHP 二进制和源码合并为单个文件)
- :pill: 自动构建环境检查器,支持自动修复 - :pill: 自动构建环境检查器,支持自动修复
- :zap: 支持 `Linux``macOS``Windows` - :zap: 支持 `Linux``macOS``Windows`
- :wrench: 通过 vendor 模式和自定义注册表实现便捷扩展 - :wrench: 通过 vendor 模式和自定义注册表实现便捷扩展
- :books: 智能依赖管理 - :books: 智能依赖管理
- 📦 自包含 `spc` 可执行文件,便于自安装 - 📦 自包含 `spc` 可执行文件,便于自安装
- :fire: 支持 100+ 热门 [PHP 扩展](https://static-php.dev/en/guide/extensions.html) - :fire: 支持 100+ 热门 [PHP 扩展](https://static-php.dev/en/guide/extensions.html)
- :floppy_disk: 支持 UPX 压缩(二进制体积可缩小 30-50% - :floppy_disk: 支持 UPX 压缩(二进制体积可缩小 30-50%
**单文件独立 php-cli** **单文件独立 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 代码与 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">
## 快速开始 ## 快速开始
### 1. 下载 spc 二进制 ### 1. 下载 spc 二进制
```bash ```bash
# For Linux x86_64 # For Linux x86_64
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-x86_64 curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-x86_64
# For Linux aarch64 # For Linux aarch64
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-aarch64 curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-aarch64
# macOS x86_64 (Intel) # macOS x86_64 (Intel)
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-macos-x86_64 curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple) # macOS aarch64 (Apple)
curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-macos-aarch64 curl -fsSL -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-macos-aarch64
# Windows (x86_64, win10 build 17063 or later, please install VS2022 first) # Windows (x86_64, win10 build 17063 or later, please install VS2022 first)
curl.exe -fsSL -o spc.exe https://dl.static-php.dev/v3/spc-bin/nightly/spc-windows-x64.exe curl.exe -fsSL -o spc.exe https://dl.static-php.dev/v3/spc-bin/nightly/spc-windows-x64.exe
``` ```
对于 macOS 和 Linux请先添加可执行权限 对于 macOS 和 Linux请先添加可执行权限
```bash ```bash
chmod +x ./spc chmod +x ./spc
``` ```
### 2. 构建静态 PHP ### 2. 构建静态 PHP
首先,创建 `craft.yml` 文件,并从 [扩展列表](https://static-php.dev/en/guide/extensions.html) 或 [命令生成器](https://static-php.dev/en/guide/cli-generator.html) 指定要包含的扩展: 首先,创建 `craft.yml` 文件,并从 [扩展列表](https://static-php.dev/en/guide/extensions.html) 或 [命令生成器](https://static-php.dev/en/guide/cli-generator.html) 指定要包含的扩展:
```yml ```yml
# PHP version support: 8.1, 8.2, 8.3, 8.4, 8.5 # PHP version support: 8.1, 8.2, 8.3, 8.4, 8.5
php-version: 8.5 php-version: 8.5
# Put your extension list here # 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" 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: sapi:
- cli - cli
- micro - micro
download-options: download-options:
parallel: 10 parallel: 10
``` ```
运行命令: 运行命令:
```bash ```bash
./spc craft ./spc craft
# 输出完整控制台日志 # 输出完整控制台日志
./spc craft -vvv ./spc craft -vvv
``` ```
### 3. 静态 PHP 使用 ### 3. 静态 PHP 使用
现在你可以将 StaticPHP 构建的二进制复制到另一台机器并在无依赖环境下运行: 现在你可以将 StaticPHP 构建的二进制复制到另一台机器并在无依赖环境下运行:
``` ```
# php-cli # php-cli
buildroot/bin/php -v buildroot/bin/php -v
# phpmicro # phpmicro
echo '<?php echo "Hello world!\n";' > a.php echo '<?php echo "Hello world!\n";' > a.php
./spc micro:combine a.php -O my-app ./spc micro:combine a.php -O my-app
./my-app ./my-app
``` ```
## 文档 ## 文档
当前 README 包含基础用法。有关 StaticPHP 的完整功能集, 当前 README 包含基础用法。有关 StaticPHP 的完整功能集,
请访问 <https://static-php.dev>。 请访问 <https://static-php.dev>。
## 直接下载 ## 直接下载
如果你暂时不想构建,或只想先测试,可以从 [Actions](https://github.com/static-php/hosted/actions/workflows/build-php-bulk.yml) 下载示例预编译产物,或从自托管服务器下载。 如果你暂时不想构建,或只想先测试,可以从 [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-bulk.yml) 下载示例预编译产物,或从自托管服务器下载。
我们为每个 PHP 版本提供 2 种扩展集合: 我们为每个 PHP 版本提供 2 种扩展集合:
- **gigantic**:尽可能包含更多扩展,二进制大小约 100-150MB。 - **gigantic**:尽可能包含更多扩展,二进制大小约 100-150MB。
- **base**:仅包含 StaticPHP 自身使用的少量扩展,二进制大小约 10MB。 - **base**:仅包含 StaticPHP 自身使用的少量扩展,二进制大小约 10MB。
> WIP > WIP
### 在线构建(使用 GitHub Actions ### 在线构建(使用 GitHub Actions
当上方直接下载的二进制无法满足你的需求时, 当上方直接下载的二进制无法满足你的需求时,
你可以使用 GitHub Actions 轻松构建静态编译的 PHP 你可以使用 GitHub Actions 轻松构建静态编译的 PHP
并同时自定义要编译的扩展列表。 并同时自定义要编译的扩展列表。
1. Fork 此仓库。 1. Fork 此仓库。
2. 进入项目的 Actions 并选择 `CI` 2. 进入项目的 Actions 并选择 `CI`
3. 选择 `Run workflow`,填写你要编译的 PHP 版本、目标类型和扩展列表。(扩展用逗号分隔,例如 `bcmath,curl,mbstring` 3. 选择 `Run workflow`,填写你要编译的 PHP 版本、目标类型和扩展列表。(扩展用逗号分隔,例如 `bcmath,curl,mbstring`
4. 等待工作流执行完成后,进入对应运行记录并下载 `Artifacts` 4. 等待工作流执行完成后,进入对应运行记录并下载 `Artifacts`
如果你启用 `debug`,构建时将输出所有日志,包括编译日志,便于排查问题。 如果你启用 `debug`,构建时将输出所有日志,包括编译日志,便于排查问题。
> 我们也计划在未来提供可复用的 GitHub Actions 工作流, > 我们也计划在未来提供可复用的 GitHub Actions 工作流,
> 这样你无需 fork 本项目,也能在自己的仓库中轻松构建 static PHP。 > 这样你无需 fork 本项目,也能在自己的仓库中轻松构建 static PHP。
## 贡献 ## 贡献
如果你需要的扩展缺失,可以创建 issue。 如果你需要的扩展缺失,可以创建 issue。
如果你熟悉本项目,也欢迎发起 pull request。 如果你熟悉本项目,也欢迎发起 pull request。
如果你想贡献文档,请直接编辑 `docs/` 如果你想贡献文档,请直接编辑 `docs/`
## 赞助本项目 ## 赞助本项目
你可以通过 [GitHub Sponsor](https://github.com/crazywhalecc) 赞助我或我的项目。你捐赠的一部分将用于维护 **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 许可证。 本项目本身采用 MIT 许可证。
一些新添加的扩展和依赖可能来自其他项目。 一些新添加的扩展和依赖可能来自其他项目。
这些源码文件头部也可能包含额外的 LICENSE 和 AUTHOR 信息。 这些源码文件头部也可能包含额外的 LICENSE 和 AUTHOR 信息。
请在编译后使用 `bin/spc dump-license` 命令导出项目中使用的开源许可证, 请在编译后使用 `bin/spc dump-license` 命令导出项目中使用的开源许可证,
并遵守对应项目的 LICENSE。 并遵守对应项目的 LICENSE。

View File

@@ -104,7 +104,7 @@ see <https://static-php.dev>.
## Direct Download ## Direct Download
If you do not want to build yet or just want to test first, you can download example pre-compiled artifacts from [Actions](https://github.com/static-php/hosted/actions/workflows/build-php-bulk.yml) or from a self-hosted server. If you do not want to build yet or just want to test first, you can download example pre-compiled artifacts from [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-bulk.yml) or from a self-hosted server.
We offer 2 types of extension sets for each PHP version: We offer 2 types of extension sets for each PHP version:

View File

@@ -1,116 +1,116 @@
param ( param (
[string] ${action} [string] ${action}
) )
function AddToPath { function AddToPath {
param ( param (
[string]$pathToAdd [string]$pathToAdd
) )
$currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User') $currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
if ($currentPath -notlike "*$pathToAdd*") { if ($currentPath -notlike "*$pathToAdd*") {
$newPath = $currentPath + ";$pathToAdd" $newPath = $currentPath + ";$pathToAdd"
[System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User') [System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host "Added '$pathToAdd' to Path." Write-Host "Added '$pathToAdd' to Path."
Write-Host "To remove path, use: " -NoNewline Write-Host "To remove path, use: " -NoNewline
Write-Host "bin/setup-runtime remove-path" -ForegroundColor Cyan Write-Host "bin/setup-runtime remove-path" -ForegroundColor Cyan
} else { } else {
Write-Host "Path already exists." Write-Host "Path already exists."
} }
} }
function RemoveFromPath { function RemoveFromPath {
param ( param (
[string]$pathToRemove [string]$pathToRemove
) )
$currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User') $currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
if ($currentPath -like "*$pathToRemove*") { if ($currentPath -like "*$pathToRemove*") {
$newPath = $currentPath -replace [regex]::Escape(';' + $pathToRemove), '' $newPath = $currentPath -replace [regex]::Escape(';' + $pathToRemove), ''
[System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User') [System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host "Removed Path '$pathToRemove'" Write-Host "Removed Path '$pathToRemove'"
} else { } else {
Write-Host "Path '$pathToRemove' not in Path" Write-Host "Path '$pathToRemove' not in Path"
} }
} }
# working dir # working dir
$WorkingDir = (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Definition)) $WorkingDir = (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Definition))
if ($action -eq 'add-path') { if ($action -eq 'add-path') {
AddToPath ($WorkingDir + '\runtime') AddToPath ($WorkingDir + '\runtime')
exit 0 exit 0
} elseif ($action -eq 'remove-path') { } elseif ($action -eq 'remove-path') {
RemoveFromPath ($WorkingDir + '\runtime') RemoveFromPath ($WorkingDir + '\runtime')
exit 0 exit 0
} elseif (-not($action -eq '')) { } elseif (-not($action -eq '')) {
Write-Host ("Invalid action: " + $action) -ForegroundColor Red Write-Host ("Invalid action: " + $action) -ForegroundColor Red
exit 1 exit 1
} }
# get php 8.1 specific version # get php 8.1 specific version
# php windows download # php windows download
$PHPRuntimeUrl = "https://windows.php.net/downloads/releases/archives/php-8.4.4-nts-Win32-vs17-x64.zip" $PHPRuntimeUrl = "https://windows.php.net/downloads/releases/archives/php-8.4.4-nts-Win32-vs17-x64.zip"
Write-Host "Downloading PHP from: " -NoNewline Write-Host "Downloading PHP from: " -NoNewline
Write-Host $PHPRuntimeUrl -ForegroundColor Cyan Write-Host $PHPRuntimeUrl -ForegroundColor Cyan
$ComposerUrl = "https://getcomposer.org/download/latest-stable/composer.phar" $ComposerUrl = "https://getcomposer.org/download/latest-stable/composer.phar"
# create dir # create dir
New-Item -Path "downloads" -ItemType Directory -Force | Out-Null New-Item -Path "downloads" -ItemType Directory -Force | Out-Null
# download php # download php
if (-not(Test-Path "downloads\php.zip")) if (-not(Test-Path "downloads\php.zip"))
{ {
Write-Host "Downloading PHP ..." Write-Host "Downloading PHP ..."
Invoke-WebRequest $PHPRuntimeUrl -OutFile "downloads\php.zip" Invoke-WebRequest $PHPRuntimeUrl -OutFile "downloads\php.zip"
} }
# extract php # extract php
New-Item -Path "runtime" -ItemType Directory -Force | Out-Null New-Item -Path "runtime" -ItemType Directory -Force | Out-Null
Write-Host "Extracting php.zip ..." Write-Host "Extracting php.zip ..."
Expand-Archive -Path "downloads/php.zip" -DestinationPath "runtime" -Force Expand-Archive -Path "downloads/php.zip" -DestinationPath "runtime" -Force
# make php.ini # make php.ini
Move-Item -Path "runtime\php.ini-production" -Destination "runtime\php.ini" -Force Move-Item -Path "runtime\php.ini-production" -Destination "runtime\php.ini" -Force
$OriginINI = Get-Content -Path "runtime\php.ini" -Raw $OriginINI = Get-Content -Path "runtime\php.ini" -Raw
$OriginINI = $OriginINI -replace ';extension=openssl', 'extension=openssl' $OriginINI = $OriginINI -replace ';extension=openssl', 'extension=openssl'
$OriginINI = $OriginINI -replace ';extension=curl', 'extension=curl' $OriginINI = $OriginINI -replace ';extension=curl', 'extension=curl'
$OriginINI = $OriginINI -replace ';extension=mbstring', 'extension=mbstring' $OriginINI = $OriginINI -replace ';extension=mbstring', 'extension=mbstring'
$OriginINI = $OriginINI -replace ';extension=sodium', 'extension=sodium' $OriginINI = $OriginINI -replace ';extension=sodium', 'extension=sodium'
$OriginINI = $OriginINI -replace ';extension_dir = "./"', ('extension_dir = "' + (Split-Path -Parent $MyInvocation.MyCommand.Definition) + '\..\runtime\ext"') $OriginINI = $OriginINI -replace ';extension_dir = "./"', ('extension_dir = "' + (Split-Path -Parent $MyInvocation.MyCommand.Definition) + '\..\runtime\ext"')
$OriginINI | Set-Content -Path "runtime\php.ini" $OriginINI | Set-Content -Path "runtime\php.ini"
# download composer # download composer
if (-not(Test-Path "runtime\composer.phar")) if (-not(Test-Path "runtime\composer.phar"))
{ {
Write-Host "Downloading composer ..." Write-Host "Downloading composer ..."
Invoke-WebRequest $ComposerUrl -OutFile "downloads\composer.phar" Invoke-WebRequest $ComposerUrl -OutFile "downloads\composer.phar"
Move-Item -Path "downloads\composer.phar" -Destination "runtime\composer.phar" -Force Move-Item -Path "downloads\composer.phar" -Destination "runtime\composer.phar" -Force
} }
# create runtime\composer.ps1 # create runtime\composer.ps1
$ComposerContent = ' $ComposerContent = '
$WorkingDir = (Split-Path -Parent $MyInvocation.MyCommand.Definition) $WorkingDir = (Split-Path -Parent $MyInvocation.MyCommand.Definition)
& ($WorkingDir + "\php.exe") (Join-Path $WorkingDir "\composer.phar") @args & ($WorkingDir + "\php.exe") (Join-Path $WorkingDir "\composer.phar") @args
' '
$ComposerContent | Set-Content -Path 'runtime\composer.ps1' -Encoding UTF8 $ComposerContent | Set-Content -Path 'runtime\composer.ps1' -Encoding UTF8
Write-Host "Successfully downloaded PHP and Composer !" -ForegroundColor Green Write-Host "Successfully downloaded PHP and Composer !" -ForegroundColor Green
Write-Host "Use static-php-cli: " -NoNewline Write-Host "Use static-php-cli: " -NoNewline
Write-Host "bin/spc" -ForegroundColor Cyan Write-Host "bin/spc" -ForegroundColor Cyan
Write-Host "Use php: " -NoNewline Write-Host "Use php: " -NoNewline
Write-Host "runtime/php" -ForegroundColor Cyan Write-Host "runtime/php" -ForegroundColor Cyan
Write-Host "Use composer: " -NoNewline Write-Host "Use composer: " -NoNewline
Write-Host "runtime/composer" -ForegroundColor Cyan Write-Host "runtime/composer" -ForegroundColor Cyan
Write-Host "" Write-Host ""
Write-Host "Don't forget installing composer dependencies '" -NoNewline Write-Host "Don't forget installing composer dependencies '" -NoNewline
Write-Host "runtime/composer install" -ForegroundColor Cyan -NoNewline Write-Host "runtime/composer install" -ForegroundColor Cyan -NoNewline
Write-Host "' before using static-php-cli !" Write-Host "' before using static-php-cli !"
Write-Host "" Write-Host ""
Write-Host "If you want to use this PHP for quality tools (like phpstan, php-cs-fixer) or other project," Write-Host "If you want to use this PHP for quality tools (like phpstan, php-cs-fixer) or other project,"
Write-Host "or use PHP, Composer as system executable," Write-Host "or use PHP, Composer as system executable,"
Write-Host "use '" -NoNewline Write-Host "use '" -NoNewline
Write-Host "bin/setup-runtime add-path" -ForegroundColor Cyan -NoNewline Write-Host "bin/setup-runtime add-path" -ForegroundColor Cyan -NoNewline
Write-Host "' to add runtime dir in Path." Write-Host "' to add runtime dir in Path."

View File

@@ -1,12 +1,12 @@
$PHP_Exec = ".\runtime\php.exe" $PHP_Exec = ".\runtime\php.exe"
if (-not(Test-Path $PHP_Exec)) { if (-not(Test-Path $PHP_Exec)) {
$PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition $PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition
if (-not $PHP_Exec) { if (-not $PHP_Exec) {
Write-Host "Error: PHP not found, you need to install PHP on your system or use 'bin/setup-runtime'." -ForegroundColor Red Write-Host "Error: PHP not found, you need to install PHP on your system or use 'bin/setup-runtime'." -ForegroundColor Red
exit 1 exit 1
} }
} }
& "$PHP_Exec" -d xdebug.mode=debug -d xdebug.client_host=127.0.0.1 -d xdebug.client_port=9003 -d xdebug.start_with_request=yes ("bin/spc") @args & "$PHP_Exec" -d xdebug.mode=debug -d xdebug.client_host=127.0.0.1 -d xdebug.client_port=9003 -d xdebug.start_with_request=yes ("bin/spc") @args
exit $LASTEXITCODE exit $LASTEXITCODE

View File

@@ -1,12 +1,12 @@
$PHP_Exec = ".\runtime\php.exe" $PHP_Exec = ".\runtime\php.exe"
if (-not(Test-Path $PHP_Exec)) { if (-not(Test-Path $PHP_Exec)) {
$PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition $PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition
if (-not $PHP_Exec) { if (-not $PHP_Exec) {
Write-Host "Error: PHP not found, you need to install PHP on your system or use 'bin/setup-runtime'." -ForegroundColor Red Write-Host "Error: PHP not found, you need to install PHP on your system or use 'bin/setup-runtime'." -ForegroundColor Red
exit 1 exit 1
} }
} }
& "$PHP_Exec" ("bin/spc") @args & "$PHP_Exec" ("bin/spc") @args
exit $LASTEXITCODE exit $LASTEXITCODE

View File

@@ -10,6 +10,7 @@
"config", "config",
"src", "src",
"vendor/psr", "vendor/psr",
"vendor/laravel/prompts",
"vendor/symfony", "vendor/symfony",
"vendor/php-di", "vendor/php-di",
"vendor/zhamao" "vendor/zhamao"

View File

@@ -12,8 +12,10 @@
"php": ">=8.4", "php": ">=8.4",
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-zlib": "*", "ext-zlib": "*",
"laravel/prompts": "~0.1",
"php-di/php-di": "^7.1", "php-di/php-di": "^7.1",
"symfony/console": "^5.4 || ^6 || ^7", "symfony/console": "^5.4 || ^6 || ^7",
"symfony/process": "^7.2",
"symfony/yaml": "^7.2", "symfony/yaml": "^7.2",
"zhamao/logger": "^1.1.4" "zhamao/logger": "^1.1.4"
}, },

568
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,6 @@ ncurses:
license-files: license-files:
- COPYING - COPYING
source: source:
type: filelist
url: 'https://ftp.gnu.org/gnu/ncurses/'
regex: '/href="(?<file>ncurses-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist type: filelist
url: 'https://ftpmirror.gnu.org/gnu/ncurses/' url: 'https://ftpmirror.gnu.org/gnu/ncurses/'
regex: '/href="(?<file>ncurses-(?<version>[^"]+)\.tar\.gz)"/' regex: '/href="(?<file>ncurses-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -68,8 +68,8 @@ SPC_PRESERVE_LOGS="no"
[windows] [windows]
; build target: win7-static ; build target: win7-static
SPC_TARGET=native-windows SPC_TARGET=native-windows
; MSYS2 root directory (msys64 subfolder), used by the Windows toolchain ; php-sdk-binary-tools path
SPC_MSYS2_PATH="${PKG_ROOT_PATH}\msys2-build-essentials\msys64" PHP_SDK_PATH="${WORKING_DIR}\php-sdk-binary-tools"
; upx executable path ; upx executable path
UPX_EXEC="${PKG_ROOT_PATH}\bin\upx.exe" UPX_EXEC="${PKG_ROOT_PATH}\bin\upx.exe"
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches ; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches
@@ -100,9 +100,10 @@ SPC_TARGET=${GNU_ARCH}-linux-musl
CC=${SPC_DEFAULT_CC} CC=${SPC_DEFAULT_CC}
CXX=${SPC_DEFAULT_CXX} CXX=${SPC_DEFAULT_CXX}
AR=${SPC_DEFAULT_AR} AR=${SPC_DEFAULT_AR}
RANLIB=${SPC_DEFAULT_RANLIB}
LD=${SPC_DEFAULT_LD} LD=${SPC_DEFAULT_LD}
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build ; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
SPC_DEFAULT_CFLAGS="-fPIC -O3 -pipe -fno-plt -fno-semantic-interposition -fstack-clash-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections" SPC_DEFAULT_CFLAGS="-fPIC -O3 -pipe -fno-plt -fno-semantic-interposition -fstack-clash-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections -Wno-unused-command-line-argument"
SPC_DEFAULT_CXXFLAGS="${SPC_DEFAULT_CFLAGS}" SPC_DEFAULT_CXXFLAGS="${SPC_DEFAULT_CFLAGS}"
SPC_DEFAULT_LDFLAGS="-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -Wl,-z,noexecstack -Wl,--gc-sections" SPC_DEFAULT_LDFLAGS="-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -Wl,-z,noexecstack -Wl,--gc-sections"
; upx executable path ; upx executable path
@@ -125,6 +126,8 @@ SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fno-ident -fPIE
SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS="-g -fstack-protector-strong -fno-ident -fPIE -fvisibility=hidden -fvisibility-inlines-hidden ${SPC_DEFAULT_CXXFLAGS}" SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS="-g -fstack-protector-strong -fno-ident -fPIE -fvisibility=hidden -fvisibility-inlines-hidden ${SPC_DEFAULT_CXXFLAGS}"
; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so ; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS="" SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS=""
; EXTRA_LDFLAGS_PROGRAM for `make` php; appended only to SAPI executable links (cli/fpm/cgi/micro/embed). Used by PGO to inject -fprofile-use= without polluting libphp.{a,so}.
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM=""
; optional, path to openssl conf. This affects where openssl will look for the default CA. ; optional, path to openssl conf. This affects where openssl will look for the default CA.
; default on Debian/Alpine: /etc/ssl, default on RHEL: /etc/pki/tls ; default on Debian/Alpine: /etc/ssl, default on RHEL: /etc/pki/tls
@@ -140,6 +143,7 @@ SPC_USE_LLVM=system
CC=${SPC_DEFAULT_CC} CC=${SPC_DEFAULT_CC}
CXX=${SPC_DEFAULT_CXX} CXX=${SPC_DEFAULT_CXX}
AR=${SPC_DEFAULT_AR} AR=${SPC_DEFAULT_AR}
RANLIB=${SPC_DEFAULT_RANLIB}
LD=${SPC_DEFAULT_LD} LD=${SPC_DEFAULT_LD}
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build ; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
SPC_DEFAULT_CFLAGS="--target=${MAC_ARCH}-apple-darwin -O3 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections" SPC_DEFAULT_CFLAGS="--target=${MAC_ARCH}-apple-darwin -O3 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections"
@@ -163,5 +167,7 @@ SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie -fvis
SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS="-g -fstack-protector-strong -fno-ident -fpie -fvisibility=hidden -fvisibility-inlines-hidden -Werror=unknown-warning-option ${SPC_DEFAULT_CXXFLAGS}" SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS="-g -fstack-protector-strong -fno-ident -fpie -fvisibility=hidden -fvisibility-inlines-hidden -Werror=unknown-warning-option ${SPC_DEFAULT_CXXFLAGS}"
; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.dylib ; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.dylib
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS="" SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS=""
; EXTRA_LDFLAGS_PROGRAM for `make` php; appended only to SAPI executable links (cli/fpm/cgi/micro/embed). Used by PGO to inject -fprofile-use= without polluting libphp.{a,dylib}.
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM=""
; minimum compatible macOS version (LLVM vars, availability not guaranteed) ; minimum compatible macOS version (LLVM vars, availability not guaranteed)
MACOSX_DEPLOYMENT_TARGET=12.0 MACOSX_DEPLOYMENT_TARGET=12.0

View File

@@ -10,5 +10,3 @@ ext-gmssl:
license: PHP-3.01 license: PHP-3.01
depends: depends:
- gmssl - gmssl
php-extension:
arg-type: with-path

View File

@@ -10,11 +10,6 @@ ext-zip:
license: PHP-3.01 license: PHP-3.01
depends: depends:
- libzip - libzip
depends@windows:
- libzip
- zlib
- xz
- bzip2
php-extension: php-extension:
arg-type: custom arg-type: custom
arg-type@windows: enable arg-type@windows: enable

View File

@@ -2,10 +2,6 @@ gettext:
type: library type: library
artifact: artifact:
source: source:
type: filelist
url: 'https://ftp.gnu.org/gnu/gettext/'
regex: '/href="(?<file>gettext-(?<version>[^"]+)\.tar\.xz)"/'
source-mirror:
type: filelist type: filelist
url: 'https://ftpmirror.gnu.org/gnu/gettext/' url: 'https://ftpmirror.gnu.org/gnu/gettext/'
regex: '/href="(?<file>gettext-(?<version>[^"]+)\.tar\.xz)"/' regex: '/href="(?<file>gettext-(?<version>[^"]+)\.tar\.xz)"/'

View File

@@ -2,13 +2,12 @@ gmp:
type: library type: library
artifact: artifact:
source: source:
type: filelist
url: 'https://ftp.gnu.org/gnu/gmp/'
regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/'
source-mirror:
type: filelist type: filelist
url: 'https://ftpmirror.gnu.org/gnu/gmp/' url: 'https://ftpmirror.gnu.org/gnu/gmp/'
regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/' regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/'
source-mirror:
type: url
url: 'https://dl.static-php.dev/static-php-cli/deps/gmp/gmp-6.3.0.tar.xz'
metadata: metadata:
license-files: ['@/gmp.txt'] license-files: ['@/gmp.txt']
license: Custom license: Custom

View File

@@ -2,10 +2,6 @@ idn2:
type: library type: library
artifact: artifact:
source: source:
type: filelist
url: 'https://ftp.gnu.org/gnu/libidn/'
regex: '/href="(?<file>libidn2-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist type: filelist
url: 'https://ftpmirror.gnu.org/gnu/libidn/' url: 'https://ftpmirror.gnu.org/gnu/libidn/'
regex: '/href="(?<file>libidn2-(?<version>[^"]+)\.tar\.gz)"/' regex: '/href="(?<file>libidn2-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -2,10 +2,6 @@ libiconv:
type: library type: library
artifact: artifact:
source: source:
type: filelist
url: 'https://ftp.gnu.org/gnu/libiconv/'
regex: '/href="(?<file>libiconv-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist type: filelist
url: 'https://ftpmirror.gnu.org/gnu/libiconv/' url: 'https://ftpmirror.gnu.org/gnu/libiconv/'
regex: '/href="(?<file>libiconv-(?<version>[^"]+)\.tar\.gz)"/' regex: '/href="(?<file>libiconv-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -9,10 +9,8 @@ libssh2:
metadata: metadata:
license-files: [COPYING] license-files: [COPYING]
license: BSD-3-Clause license: BSD-3-Clause
depends@unix: depends:
- openssl - openssl
depends@windows:
- zlib
headers: headers:
- libssh2.h - libssh2.h
- libssh2_publickey.h - libssh2_publickey.h

View File

@@ -2,10 +2,6 @@ libunistring:
type: library type: library
artifact: artifact:
source: source:
type: filelist
url: 'https://ftp.gnu.org/gnu/libunistring/'
regex: '/href="(?<file>libunistring-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist type: filelist
url: 'https://ftpmirror.gnu.org/gnu/libunistring/' url: 'https://ftpmirror.gnu.org/gnu/libunistring/'
regex: '/href="(?<file>libunistring-(?<version>[^"]+)\.tar\.gz)"/' regex: '/href="(?<file>libunistring-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -17,6 +17,7 @@ openssl:
- zlib - zlib
depends@windows: depends@windows:
- zlib - zlib
- jom
headers: headers:
- openssl - openssl
static-libs@unix: static-libs@unix:
@@ -25,6 +26,3 @@ openssl:
static-libs@windows: static-libs@windows:
- libssl.lib - libssl.lib
- libcrypto.lib - libcrypto.lib
tools@windows:
- jom
- strawberry-perl

View File

@@ -2,10 +2,6 @@ readline:
type: library type: library
artifact: artifact:
source: source:
type: filelist
url: 'https://ftp.gnu.org/gnu/readline/'
regex: '/href="(?<file>readline-(?<version>[^"]+)\.tar\.gz)"/'
source-mirror:
type: filelist type: filelist
url: 'https://ftpmirror.gnu.org/gnu/readline/' url: 'https://ftpmirror.gnu.org/gnu/readline/'
regex: '/href="(?<file>readline-(?<version>[^"]+)\.tar\.gz)"/' regex: '/href="(?<file>readline-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -10,8 +10,10 @@ frankenphp:
license: MIT license: MIT
depends: depends:
- php-embed - php-embed
- go-xcaddy
depends@windows: depends@windows:
- php-embed - php-embed
- go-win
- pthreads4w - pthreads4w
suggests@unix: suggests@unix:
- brotli - brotli
@@ -22,7 +24,3 @@ frankenphp:
- frankenphp - frankenphp
static-bins@windows: static-bins@windows:
- frankenphp.exe - frankenphp.exe
tools:
- go-xcaddy
tools@windows:
- go-win

View File

@@ -1,5 +1,5 @@
go-win: go-win:
type: tool type: target
artifact: artifact:
binary: custom binary: custom
env: env:
@@ -8,8 +8,3 @@ go-win:
GOPATH: '{pkg_root_path}/go-win/go' GOPATH: '{pkg_root_path}/go-win/go'
path@windows: path@windows:
- '{pkg_root_path}/go-win/bin' - '{pkg_root_path}/go-win/bin'
tool:
provides:
- go.exe
- gofmt.exe
binary-subdir: go-win/bin

View File

@@ -1,5 +1,5 @@
go-xcaddy: go-xcaddy:
type: tool type: target
artifact: artifact:
binary: custom binary: custom
env: env:
@@ -8,8 +8,5 @@ go-xcaddy:
GOPATH: '{pkg_root_path}/go-xcaddy/go' GOPATH: '{pkg_root_path}/go-xcaddy/go'
path@unix: path@unix:
- '{pkg_root_path}/go-xcaddy/bin' - '{pkg_root_path}/go-xcaddy/bin'
tool: static-bins:
provides: - xcaddy
- go
- gofmt
binary-subdir: go-xcaddy/bin

View File

@@ -1,11 +1,7 @@
jom: jom:
type: tool type: target
artifact: artifact:
binary: binary:
windows-x86_64: { type: url, url: 'https://download.qt.io/official_releases/jom/jom.zip', extract: '{pkg_root_path}/jom' } windows-x86_64: { type: url, url: 'https://download.qt.io/official_releases/jom/jom.zip', extract: '{pkg_root_path}/jom' }
path@windows: path@windows:
- '{pkg_root_path}\jom' - '{pkg_root_path}\jom'
tool:
provides:
- jom.exe
binary-subdir: jom

View File

@@ -0,0 +1,6 @@
llvm-compiler-rt:
type: target
artifact:
binary: custom
depends:
- zig

View File

@@ -0,0 +1,6 @@
llvm-tools:
type: target
artifact:
binary: custom
depends:
- zig

View File

@@ -1,8 +0,0 @@
msys2-build-essentials:
type: target
artifact:
binary: custom
env:
SPC_MSYS2_PATH: '{pkg_root_path}/msys2-build-essentials/msys64'
path@windows:
- '{pkg_root_path}/msys2-build-essentials/msys64/usr/bin'

View File

@@ -0,0 +1,5 @@
nasm:
type: target
artifact:
binary:
windows-x86_64: { type: url, url: 'https://dl.static-php.dev/static-php-cli/deps/nasm/nasm-2.16.01-win64.zip', extract: { nasm.exe: '{php_sdk_path}/bin/nasm.exe', ndisasm.exe: '{php_sdk_path}/bin/ndisasm.exe' } }

View File

@@ -0,0 +1,5 @@
php-sdk-binary-tools:
type: target
artifact:
binary:
windows-x86_64: { type: git, rev: master, url: 'https://github.com/php/php-sdk-binary-tools.git', extract: '{php_sdk_path}' }

View File

@@ -0,0 +1,9 @@
pkg-config:
type: target
artifact:
source: 'https://dl.static-php.dev/static-php-cli/deps/pkg-config/pkg-config-0.29.2.tar.gz'
binary:
linux-x86_64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-x86_64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
linux-aarch64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-aarch64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
macos-x86_64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-x86_64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
macos-aarch64: { type: ghrel, repo: static-php/static-php-cli-hosted, match: pkg-config-aarch64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }

View File

@@ -1,12 +1,8 @@
protoc: protoc:
type: tool type: target
artifact: artifact:
binary: binary:
linux-x86_64: { type: ghrel, repo: protocolbuffers/protobuf, match: 'protoc-([0-9.]+)-linux-x86_64\.zip', extract: '{pkg_root_path}/protoc' } linux-x86_64: { type: ghrel, repo: protocolbuffers/protobuf, match: 'protoc-([0-9.]+)-linux-x86_64\.zip', extract: '{pkg_root_path}/protoc' }
linux-aarch64: { type: ghrel, repo: protocolbuffers/protobuf, match: 'protoc-([0-9.]+)-linux-aarch_64\.zip', extract: '{pkg_root_path}/protoc' } linux-aarch64: { type: ghrel, repo: protocolbuffers/protobuf, match: 'protoc-([0-9.]+)-linux-aarch_64\.zip', extract: '{pkg_root_path}/protoc' }
path: path:
- '{pkg_root_path}/protoc/bin' - '{pkg_root_path}/protoc/bin'
tool:
provides:
- protoc
binary-subdir: protoc/bin

View File

@@ -1,9 +1,5 @@
strawberry-perl: strawberry-perl:
type: tool type: target
artifact: artifact:
binary: binary:
windows-x86_64: { type: url, url: 'https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip', extract: '{pkg_root_path}/strawberry-perl' } windows-x86_64: { type: url, url: 'https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip', extract: '{pkg_root_path}/strawberry-perl' }
tool:
provides:
- perl.exe
binary-subdir: strawberry-perl/perl/bin

View File

@@ -1,13 +1,7 @@
upx: upx:
type: tool type: target
artifact: artifact:
binary: binary:
linux-x86_64: { type: ghrel, repo: upx/upx, match: upx.+-amd64_linux\.tar\.xz, extract: { upx: '{pkg_root_path}/bin/upx' } } linux-x86_64: { type: ghrel, repo: upx/upx, match: upx.+-amd64_linux\.tar\.xz, extract: { upx: '{pkg_root_path}/bin/upx' } }
linux-aarch64: { type: ghrel, repo: upx/upx, match: upx.+-arm64_linux\.tar\.xz, extract: { upx: '{pkg_root_path}/bin/upx' } } linux-aarch64: { type: ghrel, repo: upx/upx, match: upx.+-arm64_linux\.tar\.xz, extract: { upx: '{pkg_root_path}/bin/upx' } }
windows-x86_64: { type: ghrel, repo: upx/upx, match: upx.+-win64\.zip, extract: { upx.exe: '{pkg_root_path}/bin/upx.exe' } } windows-x86_64: { type: ghrel, repo: upx/upx, match: upx.+-win64\.zip, extract: { upx.exe: '{pkg_root_path}/bin/upx.exe' } }
tool:
provides:
- upx
provides@windows:
- upx.exe
binary-subdir: bin

View File

@@ -1,9 +1,5 @@
vswhere: vswhere:
type: tool type: target
artifact: artifact:
binary: binary:
windows-x86_64: { type: url, url: 'https://github.com/microsoft/vswhere/releases/download/3.1.7/vswhere.exe', extract: '{pkg_root_path}/bin/vswhere.exe' } windows-x86_64: { type: url, url: 'https://github.com/microsoft/vswhere/releases/download/3.1.7/vswhere.exe', extract: '{pkg_root_path}/bin/vswhere.exe' }
tool:
provides:
- vswhere.exe
binary-subdir: bin

View File

@@ -0,0 +1,4 @@
zig:
type: target
artifact:
binary: custom

View File

@@ -1,9 +0,0 @@
7za-win:
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://dl.static-php.dev/v3/tools/7zip/7za.exe', extract: '{pkg_root_path}/bin/7za.exe' }
tool:
provides:
- 7za.exe
binary-subdir: bin

View File

@@ -1,10 +0,0 @@
nasm:
type: tool
artifact:
binary:
windows-x86_64: { type: url, url: 'https://dl.static-php.dev/static-php-cli/deps/nasm/nasm-2.16.01-win64.zip', extract: { nasm.exe: '{pkg_root_path}/bin/nasm.exe', ndisasm.exe: '{pkg_root_path}/bin/ndisasm.exe' } }
tool:
provides:
- nasm.exe
- ndisasm.exe
binary-subdir: bin

View File

@@ -1,13 +0,0 @@
pkg-config:
type: tool
artifact:
source: 'https://dl.static-php.dev/static-php-cli/deps/pkg-config/pkg-config-0.29.2.tar.gz'
binary:
linux-x86_64: { type: ghrel, repo: static-php/hosted, match: pkg-config-x86_64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
linux-aarch64: { type: ghrel, repo: static-php/hosted, match: pkg-config-aarch64-linux-musl-1.2.5.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
macos-x86_64: { type: ghrel, repo: static-php/hosted, match: pkg-config-x86_64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
macos-aarch64: { type: ghrel, repo: static-php/hosted, match: pkg-config-aarch64-darwin.txz, extract: { bin/pkg-config: '{pkg_root_path}/bin/pkg-config' } }
tool:
provides:
- pkg-config
binary-subdir: bin

View File

@@ -1,14 +0,0 @@
zig:
type: tool
artifact:
binary: custom
tool:
provides:
- zig
- zig-cc
- zig-c++
- zig-ar
- zig-ld.lld
- zig-ranlib
- zig-objcopy
binary-subdir: zig

View File

@@ -229,7 +229,7 @@ The following path placeholders are supported in string values of the `path`, `e
| `{working_dir}` | Working directory (project root) | | `{working_dir}` | Working directory (project root) |
| `{download_path}` | Download cache directory (`downloads/`) | | `{download_path}` | Download cache directory (`downloads/`) |
| `{source_path}` | Extracted source directory (`source/`) | | `{source_path}` | Extracted source directory (`source/`) |
| `{spc_msys2_path}` | MSYS2 root directory (`msys64/`) — Windows only | | `{php_sdk_path}` | Windows PHP SDK directory |
## target Package Type ## target Package Type

View File

@@ -58,13 +58,7 @@ A single-file hook API for lightweight patches may be provided in a future relea
### Windows-only: `--with-sdk-binary-dir` and `--vs-ver` ### Windows-only: `--with-sdk-binary-dir` and `--vs-ver`
These options are no longer accepted on the command line. In v3, the `php-sdk-binary-tools` dependency has been completely removed. v3 now manages its own **MSYS2** environment to support autotools-based library builds on Windows. Run `spc doctor --install` to download and configure MSYS2 automatically. These options are no longer accepted on the command line. Instead, set the `PHP_SDK_PATH` environment variable to point to your PHP SDK binary tools directory. The Visual Studio version is now managed by the toolchain configuration.
If you need to point to a custom MSYS2 installation, set the `SPC_MSYS2_PATH` environment variable to the `msys64` directory (e.g. `C:\msys64`). Visual Studio is now auto-detected by the toolchain — no manual version flag needed.
::: warning Migrating from v2
v2 relied on `php-sdk-binary-tools` and required `--with-sdk-binary-dir` and `--vs-ver` on every build invocation. In v3 these options are gone. Remove them from all CI scripts and run `spc doctor --install` once to set up the Windows build environment.
:::
## Renamed / Deprecated Options ## Renamed / Deprecated Options

View File

@@ -223,7 +223,7 @@ openssl:
| `{working_dir}` | 工作目录(项目根目录) | | `{working_dir}` | 工作目录(项目根目录) |
| `{download_path}` | 下载缓存目录(`downloads/` | | `{download_path}` | 下载缓存目录(`downloads/` |
| `{source_path}` | 解压源码目录(`source/` | | `{source_path}` | 解压源码目录(`source/` |
| `{spc_msys2_path}` | MSYS2 根目录(`msys64/`)——仅 Windows | | `{php_sdk_path}` | Windows PHP SDK 目录 |
## target 包类型 ## target 包类型

View File

@@ -58,13 +58,7 @@ curl -o spc https://dl.static-php.dev/v3/spc-bin/nightly/spc-linux-x86_64
### Windows 专有:`--with-sdk-binary-dir` 和 `--vs-ver` ### Windows 专有:`--with-sdk-binary-dir` 和 `--vs-ver`
这两个选项已不再被命令行接受。在 v3 中,`php-sdk-binary-tools` 依赖已被完全移除。v3 现在通过管理自己的 **MSYS2** 环境来支持 Windows 上基于 autotools 的库构建。运行 `spc doctor --install` 即可自动下载并配置 MSYS2 这两个选项已不再被命令行接受。请改为设置 `PHP_SDK_PATH` 环境变量,指向你的 PHP SDK binary tools 目录。Visual Studio 版本现在由工具链配置统一管理
如需指向自定义 MSYS2 安装目录,请设置 `SPC_MSYS2_PATH` 环境变量,值为 `msys64` 目录路径(例如 `C:\msys64`。Visual Studio 版本现在由工具链自动检测,无需手动指定版本号。
::: warning 从 v2 迁移
v2 依赖 `php-sdk-binary-tools`,并在每次构建时需要传入 `--with-sdk-binary-dir``--vs-ver` 参数。在 v3 中这些选项已被移除。请从所有 CI 脚本中删除这些参数,并使用 `spc doctor --install` 一次性完成 Windows 构建环境的配置。
:::
## 已重命名 / 已弃用的选项 ## 已重命名 / 已弃用的选项

View File

@@ -1,6 +1,6 @@
parameters: parameters:
reportUnmatchedIgnoredErrors: false reportUnmatchedIgnoredErrors: false
level: 5 level: 4
phpVersion: 80400 phpVersion: 80400
paths: paths:
- ./src/ - ./src/

View File

@@ -10,7 +10,6 @@ package:
- config/pkg/lib/ - config/pkg/lib/
- config/pkg/target/ - config/pkg/target/
- config/pkg/ext/ - config/pkg/ext/
- config/pkg/tool/
artifact: artifact:
config: config:
- config/artifact/ - config/artifact/

View File

@@ -25,7 +25,6 @@ class go_xcaddy
])] ])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult public function downBinary(ArtifactDownloader $downloader): DownloadResult
{ {
$pkgroot = PKG_ROOT_PATH;
$name = SystemTarget::getCurrentPlatformString(); $name = SystemTarget::getCurrentPlatformString();
$arch = match (explode('-', $name)[1]) { $arch = match (explode('-', $name)[1]) {
'x86_64' => 'amd64', 'x86_64' => 'amd64',
@@ -64,7 +63,7 @@ class go_xcaddy
if ($file_hash !== $hash) { if ($file_hash !== $hash) {
throw new DownloaderException("Hash mismatch for downloaded go-xcaddy binary. Expected {$hash}, got {$file_hash}"); throw new DownloaderException("Hash mismatch for downloaded go-xcaddy binary. Expected {$hash}, got {$file_hash}");
} }
return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $version], extract: "{$pkgroot}/go-xcaddy", verified: true, version: $version); return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $version], extract: '{pkg_root_path}/go-xcaddy', verified: true, version: $version);
} }
#[CustomBinaryCheckUpdate('go-xcaddy', [ #[CustomBinaryCheckUpdate('go-xcaddy', [
@@ -109,7 +108,7 @@ class go_xcaddy
'GOROOT' => "{$target_path}", 'GOROOT' => "{$target_path}",
'GOBIN' => "{$target_path}/bin", 'GOBIN' => "{$target_path}/bin",
'GOPATH' => "{$target_path}/go", 'GOPATH' => "{$target_path}/go",
])->exec('CC=cc go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest'); ])->exec('CGO_ENABLED=0 go install github.com/caddyserver/xcaddy/cmd/xcaddy@master');
GlobalEnvManager::addPathIfNotExists("{$target_path}/bin"); GlobalEnvManager::addPathIfNotExists("{$target_path}/bin");
} }
} }

View File

@@ -0,0 +1,193 @@
<?php
declare(strict_types=1);
namespace Package\Artifact;
use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
use StaticPHP\Artifact\Downloader\Type\GitHubTokenSetupTrait;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
use StaticPHP\Exception\BuildFailureException;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Runtime\SystemTarget;
/**
* Builds the compiler-rt bits zig ships without — libclang_rt.profile.a (PGO instrumentation)
* and clang_rt.crtbegin.o/crtend.o (__dso_handle for shared libs). Target-arch specific:
* libs land in PKG_ROOT_PATH/zig/lib/{triple}.
* Also builds libclang_rt.cpu_model.a (__cpu_model / __cpu_indicator_init, the libgcc-compatible
* globals that __builtin_cpu_supports()/__builtin_cpu_init() reference) for arches that have it.
*/
class llvm_compiler_rt
{
use GitHubTokenSetupTrait;
#[CustomBinary('llvm-compiler-rt', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
{
$llvmVersion = $this->detectZigLlvmVersion()
?? throw new DownloaderException('llvm-compiler-rt: could not detect bundled clang version from zig cc --version');
$tarball = "compiler-rt-{$llvmVersion}.src.tar.xz";
$url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-{$llvmVersion}/{$tarball}";
$tarballPath = DOWNLOAD_PATH . '/' . $tarball;
default_shell()->executeCurlDownload($url, $tarballPath, headers: $this->getGitHubTokenHeaders(), retries: $downloader->getRetry());
return DownloadResult::archive($tarball, ['url' => $url, 'version' => $llvmVersion], extract: '{source_path}/llvm-compiler-rt', verified: false, version: $llvmVersion);
}
#[CustomBinaryCheckUpdate('llvm-compiler-rt', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function checkUpdateBinary(?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
{
$llvmVersion = $this->detectZigLlvmVersion()
?? throw new DownloaderException('llvm-compiler-rt: could not detect bundled clang version from zig cc --version');
return new CheckUpdateResult(
old: $old_version,
new: $llvmVersion,
needUpdate: $old_version === null || $llvmVersion !== $old_version,
);
}
#[AfterBinaryExtract('llvm-compiler-rt', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function postExtract(string $target_path): void
{
$this->buildForTriple($target_path);
}
public function buildForTriple(?string $sourceDir = null, ?string $triple = null): void
{
$sourceDir ??= SOURCE_PATH . '/llvm-compiler-rt';
$triple ??= SystemTarget::getCanonicalTriple();
$libDir = zig::path() . '/lib/' . $triple;
if ($this->isBuilt($libDir)) {
return;
}
if (!is_dir($sourceDir) || !is_dir("{$sourceDir}/lib/profile")) {
throw new BuildFailureException("llvm-compiler-rt: missing source at {$sourceDir} (extraction layout changed?)");
}
f_mkdir($libDir, recursive: true);
$profileLib = "{$libDir}/libclang_rt.profile.a";
$crtBegin = "{$libDir}/clang_rt.crtbegin.o";
$crtEnd = "{$libDir}/clang_rt.crtend.o";
if (!file_exists($profileLib)) {
$this->buildProfileRuntime($sourceDir, $profileLib, $triple);
}
if (!file_exists($crtBegin) || !file_exists($crtEnd)) {
$this->buildCrtObjects($sourceDir, $crtBegin, $crtEnd, $triple);
}
$cpuModelLib = "{$libDir}/libclang_rt.cpu_model.a";
if (self::cpuModelArch($triple) !== null && !file_exists($cpuModelLib)) {
$this->buildCpuModelBuiltins($sourceDir, $cpuModelLib, $triple);
}
}
public function isBuilt(string $libDir): bool
{
return file_exists("{$libDir}/libclang_rt.profile.a")
&& file_exists("{$libDir}/clang_rt.crtbegin.o")
&& file_exists("{$libDir}/clang_rt.crtend.o")
&& (self::cpuModelArch(basename($libDir)) === null || file_exists("{$libDir}/libclang_rt.cpu_model.a"));
}
private function detectZigLlvmVersion(): ?string
{
if (!zig::isInstalled()) {
return null;
}
[$rc, $out] = shell()->execWithResult(escapeshellarg(zig::binary()) . ' cc --version', false);
if ($rc !== 0) {
return null;
}
return preg_match('/clang version (\d+\.\d+\.\d+)/', implode("\n", $out), $m) ? $m[1] : null;
}
private function buildProfileRuntime(string $srcRoot, string $libPath, string $triple): void
{
$profileSrc = "{$srcRoot}/lib/profile";
$profileInc = "{$srcRoot}/include";
// Skip OS-specific sources we can't satisfy without their SDKs.
$skip = ['/PlatformAIX', '/PlatformDarwin', '/PlatformFuchsia', '/PlatformOther', '/PlatformWindows', '/WindowsMMap'];
$sources = array_filter(
array_merge(glob("{$profileSrc}/*.c") ?: [], glob("{$profileSrc}/*.cpp") ?: []),
fn ($f) => !array_any($skip, fn ($s) => str_contains($f, $s)),
);
$objDir = "{$srcRoot}/obj-profile-{$triple}";
f_mkdir($objDir, recursive: true);
$cflags = "-target {$triple} -c -O2 -fPIC -fvisibility=hidden "
. '-I' . escapeshellarg($profileInc) . ' '
. '-DCOMPILER_RT_HAS_ATOMICS=1 -DCOMPILER_RT_HAS_FCNTL_LCK=1 -DCOMPILER_RT_HAS_UNAME=1';
$srcArgs = implode(' ', array_map('escapeshellarg', $sources));
shell()->cd($objDir)->exec("zig cc {$cflags} {$srcArgs}");
shell()->cd($objDir)->exec('zig ar rcs ' . escapeshellarg($libPath) . ' *.o');
}
private function buildCrtObjects(string $srcRoot, string $crtBegin, string $crtEnd, string $triple): void
{
$beginSrc = "{$srcRoot}/lib/builtins/crtbegin.c";
$endSrc = "{$srcRoot}/lib/builtins/crtend.c";
if (!is_file($beginSrc) || !is_file($endSrc)) {
throw new BuildFailureException("llvm-compiler-rt: crtbegin/crtend source missing under {$srcRoot}/lib/builtins");
}
$cflags = "-target {$triple} -c -O2 -fPIC -fvisibility=hidden -DCRT_HAS_INITFINI_ARRAY";
foreach ([[$beginSrc, $crtBegin], [$endSrc, $crtEnd]] as [$src, $dst]) {
shell()->exec("zig cc {$cflags} -o " . escapeshellarg($dst) . ' ' . escapeshellarg($src));
}
}
/**
* Build libclang_rt.cpu_model.a, provides
* the globals that __builtin_cpu_supports() reference.
*/
private function buildCpuModelBuiltins(string $srcRoot, string $libPath, string $triple): void
{
$builtins = "{$srcRoot}/lib/builtins";
$family = self::cpuModelArch($triple);
$cpuModelDir = "{$builtins}/cpu_model";
if (is_dir($cpuModelDir)) {
$src = "{$cpuModelDir}/{$family}.c";
$includes = '-I' . escapeshellarg($builtins) . ' -I' . escapeshellarg($cpuModelDir);
} else {
$src = "{$builtins}/cpu_model.c";
$includes = '-I' . escapeshellarg($builtins);
}
if (!is_file($src)) {
throw new BuildFailureException("llvm-compiler-rt: cpu_model source not found for {$triple} under {$builtins}");
}
$objDir = "{$srcRoot}/obj-cpu-model-{$triple}";
f_mkdir($objDir, recursive: true);
$obj = "{$objDir}/cpu_model.o";
$cflags = "-target {$triple} -c -O2 -fPIC {$includes}";
shell()->exec('zig cc ' . $cflags . ' -o ' . escapeshellarg($obj) . ' ' . escapeshellarg($src));
shell()->exec('zig ar rcs ' . escapeshellarg($libPath) . ' ' . escapeshellarg($obj));
}
private static function cpuModelArch(string $triple): ?string
{
$arch = explode('-', $triple)[0];
return match (true) {
in_array($arch, ['x86_64', 'amd64', 'i386', 'i686', 'x86'], true) => 'x86',
in_array($arch, ['aarch64', 'arm64'], true) => 'aarch64',
str_starts_with($arch, 'riscv') => 'riscv',
default => null,
};
}
}

View File

@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace Package\Artifact;
use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
use StaticPHP\Artifact\Downloader\Type\GitHubTokenSetupTrait;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Exception\BuildFailureException;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Package\PackageBuilder;
class llvm_tools
{
use GitHubTokenSetupTrait;
public const array TOOLS = ['llvm-objcopy', 'llvm-strip', 'llvm-profdata'];
#[CustomBinary('llvm-tools', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
{
$llvmVersion = $this->detectLlvmVersion()
?? throw new DownloaderException('Could not detect a clang version on host; install zig or clang first');
$tarball = "llvm-project-{$llvmVersion}.src.tar.xz";
$url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-{$llvmVersion}/{$tarball}";
$tarballPath = DOWNLOAD_PATH . '/' . $tarball;
default_shell()->executeCurlDownload($url, $tarballPath, headers: $this->getGitHubTokenHeaders(), retries: $downloader->getRetry());
return DownloadResult::archive($tarball, ['url' => $url, 'version' => $llvmVersion], extract: '{source_path}/llvm-tools', verified: false, version: $llvmVersion);
}
#[CustomBinaryCheckUpdate('llvm-tools', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function checkUpdateBinary(?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
{
$llvmVersion = $this->detectLlvmVersion()
?? throw new DownloaderException('Could not detect a clang version on host; install zig or clang first');
return new CheckUpdateResult(
old: $old_version,
new: $llvmVersion,
needUpdate: $old_version === null || $llvmVersion !== $old_version,
);
}
#[AfterBinaryExtract('llvm-tools', [
'linux-x86_64',
'linux-aarch64',
'macos-x86_64',
'macos-aarch64',
])]
public function postExtract(string $target_path): void
{
$this->buildForHost($target_path);
}
public function buildForHost(?string $sourceRoot = null): void
{
$sourceRoot ??= SOURCE_PATH . '/llvm-tools';
$binDir = PKG_ROOT_PATH . '/llvm-tools/bin';
if ($this->allBuilt($binDir)) {
return;
}
$llvmDir = "{$sourceRoot}/llvm";
if (!is_dir($llvmDir)) {
throw new BuildFailureException("llvm-tools: missing source at {$llvmDir} (extraction layout changed?)");
}
$buildDir = "{$sourceRoot}/build";
$installDir = PKG_ROOT_PATH . '/llvm-tools';
f_mkdir($buildDir, recursive: true);
f_mkdir($binDir, recursive: true);
$cmakeArgs = implode(' ', array_map('escapeshellarg', [
'-S', $llvmDir,
'-B', $buildDir,
'-DCMAKE_BUILD_TYPE=Release',
'-DLLVM_ENABLE_PROJECTS=',
'-DLLVM_TARGETS_TO_BUILD=',
'-DLLVM_INCLUDE_BENCHMARKS=OFF',
'-DLLVM_INCLUDE_TESTS=OFF',
'-DLLVM_INCLUDE_EXAMPLES=OFF',
'-DLLVM_INCLUDE_DOCS=OFF',
'-DLLVM_ENABLE_ZLIB=OFF',
'-DLLVM_ENABLE_ZSTD=OFF',
'-DLLVM_ENABLE_LIBXML2=OFF',
'-DLLVM_ENABLE_TERMINFO=OFF',
'-DLLVM_ENABLE_LIBEDIT=OFF',
'-DLLVM_ENABLE_LIBPFM=OFF',
'-DLLVM_BUILD_LLVM_DYLIB=OFF',
'-DLLVM_LINK_LLVM_DYLIB=OFF',
'-DBUILD_SHARED_LIBS=OFF',
'-DCMAKE_C_COMPILER=' . zig::binary('zig-cc'),
'-DCMAKE_CXX_COMPILER=' . zig::binary('zig-c++'),
'-DCMAKE_INSTALL_PREFIX=' . $installDir,
]));
$jobs = ApplicationContext::get(PackageBuilder::class)->concurrency;
$targetArgs = implode(' ', array_map(fn ($t) => '--target ' . escapeshellarg($t), self::TOOLS));
shell()
->setEnv(['SPC_TARGET' => GNU_ARCH . '-linux-musl'])
->exec('cmake ' . $cmakeArgs)
->exec('cmake --build ' . escapeshellarg($buildDir) . ' ' . $targetArgs . " -j {$jobs}");
foreach (self::TOOLS as $t) {
$built = "{$buildDir}/bin/{$t}";
if (!is_file($built)) {
throw new BuildFailureException("llvm-tools: missing build output {$built}");
}
copy($built, "{$binDir}/{$t}");
chmod("{$binDir}/{$t}", 0755);
}
}
public function allBuilt(string $binDir): bool
{
foreach (self::TOOLS as $t) {
$p = "{$binDir}/{$t}";
if (!is_file($p) || !is_executable($p)) {
return false;
}
}
return true;
}
private function detectLlvmVersion(): ?string
{
if (!zig::isInstalled()) {
return null;
}
[$rc, $out] = shell()->execWithResult(escapeshellarg(zig::binary()) . ' cc --version', false);
if ($rc !== 0) {
return null;
}
return preg_match('/clang version (\d+\.\d+\.\d+)/', implode("\n", $out), $m) ? $m[1] : null;
}
}

View File

@@ -1,93 +0,0 @@
<?php
declare(strict_types=1);
namespace Package\Artifact;
use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\BinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\GlobalEnvManager;
class msys2_build_essentials
{
// MSYS subsystem packages required for autotools-based builds.
private const REQUIRED_PACKAGES = ['make', 'autoconf', 'automake', 'libtool', 'pkgconf', 'perl', 'bison', 're2c'];
#[CustomBinary('msys2-build-essentials', ['windows-x86_64'])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
{
// MSYS2 nightly self-extracting archive; running it with `-y -oTARGET` extracts to TARGET\msys64\.
$url = 'https://github.com/msys2/msys2-installer/releases/download/nightly-x86_64/msys2-base-x86_64-latest.sfx.exe';
$filename = 'msys2-base-x86_64-latest.sfx.exe';
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());
return DownloadResult::file(
$filename,
['url' => $url, 'version' => 'nightly'],
version: 'nightly',
extract: '{pkg_root_path}/msys2-build-essentials',
);
}
#[BinaryExtract('msys2-build-essentials', ['windows-x86_64'])]
public function extractBinary(string $source_file, string $target_path): void
{
$target_path = FileSystem::convertPath($target_path);
$source_file = FileSystem::convertPath($source_file);
// Guard: skip re-extraction if already initialized (marker written at end of this method).
$marker = "{$target_path}\\.spc-msys2-initialized";
if (file_exists($marker)) {
return;
}
if (!is_dir($target_path)) {
FileSystem::createDir($target_path);
}
cmd()->exec("\"{$source_file}\" -y -o\"{$target_path}\"");
$msys2_bin = "{$target_path}\\msys64\\usr\\bin";
if (!file_exists("{$msys2_bin}\\bash.exe")) {
throw new DownloaderException("MSYS2 extraction failed: bash.exe not found at {$msys2_bin}\\bash.exe");
}
// Add MSYS2 usr\bin to PATH so pacman.exe can load msys-2.0.dll.
GlobalEnvManager::addPathIfNotExists($msys2_bin);
GlobalEnvManager::putenv('CHERE_INVOKING=yes');
GlobalEnvManager::putenv('MSYSTEM=MSYS');
// Disable PGP signature checking: pacman-key --init requires a pseudo-TTY which is unavailable
// from PHP. Patching pacman.conf is the standard approach for CI pipelines.
$pacman_conf = "{$target_path}\\msys64\\etc\\pacman.conf";
FileSystem::replaceFileRegex($pacman_conf, '/^SigLevel\s*=.*$/m', 'SigLevel = Never');
$pacman = "{$target_path}\\msys64\\usr\\bin\\pacman.exe";
// Two-pass update as recommended by MSYS2 CI docs.
cmd()->exec("\"{$pacman}\" --noconfirm -Syuu");
cmd()->exec("\"{$pacman}\" --noconfirm -Syuu");
$pkgs = implode(' ', self::REQUIRED_PACKAGES);
cmd()->exec("\"{$pacman}\" --noconfirm -S --needed {$pkgs}");
FileSystem::writeFile($marker, date('Y-m-d H:i:s'));
}
#[AfterBinaryExtract('msys2-build-essentials', ['windows-x86_64'])]
public function afterExtract(string $target_path): void
{
$target_path = FileSystem::convertPath($target_path);
$msys2_root = "{$target_path}\\msys64";
GlobalEnvManager::putenv("SPC_MSYS2_PATH={$msys2_root}");
GlobalEnvManager::addPathIfNotExists("{$msys2_root}\\usr\\bin");
}
}

View File

@@ -15,6 +15,23 @@ use StaticPHP\Runtime\SystemTarget;
class zig class zig
{ {
/** Directory zig extracts into. */
public static function path(): string
{
return PKG_ROOT_PATH . '/zig';
}
/** Path to a binary inside the zig install dir (zig, zig-cc, zig-c++, zig-ar, …). */
public static function binary(string $name = 'zig'): string
{
return self::path() . '/' . $name;
}
public static function isInstalled(): bool
{
return is_file(self::binary());
}
#[CustomBinary('zig', [ #[CustomBinary('zig', [
'linux-x86_64', 'linux-x86_64',
'linux-aarch64', 'linux-aarch64',
@@ -61,7 +78,7 @@ class zig
if ($file_hash !== $sha256) { if ($file_hash !== $sha256) {
throw new DownloaderException("Hash mismatch for downloaded Zig binary. Expected {$sha256}, got {$file_hash}"); throw new DownloaderException("Hash mismatch for downloaded Zig binary. Expected {$sha256}, got {$file_hash}");
} }
return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $latest_version], extract: PKG_ROOT_PATH . '/zig', verified: true, version: $latest_version); return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $latest_version], extract: '{pkg_root_path}/zig', verified: true, version: $latest_version);
} }
#[CustomBinaryCheckUpdate('zig', [ #[CustomBinaryCheckUpdate('zig', [
@@ -110,26 +127,24 @@ class zig
break; break;
} }
} }
if ($all_exist) { if (!$all_exist) {
return; $script_path = ROOT_DIR . '/src/globals/scripts/zig-cc.sh';
$script_content = file_get_contents($script_path);
file_put_contents("{$target_path}/zig-cc", $script_content);
chmod("{$target_path}/zig-cc", 0755);
$script_content = str_replace('zig cc', 'zig c++', $script_content);
file_put_contents("{$target_path}/zig-c++", $script_content);
file_put_contents("{$target_path}/zig-ar", "#!/usr/bin/env bash\nexec zig ar $@");
file_put_contents("{$target_path}/zig-ld.lld", "#!/usr/bin/env bash\nexec zig ld.lld $@");
file_put_contents("{$target_path}/zig-ranlib", "#!/usr/bin/env bash\nexec zig ranlib $@");
file_put_contents("{$target_path}/zig-objcopy", "#!/usr/bin/env bash\nexec zig objcopy $@");
chmod("{$target_path}/zig-c++", 0755);
chmod("{$target_path}/zig-ar", 0755);
chmod("{$target_path}/zig-ld.lld", 0755);
chmod("{$target_path}/zig-ranlib", 0755);
chmod("{$target_path}/zig-objcopy", 0755);
} }
$script_path = ROOT_DIR . '/src/globals/scripts/zig-cc.sh';
$script_content = file_get_contents($script_path);
file_put_contents("{$target_path}/zig-cc", $script_content);
chmod("{$target_path}/zig-cc", 0755);
$script_content = str_replace('zig cc', 'zig c++', $script_content);
file_put_contents("{$target_path}/zig-c++", $script_content);
file_put_contents("{$target_path}/zig-ar", "#!/usr/bin/env bash\nexec zig ar $@");
file_put_contents("{$target_path}/zig-ld.lld", "#!/usr/bin/env bash\nexec zig ld.lld $@");
file_put_contents("{$target_path}/zig-ranlib", "#!/usr/bin/env bash\nexec zig ranlib $@");
file_put_contents("{$target_path}/zig-objcopy", "#!/usr/bin/env bash\nexec zig objcopy $@");
chmod("{$target_path}/zig-c++", 0755);
chmod("{$target_path}/zig-ar", 0755);
chmod("{$target_path}/zig-ld.lld", 0755);
chmod("{$target_path}/zig-ranlib", 0755);
chmod("{$target_path}/zig-objcopy", 0755);
} }
} }

View File

@@ -1,57 +0,0 @@
<?php
declare(strict_types=1);
namespace Package\Extension;
use Package\Target\php;
use StaticPHP\Attribute\Package\BeforeStage;
use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Util\FileSystem;
#[Extension('gmssl')]
class gmssl extends PhpExtensionPackage
{
#[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-gmssl')]
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
#[PatchDescription('Fix ext-gmssl v1.1.1 compatibility with GmSSL >= 3.1.0 where SM2_VERIFY_CTX was removed (unified into SM2_SIGN_CTX)')]
public function patchSm2VerifyCtx(): void
{
// See: https://github.com/crazywhalecc/static-php-cli/issues/1182
FileSystem::replaceFileStr(
"{$this->getSourceDir()}/gmssl.c",
'SM2_VERIFY_CTX',
'SM2_SIGN_CTX'
);
}
#[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-gmssl')]
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
#[PatchDescription('Fix ext-gmssl v1.1.1: pbkdf2_hmac_sm3_genkey was renamed to sm3_pbkdf2 in GmSSL >= 3.2.0')]
public function patchPbkdf2Rename(): void
{
FileSystem::replaceFileStr(
"{$this->getSourceDir()}/gmssl.c",
'pbkdf2_hmac_sm3_genkey',
'sm3_pbkdf2'
);
}
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
#[PatchDescription('Add CHECK_LIB to config.w32 for static Windows builds')]
public function patchBeforeBuildconfWin(): bool
{
$configW32 = "{$this->getSourceDir()}/config.w32";
if (str_contains(FileSystem::readFile($configW32), 'CHECK_LIB(')) {
return false;
}
FileSystem::replaceFileStr(
$configW32,
'AC_DEFINE(',
'CHECK_LIB("gmssl.lib", "gmssl", PHP_GMSSL);' . PHP_EOL . 'AC_DEFINE('
);
return true;
}
}

View File

@@ -11,6 +11,7 @@ use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Attribute\PatchDescription; use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Package\PackageInstaller; use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\PhpExtensionPackage; use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Util\GlobalEnvManager;
use StaticPHP\Util\SourcePatcher; use StaticPHP\Util\SourcePatcher;
#[Extension('xlswriter')] #[Extension('xlswriter')]
@@ -20,14 +21,20 @@ class xlswriter extends PhpExtensionPackage
#[CustomPhpConfigureArg('Linux')] #[CustomPhpConfigureArg('Linux')]
public function getUnixConfigureArg(bool $shared, PackageInstaller $installer): string public function getUnixConfigureArg(bool $shared, PackageInstaller $installer): string
{ {
$shared = $shared ? '=shared' : ''; $arg = '--with-xlswriter --enable-reader';
$arg = "--with-xlswriter{$shared} --enable-reader";
if ($installer->getLibraryPackage('openssl')) { if ($installer->getLibraryPackage('openssl')) {
$arg .= ' --with-openssl=' . $installer->getLibraryPackage('openssl')->getBuildRootPath(); $arg .= ' --with-openssl=' . $installer->getLibraryPackage('openssl')->getBuildRootPath();
} }
return $arg; return $arg;
} }
#[BeforeStage('php', [php::class, 'makeForUnix'], 'ext-xlswriter')]
#[PatchDescription('Fix Unix build: add -std=gnu17 to CFLAGS to fix build errors on older GCC versions')]
public function patchBeforeUnixMake(): void
{
GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . ' -std=gnu17');
}
#[BeforeStage('php', [php::class, 'makeForWindows'], 'ext-xlswriter')] #[BeforeStage('php', [php::class, 'makeForWindows'], 'ext-xlswriter')]
#[PatchDescription('Fix Windows build: apply win32 patch and add UTF-8 BOM to theme.c')] #[PatchDescription('Fix Windows build: apply win32 patch and add UTF-8 BOM to theme.c')]
public function patchBeforeMakeForWindows(): void public function patchBeforeMakeForWindows(): void
@@ -40,4 +47,11 @@ class xlswriter extends PhpExtensionPackage
file_put_contents($this->getSourceDir() . '/library/libxlsxwriter/src/theme.c', $bom . $content); file_put_contents($this->getSourceDir() . '/library/libxlsxwriter/src/theme.c', $bom . $content);
} }
} }
public function getSharedExtensionEnv(): array
{
$parent = parent::getSharedExtensionEnv();
$parent['CFLAGS'] .= ' -std=gnu17';
return $parent;
}
} }

View File

@@ -17,7 +17,9 @@ class bzip2
#[PatchBeforeBuild] #[PatchBeforeBuild]
public function patchBeforeBuild(LibraryPackage $lib): void public function patchBeforeBuild(LibraryPackage $lib): void
{ {
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'CFLAGS=-Wall', 'CFLAGS=-fPIC -Wall'); // Makefile pins -O2 -fPIC; inject SPC_DEFAULT_CFLAGS
$extra = deduplicate_flags(trim((string) getenv('SPC_DEFAULT_CFLAGS')) . ' -fPIC -Wall');
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'CFLAGS=-Wall', "CFLAGS={$extra}");
} }
#[BuildFor('Windows')] #[BuildFor('Windows')]

View File

@@ -18,9 +18,11 @@ class fastlz
{ {
$cc = getenv('CC') ?: 'cc'; $cc = getenv('CC') ?: 'cc';
$ar = getenv('AR') ?: 'ar'; $ar = getenv('AR') ?: 'ar';
$extra = trim((string) getenv('SPC_DEFAULT_CFLAGS'));
$extra = $extra !== '' ? $extra . ' -fPIC' : '-O3 -fPIC';
shell()->cd($lib->getSourceDir())->initializeEnv($lib) shell()->cd($lib->getSourceDir())->initializeEnv($lib)
->exec("{$cc} -c -O3 -fPIC fastlz.c -o fastlz.o") ->exec("{$cc} -c {$extra} fastlz.c -o fastlz.o")
->exec("{$ar} rcs libfastlz.a fastlz.o"); ->exec("{$ar} rcs libfastlz.a fastlz.o");
// Copy header file // Copy header file

View File

@@ -22,7 +22,6 @@ class gettext_win
{ {
$ver = WindowsUtil::findVisualStudio(); $ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) { $vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the VS2022 (MSVC17) solution, which msbuild builds via forward compatibility.
'17' => '\MSVC17', '17' => '\MSVC17',
'16' => '\MSVC16', '16' => '\MSVC16',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"), default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"),
@@ -45,9 +44,7 @@ class gettext_win
{ {
$vs_ver_dir = ApplicationContext::get('gettext_win_vs_ver_dir'); $vs_ver_dir = ApplicationContext::get('gettext_win_vs_ver_dir');
cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}\\libintl_static") cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}\\libintl_static")
// WholeProgramOptimization (/GL) emits LTCG objects that frankenphp's lld-link cannot ->exec('msbuild libintl_static.vcxproj /t:Rebuild /p:Configuration=Release /p:Platform=x64 /p:WindowsTargetPlatformVersion=10.0');
// read ("is not a native COFF file"); disable it so the .lib stays plain COFF.
->exec('msbuild libintl_static.vcxproj /t:Rebuild /p:Configuration=Release /p:Platform=x64 /p:WindowsTargetPlatformVersion=10.0 /p:WholeProgramOptimization=false');
FileSystem::createDir($lib->getLibDir()); FileSystem::createDir($lib->getLibDir());
FileSystem::createDir($lib->getIncludeDir()); FileSystem::createDir($lib->getIncludeDir());
// libintl_a.lib is the static library output; copy as libintl.lib for linker compatibility // libintl_a.lib is the static library output; copy as libintl.lib for linker compatibility

View File

@@ -18,9 +18,7 @@ class gmssl
#[BuildFor('Darwin')] #[BuildFor('Darwin')]
public function build(LibraryPackage $lib): void public function build(LibraryPackage $lib): void
{ {
UnixCMakeExecutor::create($lib) UnixCMakeExecutor::create($lib)->build();
->addConfigureArgs('-DENABLE_SM2_PRIVATE_KEY_EXPORT=ON')
->build();
} }
#[BuildFor('Windows')] #[BuildFor('Windows')]
@@ -35,7 +33,6 @@ class gmssl
'-G "NMake Makefiles"', '-G "NMake Makefiles"',
'-DWIN32=ON', '-DWIN32=ON',
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
'-DENABLE_SM2_PRIVATE_KEY_EXPORT=ON',
'-DCMAKE_BUILD_TYPE=Release', '-DCMAKE_BUILD_TYPE=Release',
'-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"', '-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"',
'-DCMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"', '-DCMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"',
@@ -45,13 +42,13 @@ class gmssl
->toStep(1) ->toStep(1)
->build(); ->build();
cmd()->cd($buildDir)->exec('nmake gmssl XCFLAGS=/MT'); // fix cmake_install.cmake install prefix (GmSSL overrides it internally)
$installCmake = "{$buildDir}\\cmake_install.cmake";
FileSystem::writeFile(
$installCmake,
'set(CMAKE_INSTALL_PREFIX "' . str_replace('\\', '/', $lib->getBuildRootPath()) . '")' . PHP_EOL . FileSystem::readFile($installCmake)
);
$libPath = "{$lib->getBuildRootPath()}/lib"; cmd()->cd($buildDir)->exec('nmake install XCFLAGS=/MT');
$incPath = "{$lib->getBuildRootPath()}/include/gmssl";
FileSystem::createDir($libPath);
FileSystem::createDir($incPath);
FileSystem::copy("{$buildDir}\\bin\\gmssl.lib", "{$libPath}/gmssl.lib");
FileSystem::copyDir("{$lib->getSourceDir()}\\include\\gmssl", $incPath);
} }
} }

View File

@@ -24,9 +24,12 @@ class icu
#[BuildFor('Linux')] #[BuildFor('Linux')]
public function buildLinux(LibraryPackage $lib, ToolchainInterface $toolchain, PackageBuilder $builder): void public function buildLinux(LibraryPackage $lib, ToolchainInterface $toolchain, PackageBuilder $builder): void
{ {
// runConfigureICU bakes CXXFLAGS/LDFLAGS, apply user flags too
$userCxxFlags = trim((string) getenv('SPC_DEFAULT_CXXFLAGS'));
$userLdFlags = trim((string) getenv('SPC_DEFAULT_LDFLAGS'));
$cppflags = 'CPPFLAGS="-DU_CHARSET_IS_UTF8=1 -DU_USING_ICU_NAMESPACE=1 -DU_STATIC_IMPLEMENTATION=1 -DPIC -fPIC"'; $cppflags = 'CPPFLAGS="-DU_CHARSET_IS_UTF8=1 -DU_USING_ICU_NAMESPACE=1 -DU_STATIC_IMPLEMENTATION=1 -DPIC -fPIC"';
$cxxflags = 'CXXFLAGS="-std=c++17 -DPIC -fPIC -fno-ident"'; $cxxflags = "CXXFLAGS=\"-std=c++17 -DPIC -fPIC -fno-ident {$userCxxFlags}\"";
$ldflags = $toolchain->isStatic() ? 'LDFLAGS="-static"' : ''; $ldflags = $toolchain->isStatic() ? "LDFLAGS=\"-static {$userLdFlags}\"" : "LDFLAGS=\"{$userLdFlags}\"";
shell()->cd($lib->getSourceDir() . '/source')->initializeEnv($lib) shell()->cd($lib->getSourceDir() . '/source')->initializeEnv($lib)
->exec( ->exec(
"{$cppflags} {$cxxflags} {$ldflags} " . "{$cppflags} {$cxxflags} {$ldflags} " .

View File

@@ -17,7 +17,9 @@ class jbig
#[PatchBeforeBuild] #[PatchBeforeBuild]
public function patchBeforeBuild(LibraryPackage $lib): void public function patchBeforeBuild(LibraryPackage $lib): void
{ {
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'CFLAGS = -O2 -W -Wno-unused-result', 'CFLAGS = -O2 -W -Wno-unused-result -fPIC'); $extra = trim((string) getenv('SPC_DEFAULT_CFLAGS'));
$cflags = ($extra !== '' ? $extra : '-O2') . ' -W -Wno-unused-result -fPIC';
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'CFLAGS = -O2 -W -Wno-unused-result', "CFLAGS = {$cflags}");
} }
#[BuildFor('Darwin')] #[BuildFor('Darwin')]

View File

@@ -27,7 +27,7 @@ class krb5
$resolved = array_keys($installer->getResolvedPackages()); $resolved = array_keys($installer->getResolvedPackages());
$spc = new SPCConfigUtil(['no_php' => true, 'libs_only_deps' => true]); $spc = new SPCConfigUtil(['no_php' => true, 'libs_only_deps' => true]);
$config = $spc->getPackageDepsConfig($lib->getName(), $resolved, include_suggests: true); $config = $spc->getPackageDepsConfig($lib->getName(), $resolved);
$extraEnv = [ $extraEnv = [
'CFLAGS' => '-fcommon', 'CFLAGS' => '-fcommon',
'LIBS' => $config['libs'], 'LIBS' => $config['libs'],

View File

@@ -9,8 +9,10 @@ use StaticPHP\Attribute\Package\Library;
use StaticPHP\Package\LibraryPackage; use StaticPHP\Package\LibraryPackage;
use StaticPHP\Runtime\Executor\UnixCMakeExecutor; use StaticPHP\Runtime\Executor\UnixCMakeExecutor;
use StaticPHP\Runtime\Executor\WindowsCMakeExecutor; use StaticPHP\Runtime\Executor\WindowsCMakeExecutor;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Toolchain\Interface\ToolchainInterface; use StaticPHP\Toolchain\Interface\ToolchainInterface;
use StaticPHP\Toolchain\ZigToolchain; use StaticPHP\Toolchain\ZigToolchain;
use StaticPHP\Util\System\UnixUtil;
#[Library('libaom')] #[Library('libaom')]
class libaom extends LibraryPackage class libaom extends LibraryPackage
@@ -39,9 +41,23 @@ class libaom extends LibraryPackage
$new = trim($extra . ' -D_GNU_SOURCE'); $new = trim($extra . ' -D_GNU_SOURCE');
f_putenv("SPC_COMPILER_EXTRA={$new}"); f_putenv("SPC_COMPILER_EXTRA={$new}");
} }
$targetCpu = SystemTarget::getTargetArch();
if (str_starts_with($targetCpu, 'aarch')) {
$targetCpu = str_replace('aarch', 'arm', $targetCpu);
}
if (!UnixUtil::findCommand('nasm') && !UnixUtil::findCommand('yasm')) {
$targetCpu = 'generic';
}
UnixCMakeExecutor::create($this) UnixCMakeExecutor::create($this)
->setBuildDir("{$this->getSourceDir()}/builddir") ->setBuildDir("{$this->getSourceDir()}/builddir")
->addConfigureArgs('-DAOM_TARGET_CPU=generic') ->addConfigureArgs(
"-DAOM_TARGET_CPU={$targetCpu}",
'-DCONFIG_RUNTIME_CPU_DETECT=1',
'-DENABLE_EXAMPLES=OFF',
'-DENABLE_TESTS=OFF',
'-DENABLE_TOOLS=OFF',
'-DENABLE_DOCS=OFF',
)
->build(); ->build();
f_putenv("SPC_COMPILER_EXTRA={$extra}"); f_putenv("SPC_COMPILER_EXTRA={$extra}");
$this->patchPkgconfPrefix(['aom.pc']); $this->patchPkgconfPrefix(['aom.pc']);

View File

@@ -8,6 +8,7 @@ use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\Library; use StaticPHP\Attribute\Package\Library;
use StaticPHP\Package\LibraryPackage; use StaticPHP\Package\LibraryPackage;
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor; use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
use StaticPHP\Runtime\SystemTarget;
#[Library('libffi')] #[Library('libffi')]
class libffi extends LibraryPackage class libffi extends LibraryPackage
@@ -28,7 +29,7 @@ class libffi extends LibraryPackage
#[BuildFor('Darwin')] #[BuildFor('Darwin')]
public function buildDarwin(): void public function buildDarwin(): void
{ {
$arch = getenv('SPC_ARCH'); $arch = SystemTarget::getTargetArch();
UnixAutoconfExecutor::create($this) UnixAutoconfExecutor::create($this)
->configure( ->configure(
"--host={$arch}-apple-darwin", "--host={$arch}-apple-darwin",

View File

@@ -21,7 +21,6 @@ class libffi_win
{ {
$ver = WindowsUtil::findVisualStudio(); $ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) { $vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the vs17 solution, which msbuild builds via forward compatibility.
'17' => '\win32\vs17_x64', '17' => '\win32\vs17_x64',
'16' => '\win32\vs16_x64', '16' => '\win32\vs16_x64',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported!"), default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported!"),
@@ -34,9 +33,7 @@ class libffi_win
{ {
$vs_ver_dir = ApplicationContext::get('libffi_win_vs_ver_dir'); $vs_ver_dir = ApplicationContext::get('libffi_win_vs_ver_dir');
cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}") cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}")
// WholeProgramOptimization (/GL) emits LTCG objects that frankenphp's lld-link cannot ->exec('msbuild libffi-msvc.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64');
// read ("is not a native COFF file"); disable it so the .lib stays plain COFF.
->exec('msbuild libffi-msvc.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64 /p:WholeProgramOptimization=false');
FileSystem::createDir($lib->getLibDir()); FileSystem::createDir($lib->getLibDir());
FileSystem::createDir($lib->getIncludeDir()); FileSystem::createDir($lib->getIncludeDir());

View File

@@ -24,6 +24,17 @@ class libheif
'list(APPEND REQUIRES_PRIVATE "libbrotlidec")' . "\n" . ' list(APPEND REQUIRES_PRIVATE "libbrotlienc")' 'list(APPEND REQUIRES_PRIVATE "libbrotlidec")' . "\n" . ' list(APPEND REQUIRES_PRIVATE "libbrotlienc")'
); );
} }
// libheif 1.22+ ships a C-incompatible header: `struct heif_bad_pixel`
$heif_properties = $lib->getSourceDir() . '/libheif/api/libheif/heif_properties.h';
if (file_exists($heif_properties)
&& str_contains(file_get_contents($heif_properties), 'struct heif_bad_pixel { uint32_t row; uint32_t column; };')
) {
FileSystem::replaceFileStr(
$heif_properties,
'struct heif_bad_pixel { uint32_t row; uint32_t column; };',
'typedef struct heif_bad_pixel { uint32_t row; uint32_t column; } heif_bad_pixel;'
);
}
} }
#[BuildFor('Darwin')] #[BuildFor('Darwin')]

View File

@@ -21,7 +21,6 @@ class libiconv_win
{ {
$ver = WindowsUtil::findVisualStudio(); $ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) { $vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the VS2022 (MSVC17) solution, which msbuild builds via forward compatibility.
'17' => '\MSVC17', '17' => '\MSVC17',
'16' => '\MSVC16', '16' => '\MSVC16',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"), default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"),
@@ -34,9 +33,7 @@ class libiconv_win
{ {
$vs_ver_dir = ApplicationContext::get('vs_ver_dir'); $vs_ver_dir = ApplicationContext::get('vs_ver_dir');
cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}") cmd()->cd("{$lib->getSourceDir()}{$vs_ver_dir}")
// WholeProgramOptimization (/GL) emits LTCG objects that frankenphp's lld-link cannot ->exec('msbuild libiconv.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64');
// read ("is not a native COFF file"); disable it so the .lib stays plain COFF.
->exec('msbuild libiconv.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64 /p:WholeProgramOptimization=false');
FileSystem::createDir($lib->getLibDir()); FileSystem::createDir($lib->getLibDir());
FileSystem::createDir($lib->getIncludeDir()); FileSystem::createDir($lib->getIncludeDir());
FileSystem::copy("{$lib->getSourceDir()}{$vs_ver_dir}\\x64\\lib\\libiconv.lib", "{$lib->getLibDir()}\\libiconv.lib"); FileSystem::copy("{$lib->getSourceDir()}{$vs_ver_dir}\\x64\\lib\\libiconv.lib", "{$lib->getLibDir()}\\libiconv.lib");

View File

@@ -17,10 +17,17 @@ use StaticPHP\Util\FileSystem;
class liblz4 class liblz4
{ {
#[PatchBeforeBuild] #[PatchBeforeBuild]
#[PatchDescription('Fix Makefile install target for static liblz4')] #[PatchDescription('Compile lib sources individually so -flto -c with multiple inputs works under zig-cc/clang')]
public function patchBeforeBuild(LibraryPackage $lib): void public function patchBeforeBuild(LibraryPackage $lib): void
{ {
FileSystem::replaceFileStr($lib->getSourceDir() . '/programs/Makefile', 'install: lz4', "install: lz4\n\ninstallewfwef: lz4"); // `-flto -c` with multiple input files only writes a .o for the
// first source — the others are silently dropped, leaving liblz4.a with a
// single object. Compile each source individually so all .o files exist.
FileSystem::replaceFileStr(
$lib->getSourceDir() . '/lib/Makefile',
"liblz4.a: \$(SRCFILES)\nifeq (\$(BUILD_STATIC),yes) # can be disabled on command line\n\t@echo compiling static library\n\t\$(COMPILE.c) \$^\n\t\$(AR) rcs \$@ *.o\nendif",
"liblz4.a: \$(SRCFILES:.c=.o)\nifeq (\$(BUILD_STATIC),yes) # can be disabled on command line\n\t@echo compiling static library\n\t\$(AR) rcs \$@ \$^\nendif"
);
} }
#[BuildFor('Windows')] #[BuildFor('Windows')]

View File

@@ -9,6 +9,7 @@ use StaticPHP\Attribute\Package\Library;
use StaticPHP\Package\LibraryPackage; use StaticPHP\Package\LibraryPackage;
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor; use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
use StaticPHP\Runtime\Executor\WindowsCMakeExecutor; use StaticPHP\Runtime\Executor\WindowsCMakeExecutor;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\FileSystem; use StaticPHP\Util\FileSystem;
#[Library('libpng')] #[Library('libpng')]
@@ -24,7 +25,7 @@ class libpng
]; ];
// Enable architecture-specific optimizations // Enable architecture-specific optimizations
match (getenv('SPC_ARCH')) { match (SystemTarget::getTargetArch()) {
'x86_64' => $args[] = '--enable-intel-sse', 'x86_64' => $args[] = '--enable-intel-sse',
'aarch64' => $args[] = '--enable-arm-neon', 'aarch64' => $args[] = '--enable-arm-neon',
default => null, default => null,

View File

@@ -42,16 +42,13 @@ class libsodium
{ {
$ver = WindowsUtil::findVisualStudio(); $ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) { $vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the vs2022 solution, which msbuild builds via forward compatibility.
'17' => '\vs2022', '17' => '\vs2022',
'16' => '\vs2019', '16' => '\vs2019',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"), default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"),
}; };
cmd()->cd("{$lib->getSourceDir()}\\builds\\msvc{$vs_ver_dir}") cmd()->cd("{$lib->getSourceDir()}\\builds\\msvc{$vs_ver_dir}")
// WholeProgramOptimization (/GL) emits LTCG objects that frankenphp's lld-link cannot ->exec('msbuild libsodium.sln /t:Rebuild /p:Configuration=StaticRelease /p:Platform=x64 /p:PreprocessorDefinitions="SODIUM_STATIC=1"');
// read ("is not a native COFF file"); disable it so the .lib stays plain COFF.
->exec('msbuild libsodium.sln /t:Rebuild /p:Configuration=StaticRelease /p:Platform=x64 /p:WholeProgramOptimization=false /p:PreprocessorDefinitions="SODIUM_STATIC=1"');
FileSystem::createDir($lib->getLibDir()); FileSystem::createDir($lib->getLibDir());
FileSystem::createDir($lib->getIncludeDir()); FileSystem::createDir($lib->getIncludeDir());

View File

@@ -18,7 +18,6 @@ class libssh2
{ {
WindowsCMakeExecutor::create($lib) WindowsCMakeExecutor::create($lib)
->addConfigureArgs( ->addConfigureArgs(
'-DCRYPTO_BACKEND=WinCNG',
'-DENABLE_ZLIB_COMPRESSION=ON', '-DENABLE_ZLIB_COMPRESSION=ON',
'-DBUILD_TESTING=OFF' '-DBUILD_TESTING=OFF'
) )

View File

@@ -21,7 +21,6 @@ class mpir
{ {
$ver = WindowsUtil::findVisualStudio(); $ver = WindowsUtil::findVisualStudio();
$vs_ver_dir = match ($ver['major_version']) { $vs_ver_dir = match ($ver['major_version']) {
'18', // VS 2026 reuses the build.vc17 solution, which msbuild builds via forward compatibility.
'17' => '\build.vc17', '17' => '\build.vc17',
'16' => '\build.vc16', '16' => '\build.vc16',
default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"), default => throw new EnvironmentException("Current VS version {$ver['major_version']} is not supported yet!"),

View File

@@ -6,6 +6,8 @@ namespace Package\Library;
use StaticPHP\Attribute\Package\BuildFor; use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\Library; use StaticPHP\Attribute\Package\Library;
use StaticPHP\Attribute\Package\PatchBeforeBuild;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Package\LibraryPackage; use StaticPHP\Package\LibraryPackage;
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor; use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
use StaticPHP\Toolchain\Interface\ToolchainInterface; use StaticPHP\Toolchain\Interface\ToolchainInterface;
@@ -16,6 +18,24 @@ use StaticPHP\Util\FileSystem;
#[Library('ncursesw')] #[Library('ncursesw')]
class ncurses class ncurses
{ {
#[PatchBeforeBuild]
#[PatchDescription('Filter clang/zig "N warning(s) generated." line out of MKlib_gen.sh preprocessor pipe')]
public function patchBeforeBuild(LibraryPackage $lib): void
{
// MKlib_gen.sh feeds the C preprocessor's stdout through a sed/awk
// pipeline into lib_gen.c. zig-cc/clang emits "N warning(s) generated."
// on stdout (not stderr), and that line ends up as invalid C in the
// generated source. Filter it out of the pipe before sed sees it.
$mklibGen = $lib->getSourceDir() . '/ncurses/base/MKlib_gen.sh';
if (is_file($mklibGen) && !str_contains((string) file_get_contents($mklibGen), "| grep -v ' generated")) {
FileSystem::replaceFileStr(
$mklibGen,
'$preprocessor $TMP 2>/dev/null \\',
"\$preprocessor \$TMP 2>/dev/null \\\n| grep -v ' generated\\.\$' \\",
);
}
}
#[BuildFor('Darwin')] #[BuildFor('Darwin')]
#[BuildFor('Linux')] #[BuildFor('Linux')]
public function build(LibraryPackage $package, ToolchainInterface $toolchain): void public function build(LibraryPackage $package, ToolchainInterface $toolchain): void
@@ -45,6 +65,7 @@ class ncurses
'--without-tests', '--without-tests',
'--without-dlsym', '--without-dlsym',
'--without-debug', '--without-debug',
'--disable-stripping',
'--enable-symlinks', '--enable-symlinks',
"--with-terminfo-dirs={$terminfo_dirs}", "--with-terminfo-dirs={$terminfo_dirs}",
"--bindir={$package->getBinDir()}", "--bindir={$package->getBinDir()}",

View File

@@ -24,7 +24,7 @@ class openssl
{ {
if (SystemTarget::getTargetOS() === 'Windows') { if (SystemTarget::getTargetOS() === 'Windows') {
global $argv; global $argv;
$perl_path_native = PKG_ROOT_PATH . '\strawberry-perl\perl\bin\perl.exe'; $perl_path_native = PKG_ROOT_PATH . '\strawberry-perl-' . arch2gnu(php_uname('m')) . '-win\perl\bin\perl.exe';
$perl = file_exists($perl_path_native) ? ($perl_path_native) : WindowsUtil::findCommand('perl.exe'); $perl = file_exists($perl_path_native) ? ($perl_path_native) : WindowsUtil::findCommand('perl.exe');
if ($perl === null) { if ($perl === null) {
throw new EnvironmentException( throw new EnvironmentException(
@@ -76,7 +76,7 @@ class openssl
public function buildForDarwin(LibraryPackage $pkg): void public function buildForDarwin(LibraryPackage $pkg): void
{ {
$zlib_libs = $pkg->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles(); $zlib_libs = $pkg->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles();
$arch = getenv('SPC_ARCH'); $arch = SystemTarget::getTargetArch();
shell()->cd($pkg->getSourceDir())->initializeEnv($pkg) shell()->cd($pkg->getSourceDir())->initializeEnv($pkg)
->exec( ->exec(
@@ -95,12 +95,7 @@ class openssl
#[BuildFor('Linux')] #[BuildFor('Linux')]
public function build(LibraryPackage $lib): void public function build(LibraryPackage $lib): void
{ {
$arch = getenv('SPC_ARCH'); $arch = SystemTarget::getTargetArch();
$env = "CC='" . getenv('CC') . ' -idirafter ' . BUILD_INCLUDE_PATH .
' -idirafter /usr/include/ ' .
' -idirafter /usr/include/' . getenv('SPC_ARCH') . '-linux-gnu/ ' .
"' ";
$ex_lib = trim($lib->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles()) . ' -ldl -pthread'; $ex_lib = trim($lib->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles()) . ' -ldl -pthread';
$zlib_extra = $zlib_extra =
@@ -111,9 +106,15 @@ class openssl
$openssl_dir ??= LinuxUtil::getOSRelease()['dist'] === 'redhat' ? '/etc/pki/tls' : '/etc/ssl'; $openssl_dir ??= LinuxUtil::getOSRelease()['dist'] === 'redhat' ? '/etc/pki/tls' : '/etc/ssl';
$ex_lib = trim($ex_lib); $ex_lib = trim($ex_lib);
// anything we want included (PGO -fprofile-*, LTO, custom hardening)
// has to be appended on the command line *after* the target name.
$userCFlags = trim((string) getenv('SPC_DEFAULT_CFLAGS'));
$userLdFlags = trim((string) getenv('SPC_DEFAULT_LDFLAGS'));
$userExtra = trim($userCFlags . ' ' . $userLdFlags);
shell()->cd($lib->getSourceDir())->initializeEnv($lib) shell()->cd($lib->getSourceDir())->initializeEnv($lib)
->exec( ->exec(
"{$env} ./Configure no-shared zlib " . './Configure no-shared zlib ' .
"--prefix={$lib->getBuildRootPath()} " . "--prefix={$lib->getBuildRootPath()} " .
"--libdir={$lib->getLibDir()} " . "--libdir={$lib->getLibDir()} " .
"--openssldir={$openssl_dir} " . "--openssldir={$openssl_dir} " .
@@ -121,7 +122,8 @@ class openssl
'enable-pie ' . 'enable-pie ' .
'no-legacy ' . 'no-legacy ' .
'no-tests ' . 'no-tests ' .
"linux-{$arch}" "linux-{$arch} " .
$userExtra
) )
->exec('make clean') ->exec('make clean')
->exec("make -j{$lib->getBuilder()->concurrency} CNF_EX_LIBS=\"{$ex_lib}\"") ->exec("make -j{$lib->getBuilder()->concurrency} CNF_EX_LIBS=\"{$ex_lib}\"")

View File

@@ -58,7 +58,7 @@ class postgresql extends LibraryPackage
public function buildUnix(PackageInstaller $installer, PackageBuilder $builder): void public function buildUnix(PackageInstaller $installer, PackageBuilder $builder): void
{ {
$spc_config = new SPCConfigUtil(['no_php' => true, 'libs_only_deps' => true]); $spc_config = new SPCConfigUtil(['no_php' => true, 'libs_only_deps' => true]);
$config = $spc_config->getPackageDepsConfig('postgresql', array_keys($installer->getResolvedPackages()), include_suggests: $builder->getOption('with-suggests', false)); $config = $spc_config->getPackageDepsConfig('postgresql', array_keys($installer->getResolvedPackages()));
$env_vars = [ $env_vars = [
'CFLAGS' => $config['cflags'] . ' -std=c17', 'CFLAGS' => $config['cflags'] . ' -std=c17',

View File

@@ -20,6 +20,11 @@ class qdbm
{ {
$ac = UnixAutoconfExecutor::create($lib)->configure(); $ac = UnixAutoconfExecutor::create($lib)->configure();
FileSystem::replaceFileRegex($lib->getSourceDir() . '/Makefile', '/MYLIBS = libqdbm.a.*/m', 'MYLIBS = libqdbm.a'); FileSystem::replaceFileRegex($lib->getSourceDir() . '/Makefile', '/MYLIBS = libqdbm.a.*/m', 'MYLIBS = libqdbm.a');
// Makefile pins -O3, replace with SPC_DEFAULT_CFLAGS
$extra = trim((string) getenv('SPC_DEFAULT_CFLAGS'));
if ($extra !== '') {
FileSystem::replaceFileRegex($lib->getSourceDir() . '/Makefile', '/^CFLAGS = .*$/m', "CFLAGS = -Wall {$extra}");
}
$ac->make(SystemTarget::getTargetOS() === 'Darwin' ? 'mac' : ''); $ac->make(SystemTarget::getTargetOS() === 'Darwin' ? 'mac' : '');
$lib->patchPkgconfPrefix(['qdbm.pc']); $lib->patchPkgconfPrefix(['qdbm.pc']);
} }

View File

@@ -20,14 +20,27 @@ class unixodbc extends LibraryPackage
{ {
$sysconf_selector = match ($os = SystemTarget::getTargetOS()) { $sysconf_selector = match ($os = SystemTarget::getTargetOS()) {
'Darwin' => match (SystemTarget::getTargetArch()) { 'Darwin' => match (SystemTarget::getTargetArch()) {
'x86_64' => is_dir('/usr/local/etc') ? '/usr/local/etc' : '/opt/local/etc', 'x86_64' => '/usr/local/etc',
'aarch64' => is_dir('/opt/homebrew/etc') ? '/opt/homebrew/etc' : '/opt/local/etc', 'aarch64' => '/opt/homebrew/etc',
default => throw new WrongUsageException('Unsupported architecture: ' . GNU_ARCH), default => throw new WrongUsageException('Unsupported architecture: ' . GNU_ARCH),
}, },
'Linux' => '/etc', 'Linux' => '/etc',
default => throw new WrongUsageException("Unsupported OS: {$os}"), default => throw new WrongUsageException("Unsupported OS: {$os}"),
}; };
UnixAutoconfExecutor::create($this)
// unixodbc bundles libltdl; libltdl is incompatible with -flto
// (https://bugs.gentoo.org/532672).
$stripLto = static fn (string $s): string => clean_spaces((string) preg_replace('/(^|\s)-flto(=\S+)?(?=\s|$)/', ' ', $s));
$cflags = $stripLto((string) getenv('SPC_DEFAULT_CFLAGS'));
$cxxflags = $stripLto((string) getenv('SPC_DEFAULT_CXXFLAGS'));
$ldflags = $stripLto((string) getenv('SPC_DEFAULT_LDFLAGS'));
$make = UnixAutoconfExecutor::create($this)
->setEnv([
'CFLAGS' => $cflags,
'CXXFLAGS' => $cxxflags,
'LDFLAGS' => $ldflags,
])
->configure( ->configure(
'--disable-debug', '--disable-debug',
'--disable-dependency-tracking', '--disable-dependency-tracking',
@@ -35,8 +48,15 @@ class unixodbc extends LibraryPackage
'--with-included-ltdl', '--with-included-ltdl',
"--sysconfdir={$sysconf_selector}", "--sysconfdir={$sysconf_selector}",
'--enable-gui=no', '--enable-gui=no',
) );
->make();
// The exe/ subdirectory builds odbcinst/iusql/etc, turn it into a no-op
file_put_contents(
"{$this->getSourceDir()}/exe/Makefile",
".PHONY: all install clean check distclean install-strip\nall install clean check distclean install-strip:\n\t@true\n",
);
$make->make();
$this->patchPkgconfPrefix(['odbc.pc', 'odbccr.pc', 'odbcinst.pc']); $this->patchPkgconfPrefix(['odbc.pc', 'odbccr.pc', 'odbcinst.pc']);
$this->patchLaDependencyPrefix(); $this->patchLaDependencyPrefix();
} }

View File

@@ -40,7 +40,7 @@ class curl
->optionalPackage('brotli', ...cmake_boolean_args('CURL_BROTLI')) ->optionalPackage('brotli', ...cmake_boolean_args('CURL_BROTLI'))
->addConfigureArgs( ->addConfigureArgs(
'-DBUILD_CURL_EXE=ON', '-DBUILD_CURL_EXE=ON',
'-DZSTD_LIBRARY=' . BUILD_LIB_PATH . '/zstd_static.lib', '-DZSTD_LIBRARY=zstd_static.lib',
'-DBUILD_TESTING=OFF', '-DBUILD_TESTING=OFF',
'-DBUILD_EXAMPLES=OFF', '-DBUILD_EXAMPLES=OFF',
'-DUSE_LIBIDN2=OFF', '-DUSE_LIBIDN2=OFF',

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Package\Target; namespace Package\Target;
use Package\Target\php\frankenphp; use Package\Target\php\frankenphp;
use Package\Target\php\pgo;
use Package\Target\php\unix; use Package\Target\php\unix;
use Package\Target\php\windows; use Package\Target\php\windows;
use StaticPHP\Artifact\ArtifactCache; use StaticPHP\Artifact\ArtifactCache;
@@ -48,6 +49,7 @@ class php extends TargetPackage
use unix; use unix;
use windows; use windows;
use frankenphp; use frankenphp;
use pgo;
/** @var string[] Supported major PHP versions */ /** @var string[] Supported major PHP versions */
public const array SUPPORTED_MAJOR_VERSIONS = ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']; public const array SUPPORTED_MAJOR_VERSIONS = ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'];
@@ -255,13 +257,6 @@ class php extends TargetPackage
$installer->addBuildPackage('php-embed'); $installer->addBuildPackage('php-embed');
} }
// UPX compression: ensure the upx tool package is installed when requested.
// upx is a ToolPackage now, so it's installed directly rather than added as a
// library dependency (tool packages bypass the DependencyResolver graph).
if ($package->getBuildOption('with-upx-pack')) {
$installer->addInstallPackage('upx');
}
return [...$extensions_pkg, ...$additional_packages]; return [...$extensions_pkg, ...$additional_packages];
} }
@@ -275,12 +270,14 @@ class php extends TargetPackage
} }
} }
// linux does not support loading shared libraries when target is pure static // linux does not support loading shared libraries when target is pure static
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static'; if ($package->getName() === 'php-embed') {
if (SystemTarget::getTargetOS() === 'Linux' && ApplicationContext::get(ToolchainInterface::class)->isStatic() && $embed_type === 'shared') { $embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
throw new WrongUsageException( if (SystemTarget::getTargetOS() === 'Linux' && ApplicationContext::get(ToolchainInterface::class)->isStatic() && $embed_type === 'shared') {
'Linux does not support loading shared libraries when linking libc statically. ' . throw new WrongUsageException(
'Change SPC_CMD_VAR_PHP_EMBED_TYPE to static.' 'Linux does not support loading shared libraries when linking libc statically. ' .
); 'Change SPC_CMD_VAR_PHP_EMBED_TYPE to static.'
);
}
} }
} }
@@ -339,7 +336,7 @@ class php extends TargetPackage
logger()->info("Adding hardcoded INI [{$source_name} = {$ini_value}]"); logger()->info("Adding hardcoded INI [{$source_name} = {$ini_value}]");
} }
if (!empty($custom_ini)) { if (!empty($custom_ini)) {
ApplicationContext::invoke([SourcePatcher::class, 'patchHardcodedINI'], [$package->getSourceDir(), $custom_ini]); ApplicationContext::invoke([SourcePatcher::class, 'patchHardcodedINI'], ['php_source_dir' => $package->getSourceDir(), 'ini' => $custom_ini]);
} }
// Patch StaticPHP version // Patch StaticPHP version

View File

@@ -73,10 +73,7 @@ trait frankenphp
$staticFlags = ''; $staticFlags = '';
} }
$resolved = array_keys($installer->getResolvedPackages()); $config = new SPCConfigUtil()->config(['frankenphp']);
// remove self from deps
$resolved = array_filter($resolved, fn ($pkg_name) => $pkg_name !== $package->getName());
$config = new SPCConfigUtil()->config($resolved);
$cflags = "{$package->getLibExtraCFlags()} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . " -DFRANKENPHP_VERSION={$frankenphp_version}"; $cflags = "{$package->getLibExtraCFlags()} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . " -DFRANKENPHP_VERSION={$frankenphp_version}";
$libs = $config['libs']; $libs = $config['libs'];
@@ -88,10 +85,13 @@ trait frankenphp
$libs .= ' -lgcov'; $libs .= ' -lgcov';
} }
$extraLdProgram = clean_spaces((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM'));
$env = [ $env = [
'CGO_ENABLED' => '1', 'CGO_ENABLED' => '1',
'CGO_CFLAGS' => clean_spaces($cflags), 'CGO_CFLAGS' => clean_spaces($cflags),
'CGO_LDFLAGS' => "{$package->getLibExtraLdFlags()} {$staticFlags} {$config['ldflags']} {$libs}", 'CGO_LDFLAGS' => clean_spaces("{$package->getLibExtraLdFlags()} {$staticFlags} {$config['ldflags']} {$libs} {$extraLdProgram}"),
// cgo strips flags not on its safe allowlist; widen it
'CGO_LDFLAGS_ALLOW' => '-Wl,-z,.*|-Wl,--.*|-flto.*|-fprofile-.*',
'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' . 'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' .
'-ldflags \"-linkmode=external ' . $extLdFlags . ' ' . '-ldflags \"-linkmode=external ' . $extLdFlags . ' ' .
'-X \'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy\' ' . '-X \'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy\' ' .
@@ -101,10 +101,12 @@ trait frankenphp
"-tags={$muslTags}nobadger,nomysql,nopgx{$no_brotli}{$no_watcher}", "-tags={$muslTags}nobadger,nomysql,nopgx{$no_brotli}{$no_watcher}",
'LD_LIBRARY_PATH' => BUILD_LIB_PATH, 'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
]; ];
$pgo = file_exists("{$source_dir}/caddy/frankenphp/default.pgo") ? "--pgo {$source_dir}/caddy/frankenphp/default.pgo " : '';
InteractiveTerm::setMessage('Building frankenphp: ' . ConsoleColor::yellow('building with xcaddy')); InteractiveTerm::setMessage('Building frankenphp: ' . ConsoleColor::yellow('building with xcaddy'));
shell()->cd(BUILD_LIB_PATH) shell()->cd(BUILD_LIB_PATH)
->setEnv($env) ->setEnv($env)
->exec("xcaddy build --output frankenphp {$xcaddy_modules}"); ->exec('go clean -cache') // fix stale include evaluation
->exec("xcaddy build --output frankenphp {$pgo}{$xcaddy_modules}");
$builder->deployBinary(BUILD_LIB_PATH . '/frankenphp', BUILD_BIN_PATH . '/frankenphp'); $builder->deployBinary(BUILD_LIB_PATH . '/frankenphp', BUILD_BIN_PATH . '/frankenphp');
$package->setOutput('Binary path for FrankenPHP SAPI', BUILD_BIN_PATH . '/frankenphp'); $package->setOutput('Binary path for FrankenPHP SAPI', BUILD_BIN_PATH . '/frankenphp');

View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace Package\Target\php;
use StaticPHP\Attribute\Package\AfterStage;
use StaticPHP\Attribute\Package\BeforeStage;
use StaticPHP\Attribute\Package\ConditionalOn;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Util\Pgo\PgoContext;
use StaticPHP\Util\SourcePatcher;
trait pgo
{
#[ConditionalOn(PgoContext::class)]
#[BeforeStage('php', [self::class, 'buildconfForUnix'], 'php')]
#[PatchDescription('Inject __llvm_profile_write_file() flush at php/frankenphp shutdown for instrumented builds')]
public function pgoApplyShutdownPatches(PgoContext $pgo): void
{
if (!$pgo->isInstrument() && !$pgo->isCsInstrument()) {
return;
}
foreach (PgoContext::SHUTDOWN_PATCHES as $dir => $patch) {
$cwd = SOURCE_PATH . '/' . $dir;
if (!is_dir($cwd)) {
continue;
}
if (!SourcePatcher::patchFile($patch, $cwd)) {
throw new WrongUsageException("PGO --phase=instrument: patch {$patch} failed to apply in {$cwd}");
}
logger()->info("PGO --phase=instrument: applied {$patch}");
}
}
#[ConditionalOn(PgoContext::class)]
#[AfterStage('php', [self::class, 'configureForUnix'], 'php')]
#[PatchDescription('Patch libtool to passthrough -fcs-profile-* for context-sensitive PGO')]
public function pgoPatchLibtoolForCsInstrument(PgoContext $pgo): void
{
if (!$pgo->isCsInstrument()) {
return;
}
$libtool = SOURCE_PATH . '/php-src/libtool';
if (!is_file($libtool)) {
return;
}
$contents = (string) file_get_contents($libtool);
if (str_contains($contents, '-fcs-profile-*')) {
return;
}
$patched = str_replace('-fprofile-*|-F*', '-fprofile-*|-fcs-profile-*|-F*', $contents);
if ($patched === $contents) {
logger()->warning('PGO --phase=cs-instrument: could not patch libtool for -fcs-profile-* passthrough');
return;
}
file_put_contents($libtool, $patched);
logger()->info('PGO --phase=cs-instrument: patched libtool for -fcs-profile-* passthrough');
}
#[ConditionalOn(PgoContext::class)]
#[BeforeStage('php', [self::class, 'configureForUnix'], 'php')]
public function pgoApplyConfigureFlags(PgoContext $pgo): void
{
$sapis = $pgo->trainableSapis();
if ($sapis === []) {
return;
}
$pgo->applyEnvFor($sapis[0]);
}
#[ConditionalOn(PgoContext::class)]
#[BeforeStage('php', [self::class, 'makeCliForUnix'], 'php')]
public function pgoBeforeMakeCli(PgoContext $pgo, TargetPackage $package): void
{
$this->pgoBeforeSapiMake($pgo, $package, 'cli');
}
#[ConditionalOn(PgoContext::class)]
#[BeforeStage('php', [self::class, 'makeCgiForUnix'], 'php')]
public function pgoBeforeMakeCgi(PgoContext $pgo, TargetPackage $package): void
{
$this->pgoBeforeSapiMake($pgo, $package, 'cgi');
}
#[ConditionalOn(PgoContext::class)]
#[BeforeStage('php', [self::class, 'makeFpmForUnix'], 'php')]
public function pgoBeforeMakeFpm(PgoContext $pgo, TargetPackage $package): void
{
$this->pgoBeforeSapiMake($pgo, $package, 'fpm');
}
#[ConditionalOn(PgoContext::class)]
#[BeforeStage('php', [self::class, 'makeMicroForUnix'], 'php')]
public function pgoBeforeMakeMicro(PgoContext $pgo, TargetPackage $package): void
{
$this->pgoBeforeSapiMake($pgo, $package, 'micro');
}
#[ConditionalOn(PgoContext::class)]
#[BeforeStage('php', [self::class, 'makeEmbedForUnix'], 'php')]
public function pgoBeforeMakeEmbed(PgoContext $pgo, TargetPackage $package): void
{
$this->pgoBeforeSapiMake($pgo, $package, 'embed');
}
#[ConditionalOn(PgoContext::class)]
#[BeforeStage('php', [self::class, 'buildFrankenphpForUnix'], 'php')]
public function pgoBeforeBuildFrankenphp(PgoContext $pgo): void
{
$pgo->applyEnvFor('frankenphp');
logger()->info("PGO {$pgo->mode}: applying flags for frankenphp");
}
private function pgoBeforeSapiMake(PgoContext $pgo, TargetPackage $package, string $sapi): void
{
$resolved = $pgo->resolveSapi($sapi);
if (!in_array($resolved, $pgo->trainableSapis(), true)) {
return;
}
shell()->cd($package->getSourceDir())->exec('make clean');
$pgo->applyEnvFor($sapi);
logger()->info("PGO {$pgo->mode}: applying flags for {$sapi}");
}
}

View File

@@ -20,6 +20,7 @@ use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Package\TargetPackage; use StaticPHP\Package\TargetPackage;
use StaticPHP\Runtime\SystemTarget; use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Toolchain\Interface\ToolchainInterface; use StaticPHP\Toolchain\Interface\ToolchainInterface;
use StaticPHP\Toolchain\ToolchainManager;
use StaticPHP\Toolchain\ZigToolchain; use StaticPHP\Toolchain\ZigToolchain;
use StaticPHP\Util\DirDiff; use StaticPHP\Util\DirDiff;
use StaticPHP\Util\FileSystem; use StaticPHP\Util\FileSystem;
@@ -41,6 +42,15 @@ trait unix
// php-src patches from micro (reads SPC_MICRO_PATCHES env var) // php-src patches from micro (reads SPC_MICRO_PATCHES env var)
SourcePatcher::patchPhpSrc(); SourcePatcher::patchPhpSrc();
$microFileinfo = "{$package->getSourceDir()}/sapi/micro/php_micro_fileinfo.c";
if (is_file($microFileinfo) && !str_contains((string) file_get_contents($microFileinfo), 'Elf32_Shdr')) {
FileSystem::replaceFileStr(
$microFileinfo,
'typedef Elf32_Ehdr Elf_Ehdr;',
'typedef Elf32_Ehdr Elf_Ehdr; typedef Elf32_Shdr Elf_Shdr;',
);
}
// patch configure.ac for musl and musl-toolchain // patch configure.ac for musl and musl-toolchain
$musl = SystemTarget::getTargetOS() === 'Linux' && SystemTarget::getLibc() === 'musl'; $musl = SystemTarget::getTargetOS() === 'Linux' && SystemTarget::getLibc() === 'musl';
FileSystem::backupFile(SOURCE_PATH . '/php-src/configure.ac'); FileSystem::backupFile(SOURCE_PATH . '/php-src/configure.ac');
@@ -59,7 +69,7 @@ trait unix
} }
if (self::getPHPVersionID() >= 80300 && self::getPHPVersionID() < 80400) { if (self::getPHPVersionID() >= 80300 && self::getPHPVersionID() < 80400) {
SourcePatcher::patchFile('spc_fix_avx512_cache_before_80400.patch', $this->getSourceDir()); SourcePatcher::patchFile('spc_fix_avx512_cache_before_80400.patch', SOURCE_PATH . '/php-src');
} }
} }
@@ -92,12 +102,10 @@ trait unix
$args = []; $args = [];
$version_id = self::getPHPVersionID(); $version_id = self::getPHPVersionID();
// disable undefined behavior sanitizer when opcache JIT is enabled (Linux only) // disable undefined behavior sanitizer for zig, trips up on lua minijit and opcache-jit
if (SystemTarget::getTargetOS() === 'Linux' && !$package->getBuildOption('disable-opcache-jit', false)) { if (SystemTarget::getTargetOS() === 'Linux' && ToolchainManager::getToolchainClass() === ZigToolchain::class) {
if ($version_id >= 80500 || $installer->isPackageResolved('ext-opcache')) { $compiler_extra = getenv('SPC_COMPILER_EXTRA') ?: '';
$compiler_extra = getenv('SPC_COMPILER_EXTRA') ?: ''; GlobalEnvManager::putenv('SPC_COMPILER_EXTRA=' . trim($compiler_extra . ' -fno-sanitize=undefined'));
GlobalEnvManager::putenv('SPC_COMPILER_EXTRA=' . trim($compiler_extra . ' -fno-sanitize=undefined'));
}
} }
// PHP JSON extension is built-in since PHP 8.0 // PHP JSON extension is built-in since PHP 8.0
if ($version_id < 80000) { if ($version_id < 80000) {
@@ -129,6 +137,10 @@ trait unix
$args[] = $installer->isPackageResolved('php-cgi') ? '--enable-cgi' : '--disable-cgi'; $args[] = $installer->isPackageResolved('php-cgi') ? '--enable-cgi' : '--disable-cgi';
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static'; $embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
$args[] = $installer->isPackageResolved('php-embed') ? "--enable-embed={$embed_type}" : '--disable-embed'; $args[] = $installer->isPackageResolved('php-embed') ? "--enable-embed={$embed_type}" : '--disable-embed';
// Cross-compile: pass --host so configure picks the correct fiber asm file and host_cpu logic
if ($host_triple = SystemTarget::getAutoconfHostTriple()) {
$args[] = "--host={$host_triple}";
}
$args[] = getenv('SPC_EXTRA_PHP_VARS') ?: null; $args[] = getenv('SPC_EXTRA_PHP_VARS') ?: null;
$args = implode(' ', array_filter($args)); $args = implode(' ', array_filter($args));
@@ -141,7 +153,7 @@ trait unix
$this->seekPhpSrcLogFileOnException(fn () => shell()->cd($package->getSourceDir())->setEnv([ $this->seekPhpSrcLogFileOnException(fn () => shell()->cd($package->getSourceDir())->setEnv([
'CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'),
'CPPFLAGS' => "-I{$package->getIncludeDir()}", 'CPPFLAGS' => "-I{$package->getIncludeDir()}",
'LDFLAGS' => "-L{$package->getLibDir()} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), 'LDFLAGS' => "-L{$package->getLibDir()}",
'LIBS' => $vars['EXTRA_LIBS'] ?? '', 'LIBS' => $vars['EXTRA_LIBS'] ?? '',
])->exec("{$cmd} {$args} {$static_extension_str}"), $package->getSourceDir()); ])->exec("{$cmd} {$args} {$static_extension_str}"), $package->getSourceDir());
} }
@@ -168,7 +180,7 @@ trait unix
#[BeforeStage('php', [self::class, 'makeForUnix'], 'php')] #[BeforeStage('php', [self::class, 'makeForUnix'], 'php')]
#[PatchDescription('Patch Makefile to fix //lib path for Linux builds')] #[PatchDescription('Patch Makefile to fix //lib path for Linux builds')]
#[PatchDescription('Patch BUILD_CC to use system cc instead of zig-cc (prevents minilua crash)')] #[PatchDescription('Under CI: patch BUILD_CC to system cc — zig-cc-built minilua segfaults there for reasons we cannot reproduce locally')]
public function tryPatchMakefileUnix(TargetPackage $package, ToolchainInterface $toolchain): void public function tryPatchMakefileUnix(TargetPackage $package, ToolchainInterface $toolchain): void
{ {
if (SystemTarget::getTargetOS() !== 'Linux') { if (SystemTarget::getTargetOS() !== 'Linux') {
@@ -178,7 +190,8 @@ trait unix
// replace //lib with /lib in Makefile // replace //lib with /lib in Makefile
shell()->cd($package->getSourceDir())->exec('sed -i "s|//lib|/lib|g" Makefile'); shell()->cd($package->getSourceDir())->exec('sed -i "s|//lib|/lib|g" Makefile');
if ($toolchain instanceof ZigToolchain) { // CI escape hatch: in CI, zig-cc-built minilua segfaults
if ($toolchain instanceof ZigToolchain && getenv('CI')) {
$makefile = "{$package->getSourceDir()}/Makefile"; $makefile = "{$package->getSourceDir()}/Makefile";
FileSystem::replaceFileRegex($makefile, '/^BUILD_CC\s*=\s*zig-cc\s*$/m', 'BUILD_CC = cc'); FileSystem::replaceFileRegex($makefile, '/^BUILD_CC\s*=\s*zig-cc\s*$/m', 'BUILD_CC = cc');
} }
@@ -229,6 +242,7 @@ trait unix
#[Stage] #[Stage]
public function makeCliForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void public function makeCliForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
{ {
$start = microtime(true);
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cli')); InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cli'));
$concurrency = $builder->concurrency; $concurrency = $builder->concurrency;
$vars = $this->makeVars($installer); $vars = $this->makeVars($installer);
@@ -239,11 +253,13 @@ trait unix
$builder->deployBinary("{$package->getSourceDir()}/sapi/cli/php", BUILD_BIN_PATH . '/php'); $builder->deployBinary("{$package->getSourceDir()}/sapi/cli/php", BUILD_BIN_PATH . '/php');
$package->setOutput('Binary path for cli SAPI', BUILD_BIN_PATH . '/php'); $package->setOutput('Binary path for cli SAPI', BUILD_BIN_PATH . '/php');
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-cli'), true, $start);
} }
#[Stage] #[Stage]
public function makeCgiForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void public function makeCgiForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
{ {
$start = microtime(true);
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cgi')); InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cgi'));
$concurrency = $builder->concurrency; $concurrency = $builder->concurrency;
$vars = $this->makeVars($installer); $vars = $this->makeVars($installer);
@@ -254,11 +270,13 @@ trait unix
$builder->deployBinary("{$package->getSourceDir()}/sapi/cgi/php-cgi", BUILD_BIN_PATH . '/php-cgi'); $builder->deployBinary("{$package->getSourceDir()}/sapi/cgi/php-cgi", BUILD_BIN_PATH . '/php-cgi');
$package->setOutput('Binary path for cgi SAPI', BUILD_BIN_PATH . '/php-cgi'); $package->setOutput('Binary path for cgi SAPI', BUILD_BIN_PATH . '/php-cgi');
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-cgi'), true, $start);
} }
#[Stage] #[Stage]
public function makeFpmForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void public function makeFpmForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
{ {
$start = microtime(true);
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make fpm')); InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make fpm'));
$concurrency = $builder->concurrency; $concurrency = $builder->concurrency;
$vars = $this->makeVars($installer); $vars = $this->makeVars($installer);
@@ -269,43 +287,58 @@ trait unix
$builder->deployBinary("{$package->getSourceDir()}/sapi/fpm/php-fpm", BUILD_BIN_PATH . '/php-fpm'); $builder->deployBinary("{$package->getSourceDir()}/sapi/fpm/php-fpm", BUILD_BIN_PATH . '/php-fpm');
$package->setOutput('Binary path for fpm SAPI', BUILD_BIN_PATH . '/php-fpm'); $package->setOutput('Binary path for fpm SAPI', BUILD_BIN_PATH . '/php-fpm');
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-fpm'), true, $start);
} }
#[Stage] #[Stage]
#[PatchDescription('Patch phar extension for micro SAPI to support compressed phar')] #[PatchDescription('Patch phar extension for micro SAPI to support compressed phar')]
public function makeMicroForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void public function makeMicroForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
{ {
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make micro')); $start = microtime(true);
// apply --with-micro-fake-cli option $phar_patched = false;
$vars = $this->makeVars($installer); try {
$vars['EXTRA_CFLAGS'] .= $package->getBuildOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; if ($installer->isPackageResolved('ext-phar')) {
$makeArgs = $this->makeVarsToArgs($vars); $phar_patched = true;
// build SourcePatcher::patchMicroPhar(self::getPHPVersionID());
shell()->cd($package->getSourceDir()) }
->setEnv($vars) InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make micro'));
->exec("make -j{$builder->concurrency} {$makeArgs} micro"); // apply --with-micro-fake-cli option
$vars = $this->makeVars($installer);
$vars['EXTRA_CFLAGS'] .= $package->getBuildOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : '';
$makeArgs = $this->makeVarsToArgs($vars);
// build
shell()->cd($package->getSourceDir())
->setEnv($vars)
->exec("make -j{$builder->concurrency} {$makeArgs} micro");
$dst = BUILD_BIN_PATH . '/micro.sfx'; $dst = BUILD_BIN_PATH . '/micro.sfx';
$builder->deployBinary($package->getSourceDir() . '/sapi/micro/micro.sfx', $dst); $builder->deployBinary($package->getSourceDir() . '/sapi/micro/micro.sfx', $dst);
// patch after UPX-ed micro.sfx (Linux only) // patch after UPX-ed micro.sfx (Linux only)
if (SystemTarget::getTargetOS() === 'Linux' && $builder->getOption('with-upx-pack')) { if (SystemTarget::getTargetOS() === 'Linux' && $builder->getOption('with-upx-pack')) {
// cut binary with readelf to remove UPX extra segment // cut binary with readelf to remove UPX extra segment
[$ret, $out] = shell()->execWithResult("readelf -l {$dst} | awk '/LOAD|GNU_STACK/ {getline; print \\$1, \\$2, \\$3, \\$4, \\$6, \\$7}'"); [$ret, $out] = shell()->execWithResult("readelf -l {$dst} | awk '/LOAD|GNU_STACK/ {getline; print \\$1, \\$2, \\$3, \\$4, \\$6, \\$7}'");
$out[1] = explode(' ', $out[1]); $out[1] = explode(' ', $out[1]);
$offset = $out[1][0]; $offset = $out[1][0];
if ($ret !== 0 || !str_starts_with($offset, '0x')) { if ($ret !== 0 || !str_starts_with($offset, '0x')) {
throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output'); throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output');
}
$offset = hexdec($offset);
// remove upx extra wastes
file_put_contents($dst, substr(file_get_contents($dst), 0, $offset));
}
$package->setOutput('Binary path for micro SAPI', $dst);
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-micro'), true, $start);
} finally {
if ($phar_patched) {
SourcePatcher::unpatchMicroPhar();
} }
$offset = hexdec($offset);
// remove upx extra wastes
file_put_contents($dst, substr(file_get_contents($dst), 0, $offset));
} }
$package->setOutput('Binary path for micro SAPI', $dst);
} }
#[Stage] #[Stage]
public function makeEmbedForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void public function makeEmbedForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
{ {
$start = microtime(true);
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make embed')); InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make embed'));
$shared_exts = array_filter( $shared_exts = array_filter(
$installer->getResolvedPackages(), $installer->getResolvedPackages(),
@@ -319,22 +352,43 @@ trait unix
$root = BUILD_ROOT_PATH; $root = BUILD_ROOT_PATH;
$sed_prefix = SystemTarget::getTargetOS() === 'Darwin' ? 'sed -i ""' : 'sed -i'; $sed_prefix = SystemTarget::getTargetOS() === 'Darwin' ? 'sed -i ""' : 'sed -i';
$vars = $this->makeVars($installer);
$makeArgs = $this->makeVarsToArgs($vars);
shell()->cd($package->getSourceDir()) shell()->cd($package->getSourceDir())
->setEnv($this->makeVars($installer)) ->setEnv($vars)
->exec("{$sed_prefix} \"s|^EXTENSION_DIR = .*|EXTENSION_DIR = /" . basename(BUILD_MODULES_PATH) . '|" Makefile') ->exec("{$sed_prefix} \"s|^EXTENSION_DIR = .*|EXTENSION_DIR = /" . basename(BUILD_MODULES_PATH) . '|" Makefile')
->exec("make -j{$builder->concurrency} INSTALL_ROOT={$root} install-sapi {$install_modules} install-build install-headers install-programs"); ->exec("make -j{$builder->concurrency} {$makeArgs} INSTALL_ROOT={$root} install-sapi {$install_modules} install-build install-headers install-programs");
// install-modules deref'd libtool's `$ext.so → $ext-X.so` symlink for each built-with-php ext; restore them.
$release = null;
if (preg_match('/-release\s+(\S+)/', (string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), $m)) {
$release = $m[1];
foreach ($shared_exts as $ext) {
$name = $ext->getExtensionName();
$u = BUILD_MODULES_PATH . "/{$name}.so";
$v = BUILD_MODULES_PATH . "/{$name}-{$release}.so";
if (file_exists($v) && file_exists($u) && !is_link($u)) {
unlink($u);
symlink(basename($v), $u);
}
}
}
// ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=shared ------------- // ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=shared -------------
// process libphp.so for shared embed // INSTALL_IT for embed copies through libtool's symlink, leaving only unversioned libphp.{so,dylib} — rename and symlink back so shared exts can `-lphp`. (static libphp.a is never versioned, even with -release.)
$suffix = SystemTarget::getTargetOS() === 'Darwin' ? 'dylib' : 'so'; $suffix = SystemTarget::getTargetOS() === 'Darwin' ? 'dylib' : 'so';
$libphp_so = "{$package->getLibDir()}/libphp.{$suffix}"; $libphp_so = "{$package->getLibDir()}/libphp.{$suffix}";
if (file_exists($libphp_so)) { if (file_exists($libphp_so)) {
// rename libphp.so if -release is set if ($release !== null) {
if (SystemTarget::getTargetOS() === 'Linux') { $versioned = "{$package->getLibDir()}/libphp-{$release}.{$suffix}";
$this->processLibphpSoFile($libphp_so, $installer); if (file_exists($versioned)) {
@unlink($versioned);
}
rename($libphp_so, $versioned);
symlink(basename($versioned), $libphp_so);
$libphp_so = $versioned;
} }
// deploy
$builder->deployBinary($libphp_so, $libphp_so, false); $builder->deployBinary($libphp_so, $libphp_so, false);
$package->setOutput('Library path for embed SAPI', $libphp_so); $package->setOutput('Library path for embed SAPI', $libphp_so);
} }
@@ -343,6 +397,9 @@ trait unix
$increment_files = $diff->getChangedFiles(); $increment_files = $diff->getChangedFiles();
$files = []; $files = [];
foreach ($increment_files as $increment_file) { foreach ($increment_files as $increment_file) {
if (is_link($increment_file) || !file_exists($increment_file)) {
continue;
}
$builder->deployBinary($increment_file, $increment_file, false); $builder->deployBinary($increment_file, $increment_file, false);
$files[] = basename($increment_file); $files[] = basename($increment_file);
} }
@@ -350,6 +407,11 @@ trait unix
$package->setOutput('Built shared extensions', implode(', ', $files)); $package->setOutput('Built shared extensions', implode(', ', $files));
} }
// phpize needs prefix patched whether libphp is .a or .so
$package->runStage([$this, 'patchUnixEmbedScripts']);
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-embed'), true, $start);
// ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=static ------------- // ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=static -------------
// process libphp.a for static embed (only when present) // process libphp.a for static embed (only when present)
@@ -359,9 +421,6 @@ trait unix
shell()->exec("{$ar} -t {$libphp_a} | grep '\\.a$' | xargs -n1 {$ar} d {$libphp_a}"); shell()->exec("{$ar} -t {$libphp_a} | grep '\\.a$' | xargs -n1 {$ar} d {$libphp_a}");
UnixUtil::exportDynamicSymbols($libphp_a); UnixUtil::exportDynamicSymbols($libphp_a);
} }
// deploy embed php scripts
$package->runStage([$this, 'patchUnixEmbedScripts']);
} }
#[Stage] #[Stage]
@@ -394,8 +453,15 @@ trait unix
try { try {
logger()->debug('Building shared extensions...'); logger()->debug('Building shared extensions...');
foreach ($shared_extensions as $extension) { foreach ($shared_extensions as $extension) {
InteractiveTerm::setMessage('Building shared PHP extension: ' . ConsoleColor::yellow($extension->getName())); $ext_start = microtime(true);
$extension->buildShared(); InteractiveTerm::setMessage('Building shared extension: ' . ConsoleColor::yellow($extension->getName()));
try {
$extension->buildShared();
} catch (\Throwable $e) {
InteractiveTerm::error('Building shared extension failed: ' . ConsoleColor::red($extension->getName()));
throw $e;
}
InteractiveTerm::success('Built shared extension: ' . ConsoleColor::green($extension->getName()), true, $ext_start);
} }
} finally { } finally {
// restore php-config // restore php-config
@@ -487,6 +553,8 @@ trait unix
$package->runStage([$this, 'makeForUnix']); $package->runStage([$this, 'makeForUnix']);
} }
// shared extensions build before frankenphp so their undefined references are
// collected into libphp's exported dynamic-symbol list.
$package->runStage([$this, 'unixBuildSharedExt']); $package->runStage([$this, 'unixBuildSharedExt']);
} }
@@ -599,7 +667,7 @@ trait unix
copy(ROOT_DIR . '/src/globals/common-tests/embed.c', $sample_file_path . '/embed.c'); copy(ROOT_DIR . '/src/globals/common-tests/embed.c', $sample_file_path . '/embed.c');
copy(ROOT_DIR . '/src/globals/common-tests/embed.php', $sample_file_path . '/embed.php'); copy(ROOT_DIR . '/src/globals/common-tests/embed.php', $sample_file_path . '/embed.php');
$config = new SPCConfigUtil()->config($installer->getAvailableResolvedPackageNames()); $config = new SPCConfigUtil()->config(['php']);
$lens = "{$config['cflags']} {$config['ldflags']} {$config['libs']}"; $lens = "{$config['cflags']} {$config['ldflags']} {$config['libs']}";
if ($toolchain->isStatic()) { if ($toolchain->isStatic()) {
$lens .= ' -static'; $lens .= ' -static';
@@ -680,96 +748,37 @@ trait unix
return $php; return $php;
} }
/**
* Rename libphp.so to libphp-<release>.so if -release is set in LDFLAGS.
*/
private function processLibphpSoFile(string $libphpSo, PackageInstaller $installer): void
{
$ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: '';
$libDir = BUILD_LIB_PATH;
$modulesDir = BUILD_MODULES_PATH;
$realLibName = 'libphp.so';
$cwd = getcwd();
if (preg_match('/-release\s+(\S+)/', $ldflags, $matches)) {
$release = $matches[1];
$realLibName = "libphp-{$release}.so";
$libphpRelease = "{$libDir}/{$realLibName}";
if (!file_exists($libphpRelease) && file_exists($libphpSo)) {
rename($libphpSo, $libphpRelease);
}
if (file_exists($libphpRelease)) {
chdir($libDir);
if (file_exists($libphpSo)) {
unlink($libphpSo);
}
symlink($realLibName, 'libphp.so');
shell()->exec(sprintf(
'patchelf --set-soname %s %s',
escapeshellarg($realLibName),
escapeshellarg($libphpRelease)
));
}
if (is_dir($modulesDir)) {
chdir($modulesDir);
foreach ($installer->getResolvedPackages(PhpExtensionPackage::class) as $ext) {
if (!$ext->isBuildShared()) {
continue;
}
$name = $ext->getName();
$versioned = "{$name}-{$release}.so";
$unversioned = "{$name}.so";
$src = "{$modulesDir}/{$versioned}";
$dst = "{$modulesDir}/{$unversioned}";
if (is_file($src)) {
rename($src, $dst);
shell()->exec(sprintf(
'patchelf --set-soname %s %s',
escapeshellarg($unversioned),
escapeshellarg($dst)
));
}
}
}
chdir($cwd);
}
$target = "{$libDir}/{$realLibName}";
if (file_exists($target)) {
[, $output] = shell()->execWithResult('readelf -d ' . escapeshellarg($target));
$output = implode("\n", $output);
if (preg_match('/SONAME.*\[(.+)]/', $output, $sonameMatch)) {
$currentSoname = $sonameMatch[1];
if ($currentSoname !== basename($target)) {
shell()->exec(sprintf(
'patchelf --set-soname %s %s',
escapeshellarg(basename($target)),
escapeshellarg($target)
));
}
}
}
}
/** /**
* Make environment variables for php make. * Make environment variables for php make.
* This will call SPCConfigUtil to generate proper LDFLAGS and LIBS for static linking. * This will call SPCConfigUtil to generate proper LDFLAGS and LIBS for static linking.
*/ */
private function makeVars(PackageInstaller $installer): array private function makeVars(PackageInstaller $installer): array
{ {
$config = new SPCConfigUtil(['libs_only_deps' => true])->config($installer->getAvailableResolvedPackageNames()); $config = new SPCConfigUtil(['libs_only_deps' => true])->config(['php']);
$static = ApplicationContext::get(ToolchainInterface::class)->isStatic() ? '-all-static' : ''; $static = ApplicationContext::get(ToolchainInterface::class)->isStatic() ? '-all-static' : '';
$pie = SystemTarget::getTargetOS() === 'Linux' ? '-pie' : ''; $pie = SystemTarget::getTargetOS() === 'Linux' ? '-pie' : '';
$lib = BUILD_LIB_PATH;
// Append SPC_EXTRA_LIBS to libs for dynamic linking support (e.g., X11) // Append SPC_EXTRA_LIBS to libs for dynamic linking support (e.g., X11)
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: ''; $extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
$libs = trim($config['libs'] . ' ' . $extra_libs); $libs = trim($config['libs'] . ' ' . $extra_libs);
// libtool input (libphp.la). `make EXTRA_LDFLAGS=…` cmdline overrides fully replace the Makefile value, so re-include $config['ldflags'] for -L paths.
$extra_ldflags = clean_spaces($config['ldflags'] . ' ' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'));
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared'
&& !str_contains($extra_ldflags, '-avoid-version')
&& !preg_match('/-release\s+\S+/', $extra_ldflags)) {
$extra_ldflags = trim($extra_ldflags . ' -avoid-version -module');
}
$extra_ldflags_program_env = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM') ?: '';
$extra_ldflags_program = clean_spaces("-L{$lib} {$static} {$pie} {$extra_ldflags_program_env}");
return array_filter([ return array_filter([
'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), 'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'),
'EXTRA_CXXFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS'), 'EXTRA_CXXFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS'),
'EXTRA_LDFLAGS_PROGRAM' => deduplicate_flags(getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') . " {$config['ldflags']} {$static} {$pie}"), 'EXTRA_LDFLAGS' => $extra_ldflags,
'EXTRA_LDFLAGS' => $config['ldflags'], 'EXTRA_LDFLAGS_PROGRAM' => $extra_ldflags_program,
'EXTRA_LIBS' => $libs, 'EXTRA_LIBS' => $libs,
]); ]);
} }

View File

@@ -39,13 +39,6 @@ trait windows
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf.bat')); InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf.bat'));
cmd()->cd($package->getSourceDir())->exec('.\buildconf.bat'); cmd()->cd($package->getSourceDir())->exec('.\buildconf.bat');
// Bypass the phpsdk_version check in configure.js: we use MSVC + msys2 instead of PHP SDK, so phpsdk_version is not available and the check would always fail.
FileSystem::replaceFileStr(
"{$package->getSourceDir()}\\configure.js",
'check_binary_tools_sdk();',
'/* check_binary_tools_sdk(); skipped: using MSVC + msys2 without PHP SDK */'
);
if ($package->getBuildOption('enable-micro-win32') && $installer->isPackageResolved('php-micro')) { if ($package->getBuildOption('enable-micro-win32') && $installer->isPackageResolved('php-micro')) {
SourcePatcher::patchMicroWin32(); SourcePatcher::patchMicroWin32();
} else { } else {
@@ -95,17 +88,6 @@ trait windows
cmd()->cd($package->getSourceDir())->exec(".\\configure.bat {$args} {$static_extension_str}"); cmd()->cd($package->getSourceDir())->exec(".\\configure.bat {$args} {$static_extension_str}");
} }
#[BeforeStage('php', [self::class, 'makeCliForWindows'])]
#[PatchDescription('Patch Makefile to ensure buildroot/include comes before extension CFLAGS (fixes zip.h conflict with minizip)')]
public function patchMakefileIncludeOrder(TargetPackage $package): void
{
FileSystem::replaceFileStr(
"{$package->getSourceDir()}\\Makefile",
'$(CFLAGS_PHP_OBJ) $(CFLAGS)',
'$(CFLAGS) $(CFLAGS_PHP_OBJ)'
);
}
#[BeforeStage('php', [self::class, 'makeCliForWindows'])] #[BeforeStage('php', [self::class, 'makeCliForWindows'])]
#[PatchDescription('Patch Windows Makefile for CLI target')] #[PatchDescription('Patch Windows Makefile for CLI target')]
public function patchCLITarget(TargetPackage $package): void public function patchCLITarget(TargetPackage $package): void
@@ -530,7 +512,6 @@ HEADER;
$vc_matches = ['unknown', 'unknown']; $vc_matches = ['unknown', 'unknown'];
} else { } else {
$vc_matches = match ($vc['major_version']) { $vc_matches = match ($vc['major_version']) {
'18', // VS 2026 shares the VS2022 (v143) runtime conventions, so it reports as VS17.
'17' => ['VS17', 'Visual C++ 2022'], '17' => ['VS17', 'Visual C++ 2022'],
'16' => ['VS16', 'Visual C++ 2019'], '16' => ['VS16', 'Visual C++ 2019'],
default => ['unknown', 'unknown'], default => ['unknown', 'unknown'],

View File

@@ -2,17 +2,17 @@
declare(strict_types=1); declare(strict_types=1);
namespace Package\Tool; namespace Package\Target;
use StaticPHP\Attribute\Package\BuildFor; use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\InitPackage; use StaticPHP\Attribute\Package\InitPackage;
use StaticPHP\Attribute\Package\Tool; use StaticPHP\Attribute\Package\Target;
use StaticPHP\DI\ApplicationContext; use StaticPHP\DI\ApplicationContext;
use StaticPHP\Package\ToolPackage; use StaticPHP\Package\TargetPackage;
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor; use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
use StaticPHP\Toolchain\Interface\ToolchainInterface; use StaticPHP\Toolchain\Interface\ToolchainInterface;
#[Tool('pkg-config')] #[Target('pkg-config')]
class pkgconfig class pkgconfig
{ {
#[InitPackage] #[InitPackage]
@@ -23,7 +23,7 @@ class pkgconfig
#[BuildFor('Linux')] #[BuildFor('Linux')]
#[BuildFor('Darwin')] #[BuildFor('Darwin')]
public function build(ToolPackage $package, ToolchainInterface $toolchain): void public function build(TargetPackage $package, ToolchainInterface $toolchain): void
{ {
UnixAutoconfExecutor::create($package) UnixAutoconfExecutor::create($package)
->appendEnv([ ->appendEnv([

View File

@@ -27,12 +27,18 @@ class Artifact
/** @var null|callable Bind custom source fetcher callback */ /** @var null|callable Bind custom source fetcher callback */
protected mixed $custom_source_callback = null; protected mixed $custom_source_callback = null;
/** @var null|string Display label describing where the custom source callback came from */
protected ?string $custom_source_callback_origin = null;
/** @var null|callable Bind custom source check-update callback */ /** @var null|callable Bind custom source check-update callback */
protected mixed $custom_source_check_update_callback = null; protected mixed $custom_source_check_update_callback = null;
/** @var array<string, callable> Bind custom binary fetcher callbacks */ /** @var array<string, callable> Bind custom binary fetcher callbacks */
protected mixed $custom_binary_callbacks = []; protected mixed $custom_binary_callbacks = [];
/** @var array<string, string> Display label per platform describing where the custom binary callback came from */
protected array $custom_binary_callback_origins = [];
/** @var array<string, callable> Bind custom binary check-update callbacks */ /** @var array<string, callable> Bind custom binary check-update callbacks */
protected array $custom_binary_check_update_callbacks = []; protected array $custom_binary_check_update_callbacks = [];
@@ -285,15 +291,19 @@ class Artifact
* Get source extraction directory. * Get source extraction directory.
* *
* Rules: * Rules:
* 1. If extract is not specified: SOURCE_PATH/{artifact_name} * 1. If cache_type is 'local': use the absolute dirname recorded at download time (no symlink/copy).
* 2. If extract is relative path: SOURCE_PATH/{value} * 2. If extract is not specified: SOURCE_PATH/{artifact_name}
* 3. If extract is absolute path: {value} * 3. If extract is relative path: SOURCE_PATH/{value}
* 4. If extract is array (dict): handled by extractor (selective extraction) * 4. If extract is absolute path: {value}
* 5. If extract is array (dict): handled by extractor (selective extraction)
*/ */
public function getSourceDir(): string public function getSourceDir(): string
{ {
// Prefer cache extract path, fall back to config // Prefer cache extract path, fall back to config
$cache_info = ApplicationContext::get(ArtifactCache::class)->getSourceInfo($this->name); $cache_info = ApplicationContext::get(ArtifactCache::class)->getSourceInfo($this->name);
if (($cache_info['cache_type'] ?? null) === 'local' && isset($cache_info['dirname'])) {
return FileSystem::convertPath($cache_info['dirname']);
}
$extract = is_string($cache_info['extract'] ?? null) $extract = is_string($cache_info['extract'] ?? null)
? $cache_info['extract'] ? $cache_info['extract']
: ($this->config['source']['extract'] ?? null); : ($this->config['source']['extract'] ?? null);
@@ -407,10 +417,13 @@ class Artifact
/** /**
* Set custom source fetcher callback. * Set custom source fetcher callback.
*
* @param string $origin Short label shown in progress output (e.g. 'package downloader', 'custom url')
*/ */
public function setCustomSourceCallback(callable $callback): void public function setCustomSourceCallback(callable $callback, string $origin = 'package downloader'): void
{ {
$this->custom_source_callback = $callback; $this->custom_source_callback = $callback;
$this->custom_source_callback_origin = $origin;
} }
public function getCustomSourceCallback(): ?callable public function getCustomSourceCallback(): ?callable
@@ -418,6 +431,11 @@ class Artifact
return $this->custom_source_callback ?? null; return $this->custom_source_callback ?? null;
} }
public function getCustomSourceCallbackOrigin(): ?string
{
return $this->custom_source_callback_origin;
}
/** /**
* Set custom source check-update callback. * Set custom source check-update callback.
*/ */
@@ -452,11 +470,19 @@ class Artifact
* *
* @param string $target_os Target OS platform string (e.g. linux-x86_64) * @param string $target_os Target OS platform string (e.g. linux-x86_64)
* @param callable $callback Custom binary fetcher callback * @param callable $callback Custom binary fetcher callback
* @param string $origin Short label shown in progress output (e.g. 'package downloader')
*/ */
public function setCustomBinaryCallback(string $target_os, callable $callback): void public function setCustomBinaryCallback(string $target_os, callable $callback, string $origin = 'package downloader'): void
{ {
ConfigValidator::validatePlatformString($target_os); ConfigValidator::validatePlatformString($target_os);
$this->custom_binary_callbacks[$target_os] = $callback; $this->custom_binary_callbacks[$target_os] = $callback;
$this->custom_binary_callback_origins[$target_os] = $origin;
}
public function getCustomBinaryCallbackOrigin(): ?string
{
$current_platform = SystemTarget::getCurrentPlatformString();
return $this->custom_binary_callback_origins[$current_platform] ?? null;
} }
/** /**
@@ -644,7 +670,7 @@ class Artifact
'{artifact_name}' => $this->name, '{artifact_name}' => $this->name,
'{pkg_root_path}' => PKG_ROOT_PATH, '{pkg_root_path}' => PKG_ROOT_PATH,
'{build_root_path}' => BUILD_ROOT_PATH, '{build_root_path}' => BUILD_ROOT_PATH,
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH'), '{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: WORKING_DIR . '/php-sdk-binary-tools',
'{working_dir}' => WORKING_DIR, '{working_dir}' => WORKING_DIR,
'{download_path}' => DOWNLOAD_PATH, '{download_path}' => DOWNLOAD_PATH,
'{source_path}' => SOURCE_PATH, '{source_path}' => SOURCE_PATH,

View File

@@ -26,7 +26,6 @@ use StaticPHP\Exception\DownloaderException;
use StaticPHP\Exception\ExecutionException; use StaticPHP\Exception\ExecutionException;
use StaticPHP\Exception\ValidationException; use StaticPHP\Exception\ValidationException;
use StaticPHP\Exception\WrongUsageException; use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Package\ToolVersionRegistry;
use StaticPHP\Registry\ArtifactLoader; use StaticPHP\Registry\ArtifactLoader;
use StaticPHP\Runtime\Shell\Shell; use StaticPHP\Runtime\Shell\Shell;
use StaticPHP\Runtime\SystemTarget; use StaticPHP\Runtime\SystemTarget;
@@ -318,7 +317,10 @@ class ArtifactDownloader
if (!is_dir(DOWNLOAD_PATH)) { if (!is_dir(DOWNLOAD_PATH)) {
FileSystem::createDir(DOWNLOAD_PATH); FileSystem::createDir(DOWNLOAD_PATH);
} }
logger()->info('Downloading' . implode(', ', array_map(fn ($x) => " '{$x->getName()}'", $this->artifacts)) . " with concurrency {$this->parallel} ..."); $pending = array_values(array_filter($this->artifacts, fn ($a) => $this->generateQueue($a) !== []));
if ($pending !== []) {
logger()->info('Downloading' . implode(', ', array_map(fn ($x) => " '{$x->getName()}'", $pending)) . " with concurrency {$this->parallel} ...");
}
// Download artifacts parallelly // Download artifacts parallelly
if ($this->parallel > 1) { if ($this->parallel > 1) {
$this->downloadWithConcurrency(); $this->downloadWithConcurrency();
@@ -347,32 +349,12 @@ class ArtifactDownloader
} }
} }
public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false, bool $use_installed = false): CheckUpdateResult public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false): CheckUpdateResult
{ {
$artifact = ArtifactLoader::getArtifactInstance($artifact_name); $artifact = ArtifactLoader::getArtifactInstance($artifact_name);
if ($artifact === null) { if ($artifact === null) {
throw new WrongUsageException("Artifact '{$artifact_name}' not found, please check the name."); throw new WrongUsageException("Artifact '{$artifact_name}' not found, please check the name.");
} }
// --installed: prefer the version actually installed on disk (tool packages only) as the
// baseline, instead of the download cache. Falls back to the normal cache/bare logic below
// if this artifact has no recorded installed version (not a tool package, or not installed
// via the package installer yet).
if ($use_installed) {
$installed_version = ToolVersionRegistry::get($artifact_name);
if ($installed_version !== null) {
[$first, $second] = $prefer_source
? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name, $installed_version), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name, $installed_version)]
: [fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name, $installed_version), fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name, $installed_version)];
$result = $first() ?? $second();
if ($result !== null) {
return $result;
}
return new CheckUpdateResult(old: $installed_version, new: null, needUpdate: false, unsupported: true);
}
logger()->warning("Artifact '{$artifact_name}' has no recorded installed version (not a tool package, or not installed yet); falling back to download-cache based check.");
}
if ($bare) { if ($bare) {
[$first, $second] = $prefer_source [$first, $second] = $prefer_source
? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name)] ? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name)]
@@ -424,17 +406,16 @@ class ArtifactDownloader
* @param bool $prefer_source Whether to prefer source over binary * @param bool $prefer_source Whether to prefer source over binary
* @param bool $bare Check without requiring artifact to be downloaded first * @param bool $bare Check without requiring artifact to be downloaded first
* @param null|callable $onResult Called immediately with (string $name, CheckUpdateResult) as each result arrives * @param null|callable $onResult Called immediately with (string $name, CheckUpdateResult) as each result arrives
* @param bool $use_installed Prefer the installed (on-disk) version over the download cache (tool packages only)
* @return array<string, CheckUpdateResult> Results keyed by artifact name * @return array<string, CheckUpdateResult> Results keyed by artifact name
*/ */
public function checkUpdates(array $artifact_names, bool $prefer_source = false, bool $bare = false, ?callable $onResult = null, bool $use_installed = false): array public function checkUpdates(array $artifact_names, bool $prefer_source = false, bool $bare = false, ?callable $onResult = null): array
{ {
if ($this->parallel > 1 && count($artifact_names) > 1) { if ($this->parallel > 1 && count($artifact_names) > 1) {
return $this->checkUpdatesWithConcurrency($artifact_names, $prefer_source, $bare, $onResult, $use_installed); return $this->checkUpdatesWithConcurrency($artifact_names, $prefer_source, $bare, $onResult);
} }
$results = []; $results = [];
foreach ($artifact_names as $name) { foreach ($artifact_names as $name) {
$result = $this->checkUpdate($name, $prefer_source, $bare, $use_installed); $result = $this->checkUpdate($name, $prefer_source, $bare);
$results[$name] = $result; $results[$name] = $result;
if ($onResult !== null) { if ($onResult !== null) {
($onResult)($name, $result); ($onResult)($name, $result);
@@ -458,7 +439,7 @@ class ArtifactDownloader
return $this->options[$name] ?? $default; return $this->options[$name] ?? $default;
} }
private function checkUpdatesWithConcurrency(array $artifact_names, bool $prefer_source, bool $bare, ?callable $onResult, bool $use_installed = false): array private function checkUpdatesWithConcurrency(array $artifact_names, bool $prefer_source, bool $bare, ?callable $onResult): array
{ {
$results = []; $results = [];
$fiber_pool = []; $fiber_pool = [];
@@ -473,8 +454,8 @@ class ArtifactDownloader
// fill pool // fill pool
while (count($fiber_pool) < $this->parallel && !empty($remaining)) { while (count($fiber_pool) < $this->parallel && !empty($remaining)) {
$name = array_shift($remaining); $name = array_shift($remaining);
$fiber = new \Fiber(function () use ($name, $prefer_source, $bare, $use_installed) { $fiber = new \Fiber(function () use ($name, $prefer_source, $bare) {
return [$name, $this->checkUpdate($name, $prefer_source, $bare, $use_installed)]; return [$name, $this->checkUpdate($name, $prefer_source, $bare)];
}); });
$fiber->start(); $fiber->start();
$fiber_pool[$name] = $fiber; $fiber_pool[$name] = $fiber;
@@ -513,12 +494,12 @@ class ArtifactDownloader
return $results; return $results;
} }
private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name, ?string $old_version = null): ?CheckUpdateResult private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
{ {
if (($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) { if (($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [ return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this, ArtifactDownloader::class => $this,
'old_version' => $old_version, 'old_version' => null,
]); ]);
} }
$config = $artifact->getDownloadConfig('source'); $config = $artifact->getDownloadConfig('source');
@@ -531,16 +512,16 @@ class ArtifactDownloader
} }
/** @var CheckUpdateInterface $dl */ /** @var CheckUpdateInterface $dl */
$dl = new $cls(); $dl = new $cls();
return $dl->checkUpdate($artifact_name, $config, $old_version, $this); return $dl->checkUpdate($artifact_name, $config, null, $this);
} }
private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_name, ?string $old_version = null): ?CheckUpdateResult private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
{ {
// custom binary callback takes precedence over config-based binary // custom binary callback takes precedence over config-based binary
if (($callback = $artifact->getCustomBinaryCheckUpdateCallback()) !== null) { if (($callback = $artifact->getCustomBinaryCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [ return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this, ArtifactDownloader::class => $this,
'old_version' => $old_version, 'old_version' => null,
]); ]);
} }
$binary_config = $artifact->getDownloadConfig('binary'); $binary_config = $artifact->getDownloadConfig('binary');
@@ -554,7 +535,7 @@ class ArtifactDownloader
} }
/** @var CheckUpdateInterface $dl */ /** @var CheckUpdateInterface $dl */
$dl = new $cls(); $dl = new $cls();
return $dl->checkUpdate($artifact_name, $platform_config, $old_version, $this); return $dl->checkUpdate($artifact_name, $platform_config, null, $this);
} }
private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false): int private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false): int
@@ -573,8 +554,8 @@ class ArtifactDownloader
$instance = null; $instance = null;
$call = $this->downloaders[$item['config']['type']] ?? null; $call = $this->downloaders[$item['config']['type']] ?? null;
$type_display_name = match (true) { $type_display_name = match (true) {
$item['lock'] === 'source' && ($callback = $artifact->getCustomSourceCallback()) !== null => 'user defined source downloader', $item['lock'] === 'source' && $artifact->getCustomSourceCallback() !== null => $artifact->getCustomSourceCallbackOrigin() ?? 'source package downloader',
$item['lock'] === 'binary' && ($callback = $artifact->getCustomBinaryCallback()) !== null => 'user defined binary downloader', $item['lock'] === 'binary' && $artifact->getCustomBinaryCallback() !== null => $artifact->getCustomBinaryCallbackOrigin() ?? 'binary package downloader',
default => SPC_DOWNLOAD_TYPE_DISPLAY_NAME[$item['config']['type']] ?? $item['config']['type'], default => SPC_DOWNLOAD_TYPE_DISPLAY_NAME[$item['config']['type']] ?? $item['config']['type'],
}; };
$try_h = $try ? 'Try downloading' : 'Downloading'; $try_h = $try ? 'Try downloading' : 'Downloading';
@@ -753,6 +734,16 @@ class ArtifactDownloader
$binary_downloaded = $artifact->isBinaryDownloaded(compare_hash: true); $binary_downloaded = $artifact->isBinaryDownloaded(compare_hash: true);
$source_downloaded = $artifact->isSourceDownloaded(compare_hash: true); $source_downloaded = $artifact->isSourceDownloaded(compare_hash: true);
if ($source_downloaded && $artifact->getName() === 'php-src' && ($requested = $this->getOption('with-php'))) {
$info = ApplicationContext::get(ArtifactCache::class)->getSourceInfo('php-src');
$cv = $info['version'] ?? null;
$ct = $info['cache_type'] ?? null;
$matches = $requested === 'git' ? $ct === 'git' : ($cv !== null && $ct !== 'git' && ($cv === $requested || str_starts_with($cv, $requested . '.')));
if (!$matches) {
$source_downloaded = false;
}
}
$item_source = ['display' => 'source', 'lock' => 'source', 'config' => $artifact->getDownloadConfig('source')]; $item_source = ['display' => 'source', 'lock' => 'source', 'config' => $artifact->getDownloadConfig('source')];
$item_source_mirror = ['display' => 'source (mirror)', 'lock' => 'source', 'config' => $artifact->getDownloadConfig('source-mirror')]; $item_source_mirror = ['display' => 'source (mirror)', 'lock' => 'source', 'config' => $artifact->getDownloadConfig('source-mirror')];
@@ -847,21 +838,21 @@ class ArtifactDownloader
if (isset($this->artifacts[$artifact_name])) { if (isset($this->artifacts[$artifact_name])) {
$this->artifacts[$artifact_name]->setCustomSourceCallback(function (ArtifactDownloader $downloader) use ($artifact_name, $custom_url) { $this->artifacts[$artifact_name]->setCustomSourceCallback(function (ArtifactDownloader $downloader) use ($artifact_name, $custom_url) {
return (new Url())->download($artifact_name, ['url' => $custom_url], $downloader); return (new Url())->download($artifact_name, ['url' => $custom_url], $downloader);
}); }, 'custom url');
} }
} }
foreach ($this->custom_gits as $artifact_name => [$branch, $git_url]) { foreach ($this->custom_gits as $artifact_name => [$branch, $git_url]) {
if (isset($this->artifacts[$artifact_name])) { if (isset($this->artifacts[$artifact_name])) {
$this->artifacts[$artifact_name]->setCustomSourceCallback(function (ArtifactDownloader $downloader) use ($artifact_name, $branch, $git_url) { $this->artifacts[$artifact_name]->setCustomSourceCallback(function (ArtifactDownloader $downloader) use ($artifact_name, $branch, $git_url) {
return (new Git())->download($artifact_name, ['rev' => $branch, 'url' => $git_url], $downloader); return (new Git())->download($artifact_name, ['rev' => $branch, 'url' => $git_url], $downloader);
}); }, 'custom git');
} }
} }
foreach ($this->custom_locals as $artifact_name => $local_path) { foreach ($this->custom_locals as $artifact_name => $local_path) {
if (isset($this->artifacts[$artifact_name])) { if (isset($this->artifacts[$artifact_name])) {
$this->artifacts[$artifact_name]->setCustomSourceCallback(function (ArtifactDownloader $downloader) use ($artifact_name, $local_path) { $this->artifacts[$artifact_name]->setCustomSourceCallback(function (ArtifactDownloader $downloader) use ($artifact_name, $local_path) {
return (new LocalDir())->download($artifact_name, ['dirname' => $local_path], $downloader); return (new LocalDir())->download($artifact_name, ['dirname' => $local_path], $downloader);
}); }, 'custom local dir');
} }
} }
} }

View File

@@ -136,6 +136,12 @@ class ArtifactExtractor
throw new WrongUsageException("Artifact source [{$name}] not downloaded, please download it first!"); throw new WrongUsageException("Artifact source [{$name}] not downloaded, please download it first!");
} }
// Local (--custom-local): source lives in place at $cache_info['dirname'].
if (($cache_info['cache_type'] ?? null) === 'local') {
$artifact->emitAfterSourceExtract($artifact->getSourceDir());
return SPC_STATUS_ALREADY_EXTRACTED;
}
$source_file = $this->cache->getCacheFullPath($cache_info); $source_file = $this->cache->getCacheFullPath($cache_info);
$target_path = $artifact->getSourceDir(); $target_path = $artifact->getSourceDir();
@@ -171,8 +177,12 @@ class ArtifactExtractor
return SPC_STATUS_ALREADY_EXTRACTED; return SPC_STATUS_ALREADY_EXTRACTED;
} }
// Remove old directory if hash mismatch // Remove old directory if hash mismatch.
if (is_dir($target_path)) { // Guard: a symlink at $target_path (left over from older local-source handling) must be
// unlinked directly — never recurse into the link target, that would wipe the user's tree.
if (is_link($target_path)) {
@unlink($target_path);
} elseif (is_dir($target_path)) {
logger()->notice("Source [{$name}] hash mismatch, re-extracting..."); logger()->notice("Source [{$name}] hash mismatch, re-extracting...");
FileSystem::removeDir($target_path); FileSystem::removeDir($target_path);
} }
@@ -614,7 +624,7 @@ class ArtifactExtractor
'{source_path}' => SOURCE_PATH, '{source_path}' => SOURCE_PATH,
'{download_path}' => DOWNLOAD_PATH, '{download_path}' => DOWNLOAD_PATH,
'{working_dir}' => WORKING_DIR, '{working_dir}' => WORKING_DIR,
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH') ?: '', '{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: '',
]; ];
return str_replace(array_keys($replacement), array_values($replacement), $path); return str_replace(array_keys($replacement), array_values($replacement), $path);
} }

View File

@@ -76,10 +76,9 @@ class DownloadResult
?string $version = null, ?string $version = null,
array $metadata = [], array $metadata = [],
?string $downloader = null, ?string $downloader = null,
mixed $extract = null,
): DownloadResult { ): DownloadResult {
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file'; $cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
return new self($cache_type, config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader); return new self($cache_type, config: $config, filename: $filename, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
} }
/** /**

View File

@@ -45,11 +45,7 @@ class FileList implements DownloadTypeInterface, CheckUpdateInterface
throw new DownloaderException("Failed to get {$name} file list from {$config['url']}"); throw new DownloaderException("Failed to get {$name} file list from {$config['url']}");
} }
$versions = []; $versions = [];
$cnt = count($matches['version']); logger()->debug('Matched ' . count($matches['version']) . " versions for {$name}");
if ($cnt === 0) {
throw new DownloaderException("Failed to get {$name} file list from {$config['url']}: no version parsed");
}
logger()->debug("Matched {$cnt} versions for {$name}");
foreach ($matches['version'] as $i => $version) { foreach ($matches['version'] as $i => $version) {
$lower = strtolower($version); $lower = strtolower($version);
foreach (['alpha', 'beta', 'rc', 'pre', 'nightly', 'snapshot', 'dev'] as $beta) { foreach (['alpha', 'beta', 'rc', 'pre', 'nightly', 'snapshot', 'dev'] as $beta) {

View File

@@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Attribute\Package;
/**
* Indicates that the annotated class defines a tool package.
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
readonly class Tool
{
public function __construct(public string $name) {}
}

View File

@@ -54,10 +54,6 @@ abstract class BaseCommand extends Command
} }
set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) { set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) {
// Respect the @ suppression operator (error_reporting() returns 0 when @ is used)
if (error_reporting() === 0) {
return true;
}
$tips = [ $tips = [
E_WARNING => ['PHP Warning: ', 'warning'], E_WARNING => ['PHP Warning: ', 'warning'],
E_NOTICE => ['PHP Notice: ', 'notice'], E_NOTICE => ['PHP Notice: ', 'notice'],

View File

@@ -23,7 +23,6 @@ class CheckUpdateCommand extends BaseCommand
$this->addArgument('artifact', InputArgument::OPTIONAL, 'The name of the artifact(s) to check for updates, comma-separated (default: all downloaded artifacts)'); $this->addArgument('artifact', InputArgument::OPTIONAL, 'The name of the artifact(s) to check for updates, comma-separated (default: all downloaded artifacts)');
$this->addOption('json', null, null, 'Output result in JSON format'); $this->addOption('json', null, null, 'Output result in JSON format');
$this->addOption('bare', null, null, 'Check update without requiring the artifact to be downloaded first (old version will be null)'); $this->addOption('bare', null, null, 'Check update without requiring the artifact to be downloaded first (old version will be null)');
$this->addOption('installed', null, null, 'Compare against the version actually installed on disk (tool packages only) instead of the download cache; falls back to normal behavior for artifacts with no recorded installed version');
$this->addOption('parallel', 'p', InputOption::VALUE_REQUIRED, 'Number of parallel update checks (default: 10)', 10); $this->addOption('parallel', 'p', InputOption::VALUE_REQUIRED, 'Number of parallel update checks (default: 10)', 10);
// --with-php option for checking updates with a specific PHP version context // --with-php option for checking updates with a specific PHP version context
@@ -46,9 +45,8 @@ class CheckUpdateCommand extends BaseCommand
try { try {
$downloader = new ArtifactDownloader($this->input->getOptions()); $downloader = new ArtifactDownloader($this->input->getOptions());
$bare = (bool) $this->getOption('bare'); $bare = (bool) $this->getOption('bare');
$use_installed = (bool) $this->getOption('installed');
if ($this->getOption('json')) { if ($this->getOption('json')) {
$results = $downloader->checkUpdates($artifacts, bare: $bare, use_installed: $use_installed); $results = $downloader->checkUpdates($artifacts, bare: $bare);
$outputs = []; $outputs = [];
foreach ($results as $artifact => $result) { foreach ($results as $artifact => $result) {
$outputs[$artifact] = [ $outputs[$artifact] = [
@@ -61,7 +59,7 @@ class CheckUpdateCommand extends BaseCommand
$this->output->writeln(json_encode($outputs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); $this->output->writeln(json_encode($outputs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
return static::OK; return static::OK;
} }
$downloader->checkUpdates($artifacts, bare: $bare, use_installed: $use_installed, onResult: function (string $artifact, CheckUpdateResult $result) { $downloader->checkUpdates($artifacts, bare: $bare, onResult: function (string $artifact, CheckUpdateResult $result) {
if ($result->unsupported) { if ($result->unsupported) {
$this->output->writeln("Artifact <info>{$artifact}</info> does not support update checking, <comment>skipped</comment>"); $this->output->writeln("Artifact <info>{$artifact}</info> does not support update checking, <comment>skipped</comment>");
} elseif (!$result->needUpdate) { } elseif (!$result->needUpdate) {

View File

@@ -9,7 +9,12 @@ use StaticPHP\Doctor\Doctor;
use StaticPHP\Exception\ValidationException; use StaticPHP\Exception\ValidationException;
use StaticPHP\Package\PackageBuilder; use StaticPHP\Package\PackageBuilder;
use StaticPHP\Package\PackageInstaller; use StaticPHP\Package\PackageInstaller;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Toolchain\ToolchainManager;
use StaticPHP\Util\DependencyResolver;
use StaticPHP\Util\FileSystem; use StaticPHP\Util\FileSystem;
use StaticPHP\Util\GlobalEnvManager;
use StaticPHP\Util\Pgo\PgoContext;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Exception\ParseException;
@@ -21,6 +26,8 @@ class CraftCommand extends BaseCommand
public function configure(): void public function configure(): void
{ {
$this->addArgument('craft', null, 'Path to craft.yml file', WORKING_DIR . '/craft.yml'); $this->addArgument('craft', null, 'Path to craft.yml file', WORKING_DIR . '/craft.yml');
$this->addOption('libs-only', null, null, 'Build only the libraries needed by the configured extensions (skip PHP and SAPI build).');
PgoContext::registerOptions($this);
} }
public function handle(): int public function handle(): int
@@ -36,18 +43,16 @@ class CraftCommand extends BaseCommand
// set verbosity // set verbosity
$this->output->setVerbosity($craft['verbosity']); $this->output->setVerbosity($craft['verbosity']);
// sync logger level and ApplicationContext debug mode to match the new verbosity
$level = match ($this->output->getVerbosity()) {
OutputInterface::VERBOSITY_VERBOSE => 'info',
OutputInterface::VERBOSITY_VERY_VERBOSE, OutputInterface::VERBOSITY_DEBUG => 'debug',
default => 'warning',
};
logger()->setLevel($level);
ApplicationContext::setDebug($this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG);
// apply env // apply env
array_walk($craft['extra-env'], fn ($v, $k) => f_putenv("{$k}={$v}")); array_walk($craft['extra-env'], fn ($v, $k) => f_putenv("{$k}={$v}"));
// re-substitute env.ini's CC=${SPC_DEFAULT_CC} bindings.
ToolchainManager::initToolchain();
GlobalEnvManager::reapplyOsIni();
// stash craft for doctor checks that depend on what's being built (e.g. frankenphp → go-xcaddy)
ApplicationContext::set('craft', $craft);
// run doctor // run doctor
if ($craft['craft-options']['doctor']) { if ($craft['craft-options']['doctor']) {
$doctor = new Doctor($this->output, FIX_POLICY_AUTOFIX); $doctor = new Doctor($this->output, FIX_POLICY_AUTOFIX);
@@ -92,23 +97,67 @@ class CraftCommand extends BaseCommand
FileSystem::resetDir(SOURCE_PATH); FileSystem::resetDir(SOURCE_PATH);
} }
$pgo = $this->getOption('libs-only') ? null : PgoContext::tryFromInput($this->input, $craft['sapi'], $build_options);
$starttime = microtime(true); $starttime = microtime(true);
// run installer // run installer
$installer = new PackageInstaller($build_options); $installer = new PackageInstaller($build_options);
ApplicationContext::get(PackageBuilder::class)->setArgument('extensions', implode(',', $craft['extensions'])); ApplicationContext::get(PackageBuilder::class)->setArgument('extensions', implode(',', $craft['extensions']));
$installer->addBuildPackage('php');
if ($this->getOption('libs-only')) {
$with_suggests = (bool) ($craft['build-options']['with-suggests'] ?? false);
$libs = $this->resolveLibsForExtensions($craft, $with_suggests);
if ($libs === []) {
$this->output->writeln('<comment>No libraries needed for the configured extensions; nothing to do.</comment>');
return static::SUCCESS;
}
foreach ($libs as $lib) {
$installer->addBuildPackage($lib);
}
} else {
$installer->addBuildPackage('php');
}
$installer->run(true); $installer->run(true);
$usedtime = round(microtime(true) - $starttime, 1); $usedtime = round(microtime(true) - $starttime, 1);
$tag = $pgo !== null ? " (PGO {$pgo->mode})" : '';
$this->output->writeln("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); $this->output->writeln("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
$this->output->writeln("<info>✔ BUILD SUCCESSFUL ({$usedtime} s)</info>"); $this->output->writeln("<info>✔ BUILD SUCCESSFUL{$tag} ({$usedtime} s)</info>");
$this->output->writeln("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"); $this->output->writeln("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
if ($pgo !== null && $pgo->isInstrument()) {
$this->output->writeln("<comment>Next: exercise the instrumented binary, then re-run craft with --pgo to consume {$pgo->profileRoot}.</comment>");
}
$installer->printBuildPackageOutputs(); $installer->printBuildPackageOutputs();
return static::SUCCESS; return static::SUCCESS;
} }
/** @return list<string> library package names transitively required by the configured extensions */
private function resolveLibsForExtensions(array $craft, bool $include_suggests): array
{
$exts = array_merge($craft['extensions'], $craft['shared-extensions'] ?? []);
$ext_pkgs = array_map(fn ($x) => "ext-{$x}", $exts);
$extra = $craft['packages'] ?? [];
$resolved = DependencyResolver::resolve(
array_merge($ext_pkgs, $extra),
include_suggests: $include_suggests,
);
$libs = [];
foreach ($resolved as $pkg_name) {
if (str_starts_with($pkg_name, 'ext-') || !PackageLoader::hasPackage($pkg_name)) {
continue;
}
if (PackageLoader::getPackage($pkg_name)->getType() === 'library') {
$libs[] = $pkg_name;
}
}
return $libs;
}
/** /**
* Validate and parse craft.yml file to array. * Validate and parse craft.yml file to array.
* *
@@ -119,7 +168,7 @@ class CraftCommand extends BaseCommand
* shared-extensions: array<string>, * shared-extensions: array<string>,
* packages: array<string>, * packages: array<string>,
* sapi: array<string>, * sapi: array<string>,
* verbosity: 128|16|256|32|64|8, * verbosity: int,
* debug: bool, * debug: bool,
* clean-build: bool, * clean-build: bool,
* build-options: array<string, mixed>, * build-options: array<string, mixed>,
@@ -180,16 +229,11 @@ class CraftCommand extends BaseCommand
} }
// verbosity // verbosity
$verbosity_level = $craft['verbosity'] ?? OutputInterface::VERBOSITY_NORMAL;
$debug = $craft['debug'] ?? false; $debug = $craft['debug'] ?? false;
$verbosity_level = $debug if ($debug) {
? OutputInterface::VERBOSITY_DEBUG $verbosity_level = OutputInterface::VERBOSITY_DEBUG;
: match ((int) ($craft['verbosity'] ?? 0)) { }
OutputInterface::VERBOSITY_QUIET => OutputInterface::VERBOSITY_QUIET,
OutputInterface::VERBOSITY_VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
OutputInterface::VERBOSITY_VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
OutputInterface::VERBOSITY_DEBUG => OutputInterface::VERBOSITY_DEBUG,
default => OutputInterface::VERBOSITY_NORMAL,
};
$craft['verbosity'] = $verbosity_level; $craft['verbosity'] = $verbosity_level;
// clean-build (if true, reset before all builds) // clean-build (if true, reset before all builds)

View File

@@ -54,8 +54,8 @@ class DumpCapabilitiesCommand extends BaseCommand
{ {
$result = []; $result = [];
// library / target / virtual-target / tool // library / target / virtual-target
foreach (PackageLoader::getPackages(['library', 'target', 'virtual-target', 'tool']) as $name => $pkg) { foreach (PackageLoader::getPackages(['library', 'target', 'virtual-target']) as $name => $pkg) {
$installable = []; $installable = [];
$artifact = $pkg->getArtifact(); $artifact = $pkg->getArtifact();
if ($artifact !== null) { if ($artifact !== null) {

View File

@@ -16,7 +16,7 @@ class GenExtTestMatrixCommand extends BaseCommand
private const array OS_RUNNERS = [ private const array OS_RUNNERS = [
'linux' => ['arch' => 'x86_64', 'runner' => 'ubuntu-latest', 'os_key' => 'Linux'], 'linux' => ['arch' => 'x86_64', 'runner' => 'ubuntu-latest', 'os_key' => 'Linux'],
'windows' => ['arch' => 'x86_64', 'runner' => 'windows-2025', 'os_key' => 'Windows'], 'windows' => ['arch' => 'x86_64', 'runner' => 'windows-latest', 'os_key' => 'Windows'],
'macos' => ['arch' => 'aarch64', 'runner' => 'macos-15', 'os_key' => 'Darwin'], 'macos' => ['arch' => 'aarch64', 'runner' => 'macos-15', 'os_key' => 'Darwin'],
]; ];
@@ -60,8 +60,6 @@ class GenExtTestMatrixCommand extends BaseCommand
'glfw', 'glfw',
'imagick', 'imagick',
'intl', 'intl',
'mongodb',
'gmssl',
]; ];
/** /**

View File

@@ -154,7 +154,7 @@ class TestBotCommand extends BaseCommand
'targets' => array_values($targets), 'targets' => array_values($targets),
'gen_matrix_args' => $gen_matrix_args, 'gen_matrix_args' => $gen_matrix_args,
'gen_matrix_args_tier2' => $gen_matrix_args_tier2, 'gen_matrix_args_tier2' => $gen_matrix_args_tier2,
'php_versions' => $php_versions, 'php_versions' => array_values($php_versions),
'tier2' => $tier2, 'tier2' => $tier2,
'comment_body' => $comment_body, 'comment_body' => $comment_body,
]; ];
@@ -253,13 +253,6 @@ class TestBotCommand extends BaseCommand
$fmt($targets), $fmt($targets),
); );
$available_labels = implode(', ', [
'`need-test` (gate)',
'`test/linux` `test/windows` `test/macos` (platform)',
'`test/tier2` (extra arch)',
'`test/php-83` `test/php-84` (PHP version)',
]);
// Case 1: need-test absent → invite the author to add it // Case 1: need-test absent → invite the author to add it
if (!$need_test) { if (!$need_test) {
return implode("\n", [ return implode("\n", [
@@ -268,9 +261,11 @@ class TestBotCommand extends BaseCommand
'', '',
$detected, $detected,
'', '',
'To trigger extension build tests on this PR, add the `need-test` label.', 'To trigger extension build tests on this PR, add the `need-test` label:',
'', '',
'**Available labels**: ' . $available_labels, '**Gate**: `need-test`',
'**Platform filter** (optional, default all): `test/linux` `test/windows` `test/macos` · `test/tier2`',
'**PHP version** (optional, default 8.5): `test/php-83` `test/php-84`',
]); ]);
} }
@@ -312,7 +307,6 @@ class TestBotCommand extends BaseCommand
'', '',
$detected, $detected,
'**Active labels**: ' . $labels_str, '**Active labels**: ' . $labels_str,
'**Available labels**: ' . $available_labels,
'**Config**: ' . implode(' + ', $platform_parts) . ' | ' . $php_str, '**Config**: ' . implode(' + ', $platform_parts) . ' | ' . $php_str,
]); ]);
} }

View File

@@ -13,7 +13,7 @@ class DoctorCommand extends BaseCommand
{ {
public function configure(): void public function configure(): void
{ {
$this->addOption('auto-fix', 'y', InputOption::VALUE_OPTIONAL, 'Automatically fix failed items (if possible)', false); $this->addOption('auto-fix', null, InputOption::VALUE_OPTIONAL, 'Automatically fix failed items (if possible)', false);
} }
public function handle(): int public function handle(): int

View File

@@ -10,6 +10,7 @@ use StaticPHP\DI\ApplicationContext;
use StaticPHP\Registry\ArtifactLoader; use StaticPHP\Registry\ArtifactLoader;
use StaticPHP\Registry\PackageLoader; use StaticPHP\Registry\PackageLoader;
use StaticPHP\Util\DependencyResolver; use StaticPHP\Util\DependencyResolver;
use StaticPHP\Util\GlobalEnvManager;
use StaticPHP\Util\InteractiveTerm; use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
@@ -34,6 +35,7 @@ class ExtractCommand extends BaseCommand
public function handle(): int public function handle(): int
{ {
GlobalEnvManager::afterInit();
$cache = ApplicationContext::get(ArtifactCache::class); $cache = ApplicationContext::get(ArtifactCache::class);
$extractor = new ArtifactExtractor($cache); $extractor = new ArtifactExtractor($cache);
$force_source = (bool) $this->getOption('source-only'); $force_source = (bool) $this->getOption('source-only');

View File

@@ -23,7 +23,7 @@ class InstallPackageCommand extends BaseCommand
'The package to install (name or path)', 'The package to install (name or path)',
suggestedValues: function (CompletionInput $input) { suggestedValues: function (CompletionInput $input) {
$packages = []; $packages = [];
foreach (PackageLoader::getPackages(['target', 'virtual-target', 'tool']) as $name => $_) { foreach (PackageLoader::getPackages(['target', 'virtual-target']) as $name => $_) {
$packages[] = $name; $packages[] = $name;
} }
$val = $input->getCompletionValue(); $val = $input->getCompletionValue();

View File

@@ -4,14 +4,13 @@ declare(strict_types=1);
namespace StaticPHP\Command; namespace StaticPHP\Command;
use StaticPHP\Exception\SPCInternalException;
use StaticPHP\Runtime\Shell\Shell; use StaticPHP\Runtime\Shell\Shell;
use StaticPHP\Util\FileSystem; use StaticPHP\Util\FileSystem;
use StaticPHP\Util\InteractiveTerm; use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use function Laravel\Prompts\confirm;
#[AsCommand('reset')] #[AsCommand('reset')]
class ResetCommand extends BaseCommand class ResetCommand extends BaseCommand
@@ -47,11 +46,7 @@ class ResetCommand extends BaseCommand
// Confirm with user unless --yes is specified // Confirm with user unless --yes is specified
if (!$this->input->getOption('yes')) { if (!$this->input->getOption('yes')) {
$helper = $this->getHelper('question'); if (!confirm('Are you sure you want to continue?', false)) {
if (!$helper instanceof QuestionHelper) {
throw new SPCInternalException('Question helper not provided');
}
if (!$helper->ask($this->input, $this->output, new ConfirmationQuestion('Are you sure you want to continue? [y/N] ', false))) {
InteractiveTerm::error(message: 'Reset operation cancelled.'); InteractiveTerm::error(message: 'Reset operation cancelled.');
return static::SUCCESS; return static::SUCCESS;
} }

Some files were not shown because too many files have changed in this diff Show More