mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-18 06:15:37 +08:00
Compare commits
111 Commits
05053b5cd6
...
2.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
085abd635e | ||
|
|
3789c7c335 | ||
|
|
4fb4e42896 | ||
|
|
81e7a0c554 | ||
|
|
4cbe4ea82d | ||
|
|
ea4905cd0d | ||
|
|
3c09ba59d7 | ||
|
|
430f436b79 | ||
|
|
93001dce88 | ||
|
|
2d6d25cadf | ||
|
|
b644da8210 | ||
|
|
f1a9a28ed7 | ||
|
|
643926886c | ||
|
|
9a5a59d876 | ||
|
|
e7fe91faef | ||
|
|
e6cf05ddff | ||
|
|
c3e9a2816b | ||
|
|
0bb8e83d68 | ||
|
|
d0053534ab | ||
|
|
38725c2bb4 | ||
|
|
b8fe70e5ee | ||
|
|
3960a21e05 | ||
|
|
e14301d991 | ||
|
|
65f74044d8 | ||
|
|
f6eba32be5 | ||
|
|
af62e55da8 | ||
|
|
22a8191b97 | ||
|
|
17ff5f63bf | ||
|
|
48a7d814f8 | ||
|
|
2c5cf07e9c | ||
|
|
cace504a63 | ||
|
|
efa47af347 | ||
|
|
1c9fbdb032 | ||
|
|
ac2eb3adae | ||
|
|
d6081ee10f | ||
|
|
8b9b72958e | ||
|
|
34edb6e329 | ||
|
|
9487051796 | ||
|
|
7d0577460f | ||
|
|
6b23f4b34a | ||
|
|
451de4a6f6 | ||
|
|
33d587ee9e | ||
|
|
6b381238fe | ||
|
|
07d66ade85 | ||
|
|
e1c9240698 | ||
|
|
d3e8af23bc | ||
|
|
5f9fe2948c | ||
|
|
4c54d99621 | ||
|
|
72beba76c3 | ||
|
|
1047d471ed | ||
|
|
0496af47df | ||
|
|
0ae4bcbd6f | ||
|
|
c58bc038f4 | ||
|
|
ecaa31f38d | ||
|
|
96a7aabda0 | ||
|
|
be857913cd | ||
|
|
601444d0a5 | ||
|
|
3c972ac905 | ||
|
|
6984c29a3a | ||
|
|
cb0ea67a28 | ||
|
|
fcdb029b10 | ||
|
|
5f33a07f4c | ||
|
|
357c04def0 | ||
|
|
0dfa6e63bd | ||
|
|
af10cacdff | ||
|
|
5f5d934d58 | ||
|
|
fda637bb03 | ||
|
|
76d17ebef0 | ||
|
|
df76ecb60f | ||
|
|
83dae09208 | ||
|
|
430364aacd | ||
|
|
81f359dbcf | ||
|
|
9ed3c8b498 | ||
|
|
72abd63b69 | ||
|
|
ed67393d33 | ||
|
|
e8bc892d8b | ||
|
|
fafe7d5778 | ||
|
|
a968b19be0 | ||
|
|
e9dbeb1e34 | ||
|
|
feaff0a08c | ||
|
|
342192fd67 | ||
|
|
4efb3dfc9a | ||
|
|
b9bec5b526 | ||
|
|
1ae0752933 | ||
|
|
0696acb9c5 | ||
|
|
3087dc86f8 | ||
|
|
973c8f082f | ||
|
|
85e89e4326 | ||
|
|
fb106a3d41 | ||
|
|
fb2d676293 | ||
|
|
944cca709f | ||
|
|
5b4cbbb669 | ||
|
|
a8492f06bb | ||
|
|
b6f673cd68 | ||
|
|
42b4272965 | ||
|
|
5beb083c55 | ||
|
|
cf529adc79 | ||
|
|
5a4da8ed5d | ||
|
|
bd3092ae90 | ||
|
|
76094fd305 | ||
|
|
e1edaa24cc | ||
|
|
d0d51ccf82 | ||
|
|
f0612591be | ||
|
|
5838c87140 | ||
|
|
1ce3ba0f30 | ||
|
|
dc8f7de75a | ||
|
|
d3f8e94fe9 | ||
|
|
88cf0184cc | ||
|
|
b9e096abfa | ||
|
|
ec959f6239 | ||
|
|
5f2459ae43 |
2
.github/workflows/ext-matrix-tests.yml
vendored
2
.github/workflows/ext-matrix-tests.yml
vendored
@@ -82,7 +82,7 @@ jobs:
|
||||
- zlib
|
||||
- zstd
|
||||
php-version:
|
||||
- "8.4"
|
||||
- "git"
|
||||
operating-system:
|
||||
- "ubuntu-latest"
|
||||
#- "macos-13"
|
||||
|
||||
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@@ -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
|
||||
|
||||
|
||||
23
README-zh.md
23
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 官方对该版本的支持情况。
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -104,6 +104,7 @@ RUN apk update; \
|
||||
m4 \
|
||||
make \
|
||||
pkgconfig \
|
||||
re2c \
|
||||
wget \
|
||||
xz \
|
||||
gettext-dev \
|
||||
|
||||
@@ -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 && \
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
437
composer.lock
generated
437
composer.lock
generated
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -90,6 +93,7 @@ LD=${SPC_LINUX_DEFAULT_LD}
|
||||
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
|
||||
SPC_DEFAULT_C_FLAGS="-fPIC -Os"
|
||||
SPC_DEFAULT_CXX_FLAGS="-fPIC -Os"
|
||||
SPC_DEFAULT_LD_FLAGS=""
|
||||
; upx executable path
|
||||
UPX_EXEC=${PKG_ROOT_PATH}/bin/upx
|
||||
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches
|
||||
@@ -123,6 +127,7 @@ CXX=clang++
|
||||
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
|
||||
SPC_DEFAULT_C_FLAGS="--target=${MAC_ARCH}-apple-darwin -Os"
|
||||
SPC_DEFAULT_CXX_FLAGS="--target=${MAC_ARCH}-apple-darwin -Os"
|
||||
SPC_DEFAULT_LD_FLAGS=""
|
||||
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches
|
||||
SPC_MICRO_PATCHES=cli_checks,macos_iconv
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -797,6 +797,12 @@
|
||||
"depot.h"
|
||||
]
|
||||
},
|
||||
"re2c": {
|
||||
"source": "re2c",
|
||||
"bin-unix": [
|
||||
"re2c"
|
||||
]
|
||||
},
|
||||
"readline": {
|
||||
"source": "readline",
|
||||
"static-libs-unix": [
|
||||
|
||||
@@ -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/",
|
||||
@@ -998,6 +1012,7 @@
|
||||
"path": "php-src/ext/swoole",
|
||||
"type": "ghtar",
|
||||
"repo": "swoole/swoole-src",
|
||||
"match": "v6\\.+",
|
||||
"prefer-stable": true,
|
||||
"license": {
|
||||
"type": "file",
|
||||
|
||||
@@ -238,6 +238,7 @@ const availablePhpVersions = [
|
||||
'8.2',
|
||||
'8.3',
|
||||
'8.4',
|
||||
'8.5',
|
||||
];
|
||||
|
||||
const I18N = {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -4,45 +4,48 @@
|
||||
|
||||
## 贡献方法
|
||||
|
||||
如果你有代码或文档想要贡献,需要先了解以下内容。
|
||||
如果你有代码或文档要贡献,以下是你需要首先了解的内容。
|
||||
|
||||
1. 你要贡献什么类型的代码?(新扩展、修复 Bug、安全问题、项目框架优化、文档)
|
||||
2. 如果你贡献了新文件或新片段,你的代码是否经过 `php-cs-fixer` 和 `phpstan` 的检查?
|
||||
3. 在贡献代码前是否充分阅读了 [开发指南](../develop/)?
|
||||
|
||||
如果你可以回答以上问题,并已经对代码做出了修改,可以及时在项目 GitHub 仓库发起 Pull Request。待代码审查完毕后,可根据建议修改代码,或直接合并到主分支。
|
||||
如果你能回答上述问题并对代码进行了修改,可以及时在项目 GitHub 仓库发起 Pull Request。
|
||||
代码审查完成后,可以根据建议修改代码,或直接合并到主分支。
|
||||
|
||||
## 贡献类型
|
||||
|
||||
本项目主要用途是编译静态链接的 PHP 二进制,基于 `symfony/console` 编写了命令行处理功能。在开发之前,如果你对它不够熟悉,
|
||||
可以先查看 [symfony/console 文档](https://symfony.com/doc/current/components/console.html)。
|
||||
本项目的主要目的是编译静态链接的 PHP 二进制文件,命令行处理功能基于 `symfony/console` 编写。
|
||||
在开发之前,如果你对它不够熟悉,请先查看 [symfony/console 文档](https://symfony.com/doc/current/components/console.html)。
|
||||
|
||||
### 安全问题
|
||||
### 安全更新
|
||||
|
||||
因为本项目基本上是属于本地运行的 PHP 项目,一般来说不会存在远程攻击行为。但如果你发现了此类问题,请**不要**在 GitHub 仓库提交 PR 或 Issue,
|
||||
你需要通过 [邮件](mailto:admin@zhamao.me) 的方式联系项目维护者(crazywhalecc)。
|
||||
因为本项目基本上是一个本地运行的 PHP 项目,一般来说不会有远程攻击。
|
||||
但如果你发现此类问题,请**不要**在 GitHub 仓库提交 PR 或 Issue,
|
||||
你需要通过 [邮件](mailto:admin@zhamao.me) 联系项目维护者(crazywhalecc)。
|
||||
|
||||
### 修复 Bug
|
||||
|
||||
修复 Bug 一般不涉及项目结构和框架的修改,所以如果你可以定位到错误代码并直接修复它,请直接提交 PR。
|
||||
修复 Bug 一般不涉及项目结构和框架的修改,所以如果你能定位到错误代码并直接修复它,请直接提交 PR。
|
||||
|
||||
### 新扩展
|
||||
|
||||
对于添加一个新扩展来说,你需要先了解一些本项目的基本结构,以及如何根据现有的逻辑添加新扩展。在本页的下一章节将会详细介绍。
|
||||
对于添加新扩展,你需要了解项目的一些基本结构以及如何根据现有逻辑添加新扩展。
|
||||
这将在本页的下一节中详细介绍。
|
||||
总的来说,你需要:
|
||||
|
||||
1. 评估扩展是否可以内联编译到 PHP 中。
|
||||
2. 评估扩展的依赖库(如果有)是否可以静态编译。
|
||||
3. 写出扩展的依赖库在不同平台编译命令。
|
||||
4. 验证扩展及其依赖库能否与现有扩展和依赖库兼容。
|
||||
5. 验证扩展在 `cli`、`micro`、`fpm`、`embed` 几种 SAPI 中均正常工作。
|
||||
6. 编写文档,加入你的扩展。
|
||||
3. 编写不同平台的库编译命令。
|
||||
4. 验证扩展及其依赖项与现有扩展和依赖项兼容。
|
||||
5. 验证扩展在 `cli`、`micro`、`fpm`、`embed` SAPIs 中正常工作。
|
||||
6. 编写文档并添加你的扩展。
|
||||
|
||||
### 项目框架优化
|
||||
|
||||
如果你已经熟悉 `symfony/console` 的工作原理,并同时要对项目的框架进行一些修改或优化,请先了解以下事情:
|
||||
|
||||
1. 加入扩展不属于项目框架优化,但如果你在加入新的扩展时发现不得不优化框架,则需先对框架本身进行修改,然后再加入扩展。
|
||||
2. 对于一些大规模逻辑修改(例如涉及 LibraryBase、Extension 对象等的修改)时,建议先提交 Issue 或 Draft PR 进行讨论方案。
|
||||
3. 项目早期为纯中文开发项目,代码中存在一部分中文的注释。国际化项目后你可以提交 PR 将这些注释翻译为英语。
|
||||
4. 请不要在代码中提交包含较多无用的代码片段,例如大量未被使用的变量、方法、类、重复写了很多次的代码。
|
||||
1. 添加扩展不属于项目框架优化,但如果你在添加新扩展时发现必须优化框架,则需要先修改框架本身,然后再添加扩展。
|
||||
2. 对于一些大规模逻辑修改(例如涉及 LibraryBase、Extension 对象等的修改),建议先提交 Issue 或 Draft PR 进行讨论。
|
||||
3. 在项目早期,它是一个纯私有开发项目,代码中有一些中文注释。项目国际化后,你可以提交 PR 将这些注释翻译为英语。
|
||||
4. 请不要在代码中提交更多无用的代码片段,例如大量未使用的变量、方法、类以及多次重写的代码。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
开发本项目需要安装部署 PHP 环境,以及一些 PHP 项目常用的扩展和 Composer。
|
||||
|
||||
项目的开发环境和运行环境几乎完全一致,你可以参照 **指南-本地构建** 部分安装系统 PHP 或使用本项目预构建的静态 PHP 作为环境,这里不再赘述。
|
||||
项目的开发环境和运行环境几乎完全一致。你可以参照 **手动构建** 部分安装系统 PHP 或使用本项目预构建的静态 PHP 作为环境。这里不再赘述。
|
||||
|
||||
抛开用途,本项目本身其实就是一个 `php-cli` 程序,你可以将它当作一个正常的 PHP 项目进行编辑和开发,同时你需要了解不同系统的 Shell 命令行。
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
curl,dom,filter,mbstring,openssl,pcntl,phar,posix,sodium,tokenizer,xml,xmlwriter
|
||||
```
|
||||
|
||||
static-php-cli 项目本身不需要这么多扩展,但在开发过程中,你会用到 Composer、PHPUnit 等工具,它们需要这些扩展。
|
||||
static-php-cli 项目本身不需要这么多扩展,但在开发过程中,你会用到 Composer 和 PHPUnit 等工具,它们需要这些扩展。
|
||||
|
||||
> 对于 static-php-cli 自身构建的 micro 自执行二进制,仅需要 `pcntl,posix,mbstring,tokenizer,phar`。
|
||||
|
||||
## 开始开发
|
||||
|
||||
继续向下查看项目结构的文档,你可以从中了解 `static-php-cli` 是如何运作的。
|
||||
继续向下查看项目结构文档,你可以学习 `static-php-cli` 是如何工作的。
|
||||
|
||||
@@ -4,28 +4,28 @@
|
||||
|
||||
## php.ini 的路径是什么?
|
||||
|
||||
在 Linux、macOS 和 FreeBSD 上,`php.ini` 的默认路径是 `/usr/local/etc/php/php.ini`。
|
||||
在 Linux、macOS 和 FreeBSD 上,`php.ini` 的路径是 `/usr/local/etc/php/php.ini`。
|
||||
在 Windows 中,路径是 `C:\windows\php.ini` 或 `php.exe` 所在的当前目录。
|
||||
可以在 *nix 系统中使用手动构建选项 `--with-config-file-path` 来更改查找 `php.ini` 的目录。
|
||||
|
||||
此外,在 Linux、macOS 和 FreeBSD 上,`/usr/local/etc/php/conf.d` 目录中的 `*.ini` 文件也会被加载。
|
||||
此外,在 Linux、macOS 和 FreeBSD 上,`/usr/local/etc/php/conf.d` 目录中的 `.ini` 文件也会被加载。
|
||||
在 Windows 中,该路径默认为空。
|
||||
可以使用手动构建选项 `--with-config-file-scan-dir` 更改该目录。
|
||||
|
||||
PHP 默认也会从 [其他标准位置](https://www.php.net/manual/zh/configuration.file.php) 中搜索 `php.ini`。
|
||||
|
||||
## 静态编译的 PHP 可以安装扩展吗
|
||||
## 静态编译的 PHP 可以安装扩展吗?
|
||||
|
||||
因为传统架构下的 PHP 安装扩展的原理是使用 `.so` 类型的动态链接的库方式安装新扩展,而使用本项目编译的静态链接的 PHP。但是静态链接在不同操作系统有不同的定义。
|
||||
|
||||
首先对于 Linux 系统来说,静态链接的二进制文件是不会链接系统的动态链接库的,纯静态链接的二进制无法加载动态库,所以无法添加新的扩展。
|
||||
同时,在纯静态模式下你也不能使用 `ffi` 等扩展加载外部的 `.so` 模块。
|
||||
首先,对于 Linux 系统,静态链接的二进制文件不会链接系统的动态链接库。纯静态链接的二进制文件(`-all-static`)无法加载动态库,因此无法添加新扩展。
|
||||
同时,在纯静态模式下,你也不能使用 `ffi` 等扩展来加载外部 `.so` 模块。
|
||||
|
||||
你可以通过命令 `ldd buildroot/bin/php` 来查看你在 Linux 下构建的二进制是否为纯静态链接的。
|
||||
你可以使用命令 `ldd buildroot/bin/php` 来检查你在 Linux 下构建的二进制文件是否为纯静态链接。
|
||||
|
||||
如果你 [构建 GNU libc 兼容的 PHP](../guide/build-with-glibc),你可以使用 `ffi` 扩展加载外部的 `.so` 模块,并且加载具有相同 ABI 的 `.so` 扩展。
|
||||
如果你 [构建基于 GNU libc 的 PHP](../guide/build-with-glibc),你可以使用 `ffi` 扩展来加载外部 `.so` 模块,并加载具有相同 ABI 的 `.so` 扩展。
|
||||
|
||||
例如,你可以使用以下命令构建一个 glibc 动态链接的静态 PHP 二进制,同时支持 FFI 扩展和加载相同 PHP 版本和相同 TS 类型的 `xdebug.so` 扩展:
|
||||
例如,你可以使用以下命令构建一个与 glibc 动态链接的静态 PHP 二进制文件,支持 FFI 扩展并加载相同 PHP 版本和相同 TS 类型的 `xdebug.so` 扩展:
|
||||
|
||||
```bash
|
||||
bin/spc-gnu-docker download --for-extensions=ffi,xml --with-php=8.4
|
||||
@@ -34,14 +34,14 @@ bin/spc-gnu-docker build ffi,xml --build-cli --debug
|
||||
buildroot/bin/php -d "zend_extension=/path/to/php{PHP_VER}-{ts/nts}/xdebug.so" --ri xdebug
|
||||
```
|
||||
|
||||
对于 macOS 平台来说,macOS 下的几乎所有二进制文件都无法真正纯静态链接,几乎所有二进制文件都会链接 macOS 的系统库:`/usr/lib/libresolv.9.dylib` 和 `/usr/lib/libSystem.B.dylib`。
|
||||
因此,在 macOS 上,您可以直接构建出使用静态编译的 PHP 二进制文件和动态链接的扩展:
|
||||
对于 macOS 平台,macOS 下的几乎所有二进制文件都无法真正纯静态链接,几乎所有二进制文件都会链接 macOS 系统库:`/usr/lib/libresolv.9.dylib` 和 `/usr/lib/libSystem.B.dylib`。
|
||||
因此,在 macOS 上,你可以**直接**使用 SPC 构建具有动态链接扩展的静态编译 PHP 二进制文件:
|
||||
|
||||
1. 使用 `--build-shared=XXX` 选项构建共享扩展 `xxx.so`。例如:`bin/spc build bcmath,zlib --build-shared=xdebug --build-cli`
|
||||
2. 您将获得 `buildroot/modules/xdebug.so` 和 `buildroot/bin/php`。
|
||||
2. 你将获得 `buildroot/modules/xdebug.so` 和 `buildroot/bin/php`。
|
||||
3. `xdebug.so` 文件可用于版本和线程安全相同的 php。
|
||||
|
||||
## 可以支持 Oracle 数据库扩展吗
|
||||
## 可以支持 Oracle 数据库扩展吗?
|
||||
|
||||
部分依赖库闭源的扩展,如 `oci8`、`sourceguardian` 等,它们没有提供纯静态编译的依赖库文件(`.a`),仅提供了动态依赖库文件(`.so`),
|
||||
这些扩展无法使用源码的形式编译到 static-php-cli 中,所以本项目可能永远也不会支持这些扩展。不过,理论上你可以根据上面的问题在 macOS 和 Linux 下接入和使用这类扩展。
|
||||
@@ -49,41 +49,43 @@ buildroot/bin/php -d "zend_extension=/path/to/php{PHP_VER}-{ts/nts}/xdebug.so" -
|
||||
如果你对此类扩展有需求,或者大部分人都对这些闭源扩展使用有需求,
|
||||
可以看看有关 [standalone-php-cli](https://github.com/crazywhalecc/static-php-cli/discussions/58) 的讨论。欢迎留言。
|
||||
|
||||
## 支持 Windows 吗
|
||||
## 支持 Windows 吗?
|
||||
|
||||
该项目目前已支持 Windows,但支持的扩展数量较少,Windows 的支持并不完美,主要有以下几个问题:
|
||||
该项目目前支持 Windows,但支持的扩展数量较少。Windows 支持并不完美。主要有以下问题:
|
||||
|
||||
1. Windows 的编译流程与 *nix 不同,使用的工具链也不同,编译各个扩展的依赖库使用的编译工具也几乎完全不同。
|
||||
2. Windows 版本的需求也会根据所有使用本项目的人的需求推进,如果有很多人需要,我会尽快支持相关扩展。
|
||||
1. Windows 的编译过程与 *nix 不同,使用的工具链也不同。用于编译每个扩展依赖库的编译工具也几乎完全不同。
|
||||
2. Windows 版本的需求也会根据所有使用本项目的人的需求推进。如果很多人需要,我会尽快支持相关扩展。
|
||||
|
||||
## 使用 micro 可以保护我的源码吗
|
||||
## 我可以使用 micro 保护我的源代码吗?
|
||||
|
||||
不可以。micro.sfx 本质上是将 php 和 php 代码结合为一个文件,没有 PHP 代码编译或加密的过程。
|
||||
首先 php-src 是 PHP 代码的官方解释器,而且现在市面上还没有一个能兼容主流分支的 PHP 编译器。
|
||||
之前我在网上看到有一个项目是 BPC(Binary PHP Compiler?)可以把 PHP 编译为二进制,但是限制也是很多很多。
|
||||
不可以。micro.sfx 本质上是将 php 和 php 代码合并为一个文件,没有编译或加密 PHP 代码的过程。
|
||||
|
||||
加密保护代码的方向和编译也不是一回事,编译过后也可以通过逆向工程等方式拿到代码,真正保护还是通过加壳、加密代码等手段进行。
|
||||
首先,php-src 是 PHP 代码的官方解释器,市场上没有与主流分支兼容的 PHP 编译器。
|
||||
我在网上看到一个名为 BPC(Binary PHP Compiler?)的项目可以将 PHP 编译为二进制,但有很多限制。
|
||||
|
||||
所以本项目(static-php-cli)、相关项目(lwmbs、swoole-cli)都是提供一个对 php-src 源码的便捷编译工具,
|
||||
本项目和相关项目引用的 phpmicro 也仅仅是 PHP 的 sapi 接口封装,而不是 PHP 代码的编译工具。
|
||||
PHP 代码的编译器是完全不同的项目,因此不会考虑额外的情况。如果你对加密感兴趣,可以考虑使用现有的加密技术,如 Swoole Compiler、Source Guardian 等。
|
||||
加密和保护代码的方向与编译不同。编译后,也可以通过逆向工程等方法获得代码。真正的保护仍然通过打包和加密代码等手段进行。
|
||||
|
||||
因此,本项目(static-php-cli)和相关项目(lwmbs、swoole-cli)都提供了 php-src 源代码的便捷编译工具。
|
||||
本项目和相关项目引用的 phpmicro 只是 PHP 的 sapi 接口封装,而不是 PHP 代码的编译工具。
|
||||
PHP 代码的编译器是一个完全不同的项目,因此不考虑额外的情况。
|
||||
如果你对加密感兴趣,可以考虑使用现有的加密技术,如 Swoole Compiler、Source Guardian 等。
|
||||
|
||||
## 无法使用 ssl
|
||||
|
||||
**更新:该问题已在最新版本的 static-php-cli 中修复,现在默认读取系统的证书文件。如果你仍然遇到问题,再尝试下方的解决方案。**
|
||||
**更新:该问题已在最新版本的 static-php-cli 中修复,现在默认读取系统的证书文件。如果你仍然遇到问题,请尝试下面的解决方案。**
|
||||
|
||||
使用 curl、pgsql 等 请求 HTTPS 网站或建立 SSL 连接时,可能存在 `error:80000002:system library::No such file or directory` 错误,
|
||||
这个错误是由于静态编译的 PHP 未通过 `php.ini` 指定 `openssl.cafile` 导致的。
|
||||
使用 curl、pgsql 等请求 HTTPS 网站或建立 SSL 连接时,可能会出现 `error:80000002:system library::No such file or directory` 错误。
|
||||
此错误是由于静态编译的 PHP 未通过 `php.ini` 指定 `openssl.cafile` 导致的。
|
||||
|
||||
你可以在使用 PHP 前指定 `php.ini`,并在 INI 内添加 `openssl.cafile=/path/to/your-cert.pem` 来解决这个问题。
|
||||
你可以通过在使用 PHP 前指定 `php.ini` 并在 INI 中添加 `openssl.cafile=/path/to/your-cert.pem` 来解决此问题。
|
||||
|
||||
对于 Linux 系统,你可以从 curl 官方网站下载 [cacert.pem](https://curl.se/docs/caextract.html) 文件,也可以使用系统自带的证书文件。
|
||||
有关不同发行版的证书位置,可参考 [Go 标准库](https://go.dev/src/crypto/x509/root_linux.go)。
|
||||
有关不同发行版的证书位置,请参考 [Golang 文档](https://go.dev/src/crypto/x509/root_linux.go)。
|
||||
|
||||
> INI 配置 `openssl.cafile` 不可以使用 `ini_set()` 函数动态设置,因为 `openssl.cafile` 是一个 `PHP_INI_SYSTEM` 类型的配置,只能在 `php.ini` 文件中设置。
|
||||
> INI 配置 `openssl.cafile` 不能使用 `ini_set()` 函数动态设置,因为 `openssl.cafile` 是 `PHP_INI_SYSTEM` 类型的配置,只能在 `php.ini` 文件中设置。
|
||||
|
||||
## 为什么不支持旧版本 PHP ?
|
||||
## 为什么不支持旧版本的 PHP?
|
||||
|
||||
因为旧版本的 PHP 有很多问题,比如安全问题、性能问题、功能问题等。此外,旧版本的 PHP 很多都无法与最新的依赖库兼容,这也是不支持旧版本 PHP 的原因之一。
|
||||
因为旧版本的 PHP 有很多问题,如安全问题、性能问题和功能问题。此外,许多旧版本的 PHP 与最新的依赖库不兼容,这也是不支持旧版本 PHP 的原因之一。
|
||||
|
||||
你可以使用 static-php-cli 早期编译好的旧版本,如 PHP 8.0,但是不会明确支持早期版本。
|
||||
你可以使用 static-php-cli 早期编译的旧版本,如 PHP 8.0,但不会明确支持早期版本。
|
||||
|
||||
@@ -5,6 +5,7 @@ Action 构建指的是直接使用 GitHub Action 进行编译。
|
||||
如果你不想自行编译,可以从本项目现有的 Action 下载 Artifact,也可以从自托管的服务器下载:[进入](https://dl.static-php.dev/static-php-cli/common/)
|
||||
|
||||
> 自托管的二进制也是由 Action 构建而来,[项目仓库地址](https://github.com/static-php/static-php-cli-hosted)。
|
||||
> 包含的扩展有:bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,gd,gmp,iconv,xml,mbstring,mbregex,mysqlnd,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,redis,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip
|
||||
|
||||
## 构建方法
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ static-php-cli 是一个用于构建静态编译的 PHP 二进制的工具,目
|
||||
|
||||
在指南章节中,你将了解到如何使用 static-php-cli 构建独立的 php 程序。
|
||||
|
||||
- [Action 构建](./action-build)
|
||||
- [本地构建](./manual-build)
|
||||
- [Action 构建](./action-build)
|
||||
- [扩展列表](./extensions)
|
||||
|
||||
## 编译环境
|
||||
@@ -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 版本,以获得更好的体验。
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -10,20 +10,22 @@
|
||||
当下载资源时,你可能最终会看到类似 `curl: (56) The requested URL returned error: 403` 的错误,这通常是由于 GitHub 限制导致的。
|
||||
你可以通过在命令中添加 `--debug` 来验证,会看到类似 `[DEBU] Running command (no output) : curl -sfSL "https://api.github.com/repos/openssl/openssl/releases"` 的输出。
|
||||
|
||||
要解决这个问题,可以在 GitHub 上 [创建](https://github.com/settings/token) 一个个人访问令牌,并将其设置为环境变量 `GITHUB_TOKEN=<XXX>`。
|
||||
要解决这个问题,可以在 GitHub 上 [创建](https://github.com/settings/tokens) 一个个人访问令牌,并将其设置为环境变量 `GITHUB_TOKEN=<XXX>`。
|
||||
|
||||
如果确认地址确实无法正常访问,可以提交 Issue 或 PR 更新地址。
|
||||
如果确认地址确实无法正常访问,可以提交 Issue 或 PR 更新地址或下载类型。
|
||||
|
||||
## doctor 无法修复
|
||||
## Doctor 无法修复某些问题
|
||||
|
||||
在绝大部分情况下,doctor 模块都可以对缺失的系统环境进行自动修复和安装,但也存在特殊的环境无法正常使用自动修复功能。
|
||||
|
||||
部分项目由于系统局限(如 Windows 下无法自动安装 Visual Studio 等软件),无法使用自动修复功能。
|
||||
在遇到无法自动修复功能时,如果遇到 `Some check items can not be fixed` 字样,则表明无法自动修复,请根据终端显示的方法提交 Issue 或自行修复环境。
|
||||
由于系统限制(例如,Windows 下无法自动安装 Visual Studio 等软件),自动修复功能无法用于某些项目。
|
||||
在遇到无法自动修复功能时,如果遇到 `Some check items can not be fixed` 字样,则表明无法自动修复。
|
||||
请根据终端显示的方法提交 Issue 或自行修复环境。
|
||||
|
||||
## 编译错误
|
||||
|
||||
遇到编译错误时,如果没有开启 `--debug` 日志,请先开启调试日志,然后确定报错的命令。
|
||||
报错的终端输出对于修复编译错误非常重要,请在提交 Issue 时一并将终端日志的最后报错片段(或整个终端日志输出)上传,并且包含使用的 `spc` 命令和参数。
|
||||
报错的终端输出对于修复编译错误非常重要。
|
||||
在提交 Issue 时,请上传终端日志的最后报错片段(或整个终端日志输出),并且包含使用的 `spc` 命令和参数。
|
||||
|
||||
如果你是重复构建,请参考 [本地构建 - 多次构建](./manual-build#多次构建) 章节,清理构建缓存后再次构建。
|
||||
如果你是重复构建,请参考 [本地构建 - 多次构建](./manual-build#多次构建) 章节。
|
||||
|
||||
@@ -34,7 +34,7 @@ use Symfony\Component\Console\Application;
|
||||
*/
|
||||
final class ConsoleApplication extends Application
|
||||
{
|
||||
public const VERSION = '2.6.1';
|
||||
public const VERSION = '2.7.0';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -414,11 +422,13 @@ class Extension
|
||||
$this->builder->getOption('with-suggested-libs'),
|
||||
);
|
||||
[$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']);
|
||||
$preStatic = PHP_OS_FAMILY === 'Darwin' ? '' : '-Wl,--start-group ';
|
||||
$postStatic = PHP_OS_FAMILY === 'Darwin' ? '' : ' -Wl,--end-group ';
|
||||
$env = [
|
||||
'CFLAGS' => $config['cflags'],
|
||||
'CXXFLAGS' => $config['cflags'],
|
||||
'LDFLAGS' => $config['ldflags'],
|
||||
'LIBS' => "-Wl,--start-group {$staticLibs} -Wl,--end-group {$sharedLibs}",
|
||||
'LIBS' => clean_spaces("{$preStatic} {$staticLibs} {$postStatic} {$sharedLibs}"),
|
||||
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
|
||||
];
|
||||
if (ToolchainManager::getToolchainClass() === ZigToolchain::class && SPCTarget::getTargetOS() === 'Linux') {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace SPC\builder;
|
||||
|
||||
/**
|
||||
* Interface for library implementations
|
||||
*
|
||||
* This interface defines the basic contract that all library classes must implement.
|
||||
* It provides a common way to identify and work with different library types.
|
||||
*/
|
||||
interface LibraryInterface
|
||||
{
|
||||
/**
|
||||
* Get the name of the library
|
||||
*
|
||||
* @return string The library name
|
||||
*/
|
||||
public function getName(): string;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,12 @@ class readline extends Extension
|
||||
|
||||
public function getUnixConfigureArg(bool $shared = false): string
|
||||
{
|
||||
return '--without-libedit --with-readline=' . BUILD_ROOT_PATH;
|
||||
$enable = '--without-libedit --with-readline=' . BUILD_ROOT_PATH;
|
||||
if ($this->builder->getPHPVersionID() < 84000) {
|
||||
// the check uses `char rl_pending_input()` instead of `extern int rl_pending_input`, which makes LTO fail
|
||||
$enable .= ' ac_cv_lib_readline_rl_pending_input=yes';
|
||||
}
|
||||
return $enable;
|
||||
}
|
||||
|
||||
public function buildUnixShared(): void
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,6 +35,7 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
// cflags
|
||||
$this->arch_c_flags = getenv('SPC_DEFAULT_C_FLAGS');
|
||||
$this->arch_cxx_flags = getenv('SPC_DEFAULT_CXX_FLAGS');
|
||||
$this->arch_ld_flags = getenv('SPC_DEFAULT_LD_FLAGS');
|
||||
|
||||
// create pkgconfig and include dir (some libs cannot create them automatically)
|
||||
f_mkdir(BUILD_LIB_PATH . '/pkgconfig', recursive: true);
|
||||
@@ -65,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 ';
|
||||
@@ -72,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) ?
|
||||
@@ -88,11 +97,12 @@ 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!
|
||||
// 'LIBS' => SPCTarget::getRuntimeLibs(), // do not pass static libraries here yet, they may contain polyfills for libc functions!
|
||||
]);
|
||||
|
||||
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
|
||||
@@ -102,6 +112,7 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
'Change SPC_CMD_VAR_PHP_EMBED_TYPE to static.'
|
||||
);
|
||||
}
|
||||
|
||||
shell()->cd(SOURCE_PATH . '/php-src')
|
||||
->exec(
|
||||
$php_configure_env . ' ' .
|
||||
@@ -112,12 +123,11 @@ 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 .
|
||||
$this->makeStaticExtensionArgs() . ' '
|
||||
// 'ac_cv_lib_readline_rl_pending_input=yes'
|
||||
);
|
||||
|
||||
$this->emitPatchPoint('before-php-make');
|
||||
@@ -257,7 +267,7 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
->exec('sed -i "s|^EXTENSION_DIR = .*|EXTENSION_DIR = /' . basename(BUILD_MODULES_PATH) . '|" Makefile')
|
||||
->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install");
|
||||
|
||||
$ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS');
|
||||
$ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: '';
|
||||
$libDir = BUILD_LIB_PATH;
|
||||
$modulesDir = BUILD_MODULES_PATH;
|
||||
$libphpSo = "{$libDir}/libphp.so";
|
||||
@@ -324,7 +334,8 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
}
|
||||
|
||||
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') {
|
||||
f_passthru('ar -t ' . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 ar d " . BUILD_LIB_PATH . '/libphp.a');
|
||||
$AR = getenv('AR') ?: 'ar';
|
||||
f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a');
|
||||
}
|
||||
|
||||
if (!$this->getOption('no-strip', false) && file_exists(BUILD_LIB_PATH . '/' . $realLibName)) {
|
||||
@@ -333,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'));
|
||||
|
||||
@@ -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');
|
||||
|
||||
15
src/SPC/builder/linux/library/re2c.php
Normal file
15
src/SPC/builder/linux/library/re2c.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\builder\linux\library;
|
||||
|
||||
/**
|
||||
* a template library class for unix
|
||||
*/
|
||||
class re2c extends LinuxLibraryBase
|
||||
{
|
||||
use \SPC\builder\unix\library\re2c;
|
||||
|
||||
public const NAME = 're2c';
|
||||
}
|
||||
@@ -38,6 +38,7 @@ class MacOSBuilder extends UnixBuilderBase
|
||||
// cflags
|
||||
$this->arch_c_flags = getenv('SPC_DEFAULT_C_FLAGS');
|
||||
$this->arch_cxx_flags = getenv('SPC_DEFAULT_CXX_FLAGS');
|
||||
$this->arch_ld_flags = getenv('SPC_DEFAULT_LD_FLAGS');
|
||||
|
||||
// create pkgconfig and include dir (some libs cannot create them automatically)
|
||||
f_mkdir(BUILD_LIB_PATH . '/pkgconfig', recursive: true);
|
||||
@@ -97,9 +98,17 @@ class MacOSBuilder extends UnixBuilderBase
|
||||
$this->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) ?
|
||||
@@ -134,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 .
|
||||
@@ -267,7 +277,8 @@ class MacOSBuilder extends UnixBuilderBase
|
||||
->exec(getenv('SPC_CMD_PREFIX_PHP_MAKE') . ' INSTALL_ROOT=' . BUILD_ROOT_PATH . " {$vars} install");
|
||||
|
||||
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') {
|
||||
f_passthru('ar -t ' . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 ar d " . BUILD_LIB_PATH . '/libphp.a');
|
||||
$AR = getenv('AR') ?: 'ar';
|
||||
f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a');
|
||||
}
|
||||
$this->patchPhpScripts();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ use SPC\store\FileSystem;
|
||||
|
||||
class openssl extends MacOSLibraryBase
|
||||
{
|
||||
use \SPC\builder\traits\openssl;
|
||||
|
||||
public const NAME = 'openssl';
|
||||
|
||||
/**
|
||||
|
||||
12
src/SPC/builder/macos/library/re2c.php
Normal file
12
src/SPC/builder/macos/library/re2c.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\builder\macos\library;
|
||||
|
||||
class re2c extends MacOSLibraryBase
|
||||
{
|
||||
use \SPC\builder\unix\library\re2c;
|
||||
|
||||
public const NAME = 're2c';
|
||||
}
|
||||
@@ -97,16 +97,6 @@ trait UnixLibraryTrait
|
||||
return trim($env);
|
||||
}
|
||||
|
||||
public function getLibExtraLdFlags(): string
|
||||
{
|
||||
return getenv($this->getSnakeCaseName() . '_LDFLAGS') ?: '';
|
||||
}
|
||||
|
||||
public function getLibExtraLibs(): string
|
||||
{
|
||||
return getenv($this->getSnakeCaseName() . '_LIBS') ?: '';
|
||||
}
|
||||
|
||||
public function getLibExtraCXXFlags(): string
|
||||
{
|
||||
$env = getenv($this->getSnakeCaseName() . '_CXXFLAGS') ?: '';
|
||||
@@ -115,4 +105,18 @@ trait UnixLibraryTrait
|
||||
}
|
||||
return trim($env);
|
||||
}
|
||||
|
||||
public function getLibExtraLdFlags(): string
|
||||
{
|
||||
$env = getenv($this->getSnakeCaseName() . '_LDFLAGS') ?: '';
|
||||
if (!str_contains($env, $this->builder->arch_ld_flags)) {
|
||||
$env .= ' ' . $this->builder->arch_ld_flags;
|
||||
}
|
||||
return trim($env);
|
||||
}
|
||||
|
||||
public function getLibExtraLibs(): string
|
||||
{
|
||||
return getenv($this->getSnakeCaseName() . '_LIBS') ?: '';
|
||||
}
|
||||
}
|
||||
|
||||
38
src/SPC/builder/traits/openssl.php
Normal file
38
src/SPC/builder/traits/openssl.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\builder\traits;
|
||||
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\util\PkgConfigUtil;
|
||||
|
||||
trait openssl
|
||||
{
|
||||
public function getLibVersion(): ?string
|
||||
{
|
||||
// get openssl version from source directory
|
||||
if (file_exists("{$this->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;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,9 @@ abstract class UnixBuilderBase extends BuilderBase
|
||||
/** @var string C++ flags */
|
||||
public string $arch_cxx_flags;
|
||||
|
||||
/** @var string LD flags */
|
||||
public string $arch_ld_flags;
|
||||
|
||||
public function proveLibs(array $sorted_libraries): void
|
||||
{
|
||||
// search all supported libs
|
||||
@@ -262,7 +265,7 @@ abstract class UnixBuilderBase extends BuilderBase
|
||||
if (SPCTarget::isStatic()) {
|
||||
$extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'";
|
||||
$muslTags = 'static_build,';
|
||||
$staticFlags = '-static -static-pie';
|
||||
$staticFlags = '-static-pie';
|
||||
}
|
||||
|
||||
$config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list);
|
||||
|
||||
@@ -11,20 +11,8 @@ use SPC\util\executor\UnixAutoconfExecutor;
|
||||
|
||||
trait librdkafka
|
||||
{
|
||||
/**
|
||||
* @throws FileSystemException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function build(): void
|
||||
public function patchBeforeBuild(): bool
|
||||
{
|
||||
$cflags = $this->builder->arch_c_flags;
|
||||
$cxxflags = $this->builder->arch_cxx_flags;
|
||||
$patched_cflags = preg_replace('/\s*-flto(=\S*)?\s*/', ' ', $cflags);
|
||||
$patched_cxxflags = preg_replace('/\s*-flto(=\S*)?\s*/', ' ', $cxxflags);
|
||||
|
||||
$this->builder->arch_c_flags = $patched_cflags;
|
||||
$this->builder->arch_cxx_flags = $patched_cxxflags;
|
||||
|
||||
FileSystem::replaceFileStr(
|
||||
$this->source_dir . '/lds-gen.py',
|
||||
"funcs.append('rd_ut_coverage_check')",
|
||||
@@ -35,6 +23,15 @@ trait librdkafka
|
||||
'#error "IOV_MAX not defined"',
|
||||
"#define IOV_MAX 1024\n#define __GNU__"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FileSystemException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function build(): void
|
||||
{
|
||||
UnixAutoconfExecutor::create($this)
|
||||
->appendEnv(['CFLAGS' => '-Wno-int-conversion -Wno-unused-but-set-variable -Wno-unused-variable'])
|
||||
->optionalLib(
|
||||
@@ -64,8 +61,5 @@ trait librdkafka
|
||||
->exec("rm -rf {$this->getLibDir()}/*.so.*")
|
||||
->exec("rm -rf {$this->getLibDir()}/*.so")
|
||||
->exec("rm -rf {$this->getLibDir()}/*.dylib");
|
||||
|
||||
$this->builder->arch_c_flags = $cflags;
|
||||
$this->builder->arch_cxx_flags = $cxxflags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,9 @@ trait libxslt
|
||||
|
||||
$this->patchPkgconfPrefix(['libexslt.pc', 'libxslt.pc']);
|
||||
$this->patchLaDependencyPrefix();
|
||||
$AR = getenv('AR') ?: 'ar';
|
||||
shell()->cd(BUILD_LIB_PATH)
|
||||
->exec("ar -t libxslt.a | grep '\\.a$' | xargs -n1 ar d libxslt.a")
|
||||
->exec("ar -t libexslt.a | grep '\\.a$' | xargs -n1 ar d libexslt.a");
|
||||
->exec("{$AR} -t libxslt.a | grep '\\.a$' | xargs -n1 {$AR} d libxslt.a")
|
||||
->exec("{$AR} -t libexslt.a | grep '\\.a$' | xargs -n1 {$AR} d libexslt.a");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ trait ngtcp2
|
||||
|
||||
// on macOS, the static library may contain other static libraries?
|
||||
// ld: archive member 'libssl.a' not a mach-o file in libngtcp2_crypto_ossl.a
|
||||
shell()->cd(BUILD_LIB_PATH)->exec("ar -t libngtcp2_crypto_ossl.a | grep '\\.a$' | xargs -n1 ar d libngtcp2_crypto_ossl.a");
|
||||
$AR = getenv('AR') ?: 'ar';
|
||||
shell()->cd(BUILD_LIB_PATH)->exec("{$AR} -t libngtcp2_crypto_ossl.a | grep '\\.a$' | xargs -n1 {$AR} d libngtcp2_crypto_ossl.a");
|
||||
}
|
||||
}
|
||||
|
||||
32
src/SPC/builder/unix/library/re2c.php
Normal file
32
src/SPC/builder/unix/library/re2c.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\builder\unix\library;
|
||||
|
||||
use SPC\util\executor\UnixCMakeExecutor;
|
||||
|
||||
trait re2c
|
||||
{
|
||||
protected function build(): void
|
||||
{
|
||||
UnixCMakeExecutor::create($this)
|
||||
->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();
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,6 @@ trait unixodbc
|
||||
*/
|
||||
protected function build(): void
|
||||
{
|
||||
$cflags = $this->builder->arch_c_flags;
|
||||
$cxxflags = $this->builder->arch_cxx_flags;
|
||||
$patched_cflags = preg_replace('/\s*-flto(=\S*)?\s*/', ' ', $cflags);
|
||||
$patched_cxxflags = preg_replace('/\s*-flto(=\S*)?\s*/', ' ', $cxxflags);
|
||||
|
||||
$this->builder->arch_c_flags = $patched_cflags;
|
||||
$this->builder->arch_cxx_flags = $patched_cxxflags;
|
||||
|
||||
UnixAutoconfExecutor::create($this)
|
||||
->configure(
|
||||
'--disable-debug',
|
||||
@@ -35,8 +27,5 @@ trait unixodbc
|
||||
->make();
|
||||
$this->patchPkgconfPrefix(['odbc.pc', 'odbccr.pc', 'odbcinst.pc']);
|
||||
$this->patchLaDependencyPrefix();
|
||||
|
||||
$this->builder->arch_c_flags = $cflags;
|
||||
$this->builder->arch_cxx_flags = $cxxflags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 .
|
||||
'"'
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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("<error>Failed to get version of library {$this->getArgument('library')}</error>");
|
||||
$this->output->writeln("<error>Failed to get version of library {$this->getArgument('library')}. The version getter for [{$this->getArgument('library')}] is not implemented.</error>");
|
||||
return static::FAILURE;
|
||||
}
|
||||
$this->output->writeln("<info>{$version}</info>");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
53
src/SPC/doctor/item/Re2cVersionCheck.php
Normal file
53
src/SPC/doctor/item/Re2cVersionCheck.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\doctor\item;
|
||||
|
||||
use SPC\builder\BuilderProvider;
|
||||
use SPC\builder\traits\UnixSystemUtilTrait;
|
||||
use SPC\doctor\AsCheckItem;
|
||||
use SPC\doctor\AsFixItem;
|
||||
use SPC\doctor\CheckResult;
|
||||
use SPC\exception\DownloaderException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\Downloader;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
class Re2cVersionCheck
|
||||
{
|
||||
use UnixSystemUtilTrait;
|
||||
|
||||
#[AsCheckItem('if re2c version >= 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;
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,6 @@ namespace SPC\store;
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
|
||||
/**
|
||||
* 一个读取 config 配置的操作类
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
public static ?array $pkg = null;
|
||||
@@ -23,6 +20,10 @@ class Config
|
||||
public static ?array $pre_built = null;
|
||||
|
||||
/**
|
||||
* Get pre-built configuration by name
|
||||
*
|
||||
* @param string $name The name of the pre-built configuration
|
||||
* @return mixed The pre-built configuration or null if not found
|
||||
* @throws WrongUsageException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
@@ -50,8 +51,10 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置文件读取一个资源(source)的元信息
|
||||
* Get source configuration by name
|
||||
*
|
||||
* @param string $name The name of the source
|
||||
* @return null|array The source configuration or null if not found
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function getSource(string $name): ?array
|
||||
@@ -63,8 +66,10 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Read pkg from pkg.json
|
||||
* Get package configuration by name
|
||||
*
|
||||
* @param string $name The name of the package
|
||||
* @return null|array The package configuration or null if not found
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function getPkg(string $name): ?array
|
||||
@@ -76,11 +81,13 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据不同的操作系统分别选择不同的 lib 库依赖项
|
||||
* 如果 key 为 null,那么直接返回整个 meta。
|
||||
* 如果 key 不为 null,则可以使用的 key 有 static-libs、headers、lib-depends、lib-suggests。
|
||||
* 对于 macOS 平台,支持 frameworks。
|
||||
* Get library configuration by name and optional key
|
||||
* Supports platform-specific configurations for different operating systems
|
||||
*
|
||||
* @param string $name The name of the library
|
||||
* @param null|string $key The configuration key (static-libs, headers, lib-depends, lib-suggests, frameworks, bin)
|
||||
* @param mixed $default Default value if key not found
|
||||
* @return mixed The library configuration or default value
|
||||
* @throws FileSystemException
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
@@ -115,6 +122,9 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all library configurations
|
||||
*
|
||||
* @return array All library configurations
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function getLibs(): array
|
||||
@@ -126,6 +136,10 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extension target configuration by name
|
||||
*
|
||||
* @param string $name The name of the extension
|
||||
* @return null|array The extension target configuration or default ['static', 'shared']
|
||||
* @throws WrongUsageException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
@@ -141,6 +155,13 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extension configuration by name and optional key
|
||||
* Supports platform-specific configurations for different operating systems
|
||||
*
|
||||
* @param string $name The name of the extension
|
||||
* @param null|string $key The configuration key (lib-depends, lib-suggests, ext-depends, ext-suggests, arg-type)
|
||||
* @param mixed $default Default value if key not found
|
||||
* @return mixed The extension configuration or default value
|
||||
* @throws FileSystemException
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
@@ -175,6 +196,9 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all extension configurations
|
||||
*
|
||||
* @return array All extension configurations
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function getExts(): array
|
||||
@@ -186,6 +210,9 @@ class Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all source configurations
|
||||
*
|
||||
* @return array All source configurations
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function getSources(): array
|
||||
|
||||
@@ -18,12 +18,13 @@ use SPC\util\SPCTarget;
|
||||
class Downloader
|
||||
{
|
||||
/**
|
||||
* Get latest version from BitBucket tag (type = bitbuckettag)
|
||||
* Get latest version from BitBucket tag
|
||||
*
|
||||
* @param string $name source name
|
||||
* @param array $source source meta info: [repo]
|
||||
* @param string $name Source name
|
||||
* @param array $source Source meta info: [repo]
|
||||
* @return array<int, string> [url, filename]
|
||||
* @throws DownloaderException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function getLatestBitbucketTag(string $name, array $source): array
|
||||
{
|
||||
@@ -53,13 +54,12 @@ class Downloader
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest version from GitHub tarball (type = ghtar / ghtagtar)
|
||||
*
|
||||
* @param string $name source name
|
||||
* @param array $source source meta info: [repo]
|
||||
* @param string $type type of tarball, default is 'releases'
|
||||
* @return array<int, string> [url, filename]
|
||||
* Get latest version from GitHub tarball
|
||||
*
|
||||
* @param string $name Source name
|
||||
* @param array $source Source meta info: [repo]
|
||||
* @param string $type Type of tarball, default is 'releases'
|
||||
* @return array<int, string> [url, filename]
|
||||
* @throws DownloaderException
|
||||
*/
|
||||
public static function getLatestGithubTarball(string $name, array $source, string $type = 'releases'): array
|
||||
@@ -107,8 +107,8 @@ class Downloader
|
||||
/**
|
||||
* Get latest version from GitHub release (uploaded archive)
|
||||
*
|
||||
* @param string $name source name
|
||||
* @param array $source source meta info: [repo, match]
|
||||
* @param string $name Source name
|
||||
* @param array $source Source meta info: [repo, match]
|
||||
* @param bool $match_result Whether to return matched result by `match` param (default: true)
|
||||
* @return array<int, string> When $match_result = true, and we matched, [url, filename]. Otherwise, [{asset object}. ...]
|
||||
* @throws DownloaderException
|
||||
@@ -150,8 +150,8 @@ class Downloader
|
||||
/**
|
||||
* Get latest version from file list (regex based crawler)
|
||||
*
|
||||
* @param string $name source name
|
||||
* @param array $source source meta info: [url, regex]
|
||||
* @param string $name Source name
|
||||
* @param array $source Source meta info: [filelist]
|
||||
* @return array<int, string> [url, filename]
|
||||
* @throws DownloaderException
|
||||
*/
|
||||
@@ -187,11 +187,17 @@ class Downloader
|
||||
}
|
||||
|
||||
/**
|
||||
* Just download file using system curl command, and lock it
|
||||
* Download file from URL
|
||||
*
|
||||
* @throws FileSystemException
|
||||
* @param string $name Download name
|
||||
* @param string $url Download URL
|
||||
* @param string $filename Target filename
|
||||
* @param null|string $move_path Optional move path after download
|
||||
* @param int $download_as Download type constant
|
||||
* @param array $headers Optional HTTP headers
|
||||
* @param array $hooks Optional curl hooks
|
||||
* @throws DownloaderException
|
||||
* @throws RuntimeException
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null, int $download_as = SPC_DOWNLOAD_SOURCE, array $headers = [], array $hooks = []): void
|
||||
{
|
||||
@@ -213,11 +219,17 @@ class Downloader
|
||||
}
|
||||
|
||||
/**
|
||||
* Download git source, and lock it.
|
||||
* Download Git repository
|
||||
*
|
||||
* @throws FileSystemException
|
||||
* @param string $name Repository name
|
||||
* @param string $url Git repository URL
|
||||
* @param string $branch Branch to checkout
|
||||
* @param null|array $submodules Optional submodules to initialize
|
||||
* @param null|string $move_path Optional move path after download
|
||||
* @param int $retries Number of retry attempts
|
||||
* @param int $lock_as Lock type constant
|
||||
* @throws DownloaderException
|
||||
* @throws RuntimeException
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
public static function downloadGit(string $name, string $url, string $branch, ?array $submodules = null, ?string $move_path = null, int $retries = 0, int $lock_as = SPC_DOWNLOAD_SOURCE): void
|
||||
{
|
||||
@@ -304,7 +316,6 @@ class Downloader
|
||||
if ($pkg === null) {
|
||||
$pkg = Config::getPkg($name);
|
||||
}
|
||||
|
||||
if ($pkg === null) {
|
||||
logger()->warning('Package {name} unknown. Skipping.', ['name' => $name]);
|
||||
return;
|
||||
@@ -398,7 +409,7 @@ class Downloader
|
||||
}
|
||||
|
||||
/**
|
||||
* Download source by name and meta.
|
||||
* Download source
|
||||
*
|
||||
* @param string $name source name
|
||||
* @param null|array{
|
||||
@@ -428,7 +439,6 @@ class Downloader
|
||||
if ($source === null) {
|
||||
$source = Config::getSource($name);
|
||||
}
|
||||
|
||||
if ($source === null) {
|
||||
logger()->warning('Source {name} unknown. Skipping.', ['name' => $name]);
|
||||
return;
|
||||
@@ -522,7 +532,14 @@ class Downloader
|
||||
/**
|
||||
* Use curl command to get http response
|
||||
*
|
||||
* @param string $url Target URL
|
||||
* @param string $method HTTP method (GET, POST, etc.)
|
||||
* @param array $headers HTTP headers
|
||||
* @param array $hooks Curl hooks
|
||||
* @param int $retries Number of retry attempts
|
||||
* @return string Response body
|
||||
* @throws DownloaderException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function curlExec(string $url, string $method = 'GET', array $headers = [], array $hooks = [], int $retries = 0): string
|
||||
{
|
||||
@@ -574,6 +591,13 @@ class Downloader
|
||||
/**
|
||||
* Use curl to download sources from url
|
||||
*
|
||||
* @param string $url Download URL
|
||||
* @param string $path Target file path
|
||||
* @param string $method HTTP method
|
||||
* @param array $headers HTTP headers
|
||||
* @param array $hooks Curl hooks
|
||||
* @param int $retries Number of retry attempts
|
||||
* @throws DownloaderException
|
||||
* @throws RuntimeException
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
@@ -603,6 +627,12 @@ class Downloader
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pre-built lock name from source
|
||||
*
|
||||
* @param string $source Source name
|
||||
* @return string Lock name
|
||||
*/
|
||||
public static function getPreBuiltLockName(string $source): string
|
||||
{
|
||||
$os_family = PHP_OS_FAMILY;
|
||||
@@ -613,6 +643,12 @@ class Downloader
|
||||
return "{$source}-{$os_family}-{$gnu_arch}-{$libc}-{$libc_version}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default alternative source
|
||||
*
|
||||
* @param string $source_name Source name
|
||||
* @return array Alternative source configuration
|
||||
*/
|
||||
public static function getDefaultAlternativeSource(string $source_name): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -12,6 +12,11 @@ class FileSystem
|
||||
private static array $_extract_hook = [];
|
||||
|
||||
/**
|
||||
* Load configuration array from JSON file
|
||||
*
|
||||
* @param string $config The configuration name (ext, lib, source, pkg, pre-built)
|
||||
* @param null|string $config_dir Optional custom config directory
|
||||
* @return array The loaded configuration array
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function loadConfigArray(string $config, ?string $config_dir = null): array
|
||||
@@ -37,9 +42,10 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文件,读不出来直接抛出异常
|
||||
* Read file contents and throw exception on failure
|
||||
*
|
||||
* @param string $filename 文件路径
|
||||
* @param string $filename The file path to read
|
||||
* @return string The file contents
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function readFile(string $filename): string
|
||||
@@ -53,6 +59,12 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace string content in file
|
||||
*
|
||||
* @param string $filename The file path
|
||||
* @param mixed $search The search string
|
||||
* @param mixed $replace The replacement string
|
||||
* @return false|int Number of replacements or false on failure
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function replaceFileStr(string $filename, mixed $search = null, mixed $replace = null): false|int
|
||||
@@ -61,6 +73,12 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace content in file using regex
|
||||
*
|
||||
* @param string $filename The file path
|
||||
* @param mixed $search The regex pattern
|
||||
* @param mixed $replace The replacement string
|
||||
* @return false|int Number of replacements or false on failure
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function replaceFileRegex(string $filename, mixed $search = null, mixed $replace = null): false|int
|
||||
@@ -69,6 +87,11 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace content in file using custom callback
|
||||
*
|
||||
* @param string $filename The file path
|
||||
* @param mixed $callback The callback function
|
||||
* @return false|int Number of replacements or false on failure
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function replaceFileUser(string $filename, mixed $callback = null): false|int
|
||||
@@ -77,9 +100,10 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件后缀
|
||||
* Get file extension from filename
|
||||
*
|
||||
* @param string $fn 文件名
|
||||
* @param string $fn The filename
|
||||
* @return string The file extension (without dot)
|
||||
*/
|
||||
public static function extname(string $fn): string
|
||||
{
|
||||
@@ -91,10 +115,11 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* 寻找命令的真实路径,效果类似 which
|
||||
* Find command path in system PATH (similar to which command)
|
||||
*
|
||||
* @param string $name 命令名称
|
||||
* @param array $paths 路径列表,如果为空则默认从 PATH 系统变量搜索
|
||||
* @param string $name The command name
|
||||
* @param array $paths Optional array of paths to search
|
||||
* @return null|string The full path to the command or null if not found
|
||||
*/
|
||||
public static function findCommandPath(string $name, array $paths = []): ?string
|
||||
{
|
||||
@@ -120,6 +145,10 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy directory recursively
|
||||
*
|
||||
* @param string $from Source directory path
|
||||
* @param string $to Destination directory path
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function copyDir(string $from, string $to): void
|
||||
@@ -139,6 +168,12 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract package archive to specified directory
|
||||
*
|
||||
* @param string $name Package name
|
||||
* @param string $source_type Archive type (tar.gz, zip, etc.)
|
||||
* @param string $filename Archive filename
|
||||
* @param null|string $extract_path Optional extraction path
|
||||
* @throws RuntimeException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
@@ -171,10 +206,12 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩下载的资源包到 source 目录
|
||||
* Extract source archive to source directory
|
||||
*
|
||||
* @param string $name 资源名
|
||||
* @param string $filename 文件名
|
||||
* @param string $name Source name
|
||||
* @param string $source_type Archive type (tar.gz, zip, etc.)
|
||||
* @param string $filename Archive filename
|
||||
* @param null|string $move_path Optional move path
|
||||
* @throws FileSystemException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
@@ -207,9 +244,10 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据系统环境的不同,自动转换路径的分隔符
|
||||
* Convert path to system-specific format
|
||||
*
|
||||
* @param string $path 路径
|
||||
* @param string $path The path to convert
|
||||
* @return string The converted path
|
||||
*/
|
||||
public static function convertPath(string $path): string
|
||||
{
|
||||
@@ -219,6 +257,12 @@ class FileSystem
|
||||
return str_replace('/', DIRECTORY_SEPARATOR, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Windows path to MinGW format
|
||||
*
|
||||
* @param string $path The Windows path
|
||||
* @return string The MinGW format path
|
||||
*/
|
||||
public static function convertWinPathToMinGW(string $path): string
|
||||
{
|
||||
if (preg_match('/^[A-Za-z]:/', $path)) {
|
||||
@@ -228,28 +272,21 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归或非递归扫描目录,可返回相对目录的文件列表或绝对目录的文件列表
|
||||
* Scan directory files recursively
|
||||
*
|
||||
* @param string $dir 目录
|
||||
* @param bool $recursive 是否递归扫描子目录
|
||||
* @param bool|string $relative 是否返回相对目录,如果为true则返回相对目录,如果为false则返回绝对目录
|
||||
* @param bool $include_dir 非递归模式下,是否包含目录
|
||||
* @since 2.5
|
||||
* @param string $dir Directory to scan
|
||||
* @param bool $recursive Whether to scan recursively
|
||||
* @param bool|string $relative Whether to return relative paths
|
||||
* @param bool $include_dir Whether to include directories in result
|
||||
* @return array|false Array of files or false on failure
|
||||
*/
|
||||
public static function scanDirFiles(string $dir, bool $recursive = true, bool|string $relative = false, bool $include_dir = false): array|false
|
||||
{
|
||||
$dir = self::convertPath($dir);
|
||||
// 不是目录不扫,直接 false 处理
|
||||
if (!file_exists($dir)) {
|
||||
logger()->debug('Scan dir failed, no such file or directory.');
|
||||
return false;
|
||||
}
|
||||
if (!is_dir($dir)) {
|
||||
logger()->warning('Scan dir failed, not directory.');
|
||||
return false;
|
||||
}
|
||||
logger()->debug('scanning directory ' . $dir);
|
||||
// 套上 zm_dir
|
||||
$scan_list = scandir($dir);
|
||||
if ($scan_list === false) {
|
||||
logger()->warning('Scan dir failed, cannot scan directory: ' . $dir);
|
||||
@@ -283,18 +320,17 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该路径下的所有类名,根据 psr-4 方式
|
||||
* Get PSR-4 classes from directory
|
||||
*
|
||||
* @param string $dir 目录
|
||||
* @param string $base_namespace 基类命名空间
|
||||
* @param null|mixed $rule 规则回调
|
||||
* @param bool|string $return_path_value 是否返回路径对应的数组,默认只返回类名列表
|
||||
* @throws FileSystemException
|
||||
* @param string $dir Directory to scan
|
||||
* @param string $base_namespace Base namespace
|
||||
* @param mixed $rule Optional filtering rule
|
||||
* @param bool|string $return_path_value Whether to return path as value
|
||||
* @return array Array of class names or class=>path pairs
|
||||
*/
|
||||
public static function getClassesPsr4(string $dir, string $base_namespace, mixed $rule = null, bool|string $return_path_value = false): array
|
||||
{
|
||||
$classes = [];
|
||||
// 扫描目录,使用递归模式,相对路径模式,因为下面此路径要用作转换成namespace
|
||||
$files = FileSystem::scanDirFiles($dir, true, true);
|
||||
if ($files === false) {
|
||||
throw new FileSystemException('Cannot scan dir files during get classes psr-4 from dir: ' . $dir);
|
||||
@@ -325,15 +361,15 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除目录及目录下的所有文件(危险操作)
|
||||
* Remove directory recursively
|
||||
*
|
||||
* @throws FileSystemException
|
||||
* @param string $dir Directory to remove
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function removeDir(string $dir): bool
|
||||
{
|
||||
$dir = FileSystem::convertPath($dir);
|
||||
logger()->debug('Removing path recursively: "' . $dir . '"');
|
||||
// 不是目录不扫,直接 false 处理
|
||||
if (!file_exists($dir)) {
|
||||
logger()->debug('Scan dir failed, no such file or directory.');
|
||||
return false;
|
||||
@@ -374,6 +410,9 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Create directory recursively
|
||||
*
|
||||
* @param string $path Directory path to create
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function createDir(string $path): void
|
||||
@@ -384,7 +423,12 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed ...$args Arguments passed to file_put_contents
|
||||
* Write content to file
|
||||
*
|
||||
* @param string $path File path
|
||||
* @param mixed $content Content to write
|
||||
* @param mixed ...$args Additional arguments passed to file_put_contents
|
||||
* @return bool|int|string Result of file writing operation
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function writeFile(string $path, mixed $content, ...$args): bool|int|string
|
||||
@@ -397,27 +441,36 @@ class FileSystem
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset (remove recursively and create again) dir
|
||||
* Reset directory by removing and recreating it
|
||||
*
|
||||
* @param string $dir_name Directory name
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function resetDir(string $dir_name): void
|
||||
{
|
||||
$dir_name = self::convertPath($dir_name);
|
||||
if (is_dir($dir_name)) {
|
||||
self::removeDir($dir_name);
|
||||
}
|
||||
self::createDir($dir_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add source extraction hook
|
||||
*
|
||||
* @param string $name Source name
|
||||
* @param callable $callback Callback function
|
||||
*/
|
||||
public static function addSourceExtractHook(string $name, callable $callback): void
|
||||
{
|
||||
self::$_extract_hook[$name][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the path is a relative path (judging according to whether the first character is "/")
|
||||
* Check if path is relative
|
||||
*
|
||||
* @param string $path Path
|
||||
* @param string $path Path to check
|
||||
* @return bool True if path is relative
|
||||
*/
|
||||
public static function isRelativePath(string $path): bool
|
||||
{
|
||||
@@ -427,6 +480,12 @@ class FileSystem
|
||||
return strlen($path) > 0 && $path[0] !== '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace path variables with actual values
|
||||
*
|
||||
* @param string $path Path with variables
|
||||
* @return string Path with replaced variables
|
||||
*/
|
||||
public static function replacePathVariable(string $path): string
|
||||
{
|
||||
$replacement = [
|
||||
@@ -439,12 +498,23 @@ class FileSystem
|
||||
return str_replace(array_keys($replacement), array_values($replacement), $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create backup of file
|
||||
*
|
||||
* @param string $path File path
|
||||
* @return string Backup file path
|
||||
*/
|
||||
public static function backupFile(string $path): string
|
||||
{
|
||||
copy($path, $path . '.bak');
|
||||
return $path . '.bak';
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore file from backup
|
||||
*
|
||||
* @param string $path Original file path
|
||||
*/
|
||||
public static function restoreBackupFile(string $path): void
|
||||
{
|
||||
if (!file_exists($path . '.bak')) {
|
||||
@@ -454,14 +524,26 @@ class FileSystem
|
||||
unlink($path . '.bak');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove file if it exists
|
||||
*
|
||||
* @param string $string File path
|
||||
*/
|
||||
public static function removeFileIfExists(string $string): void
|
||||
{
|
||||
$string = self::convertPath($string);
|
||||
if (file_exists($string)) {
|
||||
unlink($string);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace line in file that contains specific string
|
||||
*
|
||||
* @param string $file File path
|
||||
* @param string $find String to find in line
|
||||
* @param string $line New line content
|
||||
* @return false|int Number of replacements or false on failure
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function replaceFileLineContainsString(string $file, string $find, string $line): false|int
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -4,15 +4,42 @@ declare(strict_types=1);
|
||||
|
||||
namespace SPC\store\pkg;
|
||||
|
||||
/**
|
||||
* Abstract base class for custom package implementations
|
||||
*
|
||||
* This class provides a framework for implementing custom package download
|
||||
* and extraction logic. Extend this class to create custom package handlers.
|
||||
*/
|
||||
abstract class CustomPackage
|
||||
{
|
||||
/**
|
||||
* Get the list of package names supported by this implementation
|
||||
*
|
||||
* @return array Array of supported package names
|
||||
*/
|
||||
abstract public function getSupportName(): array;
|
||||
|
||||
/**
|
||||
* Fetch the package from its source
|
||||
*
|
||||
* @param string $name Package name
|
||||
* @param bool $force Force download even if already exists
|
||||
* @param null|array $config Optional configuration array
|
||||
*/
|
||||
abstract public function fetch(string $name, bool $force = false, ?array $config = null): void;
|
||||
|
||||
abstract public function extract(string $name): void;
|
||||
|
||||
/**
|
||||
* Get the environment variables this package needs to be usable.
|
||||
* PATH needs to be appended, rather than replaced.
|
||||
*/
|
||||
abstract public static function getEnvironment(): array;
|
||||
|
||||
abstract public static function isInstalled(): bool;
|
||||
|
||||
/**
|
||||
* Extract the downloaded package
|
||||
*
|
||||
* @param string $name Package name
|
||||
*/
|
||||
abstract public function extract(string $name): void;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,13 @@ class Zig extends CustomPackage
|
||||
public static function isInstalled(): bool
|
||||
{
|
||||
$path = self::getPath();
|
||||
return file_exists("{$path}/zig") && file_exists("{$path}/zig-cc") && file_exists("{$path}/zig-c++");
|
||||
$files = ['zig', 'zig-cc', 'zig-c++', 'zig-ar', 'zig-ld.lld', 'zig-ranlib', 'zig-objcopy'];
|
||||
foreach ($files as $file) {
|
||||
if (!file_exists("{$path}/{$file}")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSupportName(): array
|
||||
@@ -98,12 +104,16 @@ class Zig extends CustomPackage
|
||||
{
|
||||
$pkgroot = PKG_ROOT_PATH;
|
||||
$zig_bin_dir = "{$pkgroot}/{$name}";
|
||||
$zig_exec = match (PHP_OS_FAMILY) {
|
||||
'Windows' => "{$zig_bin_dir}/zig.exe",
|
||||
default => "{$zig_bin_dir}/zig",
|
||||
};
|
||||
|
||||
if (file_exists($zig_exec) && file_exists("{$zig_bin_dir}/zig-cc")) {
|
||||
$files = ['zig', 'zig-cc', 'zig-c++', 'zig-ar', 'zig-ld.lld', 'zig-ranlib', 'zig-objcopy'];
|
||||
$all_exist = true;
|
||||
foreach ($files as $file) {
|
||||
if (!file_exists("{$zig_bin_dir}/{$file}")) {
|
||||
$all_exist = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($all_exist) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -163,10 +173,12 @@ class Zig extends CustomPackage
|
||||
$script_content = str_replace('zig cc', 'zig c++', $script_content);
|
||||
file_put_contents("{$bin_dir}/zig-c++", $script_content);
|
||||
file_put_contents("{$bin_dir}/zig-ar", "#!/usr/bin/env bash\nexec zig ar $@");
|
||||
file_put_contents("{$bin_dir}/zig-ld.lld", "#!/usr/bin/env bash\nexec zig ld.lld $@");
|
||||
file_put_contents("{$bin_dir}/zig-ranlib", "#!/usr/bin/env bash\nexec zig ranlib $@");
|
||||
file_put_contents("{$bin_dir}/zig-objcopy", "#!/usr/bin/env bash\nexec zig objcopy $@");
|
||||
chmod("{$bin_dir}/zig-c++", 0755);
|
||||
chmod("{$bin_dir}/zig-ar", 0755);
|
||||
chmod("{$bin_dir}/zig-ld.lld", 0755);
|
||||
chmod("{$bin_dir}/zig-ranlib", 0755);
|
||||
chmod("{$bin_dir}/zig-objcopy", 0755);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,25 @@ declare(strict_types=1);
|
||||
|
||||
namespace SPC\store\source;
|
||||
|
||||
/**
|
||||
* Abstract base class for custom source implementations
|
||||
*
|
||||
* This class provides a framework for implementing custom source download
|
||||
* logic. Extend this class to create custom source handlers.
|
||||
*/
|
||||
abstract class CustomSourceBase
|
||||
{
|
||||
/**
|
||||
* The name of this source implementation
|
||||
*/
|
||||
public const NAME = 'unknown';
|
||||
|
||||
/**
|
||||
* Fetch the source from its repository
|
||||
*
|
||||
* @param bool $force Force download even if already exists
|
||||
* @param null|array $config Optional configuration array
|
||||
* @param int $lock_as Lock type constant
|
||||
*/
|
||||
abstract public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void;
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class GccNativeToolchain implements ToolchainInterface
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=gcc');
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=g++');
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=ar');
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld.gold');
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld');
|
||||
}
|
||||
|
||||
public function afterInit(): void
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,9 @@ class MSVCToolchain implements ToolchainInterface
|
||||
public function initEnv(): void {}
|
||||
|
||||
public function afterInit(): void {}
|
||||
|
||||
public function getCompilerInfo(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,35 @@ declare(strict_types=1);
|
||||
|
||||
namespace SPC\toolchain;
|
||||
|
||||
/**
|
||||
* Interface for toolchain implementations
|
||||
*
|
||||
* This interface defines the contract for toolchain classes that handle
|
||||
* environment initialization and setup for different build targets.
|
||||
*/
|
||||
interface ToolchainInterface
|
||||
{
|
||||
/**
|
||||
* Initialize the environment for the given target.
|
||||
*
|
||||
* This method should set up any necessary environment variables,
|
||||
* paths, or configurations required for the build process.
|
||||
*/
|
||||
public function initEnv(): void;
|
||||
|
||||
/**
|
||||
* Perform actions after the environment has been initialized for the given target.
|
||||
*
|
||||
* This method is called after initEnv() and can be used for any
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace SPC\toolchain;
|
||||
|
||||
use SPC\builder\linux\SystemUtil;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\util\GlobalEnvManager;
|
||||
use SPC\util\SPCTarget;
|
||||
@@ -26,7 +25,7 @@ class ToolchainManager
|
||||
}
|
||||
$libc = getenv('SPC_LIBC');
|
||||
if ($libc && !getenv('SPC_TARGET')) {
|
||||
trigger_error('Setting SPC_LIBC is deprecated, please use SPC_TARGET instead.', E_USER_DEPRECATED);
|
||||
// trigger_error('Setting SPC_LIBC is deprecated, please use SPC_TARGET instead.', E_USER_DEPRECATED);
|
||||
return match ($libc) {
|
||||
'musl' => SystemUtil::isMuslDist() ? GccNativeToolchain::class : MuslToolchain::class,
|
||||
'glibc' => !SystemUtil::isMuslDist() ? GccNativeToolchain::class : throw new WrongUsageException('SPC_LIBC must be musl for musl dist.'),
|
||||
@@ -55,13 +54,17 @@ class ToolchainManager
|
||||
}
|
||||
$musl_wrapper_lib = sprintf('/lib/ld-musl-%s.so.1', php_uname('m'));
|
||||
if (SPCTarget::getLibc() === 'musl' && !SPCTarget::isStatic() && !file_exists($musl_wrapper_lib)) {
|
||||
throw new RuntimeException('You are linking against musl libc dynamically, but musl libc is not installed. Please install it with `sudo dnf install musl-libc` or `sudo apt install musl`');
|
||||
throw new WrongUsageException('You are linking against musl libc dynamically, but musl libc is not installed. Please use `bin/spc doctor` to install it.');
|
||||
}
|
||||
if (SPCTarget::getLibc() === 'glibc' && SystemUtil::isMuslDist()) {
|
||||
throw new RuntimeException('You are linking against glibc dynamically, which is only supported on musl distros.');
|
||||
throw new WrongUsageException('You are linking against glibc dynamically, which is only supported on glibc distros.');
|
||||
}
|
||||
$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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,7 @@ class ZigToolchain implements ToolchainInterface
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=zig-cc');
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=zig-c++');
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=zig-ar');
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=zig-cc');
|
||||
GlobalEnvManager::putenv('RANLIB=zig-ranlib');
|
||||
GlobalEnvManager::putenv('OBJCOPY=zig-objcopy');
|
||||
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=zig-ld.lld');
|
||||
|
||||
// Generate additional objects needed for zig toolchain
|
||||
$paths = ['/usr/lib/gcc', '/usr/local/lib/gcc'];
|
||||
@@ -61,6 +59,8 @@ class ZigToolchain implements ToolchainInterface
|
||||
GlobalEnvManager::putenv("SPC_DEFAULT_C_FLAGS={$cflags}");
|
||||
GlobalEnvManager::putenv("SPC_DEFAULT_CXX_FLAGS={$cxxflags}");
|
||||
GlobalEnvManager::putenv("SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS={$extraCflags}");
|
||||
GlobalEnvManager::putenv('RANLIB=zig-ranlib');
|
||||
GlobalEnvManager::putenv('OBJCOPY=zig-objcopy');
|
||||
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
|
||||
if (!str_contains($extra_libs, '-lunwind')) {
|
||||
// Add unwind library if not already present
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,33 +20,48 @@ class ConfigValidator
|
||||
public static function validateSource(array $data): void
|
||||
{
|
||||
foreach ($data as $name => $src) {
|
||||
isset($src['type']) || throw new ValidationException("source {$name} must have prop: [type]");
|
||||
is_string($src['type']) || throw new ValidationException("source {$name} type prop must be string");
|
||||
in_array($src['type'], ['filelist', 'git', 'ghtagtar', 'ghtar', 'ghrel', 'url', 'custom']) || throw new ValidationException("source {$name} type [{$src['type']}] is invalid");
|
||||
switch ($src['type']) {
|
||||
case 'filelist':
|
||||
isset($src['url'], $src['regex']) || throw new ValidationException("source {$name} needs [url] and [regex] props");
|
||||
is_string($src['url']) && is_string($src['regex']) || throw new ValidationException("source {$name} [url] and [regex] must be string");
|
||||
break;
|
||||
case 'git':
|
||||
isset($src['url'], $src['rev']) || throw new ValidationException("source {$name} needs [url] and [rev] props");
|
||||
is_string($src['url']) && is_string($src['rev']) || throw new ValidationException("source {$name} [url] and [rev] must be string");
|
||||
is_string($src['path'] ?? '') || throw new ValidationException("source {$name} [path] must be string");
|
||||
break;
|
||||
case 'ghtagtar':
|
||||
case 'ghtar':
|
||||
isset($src['repo']) || throw new ValidationException("source {$name} needs [repo] prop");
|
||||
is_string($src['repo']) || throw new ValidationException("source {$name} [repo] must be string");
|
||||
is_string($src['path'] ?? '') || throw new ValidationException("source {$name} [path] must be string");
|
||||
break;
|
||||
case 'ghrel':
|
||||
isset($src['repo'], $src['match']) || throw new ValidationException("source {$name} needs [repo] and [match] props");
|
||||
is_string($src['repo']) && is_string($src['match']) || throw new ValidationException("source {$name} [repo] and [match] must be string");
|
||||
break;
|
||||
case 'url':
|
||||
isset($src['url']) || throw new ValidationException("source {$name} needs [url] prop");
|
||||
is_string($src['url']) || throw new ValidationException("source {$name} [url] must be string");
|
||||
break;
|
||||
// Validate basic source type configuration
|
||||
self::validateSourceTypeConfig($src, $name, 'source');
|
||||
|
||||
// Check source-specific fields
|
||||
// check if alt is valid
|
||||
if (isset($src['alt'])) {
|
||||
if (!is_assoc_array($src['alt']) && !is_bool($src['alt'])) {
|
||||
throw new ValidationException("source {$name} alt must be object or boolean");
|
||||
}
|
||||
if (is_assoc_array($src['alt'])) {
|
||||
// validate alt source recursively
|
||||
self::validateSource([$name . '_alt' => $src['alt']]);
|
||||
}
|
||||
}
|
||||
|
||||
// check if provide-pre-built is boolean
|
||||
if (isset($src['provide-pre-built']) && !is_bool($src['provide-pre-built'])) {
|
||||
throw new ValidationException("source {$name} provide-pre-built must be boolean");
|
||||
}
|
||||
|
||||
// check if prefer-stable is boolean
|
||||
if (isset($src['prefer-stable']) && !is_bool($src['prefer-stable'])) {
|
||||
throw new ValidationException("source {$name} prefer-stable must be boolean");
|
||||
}
|
||||
|
||||
// check if license is valid
|
||||
if (isset($src['license'])) {
|
||||
if (!is_array($src['license'])) {
|
||||
throw new ValidationException("source {$name} license must be an object or array");
|
||||
}
|
||||
if (is_assoc_array($src['license'])) {
|
||||
self::checkSingleLicense($src['license'], $name);
|
||||
} elseif (is_list_array($src['license'])) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +93,11 @@ class ConfigValidator
|
||||
if (isset($lib['source']) && !empty($source_data) && !isset($source_data[$lib['source']])) {
|
||||
throw new ValidationException("lib {$name} assigns an invalid source: {$lib['source']}");
|
||||
}
|
||||
// check if [lib-depends|lib-suggests|static-libs][-windows|-unix|-macos|-linux] are valid list array
|
||||
// check if source is string
|
||||
if (isset($lib['source']) && !is_string($lib['source'])) {
|
||||
throw new ValidationException("lib {$name} source must be string");
|
||||
}
|
||||
// check if [lib-depends|lib-suggests|static-libs|headers|bin][-windows|-unix|-macos|-linux] are valid list array
|
||||
$suffixes = ['', '-windows', '-unix', '-macos', '-linux'];
|
||||
foreach ($suffixes as $suffix) {
|
||||
if (isset($lib['lib-depends' . $suffix]) && !is_list_array($lib['lib-depends' . $suffix])) {
|
||||
@@ -93,6 +112,12 @@ class ConfigValidator
|
||||
if (isset($lib['pkg-configs' . $suffix]) && !is_list_array($lib['pkg-configs' . $suffix])) {
|
||||
throw new ValidationException("lib {$name} pkg-configs must be a list");
|
||||
}
|
||||
if (isset($lib['headers' . $suffix]) && !is_list_array($lib['headers' . $suffix])) {
|
||||
throw new ValidationException("lib {$name} headers must be a list");
|
||||
}
|
||||
if (isset($lib['bin' . $suffix]) && !is_list_array($lib['bin' . $suffix])) {
|
||||
throw new ValidationException("lib {$name} bin must be a list");
|
||||
}
|
||||
}
|
||||
// check if frameworks is a list array
|
||||
if (isset($lib['frameworks']) && !is_list_array($lib['frameworks'])) {
|
||||
@@ -106,7 +131,65 @@ class ConfigValidator
|
||||
*/
|
||||
public static function validateExts(mixed $data): void
|
||||
{
|
||||
is_array($data) || throw new ValidationException('ext.json is broken');
|
||||
if (!is_array($data)) {
|
||||
throw new ValidationException('ext.json is broken');
|
||||
}
|
||||
// check each extension
|
||||
foreach ($data as $name => $ext) {
|
||||
// check if ext is an assoc array
|
||||
if (!is_assoc_array($ext)) {
|
||||
throw new ValidationException("ext {$name} is not an object");
|
||||
}
|
||||
// check if ext has valid type
|
||||
if (!in_array($ext['type'] ?? '', ['builtin', 'external', 'addon', 'wip'])) {
|
||||
throw new ValidationException("ext {$name} type is invalid");
|
||||
}
|
||||
// check if external ext has source
|
||||
if (($ext['type'] ?? '') === 'external' && !isset($ext['source'])) {
|
||||
throw new ValidationException("ext {$name} does not assign any source");
|
||||
}
|
||||
// check if source is string
|
||||
if (isset($ext['source']) && !is_string($ext['source'])) {
|
||||
throw new ValidationException("ext {$name} source must be string");
|
||||
}
|
||||
// check if support is valid
|
||||
if (isset($ext['support']) && !is_assoc_array($ext['support'])) {
|
||||
throw new ValidationException("ext {$name} support must be an object");
|
||||
}
|
||||
// check if notes is boolean
|
||||
if (isset($ext['notes']) && !is_bool($ext['notes'])) {
|
||||
throw new ValidationException("ext {$name} notes must be boolean");
|
||||
}
|
||||
// check if [lib-depends|lib-suggests|ext-depends][-windows|-unix|-macos|-linux] are valid list array
|
||||
$suffixes = ['', '-windows', '-unix', '-macos', '-linux'];
|
||||
foreach ($suffixes as $suffix) {
|
||||
if (isset($ext['lib-depends' . $suffix]) && !is_list_array($ext['lib-depends' . $suffix])) {
|
||||
throw new ValidationException("ext {$name} lib-depends must be a list");
|
||||
}
|
||||
if (isset($ext['lib-suggests' . $suffix]) && !is_list_array($ext['lib-suggests' . $suffix])) {
|
||||
throw new ValidationException("ext {$name} lib-suggests must be a list");
|
||||
}
|
||||
if (isset($ext['ext-depends' . $suffix]) && !is_list_array($ext['ext-depends' . $suffix])) {
|
||||
throw new ValidationException("ext {$name} ext-depends must be a list");
|
||||
}
|
||||
}
|
||||
// check if arg-type is valid
|
||||
if (isset($ext['arg-type'])) {
|
||||
$valid_arg_types = ['enable', 'with', 'with-path', 'custom', 'none', 'enable-path'];
|
||||
if (!in_array($ext['arg-type'], $valid_arg_types)) {
|
||||
throw new ValidationException("ext {$name} arg-type is invalid");
|
||||
}
|
||||
}
|
||||
// check if arg-type with suffix is valid
|
||||
foreach ($suffixes as $suffix) {
|
||||
if (isset($ext['arg-type' . $suffix])) {
|
||||
$valid_arg_types = ['enable', 'with', 'with-path', 'custom', 'none', 'enable-path'];
|
||||
if (!in_array($ext['arg-type' . $suffix], $valid_arg_types)) {
|
||||
throw new ValidationException("ext {$name} arg-type{$suffix} is invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +197,96 @@ class ConfigValidator
|
||||
*/
|
||||
public static function validatePkgs(mixed $data): void
|
||||
{
|
||||
is_array($data) || throw new ValidationException('pkg.json is broken');
|
||||
if (!is_array($data)) {
|
||||
throw new ValidationException('pkg.json is broken');
|
||||
}
|
||||
// check each package
|
||||
foreach ($data as $name => $pkg) {
|
||||
// check if pkg is an assoc array
|
||||
if (!is_assoc_array($pkg)) {
|
||||
throw new ValidationException("pkg {$name} is not an object");
|
||||
}
|
||||
|
||||
// Validate basic source type configuration (reuse from source validation)
|
||||
self::validateSourceTypeConfig($pkg, $name, 'pkg');
|
||||
|
||||
// Check pkg-specific fields
|
||||
// check if extract-files is valid
|
||||
if (isset($pkg['extract-files'])) {
|
||||
if (!is_assoc_array($pkg['extract-files'])) {
|
||||
throw new ValidationException("pkg {$name} extract-files must be an object");
|
||||
}
|
||||
// check each extract file mapping
|
||||
foreach ($pkg['extract-files'] as $source => $target) {
|
||||
if (!is_string($source) || !is_string($target)) {
|
||||
throw new ValidationException("pkg {$name} extract-files mapping must be string to string");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate pre-built.json configuration
|
||||
*
|
||||
* @param mixed $data pre-built.json loaded data
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public static function validatePreBuilt(mixed $data): void
|
||||
{
|
||||
if (!is_array($data)) {
|
||||
throw new ValidationException('pre-built.json is broken');
|
||||
}
|
||||
|
||||
// Check required fields
|
||||
if (!isset($data['repo'])) {
|
||||
throw new ValidationException('pre-built.json must have [repo] field');
|
||||
}
|
||||
if (!is_string($data['repo'])) {
|
||||
throw new ValidationException('pre-built.json [repo] must be string');
|
||||
}
|
||||
|
||||
// Check optional prefer-stable field
|
||||
if (isset($data['prefer-stable']) && !is_bool($data['prefer-stable'])) {
|
||||
throw new ValidationException('pre-built.json [prefer-stable] must be boolean');
|
||||
}
|
||||
|
||||
// Check match pattern fields (at least one must exist)
|
||||
$pattern_fields = ['match-pattern-linux', 'match-pattern-macos', 'match-pattern-windows'];
|
||||
$has_pattern = false;
|
||||
|
||||
foreach ($pattern_fields as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$has_pattern = true;
|
||||
if (!is_string($data[$field])) {
|
||||
throw new ValidationException("pre-built.json [{$field}] must be string");
|
||||
}
|
||||
// Validate pattern contains required placeholders
|
||||
if (!str_contains($data[$field], '{name}')) {
|
||||
throw new ValidationException("pre-built.json [{$field}] must contain {name} placeholder");
|
||||
}
|
||||
if (!str_contains($data[$field], '{arch}')) {
|
||||
throw new ValidationException("pre-built.json [{$field}] must contain {arch} placeholder");
|
||||
}
|
||||
if (!str_contains($data[$field], '{os}')) {
|
||||
throw new ValidationException("pre-built.json [{$field}] must contain {os} placeholder");
|
||||
}
|
||||
|
||||
// Linux pattern should have libc-related placeholders
|
||||
if ($field === 'match-pattern-linux') {
|
||||
if (!str_contains($data[$field], '{libc}')) {
|
||||
throw new ValidationException('pre-built.json [match-pattern-linux] must contain {libc} placeholder');
|
||||
}
|
||||
if (!str_contains($data[$field], '{libcver}')) {
|
||||
throw new ValidationException('pre-built.json [match-pattern-linux] must contain {libcver} placeholder');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$has_pattern) {
|
||||
throw new ValidationException('pre-built.json must have at least one match-pattern field');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,4 +414,110 @@ class ConfigValidator
|
||||
$craft['craft-options']['build'] ??= true;
|
||||
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)
|
||||
*
|
||||
* @param array $item The source/package item to validate
|
||||
* @param string $name The name of the item for error messages
|
||||
* @param string $config_type The type of config file ("source" or "pkg")
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private static function validateSourceTypeConfig(array $item, string $name, string $config_type): void
|
||||
{
|
||||
if (!isset($item['type'])) {
|
||||
throw new ValidationException("{$config_type} {$name} must have prop: [type]");
|
||||
}
|
||||
if (!is_string($item['type'])) {
|
||||
throw new ValidationException("{$config_type} {$name} type prop must be string");
|
||||
}
|
||||
if (!in_array($item['type'], ['filelist', 'git', 'ghtagtar', 'ghtar', 'ghrel', 'url', 'custom'])) {
|
||||
throw new ValidationException("{$config_type} {$name} type [{$item['type']}] is invalid");
|
||||
}
|
||||
|
||||
// Validate type-specific requirements
|
||||
switch ($item['type']) {
|
||||
case 'filelist':
|
||||
if (!isset($item['url'], $item['regex'])) {
|
||||
throw new ValidationException("{$config_type} {$name} needs [url] and [regex] props");
|
||||
}
|
||||
if (!is_string($item['url']) || !is_string($item['regex'])) {
|
||||
throw new ValidationException("{$config_type} {$name} [url] and [regex] must be string");
|
||||
}
|
||||
break;
|
||||
case 'git':
|
||||
if (!isset($item['url'], $item['rev'])) {
|
||||
throw new ValidationException("{$config_type} {$name} needs [url] and [rev] props");
|
||||
}
|
||||
if (!is_string($item['url']) || !is_string($item['rev'])) {
|
||||
throw new ValidationException("{$config_type} {$name} [url] and [rev] must be string");
|
||||
}
|
||||
if (isset($item['path']) && !is_string($item['path'])) {
|
||||
throw new ValidationException("{$config_type} {$name} [path] must be string");
|
||||
}
|
||||
break;
|
||||
case 'ghtagtar':
|
||||
case 'ghtar':
|
||||
if (!isset($item['repo'])) {
|
||||
throw new ValidationException("{$config_type} {$name} needs [repo] prop");
|
||||
}
|
||||
if (!is_string($item['repo'])) {
|
||||
throw new ValidationException("{$config_type} {$name} [repo] must be string");
|
||||
}
|
||||
if (isset($item['path']) && !is_string($item['path'])) {
|
||||
throw new ValidationException("{$config_type} {$name} [path] must be string");
|
||||
}
|
||||
break;
|
||||
case 'ghrel':
|
||||
if (!isset($item['repo'], $item['match'])) {
|
||||
throw new ValidationException("{$config_type} {$name} needs [repo] and [match] props");
|
||||
}
|
||||
if (!is_string($item['repo']) || !is_string($item['match'])) {
|
||||
throw new ValidationException("{$config_type} {$name} [repo] and [match] must be string");
|
||||
}
|
||||
break;
|
||||
case 'url':
|
||||
if (!isset($item['url'])) {
|
||||
throw new ValidationException("{$config_type} {$name} needs [url] prop");
|
||||
}
|
||||
if (!is_string($item['url'])) {
|
||||
throw new ValidationException("{$config_type} {$name} [url] must be string");
|
||||
}
|
||||
if (isset($item['filename']) && !is_string($item['filename'])) {
|
||||
throw new ValidationException("{$config_type} {$name} [filename] must be string");
|
||||
}
|
||||
if (isset($item['path']) && !is_string($item['path'])) {
|
||||
throw new ValidationException("{$config_type} {$name} [path] must be string");
|
||||
}
|
||||
break;
|
||||
case 'custom':
|
||||
// custom type has no specific requirements
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,30 @@ use SPC\builder\Extension;
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\store\FileSystem;
|
||||
|
||||
/**
|
||||
* Custom extension attribute and manager
|
||||
*
|
||||
* This class provides functionality to register and manage custom PHP extensions
|
||||
* that can be used during the build process.
|
||||
*/
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
|
||||
class CustomExt
|
||||
{
|
||||
private static array $custom_ext_class = [];
|
||||
|
||||
/**
|
||||
* Constructor for custom extension attribute
|
||||
*
|
||||
* @param string $ext_name The extension name
|
||||
*/
|
||||
public function __construct(protected string $ext_name) {}
|
||||
|
||||
/**
|
||||
* Load all custom extension classes
|
||||
*
|
||||
* This method scans the extension directory and registers all classes
|
||||
* that have the CustomExt attribute.
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
@@ -32,6 +46,12 @@ class CustomExt
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class name for a custom extension
|
||||
*
|
||||
* @param string $ext_name The extension name
|
||||
* @return string The class name for the extension
|
||||
*/
|
||||
public static function getExtClass(string $ext_name): string
|
||||
{
|
||||
return self::$custom_ext_class[$ext_name] ?? Extension::class;
|
||||
|
||||
@@ -9,13 +9,25 @@ use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
|
||||
/**
|
||||
* Dependency processing tool class, including processing extensions, library dependency list order, etc.
|
||||
* Dependency processing tool class
|
||||
*
|
||||
* This class handles processing extensions, library dependency list ordering, etc.
|
||||
* It provides utilities for managing dependencies between extensions and libraries.
|
||||
*/
|
||||
class DependencyUtil
|
||||
{
|
||||
/**
|
||||
* Convert platform extensions to library dependencies and suggestions.
|
||||
* Convert platform extensions to library dependencies and suggestions
|
||||
*
|
||||
* This method processes all extensions and libraries to create a comprehensive
|
||||
* dependency map that can be used for build ordering.
|
||||
*
|
||||
* Returns an associative array where the key is the extension or library name (string),
|
||||
* and the value is an array with two keys:
|
||||
* - 'depends': array of dependency names (string)
|
||||
* - 'suggests': array of suggested dependency names (string)
|
||||
*
|
||||
* @return array<string, array{depends: array<int, string>, suggests: array<int, string>}>
|
||||
* @throws WrongUsageException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
@@ -53,6 +65,10 @@ class DependencyUtil
|
||||
}
|
||||
|
||||
/**
|
||||
* Get library dependencies in correct order
|
||||
*
|
||||
* @param array $libs Array of library names
|
||||
* @return array Ordered array of library names
|
||||
* @throws WrongUsageException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
@@ -88,7 +104,13 @@ class DependencyUtil
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FileSystemException|WrongUsageException
|
||||
* Get extension dependencies in correct order
|
||||
*
|
||||
* @param array $exts Array of extension names
|
||||
* @param array $additional_libs Array of additional libraries
|
||||
* @return array Ordered array of extension names
|
||||
* @throws WrongUsageException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function getExtsAndLibs(array $exts, array $additional_libs = [], bool $include_suggested_exts = false, bool $include_suggested_libs = false): array
|
||||
{
|
||||
@@ -155,9 +177,6 @@ class DependencyUtil
|
||||
return [$exts_final, $libs_final, $not_included_final];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
private static function doVisitPlat(array $deps, array $dep_list): array
|
||||
{
|
||||
// default: get extension exts and libs sorted by dep_list
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,15 +6,38 @@ namespace SPC\util;
|
||||
|
||||
use SPC\exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Utility class for pkg-config operations
|
||||
*
|
||||
* This class provides methods to interact with pkg-config to get
|
||||
* compilation flags and library information for building extensions.
|
||||
*/
|
||||
class PkgConfigUtil
|
||||
{
|
||||
/**
|
||||
* Returns --cflags-only-other output.
|
||||
* 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
|
||||
*
|
||||
* Returns --cflags-only-other output from pkg-config.
|
||||
* The reason we return the string is we cannot use array_unique() on cflags,
|
||||
* some cflags may contains spaces.
|
||||
*
|
||||
* @param string $pkg_config_str .pc file str, accepts multiple files
|
||||
* @return string cflags string, e.g. "-Wno-implicit-int-float-conversion ..."
|
||||
* @param string $pkg_config_str .pc file string, accepts multiple files
|
||||
* @return string CFLAGS string, e.g. "-Wno-implicit-int-float-conversion ..."
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static function getCflags(string $pkg_config_str): string
|
||||
@@ -25,10 +48,12 @@ class PkgConfigUtil
|
||||
}
|
||||
|
||||
/**
|
||||
* Get library flags from pkg-config
|
||||
*
|
||||
* Returns --libs-only-l and --libs-only-other output.
|
||||
* The reason we return the array is to avoid duplicate lib defines.
|
||||
*
|
||||
* @param string $pkg_config_str .pc file str, accepts multiple files
|
||||
* @param string $pkg_config_str .pc file string, accepts multiple files
|
||||
* @return array Unique libs array, e.g. [-lz, -lxml, ...]
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
@@ -64,6 +89,13 @@ class PkgConfigUtil
|
||||
return array_reverse(array_unique(array_reverse($libs)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute pkg-config command and return result
|
||||
*
|
||||
* @param string $cmd The pkg-config command to execute
|
||||
* @return string The command output
|
||||
* @throws RuntimeException If command fails
|
||||
*/
|
||||
private static function execWithResult(string $cmd): string
|
||||
{
|
||||
f_exec($cmd, $output, $result_code);
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace SPC\util;
|
||||
|
||||
use SPC\builder\linux\SystemUtil;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\toolchain\ClangNativeToolchain;
|
||||
use SPC\toolchain\GccNativeToolchain;
|
||||
use SPC\toolchain\MuslToolchain;
|
||||
@@ -17,6 +16,8 @@ use SPC\toolchain\ToolchainManager;
|
||||
*/
|
||||
class SPCTarget
|
||||
{
|
||||
public const array LIBC_LIST = ['musl', 'glibc'];
|
||||
|
||||
/**
|
||||
* Returns whether we link the C runtime in statically.
|
||||
*/
|
||||
@@ -98,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +111,6 @@ class SPCTarget
|
||||
* Currently, we only support native building.
|
||||
*
|
||||
* @return 'BSD'|'Darwin'|'Linux'|'Windows'
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
public static function getTargetOS(): string
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,26 +43,26 @@ $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' => 'apcu,ast,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,iconv,libxml,mbregex,mbstring,opcache,openssl,pcntl,phar,posix,readline,session,simplexml,sockets,sodium,tokenizer,xml,xmlreader,xmlwriter,zip,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`).
|
||||
$shared_extensions = match (PHP_OS_FAMILY) {
|
||||
'Linux' => 'amqp,brotli,bz2,dio,ds,ev,event,ffi,ftp,gd,gettext,gmp,gmssl,igbinary,imagick,inotify,intl,ldap,lz4,memcache,memcached,mongodb,msgpack,mysqli,mysqlnd,odbc,opentelemetry,parallel,pdo,pdo_mysql,pdo_odbc,pdo_pgsql,pdo_sqlite,pdo_sqlsrv,pgsql,protobuf,rar,redis,rdkafka,shmop,spx,sqlite3,sqlsrv,ssh2,swoole,sysvmsg,sysvsem,sysvshm,tidy,uuid,uv,xdebug,xhprof,xlswriter,xsl,xz,yac,yaml,zstd',
|
||||
'Linux' => '',
|
||||
'Darwin' => '',
|
||||
'Windows' => '',
|
||||
};
|
||||
|
||||
// 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',
|
||||
};
|
||||
|
||||
@@ -157,12 +159,7 @@ if ($shared_extensions) {
|
||||
$shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
|
||||
break;
|
||||
case 'ubuntu-24.04':
|
||||
putenv('SPC_TARGET=native-native-gnu.2.17');
|
||||
$shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
|
||||
break;
|
||||
case 'ubuntu-24.04-arm':
|
||||
putenv('SPC_TARGET=native-native-gnu.2.28');
|
||||
$shared_cmd = ' --build-shared=' . quote2($shared_extensions) . ' ';
|
||||
break;
|
||||
case 'macos-13':
|
||||
case 'macos-14':
|
||||
|
||||
@@ -103,7 +103,7 @@ class BuilderTest extends TestCase
|
||||
{
|
||||
if (file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) {
|
||||
$file = SOURCE_PATH . '/php-src/main/php_version.h';
|
||||
$cnt = preg_match('/PHP_VERSION "(\d+\.\d+\.\d+)"/', file_get_contents($file), $match);
|
||||
$cnt = preg_match('/PHP_VERSION "(\d+\.\d+\.\d+(?:-[^"]+)?)/', file_get_contents($file), $match);
|
||||
if ($cnt !== 0) {
|
||||
$this->assertEquals($match[1], $this->builder->getPHPVersion());
|
||||
} else {
|
||||
|
||||
@@ -44,6 +44,35 @@ class ConfigValidatorTest extends TestCase
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
],
|
||||
'source7' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'filename' => 'test.tar.gz',
|
||||
'path' => 'test/path',
|
||||
'provide-pre-built' => true,
|
||||
'prefer-stable' => false,
|
||||
'license' => [
|
||||
'type' => 'file',
|
||||
'path' => 'LICENSE',
|
||||
],
|
||||
],
|
||||
'source8' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'alt' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://alt.example.com',
|
||||
],
|
||||
],
|
||||
'source9' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'alt' => false,
|
||||
'license' => [
|
||||
'type' => 'text',
|
||||
'text' => 'MIT License',
|
||||
],
|
||||
],
|
||||
];
|
||||
try {
|
||||
ConfigValidator::validateSource($good_source);
|
||||
@@ -83,6 +112,47 @@ class ConfigValidatorTest extends TestCase
|
||||
'source6' => [
|
||||
'type' => 'url', // no url
|
||||
],
|
||||
'source7' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'provide-pre-built' => 'not boolean', // not boolean
|
||||
],
|
||||
'source8' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'prefer-stable' => 'not boolean', // not boolean
|
||||
],
|
||||
'source9' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'license' => 'not object', // not object
|
||||
],
|
||||
'source10' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'license' => [
|
||||
'type' => 'invalid', // invalid type
|
||||
],
|
||||
],
|
||||
'source11' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'license' => [
|
||||
'type' => 'file', // missing path
|
||||
],
|
||||
],
|
||||
'source12' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'license' => [
|
||||
'type' => 'text', // missing text
|
||||
],
|
||||
],
|
||||
'source13' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com',
|
||||
'alt' => 'not object or boolean', // not object or boolean
|
||||
],
|
||||
];
|
||||
foreach ($bad_source as $name => $src) {
|
||||
try {
|
||||
@@ -112,9 +182,38 @@ class ConfigValidatorTest extends TestCase
|
||||
'lib1',
|
||||
],
|
||||
],
|
||||
'lib4' => [
|
||||
'source' => 'source4',
|
||||
'headers' => [
|
||||
'header1.h',
|
||||
'header2.h',
|
||||
],
|
||||
'headers-windows' => [
|
||||
'windows_header.h',
|
||||
],
|
||||
'bin-unix' => [
|
||||
'binary1',
|
||||
'binary2',
|
||||
],
|
||||
'frameworks' => [
|
||||
'CoreFoundation',
|
||||
'SystemConfiguration',
|
||||
],
|
||||
],
|
||||
'lib5' => [
|
||||
'type' => 'package',
|
||||
'source' => 'source5',
|
||||
'pkg-configs' => [
|
||||
'pkg1',
|
||||
'pkg2',
|
||||
],
|
||||
],
|
||||
'lib6' => [
|
||||
'type' => 'root',
|
||||
],
|
||||
];
|
||||
try {
|
||||
ConfigValidator::validateLibs($good_libs, ['source1' => [], 'source2' => [], 'source3' => []]);
|
||||
ConfigValidator::validateLibs($good_libs, ['source1' => [], 'source2' => [], 'source3' => [], 'source4' => [], 'source5' => []]);
|
||||
$this->assertTrue(true);
|
||||
} catch (ValidationException $e) {
|
||||
$this->fail($e->getMessage());
|
||||
@@ -193,6 +292,20 @@ class ConfigValidatorTest extends TestCase
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// headers must be list
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'headers' => 'not list']], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
// bin must be list
|
||||
try {
|
||||
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'bin-unix' => 'not list']], ['source1' => [], 'source2' => []]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,15 +313,459 @@ class ConfigValidatorTest extends TestCase
|
||||
*/
|
||||
public function testValidateExts(): void
|
||||
{
|
||||
ConfigValidator::validateExts([]);
|
||||
// Test valid extensions
|
||||
$valid_exts = [
|
||||
'ext1' => [
|
||||
'type' => 'builtin',
|
||||
],
|
||||
'ext2' => [
|
||||
'type' => 'external',
|
||||
'source' => 'source1',
|
||||
],
|
||||
'ext3' => [
|
||||
'type' => 'external',
|
||||
'source' => 'source2',
|
||||
'arg-type' => 'enable',
|
||||
'lib-depends' => ['lib1'],
|
||||
'lib-suggests' => ['lib2'],
|
||||
'ext-depends-windows' => ['ext1'],
|
||||
'support' => [
|
||||
'Windows' => 'wip',
|
||||
'BSD' => 'wip',
|
||||
],
|
||||
'notes' => true,
|
||||
],
|
||||
'ext4' => [
|
||||
'type' => 'external',
|
||||
'source' => 'source3',
|
||||
'arg-type-unix' => 'with-path',
|
||||
'arg-type-windows' => 'with',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validateExts($valid_exts);
|
||||
|
||||
// Test invalid data
|
||||
$this->expectException(ValidationException::class);
|
||||
ConfigValidator::validateExts(null);
|
||||
}
|
||||
|
||||
public function testValidateExtsBad(): void
|
||||
{
|
||||
// Test invalid extension type
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'invalid']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test external extension without source
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'external']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test non-object extension
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => 'not object']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid source type
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'external', 'source' => true]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid support
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'support' => 'not object']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid notes
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'notes' => 'not boolean']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid lib-depends
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'lib-depends' => 'not list']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid arg-type
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'arg-type' => 'invalid']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid arg-type with suffix
|
||||
try {
|
||||
ConfigValidator::validateExts(['ext1' => ['type' => 'builtin', 'arg-type-unix' => 'invalid']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidatePkgs(): void
|
||||
{
|
||||
ConfigValidator::validatePkgs([]);
|
||||
// Test valid packages (all supported types)
|
||||
$valid_pkgs = [
|
||||
'pkg1' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com/file.tar.gz',
|
||||
],
|
||||
'pkg2' => [
|
||||
'type' => 'ghrel',
|
||||
'repo' => 'owner/repo',
|
||||
'match' => 'file.+\.tar\.gz',
|
||||
],
|
||||
'pkg3' => [
|
||||
'type' => 'custom',
|
||||
],
|
||||
'pkg4' => [
|
||||
'type' => 'url',
|
||||
'url' => 'https://example.com/archive.zip',
|
||||
'filename' => 'archive.zip',
|
||||
'path' => 'extract/path',
|
||||
'extract-files' => [
|
||||
'source/file.exe' => '{pkg_root_path}/bin/file.exe',
|
||||
'source/lib.dll' => '{pkg_root_path}/lib/lib.dll',
|
||||
],
|
||||
],
|
||||
'pkg5' => [
|
||||
'type' => 'ghrel',
|
||||
'repo' => 'owner/repo',
|
||||
'match' => 'release.+\.zip',
|
||||
'extract-files' => [
|
||||
'binary' => '{pkg_root_path}/bin/binary',
|
||||
],
|
||||
],
|
||||
'pkg6' => [
|
||||
'type' => 'filelist',
|
||||
'url' => 'https://example.com/filelist',
|
||||
'regex' => '/href="(?<file>.*\.tar\.gz)"/',
|
||||
],
|
||||
'pkg7' => [
|
||||
'type' => 'git',
|
||||
'url' => 'https://github.com/owner/repo.git',
|
||||
'rev' => 'main',
|
||||
],
|
||||
'pkg8' => [
|
||||
'type' => 'git',
|
||||
'url' => 'https://github.com/owner/repo.git',
|
||||
'rev' => 'v1.0.0',
|
||||
'path' => 'subdir/path',
|
||||
],
|
||||
'pkg9' => [
|
||||
'type' => 'ghtagtar',
|
||||
'repo' => 'owner/repo',
|
||||
],
|
||||
'pkg10' => [
|
||||
'type' => 'ghtar',
|
||||
'repo' => 'owner/repo',
|
||||
'path' => 'subdir',
|
||||
],
|
||||
];
|
||||
ConfigValidator::validatePkgs($valid_pkgs);
|
||||
|
||||
// Test invalid data
|
||||
$this->expectException(ValidationException::class);
|
||||
ConfigValidator::validatePkgs(null);
|
||||
}
|
||||
|
||||
public function testValidatePkgsBad(): void
|
||||
{
|
||||
// Test invalid package type
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'invalid']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test non-object package
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => 'not object']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test filelist type without url
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'filelist', 'regex' => '.*']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test filelist type without regex
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'filelist', 'url' => 'https://example.com']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test git type without url
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'git', 'rev' => 'main']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test git type without rev
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'git', 'url' => 'https://github.com/owner/repo.git']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghtagtar type without repo
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghtagtar']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghtar type without repo
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghtar']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test url type without url
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test url type with non-string url
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => true]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghrel type without repo
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghrel', 'match' => 'pattern']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghrel type without match
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghrel', 'repo' => 'owner/repo']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghrel type with non-string repo
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghrel', 'repo' => true, 'match' => 'pattern']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test ghrel type with non-string match
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'ghrel', 'repo' => 'owner/repo', 'match' => 123]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test git type with non-string path
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'git', 'url' => 'https://github.com/owner/repo.git', 'rev' => 'main', 'path' => 123]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test url type with non-string filename
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => 'https://example.com', 'filename' => 123]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid extract-files (not object)
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => 'https://example.com', 'extract-files' => 'not object']]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid extract-files mapping (non-string key)
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => 'https://example.com', 'extract-files' => [123 => 'target']]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid extract-files mapping (non-string value)
|
||||
try {
|
||||
ConfigValidator::validatePkgs(['pkg1' => ['type' => 'url', 'url' => 'https://example.com', 'extract-files' => ['source' => 123]]]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidatePreBuilt(): void
|
||||
{
|
||||
// Test valid pre-built configurations
|
||||
$valid_prebuilt = [
|
||||
'basic' => [
|
||||
'repo' => 'static-php/static-php-cli-hosted',
|
||||
'match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz',
|
||||
],
|
||||
'full' => [
|
||||
'repo' => 'static-php/static-php-cli-hosted',
|
||||
'prefer-stable' => true,
|
||||
'match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz',
|
||||
'match-pattern-macos' => '{name}-{arch}-{os}.txz',
|
||||
'match-pattern-windows' => '{name}-{arch}-{os}.tgz',
|
||||
],
|
||||
'prefer-stable-false' => [
|
||||
'repo' => 'owner/repo',
|
||||
'prefer-stable' => false,
|
||||
'match-pattern-macos' => '{name}-{arch}-{os}.tar.gz',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($valid_prebuilt as $name => $config) {
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt($config);
|
||||
$this->assertTrue(true, "Config {$name} should be valid");
|
||||
} catch (ValidationException $e) {
|
||||
$this->fail("Config {$name} should be valid but got: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidatePreBuiltBad(): void
|
||||
{
|
||||
// Test non-array data
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt('invalid');
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test missing repo
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid repo type
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 123, 'match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid prefer-stable type
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'prefer-stable' => 'true', 'match-pattern-linux' => '{name}-{arch}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test no match patterns
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test invalid match pattern type
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => 123]);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test missing {name} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{arch}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test missing {arch} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{name}-{os}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test missing {os} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{name}-{arch}-{libc}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test linux pattern missing {libc} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{name}-{arch}-{os}-{libcver}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
// Test linux pattern missing {libcver} placeholder
|
||||
try {
|
||||
ConfigValidator::validatePreBuilt(['repo' => 'owner/repo', 'match-pattern-linux' => '{name}-{arch}-{os}-{libc}.txz']);
|
||||
$this->fail('should throw ValidationException');
|
||||
} catch (ValidationException) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,29 @@ use SPC\util\DependencyUtil;
|
||||
*/
|
||||
final class DependencyUtilTest extends TestCase
|
||||
{
|
||||
public function testGetExtLibsByDeps(): void
|
||||
private array $originalConfig;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// setup
|
||||
$bak = [
|
||||
// Save original configuration
|
||||
$this->originalConfig = [
|
||||
'source' => Config::$source,
|
||||
'lib' => Config::$lib,
|
||||
'ext' => Config::$ext,
|
||||
];
|
||||
// example
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Restore original configuration
|
||||
Config::$source = $this->originalConfig['source'];
|
||||
Config::$lib = $this->originalConfig['lib'];
|
||||
Config::$ext = $this->originalConfig['ext'];
|
||||
}
|
||||
|
||||
public function testGetExtLibsByDeps(): void
|
||||
{
|
||||
// Set up test data
|
||||
Config::$source = [
|
||||
'test1' => [
|
||||
'type' => 'url',
|
||||
@@ -73,14 +87,15 @@ final class DependencyUtilTest extends TestCase
|
||||
'lib-depends' => ['libeee'],
|
||||
],
|
||||
];
|
||||
// test getExtLibsByDeps (notmal test with ext-depends and lib-depends)
|
||||
|
||||
// Test dependency resolution
|
||||
[$exts, $libs, $not_included] = DependencyUtil::getExtsAndLibs(['ext-a'], include_suggested_exts: true);
|
||||
$this->assertContains('libbbb', $libs);
|
||||
$this->assertContains('libccc', $libs);
|
||||
$this->assertContains('ext-b', $exts);
|
||||
$this->assertContains('ext-b', $not_included);
|
||||
// test dep order
|
||||
|
||||
// Test dependency order
|
||||
$this->assertIsInt($b = array_search('libbbb', $libs));
|
||||
$this->assertIsInt($c = array_search('libccc', $libs));
|
||||
$this->assertIsInt($a = array_search('libaaa', $libs));
|
||||
@@ -88,10 +103,6 @@ final class DependencyUtilTest extends TestCase
|
||||
$this->assertTrue($b < $a);
|
||||
$this->assertTrue($c < $a);
|
||||
$this->assertTrue($c < $b);
|
||||
// restore
|
||||
Config::$source = $bak['source'];
|
||||
Config::$lib = $bak['lib'];
|
||||
Config::$ext = $bak['ext'];
|
||||
}
|
||||
|
||||
public function testNotExistExtException(): void
|
||||
|
||||
143
tests/SPC/util/GlobalEnvManagerTest.php
Normal file
143
tests/SPC/util/GlobalEnvManagerTest.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\util\GlobalEnvManager;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class GlobalEnvManagerTest extends TestCase
|
||||
{
|
||||
private array $originalEnv;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Save original environment variables
|
||||
$this->originalEnv = [
|
||||
'BUILD_ROOT_PATH' => getenv('BUILD_ROOT_PATH'),
|
||||
'SPC_TARGET' => getenv('SPC_TARGET'),
|
||||
'SPC_LIBC' => getenv('SPC_LIBC'),
|
||||
];
|
||||
// Temporarily set private GlobalEnvManager::$initialized to false (use reflection)
|
||||
$reflection = new \ReflectionClass(GlobalEnvManager::class);
|
||||
$property = $reflection->getProperty('initialized');
|
||||
$property->setValue(null, false);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Restore original environment variables
|
||||
foreach ($this->originalEnv as $key => $value) {
|
||||
if ($value === false) {
|
||||
putenv($key);
|
||||
} else {
|
||||
putenv("{$key}={$value}");
|
||||
}
|
||||
}
|
||||
// Temporarily set private GlobalEnvManager::$initialized to false (use reflection)
|
||||
$reflection = new \ReflectionClass(GlobalEnvManager::class);
|
||||
$property = $reflection->getProperty('initialized');
|
||||
$property->setValue(null, true);
|
||||
}
|
||||
|
||||
public function testGetInitializedEnv(): void
|
||||
{
|
||||
// Test that getInitializedEnv returns an array
|
||||
$result = GlobalEnvManager::getInitializedEnv();
|
||||
$this->assertIsArray($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider envVariableProvider
|
||||
*/
|
||||
public function testPutenv(string $envVar): void
|
||||
{
|
||||
// Test putenv functionality
|
||||
GlobalEnvManager::putenv($envVar);
|
||||
|
||||
$env = GlobalEnvManager::getInitializedEnv();
|
||||
$this->assertContains($envVar, $env);
|
||||
$this->assertEquals(explode('=', $envVar, 2)[1], getenv(explode('=', $envVar, 2)[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider pathProvider
|
||||
*/
|
||||
public function testAddPathIfNotExistsOnUnix(string $path): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$originalPath = getenv('PATH');
|
||||
GlobalEnvManager::addPathIfNotExists($path);
|
||||
|
||||
$newPath = getenv('PATH');
|
||||
$this->assertStringContainsString($path, $newPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider pathProvider
|
||||
*/
|
||||
public function testAddPathIfNotExistsWhenPathAlreadyExists(string $path): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
GlobalEnvManager::addPathIfNotExists($path);
|
||||
$pathAfterFirstAdd = getenv('PATH');
|
||||
|
||||
GlobalEnvManager::addPathIfNotExists($path);
|
||||
$pathAfterSecondAdd = getenv('PATH');
|
||||
|
||||
// Should not add the same path twice
|
||||
$this->assertEquals($pathAfterFirstAdd, $pathAfterSecondAdd);
|
||||
}
|
||||
|
||||
public function testInitWithoutBuildRootPath(): void
|
||||
{
|
||||
// Temporarily unset BUILD_ROOT_PATH
|
||||
putenv('BUILD_ROOT_PATH');
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
GlobalEnvManager::init();
|
||||
}
|
||||
|
||||
public function testAfterInit(): void
|
||||
{
|
||||
// Set required environment variable
|
||||
putenv('BUILD_ROOT_PATH=/test/path');
|
||||
putenv('SPC_SKIP_TOOLCHAIN_CHECK=true');
|
||||
|
||||
// Should not throw exception when SPC_SKIP_TOOLCHAIN_CHECK is true
|
||||
GlobalEnvManager::afterInit();
|
||||
|
||||
$this->assertTrue(true); // Test passes if no exception is thrown
|
||||
}
|
||||
|
||||
public function envVariableProvider(): array
|
||||
{
|
||||
return [
|
||||
'simple-env' => ['TEST_VAR=test_value'],
|
||||
'complex-env' => ['COMPLEX_VAR=complex_value_with_spaces'],
|
||||
'numeric-env' => ['NUMERIC_VAR=123'],
|
||||
'special-chars-env' => ['SPECIAL_VAR=test@#$%'],
|
||||
];
|
||||
}
|
||||
|
||||
public function pathProvider(): array
|
||||
{
|
||||
return [
|
||||
'simple-path' => ['/test/path'],
|
||||
'complex-path' => ['/usr/local/bin'],
|
||||
'home-path' => ['/home/user/bin'],
|
||||
'root-path' => ['/root/bin'],
|
||||
];
|
||||
}
|
||||
}
|
||||
210
tests/SPC/util/PkgConfigUtilTest.php
Normal file
210
tests/SPC/util/PkgConfigUtilTest.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\util\PkgConfigUtil;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class PkgConfigUtilTest extends TestCase
|
||||
{
|
||||
private static string $originalPath;
|
||||
|
||||
private static string $fakePkgConfigPath;
|
||||
|
||||
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
|
||||
self::$originalPath = getenv('PATH');
|
||||
|
||||
// Create fake pkg-config directory
|
||||
self::$fakePkgConfigPath = sys_get_temp_dir() . '/fake-pkg-config-' . uniqid();
|
||||
mkdir(self::$fakePkgConfigPath, 0755, true);
|
||||
|
||||
// Create fake pkg-config executable
|
||||
self::createFakePkgConfig();
|
||||
|
||||
// Add fake pkg-config to PATH
|
||||
putenv('PATH=' . self::$fakePkgConfigPath . ':' . self::$originalPath);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
// Restore original PATH
|
||||
putenv('PATH=' . self::$originalPath);
|
||||
|
||||
// Clean up fake pkg-config
|
||||
if (is_dir(self::$fakePkgConfigPath)) {
|
||||
self::removeDirectory(self::$fakePkgConfigPath);
|
||||
}
|
||||
|
||||
parent::tearDownAfterClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validPackageProvider
|
||||
*/
|
||||
public function testGetCflagsWithValidPackage(string $package, string $expectedCflags): void
|
||||
{
|
||||
$result = PkgConfigUtil::getCflags($package);
|
||||
$this->assertEquals($expectedCflags, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider validPackageProvider
|
||||
*/
|
||||
public function testGetLibsArrayWithValidPackage(string $package, string $expectedCflags, array $expectedLibs): void
|
||||
{
|
||||
$result = PkgConfigUtil::getLibsArray($package);
|
||||
$this->assertEquals($expectedLibs, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider invalidPackageProvider
|
||||
*/
|
||||
public function testGetCflagsWithInvalidPackage(string $package): void
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
PkgConfigUtil::getCflags($package);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider invalidPackageProvider
|
||||
*/
|
||||
public function testGetLibsArrayWithInvalidPackage(string $package): void
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
PkgConfigUtil::getLibsArray($package);
|
||||
}
|
||||
|
||||
public static function invalidPackageProvider(): array
|
||||
{
|
||||
return [
|
||||
'invalid-package' => ['invalid-package'],
|
||||
'empty-string' => [''],
|
||||
'non-existent-package' => ['non-existent-package'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function validPackageProvider(): array
|
||||
{
|
||||
return [
|
||||
'libxml2' => ['libxml-2.0', '-I/usr/include/libxml2', ['-lxml2', '']],
|
||||
'zlib' => ['zlib', '-I/usr/include', ['-lz', '']],
|
||||
'openssl' => ['openssl', '-I/usr/include/openssl', ['-lssl', '-lcrypto', '']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fake pkg-config executable
|
||||
*/
|
||||
private static function createFakePkgConfig(): void
|
||||
{
|
||||
$pkgConfigScript = self::$fakePkgConfigPath . '/pkg-config';
|
||||
|
||||
$script = <<<'SCRIPT'
|
||||
#!/bin/bash
|
||||
|
||||
# Fake pkg-config script for testing
|
||||
# Shift arguments to get the package name
|
||||
shift
|
||||
|
||||
case "$1" in
|
||||
--cflags-only-other)
|
||||
shift
|
||||
case "$1" in
|
||||
libxml-2.0)
|
||||
echo "-I/usr/include/libxml2"
|
||||
;;
|
||||
zlib)
|
||||
echo "-I/usr/include"
|
||||
;;
|
||||
openssl)
|
||||
echo "-I/usr/include/openssl"
|
||||
;;
|
||||
*)
|
||||
echo "Package '$1' was not found in the pkg-config search path." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--libs-only-l)
|
||||
shift
|
||||
case "$1" in
|
||||
libxml-2.0)
|
||||
echo "-lxml2"
|
||||
;;
|
||||
zlib)
|
||||
echo "-lz"
|
||||
;;
|
||||
openssl)
|
||||
echo "-lssl -lcrypto"
|
||||
;;
|
||||
*)
|
||||
echo "Package '$1' was not found in the pkg-config search path." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--libs-only-other)
|
||||
shift
|
||||
case "$1" in
|
||||
libxml-2.0)
|
||||
echo ""
|
||||
;;
|
||||
zlib)
|
||||
echo ""
|
||||
;;
|
||||
openssl)
|
||||
echo ""
|
||||
;;
|
||||
*)
|
||||
echo "Package '$1' was not found in the pkg-config search path." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Usage: pkg-config [OPTION] [PACKAGE]" >&2
|
||||
echo "Try 'pkg-config --help' for more information." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
SCRIPT;
|
||||
|
||||
file_put_contents($pkgConfigScript, $script);
|
||||
chmod($pkgConfigScript, 0755);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove directory recursively
|
||||
*/
|
||||
private static function removeDirectory(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$path = $dir . '/' . $file;
|
||||
if (is_dir($path)) {
|
||||
self::removeDirectory($path);
|
||||
} else {
|
||||
unlink($path);
|
||||
}
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
|
||||
106
tests/SPC/util/SPCTargetTest.php
Normal file
106
tests/SPC/util/SPCTargetTest.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use SPC\util\SPCTarget;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class SPCTargetTest extends TestBase
|
||||
{
|
||||
private array $originalEnv;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Save original environment variables
|
||||
$this->originalEnv = [
|
||||
'SPC_TARGET' => getenv('SPC_TARGET'),
|
||||
'SPC_LIBC' => getenv('SPC_LIBC'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Restore original environment variables
|
||||
foreach ($this->originalEnv as $key => $value) {
|
||||
if ($value === false) {
|
||||
putenv($key);
|
||||
} else {
|
||||
putenv("{$key}={$value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider libcProvider
|
||||
*/
|
||||
public function testGetLibc(string $libc, bool $expected): void
|
||||
{
|
||||
putenv("SPC_LIBC={$libc}");
|
||||
|
||||
$result = SPCTarget::getLibc();
|
||||
if ($libc === '') {
|
||||
// When SPC_LIBC is set to empty string, getenv returns empty string, not false
|
||||
$this->assertEquals('', $result);
|
||||
} else {
|
||||
$this->assertEquals($libc, $result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider libcProvider
|
||||
*/
|
||||
public function testGetLibcVersion(string $libc): void
|
||||
{
|
||||
putenv("SPC_LIBC={$libc}");
|
||||
|
||||
$result = SPCTarget::getLibcVersion();
|
||||
// The actual result depends on the system, but it could be null if libc is not available
|
||||
$this->assertIsStringOrNull($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider targetOSProvider
|
||||
*/
|
||||
public function testGetTargetOS(string $target, string $expected): void
|
||||
{
|
||||
putenv("SPC_TARGET={$target}");
|
||||
|
||||
$result = SPCTarget::getTargetOS();
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
public function testLibcListConstant(): void
|
||||
{
|
||||
$this->assertIsArray(SPCTarget::LIBC_LIST);
|
||||
$this->assertContains('musl', SPCTarget::LIBC_LIST);
|
||||
$this->assertContains('glibc', SPCTarget::LIBC_LIST);
|
||||
}
|
||||
|
||||
public function libcProvider(): array
|
||||
{
|
||||
return [
|
||||
'musl' => ['musl', true],
|
||||
'glibc' => ['glibc', false],
|
||||
'empty' => ['', false],
|
||||
];
|
||||
}
|
||||
|
||||
public function targetOSProvider(): array
|
||||
{
|
||||
return [
|
||||
'linux-target' => ['native-linux', 'Linux'],
|
||||
'macos-target' => ['native-macos', 'Darwin'],
|
||||
'windows-target' => ['native-windows', 'Windows'],
|
||||
'empty-target' => ['', PHP_OS_FAMILY],
|
||||
];
|
||||
}
|
||||
|
||||
private function assertIsStringOrNull($value): void
|
||||
{
|
||||
$this->assertTrue(is_string($value) || is_null($value), 'Value must be string or null');
|
||||
}
|
||||
}
|
||||
100
tests/SPC/util/TestBase.php
Normal file
100
tests/SPC/util/TestBase.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Base test class for util tests with output suppression
|
||||
*/
|
||||
abstract class TestBase extends TestCase
|
||||
{
|
||||
protected $outputBuffer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->suppressOutput();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$this->restoreOutput();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress output during tests
|
||||
*/
|
||||
protected function suppressOutput(): void
|
||||
{
|
||||
// Start output buffering to capture PHP output
|
||||
$this->outputBuffer = ob_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore output after tests
|
||||
*/
|
||||
protected function restoreOutput(): void
|
||||
{
|
||||
// Clean output buffer
|
||||
if ($this->outputBuffer) {
|
||||
ob_end_clean();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a UnixShell instance with debug disabled to suppress logs
|
||||
*/
|
||||
protected function createUnixShell(): \SPC\util\UnixShell
|
||||
{
|
||||
return new \SPC\util\UnixShell(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a WindowsCmd instance with debug disabled to suppress logs
|
||||
*/
|
||||
protected function createWindowsCmd(): \SPC\util\WindowsCmd
|
||||
{
|
||||
return new \SPC\util\WindowsCmd(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test with output suppression
|
||||
*/
|
||||
protected function runWithOutputSuppression(callable $callback)
|
||||
{
|
||||
$this->suppressOutput();
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
$this->restoreOutput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command with output suppression
|
||||
*/
|
||||
protected function execWithSuppression(string $command): array
|
||||
{
|
||||
$this->suppressOutput();
|
||||
try {
|
||||
exec($command, $output, $returnCode);
|
||||
return [$returnCode, $output];
|
||||
} finally {
|
||||
$this->restoreOutput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command with output redirected to /dev/null
|
||||
*/
|
||||
protected function execSilently(string $command): array
|
||||
{
|
||||
$command .= ' 2>/dev/null 1>/dev/null';
|
||||
exec($command, $output, $returnCode);
|
||||
return [$returnCode, $output];
|
||||
}
|
||||
}
|
||||
184
tests/SPC/util/UnixShellTest.php
Normal file
184
tests/SPC/util/UnixShellTest.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\util\UnixShell;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class UnixShellTest extends TestBase
|
||||
{
|
||||
public function testConstructorOnWindows(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Windows') {
|
||||
$this->markTestSkipped('This test is for Windows systems only');
|
||||
}
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Windows cannot use UnixShell');
|
||||
|
||||
new UnixShell();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider envProvider
|
||||
*/
|
||||
public function testSetEnv(array $env): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$result = $shell->setEnv($env);
|
||||
|
||||
$this->assertSame($shell, $result);
|
||||
foreach ($env as $item) {
|
||||
if (trim($item) !== '') {
|
||||
$this->assertStringContainsString($item, $shell->getEnvString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider envProvider
|
||||
*/
|
||||
public function testAppendEnv(array $env): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$shell->setEnv(['CFLAGS' => '-O2']);
|
||||
|
||||
$shell->appendEnv($env);
|
||||
|
||||
$this->assertStringContainsString('-O2', $shell->getEnvString());
|
||||
foreach ($env as $value) {
|
||||
if (trim($value) !== '') {
|
||||
$this->assertStringContainsString($value, $shell->getEnvString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider envProvider
|
||||
*/
|
||||
public function testGetEnvString(array $env): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$shell->setEnv($env);
|
||||
|
||||
$envString = $shell->getEnvString();
|
||||
|
||||
$hasNonEmptyValues = false;
|
||||
foreach ($env as $key => $value) {
|
||||
if (trim($value) !== '') {
|
||||
$this->assertStringContainsString("{$key}=\"{$value}\"", $envString);
|
||||
$hasNonEmptyValues = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If all values are empty, ensure we still have a test assertion
|
||||
if (!$hasNonEmptyValues) {
|
||||
$this->assertIsString($envString);
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetEnvStringWithEmptyEnv(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$envString = $shell->getEnvString();
|
||||
|
||||
$this->assertEquals('', trim($envString));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider commandProvider
|
||||
*/
|
||||
public function testExecWithResult(string $command): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
[$code, $output] = $shell->execWithResult($command);
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertIsArray($output);
|
||||
}
|
||||
|
||||
public function testExecWithResultWithLog(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
[$code, $output] = $shell->execWithResult('echo "test"', false);
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertIsArray($output);
|
||||
$this->assertEquals(0, $code);
|
||||
$this->assertEquals(['test'], $output);
|
||||
}
|
||||
|
||||
public function testExecWithResultWithCd(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$shell = $this->createUnixShell();
|
||||
$shell->cd('/tmp');
|
||||
|
||||
[$code, $output] = $shell->execWithResult('pwd');
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertEquals(0, $code);
|
||||
$this->assertIsArray($output);
|
||||
}
|
||||
|
||||
public static function directoryProvider(): array
|
||||
{
|
||||
return [
|
||||
'simple-directory' => ['/test/directory'],
|
||||
'home-directory' => ['/home/user'],
|
||||
'root-directory' => ['/root'],
|
||||
'tmp-directory' => ['/tmp'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function envProvider(): array
|
||||
{
|
||||
return [
|
||||
'simple-env' => [['CFLAGS' => '-O2', 'LDFLAGS' => '-L/usr/lib']],
|
||||
'complex-env' => [['CXXFLAGS' => '-std=c++11', 'LIBS' => '-lz -lxml']],
|
||||
'empty-env' => [['CFLAGS' => '', 'LDFLAGS' => ' ']],
|
||||
'mixed-env' => [['CFLAGS' => '-O2', 'EMPTY_VAR' => '']],
|
||||
];
|
||||
}
|
||||
|
||||
public static function commandProvider(): array
|
||||
{
|
||||
return [
|
||||
'echo-command' => ['echo "test"'],
|
||||
'pwd-command' => ['pwd'],
|
||||
'ls-command' => ['ls -la'],
|
||||
];
|
||||
}
|
||||
}
|
||||
68
tests/SPC/util/WindowsCmdTest.php
Normal file
68
tests/SPC/util/WindowsCmdTest.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\Tests\util;
|
||||
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\util\WindowsCmd;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class WindowsCmdTest extends TestBase
|
||||
{
|
||||
public function testConstructorOnUnix(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
$this->markTestSkipped('This test is for Unix systems only');
|
||||
}
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Only windows can use WindowsCmd');
|
||||
|
||||
new WindowsCmd();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider commandProvider
|
||||
*/
|
||||
public function testExecWithResult(string $command): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Windows') {
|
||||
$this->markTestSkipped('This test is for Windows systems only');
|
||||
}
|
||||
|
||||
$cmd = $this->createWindowsCmd();
|
||||
[$code, $output] = $cmd->execWithResult($command);
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertEquals(0, $code);
|
||||
$this->assertIsArray($output);
|
||||
$this->assertNotEmpty($output);
|
||||
}
|
||||
|
||||
public function testExecWithResultWithLog(): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Windows') {
|
||||
$this->markTestSkipped('This test is for Windows systems only');
|
||||
}
|
||||
|
||||
$cmd = $this->createWindowsCmd();
|
||||
[$code, $output] = $cmd->execWithResult('echo test', false);
|
||||
|
||||
$this->assertIsInt($code);
|
||||
$this->assertIsArray($output);
|
||||
$this->assertEquals(0, $code);
|
||||
$this->assertEquals(['test'], $output);
|
||||
}
|
||||
|
||||
public static function commandProvider(): array
|
||||
{
|
||||
return [
|
||||
'echo-command' => ['echo test'],
|
||||
'dir-command' => ['dir'],
|
||||
'cd-command' => ['cd'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user