Compare commits

...

75 Commits

Author SHA1 Message Date
crazywhalecc
e4d8e5e4d2 fix ncurses build command 2024-03-10 11:53:33 +08:00
crazywhalecc
88796bc017 update tests 2024-03-10 11:53:33 +08:00
crazywhalecc
e23daaa355 enhancement for download command 2024-03-10 11:53:33 +08:00
crazywhalecc
71017361b5 fix libxml2 build on RHEL/CentOS bug 2024-03-08 14:18:33 +08:00
crazywhalecc
d202de3f50 fix libxml2 build on RHEL/CentOS bug 2024-03-08 14:11:04 +08:00
Jerry Ma
03510073c6 Fix windows curl build (#368)
* fix curl on windows build needs nghttp2.dll bug

* add curl on windows tests

* cs fix

* fix curl headers

* exit powershell properly

* reproduce zend_mm_heap corrupted

* reproduce zend_mm_heap corrupted

* reproduce zend_mm_heap corrupted

* add for-libs option for download

* add for-libs option for download

* add for-libs option for download
2024-03-05 21:43:09 +08:00
Jerry Ma
8e58592a6e Fix swoole compile bug on Linux (#367)
* swoole ci test

* swoole ci test

* fix swoole (disable-thread-context)

* restore pgsql ver

* bump version to 2.1.4
2024-03-04 15:31:39 +08:00
Jerry Ma
96dd5ba87b Add amqp/librabbitmq support for linux, macos, windows (#366)
* add amqp/librabbitmq support for linux, macos, windows

* add test for amqp
2024-03-04 10:40:23 +08:00
Jerry Ma
d4c0290195 Fix libuuid random bug when make clean (#364)
* fix libuuid random bug when `make clean`

* test

* test [skip ci]
2024-03-01 21:27:51 +08:00
Jerry Ma
f5d1df5407 add uuid/libuuid for linux and macos (#363) 2024-03-01 20:10:48 +08:00
Jerry Ma
9664709f21 Add libtiff support (#361)
* add libtiff support

* fix command option not working on *nix

* fix test with libs ext test
2024-03-01 19:19:47 +08:00
Jerry Ma
b46655ecfe Add custom Windows micro logo support (#358)
* add custom windows micro logo option `--with-micro-logo`

* bump version 2.1.2
2024-02-29 15:35:02 +08:00
Jerry Ma
842e0add29 Add ffi support for windows x64 (#357)
* add ffi support for windows x64

* add ffi test
2024-02-29 15:34:06 +08:00
crazywhalecc
254764761d bump version to 2.1.1 2024-02-26 20:11:09 +08:00
Jerry Ma
5f6c1a0f40 update postgresql version to 16.2 (#355)
* update postgresql version to 16.2

* add tests

* add tests
2024-02-26 19:55:47 +08:00
crazywhalecc
d5dcd193cf update generated spc binary 2024-02-26 00:19:30 +08:00
Jerry Ma
5012da96be Update ConsoleApplication.php 2024-02-23 11:42:10 +08:00
Jerry Ma
df0e37cd0f Add mbregex support for windows (#351)
* add mbregex support for windows

* cs fix

* fix curl http2 support
2024-02-23 11:41:35 +08:00
Jerry Ma
4ab7b6bfdc Add sqlite support for Windows (#350)
* add sqlite support

* cs fix
2024-02-23 11:14:51 +08:00
Jerry Ma
f498250001 Add multiple XML related extensions support for Windows (#349)
* update libxml2 version

* use msys2 tar.exe instead of system32/tar.exe

* add iconv, xml, dom, xmlreader, xmlwriter, soap, libxml, simplexml support

* add test

* add sysvshm support

* add quote

* add debug

* use mingw target

* fix windows tar

* fix windows tar

* fix windows tar

* fix windows tar

* fix windows tar [skip ci]
2024-02-23 00:56:28 +08:00
Jerry Ma
3945ac037b Add curl and ssh2 support for windows (#348)
* add curl and ssh2 support for windows

* add curl and ssh2 test for windows

* cs fix

* update README [skip ci]

* update README [skip ci]

* update README [skip ci]

* update README [skip ci]

* update README [skip ci]
2024-02-22 14:37:10 +08:00
crazywhalecc
b0d8b00fcc optimize pkg-config build process 2024-02-20 15:45:00 +08:00
crazywhalecc
62b0bf8af0 fix no-strip not working for linux micro 2024-02-20 10:56:39 +08:00
crazywhalecc
905e080770 fix upx linux build 2024-02-19 15:29:43 +08:00
crazywhalecc
097ecd3fb0 test build spc 2024-02-19 13:43:58 +08:00
crazywhalecc
b9359759dd update README 2024-02-19 13:25:47 +08:00
crazywhalecc
50fe366c42 restore strip line 2024-02-19 13:25:47 +08:00
crazywhalecc
9dd89e6b02 add --with-upx-pack command 2024-02-19 13:25:47 +08:00
crazywhalecc
c03220d1ee fix source extract 2024-02-19 13:25:47 +08:00
crazywhalecc
9db843ab66 fix source extract 2024-02-19 13:25:47 +08:00
Jerry Ma
304973d9bc Update FUNDING.yml 2024-02-19 09:33:54 +08:00
Jerry Ma
ab386f820c Update FUNDING.yml 2024-02-19 09:29:45 +08:00
crazywhalecc
4adf1f5e2e add musl-toolchain pkg for aarch64 2024-02-18 16:22:17 +08:00
crazywhalecc
b4ae87585c treat https://github.com/crazywhalecc/static-php-cli/issues/249 as feature 2024-02-18 16:22:17 +08:00
crazywhalecc
ae3298472d upx use ghrel 2024-02-18 16:22:17 +08:00
crazywhalecc
d241cb993e fix 2024-02-18 16:22:17 +08:00
crazywhalecc
8376122634 sort pkg.json 2024-02-18 16:22:17 +08:00
crazywhalecc
a30e054d7d Add package management 2024-02-18 16:22:17 +08:00
Jerry Ma
78f4317660 Merge pull request #341 from crazywhalecc/no-ansi-build
Apply default `--no-ansi` output
2024-02-18 13:54:27 +08:00
crazywhalecc
3ac3dab6c8 apply default no-ansi output 2024-02-18 09:44:04 +08:00
Jerry Ma
1a7e436ee1 Merge pull request #339 from crazywhalecc/ext/gettext
Add gettext support
2024-02-17 01:31:10 +08:00
crazywhalecc
f11b36ab3c Merge remote-tracking branch 'origin/main' into ext/gettext 2024-02-17 00:47:40 +08:00
Jerry Ma
e2ef195a84 Update release-build.yml 2024-02-17 00:44:03 +08:00
crazywhalecc
645e2a9fc2 update to beta 3 2024-02-17 00:37:09 +08:00
crazywhalecc
d59b8457c6 update composer 2024-02-17 00:33:23 +08:00
crazywhalecc
a2d1262cbf update composer.lock 2024-02-17 00:25:25 +08:00
Jerry Ma
2f9a1e8601 Update release-build.yml 2024-02-17 00:09:46 +08:00
Jerry Ma
b239f60fe4 Update release-build.yml 2024-02-17 00:09:32 +08:00
Jerry Ma
983521e225 Update release-build.yml 2024-02-17 00:07:30 +08:00
Jerry Ma
aeed04a5ec Update release-build.yml 2024-02-17 00:04:02 +08:00
crazywhalecc
faed569e8a fix gettext build for linux 2024-02-16 23:28:14 +08:00
crazywhalecc
49ddb3ec13 fix gettext build 2024-02-16 23:04:58 +08:00
crazywhalecc
71c0387ab0 remove tune flags, add debug output for extension sanity check 2024-02-16 20:17:34 +08:00
crazywhalecc
e5d2d5e689 Merge branch 'main' into ext/gettext
# Conflicts:
#	config/ext.json
#	src/globals/test-extensions.php
2024-02-16 19:42:18 +08:00
Jerry Ma
be2394b39b Merge pull request #329 from crazywhalecc/suggest-cmd
add with-suggested-libs and with-suggested-exts
2024-02-16 19:36:54 +08:00
crazywhalecc
158298b96c Merge branch 'main' into suggest-cmd
# Conflicts:
#	src/globals/test-extensions.php
2024-02-16 18:58:11 +08:00
crazywhalecc
ffa84f8b91 remove unused exts 2024-02-16 18:57:44 +08:00
crazywhalecc
0954ddcc96 refactor some terminal outputs 2024-02-16 18:57:32 +08:00
crazywhalecc
d9bd96af71 add dependency util tests 2024-02-16 18:57:12 +08:00
crazywhalecc
2649dcd05c add BuilderBase::getPHPVersionFromArchive 2024-02-16 18:56:59 +08:00
crazywhalecc
939db75268 refactor DependencyUtil, use for-libs and for-sources instead of by 2024-02-16 18:56:33 +08:00
crazywhalecc
227bf73870 libxml2 use zlib prefix 2024-02-16 01:45:14 +08:00
crazywhalecc
05e3898e7a add gettext support 2024-02-16 01:28:10 +08:00
Jerry Ma
9777c9aa93 Fix openpty bug for linux (#337)
* fix openpty bug for linux

* add event test

* add download retry for download command

* use test token

* use test token

* use test token

* use test token

* re-fix this bug
2024-02-14 00:49:58 +08:00
Viktor Szépe
b8d8461e61 Fix badges in README (#333)
* Fix badges in README

* Remove Discord badge

* Fix badges in README-zh
2024-02-12 19:59:51 +08:00
Viktor Szépe
b977543c72 Update README.md 2024-02-12 19:12:43 +08:00
Viktor Szépe
4d87cd11cc Update README-zh.md 2024-02-12 19:12:43 +08:00
Viktor Szépe
957daf0547 Update README.md 2024-02-12 19:12:43 +08:00
Viktor Szépe
3dae904122 Update README-zh.md 2024-02-12 19:12:43 +08:00
Viktor Szépe
e835196972 Add language badges to README 2024-02-12 19:12:43 +08:00
crazywhalecc
52ed0e2cee add glibtoolize check for macos 2024-02-07 01:19:54 +08:00
crazywhalecc
1e898d271d add with-suggested-libs and with-suggested-exts 2024-02-06 16:06:09 +08:00
crazywhalecc
39754cde59 add micro patcher for php84 2024-02-06 15:56:47 +08:00
Peter Kokot
a6f7b938e1 Fix PHP version ID 2024-02-05 20:05:54 +08:00
Peter Kokot
0ad501af9a Patch for PHP >= 8.4
FIBER_ASSEMBLER and FIBER_ASM_ARCH Makefile variables in Windows build
system PHP 8.4 have been removed in favor of the PHP_ASSEMBLER and
FIBER_ASM_ABI.
2024-02-05 20:05:54 +08:00
87 changed files with 2841 additions and 813 deletions

View File

@@ -15,14 +15,15 @@ jobs:
strategy: strategy:
matrix: matrix:
php-version: php-version:
- "8.1" - "8.2"
micro-version: micro-version:
- "8.1.26" - "8.2.16"
operating-system: operating-system:
- "linux-x86_64" - "linux-x86_64"
- "macos-x86_64" - "macos-x86_64"
- "linux-aarch64" - "linux-aarch64"
- "macos-aarch64" - "macos-aarch64"
- "windows-x64"
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: "actions/checkout@v4" uses: "actions/checkout@v4"
@@ -59,31 +60,51 @@ jobs:
- name: "Download minimal combination" - name: "Download minimal combination"
run: | run: |
curl https://dl.static-php.dev/static-php-cli/minimal/php-${{ matrix.micro-version }}-micro-${{ matrix.operating-system }}.tar.gz -o tmp.tgz if [ "${{ matrix.operating-system }}" = "windows-x64" ]; then
tar -zxvf tmp.tgz curl https://dl.static-php.dev/static-php-cli/windows/spc-min/php-${{ matrix.micro-version }}-micro-win.zip -o tmp.zip
unzip tmp.zip
else
curl https://dl.static-php.dev/static-php-cli/minimal/php-${{ matrix.micro-version }}-micro-${{ matrix.operating-system }}.tar.gz -o tmp.tgz
tar -zxvf tmp.tgz
fi
- name: "Generate Executable" - name: "Generate Executable"
run: | run: |
cat micro.sfx spc.phar > spc bin/spc micro:combine spc.phar -M micro.sfx -O spc -I "memory_limit=2G"
chmod +x spc if [ "${{ matrix.operating-system }}" = "windows-x64" ]; then
mv spc spc.exe
else
chmod +x spc
fi
- name: "Archive Executable" - name: "Archive Executable"
run: | run: |
tar -czf spc-${{ matrix.operating-system }}.tar.gz spc if [ "${{ matrix.operating-system }}" != "windows-x64" ]; then
echo "filename=spc-${{ matrix.operating-system }}.tar.gz" >> $GITHUB_ENV tar -czf spc-${{ matrix.operating-system }}.tar.gz spc
echo "OS=${{ matrix.operating-system }}" >> $GITHUB_ENV echo "filename=spc-${{ matrix.operating-system }}.tar.gz" >> $GITHUB_ENV
if [ "${{ matrix.operating-system }}" == "linux-x86_64" ]; then echo "OS=${{ matrix.operating-system }}" >> $GITHUB_ENV
./spc dev:extensions if [ "${{ matrix.operating-system }}" == "linux-x86_64" ]; then
./spc dev:extensions
fi
else
echo "filename=spc-${{ matrix.operating-system }}.exe" >> $GITHUB_ENV
echo "OS=${{ matrix.operating-system }}" >> $GITHUB_ENV
fi fi
- name: "Copy file" - name: "Copy file"
run: "mkdir dist/ && cp ${{ env.filename }} dist/ && cp spc dist/spc-$OS" run: |
if [ "${{ matrix.operating-system }}" != "windows-x64" ]; then
mkdir dist/ && cp ${{ env.filename }} dist/ && cp spc dist/spc-$OS
else
mkdir dist/ && cp spc.exe dist/${{ env.filename }}
echo "SUFFIX=.exe" >> $GITHUB_ENV
fi
- name: upload binaries to release - name: upload binaries to release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: ${{startsWith(github.ref, 'refs/tags/') }} if: ${{startsWith(github.ref, 'refs/tags/') }}
with: with:
files: ${{ env.filename }} files: dist/${{ env.filename }}
- name: "Deploy to Self-Hosted Server" - name: "Deploy to Self-Hosted Server"
if: github.repository == 'crazywhalecc/static-php-cli' if: github.repository == 'crazywhalecc/static-php-cli'
@@ -100,5 +121,5 @@ jobs:
- name: "Upload Artifact" - name: "Upload Artifact"
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
path: spc path: spc${{ env.SUFFIX }}
name: spc-${{ matrix.operating-system }} name: spc-${{ matrix.operating-system }}${{ env.SUFFIX }}

View File

@@ -129,6 +129,13 @@ jobs:
extensions: curl, openssl, mbstring extensions: curl, openssl, mbstring
ini-values: memory_limit=-1 ini-values: memory_limit=-1
- name: "Use test token if exists"
if: matrix.os != 'windows-latest'
run: |
if [ "${{ secrets.TEST_GH_TOKEN }}" != "" ]; then
echo "GITHUB_TOKEN=${{ secrets.TEST_GH_TOKEN }}" >> $GITHUB_ENV
fi
- name: "Cache Composer packages" - name: "Cache Composer packages"
id: composer-cache id: composer-cache
uses: actions/cache@v3 uses: actions/cache@v3
@@ -152,7 +159,13 @@ jobs:
run: bin/spc doctor --auto-fix run: bin/spc doctor --auto-fix
- name: "Run Build Tests (download)" - name: "Run Build Tests (download)"
run: bin/spc download --for-extensions="$(php src/globals/test-extensions.php extensions)" --with-php=${{ matrix.php }} --debug uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
retry_on: error
command: |
bin/spc download --for-extensions="$(php src/globals/test-extensions.php extensions)" --for-libs="$(php src/globals/test-extensions.php libs)" --with-php=${{ matrix.php }} --ignore-cache-sources=php-src --debug
- name: "Run Build Tests (build)" - name: "Run Build Tests (build)"
run: bin/spc build "$(php src/globals/test-extensions.php extensions)" $(php src/globals/test-extensions.php libs_cmd) --build-cli --build-micro --build-fpm --debug run: bin/spc build "$(php src/globals/test-extensions.php extensions)" $(php src/globals/test-extensions.php libs_cmd) --build-cli --build-micro --build-fpm --debug

3
.gitignore vendored
View File

@@ -16,6 +16,9 @@ docker/source/
# default source build root directory # default source build root directory
/buildroot/ /buildroot/
# default package root directory
/pkgroot/
# tools cache files # tools cache files
.php-cs-fixer.cache .php-cs-fixer.cache
.phpunit.result.cache .phpunit.result.cache

View File

@@ -1,10 +1,11 @@
# static-php-cli # static-php-cli
[![Version](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)]() [![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md)
[![](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml) [![Chinese readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87%20%F0%9F%87%A8%F0%9F%87%B3-moccasin?style=flat-square)](README-zh.md)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)]() [![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![](https://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)]([https://static-php.dev/](https://static-php.dev/en/guide/extensions.html)) [![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![](https://dcbadge.vercel.app/api/server/RNpegEYW?style=flat-square&compact=true&theme=default-inverted)](https://discord.gg/RNpegEYW) [![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
[![Extensions](https://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)](https://static-php.dev/zh/guide/extensions.html)
**static-php-cli**是一个用于静态编译、构建 PHP 解释器的工具,支持众多流行扩展。 **static-php-cli**是一个用于静态编译、构建 PHP 解释器的工具,支持众多流行扩展。
@@ -12,9 +13,6 @@
**static-php-cli**也支持将 PHP 代码和 PHP 运行时打包为一个文件并运行。 **static-php-cli**也支持将 PHP 代码和 PHP 运行时打包为一个文件并运行。
- [README - English](./README.md)
- [README - 中文](./README-zh.md)
## 特性 ## 特性
static-php-cli简称 `spc`)有许多特性: static-php-cli简称 `spc`)有许多特性:
@@ -22,11 +20,12 @@ static-php-cli简称 `spc`)有许多特性:
- :handbag: 构建独立的单文件 PHP 解释器,无需任何依赖 - :handbag: 构建独立的单文件 PHP 解释器,无需任何依赖
- :hamburger: 构建 **[phpmicro](https://github.com/dixyes/phpmicro)** 自执行二进制(将 PHP 代码和 PHP 解释器打包为一个文件) - :hamburger: 构建 **[phpmicro](https://github.com/dixyes/phpmicro)** 自执行二进制(将 PHP 代码和 PHP 解释器打包为一个文件)
- :pill: 提供一键检查和修复编译环境的 Doctor 模块 - :pill: 提供一键检查和修复编译环境的 Doctor 模块
- :zap: 支持多个系统:`Linux``macOS``FreeBSD`[`Windows (WIP)`](https://github.com/crazywhalecc/static-php-cli/pull/301) - :zap: 支持多个系统:`Linux``macOS``FreeBSD``Windows`
- :wrench: 高度自定义的代码 patch 功能 - :wrench: 高度自定义的代码 patch 功能
- :books: 自带编译依赖管理 - :books: 自带编译依赖管理
- 📦 提供由自身编译的独立 `spc` 二进制(使用 spc 和 [box](https://github.com/box-project/box) 构建) - 📦 提供由自身编译的独立 `spc` 二进制(使用 spc 和 [box](https://github.com/box-project/box) 构建)
- :fire: 支持大量 [扩展](https://static-php.dev/zh/guide/extensions.html) - :fire: 支持大量 [扩展](https://static-php.dev/zh/guide/extensions.html)
- :floppy_disk: 整合 UPX 工具(减小二进制文件体积)
**静态 php-cli:** **静态 php-cli:**
@@ -48,16 +47,18 @@ static-php-cli简称 `spc`)有许多特性:
- [扩展组合 - bulk](https://dl.static-php.dev/static-php-cli/bulk/)bulk 组合包含了 [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) 个扩展,体积为 70MB 左右。 - [扩展组合 - bulk](https://dl.static-php.dev/static-php-cli/bulk/)bulk 组合包含了 [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) 个扩展,体积为 70MB 左右。
- [扩展组合 - minimal](https://dl.static-php.dev/static-php-cli/minimal/)minimal 组合包含了 [5](https://dl.static-php.dev/static-php-cli/minimal/README.txt) 个扩展,体积为 6MB 左右。 - [扩展组合 - minimal](https://dl.static-php.dev/static-php-cli/minimal/)minimal 组合包含了 [5](https://dl.static-php.dev/static-php-cli/minimal/README.txt) 个扩展,体积为 6MB 左右。
对于 Windows 系统,目前支持的扩展较少,故仅提供 SPC 自身运行的最小扩展组合的 `cli``micro`[扩展组合 - spc-min](https://dl.static-php.dev/static-php-cli/windows/spc-min/)。
## 使用 static-php-cli 构建 PHP ## 使用 static-php-cli 构建 PHP
### 编译环境需求 ### 编译环境需求
- PHP >= 8.1(这是 spc 自身需要的版本,不是支持的构建版本) - PHP >= 8.1(这是 spc 自身需要的版本,不是支持的构建版本)
- 扩展:`mbstring,pcntl,posix,tokenizer,phar` - 扩展:`mbstring,tokenizer,phar`
- 系统安装了 `curl``git` - 系统安装了 `curl``git`
是的,本项目采用 PHP 编写,编译前需要一个 PHP 环境,比较滑稽。 是的,本项目采用 PHP 编写,编译前需要一个 PHP 环境,比较滑稽。
但本项目默认可通过自身构建的 micro 和 static-php 二进制运行,其他只需要包含 mbstring、pcntl 扩展和 PHP 版本大于等于 8.1 即可。 但本项目默认可通过自身构建的 micro 和 static-php 二进制运行,其他只需要包含上面提到的扩展和 PHP 版本大于等于 8.1 即可。
下面是架构支持情况,:octocat: 代表支持 GitHub Action 构建,:computer: 代表支持本地构建,空 代表暂不支持。 下面是架构支持情况,:octocat: 代表支持 GitHub Action 构建,:computer: 代表支持本地构建,空 代表暂不支持。
@@ -65,10 +66,26 @@ static-php-cli简称 `spc`)有许多特性:
|---------|----------------------|----------------------| |---------|----------------------|----------------------|
| macOS | :octocat: :computer: | :octocat: :computer: | | macOS | :octocat: :computer: | :octocat: :computer: |
| Linux | :octocat: :computer: | :octocat: :computer: | | Linux | :octocat: :computer: | :octocat: :computer: |
| Windows | | | | Windows | :computer: | |
| FreeBSD | :computer: | :computer: | | FreeBSD | :computer: | :computer: |
前支持编译的 PHP 版本为:`7.3``7.4``8.0``8.1``8.2``8.3` 前支持编译的 PHP 版本
> :warning: 支持,但可能不再提供修复
>
> :heavy_check_mark: 支持
>
> :x: 不支持
| PHP Version | Status | Comment |
|-------------|--------------------|------------------------------|
| 7.2 | :x: | |
| 7.3 | :warning: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
| 7.4 | :warning: | phpmicro 和许多扩展不支持 7.3、7.4 版本 |
| 8.0 | :heavy_check_mark: | PHP 官方已停止 8.0 的维护 |
| 8.1 | :heavy_check_mark: | |
| 8.2 | :heavy_check_mark: | |
| 8.3 | :heavy_check_mark: | |
### 支持的扩展 ### 支持的扩展
@@ -108,10 +125,16 @@ curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-a
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64 curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple) # macOS aarch64 (Apple)
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64 curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
# Windows (x86_64, win10 build 17063 or later)
curl.exe -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
# add x perm # Add execute perm (Linux and macOS only)
chmod +x ./spc chmod +x ./spc
# Run (Linux and macOS)
./spc --version ./spc --version
# Run (Windows powershell)
.\spc.exe --version
``` ```
自托管 `spc` 由 GitHub Actions 构建,你也可以从 Actions 直接下载:[此处](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml)。 自托管 `spc` 由 GitHub Actions 构建,你也可以从 Actions 直接下载:[此处](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml)。
@@ -150,14 +173,16 @@ bin/spc --version
# 拉取所有依赖库 # 拉取所有依赖库
./bin/spc download --all ./bin/spc download --all
# 只拉取编译指定扩展需要的所有依赖(推荐) # 只拉取编译指定扩展需要的所有依赖(推荐)
./bin/spc download --for-extensions=openssl,pcntl,mbstring,pdo_sqlite ./bin/spc download --for-extensions="openssl,pcntl,mbstring,pdo_sqlite"
# 下载编译不同版本的 PHP (--with-php=x.y推荐 7.3 ~ 8.3) # 下载编译不同版本的 PHP (--with-php=x.y推荐 7.3 ~ 8.3)
./bin/spc download --for-extensions=openssl,curl,mbstring --with-php=8.1 ./bin/spc download --for-extensions="openssl,curl,mbstring" --with-php=8.1
# 构建包含 bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl 扩展的 php-cli 和 micro.sfx # 构建包含 bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl 扩展的 php-cli 和 micro.sfx
./bin/spc build "bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl" --build-cli --build-micro ./bin/spc build "bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl" --build-cli --build-micro
# 编译线程安全版本 (--enable-zts) # 编译线程安全版本 (--enable-zts)
./bin/spc build curl,phar --enable-zts --build-cli ./bin/spc build "curl,phar" --enable-zts --build-cli
# 编译后使用 UPX 减小可执行文件体积 (--with-upx-pack) (至少压缩至原来的 30~50%)
./bin/spc build "curl,phar" --enable-zts --build-cli --with-upx-pack
``` ```
其中,目前支持构建 climicrofpm 和 embed使用以下参数的一个或多个来指定编译的 SAPI 其中,目前支持构建 climicrofpm 和 embed使用以下参数的一个或多个来指定编译的 SAPI
@@ -171,7 +196,7 @@ bin/spc --version
如果出现了任何错误,可以使用 `--debug` 参数来展示完整的输出日志,以供排查错误: 如果出现了任何错误,可以使用 `--debug` 参数来展示完整的输出日志,以供排查错误:
```bash ```bash
./bin/spc build openssl,pcntl,mbstring --debug --build-all ./bin/spc build "openssl,pcntl,mbstring" --debug --build-all
./bin/spc download --all --debug ./bin/spc download --all --debug
``` ```
@@ -250,7 +275,7 @@ bin/spc micro:combine my-app.phar -I "memory_limit=4G" -I "disable_functions=sys
## 赞助本项目 ## 赞助本项目
你可以在 [我的个人赞助页](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md) 支持我和我的项目。 你可以在 [我的个人赞助页](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md) 支持我和我的项目。你捐赠的一部分将会被用于维护 **static-php.dev** 服务器。
## 开源协议 ## 开源协议

View File

@@ -1,10 +1,11 @@
# static-php-cli # static-php-cli
[![Version](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)]() [![Chinese readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87%20%F0%9F%87%A8%F0%9F%87%B3-moccasin?style=flat-square)](README-zh.md)
[![](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml) [![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)]() [![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![](https://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)]([https://static-php.dev/](https://static-php.dev/en/guide/extensions.html)) [![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![](https://dcbadge.vercel.app/api/server/RNpegEYW?style=flat-square&compact=true&theme=default-inverted)](https://discord.gg/RNpegEYW) [![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
[![Extensions](https://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)](https://static-php.dev/en/guide/extensions.html)
**static-php-cli** is a powerful tool designed for building static, standalone PHP runtime **static-php-cli** is a powerful tool designed for building static, standalone PHP runtime
with popular extensions. with popular extensions.
@@ -14,9 +15,6 @@ Static PHP built by **static-php-cli** supports `cli`, `fpm`, `embed` and `micro
**static-php-cli** also has the ability to package PHP projects **static-php-cli** also has the ability to package PHP projects
along with the PHP interpreter into one single executable file. along with the PHP interpreter into one single executable file.
- [README - English](./README.md)
- [README - 中文](./README-zh.md)
## Features ## Features
static-php-cli (you can call it `spc`) has a lot of features: static-php-cli (you can call it `spc`) has a lot of features:
@@ -29,6 +27,7 @@ static-php-cli (you can call it `spc`) has a lot of features:
- :books: Build dependency management - :books: Build dependency management
- 📦 Provide `spc` own standalone executable (built by spc and [box](https://github.com/box-project/box)) - 📦 Provide `spc` own standalone executable (built by spc and [box](https://github.com/box-project/box))
- :fire: Support many popular [extensions](https://static-php.dev/en/guide/extensions.html) - :fire: Support many popular [extensions](https://static-php.dev/en/guide/extensions.html)
- :floppy_disk: UPX integration (significantly reduces binary size)
**Single-file standalone php-cli:** **Single-file standalone php-cli:**
@@ -54,16 +53,19 @@ which can be downloaded directly according to your needs.
- [Extension-Combination - bulk](https://dl.static-php.dev/static-php-cli/bulk/): `bulk` contains [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) extensions and is about 70MB in size. - [Extension-Combination - bulk](https://dl.static-php.dev/static-php-cli/bulk/): `bulk` contains [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) extensions and is about 70MB in size.
- [Extension-Combination - minimal](https://dl.static-php.dev/static-php-cli/minimal/): `minimal` contains [5](https://dl.static-php.dev/static-php-cli/minimal/README.txt) extensions and is about 6MB in size. - [Extension-Combination - minimal](https://dl.static-php.dev/static-php-cli/minimal/): `minimal` contains [5](https://dl.static-php.dev/static-php-cli/minimal/README.txt) extensions and is about 6MB in size.
For Windows systems, there are currently fewer extensions supported,
so only `cli` and `micro` that run the minimum extension combination of SPC itself are provided: [Extension-Combination - spc-min](https://dl.static-php.dev/static-php-cli/windows/spc-min/).
## Build ## Build
### Compilation Requirements ### Compilation Requirements
- PHP >= 8.1 (This is the version required by spc itself, not the build version)
- Extension: `mbstring,pcntl,posix,tokenizer,phar`
- Supported OS with `curl` and `git` installed
You can say I made a PHP builder written in PHP, pretty funny. You can say I made a PHP builder written in PHP, pretty funny.
But static-php-cli runtime only requires an environment above PHP 8.1 and `mbstring`, `pcntl` extension. But static-php-cli runtime only requires an environment above PHP 8.1 and extensions mentioned below.
- PHP >= 8.1 (This is the version required by spc itself, not the build version)
- Extension: `mbstring,tokenizer,phar`
- Supported OS with `curl` and `git` installed
Here is the supported OS and arch, where :octocat: represents support for GitHub Action builds, Here is the supported OS and arch, where :octocat: represents support for GitHub Action builds,
:computer: represents support for local manual builds, and blank represents not currently supported. :computer: represents support for local manual builds, and blank represents not currently supported.
@@ -75,7 +77,23 @@ Here is the supported OS and arch, where :octocat: represents support for GitHub
| Windows | :computer: | | | Windows | :computer: | |
| FreeBSD | :computer: | :computer: | | FreeBSD | :computer: | :computer: |
Currently supported PHP versions for compilation are: `7.3`, `7.4`, `8.0`, `8.1`, `8.2`, `8.3`. Currently supported PHP versions for compilation:
> :warning: supported but not maintained
>
> :heavy_check_mark: supported
>
> :x: not supported
| PHP Version | Status | Comment |
|-------------|--------------------|---------------------------------------------------|
| 7.2 | :x: | |
| 7.3 | :warning: | phpmicro and some extensions not supported on 7.x |
| 7.4 | :warning: | phpmicro and some extensions not supported on 7.x |
| 8.0 | :heavy_check_mark: | PHP official has stopped maintenance of 8.0 |
| 8.1 | :heavy_check_mark: | |
| 8.2 | :heavy_check_mark: | |
| 8.3 | :heavy_check_mark: | |
### Supported Extensions ### Supported Extensions
@@ -118,10 +136,16 @@ curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-a
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64 curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple) # macOS aarch64 (Apple)
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64 curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
# Windows (x86_64, win10 build 17063 or later)
curl.exe -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
# add x perm # Add execute perm (Linux and macOS only)
chmod +x ./spc chmod +x ./spc
# Run (Linux and macOS)
./spc --version ./spc --version
# Run (Windows powershell)
.\spc.exe --version
``` ```
Self-hosted `spc` is built by GitHub Actions, you can also download from Actions artifacts [here](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml). Self-hosted `spc` is built by GitHub Actions, you can also download from Actions artifacts [here](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml).
@@ -151,7 +175,7 @@ bin/spc --version
Basic usage for building php with some extensions: Basic usage for building php with some extensions:
> If you are using the packaged `spc` binary, you need to replace `bin/spc` with `./spc` in the following commands. > If you are using the packaged standalone `spc` binary, you need to replace `bin/spc` with `./spc` or `.\spc.exe` in the following commands.
```bash ```bash
# Check system tool dependencies, auto-fix them if possible # Check system tool dependencies, auto-fix them if possible
@@ -160,14 +184,16 @@ Basic usage for building php with some extensions:
# fetch all libraries # fetch all libraries
./bin/spc download --all ./bin/spc download --all
# only fetch necessary sources by needed extensions (recommended) # only fetch necessary sources by needed extensions (recommended)
./bin/spc download --for-extensions=openssl,pcntl,mbstring,pdo_sqlite ./bin/spc download --for-extensions="openssl,pcntl,mbstring,pdo_sqlite"
# download different PHP version (--with-php=x.y, recommend 7.3 ~ 8.3) # download different PHP version (--with-php=x.y, recommend 7.3 ~ 8.3)
./bin/spc download --for-extensions=openssl,curl,mbstring --with-php=8.1 ./bin/spc download --for-extensions="openssl,curl,mbstring" --with-php=8.1
# with bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl extension, build both CLI and phpmicro SAPI # with bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl extension, build both CLI and phpmicro SAPI
./bin/spc build bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl --build-cli --build-micro ./bin/spc build "bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl" --build-cli --build-micro
# build thread-safe (ZTS) version (--enable-zts) # build thread-safe (ZTS) version (--enable-zts)
./bin/spc build curl,phar --enable-zts --build-cli ./bin/spc build "curl,phar" --enable-zts --build-cli
# build, pack executable with UPX (--with-upx-pack) (reduce binary size for 30~50%)
./bin/spc build "curl,phar" --enable-zts --build-cli --with-upx-pack
``` ```
Now we support `cli`, `micro`, `fpm` and `embed` SAPI. You can use one or more of the following parameters to specify the compiled SAPI: Now we support `cli`, `micro`, `fpm` and `embed` SAPI. You can use one or more of the following parameters to specify the compiled SAPI:
@@ -181,7 +207,7 @@ Now we support `cli`, `micro`, `fpm` and `embed` SAPI. You can use one or more o
If anything goes wrong, use `--debug` option to display full terminal output: If anything goes wrong, use `--debug` option to display full terminal output:
```bash ```bash
./bin/spc build openssl,pcntl,mbstring --debug --build-all ./bin/spc build "openssl,pcntl,mbstring" --debug --build-all
./bin/spc download --all --debug ./bin/spc download --all --debug
``` ```
@@ -274,7 +300,7 @@ Now there is a [static-php](https://github.com/static-php) organization, which i
## Sponsor this project ## Sponsor this project
You can sponsor my project on [this page](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md). You can sponsor my project on [this page](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md). A portion of your donation will be used to maintain the **static-php.dev** server.
## Open-Source License ## Open-Source License

View File

@@ -9,3 +9,4 @@ if (-not(Test-Path $PHP_Exec)) {
} }
& "$PHP_Exec" ("bin/spc") @args & "$PHP_Exec" ("bin/spc") @args
exit $LASTEXITCODE

View File

@@ -14,6 +14,5 @@
"vendor/zhamao" "vendor/zhamao"
], ],
"git-commit-short": "git_commit_short", "git-commit-short": "git_commit_short",
"metadata": "ConsoleApplication::VERSION",
"output": "spc.phar" "output": "spc.phar"
} }

View File

@@ -19,10 +19,10 @@
"captainhook/captainhook": "^5.10", "captainhook/captainhook": "^5.10",
"captainhook/plugin-composer": "^5.3", "captainhook/plugin-composer": "^5.3",
"friendsofphp/php-cs-fixer": "^3.25", "friendsofphp/php-cs-fixer": "^3.25",
"humbug/box": "^4.3", "humbug/box": "^4.5",
"nunomaduro/collision": "^7.8", "nunomaduro/collision": "^7.8",
"phpstan/phpstan": "^1.10", "phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.3" "phpunit/phpunit": "^10.3 || ^9"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

940
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,15 @@
{ {
"amqp": {
"type": "external",
"arg-type": "custom",
"source": "amqp",
"lib-depends": [
"librabbitmq"
],
"ext-depends-windows": [
"openssl"
]
},
"apcu": { "apcu": {
"type": "external", "type": "external",
"source": "apcu" "source": "apcu"
@@ -24,6 +35,10 @@
"arg-type": "with", "arg-type": "with",
"lib-depends": [ "lib-depends": [
"curl" "curl"
],
"ext-depends-windows": [
"zlib",
"openssl"
] ]
}, },
"dba": { "dba": {
@@ -37,6 +52,9 @@
"lib-depends": [ "lib-depends": [
"libxml2", "libxml2",
"zlib" "zlib"
],
"ext-depends-windows": [
"xml"
] ]
}, },
"event": { "event": {
@@ -59,8 +77,11 @@
"ffi": { "ffi": {
"arg-type": "custom", "arg-type": "custom",
"type": "builtin", "type": "builtin",
"lib-depends": [ "lib-depends-unix": [
"libffi" "libffi"
],
"lib-depends-windows": [
"libffi-win"
] ]
}, },
"fileinfo": { "fileinfo": {
@@ -95,7 +116,7 @@
}, },
"gettext": { "gettext": {
"type": "builtin", "type": "builtin",
"arg-type": "with", "arg-type": "with-prefix",
"lib-depends": [ "lib-depends": [
"gettext" "gettext"
] ]
@@ -119,8 +140,12 @@
"iconv": { "iconv": {
"type": "builtin", "type": "builtin",
"arg-type": "with-prefix", "arg-type": "with-prefix",
"lib-depends": [ "arg-type-windows": "with",
"lib-depends-unix": [
"libiconv" "libiconv"
],
"lib-depends-windows": [
"libiconv-win"
] ]
}, },
"igbinary": { "igbinary": {
@@ -313,13 +338,6 @@
"type": "external", "type": "external",
"source": "protobuf" "source": "protobuf"
}, },
"pspell": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"aspell"
]
},
"rar": { "rar": {
"type": "external", "type": "external",
"source": "rar", "source": "rar",
@@ -354,9 +372,11 @@
"simplexml": { "simplexml": {
"type": "builtin", "type": "builtin",
"arg-type": "custom", "arg-type": "custom",
"arg-type-windows": "with",
"lib-depends": [ "lib-depends": [
"libxml2" "libxml2"
],
"ext-depends-windows": [
"xml"
] ]
}, },
"snappy": { "snappy": {
@@ -371,18 +391,14 @@
"apcu" "apcu"
] ]
}, },
"snmp": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"net-snmp"
]
},
"soap": { "soap": {
"type": "builtin", "type": "builtin",
"arg-type": "custom", "arg-type": "custom",
"lib-depends": [ "lib-depends": [
"libxml2" "libxml2"
],
"ext-depends-windows": [
"xml"
] ]
}, },
"sockets": { "sockets": {
@@ -398,6 +414,7 @@
"sqlite3": { "sqlite3": {
"type": "builtin", "type": "builtin",
"arg-type": "with-prefix", "arg-type": "with-prefix",
"arg-type-windows": "with",
"lib-depends": [ "lib-depends": [
"sqlite" "sqlite"
] ]
@@ -417,8 +434,13 @@
"type": "external", "type": "external",
"source": "ext-ssh2", "source": "ext-ssh2",
"arg-type": "with-prefix", "arg-type": "with-prefix",
"arg-type-windows": "with",
"lib-depends": [ "lib-depends": [
"libssh2" "libssh2"
],
"ext-depends-windows": [
"openssl",
"zlib"
] ]
}, },
"swoole": { "swoole": {
@@ -493,8 +515,7 @@
"unix-only": true "unix-only": true
}, },
"sysvshm": { "sysvshm": {
"type": "builtin", "type": "builtin"
"unix-only": true
}, },
"tidy": { "tidy": {
"type": "builtin", "type": "builtin",
@@ -506,6 +527,14 @@
"tokenizer": { "tokenizer": {
"type": "builtin" "type": "builtin"
}, },
"uuid": {
"type": "external",
"source": "ext-uuid",
"arg-type": "with-prefix",
"lib-depends": [
"libuuid"
]
},
"uv": { "uv": {
"type": "external", "type": "external",
"source": "ext-uv", "source": "ext-uv",
@@ -539,6 +568,9 @@
"arg-type-windows": "with", "arg-type-windows": "with",
"lib-depends": [ "lib-depends": [
"libxml2" "libxml2"
],
"ext-depends-windows": [
"iconv"
] ]
}, },
"xmlreader": { "xmlreader": {
@@ -546,6 +578,10 @@
"arg-type": "custom", "arg-type": "custom",
"lib-depends": [ "lib-depends": [
"libxml2" "libxml2"
],
"ext-depends-windows": [
"xml",
"dom"
] ]
}, },
"xmlwriter": { "xmlwriter": {
@@ -553,6 +589,9 @@
"arg-type": "custom", "arg-type": "custom",
"lib-depends": [ "lib-depends": [
"libxml2" "libxml2"
],
"ext-depends-windows": [
"xml"
] ]
}, },
"xsl": { "xsl": {

View File

@@ -36,7 +36,7 @@
"libcurl.a" "libcurl.a"
], ],
"static-libs-windows": [ "static-libs-windows": [
"libcurl.lib" "libcurl_a.lib"
], ],
"headers": [ "headers": [
"curl" "curl"
@@ -45,19 +45,21 @@
"openssl", "openssl",
"zlib" "zlib"
], ],
"lib-suggests": [ "lib-depends-windows": [
"openssl",
"zlib",
"libssh2",
"nghttp2"
],
"lib-suggests-unix": [
"libssh2", "libssh2",
"brotli", "brotli",
"nghttp2", "nghttp2",
"zstd" "zstd"
], ],
"lib-suggests-windows": [ "lib-suggests-windows": [
"zlib",
"libssh2",
"brotli", "brotli",
"nghttp2", "zstd"
"zstd",
"openssl"
], ],
"frameworks": [ "frameworks": [
"CoreFoundation", "CoreFoundation",
@@ -83,6 +85,22 @@
"brotli" "brotli"
] ]
}, },
"gettext": {
"source": "gettext",
"static-libs-unix": [
"libintl.a"
],
"lib-depends": [
"libiconv"
],
"lib-suggests": [
"ncurses",
"libxml2"
],
"frameworks": [
"CoreFoundation"
]
},
"glfw": { "glfw": {
"source": "ext-glfw", "source": "ext-glfw",
"static-libs-unix": [ "static-libs-unix": [
@@ -129,7 +147,8 @@
"libpng", "libpng",
"libjpeg", "libjpeg",
"libwebp", "libwebp",
"freetype" "freetype",
"libtiff"
], ],
"lib-suggests": [ "lib-suggests": [
"zstd", "zstd",
@@ -215,6 +234,17 @@
"ffitarget.h" "ffitarget.h"
] ]
}, },
"libffi-win": {
"source": "libffi-win",
"static-libs-windows": [
"libffi.lib"
],
"headers-windows": [
"ffi.h",
"ffitarget.h",
"fficonfig.h"
]
},
"libiconv": { "libiconv": {
"source": "libiconv", "source": "libiconv",
"static-libs-unix": [ "static-libs-unix": [
@@ -227,6 +257,13 @@
"localcharset.h" "localcharset.h"
] ]
}, },
"libiconv-win": {
"source": "libiconv-win",
"static-libs-windows": [
"libiconv.lib",
"libiconv_a.lib"
]
},
"libjpeg": { "libjpeg": {
"source": "libjpeg", "source": "libjpeg",
"static-libs-unix": [ "static-libs-unix": [
@@ -268,6 +305,18 @@
"zlib" "zlib"
] ]
}, },
"librabbitmq": {
"source": "librabbitmq",
"static-libs-unix": [
"librabbitmq.a"
],
"static-libs-windows": [
"rabbitmq.4.lib"
],
"lib-depends": [
"openssl"
]
},
"libsodium": { "libsodium": {
"source": "libsodium", "source": "libsodium",
"static-libs-unix": [ "static-libs-unix": [
@@ -294,6 +343,18 @@
"zlib" "zlib"
] ]
}, },
"libtiff": {
"source": "libtiff",
"static-libs-unix": [
"libtiff.a"
]
},
"libuuid": {
"source": "libuuid",
"static-libs-unix": [
"libuuid.a"
]
},
"libuv": { "libuv": {
"source": "libuv", "source": "libuv",
"static-libs-unix": [ "static-libs-unix": [
@@ -316,21 +377,25 @@
"libxml2.a" "libxml2.a"
], ],
"static-libs-windows": [ "static-libs-windows": [
[ "libxml2s.lib",
"libxml2s.lib", "libxml2_a.lib"
"libxml2_a.lib"
]
], ],
"headers": [ "headers": [
"libxml2" "libxml2"
], ],
"lib-depends": [ "lib-depends-unix": [
"libiconv" "libiconv"
], ],
"lib-suggests": [ "lib-suggests-unix": [
"xz", "xz",
"icu", "icu",
"zlib" "zlib"
],
"lib-depends-windows": [
"libiconv-win"
],
"lib-suggests-windows": [
"zlib"
] ]
}, },
"libxslt": { "libxslt": {
@@ -411,10 +476,8 @@
"libonig.a" "libonig.a"
], ],
"static-libs-windows": [ "static-libs-windows": [
[ "onig.lib",
"onig.lib", "onig_a.lib"
"onig_a.lib"
]
], ],
"headers": [ "headers": [
"oniggnu.h", "oniggnu.h",
@@ -491,7 +554,10 @@
"static-libs-unix": [ "static-libs-unix": [
"libsqlite3.a" "libsqlite3.a"
], ],
"headers-unix": [ "static-libs-windows": [
"libsqlite3_a.lib"
],
"headers": [
"sqlite3.h", "sqlite3.h",
"sqlite3ext.h" "sqlite3ext.h"
] ]

46
config/pkg.json Normal file
View File

@@ -0,0 +1,46 @@
{
"musl-toolchain-aarch64-linux": {
"type": "url",
"url": "https://dl.static-php.dev/static-php-cli/deps/musl-toolchain/aarch64-musl-toolchain.tgz"
},
"musl-toolchain-x86_64-linux": {
"type": "url",
"url": "https://dl.static-php.dev/static-php-cli/deps/musl-toolchain/x86_64-musl-toolchain.tgz"
},
"nasm-x86_64-win": {
"type": "url",
"url": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip",
"extract-files": {
"nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe",
"nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"
}
},
"strawberry-perl-x86_64-win": {
"type": "url",
"url": "https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip"
},
"upx-aarch64-linux": {
"type": "ghrel",
"repo": "upx/upx",
"match": "upx.+-arm64_linux\\.tar\\.xz",
"extract-files": {
"upx": "{pkg_root_path}/bin/upx"
}
},
"upx-x86_64-linux": {
"type": "ghrel",
"repo": "upx/upx",
"match": "upx.+-amd64_linux\\.tar\\.xz",
"extract-files": {
"upx": "{pkg_root_path}/bin/upx"
}
},
"upx-x86_64-win": {
"type": "ghrel",
"repo": "upx/upx",
"match": "upx.+-win64\\.zip",
"extract-files": {
"upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe"
}
}
}

View File

@@ -6,6 +6,16 @@
"path": "LICENSE" "path": "LICENSE"
} }
}, },
"amqp": {
"type": "url",
"url": "https://pecl.php.net/get/amqp",
"path": "php-src/ext/amqp",
"filename": "amqp.tgz",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"apcu": { "apcu": {
"type": "url", "type": "url",
"url": "https://pecl.php.net/get/APCu", "url": "https://pecl.php.net/get/APCu",
@@ -100,6 +110,16 @@
"path": "LICENSE" "path": "LICENSE"
} }
}, },
"ext-uuid": {
"type": "url",
"url": "https://pecl.php.net/get/uuid",
"path": "php-src/ext/uuid",
"filename": "uuid.tgz",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"ext-uv": { "ext-uv": {
"type": "url", "type": "url",
"url": "https://pecl.php.net/get/uv", "url": "https://pecl.php.net/get/uv",
@@ -129,6 +149,15 @@
"path": "LICENSE.TXT" "path": "LICENSE.TXT"
} }
}, },
"gettext": {
"type": "filelist",
"url": "https://ftp.gnu.org/pub/gnu/gettext/",
"regex": "/href=\"(?<file>gettext-(?<version>[^\"]+)\\.tar\\.xz)\"/",
"license": {
"type": "file",
"path": "COPYING"
}
},
"gmp": { "gmp": {
"type": "ghtagtar", "type": "ghtagtar",
"repo": "alisw/GMP", "repo": "alisw/GMP",
@@ -236,6 +265,15 @@
"path": "LICENSE" "path": "LICENSE"
} }
}, },
"libffi-win": {
"type": "git",
"rev": "master",
"url": "https://github.com/static-php/libffi-win.git",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"libiconv": { "libiconv": {
"type": "filelist", "type": "filelist",
"url": "https://ftp.gnu.org/gnu/libiconv/", "url": "https://ftp.gnu.org/gnu/libiconv/",
@@ -245,6 +283,15 @@
"path": "COPYING" "path": "COPYING"
} }
}, },
"libiconv-win": {
"type": "git",
"rev": "master",
"url": "https://github.com/static-php/libiconv-win.git",
"license": {
"type": "file",
"path": "source/COPYING"
}
},
"libjpeg": { "libjpeg": {
"type": "ghtar", "type": "ghtar",
"repo": "libjpeg-turbo/libjpeg-turbo", "repo": "libjpeg-turbo/libjpeg-turbo",
@@ -280,6 +327,15 @@
"path": "LICENSE" "path": "LICENSE"
} }
}, },
"librabbitmq": {
"type": "git",
"url": "https://github.com/alanxz/rabbitmq-c.git",
"rev": "master",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"libsodium": { "libsodium": {
"type": "ghrel", "type": "ghrel",
"repo": "jedisct1/libsodium", "repo": "jedisct1/libsodium",
@@ -298,6 +354,24 @@
"path": "COPYING" "path": "COPYING"
} }
}, },
"libtiff": {
"type": "filelist",
"url": "https://download.osgeo.org/libtiff/",
"regex": "/href=\"(?<file>tiff-(?<version>[^\"]+)\\.tar\\.xz)\"/",
"license": {
"type": "file",
"path": "LICENSE.md"
}
},
"libuuid": {
"type": "git",
"url": "https://github.com/cloudbase/libuuid.git",
"rev": "master",
"license": {
"type": "file",
"path": "COPYING"
}
},
"libuv": { "libuv": {
"type": "ghtar", "type": "ghtar",
"repo": "libuv/libuv", "repo": "libuv/libuv",
@@ -322,7 +396,7 @@
}, },
"libxml2": { "libxml2": {
"type": "url", "type": "url",
"url": "https://github.com/GNOME/libxml2/archive/refs/tags/v2.9.14.tar.gz", "url": "https://github.com/GNOME/libxml2/archive/refs/tags/v2.12.5.tar.gz",
"license": { "license": {
"type": "file", "type": "file",
"path": "Copyright" "path": "Copyright"
@@ -431,7 +505,7 @@
}, },
"postgresql": { "postgresql": {
"type": "url", "type": "url",
"url": "https://ftp.postgresql.org/pub/source/v16.1/postgresql-16.1.tar.gz", "url": "https://ftp.postgresql.org/pub/source/v16.2/postgresql-16.2.tar.bz2",
"license": { "license": {
"type": "file", "type": "file",
"path": "COPYRIGHT" "path": "COPYRIGHT"

View File

@@ -6,6 +6,7 @@ namespace SPC;
use SPC\command\BuildCliCommand; use SPC\command\BuildCliCommand;
use SPC\command\BuildLibsCommand; use SPC\command\BuildLibsCommand;
use SPC\command\DeleteDownloadCommand;
use SPC\command\dev\AllExtCommand; use SPC\command\dev\AllExtCommand;
use SPC\command\dev\PhpVerCommand; use SPC\command\dev\PhpVerCommand;
use SPC\command\dev\SortConfigCommand; use SPC\command\dev\SortConfigCommand;
@@ -13,6 +14,7 @@ use SPC\command\DoctorCommand;
use SPC\command\DownloadCommand; use SPC\command\DownloadCommand;
use SPC\command\DumpLicenseCommand; use SPC\command\DumpLicenseCommand;
use SPC\command\ExtractCommand; use SPC\command\ExtractCommand;
use SPC\command\InstallPkgCommand;
use SPC\command\MicroCombineCommand; use SPC\command\MicroCombineCommand;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\Console\Command\HelpCommand;
@@ -23,7 +25,7 @@ use Symfony\Component\Console\Command\ListCommand;
*/ */
final class ConsoleApplication extends Application final class ConsoleApplication extends Application
{ {
public const VERSION = '2.1.0-beta.2'; public const VERSION = '2.1.4';
public function __construct() public function __construct()
{ {
@@ -35,6 +37,8 @@ final class ConsoleApplication extends Application
new BuildLibsCommand(), new BuildLibsCommand(),
new DoctorCommand(), new DoctorCommand(),
new DownloadCommand(), new DownloadCommand(),
new InstallPkgCommand(),
new DeleteDownloadCommand(),
new DumpLicenseCommand(), new DumpLicenseCommand(),
new ExtractCommand(), new ExtractCommand(),
new MicroCombineCommand(), new MicroCombineCommand(),

View File

@@ -9,7 +9,7 @@ use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\SourceExtractor; use SPC\store\SourceManager;
use SPC\util\CustomExt; use SPC\util\CustomExt;
abstract class BuilderBase abstract class BuilderBase
@@ -144,15 +144,15 @@ abstract class BuilderBase
{ {
CustomExt::loadCustomExt(); CustomExt::loadCustomExt();
$this->emitPatchPoint('before-php-extract'); $this->emitPatchPoint('before-php-extract');
SourceExtractor::initSource(sources: ['php-src']); SourceManager::initSource(sources: ['php-src']);
$this->emitPatchPoint('after-php-extract'); $this->emitPatchPoint('after-php-extract');
if ($this->getPHPVersionID() >= 80000) { if ($this->getPHPVersionID() >= 80000) {
$this->emitPatchPoint('before-micro-extract'); $this->emitPatchPoint('before-micro-extract');
SourceExtractor::initSource(sources: ['micro']); SourceManager::initSource(sources: ['micro']);
$this->emitPatchPoint('after-micro-extract'); $this->emitPatchPoint('after-micro-extract');
} }
$this->emitPatchPoint('before-exts-extract'); $this->emitPatchPoint('before-exts-extract');
SourceExtractor::initSource(exts: $extensions); SourceManager::initSource(exts: $extensions);
$this->emitPatchPoint('after-exts-extract'); $this->emitPatchPoint('after-exts-extract');
foreach ($extensions as $extension) { foreach ($extensions as $extension) {
$class = CustomExt::getExtClass($extension); $class = CustomExt::getExtClass($extension);
@@ -231,6 +231,30 @@ abstract class BuilderBase
throw new RuntimeException('PHP version file format is malformed, please remove it and download again'); throw new RuntimeException('PHP version file format is malformed, please remove it and download again');
} }
/**
* Get PHP version from archive file name.
*
* @param null|string $file php-*.*.*.tar.gz filename, read from lockfile if empty
*/
public function getPHPVersionFromArchive(?string $file = null): false|string
{
if ($file === null) {
$lock = file_exists(DOWNLOAD_PATH . '/.lock.json') ? file_get_contents(DOWNLOAD_PATH . '/.lock.json') : false;
if ($lock === false) {
return false;
}
$lock = json_decode($lock, true);
$file = $lock['php-src']['filename'] ?? null;
if ($file === null) {
return false;
}
}
if (preg_match('/php-(\d+\.\d+\.\d+)/', $file, $match)) {
return $match[1];
}
return false;
}
/** /**
* Get build type name string to display. * Get build type name string to display.
* *

View File

@@ -185,8 +185,11 @@ class Extension
file_get_contents(ROOT_DIR . '/src/globals/tests/' . $this->getName() . '.php') file_get_contents(ROOT_DIR . '/src/globals/tests/' . $this->getName() . '.php')
); );
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "' . trim($test) . '"'); [$ret, $out] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "' . trim($test) . '"');
if ($ret !== 0) { if ($ret !== 0) {
if ($this->builder->getOption('debug')) {
var_dump($out);
}
throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check'); throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check');
} }
} }
@@ -209,7 +212,7 @@ class Extension
// Trim additional content & escape special characters to allow inline usage // Trim additional content & escape special characters to allow inline usage
$test = str_replace( $test = str_replace(
['<?php', 'declare(strict_types=1);', "\n", '"', '$'], ['<?php', 'declare(strict_types=1);', "\n", '"', '$'],
['', '', '', '\"', '\$'], ['', '', '', '\"', '$'],
file_get_contents(FileSystem::convertPath(ROOT_DIR . '/src/globals/tests/' . $this->getName() . '.php')) file_get_contents(FileSystem::convertPath(ROOT_DIR . '/src/globals/tests/' . $this->getName() . '.php'))
); );

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\store\FileSystem;
use SPC\util\CustomExt;
#[CustomExt('amqp')]
class amqp extends Extension
{
public function patchBeforeMake(): bool
{
if (PHP_OS_FAMILY === 'Windows') {
FileSystem::replaceFileRegex(BUILD_INCLUDE_PATH . '\amqp.h', '/^#warning.*/m', '');
FileSystem::replaceFileRegex(BUILD_INCLUDE_PATH . '\amqp_framing.h', '/^#warning.*/m', '');
FileSystem::replaceFileRegex(BUILD_INCLUDE_PATH . '\amqp_ssl_socket.h', '/^#warning.*/m', '');
FileSystem::replaceFileRegex(BUILD_INCLUDE_PATH . '\amqp_tcp_socket.h', '/^#warning.*/m', '');
return true;
}
return false;
}
public function getUnixConfigureArg(): string
{
return '--with-amqp --with-librabbitmq-dir=' . BUILD_ROOT_PATH;
}
public function getWindowsConfigureArg(): string
{
return '--with-amqp';
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\extension; namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\builder\macos\MacOSBuilder;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\util\CustomExt; use SPC\util\CustomExt;
@@ -34,4 +35,16 @@ class event extends Extension
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/configure', '/-levent_openssl/', $this->getLibFilesString()); FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/configure', '/-levent_openssl/', $this->getLibFilesString());
return true; return true;
} }
/**
* @throws FileSystemException
*/
public function patchBeforeMake(): bool
{
// Prevent event extension compile error on macOS
if ($this->builder instanceof MacOSBuilder) {
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_OPENPTY 1$/m', '');
}
return true;
}
} }

View File

@@ -14,4 +14,9 @@ class ffi extends Extension
{ {
return '--with-ffi --enable-zend-signals'; return '--with-ffi --enable-zend-signals';
} }
public function getWindowsConfigureArg(): string
{
return '--with-ffi';
}
} }

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\builder\macos\MacOSBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
use SPC\util\CustomExt;
#[CustomExt('gettext')]
class gettext extends Extension
{
/**
* @throws FileSystemException
*/
public function patchBeforeBuildconf(): bool
{
if ($this->builder instanceof MacOSBuilder) {
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/ext/gettext/config.m4', 'AC_CHECK_LIB($GETTEXT_CHECK_IN_LIB', 'AC_CHECK_LIB(intl');
}
return true;
}
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public function patchBeforeConfigure(): bool
{
if ($this->builder instanceof MacOSBuilder) {
$frameworks = ' ' . $this->builder->getFrameworks(true) . ' ';
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/configure', '-lintl', $this->getLibFilesString() . $frameworks);
}
return true;
}
}

View File

@@ -31,4 +31,16 @@ class mbregex extends Extension
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli mbstring extension does not contain regex !'); throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli mbstring extension does not contain regex !');
} }
} }
public function runCliCheckWindows(): void
{
[$ret, $out] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "mbstring"', false);
if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli does not contain mbstring !');
}
$out = implode("\n", $out);
if (!str_contains($out, 'regex')) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli mbstring extension does not contain regex !');
}
}
} }

View File

@@ -15,6 +15,8 @@ class mbstring extends Extension
$arg = '--enable-mbstring'; $arg = '--enable-mbstring';
if ($this->builder->getExt('mbregex') === null) { if ($this->builder->getExt('mbregex') === null) {
$arg .= ' --disable-mbregex'; $arg .= ' --disable-mbregex';
} else {
$arg .= ' --enable-mbregex';
} }
return $arg; return $arg;
} }

View File

@@ -15,8 +15,8 @@ class swoole extends Extension
// enable swoole // enable swoole
$arg = '--enable-swoole'; $arg = '--enable-swoole';
// commonly-used feature: coroutine-time, thread-context // commonly-used feature: coroutine-time, disable-thread-context
$arg .= ' --enable-swoole-coro-time --enable-thread-context'; $arg .= ' --enable-swoole-coro-time --disable-thread-context';
// required feature: curl, openssl (but curl hook is buggy for php 8.0) // required feature: curl, openssl (but curl hook is buggy for php 8.0)
$arg .= $this->builder->getPHPVersionID() >= 80100 ? ' --enable-swoole-curl' : ' --disable-swoole-curl'; $arg .= $this->builder->getPHPVersionID() >= 80100 ? ' --enable-swoole-curl' : ' --disable-swoole-curl';

View File

@@ -6,6 +6,7 @@ namespace SPC\builder\extension;
use SPC\builder\Extension; use SPC\builder\Extension;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
use SPC\util\CustomExt; use SPC\util\CustomExt;
#[CustomExt('xml')] #[CustomExt('xml')]
@@ -33,4 +34,25 @@ class xml extends Extension
$arg .= ' --with-libxml="' . BUILD_ROOT_PATH . '"'; $arg .= ' --with-libxml="' . BUILD_ROOT_PATH . '"';
return $arg; return $arg;
} }
public function patchBeforeBuildconf(): bool
{
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/win32/build/config.w32', 'dllmain.c ', '');
return true;
}
public function getWindowsConfigureArg(): string
{
$arg = match ($this->name) {
'xml' => '--with-xml',
'soap' => '--enable-soap',
'xmlreader' => '--enable-xmlreader',
'xmlwriter' => '--enable-xmlwriter',
'dom' => '--with-dom',
'simplexml' => '--with-simplexml',
default => throw new RuntimeException('Not accept non-xml extension'),
};
$arg .= ' --with-libxml';
return $arg;
}
} }

View File

@@ -169,6 +169,27 @@ class LinuxBuilder extends UnixBuilderBase
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; $enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; $enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
// upx pack and strip for micro
if ($this->getOption('with-upx-pack', false)) {
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
'/POST_MICRO_BUILD_COMMANDS=.*/',
'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH) && ' . $this->getOption('upx-exec') . ' --best \$(SAPI_MICRO_PATH)',
);
} elseif (!$this->getOption('no-strip', false)) {
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
'/POST_MICRO_BUILD_COMMANDS=.*/',
'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH)',
);
} else {
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
'/POST_MICRO_BUILD_COMMANDS=.*/',
'POST_MICRO_BUILD_COMMANDS=true',
);
}
shell()->cd(SOURCE_PATH . '/php-src') shell()->cd(SOURCE_PATH . '/php-src')
->exec( ->exec(
"{$this->getOption('ld_library_path')} " . "{$this->getOption('ld_library_path')} " .
@@ -236,7 +257,11 @@ class LinuxBuilder extends UnixBuilderBase
->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec("make -j{$this->concurrency} {$vars} cli"); ->exec("make -j{$this->concurrency} {$vars} cli");
if (!$this->getOption('no-strip', false)) { if ($this->getOption('with-upx-pack')) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')
->exec('strip --strip-all php')
->exec($this->getOption('upx-exec') . ' --best php');
} elseif (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-all php'); shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-all php');
} }
@@ -267,10 +292,6 @@ class LinuxBuilder extends UnixBuilderBase
->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec("make -j{$this->concurrency} {$vars} micro"); ->exec("make -j{$this->concurrency} {$vars} micro");
if (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/micro')->exec('strip --strip-all micro.sfx');
}
$this->deployBinary(BUILD_TARGET_MICRO); $this->deployBinary(BUILD_TARGET_MICRO);
if ($this->phar_patched) { if ($this->phar_patched) {
@@ -291,10 +312,13 @@ class LinuxBuilder extends UnixBuilderBase
->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec("make -j{$this->concurrency} {$vars} fpm"); ->exec("make -j{$this->concurrency} {$vars} fpm");
if (!$this->getOption('no-strip', false)) { if ($this->getOption('with-upx-pack')) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')
->exec('strip --strip-all php-fpm')
->exec($this->getOption('upx-exec') . ' --best php-fpm');
} elseif (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-all php-fpm'); shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-all php-fpm');
} }
$this->deployBinary(BUILD_TARGET_FPM); $this->deployBinary(BUILD_TARGET_FPM);
} }

View File

@@ -110,11 +110,7 @@ class SystemUtil
public static function getTuneCFlags(string $arch): array public static function getTuneCFlags(string $arch): array
{ {
return match ($arch) { return match ($arch) {
'x86_64' => [ 'x86_64', 'arm64', 'aarch64' => [],
'-march=corei7',
'-mtune=core-avx2',
],
'arm64', 'aarch64' => [],
default => throw new RuntimeException('unsupported arch: ' . $arch), default => throw new RuntimeException('unsupported arch: ' . $arch),
}; };
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ class libxml2 extends LinuxLibraryBase
*/ */
public function build(): void public function build(): void
{ {
$enable_zlib = $this->builder->getLib('zlib') ? 'ON' : 'OFF'; $enable_zlib = $this->builder->getLib('zlib') ? ('ON -DZLIB_LIBRARY=' . BUILD_LIB_PATH . '/libz.a -DZLIB_INCLUDE_DIR=' . BUILD_INCLUDE_PATH) : 'OFF';
$enable_icu = $this->builder->getLib('icu') ? 'ON' : 'OFF'; $enable_icu = $this->builder->getLib('icu') ? 'ON' : 'OFF';
$enable_xz = $this->builder->getLib('xz') ? 'ON' : 'OFF'; $enable_xz = $this->builder->getLib('xz') ? 'ON' : 'OFF';
@@ -28,6 +28,7 @@ class libxml2 extends LinuxLibraryBase
'cmake ' . 'cmake ' .
'-DCMAKE_BUILD_TYPE=Release ' . '-DCMAKE_BUILD_TYPE=Release ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' ' . '-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' ' .
'-DCMAKE_INSTALL_LIBDIR=' . BUILD_LIB_PATH . ' ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " . "-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DBUILD_SHARED_LIBS=OFF ' . '-DBUILD_SHARED_LIBS=OFF ' .
'-DIconv_IS_BUILT_IN=OFF ' . '-DIconv_IS_BUILT_IN=OFF ' .

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ class libxml2 extends MacOSLibraryBase
*/ */
protected function build(): void protected function build(): void
{ {
$enable_zlib = $this->builder->getLib('zlib') ? 'ON' : 'OFF'; $enable_zlib = $this->builder->getLib('zlib') ? ('ON -DZLIB_LIBRARY=' . BUILD_LIB_PATH . '/libz.a -DZLIB_INCLUDE_DIR=' . BUILD_INCLUDE_PATH) : 'OFF';
$enable_icu = $this->builder->getLib('icu') ? 'ON' : 'OFF'; $enable_icu = $this->builder->getLib('icu') ? 'ON' : 'OFF';
$enable_xz = $this->builder->getLib('xz') ? 'ON' : 'OFF'; $enable_xz = $this->builder->getLib('xz') ? 'ON' : 'OFF';
@@ -29,6 +29,7 @@ class libxml2 extends MacOSLibraryBase
// '--debug-find ' . // '--debug-find ' .
'-DCMAKE_BUILD_TYPE=Release ' . '-DCMAKE_BUILD_TYPE=Release ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' ' . '-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' ' .
'-DCMAKE_INSTALL_LIBDIR=' . BUILD_LIB_PATH . ' ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " . "-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DBUILD_SHARED_LIBS=OFF ' . '-DBUILD_SHARED_LIBS=OFF ' .
'-DLIBXML2_WITH_ICONV=ON ' . '-DLIBXML2_WITH_ICONV=ON ' .

View File

@@ -11,7 +11,7 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourceExtractor; use SPC\store\SourceManager;
use SPC\util\DependencyUtil; use SPC\util\DependencyUtil;
abstract class UnixBuilderBase extends BuilderBase abstract class UnixBuilderBase extends BuilderBase
@@ -107,7 +107,7 @@ abstract class UnixBuilderBase extends BuilderBase
// if no libs specified, compile all supported libs // if no libs specified, compile all supported libs
if ($sorted_libraries === [] && $this->isLibsOnly()) { if ($sorted_libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list); $libraries = array_keys($support_lib_list);
$sorted_libraries = DependencyUtil::getLibsByDeps($libraries); $sorted_libraries = DependencyUtil::getLibs($libraries);
} }
// pkg-config must be compiled first, whether it is specified or not // pkg-config must be compiled first, whether it is specified or not
@@ -134,7 +134,7 @@ abstract class UnixBuilderBase extends BuilderBase
$this->emitPatchPoint('before-libs-extract'); $this->emitPatchPoint('before-libs-extract');
// extract sources // extract sources
SourceExtractor::initSource(libs: $sorted_libraries); SourceManager::initSource(libs: $sorted_libraries);
$this->emitPatchPoint('after-libs-extract'); $this->emitPatchPoint('after-libs-extract');

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
trait gettext
{
protected function build(): void
{
$extra = $this->builder->getLib('ncurses') ? ('--with-libncurses-prefix=' . BUILD_ROOT_PATH . ' ') : '';
$extra .= $this->builder->getLib('libxml2') ? ('--with-libxml2-prefix=' . BUILD_ROOT_PATH . ' ') : '';
shell()->cd($this->source_dir)
->exec(
'./configure ' .
'--enable-static ' .
'--disable-shared ' .
'--disable-java ' .
'--disable-c+ ' .
$extra .
'--with-libiconv-prefix=' . BUILD_ROOT_PATH . ' ' .
'--prefix=' . BUILD_ROOT_PATH
)
->exec('make clean')
->exec("make -j{$this->builder->concurrency}")
->exec('make install');
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
trait librabbitmq
{
/**
* @throws RuntimeException
* @throws FileSystemException
*/
protected function build(): void
{
// CMake needs a clean build directory
FileSystem::resetDir($this->source_dir . '/build');
// Start build
shell()->cd($this->source_dir . '/build')
->exec(
'cmake ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DBUILD_STATIC_LIBS=ON ' .
'..'
)
->exec("cmake --build . -j {$this->builder->concurrency}")
->exec('make install');
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
trait libtiff
{
/**
* @throws FileSystemException
* @throws RuntimeException
*/
protected function build(): void
{
shell()->cd($this->source_dir)
->exec(
'./configure ' .
'--enable-static --disable-shared ' .
'--disable-cxx ' .
'--prefix='
)
->exec('make clean')
->exec("make -j{$this->builder->concurrency}")
->exec('make install DESTDIR=' . BUILD_ROOT_PATH);
$this->patchPkgconfPrefix(['libtiff-4.pc']);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
trait libuuid
{
public function patchBeforeBuild(): bool
{
FileSystem::replaceFileStr($this->source_dir . '/configure', '-${am__api_version}', '');
return true;
}
/**
* @throws FileSystemException
* @throws RuntimeException
*/
protected function build(): void
{
shell()->cd($this->source_dir)
->exec('chmod +x configure')
->exec('chmod +x install-sh')
->exec(
'./configure ' .
'--enable-static --disable-shared ' .
'--prefix='
)
->exec('make clean')
->exec("make -j{$this->builder->concurrency}")
->exec('make install DESTDIR=' . BUILD_ROOT_PATH);
$this->patchPkgconfPrefix(['uuid.pc']);
}
}

View File

@@ -23,7 +23,7 @@ trait ncurses
'--without-tests ' . '--without-tests ' .
'--without-dlsym ' . '--without-dlsym ' .
'--without-debug ' . '--without-debug ' .
'-enable-symlinks' . '-enable-symlinks ' .
'--bindir=' . BUILD_ROOT_PATH . '/bin ' . '--bindir=' . BUILD_ROOT_PATH . '/bin ' .
'--includedir=' . BUILD_ROOT_PATH . '/include ' . '--includedir=' . BUILD_ROOT_PATH . '/include ' .
'--libdir=' . BUILD_ROOT_PATH . '/lib ' . '--libdir=' . BUILD_ROOT_PATH . '/lib ' .

View File

@@ -21,6 +21,8 @@ trait pkgconfig
'--disable-shared ' . '--disable-shared ' .
'--enable-static ' . '--enable-static ' .
'--with-internal-glib ' . '--with-internal-glib ' .
'--disable-host-tool ' .
'--with-pic ' .
'--prefix=' . BUILD_ROOT_PATH . ' ' . '--prefix=' . BUILD_ROOT_PATH . ' ' .
'--without-sysroot ' . '--without-sysroot ' .
'--without-system-include-path ' . '--without-system-include-path ' .
@@ -29,6 +31,7 @@ trait pkgconfig
) )
->exec('make clean') ->exec('make clean')
->exec("make -j{$this->builder->concurrency}") ->exec("make -j{$this->builder->concurrency}")
->exec('make install'); ->exec('make install-exec');
shell()->exec('strip ' . BUILD_ROOT_PATH . '/bin/pkg-config');
} }
} }

View File

@@ -58,11 +58,17 @@ trait postgresql
FileSystem::resetDir($this->source_dir . '/build'); FileSystem::resetDir($this->source_dir . '/build');
# 有静态链接配置 参考文件: src/interfaces/libpq/Makefile $version = $this->getVersion();
shell()->cd($this->source_dir . '/build') // 16.1 workaround
->exec('sed -i.backup "s/invokes exit\'; exit 1;/invokes exit\';/" ../src/interfaces/libpq/Makefile') if (version_compare($version, '16.1') >= 0) {
->exec('sed -i.backup "278 s/^/# /" ../src/Makefile.shlib') # 有静态链接配置 参考文件: src/interfaces/libpq/Makefile
->exec('sed -i.backup "402 s/^/# /" ../src/Makefile.shlib'); shell()->cd($this->source_dir . '/build')
->exec('sed -i.backup "s/invokes exit\'; exit 1;/invokes exit\';/" ../src/interfaces/libpq/Makefile')
->exec('sed -i.backup "278 s/^/# /" ../src/Makefile.shlib')
->exec('sed -i.backup "402 s/^/# /" ../src/Makefile.shlib');
} else {
throw new RuntimeException('Unsupported version for postgresql: ' . $version . ' !');
}
// configure // configure
shell()->cd($this->source_dir . '/build') shell()->cd($this->source_dir . '/build')
@@ -101,4 +107,17 @@ trait postgresql
->exec("rm -rf {$builddir}/lib/*.so") ->exec("rm -rf {$builddir}/lib/*.so")
->exec("rm -rf {$builddir}/lib/*.dylib"); ->exec("rm -rf {$builddir}/lib/*.dylib");
} }
private function getVersion(): string
{
try {
$file = FileSystem::readFile($this->source_dir . '/meson.build');
if (preg_match("/^\\s+version:\\s?'(.*)'/m", $file, $match)) {
return $match[1];
}
return 'unknown';
} catch (FileSystemException) {
return 'unknown';
}
}
} }

View File

@@ -10,7 +10,7 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourceExtractor; use SPC\store\SourceManager;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
use SPC\util\DependencyUtil; use SPC\util\DependencyUtil;
@@ -72,6 +72,27 @@ class WindowsBuilder extends BuilderBase
$zts = $this->zts ? '--enable-zts=yes ' : '--enable-zts=no '; $zts = $this->zts ? '--enable-zts=yes ' : '--enable-zts=no ';
// with-upx-pack for phpmicro
$makefile = FileSystem::convertPath(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag.w32');
if ($this->getOption('with-upx-pack', false)) {
if (!file_exists($makefile . '.originfile')) {
copy($makefile, $makefile . '.originfile');
FileSystem::replaceFileStr($makefile, '$(MICRO_SFX):', "_MICRO_UPX = {$this->getOption('upx-exec')} --best $(MICRO_SFX)\n$(MICRO_SFX):");
FileSystem::replaceFileStr($makefile, '@$(_MICRO_MT)', "@$(_MICRO_MT)\n\t@$(_MICRO_UPX)");
}
} elseif (file_exists($makefile . '.originfile')) {
copy($makefile . '.originfile', $makefile);
unlink($makefile . '.originfile');
}
if (($logo = $this->getOption('with-micro-logo')) !== null) {
// realpath
$logo = realpath($logo);
$micro_logo = '--enable-micro-logo=' . $logo . ' ';
} else {
$micro_logo = '';
}
cmd()->cd(SOURCE_PATH . '\php-src') cmd()->cd(SOURCE_PATH . '\php-src')
->exec( ->exec(
"{$this->sdk_prefix} configure.bat --task-args \"" . "{$this->sdk_prefix} configure.bat --task-args \"" .
@@ -81,7 +102,7 @@ class WindowsBuilder extends BuilderBase
'--with-extra-includes=' . BUILD_INCLUDE_PATH . ' ' . '--with-extra-includes=' . BUILD_INCLUDE_PATH . ' ' .
'--with-extra-libs=' . BUILD_LIB_PATH . ' ' . '--with-extra-libs=' . BUILD_LIB_PATH . ' ' .
($enableCli ? '--enable-cli=yes ' : '--enable-cli=no ') . ($enableCli ? '--enable-cli=yes ' : '--enable-cli=no ') .
($enableMicro ? '--enable-micro=yes ' : '--enable-micro=no ') . ($enableMicro ? ('--enable-micro=yes ' . $micro_logo) : '--enable-micro=no ') .
($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') . ($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') .
"{$this->makeExtensionArgs()} " . "{$this->makeExtensionArgs()} " .
$zts . $zts .
@@ -150,6 +171,8 @@ class WindowsBuilder extends BuilderBase
$makefile = FileSystem::readFile(SOURCE_PATH . '\php-src\Makefile'); $makefile = FileSystem::readFile(SOURCE_PATH . '\php-src\Makefile');
if ($this->getPHPVersionID() >= 80200 && str_contains($makefile, 'FIBER_ASM_ARCH')) { if ($this->getPHPVersionID() >= 80200 && str_contains($makefile, 'FIBER_ASM_ARCH')) {
$makefile .= "\r\n" . '$(MICRO_SFX): $(BUILD_DIR)\Zend\jump_$(FIBER_ASM_ARCH)_ms_pe_masm.obj $(BUILD_DIR)\Zend\make_$(FIBER_ASM_ARCH)_ms_pe_masm.obj' . "\r\n\r\n"; $makefile .= "\r\n" . '$(MICRO_SFX): $(BUILD_DIR)\Zend\jump_$(FIBER_ASM_ARCH)_ms_pe_masm.obj $(BUILD_DIR)\Zend\make_$(FIBER_ASM_ARCH)_ms_pe_masm.obj' . "\r\n\r\n";
} elseif ($this->getPHPVersionID() >= 80400 && str_contains($makefile, 'FIBER_ASM_ABI')) {
$makefile .= "\r\n" . '$(MICRO_SFX): $(BUILD_DIR)\Zend\jump_$(FIBER_ASM_ABI).obj $(BUILD_DIR)\Zend\make_$(FIBER_ASM_ABI).obj' . "\r\n\r\n";
} }
FileSystem::writeFile(SOURCE_PATH . '\php-src\Makefile', $makefile); FileSystem::writeFile(SOURCE_PATH . '\php-src\Makefile', $makefile);
@@ -164,10 +187,12 @@ class WindowsBuilder extends BuilderBase
SourcePatcher::patchMicro(['phar']); SourcePatcher::patchMicro(['phar']);
} }
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_micro_wrapper.bat --task-args micro"); try {
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_micro_wrapper.bat --task-args micro");
if ($this->phar_patched) { } finally {
SourcePatcher::patchMicro(['phar'], true); if ($this->phar_patched) {
SourcePatcher::patchMicro(['phar'], true);
}
} }
$this->deployBinary(BUILD_TARGET_MICRO); $this->deployBinary(BUILD_TARGET_MICRO);
@@ -190,7 +215,7 @@ class WindowsBuilder extends BuilderBase
// if no libs specified, compile all supported libs // if no libs specified, compile all supported libs
if ($sorted_libraries === [] && $this->isLibsOnly()) { if ($sorted_libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list); $libraries = array_keys($support_lib_list);
$sorted_libraries = DependencyUtil::getLibsByDeps($libraries); $sorted_libraries = DependencyUtil::getLibs($libraries);
} }
// add lib object for builder // add lib object for builder
@@ -209,7 +234,7 @@ class WindowsBuilder extends BuilderBase
} }
// extract sources // extract sources
SourceExtractor::initSource(libs: $sorted_libraries); SourceManager::initSource(libs: $sorted_libraries);
// build all libs // build all libs
foreach ($this->libs as $lib) { foreach ($this->libs as $lib) {
@@ -291,6 +316,12 @@ class WindowsBuilder extends BuilderBase
BUILD_TARGET_MICRO => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\micro.sfx", BUILD_TARGET_MICRO => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\micro.sfx",
default => throw new RuntimeException('Deployment does not accept type ' . $type), default => throw new RuntimeException('Deployment does not accept type ' . $type),
}; };
// with-upx-pack for cli
if ($this->getOption('with-upx-pack', false) && $type === BUILD_TARGET_CLI) {
cmd()->exec($this->getOption('upx-exec') . ' --best ' . escapeshellarg($src));
}
logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file'); logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file');
FileSystem::createDir(BUILD_ROOT_PATH . '\bin'); FileSystem::createDir(BUILD_ROOT_PATH . '\bin');

View File

@@ -8,6 +8,8 @@ use SPC\builder\BuilderBase;
use SPC\builder\LibraryBase; use SPC\builder\LibraryBase;
use SPC\builder\windows\WindowsBuilder; use SPC\builder\windows\WindowsBuilder;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem; use SPC\store\FileSystem;
abstract class WindowsLibraryBase extends LibraryBase abstract class WindowsLibraryBase extends LibraryBase
@@ -22,6 +24,36 @@ abstract class WindowsLibraryBase extends LibraryBase
return $this->builder; return $this->builder;
} }
/**
* @throws RuntimeException
* @throws FileSystemException
* @throws WrongUsageException
*/
public function getStaticLibFiles(string $style = 'autoconf', bool $recursive = true): string
{
$libs = [$this];
if ($recursive) {
array_unshift($libs, ...array_values($this->getDependencies(recursive: true)));
}
$sep = match ($style) {
'autoconf' => ' ',
'cmake' => ';',
default => throw new RuntimeException('style only support autoconf and cmake'),
};
$ret = [];
foreach ($libs as $lib) {
$libFiles = [];
foreach ($lib->getStaticLibs() as $name) {
$name = str_replace(' ', '\ ', FileSystem::convertPath(BUILD_LIB_PATH . "/{$name}"));
$name = str_replace('"', '\"', $name);
$libFiles[] = $name;
}
array_unshift($ret, implode($sep, $libFiles));
}
return implode($sep, $ret);
}
/** /**
* Create a nmake wrapper file. * Create a nmake wrapper file.
* *

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\store\FileSystem;
class curl extends WindowsLibraryBase
{
public const NAME = 'curl';
protected function build(): void
{
cmd()->cd($this->source_dir . '\winbuild')
->execWithWrapper(
$this->builder->makeSimpleWrapper('nmake'),
'/f Makefile.vc WITH_DEVEL=' . BUILD_ROOT_PATH . ' ' .
'WITH_PREFIX=' . BUILD_ROOT_PATH . ' ' .
'mode=static RTLIBCFG=static WITH_SSL=static WITH_NGHTTP2=static WITH_SSH2=static ENABLE_IPV6=yes WITH_ZLIB=static MACHINE=x64 DEBUG=no'
);
FileSystem::copyDir($this->source_dir . '\include\curl', BUILD_INCLUDE_PATH . '\curl');
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\builder\windows\SystemUtil;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
class libffi_win extends WindowsLibraryBase
{
public const NAME = 'libffi-win';
protected function build()
{
$vs_ver_dir = match (SystemUtil::findVisualStudio()['version']) {
'vs17' => '/win32/vs17_x64',
'vs16' => '/win32/vs16_x64',
default => throw new RuntimeException('Current VS version is not supported yet!'),
};
// start build
cmd()->cd($this->source_dir . $vs_ver_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper('msbuild'),
'libffi-msvc.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64'
);
FileSystem::createDir(BUILD_LIB_PATH);
FileSystem::createDir(BUILD_INCLUDE_PATH);
copy($this->source_dir . $vs_ver_dir . '\x64\Release\libffi.lib', BUILD_LIB_PATH . '\libffi.lib');
copy($this->source_dir . $vs_ver_dir . '\x64\Release\libffi.pdb', BUILD_LIB_PATH . '\libffi.pdb');
copy($this->source_dir . '\include\ffi.h', BUILD_INCLUDE_PATH . '\ffi.h');
FileSystem::replaceFileStr(BUILD_INCLUDE_PATH . '\ffi.h', '#define LIBFFI_H', "#define LIBFFI_H\n#define FFI_BUILDING");
copy($this->source_dir . '\src\x86\ffitarget.h', BUILD_INCLUDE_PATH . '\ffitarget.h');
copy($this->source_dir . '\fficonfig.h', BUILD_INCLUDE_PATH . '\fficonfig.h');
// copy($this->source_dir . '\msvc_build\out\static-Release\X64\libffi.lib', BUILD_LIB_PATH . '\libffi.lib');
// copy($this->source_dir . '\msvc_build\include\ffi.h', BUILD_INCLUDE_PATH . '\ffi.h');
// copy($this->source_dir . '\msvc_build\include\fficonfig.h', BUILD_INCLUDE_PATH . '\fficonfig.h');
// copy($this->source_dir . '\src\x86\ffitarget.h', BUILD_INCLUDE_PATH . '\ffitarget.h');
// FileSystem::replaceFileStr(BUILD_INCLUDE_PATH . '\ffi.h', '..\..\src\x86\ffitarget.h', 'ffitarget.h');
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\builder\windows\SystemUtil;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
class libiconv_win extends WindowsLibraryBase
{
public const NAME = 'libiconv-win';
protected function build()
{
$vs_ver_dir = match (SystemUtil::findVisualStudio()['version']) {
'vs17' => '/MSVC17',
'vs16' => '/MSVC16',
default => throw new RuntimeException('Current VS version is not supported yet!'),
};
// start build
cmd()->cd($this->source_dir . $vs_ver_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper('msbuild'),
'libiconv.sln /t:Rebuild /p:Configuration=Release /p:Platform=x64'
);
FileSystem::createDir(BUILD_LIB_PATH);
FileSystem::createDir(BUILD_INCLUDE_PATH);
copy($this->source_dir . $vs_ver_dir . '\x64\lib\libiconv.lib', BUILD_LIB_PATH . '\libiconv.lib');
copy($this->source_dir . $vs_ver_dir . '\x64\lib\libiconv_a.lib', BUILD_LIB_PATH . '\libiconv_a.lib');
copy($this->source_dir . '\source\include\iconv.h', BUILD_INCLUDE_PATH . '\iconv.h');
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\store\FileSystem;
class librabbitmq extends WindowsLibraryBase
{
public const NAME = 'librabbitmq';
protected function build(): void
{
// reset cmake
FileSystem::resetDir($this->source_dir . '\build');
// start build
cmd()->cd($this->source_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
'-B build ' .
'-A x64 ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DBUILD_STATIC_LIBS=ON ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' '
)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
"--build build --config Release --target install -j{$this->builder->concurrency}"
);
rename(BUILD_LIB_PATH . '\librabbitmq.4.lib', BUILD_LIB_PATH . '\rabbitmq.4.lib');
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\store\FileSystem;
class libssh2 extends WindowsLibraryBase
{
public const NAME = 'libssh2';
protected function build(): void
{
$zlib = $this->builder->getLib('zlib') ? 'ON' : 'OFF';
// reset cmake
FileSystem::resetDir($this->source_dir . '\build');
// start build
cmd()->cd($this->source_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
'-B build ' .
'-A x64 ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DBUILD_STATIC_LIBS=ON ' .
'-DBUILD_TESTING=OFF ' .
"-DENABLE_ZLIB_COMPRESSION={$zlib} " .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' '
)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
"--build build --config Release --target install -j{$this->builder->concurrency}"
);
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\store\FileSystem;
class libxml2 extends WindowsLibraryBase
{
public const NAME = 'libxml2';
protected function build(): void
{
$zlib = $this->builder->getLib('zlib') ? 'ON' : 'OFF';
// reset cmake
FileSystem::resetDir($this->source_dir . '\build');
// start build
cmd()->cd($this->source_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
'-B build ' .
'-A x64 ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DBUILD_STATIC_LIBS=ON ' .
"-DLIBXML2_WITH_ZLIB={$zlib} " .
'-DLIBXML2_WITH_PYTHON=OFF ' .
'-DLIBXML2_WITH_ICONV=ON ' .
'-DIconv_LIBRARY=' . BUILD_LIB_PATH . ' ' .
'-DIconv_INCLUDE_DIR=' . BUILD_INCLUDE_PATH . ' ' .
'-DLIBXML2_WITH_LZMA=OFF ' . // xz not supported yet
'-DLIBXML2_WITH_PROGRAMS=OFF ' .
'-DLIBXML2_WITH_TESTS=OFF ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' '
)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
"--build build --config Release --target install -j{$this->builder->concurrency}"
);
copy(BUILD_LIB_PATH . '\libxml2s.lib', BUILD_LIB_PATH . '\libxml2_a.lib');
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\store\FileSystem;
class nghttp2 extends WindowsLibraryBase
{
public const NAME = 'nghttp2';
protected function build(): void
{
// reset cmake
FileSystem::resetDir($this->source_dir . '\build');
// start build
cmd()->cd($this->source_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
'-B build ' .
'-A x64 ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DENABLE_SHARED_LIB=OFF ' .
'-DENABLE_STATIC_LIB=ON ' .
'-DENABLE_STATIC_CRT=ON ' .
'-DENABLE_LIB_ONLY=ON ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' '
)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
"--build build --config Release --target install -j{$this->builder->concurrency}"
);
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\store\FileSystem;
class onig extends WindowsLibraryBase
{
public const NAME = 'onig';
protected function build(): void
{
// reset cmake
FileSystem::resetDir($this->source_dir . '\build');
// start build
cmd()->cd($this->source_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
'-B build ' .
'-A x64 ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DBUILD_STATIC_LIBS=ON ' .
'-DMSVC_STATIC_RUNTIME=ON ' .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' '
)
->execWithWrapper(
$this->builder->makeSimpleWrapper('cmake'),
"--build build --config Release --target install -j{$this->builder->concurrency}"
);
copy(BUILD_LIB_PATH . '/onig.lib', BUILD_LIB_PATH . '/onig_a.lib');
}
}

View File

@@ -14,7 +14,8 @@ class openssl extends WindowsLibraryBase
protected function build(): void protected function build(): void
{ {
$perl = file_exists(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe') ? (BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe') : SystemUtil::findCommand('perl.exe'); $perl_path_native = PKG_ROOT_PATH . '\strawberry-perl-' . arch2gnu(php_uname('m')) . '-win\perl\bin\perl.exe';
$perl = file_exists($perl_path_native) ? ($perl_path_native) : SystemUtil::findCommand('perl.exe');
if ($perl === null) { if ($perl === null) {
throw new RuntimeException('You need to install perl first! (easiest way is using static-php-cli command "doctor")'); throw new RuntimeException('You need to install perl first! (easiest way is using static-php-cli command "doctor")');
} }

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
class sqlite extends WindowsLibraryBase
{
public const NAME = 'sqlite';
public function patchBeforeBuild(): bool
{
copy(ROOT_DIR . '/src/globals/extra/Makefile-sqlite', $this->source_dir . '/Makefile');
return true;
}
protected function build(): void
{
cmd()->cd($this->source_dir)->execWithWrapper($this->builder->makeSimpleWrapper('nmake'), 'PREFIX=' . BUILD_ROOT_PATH . ' install-static');
}
}

View File

@@ -56,11 +56,6 @@ abstract class BaseCommand extends Command
// 如果 return false 则错误会继续递交给 PHP 标准错误处理 // 如果 return false 则错误会继续递交给 PHP 标准错误处理
return true; return true;
}, E_ALL | E_STRICT); }, E_ALL | E_STRICT);
if ($input->getOption('debug')) {
global $ob_logger;
$ob_logger = new ConsoleLogger(LogLevel::DEBUG);
define('DEBUG_MODE', true);
}
$version = ConsoleApplication::VERSION; $version = ConsoleApplication::VERSION;
if (!$this->no_motd) { if (!$this->no_motd) {
echo " _ _ _ _ echo " _ _ _ _
@@ -83,6 +78,14 @@ abstract class BaseCommand extends Command
$this->input = $input; $this->input = $input;
$this->output = $output; $this->output = $output;
global $ob_logger;
if ($input->getOption('debug')) {
$ob_logger = new ConsoleLogger(LogLevel::DEBUG, decorated: !$input->getOption('no-ansi'));
define('DEBUG_MODE', true);
} else {
$ob_logger = new ConsoleLogger(decorated: !$input->getOption('no-ansi'));
}
// windows fallback // windows fallback
Prompt::fallbackWhen(PHP_OS_FAMILY === 'Windows'); Prompt::fallbackWhen(PHP_OS_FAMILY === 'Windows');
ConfirmPrompt::fallbackUsing(function (ConfirmPrompt $prompt) use ($input, $output) { ConfirmPrompt::fallbackUsing(function (ConfirmPrompt $prompt) use ($input, $output) {

View File

@@ -23,20 +23,22 @@ class BuildCliCommand extends BuildCommand
{ {
$this->addArgument('extensions', InputArgument::REQUIRED, 'The extensions will be compiled, comma separated'); $this->addArgument('extensions', InputArgument::REQUIRED, 'The extensions will be compiled, comma separated');
$this->addOption('with-libs', null, InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', ''); $this->addOption('with-libs', null, InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', '');
$this->addOption('build-micro', null, null, 'build micro'); $this->addOption('build-micro', null, null, 'Build micro SAPI');
$this->addOption('build-cli', null, null, 'build cli'); $this->addOption('build-cli', null, null, 'Build cli SAPI');
$this->addOption('build-fpm', null, null, 'build fpm'); $this->addOption('build-fpm', null, null, 'Build fpm SAPI');
$this->addOption('build-embed', null, null, 'build embed'); $this->addOption('build-embed', null, null, 'Build embed SAPI');
$this->addOption('build-all', null, null, 'build cli, micro, fpm, embed'); $this->addOption('build-all', null, null, 'Build all SAPI');
$this->addOption('no-strip', null, null, 'build without strip, in order to debug and load external extensions'); $this->addOption('no-strip', null, null, 'build without strip, in order to debug and load external extensions');
$this->addOption('enable-zts', null, null, 'enable ZTS support'); $this->addOption('enable-zts', null, null, 'enable ZTS support');
$this->addOption('disable-opcache-jit', null, null, 'disable opcache jit'); $this->addOption('disable-opcache-jit', null, null, 'disable opcache jit');
$this->addOption('with-hardcoded-ini', 'I', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Patch PHP source code, inject hardcoded INI'); $this->addOption('with-hardcoded-ini', 'I', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Patch PHP source code, inject hardcoded INI');
$this->addOption('with-micro-fake-cli', null, null, 'Enable phpmicro fake cli'); $this->addOption('with-micro-fake-cli', null, null, 'Let phpmicro\'s PHP_SAPI use "cli" instead of "micro"');
$this->addOption('with-suggested-libs', 'L', null, 'Build with suggested libs for selected exts and libs'); $this->addOption('with-suggested-libs', 'L', null, 'Build with suggested libs for selected exts and libs');
$this->addOption('with-suggested-exts', 'E', null, 'Build with suggested extensions for selected exts'); $this->addOption('with-suggested-exts', 'E', null, 'Build with suggested extensions for selected exts');
$this->addOption('with-added-patch', 'P', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Inject patch script outside'); $this->addOption('with-added-patch', 'P', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Inject patch script outside');
$this->addOption('without-micro-ext-test', null, null, 'Disable phpmicro with extension test code'); $this->addOption('without-micro-ext-test', null, null, 'Disable phpmicro with extension test code');
$this->addOption('with-upx-pack', null, null, 'Compress / pack binary using UPX tool (linux/windows only)');
$this->addOption('with-micro-logo', null, InputOption::VALUE_REQUIRED, 'Use custom .ico for micro.sfx (windows only)');
} }
public function handle(): int public function handle(): int
@@ -58,30 +60,79 @@ class BuildCliCommand extends BuildCommand
$this->output->writeln("<comment>\t--build-all\tBuild all SAPI: cli, micro, fpm, embed</comment>"); $this->output->writeln("<comment>\t--build-all\tBuild all SAPI: cli, micro, fpm, embed</comment>");
return static::FAILURE; return static::FAILURE;
} }
if ($rule === BUILD_TARGET_ALL) {
logger()->warning('--build-all option makes `--no-strip` always true, be aware!');
}
if (($rule & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO && $this->getOption('with-micro-logo')) {
$logo = $this->getOption('with-micro-logo');
if (!file_exists($logo)) {
logger()->error('Logo file ' . $logo . ' not exist !');
return static::FAILURE;
}
}
// Check upx
$suffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : '';
if ($this->getOption('with-upx-pack')) {
// only available for linux for now
if (!in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
logger()->error('UPX is only available on Linux and Windows!');
return static::FAILURE;
}
// need to install this manually
if (!file_exists(PKG_ROOT_PATH . '/bin/upx' . $suffix)) {
global $argv;
logger()->error('upx does not exist, please install it first:');
logger()->error('');
logger()->error("\t" . $argv[0] . ' install-pkg upx');
logger()->error('');
return static::FAILURE;
}
// exclusive with no-strip
if ($this->getOption('no-strip')) {
logger()->warning('--with-upx-pack conflicts with --no-strip, --no-strip won\'t work!');
}
}
try { try {
// create builder // create builder
$builder = BuilderProvider::makeBuilderByInput($this->input); $builder = BuilderProvider::makeBuilderByInput($this->input);
// calculate dependencies $include_suggest_ext = $this->getOption('with-suggested-exts');
[$extensions, $libraries, $not_included] = DependencyUtil::getExtLibsByDeps($extensions, $libraries); $include_suggest_lib = $this->getOption('with-suggested-libs');
[$extensions, $libraries, $not_included] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
// print info // print info
$indent_texts = [ $indent_texts = [
'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')', 'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')',
'Build SAPI' => $builder->getBuildTypeName($rule), 'Build SAPI' => $builder->getBuildTypeName($rule),
'Extensions (' . count($extensions) . ')' => implode(', ', $extensions), 'Extensions (' . count($extensions) . ')' => implode(',', $extensions),
'Libraries (' . count($libraries) . ')' => implode(', ', $libraries), 'Libraries (' . count($libraries) . ')' => implode(',', $libraries),
'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes', 'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes',
'Enable ZTS' => $builder->getOption('enable-zts') ? 'yes' : 'no', 'Enable ZTS' => $builder->getOption('enable-zts') ? 'yes' : 'no',
]; ];
if (!empty($this->input->getOption('with-hardcoded-ini'))) { if (!empty($this->input->getOption('with-hardcoded-ini'))) {
$indent_texts['Hardcoded INI'] = $this->input->getOption('with-hardcoded-ini'); $indent_texts['Hardcoded INI'] = $this->input->getOption('with-hardcoded-ini');
} }
$this->printFormatInfo($indent_texts); if ($this->input->getOption('disable-opcache-jit')) {
$indent_texts['Opcache JIT'] = 'disabled';
}
if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
$indent_texts['UPX Pack'] = 'enabled';
$builder->setOption('upx-exec', FileSystem::convertPath(PKG_ROOT_PATH . '/bin/upx' . $suffix));
}
try {
$ver = $builder->getPHPVersion();
$indent_texts['PHP Version'] = $ver;
} catch (\Throwable) {
if (($ver = $builder->getPHPVersionFromArchive()) !== false) {
$indent_texts['PHP Version'] = $ver;
}
}
if (!empty($not_included)) { if (!empty($not_included)) {
logger()->warning('Some extensions will be enabled due to dependencies: ' . implode(',', $not_included)); $indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included);
} }
logger()->info('Build will start after 2s ...'); $this->printFormatInfo($indent_texts);
logger()->notice('Build will start after 2s ...');
sleep(2); sleep(2);
if ($this->input->getOption('with-clean')) { if ($this->input->getOption('with-clean')) {

View File

@@ -60,7 +60,7 @@ class BuildLibsCommand extends BuildCommand
// 只编译 library 的情况下,标记 // 只编译 library 的情况下,标记
$builder->setLibsOnly(); $builder->setLibsOnly();
// 编译和检查库完整 // 编译和检查库完整
$libraries = DependencyUtil::getLibsByDeps($libraries); $libraries = DependencyUtil::getLibs($libraries);
$builder->buildLibs($libraries); $builder->buildLibs($libraries);
$time = round(microtime(true) - START_TIME, 3); $time = round(microtime(true) - START_TIME, 3);

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand('del-download', 'Remove locked download source or package using name', ['delete-download', 'del-down'])]
class DeleteDownloadCommand extends BaseCommand
{
public function configure(): void
{
$this->addArgument('sources', InputArgument::REQUIRED, 'The sources/packages will be deleted, comma separated');
$this->addOption('all', 'A', null, 'Delete all downloaded and locked sources/packages');
}
public function initialize(InputInterface $input, OutputInterface $output): void
{
if ($input->getOption('all')) {
$input->setArgument('sources', '');
}
parent::initialize($input, $output);
}
/**
* @throws FileSystemException
*/
public function handle(): int
{
try {
// get source list that will be downloaded
$sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
if (empty($sources)) {
logger()->notice('Removing downloads/ directory ...');
FileSystem::removeDir(DOWNLOAD_PATH);
logger()->info('Removed downloads/ dir!');
return static::SUCCESS;
}
$chosen_sources = $sources;
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
foreach ($chosen_sources as $source) {
$source = trim($source);
if (!isset($lock[$source])) {
logger()->warning("Source/Package [{$source}] not locked or not downloaded, skipped.");
continue;
}
// remove download file/dir if exists
if ($lock[$source]['source_type'] === 'archive') {
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$source]['filename']))) {
logger()->info('Deleting file ' . $path);
unlink($path);
} else {
logger()->warning("Source/Package [{$source}] file not found, skip deleting file.");
}
} else {
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$source]['dirname']))) {
logger()->info('Deleting dir ' . $path);
FileSystem::removeDir($path);
} else {
logger()->warning("Source/Package [{$source}] directory not found, skip deleting dir.");
}
}
// remove locked sources
unset($lock[$source]);
}
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
logger()->info('Delete success!');
return static::SUCCESS;
} catch (DownloaderException $e) {
logger()->error($e->getMessage());
return static::FAILURE;
} catch (WrongUsageException $e) {
logger()->critical($e->getMessage());
return static::FAILURE;
}
}
}

View File

@@ -36,26 +36,60 @@ class DownloadCommand extends BaseCommand
$this->addOption('custom-url', 'U', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Specify custom source download url, e.g "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"'); $this->addOption('custom-url', 'U', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Specify custom source download url, e.g "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"');
$this->addOption('from-zip', 'Z', InputOption::VALUE_REQUIRED, 'Fetch from zip archive'); $this->addOption('from-zip', 'Z', InputOption::VALUE_REQUIRED, 'Fetch from zip archive');
$this->addOption('for-extensions', 'e', InputOption::VALUE_REQUIRED, 'Fetch by extensions, e.g "openssl,mbstring"'); $this->addOption('for-extensions', 'e', InputOption::VALUE_REQUIRED, 'Fetch by extensions, e.g "openssl,mbstring"');
$this->addOption('for-libs', 'l', InputOption::VALUE_REQUIRED, 'Fetch by libraries, e.g "libcares,openssl,onig"');
$this->addOption('without-suggestions', null, null, 'Do not fetch suggested sources when using --for-extensions'); $this->addOption('without-suggestions', null, null, 'Do not fetch suggested sources when using --for-extensions');
$this->addOption('ignore-cache-sources', null, InputOption::VALUE_REQUIRED, 'Ignore some source caches, comma separated, e.g "php-src,curl,openssl"', '');
} }
/**
* @throws FileSystemException
* @throws WrongUsageException
*/
public function initialize(InputInterface $input, OutputInterface $output): void public function initialize(InputInterface $input, OutputInterface $output): void
{ {
if ( // mode: --all
$input->getOption('all') if ($input->getOption('all')) {
|| $input->getOption('clean') $input->setArgument('sources', implode(',', array_keys(Config::getSources())));
|| $input->getOption('from-zip') parent::initialize($input, $output);
|| $input->getOption('for-extensions') return;
) { }
// mode: --clean and --from-zip
if ($input->getOption('clean') || $input->getOption('from-zip')) {
$input->setArgument('sources', ''); $input->setArgument('sources', '');
parent::initialize($input, $output);
return;
}
// mode: normal
if (!empty($input->getArgument('sources'))) {
$final_sources = array_map('trim', array_filter(explode(',', $input->getArgument('sources'))));
} else {
$final_sources = [];
}
// mode: --for-extensions
if ($for_ext = $input->getOption('for-extensions')) {
$ext = array_map('trim', array_filter(explode(',', $for_ext)));
$sources = $this->calculateSourcesByExt($ext, !$input->getOption('without-suggestions'));
if (PHP_OS_FAMILY !== 'Windows') {
array_unshift($sources, 'pkg-config');
}
array_unshift($sources, 'php-src', 'micro');
$final_sources = array_merge($final_sources, array_diff($sources, $final_sources));
}
// mode: --for-libs
if ($for_lib = $input->getOption('for-libs')) {
$lib = array_map('trim', array_filter(explode(',', $for_lib)));
$sources = $this->calculateSourcesByLib($lib, !$input->getOption('without-suggestions'));
$final_sources = array_merge($final_sources, array_diff($sources, $final_sources));
}
if (!empty($final_sources)) {
$input->setArgument('sources', implode(',', $final_sources));
} }
parent::initialize($input, $output); parent::initialize($input, $output);
} }
/** /**
* @throws DownloaderException
* @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException
* @throws RuntimeException
*/ */
public function handle(): int public function handle(): int
{ {
@@ -107,20 +141,12 @@ class DownloadCommand extends BaseCommand
Config::$source['openssl']['regex'] = '/href="(?<file>openssl-(?<version>1.[^"]+)\.tar\.gz)\"/'; Config::$source['openssl']['regex'] = '/href="(?<file>openssl-(?<version>1.[^"]+)\.tar\.gz)\"/';
} }
// --for-extensions $chosen_sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
if ($for_ext = $this->getOption('for-extensions')) { $force_list = array_map('trim', array_filter(explode(',', $this->getOption('ignore-cache-sources'))));
$ext = array_map('trim', array_filter(explode(',', $for_ext)));
$sources = $this->calculateSourcesByExt($ext, !$this->getOption('without-suggestions')); if ($this->getOption('all')) {
array_unshift($sources, 'php-src', 'micro', 'pkg-config'); logger()->notice('Downloading with --all option will take more times to download, we recommend you to download with --for-extensions option !');
} else {
// get source list that will be downloaded
$sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
if (empty($sources)) {
logger()->notice('Downloading with --all option will take more times to download, we recommend you to download with --for-extensions option !');
$sources = array_keys(Config::getSources());
}
} }
$chosen_sources = $sources;
// Process -U options // Process -U options
$custom_urls = []; $custom_urls = [];
@@ -151,7 +177,7 @@ class DownloadCommand extends BaseCommand
Downloader::downloadSource($source, $new_config, true); Downloader::downloadSource($source, $new_config, true);
} else { } else {
logger()->info("Fetching source {$source} [{$ni}/{$cnt}]"); logger()->info("Fetching source {$source} [{$ni}/{$cnt}]");
Downloader::downloadSource($source, Config::getSource($source)); Downloader::downloadSource($source, Config::getSource($source), in_array($source, $force_list));
} }
} }
$time = round(microtime(true) - START_TIME, 3); $time = round(microtime(true) - START_TIME, 3);
@@ -166,6 +192,10 @@ class DownloadCommand extends BaseCommand
} }
} }
/**
* @throws RuntimeException
* @throws WrongUsageException
*/
private function downloadFromZip(string $path): int private function downloadFromZip(string $path): int
{ {
if (!file_exists($path)) { if (!file_exists($path)) {
@@ -191,8 +221,10 @@ class DownloadCommand extends BaseCommand
if (PHP_OS_FAMILY !== 'Windows') { if (PHP_OS_FAMILY !== 'Windows') {
$abs_path = realpath($path); $abs_path = realpath($path);
f_passthru('mkdir ' . DOWNLOAD_PATH . ' && cd ' . DOWNLOAD_PATH . ' && unzip ' . escapeshellarg($abs_path)); f_passthru('mkdir ' . DOWNLOAD_PATH . ' && cd ' . DOWNLOAD_PATH . ' && unzip ' . escapeshellarg($abs_path));
} else {
// Windows TODO
throw new WrongUsageException('Windows currently does not support --from-zip !');
} }
// Windows TODO
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) { if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
throw new RuntimeException('.lock.json not exist in "downloads/"'); throw new RuntimeException('.lock.json not exist in "downloads/"');
@@ -208,13 +240,14 @@ class DownloadCommand extends BaseCommand
/** /**
* Calculate the sources by extensions * Calculate the sources by extensions
* *
* @param array $extensions extension list * @param array $extensions extension list
* @param bool $include_suggests include suggested libs and extensions (default: true)
* @throws FileSystemException * @throws FileSystemException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
private function calculateSourcesByExt(array $extensions, bool $include_suggests = true): array private function calculateSourcesByExt(array $extensions, bool $include_suggests = true): array
{ {
[$extensions, $libraries] = $include_suggests ? DependencyUtil::getAllExtLibsByDeps($extensions) : DependencyUtil::getExtLibsByDeps($extensions); [$extensions, $libraries] = $include_suggests ? DependencyUtil::getExtsAndLibs($extensions, [], true, true) : DependencyUtil::getExtsAndLibs($extensions);
$sources = []; $sources = [];
foreach ($extensions as $extension) { foreach ($extensions as $extension) {
if (Config::getExt($extension, 'type') === 'external') { if (Config::getExt($extension, 'type') === 'external') {
@@ -226,4 +259,22 @@ class DownloadCommand extends BaseCommand
} }
return array_values(array_unique($sources)); return array_values(array_unique($sources));
} }
/**
* Calculate the sources by libraries
*
* @param array $libs library list
* @param bool $include_suggests include suggested libs (default: true)
* @throws FileSystemException
* @throws WrongUsageException
*/
private function calculateSourcesByLib(array $libs, bool $include_suggests = true): array
{
$libs = DependencyUtil::getLibs($libs, $include_suggests);
$sources = [];
foreach ($libs as $library) {
$sources[] = Config::getLib($library, 'source');
}
return array_values(array_unique($sources));
}
} }

View File

@@ -22,8 +22,8 @@ class DumpLicenseCommand extends BaseCommand
{ {
$this->addOption('for-extensions', null, InputOption::VALUE_REQUIRED, 'Dump by extensions and related libraries', null); $this->addOption('for-extensions', null, InputOption::VALUE_REQUIRED, 'Dump by extensions and related libraries', null);
$this->addOption('without-php', null, InputOption::VALUE_NONE, 'Dump without php-src'); $this->addOption('without-php', null, InputOption::VALUE_NONE, 'Dump without php-src');
$this->addOption('by-libs', null, InputOption::VALUE_REQUIRED, 'Dump by libraries', null); $this->addOption('for-libs', null, InputOption::VALUE_REQUIRED, 'Dump by libraries', null);
$this->addOption('by-sources', null, InputOption::VALUE_REQUIRED, 'Dump by original sources (source.json)', null); $this->addOption('for-sources', null, InputOption::VALUE_REQUIRED, 'Dump by original sources (source.json)', null);
$this->addOption('dump-dir', null, InputOption::VALUE_REQUIRED, 'Change dump directory', BUILD_ROOT_PATH . '/license'); $this->addOption('dump-dir', null, InputOption::VALUE_REQUIRED, 'Change dump directory', BUILD_ROOT_PATH . '/license');
} }
@@ -39,7 +39,7 @@ class DumpLicenseCommand extends BaseCommand
// 从参数中获取要编译的 extensions并转换为数组 // 从参数中获取要编译的 extensions并转换为数组
$extensions = array_map('trim', array_filter(explode(',', $this->getOption('for-extensions')))); $extensions = array_map('trim', array_filter(explode(',', $this->getOption('for-extensions'))));
// 根据提供的扩展列表获取依赖库列表并编译 // 根据提供的扩展列表获取依赖库列表并编译
[$extensions, $libraries] = DependencyUtil::getExtLibsByDeps($extensions); [$extensions, $libraries] = DependencyUtil::getExtsAndLibs($extensions);
$dumper->addExts($extensions); $dumper->addExts($extensions);
$dumper->addLibs($libraries); $dumper->addLibs($libraries);
if (!$this->getOption('without-php')) { if (!$this->getOption('without-php')) {
@@ -52,22 +52,22 @@ class DumpLicenseCommand extends BaseCommand
$this->output->writeln('Dump target dir: ' . $this->getOption('dump-dir')); $this->output->writeln('Dump target dir: ' . $this->getOption('dump-dir'));
return static::SUCCESS; return static::SUCCESS;
} }
if ($this->getOption('by-libs') !== null) { if ($this->getOption('for-libs') !== null) {
$libraries = array_map('trim', array_filter(explode(',', $this->getOption('by-libs')))); $libraries = array_map('trim', array_filter(explode(',', $this->getOption('for-libs'))));
$libraries = DependencyUtil::getLibsByDeps($libraries); $libraries = DependencyUtil::getLibs($libraries);
$dumper->addLibs($libraries); $dumper->addLibs($libraries);
$dumper->dump($this->getOption('dump-dir')); $dumper->dump($this->getOption('dump-dir'));
$this->output->writeln('Dump target dir: ' . $this->getOption('dump-dir')); $this->output->writeln('Dump target dir: ' . $this->getOption('dump-dir'));
return static::SUCCESS; return static::SUCCESS;
} }
if ($this->getOption('by-sources') !== null) { if ($this->getOption('for-sources') !== null) {
$sources = array_map('trim', array_filter(explode(',', $this->getOption('by-sources')))); $sources = array_map('trim', array_filter(explode(',', $this->getOption('for-sources'))));
$dumper->addSources($sources); $dumper->addSources($sources);
$dumper->dump($this->getOption('dump-dir')); $dumper->dump($this->getOption('dump-dir'));
$this->output->writeln('Dump target dir: ' . $this->getOption('dump-dir')); $this->output->writeln('Dump target dir: ' . $this->getOption('dump-dir'));
return static::SUCCESS; return static::SUCCESS;
} }
$this->output->writeln('You must use one of "--for-extensions=", "--by-libs=", "--by-sources=" to dump'); $this->output->writeln('You must use one of "--for-extensions=", "--for-libs=", "--for-sources=" to dump');
return static::FAILURE; return static::FAILURE;
} }
} }

View File

@@ -8,11 +8,11 @@ use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\SourceExtractor; use SPC\store\SourceManager;
use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
#[AsCommand('extract', 'Extract required sources')] #[AsCommand('extract', 'Extract required sources', ['extract-source'])]
class ExtractCommand extends BaseCommand class ExtractCommand extends BaseCommand
{ {
use UnixSystemUtilTrait; use UnixSystemUtilTrait;
@@ -34,7 +34,7 @@ class ExtractCommand extends BaseCommand
$this->output->writeln('<error>sources cannot be empty, at least contain one !</error>'); $this->output->writeln('<error>sources cannot be empty, at least contain one !</error>');
return static::FAILURE; return static::FAILURE;
} }
SourceExtractor::initSource(sources: $sources); SourceManager::initSource(sources: $sources);
logger()->info('Extract done !'); logger()->info('Extract done !');
return static::SUCCESS; return static::SUCCESS;
} }

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\PackageManager;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
#[AsCommand('install-pkg', 'Install additional packages', ['i', 'install-package'])]
class InstallPkgCommand extends BaseCommand
{
use UnixSystemUtilTrait;
public function configure(): void
{
$this->addArgument('packages', InputArgument::REQUIRED, 'The packages will be installed, comma separated');
$this->addOption('shallow-clone', null, null, 'Clone shallow');
$this->addOption('custom-url', 'U', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Specify custom source download url, e.g "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"');
}
/**
* @throws FileSystemException
*/
public function handle(): int
{
try {
// Use shallow-clone can reduce git resource download
if ($this->getOption('shallow-clone')) {
define('GIT_SHALLOW_CLONE', true);
}
// Process -U options
$custom_urls = [];
foreach ($this->input->getOption('custom-url') as $value) {
[$pkg_name, $url] = explode(':', $value, 2);
$custom_urls[$pkg_name] = $url;
}
$chosen_pkgs = array_map('trim', array_filter(explode(',', $this->getArgument('packages'))));
// Download them
f_mkdir(DOWNLOAD_PATH);
$ni = 0;
$cnt = count($chosen_pkgs);
foreach ($chosen_pkgs as $pkg) {
++$ni;
if (isset($custom_urls[$pkg])) {
$config = Config::getPkg($pkg);
$new_config = [
'type' => 'url',
'url' => $custom_urls[$pkg],
];
if (isset($config['extract'])) {
$new_config['extract'] = $config['extract'];
}
if (isset($config['filename'])) {
$new_config['filename'] = $config['filename'];
}
logger()->info("Installing source {$pkg} from custom url [{$ni}/{$cnt}]");
PackageManager::installPackage($pkg, $new_config);
} else {
logger()->info("Fetching package {$pkg} [{$ni}/{$cnt}]");
PackageManager::installPackage($pkg, Config::getPkg($pkg));
}
}
$time = round(microtime(true) - START_TIME, 3);
logger()->info('Install packages complete, used ' . $time . ' s !');
return static::SUCCESS;
} catch (DownloaderException $e) {
logger()->error($e->getMessage());
return static::FAILURE;
} catch (WrongUsageException $e) {
logger()->critical($e->getMessage());
return static::FAILURE;
}
}
}

View File

@@ -15,8 +15,6 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
use function Laravel\Prompts\table;
#[AsCommand('dev:extensions', 'Helper command that lists available extension details', ['list-ext'])] #[AsCommand('dev:extensions', 'Helper command that lists available extension details', ['list-ext'])]
class AllExtCommand extends BaseCommand class AllExtCommand extends BaseCommand
{ {
@@ -61,7 +59,7 @@ class AllExtCommand extends BaseCommand
} }
try { try {
[, $libraries, $not_included] = DependencyUtil::getExtLibsByDeps([$extension]); [, $libraries, $not_included] = DependencyUtil::getExtsAndLibs([$extension]);
} catch (WrongUsageException) { } catch (WrongUsageException) {
$libraries = $not_included = []; $libraries = $not_included = [];
} }
@@ -88,7 +86,8 @@ class AllExtCommand extends BaseCommand
if ($data === []) { if ($data === []) {
$style->warning('Unknown extension selected: ' . implode(',', $extensions)); $style->warning('Unknown extension selected: ' . implode(',', $extensions));
} else { } else {
table($columns, $data); $func = PHP_OS_FAMILY === 'Windows' ? [$style, 'table'] : '\Laravel\Prompts\table';
call_user_func($func, $columns, $data);
} }
return static::SUCCESS; return static::SUCCESS;

View File

@@ -57,6 +57,15 @@ class SortConfigCommand extends BaseCommand
return static::FAILURE; return static::FAILURE;
} }
break; break;
case 'pkg':
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/pkg.json'), true);
ConfigValidator::validatePkgs($file);
ksort($file);
if (!file_put_contents(ROOT_DIR . '/config/pkg.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n")) {
$this->output->writeln('<error>Write file pkg.json failed!</error>');
return static::FAILURE;
}
break;
default: default:
$this->output->writeln("<error>invalid config name: {$name}</error>"); $this->output->writeln("<error>invalid config name: {$name}</error>");
return 1; return 1;

View File

@@ -57,7 +57,7 @@ class BSDToolCheckList
$prefix = ''; $prefix = '';
} }
try { try {
shell(true)->exec("ASSUME_ALWAYS_YES=yes {$prefix} pkg install -y " . implode(' ', $missing)); shell(true)->exec("ASSUME_ALWAYS_YES=yes {$prefix}pkg install -y " . implode(' ', $missing));
} catch (RuntimeException) { } catch (RuntimeException) {
return false; return false;
} }

View File

@@ -14,6 +14,7 @@ use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Downloader; use SPC\store\Downloader;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\PackageManager;
class LinuxMuslCheck class LinuxMuslCheck
{ {
@@ -84,7 +85,6 @@ class LinuxMuslCheck
/** @noinspection PhpUnused */ /** @noinspection PhpUnused */
/** /**
* @throws DownloaderException
* @throws FileSystemException * @throws FileSystemException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
@@ -98,15 +98,10 @@ class LinuxMuslCheck
logger()->warning('Current user is not root, using sudo for running command'); logger()->warning('Current user is not root, using sudo for running command');
} }
$arch = arch2gnu(php_uname('m')); $arch = arch2gnu(php_uname('m'));
$musl_compile_source = [ PackageManager::installPackage("musl-toolchain-{$arch}-linux");
'type' => 'url', $pkg_root = PKG_ROOT_PATH . "/musl-toolchain-{$arch}-linux";
'url' => "https://dl.static-php.dev/static-php-cli/deps/musl-toolchain/{$arch}-musl-toolchain.tgz", shell()->exec("{$prefix}cp -rf {$pkg_root}/* /usr/local/musl");
]; FileSystem::removeDir($pkg_root);
logger()->info('Downloading ' . $musl_compile_source['url']);
Downloader::downloadSource('musl-compile', $musl_compile_source);
logger()->info('Extracting musl-cross');
FileSystem::extractSource('musl-compile', DOWNLOAD_PATH . "/{$arch}-musl-toolchain.tgz");
shell()->exec($prefix . 'cp -rf ' . SOURCE_PATH . '/musl-compile/* /usr/local/musl');
return true; return true;
} catch (RuntimeException) { } catch (RuntimeException) {
return false; return false;

View File

@@ -31,6 +31,7 @@ class MacOSToolCheckList
'gzip', 'gzip',
'bzip2', 'bzip2',
'cmake', 'cmake',
'glibtoolize',
]; ];
#[AsCheckItem('if homebrew has installed', limit_os: 'Darwin', level: 998)] #[AsCheckItem('if homebrew has installed', limit_os: 'Darwin', level: 998)]
@@ -72,8 +73,14 @@ class MacOSToolCheckList
#[AsFixItem('build-tools')] #[AsFixItem('build-tools')]
public function fixBuildTools(array $missing): bool public function fixBuildTools(array $missing): bool
{ {
$replacement = [
'glibtoolize' => 'libtool',
];
foreach ($missing as $cmd) { foreach ($missing as $cmd) {
try { try {
if (isset($replacement[$cmd])) {
$cmd = $replacement[$cmd];
}
shell(true)->exec('brew install --formula ' . escapeshellarg($cmd)); shell(true)->exec('brew install --formula ' . escapeshellarg($cmd));
} catch (RuntimeException) { } catch (RuntimeException) {
return false; return false;

View File

@@ -9,8 +9,8 @@ use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem; use SPC\doctor\AsFixItem;
use SPC\doctor\CheckResult; use SPC\doctor\CheckResult;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\store\Downloader;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\PackageManager;
class WindowsToolCheckList class WindowsToolCheckList
{ {
@@ -64,8 +64,9 @@ class WindowsToolCheckList
#[AsCheckItem('if perl(strawberry) installed', limit_os: 'Windows', level: 994)] #[AsCheckItem('if perl(strawberry) installed', limit_os: 'Windows', level: 994)]
public function checkPerl(): ?CheckResult public function checkPerl(): ?CheckResult
{ {
if (file_exists(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe')) { $arch = arch2gnu(php_uname('m'));
return CheckResult::ok(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe'); if (file_exists(PKG_ROOT_PATH . '\strawberry-perl-' . $arch . '-win\perl\bin\perl.exe')) {
return CheckResult::ok(PKG_ROOT_PATH . '\strawberry-perl-' . $arch . '-win\perl\bin\perl.exe');
} }
if (($path = SystemUtil::findCommand('perl.exe')) === null) { if (($path = SystemUtil::findCommand('perl.exe')) === null) {
return CheckResult::fail('perl not found in path.', 'install-perl'); return CheckResult::fail('perl not found in path.', 'install-perl');
@@ -91,32 +92,15 @@ class WindowsToolCheckList
#[AsFixItem('install-nasm')] #[AsFixItem('install-nasm')]
public function installNasm(): bool public function installNasm(): bool
{ {
// The hardcoded version here is to be consistent with the version compiled by `musl-cross-toolchain`. PackageManager::installPackage('nasm-x86_64-win');
$nasm_ver = '2.16.01';
$nasm_dist = "nasm-{$nasm_ver}";
$source = [
'type' => 'url',
'url' => "https://www.nasm.us/pub/nasm/releasebuilds/{$nasm_ver}/win64/{$nasm_dist}-win64.zip",
];
logger()->info('Downloading ' . $source['url']);
Downloader::downloadSource('nasm', $source);
FileSystem::extractSource('nasm', DOWNLOAD_PATH . "\\{$nasm_dist}-win64.zip");
copy(SOURCE_PATH . "\\nasm\\{$nasm_dist}\\nasm.exe", PHP_SDK_PATH . '\bin\nasm.exe');
copy(SOURCE_PATH . "\\nasm\\{$nasm_dist}\\ndisasm.exe", PHP_SDK_PATH . '\bin\ndisasm.exe');
return true; return true;
} }
#[AsFixItem('install-perl')] #[AsFixItem('install-perl')]
public function installPerl(): bool public function installPerl(): bool
{ {
$url = 'https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip'; $arch = arch2gnu(php_uname('m'));
$source = [ PackageManager::installPackage("strawberry-perl-{$arch}-win");
'type' => 'url',
'url' => $url,
];
logger()->info("Downloading {$url}");
Downloader::downloadSource('strawberry-perl', $source);
FileSystem::extractSource('strawberry-perl', DOWNLOAD_PATH . '\strawberry-perl-5.38.0.1-64bit-portable.zip', '../buildroot/perl');
return true; return true;
} }
} }

View File

@@ -12,6 +12,8 @@ use SPC\exception\WrongUsageException;
*/ */
class Config class Config
{ {
public static ?array $pkg = null;
public static ?array $source = null; public static ?array $source = null;
public static ?array $lib = null; public static ?array $lib = null;
@@ -31,6 +33,19 @@ class Config
return self::$source[$name] ?? null; return self::$source[$name] ?? null;
} }
/**
* Read pkg from pkg.json
*
* @throws FileSystemException
*/
public static function getPkg(string $name): ?array
{
if (self::$pkg === null) {
self::$pkg = FileSystem::loadConfigArray('pkg');
}
return self::$pkg[$name] ?? null;
}
/** /**
* 根据不同的操作系统分别选择不同的 lib 库依赖项 * 根据不同的操作系统分别选择不同的 lib 库依赖项
* 如果 key 为 null那么直接返回整个 meta。 * 如果 key 为 null那么直接返回整个 meta。

View File

@@ -246,6 +246,96 @@ class Downloader
}*/ }*/
} }
/**
* @throws DownloaderException
* @throws FileSystemException
*/
public static function downloadPackage(string $name, ?array $pkg = null, bool $force = false): void
{
if ($pkg === null) {
$pkg = Config::getPkg($name);
}
if ($pkg === null) {
logger()->warning('Package {name} unknown. Skipping.', ['name' => $name]);
return;
}
if (!is_dir(DOWNLOAD_PATH)) {
FileSystem::createDir(DOWNLOAD_PATH);
}
// load lock file
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading
if (isset($lock[$name]) && !$force) {
if ($lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename'])) {
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['filename']);
return;
}
if ($lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])) {
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['dirname']);
return;
}
}
try {
switch ($pkg['type']) {
case 'bitbuckettag': // BitBucket Tag
[$url, $filename] = self::getLatestBitbucketTag($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'ghtar': // GitHub Release (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'ghtagtar': // GitHub Tag (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $pkg, 'tags');
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'ghrel': // GitHub Release (uploaded)
[$url, $filename] = self::getLatestGithubRelease($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'filelist': // Basic File List (regex based crawler)
[$url, $filename] = self::getFromFileList($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'url': // Direct download URL
$url = $pkg['url'];
$filename = $pkg['filename'] ?? basename($pkg['url']);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'git': // Git repo
self::downloadGit($name, $pkg['url'], $pkg['rev'], $pkg['extract'] ?? null);
break;
case 'custom': // Custom download method, like API-based download or other
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\\store\\source');
foreach ($classes as $class) {
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
(new $class())->fetch($force);
break;
}
}
break;
default:
throw new DownloaderException('unknown source type: ' . $pkg['type']);
}
} catch (RuntimeException $e) {
// Because sometimes files downloaded through the command line are not automatically deleted after a failure.
// Here we need to manually delete the file if it is detected to exist.
if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) {
logger()->warning('Deleting download file: ' . $filename);
unlink(DOWNLOAD_PATH . '/' . $filename);
}
throw new DownloaderException('Download failed! ' . $e->getMessage());
}
}
/** /**
* Download source by name and meta. * Download source by name and meta.
* *
@@ -321,7 +411,7 @@ class Downloader
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\\store\\source'); $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\\store\\source');
foreach ($classes as $class) { foreach ($classes as $class) {
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) { if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
(new $class())->fetch(); (new $class())->fetch($force);
break; break;
} }
} }

View File

@@ -16,7 +16,7 @@ class FileSystem
*/ */
public static function loadConfigArray(string $config, ?string $config_dir = null): array public static function loadConfigArray(string $config, ?string $config_dir = null): array
{ {
$whitelist = ['ext', 'lib', 'source']; $whitelist = ['ext', 'lib', 'source', 'pkg'];
if (!in_array($config, $whitelist)) { if (!in_array($config, $whitelist)) {
throw new FileSystemException('Reading ' . $config . '.json is not allowed'); throw new FileSystemException('Reading ' . $config . '.json is not allowed');
} }
@@ -138,6 +138,37 @@ class FileSystem
} }
} }
/**
* @throws RuntimeException
* @throws FileSystemException
*/
public static function extractPackage(string $name, string $filename, ?string $extract_path = null): void
{
if ($extract_path !== null) {
// replace
$extract_path = self::replacePathVariable($extract_path);
$extract_path = self::isRelativePath($extract_path) ? (WORKING_DIR . '/' . $extract_path) : $extract_path;
} else {
$extract_path = PKG_ROOT_PATH . '/' . $name;
}
logger()->info("extracting {$name} package to {$extract_path} ...");
$target = self::convertPath($extract_path);
if (!is_dir($dir = dirname($target))) {
self::createDir($dir);
}
try {
self::extractArchive($filename, $target);
} catch (RuntimeException $e) {
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('rmdir /s /q ' . $target);
} else {
f_passthru('rm -rf ' . $target);
}
throw new FileSystemException('Cannot extract package ' . $name, $e->getCode(), $e);
}
}
/** /**
* 解压缩下载的资源包到 source 目录 * 解压缩下载的资源包到 source 目录
* *
@@ -152,52 +183,26 @@ class FileSystem
if (self::$_extract_hook === []) { if (self::$_extract_hook === []) {
SourcePatcher::init(); SourcePatcher::init();
} }
if (!is_dir(SOURCE_PATH)) {
self::createDir(SOURCE_PATH);
}
if ($move_path !== null) { if ($move_path !== null) {
$move_path = SOURCE_PATH . '/' . $move_path; $move_path = SOURCE_PATH . '/' . $move_path;
} else {
$move_path = SOURCE_PATH . "/{$name}";
}
$target = self::convertPath($move_path);
logger()->info("extracting {$name} source to {$target}" . ' ...');
if (!is_dir($dir = dirname($target))) {
self::createDir($dir);
} }
logger()->info("extracting {$name} source to " . ($move_path ?? SOURCE_PATH . "/{$name}") . ' ...');
try { try {
$target = self::convertPath($move_path ?? (SOURCE_PATH . "/{$name}")); self::extractArchive($filename, $target);
// Git source, just move
if (is_dir(self::convertPath($filename))) {
self::copyDir(self::convertPath($filename), $target);
self::emitSourceExtractHook($name);
return;
}
if (f_mkdir(directory: $target, recursive: true) !== true) {
throw new FileSystemException('create ' . $name . 'source dir failed');
}
if (in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD'])) {
match (self::extname($filename)) {
'tar', 'xz', 'txz' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
'tgz', 'gz' => f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1"),
'bz2' => f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1"),
'zip' => f_passthru("unzip {$filename} -d {$target}"),
default => throw new FileSystemException('unknown archive format: ' . $filename),
};
} elseif (PHP_OS_FAMILY === 'Windows') {
// use php-sdk-binary-tools/bin/7za.exe
$_7z = self::convertPath(PHP_SDK_PATH . '/bin/7za.exe');
f_mkdir(SOURCE_PATH . "/{$name}", recursive: true);
match (self::extname($filename)) {
'tar' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
'xz', 'txz', 'gz', 'tgz', 'bz2' => f_passthru("\"{$_7z}\" x -so {$filename} | tar -f - -x -C {$target} --strip-components 1"),
'zip' => f_passthru("\"{$_7z}\" x {$filename} -o{$target} -y"),
default => throw new FileSystemException("unknown archive format: {$filename}"),
};
}
self::emitSourceExtractHook($name); self::emitSourceExtractHook($name);
} catch (RuntimeException $e) { } catch (RuntimeException $e) {
if (PHP_OS_FAMILY === 'Windows') { if (PHP_OS_FAMILY === 'Windows') {
f_passthru('rmdir /s /q ' . SOURCE_PATH . "/{$name}"); f_passthru('rmdir /s /q ' . $target);
} else { } else {
f_passthru('rm -r ' . SOURCE_PATH . "/{$name}"); f_passthru('rm -rf ' . $target);
} }
throw new FileSystemException('Cannot extract source ' . $name, $e->getCode(), $e); throw new FileSystemException('Cannot extract source ' . $name . ': ' . $e->getMessage(), $e->getCode(), $e);
} }
} }
@@ -214,6 +219,14 @@ class FileSystem
return str_replace('/', DIRECTORY_SEPARATOR, $path); return str_replace('/', DIRECTORY_SEPARATOR, $path);
} }
public static function convertWinPathToMinGW(string $path): string
{
if (preg_match('/^[A-Za-z]:/', $path)) {
$path = '/' . strtolower(substr($path, 0, 1)) . '/' . str_replace('\\', '/', substr($path, 2));
}
return $path;
}
/** /**
* 递归或非递归扫描目录,可返回相对目录的文件列表或绝对目录的文件列表 * 递归或非递归扫描目录,可返回相对目录的文件列表或绝对目录的文件列表
* *
@@ -227,8 +240,12 @@ class FileSystem
{ {
$dir = self::convertPath($dir); $dir = self::convertPath($dir);
// 不是目录不扫,直接 false 处理 // 不是目录不扫,直接 false 处理
if (!file_exists($dir)) {
logger()->debug('Scan dir failed, no such file or directory.');
return false;
}
if (!is_dir($dir)) { if (!is_dir($dir)) {
logger()->warning('Scan dir failed, no such directory.'); logger()->warning('Scan dir failed, not directory.');
return false; return false;
} }
logger()->debug('scanning directory ' . $dir); logger()->debug('scanning directory ' . $dir);
@@ -317,8 +334,12 @@ class FileSystem
$dir = FileSystem::convertPath($dir); $dir = FileSystem::convertPath($dir);
logger()->debug('Removing path recursively: "' . $dir . '"'); logger()->debug('Removing path recursively: "' . $dir . '"');
// 不是目录不扫,直接 false 处理 // 不是目录不扫,直接 false 处理
if (!file_exists($dir)) {
logger()->debug('Scan dir failed, no such file or directory.');
return false;
}
if (!is_dir($dir)) { if (!is_dir($dir)) {
logger()->warning('Scan dir failed, no such directory.'); logger()->warning('Scan dir failed, not directory.');
return false; return false;
} }
logger()->debug('scanning directory ' . $dir); logger()->debug('scanning directory ' . $dir);
@@ -403,6 +424,63 @@ class FileSystem
return strlen($path) > 0 && $path[0] !== '/'; return strlen($path) > 0 && $path[0] !== '/';
} }
public static function replacePathVariable(string $path): string
{
$replacement = [
'{pkg_root_path}' => PKG_ROOT_PATH,
'{php_sdk_path}' => defined('PHP_SDK_PATH') ? PHP_SDK_PATH : WORKING_DIR . '/php-sdk-binary-tools',
'{working_dir}' => WORKING_DIR,
'{download_path}' => DOWNLOAD_PATH,
'{source_path}' => SOURCE_PATH,
];
return str_replace(array_keys($replacement), array_values($replacement), $path);
}
/**
* @throws RuntimeException
* @throws FileSystemException
*/
private static function extractArchive(string $filename, string $target): void
{
// Git source, just move
if (is_dir(self::convertPath($filename))) {
self::copyDir(self::convertPath($filename), $target);
return;
}
// Create base dir
if (f_mkdir(directory: $target, recursive: true) !== true) {
throw new FileSystemException('create ' . $target . ' dir failed');
}
if (!file_exists($filename)) {
throw new FileSystemException('File not exists');
}
if (in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD'])) {
match (self::extname($filename)) {
'tar', 'xz', 'txz' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
'tgz', 'gz' => f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1"),
'bz2' => f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1"),
'zip' => f_passthru("unzip {$filename} -d {$target}"),
default => throw new FileSystemException('unknown archive format: ' . $filename),
};
} elseif (PHP_OS_FAMILY === 'Windows') {
// use php-sdk-binary-tools/bin/7za.exe
$_7z = self::convertPath(PHP_SDK_PATH . '/bin/7za.exe');
// Windows notes: I hate windows tar.......
// When extracting .tar.gz like libxml2, it shows a symlink error and returns code[1].
// Related posts: https://answers.microsoft.com/en-us/windows/forum/all/tar-on-windows-fails-to-extract-archive-containing/0ee9a7ea-9b1f-4fef-86a9-5d9dc35cea2f
// And MinGW tar.exe cannot work on temporarily storage ??? (GitHub Actions hosted runner)
// Yeah, I will be an MS HATER !
match (self::extname($filename)) {
'tar' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
'xz', 'txz', 'gz', 'tgz', 'bz2' => cmd()->execWithResult("\"{$_7z}\" x -so {$filename} | tar -f - -x -C \"{$target}\" --strip-components 1"),
'zip' => f_passthru("\"{$_7z}\" x {$filename} -o{$target} -y"),
default => throw new FileSystemException("unknown archive format: {$filename}"),
};
}
}
/** /**
* @throws FileSystemException * @throws FileSystemException
*/ */

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace SPC\store;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
class PackageManager
{
public static function installPackage(string $pkg_name, ?array $config = null, bool $force = false): void
{
if ($config === null) {
$config = Config::getPkg($pkg_name);
}
if ($config === null) {
$arch = arch2gnu(php_uname('m'));
$os = match (PHP_OS_FAMILY) {
'Linux' => 'linux',
'Windows' => 'win',
'BSD' => 'freebsd',
'Darwin' => 'macos',
default => throw new WrongUsageException('Unsupported OS!'),
};
$config = Config::getPkg("{$pkg_name}-{$arch}-{$os}");
}
if ($config === null) {
throw new WrongUsageException("Package [{$pkg_name}] does not exist, please check the name and correct it !");
}
// Download package
Downloader::downloadPackage($pkg_name, $config, $force);
// After download, read lock file name
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
$filename = DOWNLOAD_PATH . '/' . ($lock[$pkg_name]['filename'] ?? $lock[$pkg_name]['dirname']);
$extract = $lock[$pkg_name]['move_path'] === null ? (PKG_ROOT_PATH . '/' . $pkg_name) : $lock[$pkg_name]['move_path'];
FileSystem::extractPackage($pkg_name, $filename, $extract);
// if contains extract-files, we just move this file to destination, and remove extract dir
if (is_array($config['extract-files'] ?? null) && is_assoc_array($config['extract-files'])) {
$scandir = FileSystem::scanDirFiles($extract, true, true);
foreach ($config['extract-files'] as $file => $target) {
$target = FileSystem::convertPath(FileSystem::replacePathVariable($target));
if (!is_dir($dir = dirname($target))) {
f_mkdir($dir, 0755, true);
}
logger()->debug("Moving package [{$pkg_name}] file {$file} to {$target}");
// match pattern, needs to scan dir
$file = FileSystem::convertPath($file);
$found = false;
foreach ($scandir as $item) {
if (match_pattern($file, $item)) {
$file = $item;
$found = true;
break;
}
}
if ($found === false) {
throw new FileSystemException('Unable to find extract-files item: ' . $file);
}
rename(FileSystem::convertPath($extract . '/' . $file), $target);
}
FileSystem::removeDir($extract);
}
}
}

View File

@@ -8,7 +8,7 @@ use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
class SourceExtractor class SourceManager
{ {
/** /**
* @throws WrongUsageException * @throws WrongUsageException

View File

@@ -106,7 +106,7 @@ class SourcePatcher
} }
$patch_list = $list ?? $default; $patch_list = $list ?? $default;
$patches = []; $patches = [];
$serial = ['80', '81', '82', '83']; $serial = ['80', '81', '82', '83', '84'];
foreach ($patch_list as $patchName) { foreach ($patch_list as $patchName) {
if (file_exists(SOURCE_PATH . "/php-src/sapi/micro/patches/{$patchName}.patch")) { if (file_exists(SOURCE_PATH . "/php-src/sapi/micro/patches/{$patchName}.patch")) {
$patches[] = "sapi/micro/patches/{$patchName}.patch"; $patches[] = "sapi/micro/patches/{$patchName}.patch";
@@ -197,7 +197,6 @@ class SourcePatcher
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_STRLCAT 1$/m', ''); FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_STRLCAT 1$/m', '');
} }
if ($builder instanceof UnixBuilderBase) { if ($builder instanceof UnixBuilderBase) {
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_OPENPTY 1$/m', '');
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'install-micro', ''); FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'install-micro', '');
} }

View File

@@ -8,5 +8,5 @@ abstract class CustomSourceBase
{ {
public const NAME = 'unknown'; public const NAME = 'unknown';
abstract public function fetch(); abstract public function fetch(bool $force = false);
} }

View File

@@ -7,7 +7,6 @@ namespace SPC\store\source;
use JetBrains\PhpStorm\ArrayShape; use JetBrains\PhpStorm\ArrayShape;
use SPC\exception\DownloaderException; use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\Downloader; use SPC\store\Downloader;
class PhpSource extends CustomSourceBase class PhpSource extends CustomSourceBase
@@ -16,13 +15,12 @@ class PhpSource extends CustomSourceBase
/** /**
* @throws DownloaderException * @throws DownloaderException
* @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException
*/ */
public function fetch(): void public function fetch(bool $force = false): void
{ {
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.1'; $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.1';
Downloader::downloadSource('php-src', self::getLatestPHPInfo($major)); Downloader::downloadSource('php-src', self::getLatestPHPInfo($major), $force);
} }
/** /**

View File

@@ -6,7 +6,6 @@ namespace SPC\store\source;
use SPC\exception\DownloaderException; use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\Downloader; use SPC\store\Downloader;
class PostgreSQLSource extends CustomSourceBase class PostgreSQLSource extends CustomSourceBase
@@ -15,12 +14,11 @@ class PostgreSQLSource extends CustomSourceBase
/** /**
* @throws DownloaderException * @throws DownloaderException
* @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException
*/ */
public function fetch(): void public function fetch(bool $force = false): void
{ {
Downloader::downloadSource('postgresql', self::getLatestInfo()); Downloader::downloadSource('postgresql', self::getLatestInfo(), $force);
} }
/** /**

View File

@@ -70,4 +70,12 @@ class ConfigValidator
{ {
is_array($data) || throw new ValidationException('ext.json is broken'); is_array($data) || throw new ValidationException('ext.json is broken');
} }
/**
* @throws ValidationException
*/
public static function validatePkgs(mixed $data): void
{
is_array($data) || throw new ValidationException('pkg.json is broken');
}
} }

View File

@@ -5,90 +5,164 @@ declare(strict_types=1);
namespace SPC\util; namespace SPC\util;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\store\Config; use SPC\store\Config;
/** /**
* 依赖处理工具类,包含处理扩展、库的依赖列表顺序等 * Dependency processing tool class, including processing extensions, library dependency list order, etc.
*/ */
class DependencyUtil class DependencyUtil
{ {
/** /**
* Obtain the dependent lib list according to the required ext list, and sort according to the dependency * Convert platform extensions to library dependencies and suggestions.
* *
* @param array $exts extensions list
* @param array $additional_libs List of additional libraries to add to activate the extra library features triggered by lib-suggests
* @return array Returns an array containing three arrays, [extensions, libraries, not included extensions]
* @throws WrongUsageException * @throws WrongUsageException
* @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException
*/ */
public static function getExtLibsByDeps(array $exts, array $additional_libs = []): array public static function platExtToLibs(): array
{ {
$sorted = []; $exts = Config::getExts();
$visited = []; $libs = Config::getLibs();
$not_included_exts = []; $dep_list = [];
foreach ($exts as $ext) { foreach ($exts as $ext_name => $ext) {
if (!isset($visited[$ext])) { // convert ext-depends value to ext@xxx
self::visitExtDeps($ext, $visited, $sorted); $ext_depends = Config::getExt($ext_name, 'ext-depends', []);
} $ext_depends = array_map(fn ($x) => "ext@{$x}", $ext_depends);
// convert ext-suggests value to ext@xxx
$ext_suggests = Config::getExt($ext_name, 'ext-suggests', []);
$ext_suggests = array_map(fn ($x) => "ext@{$x}", $ext_suggests);
// merge ext-depends with lib-depends
$lib_depends = Config::getExt($ext_name, 'lib-depends', []);
$depends = array_merge($ext_depends, $lib_depends);
// merge ext-suggests with lib-suggests
$lib_suggests = Config::getExt($ext_name, 'lib-suggests', []);
$suggests = array_merge($ext_suggests, $lib_suggests);
$dep_list["ext@{$ext_name}"] = [
'depends' => $depends,
'suggests' => $suggests,
];
} }
$sorted_suggests = []; foreach ($libs as $lib_name => $lib) {
$visited_suggests = []; $dep_list[$lib_name] = [
$final = []; 'depends' => Config::getLib($lib_name, 'lib-depends', []),
foreach ($exts as $ext) { 'suggests' => Config::getLib($lib_name, 'lib-suggests', []),
if (!isset($visited_suggests[$ext])) { ];
self::visitExtAllDeps($ext, $visited_suggests, $sorted_suggests);
}
} }
foreach ($sorted_suggests as $suggest) { // here is an array that only contains dependency map
if (in_array($suggest, $sorted)) { return $dep_list;
$final[] = $suggest;
}
}
$libs = $additional_libs;
foreach ($final as $ext) {
if (!in_array($ext, $exts)) {
$not_included_exts[] = $ext;
}
foreach (Config::getExt($ext, 'lib-depends', []) as $lib) {
if (!in_array($lib, $libs)) {
$libs[] = $lib;
}
}
}
return [$final, self::getLibsByDeps($libs), $not_included_exts];
} }
/** /**
* 根据 lib 库的依赖关系进行一个排序,同时返回多出来的依赖列表
*
* @param array $libs 要排序的 libs 列表
* @return array 排序后的列表
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException * @throws WrongUsageException
* @throws FileSystemException
*/ */
public static function getLibsByDeps(array $libs): array public static function getLibs(array $libs, bool $include_suggested_libs = false): array
{ {
$sorted = []; $dep_list = self::platExtToLibs();
$visited = [];
// 遍历所有 if ($include_suggested_libs) {
foreach ($libs as $lib) { foreach ($dep_list as $name => $obj) {
if (!isset($visited[$lib])) { foreach ($obj['suggests'] as $id => $suggest) {
self::visitLibDeps($lib, $visited, $sorted); if (!str_starts_with($suggest, 'ext@')) {
$dep_list[$name]['depends'][] = $suggest;
array_splice($dep_list[$name]['suggests'], $id, 1);
}
}
} }
} }
$final = self::doVisitPlat($libs, $dep_list);
$libs_final = [];
foreach ($final as $item) {
if (!str_starts_with($item, 'ext@')) {
$libs_final[] = $item;
}
}
return $libs_final;
}
/**
* @throws FileSystemException|WrongUsageException
*/
public static function getExtsAndLibs(array $exts, array $additional_libs = [], bool $include_suggested_exts = false, bool $include_suggested_libs = false): array
{
$dep_list = self::platExtToLibs();
// include suggested extensions
if ($include_suggested_exts) {
// check every deps suggests contains ext@
foreach ($dep_list as $name => $obj) {
foreach ($obj['suggests'] as $id => $suggest) {
if (str_starts_with($suggest, 'ext@')) {
$dep_list[$name]['depends'][] = $suggest;
array_splice($dep_list[$name]['suggests'], $id, 1);
}
}
}
}
// include suggested libraries
if ($include_suggested_libs) {
// check every deps suggests
foreach ($dep_list as $name => $obj) {
foreach ($obj['suggests'] as $id => $suggest) {
if (!str_starts_with($suggest, 'ext@')) {
$dep_list[$name]['depends'][] = $suggest;
array_splice($dep_list[$name]['suggests'], $id, 1);
}
}
}
}
// convert ext_name to ext@ext_name
$origin_exts = $exts;
$exts = array_map(fn ($x) => "ext@{$x}", $exts);
$exts = array_merge($exts, $additional_libs);
$final = self::doVisitPlat($exts, $dep_list);
// revert array
$exts_final = [];
$libs_final = [];
$not_included_final = [];
foreach ($final as $item) {
if (str_starts_with($item, 'ext@')) {
$tmp = substr($item, 4);
if (!in_array($tmp, $origin_exts)) {
$not_included_final[] = $tmp;
}
$exts_final[] = $tmp;
} else {
$libs_final[] = $item;
}
}
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
$sorted = [];
$visited = [];
foreach ($deps as $ext_name) {
if (!isset($dep_list[$ext_name])) {
$ext_name = str_starts_with($ext_name, 'ext@') ? ('Extension [' . substr($ext_name, 4) . ']') : ('Library [' . $ext_name . ']');
throw new WrongUsageException("{$ext_name} not exist !");
}
if (!isset($visited[$ext_name])) {
self::visitPlatDeps($ext_name, $dep_list, $visited, $sorted);
}
}
$sorted_suggests = []; $sorted_suggests = [];
$visited_suggests = []; $visited_suggests = [];
$final = []; $final = [];
foreach ($libs as $lib) { foreach ($deps as $ext_name) {
if (!isset($visited_suggests[$lib])) { if (!isset($visited_suggests[$ext_name])) {
self::visitLibAllDeps($lib, $visited_suggests, $sorted_suggests); self::visitPlatAllDeps($ext_name, $dep_list, $visited_suggests, $sorted_suggests);
} }
} }
foreach ($sorted_suggests as $suggest) { foreach ($sorted_suggests as $suggest) {
@@ -99,49 +173,7 @@ class DependencyUtil
return $final; return $final;
} }
public static function getAllExtLibsByDeps(array $exts): array private static function visitPlatAllDeps(string $lib_name, array $dep_list, array &$visited, array &$sorted): void
{
$sorted = [];
$visited = [];
$not_included_exts = [];
foreach ($exts as $ext) {
if (!isset($visited[$ext])) {
self::visitExtAllDeps($ext, $visited, $sorted);
}
}
$libs = [];
foreach ($sorted as $ext) {
if (!in_array($ext, $exts)) {
$not_included_exts[] = $ext;
}
foreach (array_merge(Config::getExt($ext, 'lib-depends', []), Config::getExt($ext, 'lib-suggests', [])) as $dep) {
if (!in_array($dep, $libs)) {
$libs[] = $dep;
}
}
}
return [$sorted, self::getAllLibsByDeps($libs), $not_included_exts];
}
public static function getAllLibsByDeps(array $libs): array
{
$sorted = [];
$visited = [];
foreach ($libs as $lib) {
if (!isset($visited[$lib])) {
self::visitLibAllDeps($lib, $visited, $sorted);
}
}
return $sorted;
}
/**
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
*/
private static function visitLibAllDeps(string $lib_name, array &$visited, array &$sorted): void
{ {
// 如果已经识别到了,那就不管 // 如果已经识别到了,那就不管
if (isset($visited[$lib_name])) { if (isset($visited[$lib_name])) {
@@ -149,37 +181,13 @@ class DependencyUtil
} }
$visited[$lib_name] = true; $visited[$lib_name] = true;
// 遍历该依赖的所有依赖(此处的 getLib 如果检测到当前库不存在的话,会抛出异常) // 遍历该依赖的所有依赖(此处的 getLib 如果检测到当前库不存在的话,会抛出异常)
foreach (array_merge(Config::getLib($lib_name, 'lib-depends', []), Config::getLib($lib_name, 'lib-suggests', [])) as $dep) { foreach (array_merge($dep_list[$lib_name]['depends'], $dep_list[$lib_name]['suggests']) as $dep) {
self::visitLibDeps($dep, $visited, $sorted); self::visitPlatAllDeps($dep, $dep_list, $visited, $sorted);
} }
$sorted[] = $lib_name; $sorted[] = $lib_name;
} }
/** private static function visitPlatDeps(string $lib_name, array $dep_list, array &$visited, array &$sorted): void
* @throws RuntimeException
* @throws FileSystemException
* @throws WrongUsageException
*/
private static function visitExtAllDeps(string $ext_name, array &$visited, array &$sorted): void
{
// 如果已经识别到了,那就不管
if (isset($visited[$ext_name])) {
return;
}
$visited[$ext_name] = true;
// 遍历该依赖的所有依赖(此处的 getLib 如果检测到当前库不存在的话,会抛出异常)
foreach (array_merge(Config::getExt($ext_name, 'ext-depends', []), Config::getExt($ext_name, 'ext-suggests', [])) as $dep) {
self::visitExtDeps($dep, $visited, $sorted);
}
$sorted[] = $ext_name;
}
/**
* @throws RuntimeException
* @throws FileSystemException
* @throws WrongUsageException
*/
private static function visitLibDeps(string $lib_name, array &$visited, array &$sorted): void
{ {
// 如果已经识别到了,那就不管 // 如果已经识别到了,那就不管
if (isset($visited[$lib_name])) { if (isset($visited[$lib_name])) {
@@ -187,26 +195,9 @@ class DependencyUtil
} }
$visited[$lib_name] = true; $visited[$lib_name] = true;
// 遍历该依赖的所有依赖(此处的 getLib 如果检测到当前库不存在的话,会抛出异常) // 遍历该依赖的所有依赖(此处的 getLib 如果检测到当前库不存在的话,会抛出异常)
foreach (Config::getLib($lib_name, 'lib-depends', []) as $dep) { foreach ($dep_list[$lib_name]['depends'] as $dep) {
self::visitLibDeps($dep, $visited, $sorted); self::visitPlatDeps($dep, $dep_list, $visited, $sorted);
} }
$sorted[] = $lib_name; $sorted[] = $lib_name;
} }
/**
* @throws RuntimeException
* @throws FileSystemException
* @throws WrongUsageException
*/
private static function visitExtDeps(string $ext_name, array &$visited, array &$sorted): void
{
if (isset($visited[$ext_name])) {
return;
}
$visited[$ext_name] = true;
foreach (Config::getExt($ext_name, 'ext-depends', []) as $dep) {
self::visitExtDeps($dep, $visited, $sorted);
}
$sorted[] = $ext_name;
}
} }

View File

@@ -14,6 +14,7 @@ define('START_TIME', microtime(true));
define('BUILD_ROOT_PATH', FileSystem::convertPath(is_string($a = getenv('BUILD_ROOT_PATH')) ? $a : (WORKING_DIR . '/buildroot'))); define('BUILD_ROOT_PATH', FileSystem::convertPath(is_string($a = getenv('BUILD_ROOT_PATH')) ? $a : (WORKING_DIR . '/buildroot')));
define('SOURCE_PATH', FileSystem::convertPath(is_string($a = getenv('SOURCE_PATH')) ? $a : (WORKING_DIR . '/source'))); define('SOURCE_PATH', FileSystem::convertPath(is_string($a = getenv('SOURCE_PATH')) ? $a : (WORKING_DIR . '/source')));
define('DOWNLOAD_PATH', FileSystem::convertPath(is_string($a = getenv('DOWNLOAD_PATH')) ? $a : (WORKING_DIR . '/downloads'))); define('DOWNLOAD_PATH', FileSystem::convertPath(is_string($a = getenv('DOWNLOAD_PATH')) ? $a : (WORKING_DIR . '/downloads')));
define('PKG_ROOT_PATH', FileSystem::convertPath(is_string($a = getenv('PKG_ROOT_PATH')) ? $a : (WORKING_DIR . '/pkgroot')));
define('BUILD_BIN_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_BIN_PATH')) ? $a : (BUILD_ROOT_PATH . '/bin'))); define('BUILD_BIN_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_BIN_PATH')) ? $a : (BUILD_ROOT_PATH . '/bin')));
define('BUILD_LIB_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_LIB_PATH')) ? $a : (BUILD_ROOT_PATH . '/lib'))); define('BUILD_LIB_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_LIB_PATH')) ? $a : (BUILD_ROOT_PATH . '/lib')));
define('BUILD_INCLUDE_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_INCLUDE_PATH')) ? $a : (BUILD_ROOT_PATH . '/include'))); define('BUILD_INCLUDE_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_INCLUDE_PATH')) ? $a : (BUILD_ROOT_PATH . '/include')));

View File

@@ -0,0 +1,69 @@
CC=cl.exe /nologo
AR=lib.exe /nologo
LINK=link.exe /nologo
!IF "" == "$(MACHINE)"
MACHINE=x64
!ENDIF
!IF "" == "$(CRT)"
CRT=vc15
!ENDIF
!IF "" == "$(PREFIX)"
PREFIX="$(CRT)-$(MACHINE)"
!ENDIF
COMMON_CFLAGS=/D SQLITE_THREADSAFE=1 /DSQLITE_ENABLE_FTS3=1 /D SQLITE_ENABLE_FTS4=1 /D SQLITE_ENABLE_FTS5=1 /D SQLITE_ENABLE_JSON1=1 /D SQLITE_ENABLE_COLUMN_METADATA=1 /D SQLITE_CORE=1
!IF "$(DEBUG)"=="1"
SQLITE3_STATIC_BASE=libsqlite3_a_debug
SQLITE3_DLL_BASE=libsqlite3_debug
SQLITE3_EXE_BASE=sqlite3
CFLAGS=$(COMMON_CFLAGS) /Zi /MDd /Od /W3
LDFLAGS=/DEBUG /GUARD:CF /INCREMENTAL:NO
!ELSE
SQLITE3_STATIC_BASE=libsqlite3_a
SQLITE3_DLL_BASE=libsqlite3
SQLITE3_EXE_BASE=sqlite3
CFLAGS=$(COMMON_CFLAGS) /Zi /MT /guard:cf /Zc:inline /Qspectre /Ox /W3 /GF /GL /Gw
LDFLAGS=/GUARD:CF /INCREMENTAL:NO /NXCOMPAT /DYNAMICBASE
!ENDIF
all: $(SQLITE3_STATIC_BASE).lib $(SQLITE3_EXE_BASE).exe $(SQLITE3_DLL_BASE).dll
install: all
if not exist $(PREFIX)\bin mkdir $(PREFIX)\bin
if not exist $(PREFIX)\include mkdir $(PREFIX)\include
if not exist $(PREFIX)\lib mkdir $(PREFIX)\lib
copy /Y sqlite3.h $(PREFIX)\include
copy /Y sqlite3ext.h $(PREFIX)\include
copy /Y $(SQLITE3_STATIC_BASE).lib $(PREFIX)\lib
copy /Y $(SQLITE3_STATIC_BASE).pdb $(PREFIX)\lib
copy /Y $(SQLITE3_DLL_BASE).lib $(PREFIX)\lib
copy /Y $(SQLITE3_DLL_BASE).pdb $(PREFIX)\bin
copy /Y $(SQLITE3_DLL_BASE).dll $(PREFIX)\bin
copy /Y $(SQLITE3_EXE_BASE).exe $(PREFIX)\bin
copy /Y $(SQLITE3_EXE_BASE).pdb $(PREFIX)\bin
install-static: $(SQLITE3_STATIC_BASE).lib
if not exist $(PREFIX)\include mkdir $(PREFIX)\include
if not exist $(PREFIX)\lib mkdir $(PREFIX)\lib
copy /Y sqlite3.h $(PREFIX)\include
copy /Y sqlite3ext.h $(PREFIX)\include
copy /Y $(SQLITE3_STATIC_BASE).lib $(PREFIX)\lib
clean:
del *.obj *.lib *.exe *.pdb *.dll *.exp
$(SQLITE3_STATIC_BASE).lib: sqlite3.c sqlite3.h
$(CC) $(CFLAGS) /Fd$(SQLITE3_STATIC_BASE).pdb /c sqlite3.c
$(AR) sqlite3.obj /OUT:$(SQLITE3_STATIC_BASE).lib
$(SQLITE3_EXE_BASE).exe: shell.c sqlite3.c sqlite3.h
$(CC) $(CFLAGS) shell.c sqlite3.c /Fd$(SQLITE3_EXE_BASE).pdb /Fe$(SQLITE3_EXE_BASE).exe
$(SQLITE3_DLL_BASE).dll: sqlite3.c sqlite3.h
$(CC) $(CFLAGS) /DSQLITE_API=__declspec(dllexport) /Fd$(SQLITE3_DLL_BASE).pdb /c sqlite3.c
$(LINK) /DLL /OUT:$(SQLITE3_DLL_BASE).dll sqlite3.obj

View File

@@ -47,6 +47,13 @@ function arch2gnu(string $arch): string
}; };
} }
function match_pattern(string $pattern, string $subject): bool
{
$pattern = str_replace(['\*', '\\\\.*'], ['.*', '\*'], preg_quote($pattern, '/'));
$pattern = '/^' . $pattern . '$/i';
return preg_match($pattern, $subject) === 1;
}
function quote(string $str, string $quote = '"'): string function quote(string $str, string $quote = '"'): string
{ {
return $quote . $str . $quote; return $quote . $str . $quote;

View File

@@ -13,8 +13,8 @@ declare(strict_types=1);
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`). // If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
$extensions = match (PHP_OS_FAMILY) { $extensions = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'calendar,ctype,curl,dom,fileinfo,filter,gd,iconv,imagick,mbregex,mbstring,mysqli,mysqlnd,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,rar,redis,session,simplexml,soap,sockets,sqlite3,swoole,swoole-hook-mysql,tokenizer,xlswriter,xml,xmlreader,xmlwriter,zip,zlib', 'Linux', 'Darwin' => 'zlib,openssl,curl,pcntl,posix,mbstring,tokenizer,phar',
'Windows' => 'mbstring,openssl', 'Windows' => 'mbstring,pdo_sqlite,mbregex,ffi',
}; };
// If you want to test lib-suggests feature with extension, add them below (comma separated, example `libwebp,libavif`). // If you want to test lib-suggests feature with extension, add them below (comma separated, example `libwebp,libavif`).
@@ -62,6 +62,7 @@ $final_libs = trim($with_libs, $trim_value);
if (PHP_OS_FAMILY === 'Windows') { if (PHP_OS_FAMILY === 'Windows') {
$final_extensions_cmd = '"' . $final_extensions . '"'; $final_extensions_cmd = '"' . $final_extensions . '"';
$final_libs = $final_libs === '' ? '' : ('"' . $final_libs . '"');
} else { } else {
$final_extensions_cmd = $final_extensions; $final_extensions_cmd = $final_extensions;
} }
@@ -69,7 +70,7 @@ if (PHP_OS_FAMILY === 'Windows') {
echo match ($argv[1]) { echo match ($argv[1]) {
'extensions' => $final_extensions, 'extensions' => $final_extensions,
'libs' => $final_libs, 'libs' => $final_libs,
'libs_cmd' => ($final_libs === '' ? '' : (' --with-libs="' . $final_libs . '"')), 'libs_cmd' => ($final_libs === '' ? '' : (' --with-libs=' . $final_libs)),
'cmd' => $final_extensions_cmd . ($final_libs === '' ? '' : (' --with-libs="' . $final_libs . '"')), 'cmd' => $final_extensions_cmd . ($final_libs === '' ? '' : (' --with-libs=' . $final_libs)),
default => '', default => '',
}; };

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
assert(function_exists('gettext'));
assert(function_exists('bindtextdomain'));
assert(function_exists('textdomain'));
assert(function_exists('bind_textdomain_codeset'));
if (!is_dir('locale/en_US/LC_MESSAGES/')) {
mkdir('locale/en_US/LC_MESSAGES/', 0755, true);
}
if (!file_exists('locale/en_US/LC_MESSAGES/test.mo')) {
$mo = '3hIElQAAAAACAAAAHAAAACwAAAAFAAAAPAAAAAAAAABQAAAABgAAAFEAAAAXAQAAWAAAAAcAAABwAQAAAQAAAAAAAAAAAAAAAgAAAAAAAAAA56S65L6LAFByb2plY3QtSWQtVmVyc2lvbjogUEFDS0FHRSBWRVJTSU9OClJlcG9ydC1Nc2dpZC1CdWdzLVRvOiAKUE8tUmV2aXNpb24tRGF0ZTogWUVBUi1NTy1EQSBITzpNSStaT05FCkxhc3QtVHJhbnNsYXRvcjogRlVMTCBOQU1FIDxFTUFJTEBBRERSRVNTPgpMYW5ndWFnZS1UZWFtOiBMQU5HVUFHRSA8TExAbGkub3JnPgpMYW5ndWFnZTogCk1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD1VVEYtOApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA4Yml0CgBFeGFtcGxlAA==';
file_put_contents('locale/en_US/LC_MESSAGES/test.mo', base64_decode($mo));
}
putenv('LANG=en_US');
setlocale(LC_ALL, 'en_US');
$domain = 'test';
bindtextdomain($domain, 'locale/');
bind_textdomain_codeset($domain, 'UTF-8');
textdomain($domain);
assert(gettext(json_decode('"\u793a\u4f8b"', true)) === 'Example');

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace SPC\Tests\util;
use PHPUnit\Framework\TestCase;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\util\DependencyUtil;
/**
* @internal
*/
final class DependencyUtilTest extends TestCase
{
public function testGetExtLibsByDeps(): void
{
// example
Config::$source = [
'test1' => [
'type' => 'url',
'url' => 'https://pecl.php.net/get/APCu',
'filename' => 'apcu.tgz',
'license' => [
'type' => 'file',
'path' => 'LICENSE',
],
],
];
Config::$lib = [
'libaaa' => [
'source' => 'test1',
'static-libs' => ['libaaa.a'],
'lib-depends' => ['libbbb', 'libccc'],
'lib-suggests' => ['libeee'],
],
'libbbb' => [
'source' => 'test1',
'static-libs' => ['libbbb.a'],
'lib-suggests' => ['libccc'],
],
'libccc' => [
'source' => 'test1',
'static-libs' => ['libccc.a'],
],
'libeee' => [
'source' => 'test1',
'static-libs' => ['libeee.a'],
'lib-suggests' => ['libfff'],
],
'libfff' => [
'source' => 'test1',
'static-libs' => ['libfff.a'],
],
];
Config::$ext = [
'ext-a' => [
'type' => 'builtin',
'lib-depends' => ['libaaa'],
'ext-suggests' => ['ext-b'],
],
'ext-b' => [
'type' => 'builtin',
'lib-depends' => ['libeee'],
],
];
// test getExtLibsByDeps (notmal test with ext-depends and lib-depends)
[$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
$this->assertIsInt($b = array_search('libbbb', $libs));
$this->assertIsInt($c = array_search('libccc', $libs));
$this->assertIsInt($a = array_search('libaaa', $libs));
// libbbb, libaaa
$this->assertTrue($b < $a);
$this->assertTrue($c < $a);
$this->assertTrue($c < $b);
}
public function testNotExistExtException(): void
{
$this->expectException(WrongUsageException::class);
DependencyUtil::getExtsAndLibs(['sdsd']);
}
}