Compare commits

...

34 Commits

Author SHA1 Message Date
crazywhalecc
73035067e3 fix swoole-hook 2024-02-04 18:00:35 +08:00
crazywhalecc
421b3830b6 fix mbregex test for micro 2024-02-04 18:00:35 +08:00
crazywhalecc
59dcb905fe add test 2024-02-04 18:00:35 +08:00
crazywhalecc
d222190fc7 fix curl bug #327 2024-02-04 18:00:35 +08:00
crazywhalecc
06e9864d19 fix swoole hook check 2024-02-04 18:00:35 +08:00
crazywhalecc
0b1a321615 change with-micro-ext-test to without 2024-02-04 18:00:35 +08:00
crazywhalecc
9ee7d7769a add arm macos test 2024-02-04 11:22:53 +08:00
crazywhalecc
2440a65d8e fix windows test 2024-02-04 11:22:53 +08:00
crazywhalecc
839931d65f add static-php-cli.version ini for php-src 2024-02-04 11:22:53 +08:00
Jerry Ma
2591b48abe Update README-zh.md 2024-02-02 09:45:55 +08:00
Jerry Ma
2549597871 Update README.md 2024-02-02 09:44:37 +08:00
Jerry Ma
2064172bed Update build-macos-aarch64.yml 2024-01-31 11:58:56 +08:00
Jerry Ma
dc19d0c61d Create build-macos-aarch64.yml 2024-01-31 11:26:40 +08:00
crazywhalecc
05c6dc6dab add more tests 2024-01-29 09:55:20 +08:00
Jerry Ma
4b653bc293 Update README-zh.md 2024-01-11 10:08:32 +08:00
Jerry Ma
1c3476b8e9 Update README.md 2024-01-11 10:08:02 +08:00
Jerry Ma
61ca501c2a Update README.md 2024-01-11 09:51:48 +08:00
crazywhalecc
d916feec62 test bulk 2024-01-11 09:48:28 +08:00
crazywhalecc
0dcecf6ca4 fix windows mbstring 2024-01-11 09:48:28 +08:00
crazywhalecc
a04deb458f fix test build 2024-01-11 09:48:28 +08:00
crazywhalecc
34f810571e fix phpunit test 2024-01-11 09:48:28 +08:00
crazywhalecc
5ab4d140d5 fix phpstan 2024-01-11 09:48:28 +08:00
crazywhalecc
104778d17a add windows support 2024-01-11 09:48:28 +08:00
crazywhalecc
5f8641f417 update README 2024-01-10 11:13:47 +08:00
crazywhalecc
17b69ec1e9 update README 2024-01-10 11:11:45 +08:00
Jerry Ma
8de942c274 Adjust function to protected (#315)
* add `--with-added-patch` command

* add BuilderProvider::getBuilder() function

* cs fix

* add builder() and patch_point() global functions

* bump version to 2.0.1

* adjust function prefix

* adjust function prefix
2024-01-10 11:10:40 +08:00
crazywhalecc
a7d5a48b48 bump version to 2.0.1 2024-01-09 11:51:11 +08:00
crazywhalecc
fdc00301c0 add builder() and patch_point() global functions 2024-01-09 11:51:11 +08:00
crazywhalecc
7620d5900e cs fix 2024-01-09 11:51:11 +08:00
crazywhalecc
e973fe743e add BuilderProvider::getBuilder() function 2024-01-09 11:51:11 +08:00
crazywhalecc
149e844d59 add --with-added-patch command 2024-01-09 11:51:11 +08:00
crazywhalecc
12ea3218e8 fix --from-zip not working bug 2024-01-08 23:36:19 +08:00
crazywhalecc
f9e7af1c9a add deepin support for doctor 2024-01-08 23:36:08 +08:00
Jerry Ma
8d2f6baaa2 Update README.md 2024-01-07 12:54:48 +08:00
55 changed files with 2122 additions and 487 deletions

View File

@@ -0,0 +1,127 @@
name: CI on arm64 macOS
on:
workflow_dispatch:
inputs:
version:
required: true
description: php version to compile
default: '8.2'
type: choice
options:
- '8.3'
- '8.2'
- '8.1'
- '8.0'
- '7.4'
build-cli:
description: build cli binary
default: true
type: boolean
build-micro:
description: build phpmicro binary
type: boolean
build-fpm:
description: build fpm binary
type: boolean
extensions:
description: extensions to compile (comma separated)
required: true
type: string
debug:
type: boolean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
build:
name: build ${{ inputs.version }} on macOS arm64
runs-on: macos-14
steps:
- uses: actions/checkout@v3
# Install macOS missing packages and mark os suffix
- run: |
brew install automake gzip
echo "SPC_BUILD_OS=macos" >> $GITHUB_ENV
- name: "Setup PHP"
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
tools: pecl, composer
extensions: curl, openssl, mbstring, tokenizer
ini-values: memory_limit=-1
# Cache composer dependencies
- id: cache-composer-deps
uses: actions/cache@v3
with:
path: vendor
key: composer-dependencies
# If there's no Composer cache, install dependencies
- if: steps.cache-composer-deps.outputs.cache-hit != 'true'
run: composer update --no-dev --classmap-authoritative
# Cache downloaded source
- id: cache-download
uses: actions/cache@v3
with:
path: downloads
key: php-${{ inputs.version }}-dependencies-${{ inputs.extensions }}
# With or without debug
- if: inputs.debug == true
run: echo "SPC_BUILD_DEBUG=--debug" >> $GITHUB_ENV
# With target select: cli, micro or both
- if: ${{ inputs.build-cli == true }}
run: echo "SPC_BUILD_CLI=--build-cli" >> $GITHUB_ENV
- if: ${{ inputs.build-micro == true }}
run: echo "SPC_BUILD_MICRO=--build-micro" >> $GITHUB_ENV
- if: ${{ inputs.build-fpm == true }}
run: echo "SPC_BUILD_FPM=--build-fpm" >> $GITHUB_ENV
# If there's no dependencies cache, fetch sources, with or without debug
- if: steps.cache-download.outputs.cache-hit != 'true'
run: ./bin/spc download --with-php=${{ inputs.version }} --for-extensions=${{ inputs.extensions }} ${{ env.SPC_BUILD_DEBUG }}
# Run build command
- run: ./bin/spc build ${{ inputs.extensions }} ${{ env.SPC_BUILD_DEBUG }} ${{ env.SPC_BUILD_CLI }} ${{ env.SPC_BUILD_MICRO }} ${{ env.SPC_BUILD_FPM }}
# Upload cli executable
- if: ${{ inputs.build-cli == true }}
uses: actions/upload-artifact@v3
with:
name: php-${{ inputs.version }}-${{ env.SPC_BUILD_OS }}
path: buildroot/bin/php
# Upload micro self-extracted executable
- if: ${{ inputs.build-micro == true }}
uses: actions/upload-artifact@v3
with:
name: micro-${{ inputs.version }}-${{ env.SPC_BUILD_OS }}
path: buildroot/bin/micro.sfx
# Upload fpm executable
- if: ${{ inputs.build-fpm == true }}
uses: actions/upload-artifact@v3
with:
name: php-fpm-${{ inputs.version }}-${{ env.SPC_BUILD_OS }}
path: buildroot/bin/php-fpm
# Upload extensions metadata
- uses: actions/upload-artifact@v3
with:
name: license-files
path: buildroot/license/
- uses: actions/upload-artifact@v3
with:
name: build-meta
path: |
buildroot/build-extensions.json
buildroot/build-libraries.json

View File

@@ -114,6 +114,8 @@ jobs:
os: os:
- ubuntu-latest - ubuntu-latest
- macos-latest - macos-latest
- windows-latest
- macos-14
fail-fast: false fail-fast: false
steps: steps:
- name: "Checkout" - name: "Checkout"
@@ -153,4 +155,4 @@ jobs:
run: bin/spc download --for-extensions="$(php src/globals/test-extensions.php extensions)" --with-php=${{ matrix.php }} --debug run: bin/spc download --for-extensions="$(php src/globals/test-extensions.php extensions)" --with-php=${{ matrix.php }} --debug
- name: "Run Build Tests (build)" - name: "Run Build Tests (build)"
run: bin/spc build $(php src/globals/test-extensions.php 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

View File

@@ -1,32 +1,46 @@
# static-php-cli # static-php-cli
Build single static PHP binary, with PHP project together, with popular extensions included. [![Version](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)]()
🌐 **[中文](README-zh.md)** | **[English](README.md)**
编译纯静态的 PHP Binary 二进制文件,带有各种扩展,让 PHP-cli 应用变得更便携cli SAPI
<img width="600" alt="截屏2023-05-02 15 53 13" src="https://user-images.githubusercontent.com/20330940/235610282-23e58d68-bd35-4092-8465-171cff2d5ba8.png">
同时可以使用 micro 二进制文件,将 PHP 源码和 PHP 二进制构建为一个文件分发micro SAPI
<img width="600" alt="截屏2023-05-02 15 52 33" src="https://user-images.githubusercontent.com/20330940/235610318-2ef4e3f1-278b-4ca4-99f4-b38120efc395.png">
> 该 SAPI 源自 [dixyes/phpmicro](https://github.com/dixyes/phpmicro) 的 [Fork 仓库](https://github.com/static-php/phpmicro)。
[![Version](https://img.shields.io/badge/Version-2.0.0-green.svg?style=flat-square)]()
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)]()
[![](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://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://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)]() [![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)]()
[![](https://img.shields.io/github/search/crazywhalecc/static-php-cli/TODO?label=TODO%20Counter&style=flat-square)]() [![](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))
[![](https://dcbadge.vercel.app/api/server/RNpegEYW?style=flat-square&compact=true&theme=default-inverted)](https://discord.gg/RNpegEYW)
> 项目名称是 static-php-cli,但其实支持 cli、fpm、micro 和 embed SAPI 😎 **static-php-cli**是一个用于静态编译、构建 PHP 解释器的工具,支持众多流行扩展。
目前 static-php-cli 支持 `cli``fpm``embed``micro` SAPI。
**static-php-cli**也支持将 PHP 代码和 PHP 运行时打包为一个文件并运行。
- [README - English](./README.md)
- [README - 中文](./README-zh.md)
## 特性
static-php-cli简称 `spc`)有许多特性:
- :handbag: 构建独立的单文件 PHP 解释器,无需任何依赖
- :hamburger: 构建 **[phpmicro](https://github.com/dixyes/phpmicro)** 自执行二进制(将 PHP 代码和 PHP 解释器打包为一个文件)
- :pill: 提供一键检查和修复编译环境的 Doctor 模块
- :zap: 支持多个系统:`Linux``macOS``FreeBSD`、[`Windows (WIP)`](https://github.com/crazywhalecc/static-php-cli/pull/301)
- :wrench: 高度自定义的代码 patch 功能
- :books: 自带编译依赖管理
- 📦 提供由自身编译的独立 `spc` 二进制(使用 spc 和 [box](https://github.com/box-project/box) 构建)
- :fire: 支持大量 [扩展](https://static-php.dev/zh/guide/extensions.html)
**静态 php-cli:**
<img width="700" alt="out1" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/01a2e60f-13b0-4242-a645-f7afa4936396">
**使用 phpmicro 打包 PHP 代码:**
<img width="700" alt="out2" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/46b7128d-fb72-4169-957e-48564c3ff3e2">
## 文档 ## 文档
目前 README 编写了基本用法。有关 static-php-cli 所有的功能,请点击这里查看文档:<https://static-php.dev>。 目前 README 编写了基本用法。有关 static-php-cli 所有的功能,请点击这里查看文档:<https://static-php.dev>。
## 自托管直接下载 ## 直接下载
如果你不想自行编译 PHP可以从本项目现有的示例 Action 下载 Artifact也可以从自托管的服务器下载。 如果你不想自行编译 PHP可以从本项目现有的示例 Action 下载 Artifact也可以从自托管的服务器下载。
@@ -38,6 +52,10 @@ Build single static PHP binary, with PHP project together, with popular extensio
### 编译环境需求 ### 编译环境需求
- PHP >= 8.1(这是 spc 自身需要的版本,不是支持的构建版本)
- 扩展:`mbstring,pcntl,posix,tokenizer,phar`
- 系统安装了 `curl``git`
是的,本项目采用 PHP 编写,编译前需要一个 PHP 环境,比较滑稽。 是的,本项目采用 PHP 编写,编译前需要一个 PHP 环境,比较滑稽。
但本项目默认可通过自身构建的 micro 和 static-php 二进制运行,其他只需要包含 mbstring、pcntl 扩展和 PHP 版本大于等于 8.1 即可。 但本项目默认可通过自身构建的 micro 和 static-php 二进制运行,其他只需要包含 mbstring、pcntl 扩展和 PHP 版本大于等于 8.1 即可。
@@ -45,16 +63,14 @@ Build single static PHP binary, with PHP project together, with popular extensio
| | x86_64 | aarch64 | | | x86_64 | aarch64 |
|---------|----------------------|----------------------| |---------|----------------------|----------------------|
| macOS | :octocat: :computer: | :computer: | | macOS | :octocat: :computer: | :octocat: :computer: |
| Linux | :octocat: :computer: | :octocat: :computer: | | Linux | :octocat: :computer: | :octocat: :computer: |
| Windows | | | | Windows | | |
| FreeBSD | :computer: | :computer: | | FreeBSD | :computer: | :computer: |
> macOS-arm64 因 GitHub 暂未提供 arm runner如果要构建 arm 二进制,可以使用手动构建。
目前支持编译的 PHP 版本为:`7.3``7.4``8.0``8.1``8.2``8.3` 目前支持编译的 PHP 版本为:`7.3``7.4``8.0``8.1``8.2``8.3`
### 支持的扩展情况 ### 支持的扩展
请先根据下方扩展列表选择你要编译的扩展。 请先根据下方扩展列表选择你要编译的扩展。
@@ -63,7 +79,7 @@ Build single static PHP binary, with PHP project together, with popular extensio
> 如果这里没有你需要的扩展,可以提交 Issue。 > 如果这里没有你需要的扩展,可以提交 Issue。
### 使用 Actions 构建 ### 在线构建(使用 GitHub Actions
使用 GitHub Action 可以方便地构建一个静态编译的 PHP同时可以自行定义要编译的扩展。 使用 GitHub Action 可以方便地构建一个静态编译的 PHP同时可以自行定义要编译的扩展。
@@ -74,72 +90,77 @@ Build single static PHP binary, with PHP project together, with popular extensio
如果你选择了 `debug`,则会在构建时输出所有日志,包括编译的日志,以供排查错误。 如果你选择了 `debug`,则会在构建时输出所有日志,包括编译的日志,以供排查错误。
### 手动构建(使用 SPC 二进制) ### 本地构建(使用 spc 二进制)
项目提供了一个 static-php-cli 的二进制文件,你可以直接下载对应平台的二进制文件,然后使用它来构建静态的 PHP。目前 `spc` 二进制支持的平台有 Linux 和 macOS 项目提供了 static-php-cli 的二进制文件`spc`
您可以使用 `spc` 二进制文件,无需安装任何运行时(用起来就像 golang 程序)。
目前,`spc` 二进制文件提供的平台有 Linux 和 macOS。
下面是从 GitHub Action 下载的方法 使用以下命令从自托管服务器下载
1. 进入 [GitHub Action](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml)。
2. 选择一个最新的构建任务,进入后选择 `Artifacts`,下载对应平台的二进制文件。
3. 解压 `.zip` 文件。解压后,为其添加执行权限:`chmod +x ./spc`
你也可以从自托管的服务器下载二进制文件:[进入](https://dl.static-php.dev/static-php-cli/spc-bin/nightly/)。
### 手动构建(使用源码)
先克隆本项目:
```bash ```bash
# Download from self-hosted nightly builds (sync with main branch)
# For Linux x86_64
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
# For Linux aarch64
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
# macOS x86_64 (Intel)
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple)
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
# add x perm
chmod +x ./spc
./spc --version
```
自托管 `spc` 由 GitHub Actions 构建,你也可以从 Actions 直接下载:[此处](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml)。
### 本地构建(使用 git 源码)
```bash
# clone 仓库即可
git clone https://github.com/crazywhalecc/static-php-cli.git git clone https://github.com/crazywhalecc/static-php-cli.git
``` ```
如果你本机没有安装 PHP你需要先使用包管理例如 brew、apt、yum、apk 等)安装 php 如果您的系统上尚未安装 php我们建议你使用内置的 setup-runtime 自动安装 PHP 和 Composer
你也可以通过 `bin/setup-runtime` 命令下载静态编译好的 php-cli 和 Composer。下载的 php 和 Composer 将保存为 `bin/php``bin/composer`
```bash ```bash
cd static-php-cli cd static-php-cli
chmod +x bin/setup-runtime chmod +x bin/setup-runtime
./bin/setup-runtime # it will download static php (from self-hosted server) and composer (from getcomposer)
bin/setup-runtime
# 使用独立的 php 运行 static-php-cli # initialize composer deps
./bin/php bin/spc bin/composer install
# chmod
# 使用 composer
./bin/php bin/composer
# 初始化本项目
cd static-php-cli
composer update
chmod +x bin/spc chmod +x bin/spc
bin/spc --version
``` ```
### 使用 static-php-cli 命令行程序 ### 开始构建 PHP
下面是使用 static-php-cli 编译静态 php 和 micro 的基础用法: 下面是使用 static-php-cli 的基础用法:
> 如果你使用的是打包好的 `spc` 二进制,你需要将下列命令的 `bin/spc` 替换为 `./spc`。 > 如果你使用的是打包好的 `spc` 二进制,你需要将下列命令的 `./bin/spc` 替换为 `./spc`。
```bash ```bash
# 检查环境依赖,并根据提示的命令安装缺失的编译工具 # 检查环境依赖,并根据尝试自动安装缺失的编译工具
./bin/spc doctor ./bin/spc doctor --auto-fix
# 拉取所有依赖库 # 拉取所有依赖库
./bin/spc fetch --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)
./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)
./bin/spc build curl,phar --enable-zts --build-cli
``` ```
你也可以使用参数 `--with-php=x.y` 来指定下载的 PHP 版本,目前支持 7.3 ~ 8.3 其中,目前支持构建 climicrofpm 和 embed使用以下参数的一个或多个来指定编译的 SAPI
```bash
# 优先考虑使用 >= 8.0 的 PHP 版本,因为 phpmicro 不支持在 PHP7 中构建
./bin/spc download --with-php=8.2 --all
```
其中,目前支持构建 climicrofpm 三种静态二进制,使用以下参数的一个或多个来指定编译的 SAPI
- `--build-cli`:构建 cli 二进制 - `--build-cli`:构建 cli 二进制
- `--build-micro`:构建 phpmicro 自执行二进制 - `--build-micro`:构建 phpmicro 自执行二进制
@@ -151,17 +172,9 @@ chmod +x bin/spc
```bash ```bash
./bin/spc build openssl,pcntl,mbstring --debug --build-all ./bin/spc build openssl,pcntl,mbstring --debug --build-all
./bin/spc fetch --all --debug ./bin/spc download --all --debug
``` ```
此外,默认编译的 PHP 为 NTS 版本。如需编译线程安全版本ZTS只需添加参数 `--enable-zts` 即可。
```bash
./bin/spc build openssl,pcntl --build-all --enable-zts
```
同时,你也可以使用参数 `--no-strip` 来关闭裁剪,关闭裁剪后可以使用 gdb 等工具调试,但这样会让静态二进制体积变大。
## 不同 SAPI 的使用 ## 不同 SAPI 的使用
### 使用 cli ### 使用 cli
@@ -241,14 +254,13 @@ bin/spc micro:combine my-app.phar -I "memory_limit=4G" -I "disable_functions=sys
## 开源协议 ## 开源协议
本项目依据旧版本惯例采用 MIT License 开源,部分扩展的集成编译命令参考或修改自以下项目: 本项目采用 MIT License 许可开源,下面是类似的项目:
- [dixyes/lwmbs](https://github.com/dixyes/lwmbs)(木兰宽松许可证) - [dixyes/lwmbs](https://github.com/dixyes/lwmbs)
- [swoole/swoole-cli](https://github.com/swoole/swoole-cli)Apache 2.0 LICENSE、SWOOLE-CLI LICENSE - [swoole/swoole-cli](https://github.com/swoole/swoole-cli)
该项目使用了 [dixyes/lwmbs](https://github.com/dixyes/lwmbs) 中的一些代码,例如 Windows 静态构建目标和 libiconv 库支持。
lwmbs 使用 [Mulan PSL 2](http://license.coscl.org.cn/MulanPSL2) 许可进行分发。对应文件有关于作者和许可的特殊说明,除此之外,均使用 MIT 授权许可。
因本项目的特殊性,使用项目编译过程中会使用很多其他开源项目,例如 curl、protobuf 等,它们都有各自的开源协议。 因本项目的特殊性,使用项目编译过程中会使用很多其他开源项目,例如 curl、protobuf 等,它们都有各自的开源协议。
请在编译完成后,使用命令 `bin/spc dump-license` 导出项目使用项目的开源协议,并遵守对应项目的 LICENSE。 请在编译完成后,使用命令 `bin/spc dump-license` 导出项目使用项目的开源协议,并遵守对应项目的 LICENSE。
## 进阶
本项目重构分支为模块化编写。如果你对本项目感兴趣,想加入开发,可以参照文档的 [贡献指南](https://static-php.dev) 贡献代码或文档。

181
README.md
View File

@@ -1,28 +1,42 @@
# static-php-cli # static-php-cli
Build single static PHP binary, with PHP project together, with popular extensions included. [![Version](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)]()
🌐 **[中文](README-zh.md)** | **[English](README.md)**
> 2.0 Release is coming soon, windows support will be added in v2.1.
The project name is static-php-cli, but it actually supports cli, fpm, micro and embed SAPI 😎
Compile a purely static php-cli binary file with various extensions to make PHP applications more portable! (cli SAPI)
<img width="600" alt="2023-05-02 15 53 13" src="https://user-images.githubusercontent.com/20330940/235610282-23e58d68-bd35-4092-8465-171cff2d5ba8.png">
You can also use the micro binary file to combine php binary and php source code into one for distribution! (micro SAPI)
<img width="600" alt="2023-05-02 15 52 33" src="https://user-images.githubusercontent.com/20330940/235610318-2ef4e3f1-278b-4ca4-99f4-b38120efc395.png">
> This SAPI feature is from the [Fork](https://github.com/static-php/phpmicro) of [dixyes/phpmicro](https://github.com/dixyes/phpmicro).
[![Version](https://img.shields.io/badge/Version-2.0.0-green.svg?style=flat-square)]()
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)]()
[![](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://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)]()
[![](https://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)]([https://static-php.dev/](https://static-php.dev/en/guide/extensions.html)) [![](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))
[![](https://img.shields.io/github/search/crazywhalecc/static-php-cli/TODO?label=TODO%20Counter&style=flat-square)]() [![](https://dcbadge.vercel.app/api/server/RNpegEYW?style=flat-square&compact=true&theme=default-inverted)](https://discord.gg/RNpegEYW)
**static-php-cli** is a powerful tool designed for building static, standalone PHP runtime
with popular extensions.
Static PHP built by **static-php-cli** supports `cli`, `fpm`, `embed` and `micro` SAPI.
**static-php-cli** also has the ability to package PHP projects
along with the PHP interpreter into one single executable file.
- [README - English](./README.md)
- [README - 中文](./README-zh.md)
## Features
static-php-cli (you can call it `spc`) has a lot of features:
- :handbag: Build single-file php executable, without any dependencies
- :hamburger: Build **[phpmicro](https://github.com/dixyes/phpmicro)** self-extracted executable (glue php binary and php source code into one file)
- :pill: Automatic build environment checker (Doctor module)
- :zap: `Linux`, `macOS`, `FreeBSD`, `Windows` support
- :wrench: Configurable source code patches
- :books: Build dependency management
- 📦 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)
**Single-file standalone php-cli:**
<img width="700" alt="out1" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/01a2e60f-13b0-4242-a645-f7afa4936396">
**Combine PHP code with PHP interpreter using phpmicro:**
<img width="700" alt="out2" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/46b7128d-fb72-4169-957e-48564c3ff3e2">
## Documentation ## Documentation
@@ -31,7 +45,7 @@ see <https://static-php.dev> .
## Direct Download ## Direct Download
If you don't want to compile yourself, you can download example pre-compiled artifact from [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-common.yml), or from self-hosted server. If you don't want to build or want to test first, you can download example pre-compiled artifact from [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-bulk.yml), or from self-hosted server.
Below are several precompiled static-php binaries with different extension combinations, Below are several precompiled static-php binaries with different extension combinations,
which can be downloaded directly according to your needs. which can be downloaded directly according to your needs.
@@ -40,25 +54,27 @@ 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.
## Use static-php-cli to build PHP ## Build
### Compilation Requirements ### Compilation Requirements
Yes, this project is written in PHP, pretty funny. - 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.
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 `mbstring`, `pcntl` extension.
Here is the architecture support status, 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.
| | x86_64 | aarch64 | | | x86_64 | aarch64 |
|---------|----------------------|----------------------| |---------|----------------------|----------------------|
| macOS | :octocat: :computer: | :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: |
> macOS-arm64 is not supported for GitHub Actions, if you are going to build on arm, you can build it manually on your own machine.
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 are: `7.3`, `7.4`, `8.0`, `8.1`, `8.2`, `8.3`.
### Supported Extensions ### Supported Extensions
@@ -72,7 +88,7 @@ Please first select the extension you want to compile based on the extension lis
Here is the current planned roadmap for extension support: [#152](https://github.com/crazywhalecc/static-php-cli/issues/152) . Here is the current planned roadmap for extension support: [#152](https://github.com/crazywhalecc/static-php-cli/issues/152) .
### GitHub Actions Build ### Build Online (using GitHub Actions)
Use GitHub Action to easily build a statically compiled PHP, Use GitHub Action to easily build a statically compiled PHP,
and at the same time define the extensions to be compiled by yourself. and at the same time define the extensions to be compiled by yourself.
@@ -84,80 +100,77 @@ and at the same time define the extensions to be compiled by yourself.
If you enable `debug`, all logs will be output at build time, including compiled logs, for troubleshooting. If you enable `debug`, all logs will be output at build time, including compiled logs, for troubleshooting.
- When using ubuntu-latest, it will build linux-x86_64 binary. ### Build Locally (using SPC binary)
- When using macos-latest, it will build macOS-x86_64 binary.
### Manual build (using SPC binary) This project provides a binary file of static-php-cli: `spc`.
You can use `spc` binary instead of installing any runtime like golang app.
This project provides a binary file of static-php-cli.
You can directly download the binary file of the corresponding platform and then use it to build static PHP.
Currently, the platforms supported by `spc` binary are Linux and macOS. Currently, the platforms supported by `spc` binary are Linux and macOS.
Here's how to download from GitHub Actions: Download from self-hosted nightly builds using commands below:
1. Enter [GitHub Actions](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml) or [self-hosted nightly builds](https://dl.static-php.dev/static-php-cli/spc-bin/nightly/).
2. If you download from GHA, select the latest build task, select `Artifacts`, and download the binary file of the corresponding platform.
3. If you download from GHA, unzip the `.zip` file. After decompressing, add execution permissions to it: `chmod +x ./spc`.
4. If you download from self-hosted server, download `spc-$os-$arch` file and just use it (don't forget `chmod +x`).
> SPC single-file binary is built by phpmicro and box, and it doesn't need to install PHP. Just treat `spc` as a standalone executable.
### Manual build (using source code)
Clone repo first:
```bash ```bash
# Download from self-hosted nightly builds (sync with main branch)
# For Linux x86_64
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
# For Linux aarch64
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
# macOS x86_64 (Intel)
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple)
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
# add x perm
chmod +x ./spc
./spc --version
```
Self-hosted `spc` is built by GitHub Actions, you can also download from Actions artifacts [here](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml).
### Build Locally (using git source)
```bash
# just clone me!
git clone https://github.com/crazywhalecc/static-php-cli.git git clone https://github.com/crazywhalecc/static-php-cli.git
``` ```
If you have not installed php on your system, you can use package management to install PHP (such as brew, apt, yum, apk etc.). If you have not installed php on your system, we recommend that you use the built-in setup-runtime to install PHP and Composer automatically.
And you can also download single-file php binary and composer using command `bin/setup-runtime`.
The PHP runtime for static-php-cli itself will be downloaded at `bin/php`, and composer is at `bin/composer`.
```bash ```bash
cd static-php-cli cd static-php-cli
chmod +x bin/setup-runtime chmod +x bin/setup-runtime
# It will download php-cli from self-hosted server and composer from getcomposer.org # it will download static php (from self-hosted server) and composer (from getcomposer)
./bin/setup-runtime bin/setup-runtime
# initialize composer deps
# Use this php runtime to run static-php-cli compiler bin/composer install
./bin/php bin/spc # chmod
# Use composer
./bin/php bin/composer
# Initialize this project
cd static-php-cli
composer update
chmod +x bin/spc chmod +x bin/spc
bin/spc --version
``` ```
### Use static-php-cli ### Start Building PHP
Basic usage for building php and micro 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 `spc` binary, you need to replace `bin/spc` with `./spc` in the following commands.
```bash ```bash
# Check system tool dependencies, fix them if possible # Check system tool dependencies, auto-fix them if possible
./bin/spc doctor ./bin/spc doctor --auto-fix
# fetch all libraries # fetch all libraries
./bin/spc download --all ./bin/spc download --all
# only fetch necessary sources by needed extensions # 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)
./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)
./bin/spc build curl,phar --enable-zts --build-cli
``` ```
You can also use the parameter `--with-php=x.y` to specify the downloaded PHP version, currently supports 7.4 ~ 8.3: Now we support `cli`, `micro`, `fpm` and `embed` SAPI. You can use one or more of the following parameters to specify the compiled SAPI:
```bash
# Using PHP >= 8.0 is recommended, because PHP7 cannot use phpmicro
./bin/spc fetch --with-php=8.2 --all
```
Now we support `cli`, `micro`, `fpm`, you can use one or more of the following parameters to specify the compiled SAPI:
- `--build-cli`: build static cli executable - `--build-cli`: build static cli executable
- `--build-micro`: build static phpmicro self-extracted executable - `--build-micro`: build static phpmicro self-extracted executable
@@ -169,17 +182,9 @@ 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 fetch --all --debug ./bin/spc download --all --debug
``` ```
In addition, we build NTS (non-thread-safe) by default. If you are going to build ZTS version, just add `--enable-zts` option.
```bash
./bin/spc build openssl,pcntl --build-all --enable-zts
```
Adding option `--no-strip` can produce binaries with debug symbols, in order to debug (using gdb). Disabling strip will increase the size of static binary.
## Different SAPI Usage ## Different SAPI Usage
### Use cli ### Use cli
@@ -282,10 +287,12 @@ These are similar projects:
- [dixyes/lwmbs](https://github.com/dixyes/lwmbs) - [dixyes/lwmbs](https://github.com/dixyes/lwmbs)
- [swoole/swoole-cli](https://github.com/swoole/swoole-cli) - [swoole/swoole-cli](https://github.com/swoole/swoole-cli)
The project uses some code from [dixyes/lwmbs](https://github.com/dixyes/lwmbs), such as windows static build target and libiconv support.
lwmbs is licensed under the [Mulan PSL 2](http://license.coscl.org.cn/MulanPSL2).
Due to the special nature of this project, Due to the special nature of this project,
many other open source projects such as curl and protobuf will be used during the project compilation process, many other open source projects such as curl and protobuf will be used during the project compilation process,
and they all have their own open source licenses. and they all have their own open source licenses.
Please use the `bin/spc dump-license` command to export the open source licenses used in the project after compilation, Please use the `bin/spc dump-license` command to export the open source licenses used in the project after compilation,
and comply with the corresponding project's LICENSE. and comply with the corresponding project's LICENSE.

View File

@@ -28,7 +28,7 @@ function RemoveFromPath {
$currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User') $currentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
if ($currentPath -like "*$pathToRemove*") { if ($currentPath -like "*$pathToRemove*") {
$newPath = $currentPath -replace [regex]::Escape($pathToRemove), '' $newPath = $currentPath -replace [regex]::Escape(';' + $pathToRemove), ''
[System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User') [System.Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host "Removed Path '$pathToRemove'" Write-Host "Removed Path '$pathToRemove'"
} else { } else {
@@ -92,7 +92,7 @@ if (-not(Test-Path "runtime\composer.phar"))
# create runtime\composer.ps1 # create runtime\composer.ps1
$ComposerContent = ' $ComposerContent = '
$WorkingDir = (Split-Path -Parent $MyInvocation.MyCommand.Definition) $WorkingDir = (Split-Path -Parent $MyInvocation.MyCommand.Definition)
Start-Process ($WorkingDir + "\php.exe") ($WorkingDir + "\composer.phar " + $args) -NoNewWindow -Wait & ($WorkingDir + "\php.exe") (Join-Path $WorkingDir "\composer.phar") @args
' '
$ComposerContent | Set-Content -Path 'runtime\composer.ps1' -Encoding UTF8 $ComposerContent | Set-Content -Path 'runtime\composer.ps1' -Encoding UTF8

View File

@@ -21,4 +21,5 @@ try {
(new ConsoleApplication())->run(); (new ConsoleApplication())->run();
} catch (Exception $e) { } catch (Exception $e) {
ExceptionHandler::getInstance()->handle($e); ExceptionHandler::getInstance()->handle($e);
exit(1);
} }

View File

@@ -1,4 +1,4 @@
$PHP_Exec = "runtime\php.exe" $PHP_Exec = ".\runtime\php.exe"
if (-not(Test-Path $PHP_Exec)) { if (-not(Test-Path $PHP_Exec)) {
$PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition $PHP_Exec = Get-Command php.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Definition
@@ -8,5 +8,4 @@ if (-not(Test-Path $PHP_Exec)) {
} }
} }
$phpArgs = "bin\spc " + $args & "$PHP_Exec" ("bin/spc") @args
Start-Process $PHP_Exec -ArgumentList $phpArgs -NoNewWindow -Wait

View File

@@ -106,7 +106,8 @@
"source": "ext-glfw", "source": "ext-glfw",
"lib-depends": [ "lib-depends": [
"glfw" "glfw"
] ],
"lib-depends-windows": []
}, },
"gmp": { "gmp": {
"type": "builtin", "type": "builtin",
@@ -238,6 +239,7 @@
"openssl": { "openssl": {
"type": "builtin", "type": "builtin",
"arg-type": "custom", "arg-type": "custom",
"arg-type-windows": "with",
"lib-depends": [ "lib-depends": [
"openssl", "openssl",
"zlib" "zlib"

View File

@@ -57,9 +57,7 @@
"brotli", "brotli",
"nghttp2", "nghttp2",
"zstd", "zstd",
"openssl", "openssl"
"idn2",
"psl"
], ],
"frameworks": [ "frameworks": [
"CoreFoundation", "CoreFoundation",

View File

@@ -15,3 +15,4 @@ parameters:
analyseAndScan: analyseAndScan:
- ./src/globals/tests/swoole.php - ./src/globals/tests/swoole.php
- ./src/globals/tests/swoole.phpt - ./src/globals/tests/swoole.phpt
- ./src/globals/test-extensions.php

View File

@@ -23,7 +23,7 @@ use Symfony\Component\Console\Command\ListCommand;
*/ */
final class ConsoleApplication extends Application final class ConsoleApplication extends Application
{ {
public const VERSION = '2.0.0'; public const VERSION = '2.1.0-beta.2';
public function __construct() public function __construct()
{ {

View File

@@ -4,14 +4,13 @@ declare(strict_types=1);
namespace SPC\builder; namespace SPC\builder;
use SPC\exception\ExceptionHandler;
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\Config; use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\SourceExtractor; use SPC\store\SourceExtractor;
use SPC\util\CustomExt; use SPC\util\CustomExt;
use SPC\util\DependencyUtil;
abstract class BuilderBase abstract class BuilderBase
{ {
@@ -30,6 +29,9 @@ abstract class BuilderBase
/** @var array<string, mixed> compile options */ /** @var array<string, mixed> compile options */
protected array $options = []; protected array $options = [];
/** @var string patch point name */
protected string $patch_point = '';
/** /**
* Build libraries * Build libraries
* *
@@ -37,60 +39,9 @@ abstract class BuilderBase
* @throws FileSystemException * @throws FileSystemException
* @throws RuntimeException * @throws RuntimeException
* @throws WrongUsageException * @throws WrongUsageException
* @internal
*/ */
public function buildLibs(array $sorted_libraries): void abstract public function buildLibs(array $sorted_libraries);
{
// search all supported libs
$support_lib_list = [];
$classes = FileSystem::getClassesPsr4(
ROOT_DIR . '/src/SPC/builder/' . osfamily2dir() . '/library',
'SPC\\builder\\' . osfamily2dir() . '\\library'
);
foreach ($classes as $class) {
if (defined($class . '::NAME') && $class::NAME !== 'unknown' && Config::getLib($class::NAME) !== null) {
$support_lib_list[$class::NAME] = $class;
}
}
// if no libs specified, compile all supported libs
if ($sorted_libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list);
$sorted_libraries = DependencyUtil::getLibsByDeps($libraries);
}
// pkg-config must be compiled first, whether it is specified or not
if (!in_array('pkg-config', $sorted_libraries)) {
array_unshift($sorted_libraries, 'pkg-config');
}
// add lib object for builder
foreach ($sorted_libraries as $library) {
// if some libs are not supported (but in config "lib.json", throw exception)
if (!isset($support_lib_list[$library])) {
throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!');
}
$lib = new ($support_lib_list[$library])($this);
$this->addLib($lib);
}
// calculate and check dependencies
foreach ($this->libs as $lib) {
$lib->calcDependency();
}
// extract sources
SourceExtractor::initSource(libs: $sorted_libraries);
// build all libs
foreach ($this->libs as $lib) {
match ($lib->tryBuild($this->getOption('rebuild', false))) {
BUILD_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] build success'),
BUILD_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'),
BUILD_STATUS_FAILED => logger()->error('lib [' . $lib::NAME . '] build failed'),
default => logger()->warning('lib [' . $lib::NAME . '] build status unknown'),
};
}
}
/** /**
* Add library to build. * Add library to build.
@@ -172,6 +123,8 @@ abstract class BuilderBase
/** /**
* Set libs only mode. * Set libs only mode.
*
* @internal
*/ */
public function setLibsOnly(bool $status = true): void public function setLibsOnly(bool $status = true): void
{ {
@@ -185,15 +138,22 @@ abstract class BuilderBase
* @throws RuntimeException * @throws RuntimeException
* @throws \ReflectionException * @throws \ReflectionException
* @throws WrongUsageException * @throws WrongUsageException
* @internal
*/ */
public function proveExts(array $extensions): void public function proveExts(array $extensions): void
{ {
CustomExt::loadCustomExt(); CustomExt::loadCustomExt();
$this->emitPatchPoint('before-php-extract');
SourceExtractor::initSource(sources: ['php-src']); SourceExtractor::initSource(sources: ['php-src']);
$this->emitPatchPoint('after-php-extract');
if ($this->getPHPVersionID() >= 80000) { if ($this->getPHPVersionID() >= 80000) {
$this->emitPatchPoint('before-micro-extract');
SourceExtractor::initSource(sources: ['micro']); SourceExtractor::initSource(sources: ['micro']);
$this->emitPatchPoint('after-micro-extract');
} }
$this->emitPatchPoint('before-exts-extract');
SourceExtractor::initSource(exts: $extensions); SourceExtractor::initSource(exts: $extensions);
$this->emitPatchPoint('after-exts-extract');
foreach ($extensions as $extension) { foreach ($extensions as $extension) {
$class = CustomExt::getExtClass($extension); $class = CustomExt::getExtClass($extension);
$ext = new $class($extension, $this); $ext = new $class($extension, $this);
@@ -223,6 +183,7 @@ abstract class BuilderBase
{ {
$ret = []; $ret = [];
foreach ($this->exts as $ext) { foreach ($this->exts as $ext) {
logger()->info($ext->getName() . ' is using ' . $ext->getConfigureArg());
$ret[] = trim($ext->getConfigureArg()); $ret[] = trim($ext->getConfigureArg());
} }
logger()->debug('Using configure: ' . implode(' ', $ret)); logger()->debug('Using configure: ' . implode(' ', $ret));
@@ -342,6 +303,39 @@ abstract class BuilderBase
return implode(' ', $env); return implode(' ', $env);
} }
/**
* Get builder patch point name.
*/
public function getPatchPoint(): string
{
return $this->patch_point;
}
public function emitPatchPoint(string $point_name): void
{
$this->patch_point = $point_name;
if (($patches = $this->getOption('with-added-patch', [])) === []) {
return;
}
foreach ($patches as $patch) {
try {
if (!file_exists($patch)) {
throw new RuntimeException("Additional patch script file {$patch} not found!");
}
logger()->debug('Running additional patch script: ' . $patch);
require $patch;
} catch (\Throwable $e) {
logger()->critical('Patch script ' . $patch . ' failed to run.');
if ($this->getOption('debug')) {
ExceptionHandler::getInstance()->handle($e);
} else {
logger()->critical('Please check with --debug option to see more details.');
}
}
}
}
/** /**
* Check if all libs are downloaded. * Check if all libs are downloaded.
* If not, throw exception. * If not, throw exception.
@@ -365,4 +359,22 @@ abstract class BuilderBase
); );
} }
} }
/**
* Generate micro extension test php code.
*/
protected function generateMicroExtTests(): string
{
$php = "<?php\n\necho '[micro-test-start]' . PHP_EOL;\n";
foreach ($this->getExts() as $ext) {
$ext_name = $ext->getDistName();
if (!empty($ext_name)) {
$php .= "echo 'Running micro with {$ext_name} test' . PHP_EOL;\n";
$php .= "assert(extension_loaded('{$ext_name}'));\n\n";
}
}
$php .= "echo '[micro-test-end]';\n";
return $php;
}
} }

View File

@@ -7,6 +7,7 @@ namespace SPC\builder;
use SPC\builder\freebsd\BSDBuilder; use SPC\builder\freebsd\BSDBuilder;
use SPC\builder\linux\LinuxBuilder; use SPC\builder\linux\LinuxBuilder;
use SPC\builder\macos\MacOSBuilder; use SPC\builder\macos\MacOSBuilder;
use SPC\builder\windows\WindowsBuilder;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
@@ -17,6 +18,8 @@ use Symfony\Component\Console\Input\InputInterface;
*/ */
class BuilderProvider class BuilderProvider
{ {
private static ?BuilderBase $builder = null;
/** /**
* @throws FileSystemException * @throws FileSystemException
* @throws RuntimeException * @throws RuntimeException
@@ -24,16 +27,24 @@ class BuilderProvider
*/ */
public static function makeBuilderByInput(InputInterface $input): BuilderBase public static function makeBuilderByInput(InputInterface $input): BuilderBase
{ {
return match (PHP_OS_FAMILY) { self::$builder = match (PHP_OS_FAMILY) {
// 'Windows' => new WindowsBuilder( 'Windows' => new WindowsBuilder($input->getOptions()),
// binary_sdk_dir: $input->getOption('with-sdk-binary-dir'),
// vs_ver: $input->getOption('vs-ver'),
// arch: $input->getOption('arch'),
// ),
'Darwin' => new MacOSBuilder($input->getOptions()), 'Darwin' => new MacOSBuilder($input->getOptions()),
'Linux' => new LinuxBuilder($input->getOptions()), 'Linux' => new LinuxBuilder($input->getOptions()),
'BSD' => new BSDBuilder($input->getOptions()), 'BSD' => new BSDBuilder($input->getOptions()),
default => throw new WrongUsageException('Current OS "' . PHP_OS_FAMILY . '" is not supported yet'), default => throw new WrongUsageException('Current OS "' . PHP_OS_FAMILY . '" is not supported yet'),
}; };
return self::$builder;
}
/**
* @throws WrongUsageException
*/
public static function getBuilder(): BuilderBase
{
if (self::$builder === null) {
throw new WrongUsageException('Builder has not been initialized');
}
return self::$builder;
} }
} }

View File

@@ -8,6 +8,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\FileSystem;
class Extension class Extension
{ {
@@ -42,7 +43,7 @@ class Extension
$arg = $this->getEnableArg(); $arg = $this->getEnableArg();
switch (PHP_OS_FAMILY) { switch (PHP_OS_FAMILY) {
case 'Windows': case 'Windows':
$arg = $this->getWindowsConfigureArg(); $arg .= $this->getWindowsConfigureArg();
break; break;
case 'Darwin': case 'Darwin':
case 'Linux': case 'Linux':
@@ -164,14 +165,13 @@ class Extension
} }
/** /**
* Run compile check if build target is cli
* If you need to run some check, overwrite this or add your assert in src/globals/tests/{extension_name}.php
* If check failed, throw RuntimeException
*
* @throws RuntimeException * @throws RuntimeException
*/ */
public function runCliCheck(): void public function runCliCheckUnix(): void
{ {
// Run compile check if build target is cli
// If you need to run some check, overwrite this or add your assert in src/globals/tests/{extension_name}.php
// If check failed, throw RuntimeException
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "' . $this->getDistName() . '"', false); [$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "' . $this->getDistName() . '"', false);
if ($ret !== 0) { if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret); throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret);
@@ -192,6 +192,34 @@ class Extension
} }
} }
/**
* @throws RuntimeException
*/
public function runCliCheckWindows(): void
{
// Run compile check if build target is cli
// If you need to run some check, overwrite this or add your assert in src/globals/tests/{extension_name}.php
// If check failed, throw RuntimeException
[$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe --ri "' . $this->getDistName() . '"', false);
if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret);
}
if (file_exists(FileSystem::convertPath(ROOT_DIR . '/src/globals/tests/' . $this->getName() . '.php'))) {
// Trim additional content & escape special characters to allow inline usage
$test = str_replace(
['<?php', 'declare(strict_types=1);', "\n", '"', '$'],
['', '', '', '\"', '\$'],
file_get_contents(FileSystem::convertPath(ROOT_DIR . '/src/globals/tests/' . $this->getName() . '.php'))
);
[$ret] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php.exe -r "' . trim($test) . '"');
if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check');
}
}
}
/** /**
* @throws RuntimeException * @throws RuntimeException
*/ */

View File

@@ -140,7 +140,9 @@ abstract class LibraryBase
if (!$this->patched && $this->patchBeforeBuild()) { if (!$this->patched && $this->patchBeforeBuild()) {
file_put_contents($this->source_dir . '/.spc.patched', 'PATCHED!!!'); file_put_contents($this->source_dir . '/.spc.patched', 'PATCHED!!!');
} }
$this->getBuilder()->emitPatchPoint('before-library[ ' . static::NAME . ']-build');
$this->build(); $this->build();
$this->getBuilder()->emitPatchPoint('after-library[ ' . static::NAME . ']-build');
return BUILD_STATUS_OK; return BUILD_STATUS_OK;
} }

View File

@@ -34,4 +34,9 @@ class glfw extends Extension
{ {
return '--enable-glfw --with-glfw-dir=' . BUILD_ROOT_PATH; return '--enable-glfw --with-glfw-dir=' . BUILD_ROOT_PATH;
} }
public function getWindowsConfigureArg(): string
{
return '--enable-glfw=static';
}
} }

View File

@@ -11,6 +11,11 @@ use SPC\util\CustomExt;
#[CustomExt('mbregex')] #[CustomExt('mbregex')]
class mbregex extends Extension class mbregex extends Extension
{ {
public function getDistName(): string
{
return 'mbstring';
}
public function getConfigureArg(): string public function getConfigureArg(): string
{ {
return ''; return '';
@@ -19,7 +24,7 @@ class mbregex extends Extension
/** /**
* mbregex is not an extension, we need to overwrite the default check. * mbregex is not an extension, we need to overwrite the default check.
*/ */
public function runCliCheck(): void public function runCliCheckUnix(): void
{ {
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "mbstring" | grep regex', false); [$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "mbstring" | grep regex', false);
if ($ret !== 0) { if ($ret !== 0) {

View File

@@ -10,7 +10,7 @@ use SPC\util\CustomExt;
#[CustomExt('mbstring')] #[CustomExt('mbstring')]
class mbstring extends Extension class mbstring extends Extension
{ {
public function getUnixConfigureArg(): string public function getConfigureArg(): string
{ {
$arg = '--enable-mbstring'; $arg = '--enable-mbstring';
if ($this->builder->getExt('mbregex') === null) { if ($this->builder->getExt('mbregex') === null) {

View File

@@ -11,7 +11,7 @@ use SPC\util\CustomExt;
#[CustomExt('password-argon2')] #[CustomExt('password-argon2')]
class password_argon2 extends Extension class password_argon2 extends Extension
{ {
public function runCliCheck(): void public function runCliCheckUnix(): void
{ {
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "assert(defined(\'PASSWORD_ARGON2I\'));"'); [$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "assert(defined(\'PASSWORD_ARGON2I\'));"');
if ($ret !== 0) { if ($ret !== 0) {

View File

@@ -11,6 +11,11 @@ use SPC\util\CustomExt;
#[CustomExt('swoole-hook-mysql')] #[CustomExt('swoole-hook-mysql')]
class swoole_hook_mysql extends Extension class swoole_hook_mysql extends Extension
{ {
public function getDistName(): string
{
return 'swoole';
}
public function getUnixConfigureArg(): string public function getUnixConfigureArg(): string
{ {
// pdo_mysql doesn't need to be disabled // pdo_mysql doesn't need to be disabled
@@ -18,7 +23,7 @@ class swoole_hook_mysql extends Extension
return ''; return '';
} }
public function runCliCheck(): void public function runCliCheckUnix(): void
{ {
// skip if not enable swoole // skip if not enable swoole
if ($this->builder->getExt('swoole') === null) { if ($this->builder->getExt('swoole') === null) {

View File

@@ -12,6 +12,11 @@ use SPC\util\CustomExt;
#[CustomExt('swoole-hook-pgsql')] #[CustomExt('swoole-hook-pgsql')]
class swoole_hook_pgsql extends Extension class swoole_hook_pgsql extends Extension
{ {
public function getDistName(): string
{
return 'swoole';
}
public function getUnixConfigureArg(): string public function getUnixConfigureArg(): string
{ {
// pdo_pgsql need to be disabled // pdo_pgsql need to be disabled
@@ -22,7 +27,7 @@ class swoole_hook_pgsql extends Extension
return '--enable-swoole-pgsql'; return '--enable-swoole-pgsql';
} }
public function runCliCheck(): void public function runCliCheckUnix(): void
{ {
// skip if not enable swoole // skip if not enable swoole
if ($this->builder->getExt('swoole') === null) { if ($this->builder->getExt('swoole') === null) {

View File

@@ -12,6 +12,11 @@ use SPC\util\CustomExt;
#[CustomExt('swoole-hook-sqlite')] #[CustomExt('swoole-hook-sqlite')]
class swoole_hook_sqlite extends Extension class swoole_hook_sqlite extends Extension
{ {
public function getDistName(): string
{
return 'swoole';
}
public function getUnixConfigureArg(): string public function getUnixConfigureArg(): string
{ {
// pdo_pgsql need to be disabled // pdo_pgsql need to be disabled
@@ -22,7 +27,7 @@ class swoole_hook_sqlite extends Extension
return '--enable-swoole-sqlite'; return '--enable-swoole-sqlite';
} }
public function runCliCheck(): void public function runCliCheckUnix(): void
{ {
// skip if not enable swoole // skip if not enable swoole
if ($this->builder->getExt('swoole') === null) { if ($this->builder->getExt('swoole') === null) {

View File

@@ -4,19 +4,15 @@ declare(strict_types=1);
namespace SPC\builder\freebsd; namespace SPC\builder\freebsd;
use SPC\builder\BuilderBase; use SPC\builder\unix\UnixBuilderBase;
use SPC\builder\traits\UnixBuilderTrait;
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\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
class BSDBuilder extends BuilderBase class BSDBuilder extends UnixBuilderBase
{ {
/** Unix compatible builder methods */
use UnixBuilderTrait;
/** @var bool Micro patch phar flag */ /** @var bool Micro patch phar flag */
private bool $phar_patched = false; private bool $phar_patched = false;
@@ -81,10 +77,12 @@ class BSDBuilder extends BuilderBase
} }
$this->setOption('extra-libs', $extra_libs); $this->setOption('extra-libs', $extra_libs);
$this->emitPatchPoint('before-php-buildconf');
SourcePatcher::patchBeforeBuildconf($this); SourcePatcher::patchBeforeBuildconf($this);
shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force'); shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force');
$this->emitPatchPoint('before-php-configure');
SourcePatcher::patchBeforeConfigure($this); SourcePatcher::patchBeforeConfigure($this);
$json_74 = $this->getPHPVersionID() < 80000 ? '--enable-json ' : ''; $json_74 = $this->getPHPVersionID() < 80000 ? '--enable-json ' : '';
@@ -115,6 +113,7 @@ class BSDBuilder extends BuilderBase
$this->makeExtensionArgs() $this->makeExtensionArgs()
); );
$this->emitPatchPoint('before-php-make');
SourcePatcher::patchBeforeMake($this); SourcePatcher::patchBeforeMake($this);
$this->cleanMake(); $this->cleanMake();
@@ -140,6 +139,7 @@ class BSDBuilder extends BuilderBase
} }
if (php_uname('m') === $this->getOption('arch')) { if (php_uname('m') === $this->getOption('arch')) {
$this->emitPatchPoint('before-sanity-check');
$this->sanityCheck($build_target); $this->sanityCheck($build_target);
} }
} }
@@ -150,7 +150,7 @@ class BSDBuilder extends BuilderBase
* @throws RuntimeException * @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException
*/ */
public function buildCli(): void protected function buildCli(): void
{ {
$vars = SystemUtil::makeEnvVarString([ $vars = SystemUtil::makeEnvVarString([
'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size 'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size
@@ -173,7 +173,7 @@ class BSDBuilder extends BuilderBase
* @throws RuntimeException * @throws RuntimeException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
public function buildMicro(): void protected function buildMicro(): void
{ {
if ($this->getPHPVersionID() < 80000) { if ($this->getPHPVersionID() < 80000) {
throw new WrongUsageException('phpmicro only support PHP >= 8.0!'); throw new WrongUsageException('phpmicro only support PHP >= 8.0!');
@@ -211,7 +211,7 @@ class BSDBuilder extends BuilderBase
* @throws RuntimeException * @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException
*/ */
public function buildFpm(): void protected function buildFpm(): void
{ {
$vars = SystemUtil::makeEnvVarString([ $vars = SystemUtil::makeEnvVarString([
'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size 'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size
@@ -231,7 +231,7 @@ class BSDBuilder extends BuilderBase
* *
* @throws RuntimeException * @throws RuntimeException
*/ */
public function buildEmbed(): void protected function buildEmbed(): void
{ {
$vars = SystemUtil::makeEnvVarString([ $vars = SystemUtil::makeEnvVarString([
'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size 'EXTRA_CFLAGS' => '-g -Os', // with debug information, but optimize for size

View File

@@ -4,20 +4,16 @@ declare(strict_types=1);
namespace SPC\builder\linux; namespace SPC\builder\linux;
use SPC\builder\BuilderBase;
use SPC\builder\linux\library\LinuxLibraryBase; use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\builder\traits\UnixBuilderTrait; use SPC\builder\unix\UnixBuilderBase;
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\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
class LinuxBuilder extends BuilderBase class LinuxBuilder extends UnixBuilderBase
{ {
/** Unix compatible builder methods */
use UnixBuilderTrait;
/** @var array Tune cflags */ /** @var array Tune cflags */
public array $tune_c_flags; public array $tune_c_flags;
@@ -147,10 +143,13 @@ class LinuxBuilder extends BuilderBase
'LDFLAGS' => '-L' . BUILD_LIB_PATH, 'LDFLAGS' => '-L' . BUILD_LIB_PATH,
'LIBS' => '-ldl -lpthread', 'LIBS' => '-ldl -lpthread',
]); ]);
$this->emitPatchPoint('before-php-buildconf');
SourcePatcher::patchBeforeBuildconf($this); SourcePatcher::patchBeforeBuildconf($this);
shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force'); shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force');
$this->emitPatchPoint('before-php-configure');
SourcePatcher::patchBeforeConfigure($this); SourcePatcher::patchBeforeConfigure($this);
$phpVersionID = $this->getPHPVersionID(); $phpVersionID = $this->getPHPVersionID();
@@ -193,6 +192,7 @@ class LinuxBuilder extends BuilderBase
' ' . $envs_build_php . ' ' ' ' . $envs_build_php . ' '
); );
$this->emitPatchPoint('before-php-make');
SourcePatcher::patchBeforeMake($this); SourcePatcher::patchBeforeMake($this);
$this->cleanMake(); $this->cleanMake();
@@ -218,6 +218,7 @@ class LinuxBuilder extends BuilderBase
} }
if (php_uname('m') === $this->getOption('arch')) { if (php_uname('m') === $this->getOption('arch')) {
$this->emitPatchPoint('before-sanity-check');
$this->sanityCheck($build_target); $this->sanityCheck($build_target);
} }
} }
@@ -228,7 +229,7 @@ class LinuxBuilder extends BuilderBase
* @throws RuntimeException * @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException
*/ */
public function buildCli(): void protected function buildCli(): void
{ {
$vars = SystemUtil::makeEnvVarString($this->getBuildVars()); $vars = SystemUtil::makeEnvVarString($this->getBuildVars());
shell()->cd(SOURCE_PATH . '/php-src') shell()->cd(SOURCE_PATH . '/php-src')
@@ -249,7 +250,7 @@ class LinuxBuilder extends BuilderBase
* @throws RuntimeException * @throws RuntimeException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
public function buildMicro(): void protected function buildMicro(): void
{ {
if ($this->getPHPVersionID() < 80000) { if ($this->getPHPVersionID() < 80000) {
throw new WrongUsageException('phpmicro only support PHP >= 8.0!'); throw new WrongUsageException('phpmicro only support PHP >= 8.0!');
@@ -283,7 +284,7 @@ class LinuxBuilder extends BuilderBase
* @throws FileSystemException * @throws FileSystemException
* @throws RuntimeException * @throws RuntimeException
*/ */
public function buildFpm(): void protected function buildFpm(): void
{ {
$vars = SystemUtil::makeEnvVarString($this->getBuildVars()); $vars = SystemUtil::makeEnvVarString($this->getBuildVars());
shell()->cd(SOURCE_PATH . '/php-src') shell()->cd(SOURCE_PATH . '/php-src')
@@ -302,7 +303,7 @@ class LinuxBuilder extends BuilderBase
* *
* @throws RuntimeException * @throws RuntimeException
*/ */
public function buildEmbed(): void protected function buildEmbed(): void
{ {
$vars = SystemUtil::makeEnvVarString($this->getBuildVars()); $vars = SystemUtil::makeEnvVarString($this->getBuildVars());

View File

@@ -209,4 +209,21 @@ class SystemUtil
} }
return $ret; return $ret;
} }
/**
* Get fully-supported linux distros.
*
* @return string[] List of supported Linux distro name for doctor
*/
public static function getSupportedDistros(): array
{
return [
// debian-like
'debian', 'ubuntu', 'Deepin',
// rhel-like
'redhat',
// alpine
'alpine',
];
}
} }

View File

@@ -4,20 +4,16 @@ declare(strict_types=1);
namespace SPC\builder\macos; namespace SPC\builder\macos;
use SPC\builder\BuilderBase;
use SPC\builder\macos\library\MacOSLibraryBase; use SPC\builder\macos\library\MacOSLibraryBase;
use SPC\builder\traits\UnixBuilderTrait; use SPC\builder\unix\UnixBuilderBase;
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\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourcePatcher; use SPC\store\SourcePatcher;
class MacOSBuilder extends BuilderBase class MacOSBuilder extends UnixBuilderBase
{ {
/** Unix compatible builder methods */
use UnixBuilderTrait;
/** @var bool Micro patch phar flag */ /** @var bool Micro patch phar flag */
private bool $phar_patched = false; private bool $phar_patched = false;
@@ -141,10 +137,12 @@ class MacOSBuilder extends BuilderBase
} }
$this->setOption('extra-libs', $extra_libs); $this->setOption('extra-libs', $extra_libs);
$this->emitPatchPoint('before-php-buildconf');
SourcePatcher::patchBeforeBuildconf($this); SourcePatcher::patchBeforeBuildconf($this);
shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force'); shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force');
$this->emitPatchPoint('before-php-configure');
SourcePatcher::patchBeforeConfigure($this); SourcePatcher::patchBeforeConfigure($this);
$json_74 = $this->getPHPVersionID() < 80000 ? '--enable-json ' : ''; $json_74 = $this->getPHPVersionID() < 80000 ? '--enable-json ' : '';
@@ -190,6 +188,7 @@ class MacOSBuilder extends BuilderBase
$envs_build_php $envs_build_php
); );
$this->emitPatchPoint('before-php-make');
SourcePatcher::patchBeforeMake($this); SourcePatcher::patchBeforeMake($this);
$this->cleanMake(); $this->cleanMake();
@@ -215,6 +214,7 @@ class MacOSBuilder extends BuilderBase
} }
if (php_uname('m') === $this->getOption('arch')) { if (php_uname('m') === $this->getOption('arch')) {
$this->emitPatchPoint('before-sanity-check');
$this->sanityCheck($build_target); $this->sanityCheck($build_target);
} }
} }
@@ -225,7 +225,7 @@ class MacOSBuilder extends BuilderBase
* @throws RuntimeException * @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException
*/ */
public function buildCli(): void protected function buildCli(): void
{ {
$vars = SystemUtil::makeEnvVarString($this->getBuildVars()); $vars = SystemUtil::makeEnvVarString($this->getBuildVars());
@@ -244,7 +244,7 @@ class MacOSBuilder extends BuilderBase
* @throws RuntimeException * @throws RuntimeException
* @throws WrongUsageException * @throws WrongUsageException
*/ */
public function buildMicro(): void protected function buildMicro(): void
{ {
if ($this->getPHPVersionID() < 80000) { if ($this->getPHPVersionID() < 80000) {
throw new WrongUsageException('phpmicro only support PHP >= 8.0!'); throw new WrongUsageException('phpmicro only support PHP >= 8.0!');
@@ -280,7 +280,7 @@ class MacOSBuilder extends BuilderBase
* @throws RuntimeException * @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException
*/ */
public function buildFpm(): void protected function buildFpm(): void
{ {
$vars = SystemUtil::makeEnvVarString($this->getBuildVars()); $vars = SystemUtil::makeEnvVarString($this->getBuildVars());
@@ -297,7 +297,7 @@ class MacOSBuilder extends BuilderBase
* *
* @throws RuntimeException * @throws RuntimeException
*/ */
public function buildEmbed(): void protected function buildEmbed(): void
{ {
$vars = SystemUtil::makeEnvVarString($this->getBuildVars()); $vars = SystemUtil::makeEnvVarString($this->getBuildVars());

View File

@@ -1,7 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\traits;
trait LibraryTrait {}

View File

@@ -12,8 +12,6 @@ use SPC\store\FileSystem;
trait UnixLibraryTrait trait UnixLibraryTrait
{ {
use LibraryTrait;
/** /**
* @throws RuntimeException * @throws RuntimeException
* @throws FileSystemException * @throws FileSystemException

View File

@@ -2,15 +2,19 @@
declare(strict_types=1); declare(strict_types=1);
namespace SPC\builder\traits; namespace SPC\builder\unix;
use SPC\builder\BuilderBase;
use SPC\builder\linux\LinuxBuilder; use SPC\builder\linux\LinuxBuilder;
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\Config;
use SPC\store\FileSystem; use SPC\store\FileSystem;
use SPC\store\SourceExtractor;
use SPC\util\DependencyUtil;
trait UnixBuilderTrait abstract class UnixBuilderBase extends BuilderBase
{ {
/** @var string cflags */ /** @var string cflags */
public string $arch_c_flags; public string $arch_c_flags;
@@ -49,77 +53,6 @@ trait UnixBuilderTrait
return array_map(fn ($x) => realpath(BUILD_LIB_PATH . "/{$x}"), $libFiles); return array_map(fn ($x) => realpath(BUILD_LIB_PATH . "/{$x}"), $libFiles);
} }
/**
* Sanity check after build complete
*
* @throws RuntimeException
*/
public function sanityCheck(int $build_target): void
{
// sanity check for php-cli
if (($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
logger()->info('running cli sanity check');
[$ret, $output] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "echo \"hello\";"');
if ($ret !== 0 || trim(implode('', $output)) !== 'hello') {
throw new RuntimeException('cli failed sanity check');
}
foreach ($this->exts as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheck();
}
}
// sanity check for phpmicro
if (($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
if (file_exists(SOURCE_PATH . '/hello.exe')) {
@unlink(SOURCE_PATH . '/hello.exe');
}
file_put_contents(
SOURCE_PATH . '/hello.exe',
file_get_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx') .
'<?php echo "hello";'
);
chmod(SOURCE_PATH . '/hello.exe', 0755);
[$ret, $output2] = shell()->execWithResult(SOURCE_PATH . '/hello.exe');
if ($ret !== 0 || trim($out = implode('', $output2)) !== 'hello') {
throw new RuntimeException('micro failed sanity check, ret[' . $ret . '], out[' . ($out ?? 'NULL') . ']');
}
}
}
/**
* 将编译好的二进制文件发布到 buildroot
*
* @param int $type 发布类型
* @throws RuntimeException
* @throws FileSystemException
*/
public function deployBinary(int $type): bool
{
$src = match ($type) {
BUILD_TARGET_CLI => SOURCE_PATH . '/php-src/sapi/cli/php',
BUILD_TARGET_MICRO => SOURCE_PATH . '/php-src/sapi/micro/micro.sfx',
BUILD_TARGET_FPM => SOURCE_PATH . '/php-src/sapi/fpm/php-fpm',
default => throw new RuntimeException('Deployment does not accept type ' . $type),
};
logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file');
FileSystem::createDir(BUILD_ROOT_PATH . '/bin');
shell()->exec('cp ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_ROOT_PATH . '/bin/'));
return true;
}
/**
* Run php clean
*
* @throws RuntimeException
*/
public function cleanMake(): void
{
logger()->info('cleaning up');
shell()->cd(SOURCE_PATH . '/php-src')->exec('make clean');
}
/** /**
* Return generic cmake options when configuring cmake projects * Return generic cmake options when configuring cmake projects
*/ */
@@ -156,4 +89,139 @@ trait UnixBuilderTrait
} }
return $extra; return $extra;
} }
public function buildLibs(array $sorted_libraries): void
{
// search all supported libs
$support_lib_list = [];
$classes = FileSystem::getClassesPsr4(
ROOT_DIR . '/src/SPC/builder/' . osfamily2dir() . '/library',
'SPC\\builder\\' . osfamily2dir() . '\\library'
);
foreach ($classes as $class) {
if (defined($class . '::NAME') && $class::NAME !== 'unknown' && Config::getLib($class::NAME) !== null) {
$support_lib_list[$class::NAME] = $class;
}
}
// if no libs specified, compile all supported libs
if ($sorted_libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list);
$sorted_libraries = DependencyUtil::getLibsByDeps($libraries);
}
// pkg-config must be compiled first, whether it is specified or not
if (!in_array('pkg-config', $sorted_libraries)) {
array_unshift($sorted_libraries, 'pkg-config');
}
// add lib object for builder
foreach ($sorted_libraries as $library) {
// if some libs are not supported (but in config "lib.json", throw exception)
if (!isset($support_lib_list[$library])) {
throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!');
}
$lib = new ($support_lib_list[$library])($this);
$this->addLib($lib);
}
// calculate and check dependencies
foreach ($this->libs as $lib) {
$lib->calcDependency();
}
// patch point
$this->emitPatchPoint('before-libs-extract');
// extract sources
SourceExtractor::initSource(libs: $sorted_libraries);
$this->emitPatchPoint('after-libs-extract');
// build all libs
foreach ($this->libs as $lib) {
match ($lib->tryBuild($this->getOption('rebuild', false))) {
BUILD_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] build success'),
BUILD_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'),
BUILD_STATUS_FAILED => logger()->error('lib [' . $lib::NAME . '] build failed'),
default => logger()->warning('lib [' . $lib::NAME . '] build status unknown'),
};
}
}
/**
* Sanity check after build complete
*
* @throws RuntimeException
*/
protected function sanityCheck(int $build_target): void
{
// sanity check for php-cli
if (($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
logger()->info('running cli sanity check');
[$ret, $output] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "echo \"hello\";"');
if ($ret !== 0 || trim(implode('', $output)) !== 'hello') {
throw new RuntimeException('cli failed sanity check');
}
foreach ($this->exts as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckUnix();
}
}
// sanity check for phpmicro
if (($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
if (file_exists(SOURCE_PATH . '/hello.exe')) {
@unlink(SOURCE_PATH . '/hello.exe');
}
file_put_contents(
SOURCE_PATH . '/hello.exe',
file_get_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx') .
($this->getOption('without-micro-ext-test') ? '<?php echo "[micro-test-start][micro-test-end]";' : $this->generateMicroExtTests())
);
chmod(SOURCE_PATH . '/hello.exe', 0755);
[$ret, $output2] = shell()->execWithResult(SOURCE_PATH . '/hello.exe');
$raw_out = trim(implode('', $output2));
$condition[0] = $ret === 0;
$condition[1] = str_starts_with($raw_out, '[micro-test-start]') && str_ends_with($raw_out, '[micro-test-end]');
foreach ($condition as $k => $v) {
if (!$v) {
throw new RuntimeException("micro failed sanity check with condition[{$k}], ret[{$ret}], out[{$raw_out}]");
}
}
}
}
/**
* 将编译好的二进制文件发布到 buildroot
*
* @param int $type 发布类型
* @throws RuntimeException
* @throws FileSystemException
*/
protected function deployBinary(int $type): bool
{
$src = match ($type) {
BUILD_TARGET_CLI => SOURCE_PATH . '/php-src/sapi/cli/php',
BUILD_TARGET_MICRO => SOURCE_PATH . '/php-src/sapi/micro/micro.sfx',
BUILD_TARGET_FPM => SOURCE_PATH . '/php-src/sapi/fpm/php-fpm',
default => throw new RuntimeException('Deployment does not accept type ' . $type),
};
logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file');
FileSystem::createDir(BUILD_ROOT_PATH . '/bin');
shell()->exec('cp ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_ROOT_PATH . '/bin/'));
return true;
}
/**
* Run php clean
*
* @throws RuntimeException
*/
protected function cleanMake(): void
{
logger()->info('cleaning up');
shell()->cd(SOURCE_PATH . '/php-src')->exec('make clean');
}
} }

View File

@@ -52,7 +52,7 @@ trait curl
// compile // compile
shell()->cd($this->source_dir . '/build') shell()->cd($this->source_dir . '/build')
->exec('sed -i.save s@\${CMAKE_C_IMPLICIT_LINK_LIBRARIES}@@ ../CMakeLists.txt') ->exec('sed -i.save s@\${CMAKE_C_IMPLICIT_LINK_LIBRARIES}@@ ../CMakeLists.txt')
->exec("cmake {$this->builder->makeCmakeArgs()} -DBUILD_SHARED_LIBS=OFF -DBUILD_CURL_EXE=OFF {$extra} ..") ->exec("cmake {$this->builder->makeCmakeArgs()} -DBUILD_SHARED_LIBS=OFF -DBUILD_CURL_EXE=OFF -DBUILD_LIBCURL_DOCS=OFF {$extra} ..")
->exec("make -j{$this->builder->concurrency}") ->exec("make -j{$this->builder->concurrency}")
->exec('make install DESTDIR=' . BUILD_ROOT_PATH); ->exec('make install DESTDIR=' . BUILD_ROOT_PATH);
// patch pkgconf // patch pkgconf

View File

@@ -4,17 +4,25 @@ declare(strict_types=1);
namespace SPC\builder\windows; namespace SPC\builder\windows;
use SPC\exception\FileSystemException;
use SPC\store\FileSystem;
class SystemUtil class SystemUtil
{ {
/** /**
* @param string $name 命令名称 * Find windows program using executable name.
* @param array $paths 寻找的目标路径(如果不传入,则使用环境变量 PATH *
* @return null|string 找到了返回命令路径,找不到返回 null * @param string $name command name (xxx.exe)
* @param array $paths search path (default use env path)
* @return null|string null if not found, string is absolute path
*/ */
public static function findCommand(string $name, array $paths = []): ?string public static function findCommand(string $name, array $paths = [], bool $include_sdk_bin = false): ?string
{ {
if (!$paths) { if (!$paths) {
$paths = explode(PATH_SEPARATOR, getenv('Path')); $paths = explode(PATH_SEPARATOR, getenv('Path'));
if ($include_sdk_bin) {
$paths[] = PHP_SDK_PATH . '\bin';
}
} }
foreach ($paths as $path) { foreach ($paths as $path) {
if (file_exists($path . DIRECTORY_SEPARATOR . $name)) { if (file_exists($path . DIRECTORY_SEPARATOR . $name)) {
@@ -23,4 +31,74 @@ class SystemUtil
} }
return null; return null;
} }
/**
* Find Visual Studio installation.
*
* @return array<string, string>|false False if not installed, array contains 'version' and 'dir'
*/
public static function findVisualStudio(): array|false
{
$check_path = [
'C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe' => 'vs17',
'C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe' => 'vs17',
'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe' => 'vs17',
'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe' => 'vs16',
'C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe' => 'vs16',
'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe' => 'vs16',
];
foreach ($check_path as $path => $vs_version) {
if (file_exists($path)) {
$vs_ver = $vs_version;
$d_dir = dirname($path, 4);
return [
'version' => $vs_ver,
'dir' => $d_dir,
];
}
}
return false;
}
/**
* Get CPU count for concurrency.
*/
public static function getCpuCount(): int
{
$result = f_exec('echo %NUMBER_OF_PROCESSORS%', $out, $code);
if ($code !== 0 || !$result) {
return 1;
}
return intval($result);
}
/**
* Create CMake toolchain file.
*
* @param null|string $cflags CFLAGS for cmake, default use '/MT /Os /Ob1 /DNDEBUG /D_ACRTIMP= /D_CRTIMP='
* @param null|string $ldflags LDFLAGS for cmake, default use '/nodefaultlib:msvcrt /nodefaultlib:msvcrtd /defaultlib:libcmt'
* @throws FileSystemException
*/
public static function makeCmakeToolchainFile(?string $cflags = null, ?string $ldflags = null): string
{
if ($cflags === null) {
$cflags = '/MT /Os /Ob1 /DNDEBUG /D_ACRTIMP= /D_CRTIMP=';
}
if ($ldflags === null) {
$ldflags = '/nodefaultlib:msvcrt /nodefaultlib:msvcrtd /defaultlib:libcmt';
}
$buildroot = str_replace('\\', '\\\\', BUILD_ROOT_PATH);
$toolchain = <<<CMAKE
set(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_C_FLAGS "{$cflags}")
SET(CMAKE_C_FLAGS_DEBUG "{$cflags}")
SET(CMAKE_CXX_FLAGS "{$cflags}")
SET(CMAKE_CXX_FLAGS_DEBUG "{$cflags}")
SET(CMAKE_EXE_LINKER_FLAGS "{$ldflags}")
SET(CMAKE_FIND_ROOT_PATH "{$buildroot}")
SET(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
CMAKE;
FileSystem::writeFile(SOURCE_PATH . '\toolchain.cmake', $toolchain);
return realpath(SOURCE_PATH . '\toolchain.cmake');
}
} }

View File

@@ -0,0 +1,343 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows;
use SPC\builder\BuilderBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\SourceExtractor;
use SPC\store\SourcePatcher;
use SPC\util\DependencyUtil;
class WindowsBuilder extends BuilderBase
{
/** @var string cmake toolchain file */
public string $cmake_toolchain_file;
public string $sdk_prefix;
private bool $zts;
/** @var bool Micro patch phar flag */
private bool $phar_patched = false;
/**
* @throws FileSystemException
*/
public function __construct(array $options = [])
{
$this->options = $options;
// ---------- set necessary options ----------
// set sdk (require visual studio 16 or 17)
$vs = SystemUtil::findVisualStudio()['version'];
$this->sdk_prefix = PHP_SDK_PATH . "\\phpsdk-{$vs}-x64.bat -t";
// set zts
$this->zts = $this->getOption('enable-zts', false);
// set concurrency
$this->concurrency = SystemUtil::getCpuCount();
// make cmake toolchain
$this->cmake_toolchain_file = SystemUtil::makeCmakeToolchainFile();
}
/**
* @throws RuntimeException
* @throws WrongUsageException
* @throws FileSystemException
*/
public function buildPHP(int $build_target = BUILD_TARGET_NONE): void
{
// ---------- Update extra-libs ----------
$extra_libs = $this->getOption('extra-libs', '');
$extra_libs .= (empty($extra_libs) ? '' : ' ') . implode(' ', $this->getAllStaticLibFiles());
$this->setOption('extra-libs', $extra_libs);
$enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
SourcePatcher::patchBeforeBuildconf($this);
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} buildconf.bat");
SourcePatcher::patchBeforeConfigure($this);
$zts = $this->zts ? '--enable-zts=yes ' : '--enable-zts=no ';
cmd()->cd(SOURCE_PATH . '\php-src')
->exec(
"{$this->sdk_prefix} configure.bat --task-args \"" .
'--disable-all ' .
'--disable-cgi ' .
'--with-php-build=' . BUILD_ROOT_PATH . ' ' .
'--with-extra-includes=' . BUILD_INCLUDE_PATH . ' ' .
'--with-extra-libs=' . BUILD_LIB_PATH . ' ' .
($enableCli ? '--enable-cli=yes ' : '--enable-cli=no ') .
($enableMicro ? '--enable-micro=yes ' : '--enable-micro=no ') .
($enableEmbed ? '--enable-embed=yes ' : '--enable-embed=no ') .
"{$this->makeExtensionArgs()} " .
$zts .
'"'
);
SourcePatcher::patchBeforeMake($this);
$this->cleanMake();
if ($enableCli) {
logger()->info('building cli');
$this->buildCli();
}
if ($enableFpm) {
logger()->warning('Windows does not support fpm SAPI, I will skip it.');
}
if ($enableMicro) {
logger()->info('building micro');
$this->buildMicro();
}
if ($enableEmbed) {
logger()->warning('Windows does not currently support embed SAPI.');
// logger()->info('building embed');
$this->buildEmbed();
}
$this->sanityCheck($build_target);
}
/**
* @throws FileSystemException
* @throws RuntimeException
*/
public function buildCli(): void
{
SourcePatcher::patchWindowsCLITarget();
// add nmake wrapper
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_cli_wrapper.bat', "nmake /nologo LIBS_CLI=\"{$this->getOption('extra-libs')} ws2_32.lib shell32.lib\" EXTRA_LD_FLAGS_PROGRAM= %*");
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_cli_wrapper.bat --task-args php.exe");
$this->deployBinary(BUILD_TARGET_CLI);
}
public function buildEmbed(): void
{
// TODO: add embed support for windows
/*
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_embed_wrapper.bat', 'nmake /nologo %*');
cmd()->cd(SOURCE_PATH . '\php-src')
->exec("{$this->sdk_prefix} nmake_embed_wrapper.bat --task-args php8embed.lib");
*/
}
/**
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
*/
public function buildMicro(): void
{
// workaround for fiber (originally from https://github.com/dixyes/lwmbs/blob/master/windows/MicroBuild.php)
$makefile = FileSystem::readFile(SOURCE_PATH . '\php-src\Makefile');
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";
}
FileSystem::writeFile(SOURCE_PATH . '\php-src\Makefile', $makefile);
// add nmake wrapper
$fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' /DPHP_MICRO_FAKE_CLI" ' : '';
$wrapper = "nmake /nologo LIBS_MICRO=\"{$this->getOption('extra-libs')} ws2_32.lib shell32.lib\" CFLAGS_MICRO=\"/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1{$fake_cli}\" %*";
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_micro_wrapper.bat', $wrapper);
// phar patch for micro
if ($this->getExt('phar')) {
$this->phar_patched = true;
SourcePatcher::patchMicro(['phar']);
}
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_micro_wrapper.bat --task-args micro");
if ($this->phar_patched) {
SourcePatcher::patchMicro(['phar'], true);
}
$this->deployBinary(BUILD_TARGET_MICRO);
}
public function buildLibs(array $sorted_libraries): void
{
// search all supported libs
$support_lib_list = [];
$classes = FileSystem::getClassesPsr4(
ROOT_DIR . '\src\SPC\builder\\' . osfamily2dir() . '\\library',
'SPC\\builder\\' . osfamily2dir() . '\\library'
);
foreach ($classes as $class) {
if (defined($class . '::NAME') && $class::NAME !== 'unknown' && Config::getLib($class::NAME) !== null) {
$support_lib_list[$class::NAME] = $class;
}
}
// if no libs specified, compile all supported libs
if ($sorted_libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list);
$sorted_libraries = DependencyUtil::getLibsByDeps($libraries);
}
// add lib object for builder
foreach ($sorted_libraries as $library) {
// if some libs are not supported (but in config "lib.json", throw exception)
if (!isset($support_lib_list[$library])) {
throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!');
}
$lib = new ($support_lib_list[$library])($this);
$this->addLib($lib);
}
// calculate and check dependencies
foreach ($this->libs as $lib) {
$lib->calcDependency();
}
// extract sources
SourceExtractor::initSource(libs: $sorted_libraries);
// build all libs
foreach ($this->libs as $lib) {
match ($lib->tryBuild($this->getOption('rebuild', false))) {
BUILD_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] build success'),
BUILD_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'),
BUILD_STATUS_FAILED => logger()->error('lib [' . $lib::NAME . '] build failed'),
default => logger()->warning('lib [' . $lib::NAME . '] build status unknown'),
};
}
}
/**
* @throws FileSystemException
* @throws RuntimeException
*/
public function cleanMake(): void
{
FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_clean_wrapper.bat', 'nmake /nologo %*');
cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_clean_wrapper.bat --task-args \"clean\"");
}
/**
* Run extension and PHP cli and micro check
*
* @throws RuntimeException
*/
public function sanityCheck(mixed $build_target): void
{
// sanity check for php-cli
if (($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
logger()->info('running cli sanity check');
[$ret, $output] = cmd()->execWithResult(BUILD_ROOT_PATH . '\bin\php.exe -r "echo \"hello\";"');
if ($ret !== 0 || trim(implode('', $output)) !== 'hello') {
throw new RuntimeException('cli failed sanity check');
}
foreach ($this->exts as $ext) {
logger()->debug('testing ext: ' . $ext->getName());
$ext->runCliCheckWindows();
}
}
// sanity check for phpmicro
if (($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
if (file_exists(SOURCE_PATH . '\hello.exe')) {
@unlink(SOURCE_PATH . '\hello.exe');
}
file_put_contents(
SOURCE_PATH . '\hello.exe',
file_get_contents(BUILD_ROOT_PATH . '\bin\micro.sfx') .
($this->getOption('without-micro-ext-test') ? '<?php echo "[micro-test-start][micro-test-end]";' : $this->generateMicroExtTests())
);
chmod(SOURCE_PATH . '\hello.exe', 0755);
[$ret, $output2] = cmd()->execWithResult(SOURCE_PATH . '\hello.exe');
$raw_out = trim(implode('', $output2));
$condition[0] = $ret === 0;
$condition[1] = str_starts_with($raw_out, '[micro-test-start]') && str_ends_with($raw_out, '[micro-test-end]');
foreach ($condition as $k => $v) {
if (!$v) {
throw new RuntimeException("micro failed sanity check with condition[{$k}], ret[{$ret}], out[{$raw_out}]");
}
}
}
}
/**
* 将编译好的二进制文件发布到 buildroot
*
* @param int $type 发布类型
* @throws RuntimeException
* @throws FileSystemException
*/
public function deployBinary(int $type): bool
{
$ts = $this->zts ? '_TS' : '';
$src = match ($type) {
BUILD_TARGET_CLI => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\php.exe",
BUILD_TARGET_MICRO => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\micro.sfx",
default => throw new RuntimeException('Deployment does not accept type ' . $type),
};
logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file');
FileSystem::createDir(BUILD_ROOT_PATH . '\bin');
cmd()->exec('copy ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_ROOT_PATH . '\bin\\'));
return true;
}
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public function getAllStaticLibFiles(): array
{
$libs = [];
// reorder libs
foreach ($this->libs as $lib) {
foreach ($lib->getDependencies() as $dep) {
$libs[] = $dep;
}
$libs[] = $lib;
}
$libFiles = [];
$libNames = [];
// merge libs
foreach ($libs as $lib) {
if (!in_array($lib::NAME, $libNames, true)) {
$libNames[] = $lib::NAME;
array_unshift($libFiles, ...$lib->getStaticLibs());
}
}
return $libFiles;
}
/**
* Generate command wrapper prefix for php-sdk internal commands.
*
* @param string $internal_cmd command in php-sdk-tools\bin
* @return string Example: C:\php-sdk-tools\phpsdk-vs17-x64.bat -t source\cmake_wrapper.bat --task-args
*/
public function makeSimpleWrapper(string $internal_cmd): string
{
$wrapper_bat = SOURCE_PATH . '\\' . crc32($internal_cmd) . '_wrapper.bat';
if (!file_exists($wrapper_bat)) {
file_put_contents($wrapper_bat, $internal_cmd . ' %*');
}
return "{$this->sdk_prefix} {$wrapper_bat} --task-args";
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\builder\BuilderBase;
use SPC\builder\LibraryBase;
use SPC\builder\windows\WindowsBuilder;
use SPC\exception\FileSystemException;
use SPC\store\FileSystem;
abstract class WindowsLibraryBase extends LibraryBase
{
public function __construct(protected WindowsBuilder $builder)
{
parent::__construct();
}
public function getBuilder(): BuilderBase
{
return $this->builder;
}
/**
* Create a nmake wrapper file.
*
* @param string $content nmake wrapper content
* @param string $default_filename default nmake wrapper filename
* @throws FileSystemException
*/
public function makeNmakeWrapper(string $content, string $default_filename = ''): string
{
if ($default_filename === '') {
$default_filename = $this->source_dir . '\nmake_wrapper.bat';
}
FileSystem::writeFile($default_filename, $content);
return 'nmake_wrapper.bat';
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\builder\windows\SystemUtil;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
class openssl extends WindowsLibraryBase
{
public const NAME = 'openssl';
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');
if ($perl === null) {
throw new RuntimeException('You need to install perl first! (easiest way is using static-php-cli command "doctor")');
}
cmd()->cd($this->source_dir)
->execWithWrapper(
$this->builder->makeSimpleWrapper($perl),
'Configure zlib VC-WIN64A ' .
'no-shared ' .
'--prefix=' . quote(BUILD_ROOT_PATH) . ' ' .
'--with-zlib-lib=' . quote(BUILD_LIB_PATH) . ' ' .
'--with-zlib-include=' . quote(BUILD_INCLUDE_PATH) . ' ' .
'--release ' .
'no-legacy '
);
// patch zlib
FileSystem::replaceFileStr($this->source_dir . '\Makefile', 'ZLIB1', 'zlibstatic.lib');
// patch debug: https://stackoverflow.com/questions/18486243/how-do-i-build-openssl-statically-linked-against-windows-runtime
FileSystem::replaceFileStr($this->source_dir . '\Makefile', '/debug', '/incremental:no /opt:icf /dynamicbase /nxcompat /ltcg /nodefaultlib:msvcrt');
cmd()->cd($this->source_dir)->execWithWrapper(
$this->builder->makeSimpleWrapper('nmake'),
'install_dev ' .
'CNF_LDFLAGS="/NODEFAULTLIB:kernel32.lib /NODEFAULTLIB:msvcrt /NODEFAULTLIB:msvcrtd /DEFAULTLIB:libcmt /LIBPATH:' . BUILD_LIB_PATH . ' zlibstatic.lib"'
);
copy($this->source_dir . '\ms\applink.c', BUILD_INCLUDE_PATH . '\openssl\applink.c');
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\store\FileSystem;
class zlib extends WindowsLibraryBase
{
public const NAME = 'zlib';
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 ' .
'-DSKIP_INSTALL_FILES=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 . '\zlibstatic.lib', BUILD_LIB_PATH . '\zlib_a.lib');
unlink(BUILD_ROOT_PATH . '\bin\zlib.dll');
unlink(BUILD_LIB_PATH . '\zlib.lib');
}
}

View File

@@ -35,6 +35,8 @@ class BuildCliCommand extends BuildCommand
$this->addOption('with-micro-fake-cli', null, null, 'Enable phpmicro fake cli'); $this->addOption('with-micro-fake-cli', null, null, 'Enable phpmicro fake cli');
$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('without-micro-ext-test', null, null, 'Disable phpmicro with extension test code');
} }
public function handle(): int public function handle(): int
@@ -102,6 +104,9 @@ class BuildCliCommand extends BuildCommand
SourcePatcher::patchHardcodedINI($custom_ini); SourcePatcher::patchHardcodedINI($custom_ini);
} }
// add static-php-cli.version to main.c, in order to debug php failure more easily
SourcePatcher::patchSPCVersionToPHP($this->getApplication()->getVersion());
// start to build // start to build
$builder->buildPHP($rule); $builder->buildPHP($rule);
@@ -119,13 +124,17 @@ class BuildCliCommand extends BuildCommand
$fixed = ' (host system)'; $fixed = ' (host system)';
} }
if (($rule & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) { if (($rule & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
logger()->info('Static php binary path' . $fixed . ': ' . $build_root_path . '/bin/php'); $win_suffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : '';
$path = FileSystem::convertPath("{$build_root_path}/bin/php{$win_suffix}");
logger()->info("Static php binary path{$fixed}: {$path}");
} }
if (($rule & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) { if (($rule & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
logger()->info('phpmicro binary path' . $fixed . ': ' . $build_root_path . '/bin/micro.sfx'); $path = FileSystem::convertPath("{$build_root_path}/bin/micro.sfx");
logger()->info("phpmicro binary path{$fixed}: {$path}");
} }
if (($rule & BUILD_TARGET_FPM) === BUILD_TARGET_FPM) { if (($rule & BUILD_TARGET_FPM) === BUILD_TARGET_FPM && PHP_OS_FAMILY !== 'Windows') {
logger()->info('Static php-fpm binary path' . $fixed . ': ' . $build_root_path . '/bin/php-fpm'); $path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm");
logger()->info("Static php-fpm binary path{$fixed}: {$path}");
} }
// export metadata // export metadata
@@ -134,7 +143,8 @@ class BuildCliCommand extends BuildCommand
// export licenses // export licenses
$dumper = new LicenseDumper(); $dumper = new LicenseDumper();
$dumper->addExts($extensions)->addLibs($libraries)->addSources(['php-src'])->dump(BUILD_ROOT_PATH . '/license'); $dumper->addExts($extensions)->addLibs($libraries)->addSources(['php-src'])->dump(BUILD_ROOT_PATH . '/license');
logger()->info('License path' . $fixed . ': ' . $build_root_path . '/license/'); $path = FileSystem::convertPath("{$build_root_path}/license/");
logger()->info("License path{$fixed}: {$path}");
return static::SUCCESS; return static::SUCCESS;
} catch (WrongUsageException $e) { } catch (WrongUsageException $e) {
// WrongUsageException is not an exception, it's a user error, so we just print the error message // WrongUsageException is not an exception, it's a user error, so we just print the error message

View File

@@ -116,6 +116,7 @@ class DownloadCommand extends BaseCommand
// get source list that will be downloaded // get source list that will be downloaded
$sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources')))); $sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
if (empty($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()); $sources = array_keys(Config::getSources());
} }
} }
@@ -188,7 +189,8 @@ class DownloadCommand extends BaseCommand
// create downloads // create downloads
try { try {
if (PHP_OS_FAMILY !== 'Windows') { if (PHP_OS_FAMILY !== 'Windows') {
f_passthru('mkdir ' . DOWNLOAD_PATH . ' && cd ' . DOWNLOAD_PATH . ' && unzip ' . escapeshellarg($path)); $abs_path = realpath($path);
f_passthru('mkdir ' . DOWNLOAD_PATH . ' && cd ' . DOWNLOAD_PATH . ' && unzip ' . escapeshellarg($abs_path));
} }
// Windows TODO // Windows TODO

View File

@@ -87,6 +87,10 @@ class MicroCombineCommand extends BaseCommand
// 8. Combine ! // 8. Combine !
$output = FileSystem::isRelativePath($output) ? (WORKING_DIR . '/' . $output) : $output; $output = FileSystem::isRelativePath($output) ? (WORKING_DIR . '/' . $output) : $output;
$file_target = file_get_contents($micro_file) . $ini_part . file_get_contents($file); $file_target = file_get_contents($micro_file) . $ini_part . file_get_contents($file);
if (PHP_OS_FAMILY === 'Windows' && !str_ends_with(strtolower($output), '.exe')) {
$output .= '.exe';
}
$output = FileSystem::convertPath($output);
$result = file_put_contents($output, $file_target); $result = file_put_contents($output, $file_target);
if ($result === false) { if ($result === false) {
$this->output->writeln('<error>Combine failed.</error>'); $this->output->writeln('<error>Combine failed.</error>');

View File

@@ -62,6 +62,7 @@ class LinuxToolCheckList
'ubuntu', 'ubuntu',
'alpine', 'alpine',
'redhat', 'redhat',
'Deepin',
'debian' => CheckResult::fail(implode(', ', $missing) . ' not installed on your system', 'install-linux-tools', [$distro, $missing]), 'debian' => CheckResult::fail(implode(', ', $missing) . ' not installed on your system', 'install-linux-tools', [$distro, $missing]),
default => CheckResult::fail(implode(', ', $missing) . ' not installed on your system'), default => CheckResult::fail(implode(', ', $missing) . ' not installed on your system'),
}; };
@@ -70,7 +71,7 @@ class LinuxToolCheckList
} }
/** @noinspection PhpUnused */ /** @noinspection PhpUnused */
#[AsCheckItem('if necessary packages are installed', limit_os: 'Linux')] #[AsCheckItem('if necessary linux headers are installed', limit_os: 'Linux')]
public function checkSystemOSPackages(): ?CheckResult public function checkSystemOSPackages(): ?CheckResult
{ {
if (SystemUtil::isMuslDist()) { if (SystemUtil::isMuslDist()) {
@@ -90,7 +91,7 @@ class LinuxToolCheckList
public function fixBuildTools(array $distro, array $missing): bool public function fixBuildTools(array $distro, array $missing): bool
{ {
$install_cmd = match ($distro['dist']) { $install_cmd = match ($distro['dist']) {
'ubuntu', 'debian' => 'apt-get install -y', 'ubuntu', 'debian', 'Deepin' => 'apt-get install -y',
'alpine' => 'apk add', 'alpine' => 'apk add',
'redhat' => 'dnf install -y', 'redhat' => 'dnf install -y',
default => throw new RuntimeException('Current linux distro does not have an auto-install script for musl packages yet.'), default => throw new RuntimeException('Current linux distro does not have an auto-install script for musl packages yet.'),
@@ -101,7 +102,7 @@ class LinuxToolCheckList
logger()->warning('Current user is not root, using sudo for running command'); logger()->warning('Current user is not root, using sudo for running command');
} }
try { try {
$is_debian = in_array($distro['dist'], ['debian', 'ubuntu']); $is_debian = in_array($distro['dist'], ['debian', 'ubuntu', 'Deepin']);
$to_install = $is_debian ? str_replace('xz', 'xz-utils', $missing) : $missing; $to_install = $is_debian ? str_replace('xz', 'xz-utils', $missing) : $missing;
// debian, alpine libtool -> libtoolize // debian, alpine libtool -> libtoolize
$to_install = str_replace('libtoolize', 'libtool', $to_install); $to_install = str_replace('libtoolize', 'libtool', $to_install);

View File

@@ -13,13 +13,14 @@ class OSCheckList
{ {
use UnixSystemUtilTrait; use UnixSystemUtilTrait;
#[AsCheckItem('if current OS are supported', level: 999)] #[AsCheckItem('if current OS are supported', level: 1000)]
public function checkOS(): ?CheckResult public function checkOS(): ?CheckResult
{ {
if (!in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD', 'Windows'])) { if (!in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD', 'Windows'])) {
return CheckResult::fail('Current OS is not supported: ' . PHP_OS_FAMILY); return CheckResult::fail('Current OS is not supported: ' . PHP_OS_FAMILY);
} }
$distro = PHP_OS_FAMILY === 'Linux' ? (' ' . SystemUtil::getOSRelease()['dist']) : ''; $distro = PHP_OS_FAMILY === 'Linux' ? (' ' . SystemUtil::getOSRelease()['dist']) : '';
return CheckResult::ok(PHP_OS_FAMILY . ' ' . php_uname('m') . $distro . ', supported'); $known_distro = PHP_OS_FAMILY === 'Linux' && in_array(SystemUtil::getOSRelease()['dist'], SystemUtil::getSupportedDistros());
return CheckResult::ok(PHP_OS_FAMILY . ' ' . php_uname('m') . $distro . ', supported' . ($known_distro ? '' : ' (but not tested on this distro)'));
} }
} }

View File

@@ -9,10 +9,22 @@ 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;
class WindowsToolCheckList class WindowsToolCheckList
{ {
#[AsCheckItem('if git are installed', limit_os: 'Windows', level: 999)] #[AsCheckItem('if Visual Studio are installed', limit_os: 'Windows', level: 999)]
public function checkVS(): ?CheckResult
{
$vs_ver = SystemUtil::findVisualStudio();
if ($vs_ver === false) {
return CheckResult::fail('Visual Studio not installed, please install VS 2022/2019.');
}
return CheckResult::ok($vs_ver['version'] . ' ' . $vs_ver['dir']);
}
#[AsCheckItem('if git are installed', limit_os: 'Windows', level: 998)]
public function checkGit(): ?CheckResult public function checkGit(): ?CheckResult
{ {
if (SystemUtil::findCommand('git.exe') === null) { if (SystemUtil::findCommand('git.exe') === null) {
@@ -22,7 +34,7 @@ class WindowsToolCheckList
return CheckResult::ok(); return CheckResult::ok();
} }
#[AsCheckItem('if php-sdk-binary-tools are downloaded', limit_os: 'Windows', level: 998)] #[AsCheckItem('if php-sdk-binary-tools are downloaded', limit_os: 'Windows', level: 997)]
public function checkSDK(): ?CheckResult public function checkSDK(): ?CheckResult
{ {
if (!file_exists(PHP_SDK_PATH . DIRECTORY_SEPARATOR . 'phpsdk-starter.bat')) { if (!file_exists(PHP_SDK_PATH . DIRECTORY_SEPARATOR . 'phpsdk-starter.bat')) {
@@ -31,14 +43,80 @@ class WindowsToolCheckList
return CheckResult::ok(PHP_SDK_PATH); return CheckResult::ok(PHP_SDK_PATH);
} }
#[AsCheckItem('if git associated command exists', limit_os: 'Windows', level: 996)]
public function checkGitPatch(): ?CheckResult
{
if (($path = SystemUtil::findCommand('patch.exe')) === null) {
return CheckResult::fail('Git patch (minGW command) not found in path. You need to add "C:\Program Files\Git\usr\bin" in Path.');
}
return CheckResult::ok();
}
#[AsCheckItem('if nasm installed', limit_os: 'Windows', level: 995)]
public function checkNasm(): ?CheckResult
{
if (SystemUtil::findCommand('nasm.exe', include_sdk_bin: true) === null) {
return CheckResult::fail('nasm.exe not found in path.', 'install-nasm');
}
return CheckResult::ok();
}
#[AsCheckItem('if perl(strawberry) installed', limit_os: 'Windows', level: 994)]
public function checkPerl(): ?CheckResult
{
if (file_exists(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe')) {
return CheckResult::ok(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe');
}
if (($path = SystemUtil::findCommand('perl.exe')) === null) {
return CheckResult::fail('perl not found in path.', 'install-perl');
}
if (!str_contains(implode('', cmd()->execWithResult(quote($path) . ' -v')[1]), 'MSWin32')) {
return CheckResult::fail($path . ' is not built for msvc.', 'install-perl');
}
return CheckResult::ok();
}
#[AsFixItem('install-php-sdk')] #[AsFixItem('install-php-sdk')]
public function installPhpSdk(): bool public function installPhpSdk(): bool
{ {
try { try {
cmd(true)->exec('git clone https://github.com/php/php-sdk-binary-tools.git ' . PHP_SDK_PATH); FileSystem::removeDir(PHP_SDK_PATH);
cmd(true)->exec('git.exe clone --depth 1 https://github.com/php/php-sdk-binary-tools.git ' . PHP_SDK_PATH);
} catch (RuntimeException) { } catch (RuntimeException) {
return false; return false;
} }
return true; return true;
} }
#[AsFixItem('install-nasm')]
public function installNasm(): bool
{
// The hardcoded version here is to be consistent with the version compiled by `musl-cross-toolchain`.
$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;
}
#[AsFixItem('install-perl')]
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';
$source = [
'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;
}
} }

View File

@@ -168,14 +168,15 @@ class Downloader
public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null): void public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null): void
{ {
logger()->debug("Downloading {$url}"); logger()->debug("Downloading {$url}");
pcntl_signal(SIGINT, function () use ($filename) { $cancel_func = function () use ($filename) {
if (file_exists(DOWNLOAD_PATH . '/' . $filename)) { if (file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/' . $filename))) {
logger()->warning('Deleting download file: ' . $filename); logger()->warning('Deleting download file: ' . $filename);
unlink(DOWNLOAD_PATH . '/' . $filename); unlink(FileSystem::convertPath(DOWNLOAD_PATH . '/' . $filename));
} }
}); };
self::curlDown(url: $url, path: DOWNLOAD_PATH . "/{$filename}"); self::registerCancelEvent($cancel_func);
pcntl_signal(SIGINT, SIG_IGN); self::curlDown(url: $url, path: FileSystem::convertPath(DOWNLOAD_PATH . "/{$filename}"));
self::unregisterCancelEvent();
logger()->debug("Locking {$filename}"); logger()->debug("Locking {$filename}");
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path]); self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path]);
} }
@@ -187,7 +188,7 @@ class Downloader
*/ */
public static function lockSource(string $name, array $data): void public static function lockSource(string $name, array $data): void
{ {
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) { if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.lock.json'))) {
$lock = []; $lock = [];
} else { } else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
@@ -204,24 +205,25 @@ class Downloader
*/ */
public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null): void public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null): void
{ {
$download_path = DOWNLOAD_PATH . "/{$name}"; $download_path = FileSystem::convertPath(DOWNLOAD_PATH . "/{$name}");
if (file_exists($download_path)) { if (file_exists($download_path)) {
FileSystem::removeDir($download_path); FileSystem::removeDir($download_path);
} }
logger()->debug("cloning {$name} source"); logger()->debug("cloning {$name} source");
$check = !defined('DEBUG_MODE') ? ' -q' : ''; $check = !defined('DEBUG_MODE') ? ' -q' : '';
pcntl_signal(SIGINT, function () use ($download_path) { $cancel_func = function () use ($download_path) {
if (is_dir($download_path)) { if (is_dir($download_path)) {
logger()->warning('Removing path ' . $download_path); logger()->warning('Removing path ' . $download_path);
FileSystem::removeDir($download_path); FileSystem::removeDir($download_path);
} }
}); };
self::registerCancelEvent($cancel_func);
f_passthru( f_passthru(
'git clone' . $check . SPC_GIT_EXEC . ' clone' . $check .
' --config core.autocrlf=false ' . ' --config core.autocrlf=false ' .
"--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\"" "--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\""
); );
pcntl_signal(SIGINT, SIG_IGN); self::unregisterCancelEvent();
// Lock // Lock
logger()->debug("Locking git source {$name}"); logger()->debug("Locking git source {$name}");
@@ -251,7 +253,6 @@ class Downloader
* @param null|array $source source meta info: [type, path, rev, url, filename, regex, license] * @param null|array $source source meta info: [type, path, rev, url, filename, regex, license]
* @throws DownloaderException * @throws DownloaderException
* @throws FileSystemException * @throws FileSystemException
* @throws RuntimeException
*/ */
public static function downloadSource(string $name, ?array $source = null, bool $force = false): void public static function downloadSource(string $name, ?array $source = null, bool $force = false): void
{ {
@@ -359,12 +360,12 @@ class Downloader
}; };
$headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers)); $headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$cmd = "curl -sfSL {$methodArg} {$headerArg} \"{$url}\""; $cmd = SPC_CURL_EXEC . " -sfSL {$methodArg} {$headerArg} \"{$url}\"";
if (getenv('CACHE_API_EXEC') === 'yes') { if (getenv('CACHE_API_EXEC') === 'yes') {
if (!file_exists(DOWNLOAD_PATH . '/.curl_exec_cache')) { if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.curl_exec_cache'))) {
$cache = []; $cache = [];
} else { } else {
$cache = json_decode(file_get_contents(DOWNLOAD_PATH . '/.curl_exec_cache'), true); $cache = json_decode(file_get_contents(FileSystem::convertPath(DOWNLOAD_PATH . '/.curl_exec_cache')), true);
} }
if (isset($cache[$cmd]) && $cache[$cmd]['expire'] >= time()) { if (isset($cache[$cmd]) && $cache[$cmd]['expire'] >= time()) {
return $cache[$cmd]['cache']; return $cache[$cmd]['cache'];
@@ -375,7 +376,7 @@ class Downloader
} }
$cache[$cmd]['cache'] = implode("\n", $output); $cache[$cmd]['cache'] = implode("\n", $output);
$cache[$cmd]['expire'] = time() + 3600; $cache[$cmd]['expire'] = time() + 3600;
file_put_contents(DOWNLOAD_PATH . '/.curl_exec_cache', json_encode($cache)); file_put_contents(FileSystem::convertPath(DOWNLOAD_PATH . '/.curl_exec_cache'), json_encode($cache));
return $cache[$cmd]['cache']; return $cache[$cmd]['cache'];
} }
f_exec($cmd, $output, $ret); f_exec($cmd, $output, $ret);
@@ -404,7 +405,35 @@ class Downloader
}; };
$headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers)); $headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$check = !defined('DEBUG_MODE') ? 's' : '#'; $check = !defined('DEBUG_MODE') ? 's' : '#';
$cmd = "curl -{$check}fSL -o \"{$path}\" {$methodArg} {$headerArg} \"{$url}\""; $cmd = SPC_CURL_EXEC . " -{$check}fSL -o \"{$path}\" {$methodArg} {$headerArg} \"{$url}\"";
f_passthru($cmd); f_passthru($cmd);
} }
/**
* Register CTRL+C event for different OS.
*
* @param callable $callback callback function
*/
private static function registerCancelEvent(callable $callback): void
{
if (PHP_OS_FAMILY === 'Windows') {
sapi_windows_set_ctrl_handler($callback);
} elseif (extension_loaded('pcntl')) {
pcntl_signal(SIGINT, $callback);
} else {
logger()->debug('You have not enabled `pcntl` extension, cannot prevent download file corruption when Ctrl+C');
}
}
/**
* Unegister CTRL+C event for different OS.
*/
private static function unregisterCancelEvent(): void
{
if (PHP_OS_FAMILY === 'Windows') {
sapi_windows_set_ctrl_handler(null);
} elseif (extension_loaded('pcntl')) {
pcntl_signal(SIGINT, SIG_IGN);
}
}
} }

View File

@@ -14,13 +14,13 @@ class FileSystem
/** /**
* @throws FileSystemException * @throws FileSystemException
*/ */
public static function loadConfigArray(string $config): array public static function loadConfigArray(string $config, ?string $config_dir = null): array
{ {
$whitelist = ['ext', 'lib', 'source']; $whitelist = ['ext', 'lib', 'source'];
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');
} }
$tries = [ $tries = $config_dir !== null ? [FileSystem::convertPath($config_dir . '/' . $config . '.json')] : [
WORKING_DIR . '/config/' . $config . '.json', WORKING_DIR . '/config/' . $config . '.json',
ROOT_DIR . '/config/' . $config . '.json', ROOT_DIR . '/config/' . $config . '.json',
]; ];
@@ -160,78 +160,35 @@ class FileSystem
} }
logger()->info("extracting {$name} source to " . ($move_path ?? SOURCE_PATH . "/{$name}") . ' ...'); logger()->info("extracting {$name} source to " . ($move_path ?? SOURCE_PATH . "/{$name}") . ' ...');
try { try {
$target = $move_path ?? (SOURCE_PATH . "/{$name}"); $target = self::convertPath($move_path ?? (SOURCE_PATH . "/{$name}"));
// Git source, just move // Git source, just move
if (is_dir($filename)) { if (is_dir(self::convertPath($filename))) {
self::copyDir($filename, $target); self::copyDir(self::convertPath($filename), $target);
self::emitSourceExtractHook($name); self::emitSourceExtractHook($name);
return; 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'])) { if (in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD'])) {
if (f_mkdir(directory: $target, recursive: true) !== true) { match (self::extname($filename)) {
throw new FileSystemException('create ' . $name . 'source dir failed'); '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"),
switch (self::extname($filename)) { 'bz2' => f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1"),
case 'xz': 'zip' => f_passthru("unzip {$filename} -d {$target}"),
case 'txz': default => throw new FileSystemException('unknown archive format: ' . $filename),
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"); };
// f_passthru("cat {$filename} | xz -d | tar -x -C " . SOURCE_PATH . "/{$name} --strip-components 1");
break;
case 'gz':
case 'tgz':
f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1");
break;
case 'bz2':
f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1");
break;
case 'zip':
f_passthru("unzip {$filename} -d {$target}");
break;
// case 'zstd':
// case 'zst':
// passthru('cat ' . $filename . ' | zstd -d | tar -x -C ".SOURCE_PATH . "/' . $name . ' --strip-components 1', $ret);
// break;
case 'tar':
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
break;
default:
throw new FileSystemException('unknown archive format: ' . $filename);
}
} elseif (PHP_OS_FAMILY === 'Windows') { } elseif (PHP_OS_FAMILY === 'Windows') {
// find 7z // use php-sdk-binary-tools/bin/7za.exe
$_7zExe = self::findCommandPath('7z', [ $_7z = self::convertPath(PHP_SDK_PATH . '/bin/7za.exe');
'C:\Program Files\7-Zip-Zstandard',
'C:\Program Files (x86)\7-Zip-Zstandard',
'C:\Program Files\7-Zip',
'C:\Program Files (x86)\7-Zip',
]);
if (!$_7zExe) {
throw new FileSystemException('windows needs 7z to unpack');
}
f_mkdir(SOURCE_PATH . "/{$name}", recursive: true); f_mkdir(SOURCE_PATH . "/{$name}", recursive: true);
switch (self::extname($filename)) { match (self::extname($filename)) {
case 'zstd': 'tar' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
case 'zst': 'xz', 'txz', 'gz', 'tgz', 'bz2' => f_passthru("\"{$_7z}\" x -so {$filename} | tar -f - -x -C {$target} --strip-components 1"),
if (!str_contains($_7zExe, 'Zstandard')) { 'zip' => f_passthru("\"{$_7z}\" x {$filename} -o{$target} -y"),
throw new FileSystemException("zstd is not supported: {$filename}"); default => throw new FileSystemException("unknown archive format: {$filename}"),
} };
// no break
case 'xz':
case 'txz':
case 'gz':
case 'tgz':
case 'bz2':
f_passthru("\"{$_7zExe}\" x -so {$filename} | tar -f - -x -C {$target} --strip-components 1");
break;
case 'tar':
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
break;
case 'zip':
f_passthru("\"{$_7zExe}\" x {$filename} -o{$target}");
break;
default:
throw new FileSystemException("unknown archive format: {$filename}");
}
} }
self::emitSourceExtractHook($name); self::emitSourceExtractHook($name);
} catch (RuntimeException $e) { } catch (RuntimeException $e) {
@@ -398,7 +355,7 @@ class FileSystem
public static function createDir(string $path): void public static function createDir(string $path): void
{ {
if (!is_dir($path) && !f_mkdir($path, 0755, true) && !is_dir($path)) { if (!is_dir($path) && !f_mkdir($path, 0755, true) && !is_dir($path)) {
throw new FileSystemException(sprintf('无法建立目录:%s', $path)); throw new FileSystemException(sprintf('Unable to create dir: %s', $path));
} }
} }
@@ -408,7 +365,7 @@ class FileSystem
*/ */
public static function writeFile(string $path, mixed $content, ...$args): bool|int|string public static function writeFile(string $path, mixed $content, ...$args): bool|int|string
{ {
$dir = pathinfo($path, PATHINFO_DIRNAME); $dir = pathinfo(self::convertPath($path), PATHINFO_DIRNAME);
if (!is_dir($dir) && !mkdir($dir, 0755, true)) { if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
throw new FileSystemException('Write file failed, cannot create parent directory: ' . $dir); throw new FileSystemException('Write file failed, cannot create parent directory: ' . $dir);
} }

View File

@@ -6,6 +6,7 @@ namespace SPC\store;
use SPC\builder\BuilderBase; use SPC\builder\BuilderBase;
use SPC\builder\linux\LinuxBuilder; use SPC\builder\linux\LinuxBuilder;
use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\FileSystemException; use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
@@ -31,6 +32,20 @@ class SourcePatcher
logger()->info('Extension [' . $ext->getName() . '] patched before buildconf'); logger()->info('Extension [' . $ext->getName() . '] patched before buildconf');
} }
} }
// patch windows php 8.1 bug
if (PHP_OS_FAMILY === 'Windows' && $builder->getPHPVersionID() >= 80100 && $builder->getPHPVersionID() < 80200) {
logger()->info('Patching PHP 8.1 windows Fiber bug');
FileSystem::replaceFileStr(
SOURCE_PATH . '\php-src\win32\build\config.w32',
"ADD_FLAG('LDFLAGS', '$(BUILD_DIR)\\\\Zend\\\\jump_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj');",
"ADD_FLAG('ASM_OBJS', '$(BUILD_DIR)\\\\Zend\\\\jump_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj $(BUILD_DIR)\\\\Zend\\\\make_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj');"
);
FileSystem::replaceFileStr(
SOURCE_PATH . '\php-src\win32\build\config.w32',
"ADD_FLAG('LDFLAGS', '$(BUILD_DIR)\\\\Zend\\\\make_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj');",
''
);
}
} }
/** /**
@@ -181,9 +196,10 @@ class SourcePatcher
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_STRLCPY 1$/m', ''); FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_STRLCPY 1$/m', '');
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', '');
} }
FileSystem::replaceFileRegex(SOURCE_PATH . '/php-src/main/php_config.h', '/^#define HAVE_OPENPTY 1$/m', ''); 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', '');
}
// call extension patch before make // call extension patch before make
foreach ($builder->getExts() as $ext) { foreach ($builder->getExts() as $ext) {
@@ -252,4 +268,48 @@ class SourcePatcher
@unlink($embed_c_bak); @unlink($embed_c_bak);
return $result; return $result;
} }
/**
* Patch cli SAPI Makefile for Windows.
*
* @throws FileSystemException
* @throws RuntimeException
*/
public static function patchWindowsCLITarget(): void
{
// search Makefile code line contains "$(BUILD_DIR)\php.exe:"
$content = FileSystem::readFile(SOURCE_PATH . '/php-src/Makefile');
$lines = explode("\r\n", $content);
$line_num = 0;
$found = false;
foreach ($lines as $v) {
if (strpos($v, '$(BUILD_DIR)\php.exe:') !== false) {
$found = $line_num;
break;
}
++$line_num;
}
if ($found === false) {
throw new RuntimeException('Cannot patch windows CLI Makefile!');
}
$lines[$line_num] = '$(BUILD_DIR)\php.exe: generated_files $(DEPS_CLI) $(PHP_GLOBAL_OBJS) $(CLI_GLOBAL_OBJS) $(STATIC_EXT_OBJS) $(ASM_OBJS) $(BUILD_DIR)\php.exe.res $(BUILD_DIR)\php.exe.manifest';
$lines[$line_num + 1] = "\t" . '"$(LINK)" /nologo $(PHP_GLOBAL_OBJS_RESP) $(CLI_GLOBAL_OBJS_RESP) $(STATIC_EXT_OBJS_RESP) $(STATIC_EXT_LIBS) $(ASM_OBJS) $(LIBS) $(LIBS_CLI) $(BUILD_DIR)\php.exe.res /out:$(BUILD_DIR)\php.exe $(LDFLAGS) $(LDFLAGS_CLI) /ltcg /nodefaultlib:msvcrt /nodefaultlib:msvcrtd /ignore:4286';
FileSystem::writeFile(SOURCE_PATH . '/php-src/Makefile', implode("\r\n", $lines));
}
/**
* Add additional `static-php-cli.version` ini value for PHP source.
*
* @throws FileSystemException
*/
public static function patchSPCVersionToPHP(string $version = 'unknown'): void
{
// detect patch
$file = FileSystem::readFile(SOURCE_PATH . '/php-src/main/main.c');
if (!str_contains($file, 'static-php-cli.version')) {
logger()->debug('Inserting static-php-cli.version to php-src');
$file = str_replace('PHP_INI_BEGIN()', "PHP_INI_BEGIN()\n\tPHP_INI_ENTRY(\"static-php-cli.version\",\t\"{$version}\",\tPHP_INI_ALL,\tNULL)", $file);
FileSystem::writeFile(SOURCE_PATH . '/php-src/main/main.c', $file);
}
}
} }

View File

@@ -50,6 +50,11 @@ class WindowsCmd
return $this; return $this;
} }
public function execWithWrapper(string $wrapper, string $args): WindowsCmd
{
return $this->exec($wrapper . ' "' . str_replace('"', '^"', $args) . '"');
}
public function execWithResult(string $cmd, bool $with_log = true): array public function execWithResult(string $cmd, bool $with_log = true): array
{ {
if ($with_log) { if ($with_log) {

View File

@@ -2,6 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
use SPC\store\FileSystem;
use ZM\Logger\ConsoleLogger; use ZM\Logger\ConsoleLogger;
define('WORKING_DIR', getcwd()); define('WORKING_DIR', getcwd());
@@ -10,12 +11,12 @@ define('ROOT_DIR', dirname(__DIR__, 2));
// CLI start time // CLI start time
define('START_TIME', microtime(true)); define('START_TIME', microtime(true));
define('BUILD_ROOT_PATH', 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', 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', 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('BUILD_BIN_PATH', 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', 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', 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')));
define('SEPARATED_PATH', [ define('SEPARATED_PATH', [
'/' . pathinfo(BUILD_LIB_PATH)['basename'], // lib '/' . pathinfo(BUILD_LIB_PATH)['basename'], // lib
'/' . pathinfo(BUILD_INCLUDE_PATH)['basename'], // include '/' . pathinfo(BUILD_INCLUDE_PATH)['basename'], // include
@@ -26,6 +27,10 @@ if (PHP_OS_FAMILY === 'Windows') {
define('PHP_SDK_PATH', is_string($a = getenv('PHP_SDK_PATH')) ? $a : (WORKING_DIR . DIRECTORY_SEPARATOR . 'php-sdk-binary-tools')); define('PHP_SDK_PATH', is_string($a = getenv('PHP_SDK_PATH')) ? $a : (WORKING_DIR . DIRECTORY_SEPARATOR . 'php-sdk-binary-tools'));
} }
// for windows, prevent calling Invoke-WebRequest and wsl command
const SPC_CURL_EXEC = PHP_OS_FAMILY === 'Windows' ? 'curl.exe' : 'curl';
const SPC_GIT_EXEC = PHP_OS_FAMILY === 'Windows' ? 'git.exe' : 'git';
// dangerous command // dangerous command
const DANGER_CMD = [ const DANGER_CMD = [
'rm', 'rm',

View File

@@ -3,6 +3,8 @@
declare(strict_types=1); declare(strict_types=1);
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use SPC\builder\BuilderBase;
use SPC\builder\BuilderProvider;
use SPC\exception\RuntimeException; use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException; use SPC\exception\WrongUsageException;
use SPC\util\UnixShell; use SPC\util\UnixShell;
@@ -127,3 +129,23 @@ function cmd(?bool $debug = null): WindowsCmd
{ {
return new WindowsCmd($debug); return new WindowsCmd($debug);
} }
/**
* Get current builder.
*
* @throws WrongUsageException
*/
function builder(): BuilderBase
{
return BuilderProvider::getBuilder();
}
/**
* Get current patch point.
*
* @throws WrongUsageException
*/
function patch_point(): string
{
return BuilderProvider::getBuilder()->getPatchPoint();
}

View File

@@ -1,5 +1,7 @@
<?php <?php
/** @noinspection ALL */
declare(strict_types=1); declare(strict_types=1);
/** /**
@@ -10,14 +12,24 @@ declare(strict_types=1);
// --------------------------------- edit area --------------------------------- // --------------------------------- edit area ---------------------------------
// 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 = 'ldap'; $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',
'Windows' => 'mbstring,openssl',
};
// 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`).
$with_libs = ''; $with_libs = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => '',
'Windows' => '',
};
// Please change your test base combination. We recommend testing with `common`. // Please change your test base combination. We recommend testing with `common`.
// You can use `common`, `bulk`, `minimal` or `none`. // You can use `common`, `bulk`, `minimal` or `none`.
$base_combination = 'minimal'; // note: combination is only available for *nix platform. Windows must use `none` combination
$base_combination = match (PHP_OS_FAMILY) {
'Linux', 'Darwin' => 'none',
'Windows' => 'none',
};
// -------------------------- code area, do not modify -------------------------- // -------------------------- code area, do not modify --------------------------
@@ -48,9 +60,16 @@ $trim_value = "\r\n \t,";
$final_extensions = trim(trim($extensions, $trim_value) . ',' . _getCombination($base_combination), $trim_value); $final_extensions = trim(trim($extensions, $trim_value) . ',' . _getCombination($base_combination), $trim_value);
$final_libs = trim($with_libs, $trim_value); $final_libs = trim($with_libs, $trim_value);
if (PHP_OS_FAMILY === 'Windows') {
$final_extensions_cmd = '"' . $final_extensions . '"';
} else {
$final_extensions_cmd = $final_extensions;
}
echo match ($argv[1]) { echo match ($argv[1]) {
'extensions' => $final_extensions, 'extensions' => $final_extensions,
'libs' => $final_libs, 'libs' => $final_libs,
'cmd' => $final_extensions . ($final_libs === '' ? '' : (' --with-libs=' . $final_libs)), 'libs_cmd' => ($final_libs === '' ? '' : (' --with-libs="' . $final_libs . '"')),
'cmd' => $final_extensions_cmd . ($final_libs === '' ? '' : (' --with-libs="' . $final_libs . '"')),
default => '', default => '',
}; };

View File

@@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
assert(function_exists('openssl_digest'));
assert(openssl_digest('123456', 'md5') === 'e10adc3949ba59abbe56e057f20f883e');

View File

@@ -3,3 +3,4 @@
declare(strict_types=1); declare(strict_types=1);
assert(function_exists('gzcompress')); assert(function_exists('gzcompress'));
assert(gzdecode(gzencode('aaa')) === 'aaa');

View File

@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace SPC\Tests\globals;
use PHPUnit\Framework\TestCase;
use Psr\Log\LogLevel;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use ZM\Logger\ConsoleLogger;
/**
* @internal
*/
class GlobalFunctionsTest extends TestCase
{
private static $logger_cache;
public static function setUpBeforeClass(): void
{
global $ob_logger;
self::$logger_cache = $ob_logger;
$ob_logger = new ConsoleLogger(LogLevel::ALERT);
}
public static function tearDownAfterClass(): void
{
global $ob_logger;
$ob_logger = self::$logger_cache;
}
public function testIsAssocArray(): void
{
$this->assertTrue(is_assoc_array(['a' => 1, 'b' => 2]));
$this->assertFalse(is_assoc_array([1, 2, 3]));
}
public function testLogger(): void
{
$this->assertInstanceOf('Psr\Log\LoggerInterface', logger());
}
/**
* @throws WrongUsageException
*/
public function testArch2Gnu(): void
{
$this->assertEquals('x86_64', arch2gnu('x86_64'));
$this->assertEquals('x86_64', arch2gnu('x64'));
$this->assertEquals('x86_64', arch2gnu('amd64'));
$this->assertEquals('aarch64', arch2gnu('arm64'));
$this->assertEquals('aarch64', arch2gnu('aarch64'));
$this->expectException('SPC\exception\WrongUsageException');
arch2gnu('armv7');
}
public function testQuote(): void
{
$this->assertEquals('"hello"', quote('hello'));
$this->assertEquals("'hello'", quote('hello', "'"));
}
/**
* @throws RuntimeException
*/
public function testFPassthru(): void
{
if (PHP_OS_FAMILY === 'Windows') {
$this->markTestSkipped('Windows not support f_passthru');
}
$this->assertEquals(null, f_passthru('echo ""'));
$this->expectException('SPC\exception\RuntimeException');
f_passthru('false');
}
public function testFPutenv(): void
{
$this->assertTrue(f_putenv('SPC_TEST_ENV=1'));
$this->assertEquals('1', getenv('SPC_TEST_ENV'));
}
public function testShell(): void
{
if (PHP_OS_FAMILY === 'Windows') {
$this->markTestSkipped('Windows not support shell');
}
$shell = shell();
$this->assertInstanceOf('SPC\util\UnixShell', $shell);
$this->assertInstanceOf('SPC\util\UnixShell', $shell->cd('/'));
$this->assertInstanceOf('SPC\util\UnixShell', $shell->exec('echo ""'));
$this->assertInstanceOf('SPC\util\UnixShell', $shell->setEnv(['SPC_TEST_ENV' => '1']));
[$code, $out] = $shell->execWithResult('echo "_"');
$this->assertEquals(0, $code);
$this->assertEquals('_', implode('', $out));
$this->expectException('SPC\exception\RuntimeException');
$shell->exec('false');
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace SPC\Tests\store;
use PHPUnit\Framework\TestCase;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
/**
* @internal
*/
class ConfigTest extends TestCase
{
/**
* @throws FileSystemException
*/
public static function setUpBeforeClass(): void
{
$testdir = WORKING_DIR . '/.configtest';
FileSystem::createDir($testdir);
FileSystem::writeFile($testdir . '/lib.json', file_get_contents(ROOT_DIR . '/config/lib.json'));
FileSystem::writeFile($testdir . '/ext.json', file_get_contents(ROOT_DIR . '/config/ext.json'));
FileSystem::writeFile($testdir . '/source.json', file_get_contents(ROOT_DIR . '/config/source.json'));
FileSystem::loadConfigArray('lib', $testdir);
FileSystem::loadConfigArray('ext', $testdir);
FileSystem::loadConfigArray('source', $testdir);
}
/**
* @throws FileSystemException
*/
public static function tearDownAfterClass(): void
{
FileSystem::removeDir(WORKING_DIR . '/.configtest');
}
/**
* @throws FileSystemException
*/
public function testGetExts()
{
$this->assertTrue(is_assoc_array(Config::getExts()));
}
/**
* @throws FileSystemException
* @throws WrongUsageException
*/
public function testGetLib()
{
$this->assertIsArray(Config::getLib('zlib'));
match (PHP_OS_FAMILY) {
'FreeBSD', 'Darwin', 'Linux' => $this->assertStringEndsWith('.a', Config::getLib('zlib', 'static-libs', [])[0]),
'Windows' => $this->assertStringEndsWith('.lib', Config::getLib('zlib', 'static-libs', [])[0]),
default => null,
};
}
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public function testGetExt()
{
$this->assertIsArray(Config::getExt('bcmath'));
$this->assertEquals('builtin', Config::getExt('bcmath', 'type'));
}
/**
* @throws FileSystemException
*/
public function testGetSources()
{
$this->assertTrue(is_assoc_array(Config::getSources()));
}
/**
* @throws FileSystemException
*/
public function testGetSource()
{
$this->assertIsArray(Config::getSource('php-src'));
}
/**
* @throws FileSystemException
*/
public function testGetLibs()
{
$this->assertTrue(is_assoc_array(Config::getLibs()));
}
}

View File

@@ -0,0 +1,213 @@
<?php
declare(strict_types=1);
namespace SPC\Tests\store;
use PHPUnit\Framework\TestCase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
/**
* @internal
*/
class FileSystemTest extends TestCase
{
private const TEST_FILE_CONTENT = 'Hello! Bye!';
public static function setUpBeforeClass(): void
{
if (file_put_contents(WORKING_DIR . '/.testfile', self::TEST_FILE_CONTENT) === false) {
static::markTestSkipped('Current environment or working dir is not writable!');
}
}
public static function tearDownAfterClass(): void
{
if (file_exists(WORKING_DIR . '/.testfile')) {
unlink(WORKING_DIR . '/.testfile');
}
}
/**
* @throws FileSystemException
*/
public function testReplaceFileRegex()
{
$file = WORKING_DIR . '/.txt1';
file_put_contents($file, 'hello');
FileSystem::replaceFileRegex($file, '/ll/', '11');
$this->assertEquals('he11o', file_get_contents($file));
unlink($file);
}
public function testFindCommandPath()
{
$this->assertNull(FileSystem::findCommandPath('randomtestxxxxx'));
if (PHP_OS_FAMILY === 'Windows') {
$this->assertIsString(FileSystem::findCommandPath('explorer'));
} elseif (in_array(PHP_OS_FAMILY, ['Linux', 'Darwin', 'FreeBSD'])) {
$this->assertIsString(FileSystem::findCommandPath('uname'));
}
}
/**
* @throws FileSystemException
*/
public function testReadFile()
{
$file = WORKING_DIR . '/.testread';
file_put_contents($file, 'haha');
$content = FileSystem::readFile($file);
$this->assertEquals('haha', $content);
@unlink($file);
}
/**
* @throws FileSystemException
*/
public function testReplaceFileUser()
{
$file = WORKING_DIR . '/.txt1';
file_put_contents($file, 'hello');
FileSystem::replaceFileUser($file, function ($file) {
return str_replace('el', '55', $file);
});
$this->assertEquals('h55lo', file_get_contents($file));
unlink($file);
}
public function testExtname()
{
$this->assertEquals('exe', FileSystem::extname('/tmp/asd.exe'));
$this->assertEquals('', FileSystem::extname('/tmp/asd.'));
}
/**
* @throws \ReflectionException
* @throws FileSystemException
*/
public function testGetClassesPsr4()
{
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/builder/extension', 'SPC\\builder\\extension');
foreach ($classes as $class) {
$this->assertIsString($class);
new \ReflectionClass($class);
}
}
public function testConvertPath()
{
$this->assertEquals('phar://C:/pharfile.phar', FileSystem::convertPath('phar://C:/pharfile.phar'));
if (DIRECTORY_SEPARATOR === '\\') {
$this->assertEquals('C:\Windows\win.ini', FileSystem::convertPath('C:\Windows/win.ini'));
}
}
/**
* @throws FileSystemException
*/
public function testCreateDir()
{
FileSystem::createDir(WORKING_DIR . '/.testdir');
$this->assertDirectoryExists(WORKING_DIR . '/.testdir');
rmdir(WORKING_DIR . '/.testdir');
}
/**
* @throws FileSystemException
*/
public function testReplaceFileStr()
{
$file = WORKING_DIR . '/.txt1';
file_put_contents($file, 'hello');
FileSystem::replaceFileStr($file, 'el', '55');
$this->assertEquals('h55lo', file_get_contents($file));
unlink($file);
}
/**
* @throws FileSystemException
*/
public function testResetDir()
{
// prepare fake git dir to test
FileSystem::createDir(WORKING_DIR . '/.fake_down_test');
FileSystem::writeFile(WORKING_DIR . '/.fake_down_test/a.c', 'int main() { return 0; }');
FileSystem::resetDir(WORKING_DIR . '/.fake_down_test');
$this->assertFileDoesNotExist(WORKING_DIR . '/.fake_down_test/a.c');
FileSystem::removeDir(WORKING_DIR . '/.fake_down_test');
}
/**
* @throws FileSystemException
* @throws RuntimeException
*/
public function testCopyDir()
{
// prepare fake git dir to test
FileSystem::createDir(WORKING_DIR . '/.fake_down_test');
FileSystem::writeFile(WORKING_DIR . '/.fake_down_test/a.c', 'int main() { return 0; }');
FileSystem::copyDir(WORKING_DIR . '/.fake_down_test', WORKING_DIR . '/.fake_down_test2');
$this->assertDirectoryExists(WORKING_DIR . '/.fake_down_test2');
$this->assertFileExists(WORKING_DIR . '/.fake_down_test2/a.c');
FileSystem::removeDir(WORKING_DIR . '/.fake_down_test');
FileSystem::removeDir(WORKING_DIR . '/.fake_down_test2');
}
/**
* @throws FileSystemException
*/
public function testRemoveDir()
{
FileSystem::createDir(WORKING_DIR . '/.fake_down_test');
$this->assertDirectoryExists(WORKING_DIR . '/.fake_down_test');
FileSystem::removeDir(WORKING_DIR . '/.fake_down_test');
$this->assertDirectoryDoesNotExist(WORKING_DIR . '/.fake_down_test');
}
/**
* @throws FileSystemException
*/
public function testLoadConfigArray()
{
$arr = FileSystem::loadConfigArray('lib');
$this->assertArrayHasKey('zlib', $arr);
}
public function testIsRelativePath()
{
$this->assertTrue(FileSystem::isRelativePath('.'));
$this->assertTrue(FileSystem::isRelativePath('.\\sdf'));
if (DIRECTORY_SEPARATOR === '\\') {
$this->assertFalse(FileSystem::isRelativePath('C:\\asdasd/fwe\asd'));
} else {
$this->assertFalse(FileSystem::isRelativePath('/fwefwefewf'));
}
}
public function testScanDirFiles()
{
$this->assertFalse(FileSystem::scanDirFiles('wfwefewfewf'));
$files = FileSystem::scanDirFiles(ROOT_DIR . '/config', true, true);
$this->assertContains('lib.json', $files);
}
/**
* @throws FileSystemException
*/
public function testWriteFile()
{
FileSystem::writeFile(WORKING_DIR . '/.txt', 'txt');
$this->assertFileExists(WORKING_DIR . '/.txt');
$this->assertEquals('txt', FileSystem::readFile(WORKING_DIR . '/.txt'));
unlink(WORKING_DIR . '/.txt');
}
}

View File

@@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace SPC\Tests\util;
use PHPUnit\Framework\TestCase;
use SPC\exception\ValidationException;
use SPC\util\ConfigValidator;
/**
* @internal
*/
class ConfigValidatorTest extends TestCase
{
public function testValidateSourceGood(): void
{
$good_source = [
'source1' => [
'type' => 'filelist',
'url' => 'https://example.com',
'regex' => '.*',
],
'source2' => [
'type' => 'git',
'url' => 'https://example.com',
'rev' => 'master',
],
'source3' => [
'type' => 'ghtagtar',
'repo' => 'aaaa/bbbb',
],
'source4' => [
'type' => 'ghtar',
'repo' => 'aaa/bbb',
'path' => 'path/to/dir',
],
'source5' => [
'type' => 'ghrel',
'repo' => 'aaa/bbb',
'match' => '.*',
],
'source6' => [
'type' => 'url',
'url' => 'https://example.com',
],
];
try {
ConfigValidator::validateSource($good_source);
$this->assertTrue(true);
} catch (ValidationException $e) {
$this->fail($e->getMessage());
}
}
public function testValidateSourceBad(): void
{
$bad_source = [
'source1' => [
'type' => 'filelist',
'url' => 'https://example.com',
// no regex
],
'source2' => [
'type' => 'git',
'url' => true, // not string
'rev' => 'master',
],
'source3' => [
'type' => 'ghtagtar',
'url' => 'aaaa/bbbb', // not repo
],
'source4' => [
'type' => 'ghtar',
'repo' => 'aaa/bbb',
'path' => true, // not string
],
'source5' => [
'type' => 'ghrel',
'repo' => 'aaa/bbb',
'match' => 1, // not string
],
'source6' => [
'type' => 'url', // no url
],
];
foreach ($bad_source as $name => $src) {
try {
ConfigValidator::validateSource([$name => $src]);
$this->fail("should throw ValidationException for source {$name}");
} catch (ValidationException) {
$this->assertTrue(true);
}
}
}
public function testValidateLibsGood(): void
{
$good_libs = [
'lib1' => [
'source' => 'source1',
],
'lib2' => [
'source' => 'source2',
'lib-depends' => [
'lib1',
],
],
'lib3' => [
'source' => 'source3',
'lib-suggests' => [
'lib1',
],
],
];
try {
ConfigValidator::validateLibs($good_libs, ['source1' => [], 'source2' => [], 'source3' => []]);
$this->assertTrue(true);
} catch (ValidationException $e) {
$this->fail($e->getMessage());
}
}
public function testValidateLibsBad(): void
{
// lib.json is broken
try {
ConfigValidator::validateLibs('not array');
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
// lib source not exists
try {
ConfigValidator::validateLibs(['lib1' => ['source' => 'source3']], ['source1' => [], 'source2' => []]);
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
// source must be string
try {
ConfigValidator::validateLibs(['lib1' => ['source' => true]], ['source1' => [], 'source2' => []]);
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
// lib-depends must be list
try {
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'lib-depends' => ['a' => 'not list']]], ['source1' => [], 'source2' => []]);
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
// lib-suggests must be list
try {
ConfigValidator::validateLibs(['lib1' => ['source' => 'source1', 'lib-suggests' => ['a' => 'not list']]], ['source1' => [], 'source2' => []]);
$this->fail('should throw ValidationException');
} catch (ValidationException) {
$this->assertTrue(true);
}
}
/**
* @throws ValidationException
*/
public function testValidateExts(): void
{
ConfigValidator::validateExts([]);
$this->expectException(ValidationException::class);
ConfigValidator::validateExts(null);
}
}

View File

@@ -13,7 +13,13 @@ use SPC\util\LicenseDumper;
*/ */
final class LicenseDumperTest extends TestCase final class LicenseDumperTest extends TestCase
{ {
private const DIRECTORY = '../../var/license-dump'; private const DIRECTORY = __DIR__ . '/../../var/license-dump';
public static function tearDownAfterClass(): void
{
@rmdir(self::DIRECTORY);
@rmdir(dirname(self::DIRECTORY));
}
protected function setUp(): void protected function setUp(): void
{ {