diff --git a/.github/workflows/ext-matrix-tests.yml b/.github/workflows/ext-matrix-tests.yml
index e38ed2ed..2396718c 100644
--- a/.github/workflows/ext-matrix-tests.yml
+++ b/.github/workflows/ext-matrix-tests.yml
@@ -82,7 +82,7 @@ jobs:
- zlib
- zstd
php-version:
- - "8.4"
+ - "git"
operating-system:
- "ubuntu-latest"
#- "macos-13"
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a360e54a..f399a043 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -145,6 +145,10 @@ jobs:
os: ${{ fromJSON(needs.define-matrix.outputs.os) }}
fail-fast: false
steps:
+ - name: "Update runner packages"
+ if: ${{ startsWith(matrix.os, 'ubuntu-') }}
+ run: sudo apt-get update && sudo apt-get install -y ca-certificates
+
- name: "Checkout"
uses: actions/checkout@v4
diff --git a/README-zh.md b/README-zh.md
index 0a0c4578..402fd635 100755
--- a/README-zh.md
+++ b/README-zh.md
@@ -76,22 +76,23 @@ static-php-cli(简称 `spc`)有许多特性:
当前支持编译的 PHP 版本:
-> :warning: 支持,但 static-php-cli 作者可能不再提供补丁修复
+> :warning: 部分支持,对于新的测试版和旧版本可能存在问题。
>
> :heavy_check_mark: 支持
>
> :x: 不支持
-| PHP Version | Status | Comment |
-|-------------|--------------------|----------------------------------------------|
-| 7.2 | :x: | |
-| 7.3 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
-| 7.4 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
-| 8.0 | :warning: | PHP 官方已停止 8.0 的维护,我们不再处理 8.0 相关的 backport 支持 |
-| 8.1 | :heavy_check_mark: | PHP 官方仅对 8.1 提供安全更新 |
-| 8.2 | :heavy_check_mark: | |
-| 8.3 | :heavy_check_mark: | |
-| 8.4 | :heavy_check_mark: | |
+| PHP Version | Status | Comment |
+|-------------|--------------------|---------------------------------------------------------|
+| 7.2 | :x: | |
+| 7.3 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
+| 7.4 | :x: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
+| 8.0 | :warning: | PHP 官方已停止 8.0 的维护,我们不再处理 8.0 相关的 backport 支持 |
+| 8.1 | :heavy_check_mark: | PHP 官方仅对 8.1 提供安全更新,在 8.5 发布后我们不再处理 8.1 相关的 backport 支持 |
+| 8.2 | :heavy_check_mark: | |
+| 8.3 | :heavy_check_mark: | |
+| 8.4 | :heavy_check_mark: | |
+| 8.5 (alpha) | :warning: | PHP 8.5 目前处于 alpha 阶段 |
> 这个表格的支持状态是 static-php-cli 对构建对应版本的支持情况,不是 PHP 官方对该版本的支持情况。
diff --git a/README.md b/README.md
index 46200f09..22eda03f 100755
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@ Here is the supported OS and arch, where :octocat: represents support for GitHub
Currently supported PHP versions for compilation:
-> :warning: supported but not maintained by static-php-cli authors
+> :warning: Partial support, there may be issues with newer test versions or older versions.
>
> :heavy_check_mark: supported
>
@@ -95,10 +95,11 @@ Currently supported PHP versions for compilation:
| 7.3 | :x: | phpmicro and some extensions not supported on 7.x |
| 7.4 | :x: | phpmicro and some extensions not supported on 7.x |
| 8.0 | :warning: | PHP official has stopped maintenance of 8.0, we no longer provide backport support for version 8.0 |
-| 8.1 | :heavy_check_mark: | PHP official has security fixes only |
+| 8.1 | :heavy_check_mark: | PHP official has security fixes only, we no longer provide backport support when 8.5 released |
| 8.2 | :heavy_check_mark: | |
| 8.3 | :heavy_check_mark: | |
| 8.4 | :heavy_check_mark: | |
+| 8.5 (alpha) | :warning: | PHP 8.5 is in alpha |
> This table shows the support status for static-php-cli in building the corresponding version,
> not the official PHP support status for that version.
diff --git a/bin/spc-alpine-docker b/bin/spc-alpine-docker
index 18e981f8..204273e3 100755
--- a/bin/spc-alpine-docker
+++ b/bin/spc-alpine-docker
@@ -104,6 +104,7 @@ RUN apk update; \
m4 \
make \
pkgconfig \
+ re2c \
wget \
xz \
gettext-dev \
diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker
index 1c033134..0d708492 100755
--- a/bin/spc-gnu-docker
+++ b/bin/spc-gnu-docker
@@ -3,7 +3,7 @@
set -e
# This file is using docker to run commands
-SPC_DOCKER_VERSION=v4
+SPC_DOCKER_VERSION=v5
# Detect docker can run
if ! which docker >/dev/null; then
@@ -102,17 +102,20 @@ RUN curl -o cmake.tgz -#fSL https://github.com/Kitware/CMake/releases/download/v
tar -xzf cmake.tgz -C /cmake --strip-components 1
WORKDIR /app
-ADD ./src /app/src
COPY ./composer.* /app/
ADD ./bin/setup-runtime /app/bin/setup-runtime
ADD ./bin/spc /app/bin/spc
RUN /app/bin/setup-runtime
+ADD ./src /app/src
RUN /app/bin/php /app/bin/composer install --no-dev
ENV SPC_LIBC=glibc
ENV PATH="/app/bin:/cmake/bin:/opt/rh/devtoolset-10/root/usr/bin:\$PATH"
-ADD ./config/env.ini /app/config/env.ini
+ADD ./config /app/config
RUN CC=gcc bin/spc doctor --auto-fix --debug
+RUN if [ -f /app/buildroot/bin/re2c ]; then \
+ cp /app/buildroot/bin/re2c /usr/local/bin/re2c ;\
+ fi
RUN curl -o make.tgz -fsSL https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && \
tar -zxvf make.tgz && \
diff --git a/composer.json b/composer.json
index a3226d69..f3a29057 100644
--- a/composer.json
+++ b/composer.json
@@ -23,7 +23,6 @@
"captainhook/hook-installer": "^1.0",
"friendsofphp/php-cs-fixer": "^3.60",
"humbug/box": "^4.5.0 || ^4.6.0",
- "nunomaduro/collision": "^7.8",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.3 || ^9.5"
},
diff --git a/composer.lock b/composer.lock
index a05a1e0d..5beddd6a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d705cec8270679a463d9e04310208967",
+ "content-hash": "dbefe1ee5c584f6f0cc64c26fb83451c",
"packages": [
{
"name": "illuminate/collections",
@@ -416,47 +416,47 @@
},
{
"name": "symfony/console",
- "version": "v6.4.23",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "9056771b8eca08d026cd3280deeec3cfd99c4d93"
+ "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/9056771b8eca08d026cd3280deeec3cfd99c4d93",
- "reference": "9056771b8eca08d026cd3280deeec3cfd99c4d93",
+ "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1",
+ "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^2.5|^3",
- "symfony/string": "^5.4|^6.0|^7.0"
+ "symfony/string": "^7.2"
},
"conflict": {
- "symfony/dependency-injection": "<5.4",
- "symfony/dotenv": "<5.4",
- "symfony/event-dispatcher": "<5.4",
- "symfony/lock": "<5.4",
- "symfony/process": "<5.4"
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
- "symfony/config": "^5.4|^6.0|^7.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
- "symfony/lock": "^5.4|^6.0|^7.0",
- "symfony/messenger": "^5.4|^6.0|^7.0",
- "symfony/process": "^5.4|^6.0|^7.0",
- "symfony/stopwatch": "^5.4|^6.0|^7.0",
- "symfony/var-dumper": "^5.4|^6.0|^7.0"
+ "symfony/lock": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -490,7 +490,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.4.23"
+ "source": "https://github.com/symfony/console/tree/v7.3.2"
},
"funding": [
{
@@ -501,12 +501,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-27T19:37:22+00:00"
+ "time": "2025-07-30T17:13:41+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1040,16 +1044,16 @@
},
{
"name": "symfony/string",
- "version": "v7.3.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125"
+ "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125",
- "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125",
+ "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca",
+ "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca",
"shasum": ""
},
"require": {
@@ -1107,7 +1111,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.3.0"
+ "source": "https://github.com/symfony/string/tree/v7.3.2"
},
"funding": [
{
@@ -1118,25 +1122,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-20T20:19:01+00:00"
+ "time": "2025-07-10T08:47:49+00:00"
},
{
"name": "symfony/yaml",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb"
+ "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb",
- "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30",
+ "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30",
"shasum": ""
},
"require": {
@@ -1179,7 +1187,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v7.3.1"
+ "source": "https://github.com/symfony/yaml/tree/v7.3.2"
},
"funding": [
{
@@ -1190,12 +1198,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-03T06:57:57+00:00"
+ "time": "2025-07-10T08:47:49+00:00"
},
{
"name": "zhamao/logger",
@@ -2078,7 +2090,7 @@
},
{
"name": "captainhook/captainhook-phar",
- "version": "5.25.6",
+ "version": "5.25.10",
"source": {
"type": "git",
"url": "https://github.com/captainhook-git/captainhook-phar.git",
@@ -2132,7 +2144,7 @@
],
"support": {
"issues": "https://github.com/captainhook-git/captainhook/issues",
- "source": "https://github.com/captainhook-git/captainhook-phar/tree/5.25.6"
+ "source": "https://github.com/captainhook-git/captainhook-phar/tree/5.25.10"
},
"funding": [
{
@@ -2831,93 +2843,22 @@
],
"time": "2025-02-13T23:05:19+00:00"
},
- {
- "name": "filp/whoops",
- "version": "2.18.3",
- "source": {
- "type": "git",
- "url": "https://github.com/filp/whoops.git",
- "reference": "59a123a3d459c5a23055802237cb317f609867e5"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5",
- "reference": "59a123a3d459c5a23055802237cb317f609867e5",
- "shasum": ""
- },
- "require": {
- "php": "^7.1 || ^8.0",
- "psr/log": "^1.0.1 || ^2.0 || ^3.0"
- },
- "require-dev": {
- "mockery/mockery": "^1.0",
- "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
- "symfony/var-dumper": "^4.0 || ^5.0"
- },
- "suggest": {
- "symfony/var-dumper": "Pretty print complex values better with var-dumper available",
- "whoops/soap": "Formats errors as SOAP responses"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.7-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Whoops\\": "src/Whoops/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Filipe Dobreira",
- "homepage": "https://github.com/filp",
- "role": "Developer"
- }
- ],
- "description": "php error handling for cool kids",
- "homepage": "https://filp.github.io/whoops/",
- "keywords": [
- "error",
- "exception",
- "handling",
- "library",
- "throwable",
- "whoops"
- ],
- "support": {
- "issues": "https://github.com/filp/whoops/issues",
- "source": "https://github.com/filp/whoops/tree/2.18.3"
- },
- "funding": [
- {
- "url": "https://github.com/denis-sokolov",
- "type": "github"
- }
- ],
- "time": "2025-06-16T00:02:10+00:00"
- },
{
"name": "friendsofphp/php-cs-fixer",
- "version": "v3.84.0",
+ "version": "v3.85.1",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
- "reference": "38dad0767bf2a9b516b976852200ae722fe984ca"
+ "reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/38dad0767bf2a9b516b976852200ae722fe984ca",
- "reference": "38dad0767bf2a9b516b976852200ae722fe984ca",
+ "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2fb6d7f6c3398dca5786a1635b27405d73a417ba",
+ "reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba",
"shasum": ""
},
"require": {
- "clue/ndjson-react": "^1.0",
+ "clue/ndjson-react": "^1.3",
"composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.5",
"ext-filter": "*",
@@ -2927,12 +2868,12 @@
"fidry/cpu-core-counter": "^1.2",
"php": "^7.4 || ^8.0",
"react/child-process": "^0.6.6",
- "react/event-loop": "^1.0",
- "react/promise": "^2.11 || ^3.0",
- "react/socket": "^1.0",
- "react/stream": "^1.0",
+ "react/event-loop": "^1.5",
+ "react/promise": "^3.2",
+ "react/socket": "^1.16",
+ "react/stream": "^1.4",
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0",
- "symfony/console": "^5.4.45 || ^6.4.13 || ^7.0",
+ "symfony/console": "^5.4.47 || ^6.4.13 || ^7.0",
"symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0",
"symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0",
"symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0",
@@ -2997,7 +2938,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
- "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.84.0"
+ "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.85.1"
},
"funding": [
{
@@ -3005,7 +2946,7 @@
"type": "github"
}
],
- "time": "2025-07-15T18:21:57+00:00"
+ "time": "2025-07-29T22:22:50+00:00"
},
{
"name": "humbug/box",
@@ -3660,16 +3601,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.5.0",
+ "version": "v5.6.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "ae59794362fe85e051a58ad36b289443f57be7a9"
+ "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9",
- "reference": "ae59794362fe85e051a58ad36b289443f57be7a9",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56",
+ "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56",
"shasum": ""
},
"require": {
@@ -3712,190 +3653,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0"
},
- "time": "2025-05-31T08:24:38+00:00"
- },
- {
- "name": "nunomaduro/collision",
- "version": "v7.12.0",
- "source": {
- "type": "git",
- "url": "https://github.com/nunomaduro/collision.git",
- "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/collision/zipball/995245421d3d7593a6960822063bdba4f5d7cf1a",
- "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a",
- "shasum": ""
- },
- "require": {
- "filp/whoops": "^2.17.0",
- "nunomaduro/termwind": "^1.17.0",
- "php": "^8.1.0",
- "symfony/console": "^6.4.17"
- },
- "conflict": {
- "laravel/framework": ">=11.0.0"
- },
- "require-dev": {
- "brianium/paratest": "^7.4.8",
- "laravel/framework": "^10.48.29",
- "laravel/pint": "^1.21.2",
- "laravel/sail": "^1.41.0",
- "laravel/sanctum": "^3.3.3",
- "laravel/tinker": "^2.10.1",
- "nunomaduro/larastan": "^2.10.0",
- "orchestra/testbench-core": "^8.35.0",
- "pestphp/pest": "^2.36.0",
- "phpunit/phpunit": "^10.5.36",
- "sebastian/environment": "^6.1.0",
- "spatie/laravel-ignition": "^2.9.1"
- },
- "type": "library",
- "extra": {
- "laravel": {
- "providers": [
- "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider"
- ]
- }
- },
- "autoload": {
- "files": [
- "./src/Adapters/Phpunit/Autoload.php"
- ],
- "psr-4": {
- "NunoMaduro\\Collision\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nuno Maduro",
- "email": "enunomaduro@gmail.com"
- }
- ],
- "description": "Cli error handling for console/command-line PHP applications.",
- "keywords": [
- "artisan",
- "cli",
- "command-line",
- "console",
- "error",
- "handling",
- "laravel",
- "laravel-zero",
- "php",
- "symfony"
- ],
- "support": {
- "issues": "https://github.com/nunomaduro/collision/issues",
- "source": "https://github.com/nunomaduro/collision"
- },
- "funding": [
- {
- "url": "https://www.paypal.com/paypalme/enunomaduro",
- "type": "custom"
- },
- {
- "url": "https://github.com/nunomaduro",
- "type": "github"
- },
- {
- "url": "https://www.patreon.com/nunomaduro",
- "type": "patreon"
- }
- ],
- "time": "2025-03-14T22:35:49+00:00"
- },
- {
- "name": "nunomaduro/termwind",
- "version": "v1.17.0",
- "source": {
- "type": "git",
- "url": "https://github.com/nunomaduro/termwind.git",
- "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/5369ef84d8142c1d87e4ec278711d4ece3cbf301",
- "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301",
- "shasum": ""
- },
- "require": {
- "ext-mbstring": "*",
- "php": "^8.1",
- "symfony/console": "^6.4.15"
- },
- "require-dev": {
- "illuminate/console": "^10.48.24",
- "illuminate/support": "^10.48.24",
- "laravel/pint": "^1.18.2",
- "pestphp/pest": "^2.36.0",
- "pestphp/pest-plugin-mock": "2.0.0",
- "phpstan/phpstan": "^1.12.11",
- "phpstan/phpstan-strict-rules": "^1.6.1",
- "symfony/var-dumper": "^6.4.15",
- "thecodingmachine/phpstan-strict-rules": "^1.0.0"
- },
- "type": "library",
- "extra": {
- "laravel": {
- "providers": [
- "Termwind\\Laravel\\TermwindServiceProvider"
- ]
- }
- },
- "autoload": {
- "files": [
- "src/Functions.php"
- ],
- "psr-4": {
- "Termwind\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nuno Maduro",
- "email": "enunomaduro@gmail.com"
- }
- ],
- "description": "Its like Tailwind CSS, but for the console.",
- "keywords": [
- "cli",
- "console",
- "css",
- "package",
- "php",
- "style"
- ],
- "support": {
- "issues": "https://github.com/nunomaduro/termwind/issues",
- "source": "https://github.com/nunomaduro/termwind/tree/v1.17.0"
- },
- "funding": [
- {
- "url": "https://www.paypal.com/paypalme/enunomaduro",
- "type": "custom"
- },
- {
- "url": "https://github.com/nunomaduro",
- "type": "github"
- },
- {
- "url": "https://github.com/xiCO2k",
- "type": "github"
- }
- ],
- "time": "2024-11-21T10:36:35+00:00"
+ "time": "2025-07-27T20:03:57+00:00"
},
{
"name": "phar-io/composer-distributor",
@@ -6881,16 +6641,16 @@
},
{
"name": "symfony/filesystem",
- "version": "v7.3.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb"
+ "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
- "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd",
+ "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd",
"shasum": ""
},
"require": {
@@ -6927,7 +6687,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v7.3.0"
+ "source": "https://github.com/symfony/filesystem/tree/v7.3.2"
},
"funding": [
{
@@ -6938,25 +6698,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-10-25T15:15:23+00:00"
+ "time": "2025-07-07T08:17:47+00:00"
},
{
"name": "symfony/finder",
- "version": "v7.3.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d"
+ "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d",
- "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe",
+ "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe",
"shasum": ""
},
"require": {
@@ -6991,7 +6755,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v7.3.0"
+ "source": "https://github.com/symfony/finder/tree/v7.3.2"
},
"funding": [
{
@@ -7002,25 +6766,29 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2024-12-30T19:00:26+00:00"
+ "time": "2025-07-15T13:41:35+00:00"
},
{
"name": "symfony/options-resolver",
- "version": "v7.3.0",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
- "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca"
+ "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca",
- "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37",
+ "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37",
"shasum": ""
},
"require": {
@@ -7058,7 +6826,7 @@
"options"
],
"support": {
- "source": "https://github.com/symfony/options-resolver/tree/v7.3.0"
+ "source": "https://github.com/symfony/options-resolver/tree/v7.3.2"
},
"funding": [
{
@@ -7069,12 +6837,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-04T13:12:05+00:00"
+ "time": "2025-07-15T11:36:08+00:00"
},
{
"name": "symfony/polyfill-iconv",
@@ -7296,16 +7068,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v7.3.1",
+ "version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42"
+ "reference": "53205bea27450dc5c65377518b3275e126d45e75"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42",
- "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75",
+ "reference": "53205bea27450dc5c65377518b3275e126d45e75",
"shasum": ""
},
"require": {
@@ -7317,7 +7089,6 @@
"symfony/console": "<6.4"
},
"require-dev": {
- "ext-iconv": "*",
"symfony/console": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/process": "^6.4|^7.0",
@@ -7360,7 +7131,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.3.1"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.3.2"
},
"funding": [
{
@@ -7371,12 +7142,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-06-27T19:55:54+00:00"
+ "time": "2025-07-29T20:02:46+00:00"
},
{
"name": "thecodingmachine/safe",
diff --git a/config/env.ini b/config/env.ini
index 465f72d5..04058305 100644
--- a/config/env.ini
+++ b/config/env.ini
@@ -10,6 +10,7 @@
;
; Here's a list of env vars, these value cannot be changed anywhere:
;
+; SPC_VERSION: the version of static-php-cli.
; WORKING_DIR: the working directory of the build process. (default: `$(pwd)`)
; ROOT_DIR: the root directory of static-php-cli. (default: `/path/to/static-php-cli`, when running in phar or micro mode: `phar://path/to/spc.phar`)
; BUILD_ROOT_PATH: the root path of the build process. (default: `$(pwd)/buildroot`)
@@ -43,6 +44,8 @@ SPC_SKIP_PHP_VERSION_CHECK="no"
SPC_SKIP_DOCTOR_CHECK_ITEMS=""
; extra modules that xcaddy will include in the FrankenPHP build
SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/frankenphp/caddy --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli"
+; The display message for php version output (PHP >= 8.4 available)
+PHP_BUILD_PROVIDER="static-php-cli ${SPC_VERSION}"
; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them
; only useful for builds targeting not pure-static linking
diff --git a/config/ext.json b/config/ext.json
index 2cf81b18..14eabf17 100644
--- a/config/ext.json
+++ b/config/ext.json
@@ -488,6 +488,7 @@
"opcache": {
"type": "builtin",
"arg-type-unix": "custom",
+ "arg-type-windows": "enable",
"zend-extension": true
},
"openssl": {
@@ -530,9 +531,10 @@
},
"notes": true,
"type": "builtin",
- "arg-type": "with-path",
+ "arg-type": "custom",
"lib-depends": [
- "libargon2"
+ "libargon2",
+ "openssl"
]
},
"pcntl": {
diff --git a/config/lib.json b/config/lib.json
index d1295074..b8abd577 100644
--- a/config/lib.json
+++ b/config/lib.json
@@ -797,6 +797,12 @@
"depot.h"
]
},
+ "re2c": {
+ "source": "re2c",
+ "bin-unix": [
+ "re2c"
+ ]
+ },
"readline": {
"source": "readline",
"static-libs-unix": [
diff --git a/config/source.json b/config/source.json
index 90ed41eb..79e4b153 100644
--- a/config/source.json
+++ b/config/source.json
@@ -733,7 +733,7 @@
"micro": {
"type": "git",
"path": "php-src/sapi/micro",
- "rev": "84beta",
+ "rev": "php-85-win",
"url": "https://github.com/static-php/phpmicro",
"license": {
"type": "file",
@@ -932,6 +932,20 @@
"path": "LICENSE"
}
},
+ "re2c": {
+ "type": "ghrel",
+ "repo": "skvadrik/re2c",
+ "match": "re2c.+\\.tar\\.xz",
+ "prefer-stable": true,
+ "alt": {
+ "type": "url",
+ "url": "https://dl.static-php.dev/static-php-cli/deps/re2c/re2c-4.3.tar.xz"
+ },
+ "license": {
+ "type": "file",
+ "path": "LICENSE"
+ }
+ },
"readline": {
"type": "filelist",
"url": "https://ftp.gnu.org/pub/gnu/readline/",
diff --git a/docs/.vitepress/components/CliGenerator.vue b/docs/.vitepress/components/CliGenerator.vue
index ad17783a..f9f3613d 100644
--- a/docs/.vitepress/components/CliGenerator.vue
+++ b/docs/.vitepress/components/CliGenerator.vue
@@ -238,6 +238,7 @@ const availablePhpVersions = [
'8.2',
'8.3',
'8.4',
+ '8.5',
];
const I18N = {
diff --git a/docs/en/guide/index.md b/docs/en/guide/index.md
index 9f9bee4c..253562b5 100644
--- a/docs/en/guide/index.md
+++ b/docs/en/guide/index.md
@@ -32,7 +32,7 @@ Windows currently only supports the x86_64 architecture, and does not support 32
## Supported PHP Version
-Currently, static php cli supports PHP versions 8.1 to 8.4, and theoretically supports PHP 8.0 and earlier versions.
+Currently, static php cli supports PHP versions 8.1 to 8.5, and theoretically supports PHP 8.0 and earlier versions.
Simply select the earlier version when downloading.
However, due to some extensions and special components that have stopped supporting earlier versions of PHP,
static-php-cli will not explicitly support earlier versions.
diff --git a/docs/en/guide/manual-build.md b/docs/en/guide/manual-build.md
index e8e4510e..e9afaa4c 100644
--- a/docs/en/guide/manual-build.md
+++ b/docs/en/guide/manual-build.md
@@ -242,8 +242,8 @@ Also, it is available when downloading with the `--for-extensions` option.
```bash
-# Specifying to download a beta version of PHP8.3
-bin/spc download --all -U "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"
+# Specifying to download a alpha version of PHP 8.5
+bin/spc download --all -U "php-src:https://downloads.php.net/~edorian/php-8.5.0alpha2.tar.xz"
# Specifying to download an older version of the curl library
bin/spc download --all -U "curl:https://curl.se/download/curl-7.88.1.tar.gz"
diff --git a/docs/zh/guide/index.md b/docs/zh/guide/index.md
index 33192360..318c40b8 100644
--- a/docs/zh/guide/index.md
+++ b/docs/zh/guide/index.md
@@ -29,6 +29,6 @@ Windows 目前只支持 x86_64 架构,不支持 32 位 x86、不支持 arm64
## PHP 支持版本
-目前,static-php-cli 对 PHP 8.1 ~ 8.4 版本是支持的,对于 PHP 8.0 及更早版本理论上支持,只需下载时选择早期版本即可。
+目前,static-php-cli 对 PHP 8.1 ~ 8.5 版本是支持的,对于 PHP 8.0 及更早版本理论上支持,只需下载时选择早期版本即可。
但由于部分扩展和特殊组件已对早期版本的 PHP 停止了支持,所以 static-php-cli 不会明确支持早期版本。
我们推荐你编译尽可能新的 PHP 版本,以获得更好的体验。
diff --git a/docs/zh/guide/manual-build.md b/docs/zh/guide/manual-build.md
index 5301fca1..dcc9d255 100644
--- a/docs/zh/guide/manual-build.md
+++ b/docs/zh/guide/manual-build.md
@@ -209,8 +209,8 @@ bin/spc download --from-zip=/path/to/your/download.zip
让下载器强制使用你指定的链接下载此 source 的包。使用方法为 `{source-name}:{url}` 即可,可同时重写多个库的下载地址。在使用 `--for-extensions` 选项下载时同样可用。
```bash
-# 例如:指定下载测试版的 PHP8.3
-bin/spc download --all -U "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"
+# 例如:指定下载 Alpha 版的 PHP8.5
+bin/spc download --all -U "php-src:https://downloads.php.net/~edorian/php-8.5.0alpha2.tar.xz"
# 指定下载旧版本的 curl 库
bin/spc download --all -U "curl:https://curl.se/download/curl-7.88.1.tar.gz"
diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php
index becd0f43..5dec6ad8 100644
--- a/src/SPC/builder/BuilderBase.php
+++ b/src/SPC/builder/BuilderBase.php
@@ -339,16 +339,21 @@ abstract class BuilderBase
throw new RuntimeException('PHP version file format is malformed, please remove it and download again');
}
- public function getPHPVersion(): string
+ public function getPHPVersion(bool $exception_on_failure = true): string
{
if (!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) {
+ if (!$exception_on_failure) {
+ return 'unknown';
+ }
throw new WrongUsageException('PHP source files are not available, you need to download them first');
}
$file = file_get_contents(SOURCE_PATH . '/php-src/main/php_version.h');
if (preg_match('/PHP_VERSION "(.*)"/', $file, $match) !== 0) {
return $match[1];
}
-
+ if (!$exception_on_failure) {
+ return 'unknown';
+ }
throw new RuntimeException('PHP version file format is malformed, please remove it and download again');
}
@@ -366,7 +371,7 @@ abstract class BuilderBase
}
$file = LockFile::getLockFullPath($lock);
}
- if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) {
+ if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+|alpha\d+|beta\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) {
return $match[1];
}
return false;
diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php
index 90f2e27e..435582c0 100644
--- a/src/SPC/builder/Extension.php
+++ b/src/SPC/builder/Extension.php
@@ -183,6 +183,14 @@ class Extension
return false;
}
+ /**
+ * Patch code before ./configure.bat for Windows
+ */
+ public function patchBeforeWindowsConfigure(): bool
+ {
+ return false;
+ }
+
/**
* Patch code before make
* If you need to patch some code, overwrite this
diff --git a/src/SPC/builder/LibraryBase.php b/src/SPC/builder/LibraryBase.php
index 5f03d72c..e1a3b03a 100644
--- a/src/SPC/builder/LibraryBase.php
+++ b/src/SPC/builder/LibraryBase.php
@@ -288,6 +288,16 @@ abstract class LibraryBase
return false;
}
+ /**
+ * Patch code before windows configure.bat
+ * If you need to patch some code, overwrite this
+ * return true if you patched something, false if not
+ */
+ public function patchBeforeWindowsConfigure(): bool
+ {
+ return false;
+ }
+
/**
* Patch code before make
* If you need to patch some code, overwrite this
diff --git a/src/SPC/builder/extension/curl.php b/src/SPC/builder/extension/curl.php
index a9079830..17db61d3 100644
--- a/src/SPC/builder/extension/curl.php
+++ b/src/SPC/builder/extension/curl.php
@@ -61,7 +61,7 @@ class curl extends Extension
public function patchBeforeMake(): bool
{
$patched = parent::patchBeforeMake();
- $extra_libs = getenv('SPC_EXTRA_LIBS');
+ $extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
if ($this->builder instanceof WindowsBuilder && !str_contains($extra_libs, 'secur32.lib')) {
$extra_libs .= ' secur32.lib';
putenv('SPC_EXTRA_LIBS=' . trim($extra_libs));
diff --git a/src/SPC/builder/extension/imap.php b/src/SPC/builder/extension/imap.php
index 81bb3164..d220bc9b 100644
--- a/src/SPC/builder/extension/imap.php
+++ b/src/SPC/builder/extension/imap.php
@@ -49,7 +49,7 @@ class imap extends Extension
if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::isMuslDist()) {
return $patched;
}
- $extra_libs = trim(getenv('SPC_EXTRA_LIBS') . ' -lcrypt');
+ $extra_libs = trim((getenv('SPC_EXTRA_LIBS') ?: '') . ' -lcrypt');
f_putenv('SPC_EXTRA_LIBS=' . $extra_libs);
return true;
}
diff --git a/src/SPC/builder/extension/opcache.php b/src/SPC/builder/extension/opcache.php
index 5d9dda0a..b73dbbd6 100644
--- a/src/SPC/builder/extension/opcache.php
+++ b/src/SPC/builder/extension/opcache.php
@@ -26,19 +26,26 @@ class opcache extends Extension
public function patchBeforeBuildconf(): bool
{
+ $version = $this->builder->getPHPVersion();
if (file_exists(SOURCE_PATH . '/php-src/.opcache_patched')) {
return false;
}
// if 8.2.0 <= PHP_VERSION < 8.2.23, we need to patch from legacy patch file
- if (version_compare($this->builder->getPHPVersion(), '8.2.0', '>=') && version_compare($this->builder->getPHPVersion(), '8.2.23', '<')) {
+ if (version_compare($version, '8.2.0', '>=') && version_compare($version, '8.2.23', '<')) {
SourcePatcher::patchFile('spc_fix_static_opcache_before_80222.patch', SOURCE_PATH . '/php-src');
}
// if 8.3.0 <= PHP_VERSION < 8.3.11, we need to patch from legacy patch file
- elseif (version_compare($this->builder->getPHPVersion(), '8.3.0', '>=') && version_compare($this->builder->getPHPVersion(), '8.3.11', '<')) {
+ elseif (version_compare($version, '8.3.0', '>=') && version_compare($version, '8.3.11', '<')) {
SourcePatcher::patchFile('spc_fix_static_opcache_before_80310.patch', SOURCE_PATH . '/php-src');
- } else {
+ }
+ // if 8.3.12 <= PHP_VERSION < 8.5.0-dev, we need to patch from legacy patch file
+ elseif (version_compare($version, '8.5.0-dev', '<')) {
SourcePatcher::patchMicro(items: ['static_opcache']);
}
+ // PHP 8.5.0-dev and later supports static opcache without patching
+ else {
+ return false;
+ }
return file_put_contents(SOURCE_PATH . '/php-src/.opcache_patched', '1') !== false;
}
diff --git a/src/SPC/builder/extension/openssl.php b/src/SPC/builder/extension/openssl.php
index 3ef4998f..bf61fa37 100644
--- a/src/SPC/builder/extension/openssl.php
+++ b/src/SPC/builder/extension/openssl.php
@@ -27,6 +27,19 @@ class openssl extends Extension
public function getUnixConfigureArg(bool $shared = false): string
{
$openssl_dir = $this->builder->getPHPVersionID() >= 80400 ? '' : ' --with-openssl-dir=' . BUILD_ROOT_PATH;
- return '--with-openssl=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $openssl_dir;
+ $args = '--with-openssl=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $openssl_dir;
+ if ($this->builder->getPHPVersionID() >= 80500 || ($this->builder->getPHPVersionID() >= 80400 && !$this->builder->getOption('enable-zts'))) {
+ $args .= ' --with-openssl-argon2 OPENSSL_LIBS="-lz"';
+ }
+ return $args;
+ }
+
+ public function getWindowsConfigureArg(bool $shared = false): string
+ {
+ $args = '--with-openssl';
+ if ($this->builder->getPHPVersionID() >= 80500 || ($this->builder->getPHPVersionID() >= 80400 && !$this->builder->getOption('enable-zts'))) {
+ $args .= ' --with-openssl-argon2';
+ }
+ return $args;
}
}
diff --git a/src/SPC/builder/extension/password_argon2.php b/src/SPC/builder/extension/password_argon2.php
index 4a7fd9d0..7a3d8e0c 100644
--- a/src/SPC/builder/extension/password_argon2.php
+++ b/src/SPC/builder/extension/password_argon2.php
@@ -42,4 +42,14 @@ class password_argon2 extends Extension
}
return $patched;
}
+
+ public function getConfigureArg(bool $shared = false): string
+ {
+ if ($this->builder->getLib('openssl') !== null) {
+ if ($this->builder->getPHPVersionID() >= 80500 || ($this->builder->getPHPVersionID() >= 80400 && !$this->builder->getOption('enable-zts'))) {
+ return '--without-password-argon2'; // use --with-openssl-argon2 in openssl extension instead
+ }
+ }
+ return '--with-password-argon2';
+ }
}
diff --git a/src/SPC/builder/extension/sqlsrv.php b/src/SPC/builder/extension/sqlsrv.php
index edf5d919..04bd5226 100644
--- a/src/SPC/builder/extension/sqlsrv.php
+++ b/src/SPC/builder/extension/sqlsrv.php
@@ -24,7 +24,7 @@ class sqlsrv extends Extension
return false;
}
- public function patchBeforeConfigure(): bool
+ public function patchBeforeWindowsConfigure(): bool
{
if ($this->pdo_sqlsrv_patched) {
// revert pdo_sqlsrv patch
diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php
index 28edeb22..38990403 100644
--- a/src/SPC/builder/linux/LinuxBuilder.php
+++ b/src/SPC/builder/linux/LinuxBuilder.php
@@ -66,6 +66,17 @@ class LinuxBuilder extends UnixBuilderBase
$phpVersionID = $this->getPHPVersionID();
$json_74 = $phpVersionID < 80000 ? '--enable-json ' : '';
+ $opcache_jit = !$this->getOption('disable-opcache-jit', false);
+ if ($opcache_jit && ($phpVersionID >= 80500 || $this->getExt('opcache'))) {
+ // php 8.5 contains opcache extension by default,
+ // if opcache_jit is enabled for 8.5 or opcache enabled,
+ // we need to disable undefined behavior sanitizer.
+ f_putenv('SPC_COMPILER_EXTRA=-fno-sanitize=undefined');
+ } elseif ($opcache_jit) {
+ $opcache_jit = false;
+ }
+ $opcache_jit_arg = $opcache_jit ? '--enable-opcache-jit ' : '--disable-opcache-jit ';
+
if ($this->getOption('enable-zts', false)) {
$maxExecutionTimers = $phpVersionID >= 80100 ? '--enable-zend-max-execution-timers ' : '';
$zts = '--enable-zts --disable-zend-signals ';
@@ -73,10 +84,7 @@ class LinuxBuilder extends UnixBuilderBase
$maxExecutionTimers = '';
$zts = '';
}
- $disable_jit = $this->getOption('disable-opcache-jit', false) ? '--disable-opcache-jit ' : '';
- if (!$disable_jit && $this->getExt('opcache')) {
- f_putenv('SPC_COMPILER_EXTRA=-fno-sanitize=undefined');
- }
+
$config_file_path = $this->getOption('with-config-file-path', false) ?
('--with-config-file-path=' . $this->getOption('with-config-file-path') . ' ') : '';
$config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ?
@@ -89,9 +97,10 @@ class LinuxBuilder extends UnixBuilderBase
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
// prepare build php envs
+ // $musl_flag = SPCTarget::getLibc() === 'musl' ? ' -D__MUSL__' : ' -U__MUSL__';
$php_configure_env = SystemUtil::makeEnvVarString([
'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'),
- 'CPPFLAGS' => '-I' . BUILD_INCLUDE_PATH,
+ 'CPPFLAGS' => '-I' . BUILD_INCLUDE_PATH, // . ' -Dsomethinghere', // . $musl_flag,
'LDFLAGS' => '-L' . BUILD_LIB_PATH,
// 'LIBS' => SPCTarget::getRuntimeLibs(), // do not pass static libraries here yet, they may contain polyfills for libc functions!
]);
@@ -114,7 +123,7 @@ class LinuxBuilder extends UnixBuilderBase
($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
$config_file_path .
$config_file_scan_dir .
- $disable_jit .
+ $opcache_jit_arg .
$json_74 .
$zts .
$maxExecutionTimers .
@@ -335,6 +344,15 @@ class LinuxBuilder extends UnixBuilderBase
$this->patchPhpScripts();
}
+ /**
+ * Return extra variables for php make command.
+ *
+ * @throws FileSystemException
+ * @throws RuntimeException
+ * @throws WrongUsageException
+ * @throws \ReflectionException
+ * @throws \Throwable
+ */
private function getMakeExtraVars(): array
{
$config = (new SPCConfigUtil($this, ['libs_only_deps' => true, 'absolute_libs' => true]))->config($this->ext_list, $this->lib_list, $this->getOption('with-suggested-exts'), $this->getOption('with-suggested-libs'));
diff --git a/src/SPC/builder/linux/library/openssl.php b/src/SPC/builder/linux/library/openssl.php
index 161182cb..3da4bb95 100644
--- a/src/SPC/builder/linux/library/openssl.php
+++ b/src/SPC/builder/linux/library/openssl.php
@@ -28,6 +28,8 @@ use SPC\store\FileSystem;
class openssl extends LinuxLibraryBase
{
+ use \SPC\builder\traits\openssl;
+
public const NAME = 'openssl';
/**
@@ -37,8 +39,6 @@ class openssl extends LinuxLibraryBase
*/
public function build(): void
{
- [,,$destdir] = SEPARATED_PATH;
-
$extra = '';
$ex_lib = '-ldl -pthread';
$arch = getenv('SPC_ARCH');
diff --git a/src/SPC/builder/linux/library/re2c.php b/src/SPC/builder/linux/library/re2c.php
new file mode 100644
index 00000000..91c6ad50
--- /dev/null
+++ b/src/SPC/builder/linux/library/re2c.php
@@ -0,0 +1,15 @@
+emitPatchPoint('before-php-configure');
SourcePatcher::patchBeforeConfigure($this);
- $json_74 = $this->getPHPVersionID() < 80000 ? '--enable-json ' : '';
+ $phpVersionID = $this->getPHPVersionID();
+ $json_74 = $phpVersionID < 80000 ? '--enable-json ' : '';
$zts = $this->getOption('enable-zts', false) ? '--enable-zts --disable-zend-signals ' : '';
+ $opcache_jit = !$this->getOption('disable-opcache-jit', false);
+ // disable opcache jit for PHP < 8.5.0 when opcache is not enabled
+ if ($opcache_jit && $phpVersionID < 80500 && !$this->getExt('opcache')) {
+ $opcache_jit = false;
+ }
+ $opcache_jit_arg = $opcache_jit ? '--enable-opcache-jit ' : '--disable-opcache-jit ';
+
$config_file_path = $this->getOption('with-config-file-path', false) ?
('--with-config-file-path=' . $this->getOption('with-config-file-path') . ' ') : '';
$config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ?
@@ -135,6 +143,7 @@ class MacOSBuilder extends UnixBuilderBase
($enableFpm ? '--enable-fpm ' : '--disable-fpm ') .
($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
($enableMicro ? '--enable-micro ' : '--disable-micro ') .
+ $opcache_jit_arg .
$config_file_path .
$config_file_scan_dir .
$json_74 .
diff --git a/src/SPC/builder/macos/library/openssl.php b/src/SPC/builder/macos/library/openssl.php
index c1826b5c..df0f4a52 100644
--- a/src/SPC/builder/macos/library/openssl.php
+++ b/src/SPC/builder/macos/library/openssl.php
@@ -28,6 +28,8 @@ use SPC\store\FileSystem;
class openssl extends MacOSLibraryBase
{
+ use \SPC\builder\traits\openssl;
+
public const NAME = 'openssl';
/**
diff --git a/src/SPC/builder/macos/library/re2c.php b/src/SPC/builder/macos/library/re2c.php
new file mode 100644
index 00000000..925d1f28
--- /dev/null
+++ b/src/SPC/builder/macos/library/re2c.php
@@ -0,0 +1,12 @@
+source_dir}/VERSION.dat")) {
+ // parse as INI
+ $version = parse_ini_file("{$this->source_dir}/VERSION.dat");
+ if ($version !== false) {
+ return "{$version['MAJOR']}.{$version['MINOR']}.{$version['PATCH']}";
+ }
+ }
+ // get openssl version from pkg-config
+ if (PHP_OS_FAMILY !== 'Windows') {
+ try {
+ return PkgConfigUtil::getModuleVersion('openssl');
+ } catch (RuntimeException) {
+ }
+ }
+ // get openssl version from header openssl/opensslv.h
+ if (file_exists(BUILD_INCLUDE_PATH . '/openssl/opensslv.h')) {
+ if (preg_match('/OPENSSL_VERSION_STR "(.*)"/', FileSystem::readFile(BUILD_INCLUDE_PATH . '/openssl/opensslv.h'), $match)) {
+ return $match[1];
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/SPC/builder/unix/library/re2c.php b/src/SPC/builder/unix/library/re2c.php
new file mode 100644
index 00000000..cd6a562a
--- /dev/null
+++ b/src/SPC/builder/unix/library/re2c.php
@@ -0,0 +1,32 @@
+addConfigureArgs(
+ '-DRE2C_BUILD_TESTS=OFF',
+ '-DRE2C_BUILD_EXAMPLES=OFF',
+ '-DRE2C_BUILD_DOCS=OFF',
+ '-DRE2C_BUILD_RE2D=OFF',
+ '-DRE2C_BUILD_RE2GO=OFF',
+ '-DRE2C_BUILD_RE2HS=OFF',
+ '-DRE2C_BUILD_RE2JAVA=OFF',
+ '-DRE2C_BUILD_RE2JS=OFF',
+ '-DRE2C_BUILD_RE2OCAML=OFF',
+ '-DRE2C_BUILD_RE2PY=OFF',
+ '-DRE2C_BUILD_RE2RUST=OFF',
+ '-DRE2C_BUILD_RE2SWIFT=OFF',
+ '-DRE2C_BUILD_RE2V=OFF',
+ '-DRE2C_BUILD_RE2ZIG=OFF',
+ )
+ ->build();
+ }
+}
diff --git a/src/SPC/builder/windows/WindowsBuilder.php b/src/SPC/builder/windows/WindowsBuilder.php
index fb49a013..ac29ae3a 100644
--- a/src/SPC/builder/windows/WindowsBuilder.php
+++ b/src/SPC/builder/windows/WindowsBuilder.php
@@ -89,6 +89,9 @@ class WindowsBuilder extends BuilderBase
}
}
+ $opcache_jit = !$this->getOption('disable-opcache-jit', false);
+ $opcache_jit_arg = $opcache_jit ? '--enable-opcache-jit=yes ' : '--enable-opcache-jit=no ';
+
if (($logo = $this->getOption('with-micro-logo')) !== null) {
// realpath
// $logo = realpath($logo);
@@ -115,6 +118,7 @@ class WindowsBuilder extends BuilderBase
($enableMicro ? ('--enable-micro=yes ' . $micro_logo . $micro_w32) : '--enable-micro=no ') .
($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') .
$config_file_scan_dir .
+ $opcache_jit_arg .
"{$this->makeStaticExtensionArgs()} " .
$zts .
'"'
diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php
index e914c70d..229b4e89 100644
--- a/src/SPC/command/BuildPHPCommand.php
+++ b/src/SPC/command/BuildPHPCommand.php
@@ -159,7 +159,7 @@ class BuildPHPCommand extends BuildCommand
$indent_texts['UPX Pack'] = 'enabled';
}
- $ver = $builder->getPHPVersionFromArchive() ?: $builder->getPHPVersion();
+ $ver = $builder->getPHPVersionFromArchive() ?: $builder->getPHPVersion(false);
$indent_texts['PHP Version'] = $ver;
if (!empty($not_included)) {
@@ -269,6 +269,7 @@ class BuildPHPCommand extends BuildCommand
} catch (WrongUsageException $e) {
// WrongUsageException is not an exception, it's a user error, so we just print the error message
logger()->critical($e->getMessage());
+ logger()->error($e->getTraceAsString());
return static::FAILURE;
} catch (\Throwable $e) {
if ($this->getOption('debug')) {
diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php
index dfa4c9ee..ddcb50ae 100644
--- a/src/SPC/command/DownloadCommand.php
+++ b/src/SPC/command/DownloadCommand.php
@@ -108,12 +108,9 @@ class DownloadCommand extends BaseCommand
// Define PHP major version
$ver = $this->php_major_ver = $this->getOption('with-php');
define('SPC_BUILD_PHP_VERSION', $ver);
- // match x.y
- preg_match('/^\d+\.\d+$/', $ver, $matches);
- if (!$matches) {
- // match x.y.z
- preg_match('/^\d+\.\d+\.\d+$/', $ver, $matches);
- if (!$matches) {
+ if ($ver !== 'git' && !preg_match('/^\d+\.\d+$/', $ver)) {
+ // If not git, we need to check the version format
+ if (!preg_match('/^\d+\.\d+(\.\d+)?$/', $ver)) {
logger()->error("bad version arg: {$ver}, x.y or x.y.z required!");
return static::FAILURE;
}
diff --git a/src/SPC/command/dev/LibVerCommand.php b/src/SPC/command/dev/LibVerCommand.php
index 717fd155..246618e4 100644
--- a/src/SPC/command/dev/LibVerCommand.php
+++ b/src/SPC/command/dev/LibVerCommand.php
@@ -8,6 +8,7 @@ use SPC\builder\BuilderProvider;
use SPC\command\BaseCommand;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
+use SPC\util\DependencyUtil;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -41,7 +42,10 @@ class LibVerCommand extends BaseCommand
return static::FAILURE;
}
- $builder->proveLibs([$this->getArgument('library')]);
+ // parse the dependencies
+ [, $libs] = DependencyUtil::getExtsAndLibs([], [$this->getArgument('library')]);
+
+ $builder->proveLibs($libs);
// Check whether lib is extracted
if (!is_dir(SOURCE_PATH . '/' . $this->getArgument('library'))) {
@@ -51,7 +55,7 @@ class LibVerCommand extends BaseCommand
$version = $builder->getLib($this->getArgument('library'))->getLibVersion();
if ($version === null) {
- $this->output->writeln("Failed to get version of library {$this->getArgument('library')}");
+ $this->output->writeln("Failed to get version of library {$this->getArgument('library')}. The version getter for [{$this->getArgument('library')}] is not implemented.");
return static::FAILURE;
}
$this->output->writeln("{$version}");
diff --git a/src/SPC/doctor/AsCheckItem.php b/src/SPC/doctor/AsCheckItem.php
index f64d914b..0fa7466f 100644
--- a/src/SPC/doctor/AsCheckItem.php
+++ b/src/SPC/doctor/AsCheckItem.php
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace SPC\doctor;
-#[\Attribute(\Attribute::TARGET_METHOD)]
+#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class AsCheckItem
{
public mixed $callback = null;
diff --git a/src/SPC/doctor/item/LinuxToolCheckList.php b/src/SPC/doctor/item/LinuxToolCheckList.php
index b5c9cb03..58ad8a31 100644
--- a/src/SPC/doctor/item/LinuxToolCheckList.php
+++ b/src/SPC/doctor/item/LinuxToolCheckList.php
@@ -16,7 +16,7 @@ class LinuxToolCheckList
use UnixSystemUtilTrait;
public const TOOLS_ALPINE = [
- 'make', 'bison', 'flex',
+ 'make', 'bison', 're2c', 'flex',
'git', 'autoconf', 'automake', 'gettext-dev',
'tar', 'unzip', 'gzip',
'bzip2', 'cmake', 'gcc',
@@ -26,7 +26,7 @@ class LinuxToolCheckList
];
public const TOOLS_DEBIAN = [
- 'make', 'bison', 'flex',
+ 'make', 'bison', 're2c', 'flex',
'git', 'autoconf', 'automake', 'autopoint',
'tar', 'unzip', 'gzip',
'bzip2', 'cmake', 'patch',
@@ -35,7 +35,7 @@ class LinuxToolCheckList
];
public const TOOLS_RHEL = [
- 'perl', 'make', 'bison', 'flex',
+ 'perl', 'make', 'bison', 're2c', 'flex',
'git', 'autoconf', 'automake',
'tar', 'unzip', 'gzip', 'gcc',
'bzip2', 'cmake', 'patch', 'which',
diff --git a/src/SPC/doctor/item/MacOSToolCheckList.php b/src/SPC/doctor/item/MacOSToolCheckList.php
index b4043a1d..249b618e 100644
--- a/src/SPC/doctor/item/MacOSToolCheckList.php
+++ b/src/SPC/doctor/item/MacOSToolCheckList.php
@@ -19,6 +19,7 @@ class MacOSToolCheckList
'curl',
'make',
'bison',
+ 're2c',
'flex',
'pkg-config',
'git',
@@ -61,6 +62,30 @@ class MacOSToolCheckList
return CheckResult::ok();
}
+ #[AsCheckItem('if bison version is 3.0 or later', limit_os: 'Darwin')]
+ public function checkBisonVersion(array $command_path = []): ?CheckResult
+ {
+ // if the bison command is /usr/bin/bison, it is the system bison that may be too old
+ if (($bison = $this->findCommand('bison', $command_path)) === null) {
+ return CheckResult::fail('bison is not installed or too old', 'build-tools', [['bison']]);
+ }
+ // check version: bison (GNU Bison) x.y(.z)
+ $version = shell()->execWithResult("{$bison} --version", false);
+ if (preg_match('/bison \(GNU Bison\) (\d+)\.(\d+)(?:\.(\d+))?/', $version[1][0], $matches)) {
+ $major = (int) $matches[1];
+ // major should be 3 or later
+ if ($major < 3) {
+ // find homebrew keg-only bison
+ if ($command_path !== []) {
+ return CheckResult::fail("Current {$bison} version is too old: " . $matches[0]);
+ }
+ return $this->checkBisonVersion(['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin']);
+ }
+ return CheckResult::ok($matches[0]);
+ }
+ return CheckResult::fail('bison version cannot be determined');
+ }
+
#[AsFixItem('brew')]
public function fixBrew(): bool
{
diff --git a/src/SPC/doctor/item/Re2cVersionCheck.php b/src/SPC/doctor/item/Re2cVersionCheck.php
new file mode 100644
index 00000000..1c130379
--- /dev/null
+++ b/src/SPC/doctor/item/Re2cVersionCheck.php
@@ -0,0 +1,53 @@
+= 1.0.3', limit_os: 'Linux', level: 20)]
+ #[AsCheckItem('if re2c version >= 1.0.3', limit_os: 'Darwin', level: 20)]
+ public function checkRe2cVersion(): ?CheckResult
+ {
+ $ver = shell(false)->execWithResult('re2c --version', false);
+ // match version: re2c X.X(.X)
+ if ($ver[0] !== 0 || !preg_match('/re2c\s+(\d+\.\d+(\.\d+)?)/', $ver[1][0], $matches)) {
+ return CheckResult::fail('Failed to get re2c version', 'build-re2c');
+ }
+ $version_string = $matches[1];
+ if (version_compare($version_string, '1.0.3') < 0) {
+ return CheckResult::fail('re2c version is too low (' . $version_string . ')', 'build-re2c');
+ }
+ return CheckResult::ok($version_string);
+ }
+
+ #[AsFixItem('build-re2c')]
+ public function buildRe2c(): bool
+ {
+ try {
+ Downloader::downloadSource('re2c');
+ } catch (DownloaderException) {
+ logger()->warning('Failed to download re2c version, trying alternative');
+ $alt = Config::getSource('re2c');
+ $alt = [...$alt, ...$alt['alt'] ?? []];
+ Downloader::downloadSource('re2c', $alt);
+ }
+ $builder = BuilderProvider::makeBuilderByInput(new ArgvInput([]));
+ $builder->proveLibs(['re2c']);
+ $builder->setupLibs();
+ return true;
+ }
+}
diff --git a/src/SPC/exception/ExceptionHandler.php b/src/SPC/exception/ExceptionHandler.php
index 544575dc..d103b957 100644
--- a/src/SPC/exception/ExceptionHandler.php
+++ b/src/SPC/exception/ExceptionHandler.php
@@ -10,20 +10,6 @@ class ExceptionHandler
private static ?ExceptionHandler $obj = null;
- private function __construct()
- {
- $whoops_class = 'Whoops\Run';
- $collision_class = 'NunoMaduro\Collision\Handler';
- if (class_exists($collision_class) && class_exists($whoops_class)) {
- /* @phpstan-ignore-next-line */
- $this->whoops = new $whoops_class();
- $this->whoops->allowQuit(false);
- $this->whoops->writeToOutput(false);
- $this->whoops->pushHandler(new $collision_class());
- $this->whoops->register();
- }
- }
-
public static function getInstance(): ExceptionHandler
{
if (self::$obj === null) {
@@ -34,13 +20,8 @@ class ExceptionHandler
public function handle(\Throwable $e): void
{
- if (is_null($this->whoops)) {
- logger()->error('Uncaught ' . get_class($e) . ': ' . $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')');
- logger()->error($e->getTraceAsString());
- return;
- }
- $this->whoops->handleException($e);
-
+ logger()->error('Uncaught ' . get_class($e) . ': ' . $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')');
+ logger()->error($e->getTraceAsString());
logger()->critical('You can report this exception to static-php-cli GitHub repo.');
}
}
diff --git a/src/SPC/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php
index 02a64624..85b7ddf1 100644
--- a/src/SPC/store/SourcePatcher.php
+++ b/src/SPC/store/SourcePatcher.php
@@ -7,6 +7,7 @@ namespace SPC\store;
use SPC\builder\BuilderBase;
use SPC\builder\linux\SystemUtil;
use SPC\builder\unix\UnixBuilderBase;
+use SPC\builder\windows\WindowsBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
@@ -68,6 +69,35 @@ class SourcePatcher
);
}
+ // Fix PHP VS version
+ if ($builder instanceof WindowsBuilder) {
+ // get vs version
+ $vc = \SPC\builder\windows\SystemUtil::findVisualStudio();
+ $vc_matches = match ($vc['version']) {
+ 'vs17' => ['VS17', 'Visual C++ 2022'],
+ 'vs16' => ['VS16', 'Visual C++ 2019'],
+ default => ['unknown', 'unknown'],
+ };
+ // patch php-src/win32/build/confutils.js
+ FileSystem::replaceFileStr(
+ SOURCE_PATH . '\php-src\win32\build\confutils.js',
+ 'var name = "unknown";',
+ "var name = short ? \"{$vc_matches[0]}\" : \"{$vc_matches[1]}\";return name;"
+ );
+ }
+
+ // patch configure.ac
+ $musl = SPCTarget::getLibc() === 'musl';
+ FileSystem::backupFile(SOURCE_PATH . '/php-src/configure.ac');
+ FileSystem::replaceFileStr(
+ SOURCE_PATH . '/php-src/configure.ac',
+ 'if command -v ldd >/dev/null && ldd --version 2>&1 | grep ^musl >/dev/null 2>&1',
+ 'if ' . ($musl ? 'true' : 'false')
+ );
+ if (getenv('SPC_LIBC') === false && ($libc = SPCTarget::getLibc()) !== null) {
+ putenv("SPC_LIBC={$libc}");
+ }
+
// patch php-src/build/php.m4 PKG_CHECK_MODULES -> PKG_CHECK_MODULES_STATIC
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/build/php.m4', 'PKG_CHECK_MODULES(', 'PKG_CHECK_MODULES_STATIC(');
@@ -87,7 +117,8 @@ class SourcePatcher
public static function patchBeforeConfigure(BuilderBase $builder): void
{
foreach ($builder->getExts() as $ext) {
- if ($ext->patchBeforeConfigure() === true) {
+ $patch = $builder instanceof WindowsBuilder ? $ext->patchBeforeWindowsConfigure() : $ext->patchBeforeConfigure();
+ if ($patch === true) {
logger()->info("Extension [{$ext->getName()}] patched before configure");
}
}
@@ -97,7 +128,14 @@ class SourcePatcher
}
}
// patch capstone
- FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/configure', '/have_capstone="yes"/', 'have_capstone="no"');
+ if (is_unix()) {
+ FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/configure', '/have_capstone="yes"/', 'have_capstone="no"');
+ }
+
+ if (file_exists(SOURCE_PATH . '/php-src/configure.ac.bak')) {
+ // restore configure.ac
+ FileSystem::restoreBackupFile(SOURCE_PATH . '/php-src/configure.ac');
+ }
}
/**
@@ -132,7 +170,7 @@ class SourcePatcher
}
$patch_list = $spc_micro_patches;
$patches = [];
- $serial = ['80', '81', '82', '83', '84'];
+ $serial = ['80', '81', '82', '83', '84', '85'];
foreach ($patch_list as $patchName) {
if (file_exists(SOURCE_PATH . "/php-src/sapi/micro/patches/{$patchName}.patch")) {
$patches[] = "sapi/micro/patches/{$patchName}.patch";
@@ -584,7 +622,7 @@ class SourcePatcher
*/
public static function patchSPCVersionToPHP(string $version = 'unknown'): void
{
- // detect patch
+ // detect patch (remove this when 8.3 deprecated)
$file = FileSystem::readFile(SOURCE_PATH . '/php-src/main/main.c');
if (!str_contains($file, 'static-php-cli.version')) {
logger()->debug('Inserting static-php-cli.version to php-src');
diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php
index d6807cd1..72a16849 100644
--- a/src/SPC/store/source/PhpSource.php
+++ b/src/SPC/store/source/PhpSource.php
@@ -21,8 +21,14 @@ class PhpSource extends CustomSourceBase
*/
public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void
{
- $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.3';
- Downloader::downloadSource('php-src', $this->getLatestPHPInfo($major), $force);
+ $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4';
+ if ($major === '8.5') {
+ Downloader::downloadSource('php-src', ['type' => 'url', 'url' => 'https://downloads.php.net/~daniels/php-8.5.0alpha4.tar.xz'], $force);
+ } elseif ($major === 'git') {
+ Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force);
+ } else {
+ Downloader::downloadSource('php-src', self::getLatestPHPInfo($major), $force);
+ }
}
/**
@@ -36,7 +42,7 @@ class PhpSource extends CustomSourceBase
// 查找最新的小版本号
$info = json_decode(Downloader::curlExec(
url: "https://www.php.net/releases/index.php?json&version={$major_version}",
- retries: (int) getenv('SPC_DOWNLOAD_RETRIES')
+ retries: intval(getenv('SPC_DOWNLOAD_RETRIES') ?: 0)
), true);
if (!isset($info['version'])) {
throw new DownloaderException("Version {$major_version} not found.");
diff --git a/src/SPC/toolchain/ClangNativeToolchain.php b/src/SPC/toolchain/ClangNativeToolchain.php
index aef844d7..6cccb4af 100644
--- a/src/SPC/toolchain/ClangNativeToolchain.php
+++ b/src/SPC/toolchain/ClangNativeToolchain.php
@@ -7,6 +7,7 @@ namespace SPC\toolchain;
use SPC\builder\freebsd\SystemUtil as FreeBSDSystemUtil;
use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
use SPC\builder\macos\SystemUtil as MacOSSystemUtil;
+use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\util\GlobalEnvManager;
@@ -37,8 +38,19 @@ class ClangNativeToolchain implements ToolchainInterface
'Linux' => LinuxSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."),
'Darwin' => MacOSSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."),
'BSD' => FreeBSDSystemUtil::findCommand($command) ?? throw new WrongUsageException("{$command} not found, please install it or set {$env} to a valid path."),
- default => throw new \RuntimeException(__CLASS__ . ' is not supported on ' . PHP_OS_FAMILY . '.'),
+ default => throw new RuntimeException(__CLASS__ . ' is not supported on ' . PHP_OS_FAMILY . '.'),
};
}
}
+
+ public function getCompilerInfo(): ?string
+ {
+ $compiler = getenv('CC') ?: 'clang';
+ $version = shell(false)->execWithResult("{$compiler} --version", false);
+ $head = pathinfo($compiler, PATHINFO_BASENAME);
+ if ($version[0] === 0 && preg_match('/clang version (\d+.\d+.\d+)/', $version[1][0], $match)) {
+ return "{$head} {$match[1]}";
+ }
+ return $head;
+ }
}
diff --git a/src/SPC/toolchain/GccNativeToolchain.php b/src/SPC/toolchain/GccNativeToolchain.php
index 1ada28b5..05fe026a 100644
--- a/src/SPC/toolchain/GccNativeToolchain.php
+++ b/src/SPC/toolchain/GccNativeToolchain.php
@@ -35,4 +35,15 @@ class GccNativeToolchain implements ToolchainInterface
};
}
}
+
+ public function getCompilerInfo(): ?string
+ {
+ $compiler = getenv('CC') ?: 'gcc';
+ $version = shell(false)->execWithResult("{$compiler} --version", false);
+ $head = pathinfo($compiler, PATHINFO_BASENAME);
+ if ($version[0] === 0 && preg_match('/gcc.*(\d+.\d+.\d+)/', $version[1][0], $match)) {
+ return "{$head} {$match[1]}";
+ }
+ return $head;
+ }
}
diff --git a/src/SPC/toolchain/MSVCToolchain.php b/src/SPC/toolchain/MSVCToolchain.php
index 567cff38..55559e8a 100644
--- a/src/SPC/toolchain/MSVCToolchain.php
+++ b/src/SPC/toolchain/MSVCToolchain.php
@@ -9,4 +9,9 @@ class MSVCToolchain implements ToolchainInterface
public function initEnv(): void {}
public function afterInit(): void {}
+
+ public function getCompilerInfo(): ?string
+ {
+ return null;
+ }
}
diff --git a/src/SPC/toolchain/MuslToolchain.php b/src/SPC/toolchain/MuslToolchain.php
index e996ef1f..684473c6 100644
--- a/src/SPC/toolchain/MuslToolchain.php
+++ b/src/SPC/toolchain/MuslToolchain.php
@@ -36,4 +36,15 @@ class MuslToolchain implements ToolchainInterface
throw new WrongUsageException('You are building with musl-libc target in glibc distro, but musl-toolchain is not installed, please install musl-toolchain first. (You can use `doctor` command to install it)');
}
}
+
+ public function getCompilerInfo(): ?string
+ {
+ $compiler = getenv('CC') ?: getenv('SPC_LINUX_DEFAULT_CC');
+ $version = shell(false)->execWithResult("{$compiler} --version", false);
+ $head = pathinfo($compiler, PATHINFO_BASENAME);
+ if ($version[0] === 0 && preg_match('/linux-musl-cc.*(\d+.\d+.\d+)/', $version[1][0], $match)) {
+ return "{$head} {$match[1]}";
+ }
+ return $head;
+ }
}
diff --git a/src/SPC/toolchain/ToolchainInterface.php b/src/SPC/toolchain/ToolchainInterface.php
index 62cf2b2d..a08fb869 100644
--- a/src/SPC/toolchain/ToolchainInterface.php
+++ b/src/SPC/toolchain/ToolchainInterface.php
@@ -27,4 +27,12 @@ interface ToolchainInterface
* post-initialization setup or validation.
*/
public function afterInit(): void;
+
+ /**
+ * Returns the compiler name and version for toolchains.
+ *
+ * If the toolchain does not support compiler information,
+ * this method can return null.
+ */
+ public function getCompilerInfo(): ?string;
}
diff --git a/src/SPC/toolchain/ToolchainManager.php b/src/SPC/toolchain/ToolchainManager.php
index 0c92c58a..9672977d 100644
--- a/src/SPC/toolchain/ToolchainManager.php
+++ b/src/SPC/toolchain/ToolchainManager.php
@@ -61,6 +61,10 @@ class ToolchainManager
}
$toolchain = getenv('SPC_TOOLCHAIN');
/* @var ToolchainInterface $toolchain */
- (new $toolchain())->afterInit();
+ $instance = new $toolchain();
+ $instance->afterInit();
+ if (getenv('PHP_BUILD_COMPILER') === false && ($compiler_info = $instance->getCompilerInfo())) {
+ GlobalEnvManager::putenv("PHP_BUILD_COMPILER={$compiler_info}");
+ }
}
}
diff --git a/src/SPC/toolchain/ZigToolchain.php b/src/SPC/toolchain/ZigToolchain.php
index 613c0f97..93d004a5 100644
--- a/src/SPC/toolchain/ZigToolchain.php
+++ b/src/SPC/toolchain/ZigToolchain.php
@@ -68,4 +68,10 @@ class ZigToolchain implements ToolchainInterface
GlobalEnvManager::putenv("SPC_EXTRA_LIBS={$extra_libs}");
}
}
+
+ public function getCompilerInfo(): ?string
+ {
+ $version = shell(false)->execWithResult('zig version', false)[1][0] ?? '';
+ return trim("zig {$version}");
+ }
}
diff --git a/src/SPC/util/ConfigValidator.php b/src/SPC/util/ConfigValidator.php
index cfb9c2c2..baa42b59 100644
--- a/src/SPC/util/ConfigValidator.php
+++ b/src/SPC/util/ConfigValidator.php
@@ -47,20 +47,20 @@ class ConfigValidator
// check if license is valid
if (isset($src['license'])) {
- if (!is_assoc_array($src['license'])) {
- throw new ValidationException("source {$name} license must be object");
+ if (!is_array($src['license'])) {
+ throw new ValidationException("source {$name} license must be an object or array");
}
- if (!isset($src['license']['type'])) {
- throw new ValidationException("source {$name} license must have type");
- }
- if (!in_array($src['license']['type'], ['file', 'text'])) {
- throw new ValidationException("source {$name} license type is invalid");
- }
- if ($src['license']['type'] === 'file' && !isset($src['license']['path'])) {
- throw new ValidationException("source {$name} license file must have path");
- }
- if ($src['license']['type'] === 'text' && !isset($src['license']['text'])) {
- throw new ValidationException("source {$name} license text must have text");
+ if (is_assoc_array($src['license'])) {
+ self::checkSingleLicense($src['license'], $name);
+ } elseif (is_list_array($src['license'])) {
+ foreach ($src['license'] as $license) {
+ if (!is_assoc_array($license)) {
+ throw new ValidationException("source {$name} license must be an object or array");
+ }
+ self::checkSingleLicense($license, $name);
+ }
+ } else {
+ throw new ValidationException("source {$name} license must be an object or array");
}
}
}
@@ -415,6 +415,31 @@ class ConfigValidator
return $craft;
}
+ /**
+ * @throws ValidationException
+ */
+ private static function checkSingleLicense(array $license, string $name): void
+ {
+ if (!is_assoc_array($license)) {
+ throw new ValidationException("source {$name} license must be an object");
+ }
+ if (!isset($license['type'])) {
+ throw new ValidationException("source {$name} license must have type");
+ }
+ if (!in_array($license['type'], ['file', 'text'])) {
+ throw new ValidationException("source {$name} license type is invalid");
+ }
+ if (!in_array($license['type'], ['file', 'text'])) {
+ throw new ValidationException("source {$name} license type is invalid");
+ }
+ if ($license['type'] === 'file' && !isset($license['path'])) {
+ throw new ValidationException("source {$name} license file must have path");
+ }
+ if ($license['type'] === 'text' && !isset($license['text'])) {
+ throw new ValidationException("source {$name} license text must have text");
+ }
+ }
+
/**
* Validate source type configuration (shared between source.json and pkg.json)
*
diff --git a/src/SPC/util/GlobalEnvManager.php b/src/SPC/util/GlobalEnvManager.php
index f5dafea2..9d756d7e 100644
--- a/src/SPC/util/GlobalEnvManager.php
+++ b/src/SPC/util/GlobalEnvManager.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace SPC\util;
+use SPC\builder\macos\SystemUtil;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\toolchain\ToolchainManager;
@@ -117,6 +118,15 @@ class GlobalEnvManager
if (!filter_var(getenv('SPC_SKIP_TOOLCHAIN_CHECK'), FILTER_VALIDATE_BOOL)) {
ToolchainManager::afterInitToolchain();
}
+ // test bison
+ if (PHP_OS_FAMILY === 'Darwin') {
+ if ($bison = SystemUtil::findCommand('bison', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin'])) {
+ self::putenv("BISON={$bison}");
+ }
+ if ($yacc = SystemUtil::findCommand('yacc', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin'])) {
+ self::putenv("YACC={$yacc}");
+ }
+ }
}
/**
diff --git a/src/SPC/util/PkgConfigUtil.php b/src/SPC/util/PkgConfigUtil.php
index fd11df61..8bb7e985 100644
--- a/src/SPC/util/PkgConfigUtil.php
+++ b/src/SPC/util/PkgConfigUtil.php
@@ -14,6 +14,21 @@ use SPC\exception\RuntimeException;
*/
class PkgConfigUtil
{
+ /**
+ * Returns the version of a module.
+ * This method uses `pkg-config --modversion` to get the version of the specified module.
+ * If the module is not found, it will throw a RuntimeException.
+ *
+ * @param string $pkg_config_str .pc file str, accepts multiple files
+ * @return string version string, e.g. "1.2.3"
+ * @throws RuntimeException
+ */
+ public static function getModuleVersion(string $pkg_config_str): string
+ {
+ $result = self::execWithResult("pkg-config --modversion {$pkg_config_str}");
+ return trim($result);
+ }
+
/**
* Get CFLAGS from pkg-config
*
diff --git a/src/SPC/util/SPCTarget.php b/src/SPC/util/SPCTarget.php
index d4164ceb..c6519a9a 100644
--- a/src/SPC/util/SPCTarget.php
+++ b/src/SPC/util/SPCTarget.php
@@ -99,8 +99,11 @@ class SPCTarget
*/
public static function getLibcVersion(): ?string
{
- $libc = self::getLibc();
- return SystemUtil::getLibcVersionIfExists($libc);
+ if (PHP_OS_FAMILY === 'Linux') {
+ $libc = self::getLibc();
+ return SystemUtil::getLibcVersionIfExists($libc);
+ }
+ return null;
}
/**
diff --git a/src/globals/ext-tests/curl.php b/src/globals/ext-tests/curl.php
index 7c3eecf7..16b31e6e 100644
--- a/src/globals/ext-tests/curl.php
+++ b/src/globals/ext-tests/curl.php
@@ -6,13 +6,27 @@ assert(function_exists('curl_init'));
assert(function_exists('curl_setopt'));
assert(function_exists('curl_exec'));
assert(function_exists('curl_close'));
+assert(function_exists('curl_version'));
$curl_version = curl_version();
if (stripos($curl_version['ssl_version'], 'schannel') !== false) {
- $curl = curl_init();
- curl_setopt($curl, CURLOPT_URL, 'https://captive.apple.com/');
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($curl, CURLOPT_HEADER, 0);
- $data = curl_exec($curl);
- curl_close($curl);
- assert($data !== false);
+ $domain_list = [
+ 'https://captive.apple.com/',
+ 'https://detectportal.firefox.com/',
+ 'https://static-php.dev/',
+ 'https://www.example.com/',
+ ];
+ $valid = false;
+ foreach ($domain_list as $domain) {
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_URL, $domain);
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($curl, CURLOPT_HEADER, 0);
+ $data = curl_exec($curl);
+ curl_close($curl);
+ if ($data !== false) {
+ $valid = true;
+ break;
+ }
+ }
+ assert($valid);
}
diff --git a/src/globals/ext-tests/openssl.php b/src/globals/ext-tests/openssl.php
index 0453101a..c687d0fc 100644
--- a/src/globals/ext-tests/openssl.php
+++ b/src/globals/ext-tests/openssl.php
@@ -5,5 +5,32 @@ declare(strict_types=1);
assert(function_exists('openssl_digest'));
assert(openssl_digest('123456', 'md5') === 'e10adc3949ba59abbe56e057f20f883e');
if (file_exists('/etc/ssl/openssl.cnf')) {
- assert(file_get_contents('https://captive.apple.com/') !== false);
+ $domain_list = [
+ 'captive.apple.com',
+ 'detectportal.firefox.com',
+ 'static-php.dev',
+ 'www.example.com',
+ ];
+ $valid = false;
+ foreach ($domain_list as $domain) {
+ $ssloptions = [
+ 'capture_peer_cert' => true,
+ 'capture_peer_cert_chain' => true,
+ 'allow_self_signed' => false,
+ 'CN_match' => $domain,
+ 'verify_peer' => true,
+ 'SNI_enabled' => true,
+ 'SNI_server_name' => $domain,
+ ];
+ $context = stream_context_create(['ssl' => $ssloptions]);
+ $result = stream_socket_client("ssl://{$domain}:443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
+ if ($result !== false) {
+ $valid = true;
+ break;
+ }
+ }
+ assert($valid);
+}
+if (PHP_VERSION_ID >= 80500 && defined('OPENSSL_VERSION_NUMBER') && OPENSSL_VERSION_NUMBER >= 0x30200000) {
+ assert(function_exists('openssl_password_hash'));
}
diff --git a/src/globals/internal-env.php b/src/globals/internal-env.php
index 1046848e..22918fdf 100644
--- a/src/globals/internal-env.php
+++ b/src/globals/internal-env.php
@@ -6,9 +6,12 @@ use SPC\builder\freebsd\SystemUtil as BSDSystemUtil;
use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
use SPC\builder\macos\SystemUtil as MacOSSystemUtil;
use SPC\builder\windows\SystemUtil as WindowsSystemUtil;
+use SPC\ConsoleApplication;
use SPC\store\FileSystem;
use SPC\util\GlobalEnvManager;
+// static-php-cli version string
+const SPC_VERSION = ConsoleApplication::VERSION;
// output path for everything, other paths are defined relative to this by default
define('BUILD_ROOT_PATH', FileSystem::convertPath(is_string($a = getenv('BUILD_ROOT_PATH')) ? $a : (WORKING_DIR . '/buildroot')));
// output path for header files for development
@@ -44,6 +47,7 @@ define('SEPARATED_PATH', [
]);
// add these to env vars with same name
+GlobalEnvManager::putenv('SPC_VERSION=' . SPC_VERSION);
GlobalEnvManager::putenv('BUILD_ROOT_PATH=' . BUILD_ROOT_PATH);
GlobalEnvManager::putenv('BUILD_INCLUDE_PATH=' . BUILD_INCLUDE_PATH);
GlobalEnvManager::putenv('BUILD_LIB_PATH=' . BUILD_LIB_PATH);
diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php
index f47340a0..d311962d 100644
--- a/src/globals/test-extensions.php
+++ b/src/globals/test-extensions.php
@@ -17,15 +17,17 @@ $test_php_version = [
// '8.2',
// '8.3',
'8.4',
+ // '8.5',
+ 'git',
];
// test os (macos-13, macos-14, macos-15, ubuntu-latest, windows-latest are available)
$test_os = [
- 'macos-13', // bin/spc for x86_64
+ // 'macos-13', // bin/spc for x86_64
// 'macos-14', // bin/spc for arm64
- 'macos-15', // bin/spc for arm64
- // 'ubuntu-latest', // bin/spc-alpine-docker for x86_64
- 'ubuntu-22.04', // bin/spc-gnu-docker for x86_64
+ // 'macos-15', // bin/spc for arm64
+ 'ubuntu-latest', // bin/spc-alpine-docker for x86_64
+ // 'ubuntu-22.04', // bin/spc-gnu-docker for x86_64
'ubuntu-24.04', // bin/spc for x86_64
'ubuntu-22.04-arm', // bin/spc-gnu-docker for arm64
'ubuntu-24.04-arm', // bin/spc for arm64
@@ -41,15 +43,15 @@ $no_strip = false;
$upx = false;
// whether to test frankenphp build, only available for macos and linux
-$frankenphp = true;
+$frankenphp = false;
// prefer downloading pre-built packages to speed up the build process
$prefer_pre_built = false;
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
$extensions = match (PHP_OS_FAMILY) {
- 'Linux', 'Darwin' => 'ast,bcmath,calendar,ctype,dba,dom,exif,fileinfo,filter,libxml,mbregex,mbstring,pcntl,phar,posix,session,simplexml,sockets,sodium,tokenizer,xml,xmlreader,xmlwriter,zlib',
- 'Windows' => 'intl',
+ 'Linux', 'Darwin' => 'bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,iconv,xml,mbstring,mbregex,mysqlnd,openssl,pdo,pdo_mysql,pdo_sqlite,phar,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip',
+ 'Windows' => 'bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,iconv,xml,mbstring,mbregex,mysqlnd,openssl,pdo,pdo_mysql,pdo_sqlite,phar,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip',
};
// If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`).
@@ -60,7 +62,7 @@ $shared_extensions = match (PHP_OS_FAMILY) {
};
// If you want to test lib-suggests for all extensions and libraries, set it to true.
-$with_suggested_libs = true;
+$with_suggested_libs = false;
// If you want to test extra libs for extensions, add them below (comma separated, example `libwebp,libavif`). Unnecessary, when $with_suggested_libs is true.
$with_libs = match (PHP_OS_FAMILY) {
@@ -72,7 +74,7 @@ $with_libs = match (PHP_OS_FAMILY) {
// You can use `common`, `bulk`, `minimal` or `none`.
// note: combination is only available for *nix platform. Windows must use `none` combination
$base_combination = match (PHP_OS_FAMILY) {
- 'Linux', 'Darwin' => 'minimal',
+ 'Linux', 'Darwin' => 'none',
'Windows' => 'none',
};
diff --git a/tests/SPC/util/PkgConfigUtilTest.php b/tests/SPC/util/PkgConfigUtilTest.php
index 6d8988fd..64882a54 100644
--- a/tests/SPC/util/PkgConfigUtilTest.php
+++ b/tests/SPC/util/PkgConfigUtilTest.php
@@ -19,6 +19,10 @@ final class PkgConfigUtilTest extends TestCase
public static function setUpBeforeClass(): void
{
+ if (PHP_OS_FAMILY === 'Windows') {
+ // Skip tests on Windows as pkg-config is not typically available
+ self::markTestSkipped('PkgConfigUtil tests are not applicable on Windows.');
+ }
parent::setUpBeforeClass();
// Save original PATH
diff --git a/tests/SPC/util/SPCConfigUtilTest.php b/tests/SPC/util/SPCConfigUtilTest.php
index 7b8baa6c..0c0fbc7e 100644
--- a/tests/SPC/util/SPCConfigUtilTest.php
+++ b/tests/SPC/util/SPCConfigUtilTest.php
@@ -21,6 +21,10 @@ class SPCConfigUtilTest extends TestCase
*/
public static function setUpBeforeClass(): void
{
+ if (PHP_OS_FAMILY === 'Windows') {
+ // Skip tests on Windows as SPCConfigUtil is not applicable
+ self::markTestSkipped('SPCConfigUtil tests are not applicable on Windows.');
+ }
$testdir = WORKING_DIR . '/.configtest';
FileSystem::createDir($testdir);
FileSystem::writeFile($testdir . '/lib.json', file_get_contents(ROOT_DIR . '/config/lib.json'));