Compare commits

..

4 Commits

Author SHA1 Message Date
Jerry Ma
cecf204f7b Update README.md 2023-04-30 17:21:27 +08:00
Jerry
8391d3a60c Update README-en.md 2023-03-28 10:29:49 +08:00
Jerry
1a516a13dd Update README.md 2023-03-28 10:28:18 +08:00
Jerry
62bee67274 Update README.md 2023-03-18 17:48:46 +08:00
98 changed files with 1483 additions and 6186 deletions

67
.github/workflows/build-php.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Build PHP
on:
push:
branches: [ master ]
paths:
- ".github/workflows/**.yml"
- "docker/**"
jobs:
integration:
name: Build PHP ${{ matrix.php-versions }} for ${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: [ "7.4.30", "8.0.23", "8.1.10", "8.2.0" ]
arch: [ "x86_64", "aarch64", "armv7l" ]
steps:
- uses: actions/checkout@v2
- name: Check Dockerfile to ${{ matrix.arch }}
run: |
cd docker/
if [[ "${{ matrix.arch }}" != "x86_64" ]]; then
if [[ "${{ matrix.arch }}" = "armv7l" ]]; then
sed -ie 's/alpine:latest/multiarch\/alpine:armv7-latest-stable/g' Dockerfile
else
sed -ie 's/alpine:latest/multiarch\/alpine:${{ matrix.arch }}-v3.16/g' Dockerfile
fi
docker run --rm --privileged multiarch/qemu-user-static:register --reset
fi
- name: Build micro and PHP distribution ${{ matrix.php-versions }} for ${{ matrix.arch }}
id: buildphp
run: |
cd docker/ && docker build . --tag static-php --build-arg USE_BACKUP_ADDRESS=yes && \
mkdir ../dist && \
docker run --rm -v $(pwd)/../dist:/dist/ static-php build-php original ${{ matrix.php-versions }} all /dist/
- name: Fail if anything failed
if: steps.buildphp == 'failure'
run: |
false
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: static-php-cli_${{ matrix.php-versions }}_${{ matrix.arch }}
path: |
dist
- name: Pack PHP ${{ matrix.php-versions }} to archive
run: |
cd dist
tar -zcvf "php-${{ matrix.php-versions }}-static-bin-${{ matrix.arch }}.tar.gz" ./php && rm ./php
if [ -f "./micro.sfx" ]; then
tar -zcvf "micro-${{ matrix.php-versions }}-${{ matrix.arch }}.tar.gz" ./micro.sfx && rm ./micro.sfx
fi
- name: Deploy to Zhamao Server
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SERVER_SECRET_KEY }}
ARGS: "-rltgoDzvO"
SOURCE: "dist/"
REMOTE_HOST: ${{ secrets.DEPLOY_SERVER_HOST }}
REMOTE_PORT: ${{ secrets.DEPLOY_SERVER_PORT }}
REMOTE_USER: ${{ secrets.DEPLOY_SERVER_USER }}
TARGET: ${{ secrets.DEPLOY_SERVER_TARGET }}
- name: Remove dist directory
run: |
rm -rf dist/
docker images | grep -v REPOSITORY | awk '{print $3}' | xargs docker rmi --force

16
.gitignore vendored
View File

@@ -3,19 +3,3 @@ runtime/
docker/libraries/ docker/libraries/
docker/extensions/ docker/extensions/
docker/source/ docker/source/
# Composer file
composer.lock
/vendor/
# default source extract directory
/source/
# default source download directory
/downloads/
# default source build root directory
/buildroot/
# php cs fixer cache file
.php-cs-fixer.cache

View File

@@ -1,69 +0,0 @@
<?php
declare(strict_types=1);
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PSR12' => true,
'@Symfony' => true,
'@PhpCsFixer' => true,
'array_syntax' => [
'syntax' => 'short',
],
'list_syntax' => [
'syntax' => 'short',
],
'concat_space' => [
'spacing' => 'one',
],
'blank_line_before_statement' => [
'statements' => [
'declare',
],
],
'ordered_imports' => [
'imports_order' => [
'class',
'function',
'const',
],
'sort_algorithm' => 'alpha',
],
'single_line_comment_style' => [
'comment_types' => [
],
],
'yoda_style' => [
'always_move_variable' => false,
'equal' => false,
'identical' => false,
],
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'constant_case' => [
'case' => 'lower',
],
'class_attributes_separation' => true,
'combine_consecutive_unsets' => true,
'declare_strict_types' => true,
'linebreak_after_opening_tag' => true,
'lowercase_static_reference' => true,
'no_useless_else' => true,
'no_unused_imports' => true,
'not_operator_with_successor_space' => false,
'not_operator_with_space' => false,
'ordered_class_elements' => true,
'php_unit_strict' => false,
'phpdoc_separation' => false,
'single_quote' => true,
'standardize_not_equals' => true,
'multiline_comment_opening_closing' => true,
'phpdoc_summary' => false,
'php_unit_test_class_requires_covers' => false,
'phpdoc_var_without_name' => false,
])
->setFinder(
PhpCsFixer\Finder::create()->in(__DIR__ . '/src')
);

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2022-2023 Jerry Ma Copyright (c) 2022 Jerry Ma
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,69 +1,187 @@
# static-php-cli # static-php-cli
Compile A Statically Linked PHP With Swoole and other Extensions.
Compile a purely static PHP binary file with various extensions to make PHP-cli applications more portable! Compile A Statically Linked PHP With Swoole and other Popular Extensions.
You can also use the micro binary file to package PHP source code and binary files into one for distribution! Compile A Single Binary With PHP Code.
Note: only support cli SAPI, not support fpm, cgi. [![version](https://img.shields.io/badge/version-1.5.2-green.svg)]()
![Build Actions](https://github.com/crazywhalecc/static-php-cli/actions/workflows/build-php.yml/badge.svg)
## Refactoring
The current project is under reconstruction, and this branch is an older version.
The refactored version supports many new features. The following is the current progress of refactoring:
- **Discussionhttps://github.com/crazywhalecc/static-php-cli/discussions/29**
- **Branch[refactor](https://github.com/crazywhalecc/static-php-cli/tree/refactor)**
- **Bugs and TODOshttps://github.com/crazywhalecc/static-php-cli/issues/32**
## Compilation Requirements ## Compilation Requirements
Yes, this project is written in PHP, pretty funny. - Supporting architecture: `x86_64`, `arm64(aarch64)`, `armv7(armv7l)`
But php-static-cli only requires an environment above PHP 8.0. - Docker required (or alpine linux 3.13+)
- PHP version from 7.2 to 8.1
- Micro Package requires PHP >= 8.0
- Linux ## Runtime Requirements
- Supported arch: aarch64, amd64
- Supported distributions: alpine, ubuntu, centos
- Requirements: (TODO)
- macOS
- Supported arch: arm64, x86_64
- Requirements: make, bison, flex, pkg-config, git, autoconf, automake, tar, unzip, xz, gzip, bzip2, cmake
- Windows
- Supported arch: x86_64
- Requirements: (TODO)
- PHP
- Supported version: 8.0, 8.1, 8.2
## Usage (WIP) Linux
After stable release for this project, a single phar and single binary for this tool will be published. ## Usage
And currently you may need to clone this branch and edit GitHub Action to build. 1. Directly download static binary from this link or [Actions uploaded artifacts](https://github.com/crazywhalecc/static-php-cli/actions).
<https://dl.zhamao.xin/php-bin/file/>
2. Use fast install script `install-runtime.sh` to download static php and composer distribution into `runtime/` directory
But this script has some Chinese comments and prompts, if you cannot understand or have to use it in English, I will make an pure international version! :)
```bash ```bash
chmod +x spc bash -c "`curl -fsSL https://raw.githubusercontent.com/crazywhalecc/static-php-cli/master/install-runtime.sh`"
# fetch all libraries
./spc fetch --all
# with bcmath,openssl,swoole extension, build both CLI and phpmicro SAPI
./spc build "bcmath,openssl,swoole" --build-all
``` ```
## Current Status ## Packing PHP Code into a Static Binary
- [X] Basic CLI framework (by symfony/console) From v1.5.0, we support packing PHP code into a static binary. You can pack your PHP code into a static binary by micro.
- [ ] Linux support
- [X] macOS support
- [X] Exception handler
- [ ] Windows support
- [X] PHP 7.4 support
## Supported Extensions (WIP) You can directly download `micro-` prefix file, untar it and you will get file `micro.sfx`.
[Support Extension List](/ext-support.md) Here's a simple example to use it:
## Open-Source LICENSE ```bash
echo "<?php echo 'Hello world' . PHP_EOL;" > code.php
cat micro.sfx code.php > single-app && chmod +x single-app
./single-app
This project is based on the tradition of using the MIT License for old versions, # If packing phar into a static binary, just change code.php to your phar path.
while the new version references source code from some other projects. ```
Special thanks to: > Note: It means that your PHP code won't be compiled and you can't protect your source code by using micro!
>
> If you are looking for compiling PHP code or encrypting code, here's not your solution.
>
> Special thanks: <https://github.com/dixyes/phpmicro>
- [dixyes/lwmbs](https://github.com/dixyes/lwmbs) (Mulun Permissive License) ## Compiling
- [swoole/swoole-cli](https://github.com/swoole/swoole-cli) (Apache 2.0 LICENSE+SWOOLE-CLI LICENSE)
Due to the special nature of this project, Here's help command to compile it yourself:
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.
Please use the `dump-license`(TODO) command to export the open source licenses used in the project after compilation, ```bash
and comply with the corresponding project's LICENSE. git clone https://github.com/crazywhalecc/static-php-cli.git
cd static-php-cli/docker
docker build -t static-php . --build-arg USE_BACKUP_ADDRESS=yes
# Making a directory to put binary files
mkdir dist
# It will ask you for PHP version, extensions, and compile static binaries
docker run --rm -v $(pwd)/dist:/dist/ -it static-php build-php
```
After compilation you can use command to get static php binary file.
```bash
cd dist
file ./php
```
If you don't want to use docker, a single script for compiling in **Alpine Linux**:
```bash
cd docker
# Change PHP Version
export VER_PHP="8.1.7"
# Use Original download link (Default is China mainland mirror link, for others please use 'yes' for original link)
export USE_BACKUP="yes"
./fast-compiler.sh
```
To customize PHP extensions, edit `docker/extensions.txt` file, and rules below:
- Use `^` as deselect, to mark not install. Use `#` as comments.
- extensions name uses lower case, and default file contains all supported extensions, if u need other extensions, consider write an Issue
## Supported PHP extensions
| Support | PHP Ext Name | Version | Comments |
| ------- | ------------ | ------- | ---------------------------------------- |
| yes | bcmath | * | |
| yes | calendar | * | |
| yes | ctype | * | |
| yes | curl | * | |
| yes | dom | * | |
| yes | event | >=3.0.8 | author's bitbucket version, not pecl |
| yes | exif | * | |
| yes | filter | * | |
| yes | fileinfo | * | |
| yes | gd | * | |
| yes | hash | * | |
| yes | iconv | * | |
| yes | inotify | 3.0.0 | |
| yes | json | * | |
| yes | libxml | * | |
| yes | mbstring | * | |
| yes | mongodb | >=1.9.1 | not tested |
| | mysqli | | |
| yes | mysqlnd | * | |
| yes | openssl | * | |
| yes | pcntl | * | |
| yes | pdo | * | |
| yes | pdo_mysql | * | |
| yes | pdo_sqlite | * | |
| | pdo_pgsql | * | |
| yes | phar | * | |
| yes | posix | * | |
| yes, not compiled | protobuf | * | Not compiled and enabled as default |
| yes | readline | * | Not support `./php -a` |
| yes | redis | * | |
| yes | shmop | * | |
| yes | simplexml | * | |
| yes | soap | * | |
| yes | sockets | * | |
| yes | sqlite3 | * | |
| yes | swoole | >=4.6.6 | support mysqlnd, sockets, openssl, redis |
| yes | tokenizer | * | |
| yes | xml | * | |
| yes | xmlreader | * | |
| yes | xmlwriter | * | |
| yes | zip | * | not support `bzip2`, `lzma` compression |
| yes | zlib | * | |
## Customization
- If you are going to run without prompt, Just add it to the end of `docker run xxx` cmd according to the parameters given below.
> 1st parameter `original` represents that you are using global original download address to fetch dependencies, if you are in mainland China, use `mirror`.
>
> 2nd parameter `8.1.7` is your PHP version you are compiling.
>
> 3rd parameter `all` represents that you will compile all supported extensions.
>
> 4th parameter `/dist/` is your binary output directory.
>
> For example, `docker run --rm -v $(pwd)/dist:/dist/ -it static-php build-php original 8.1.7 all /dist/`
- `docker/extensions.txt` edit extensions.
- `docker/compile-php.sh` file `php_compile_args` function to adjust PHP configure arguments.
- `docker/check-extensions.sh` file `check_in_configure` function to adjust extensions' configure arguments.
- `docker/config.json` edit extensions and dependencies version and download links.
## Current Issue
- [X] Not support event(libevent), because of its `config.m4` and code.
- [ ] Swoole not support `--enable-swoole-curl`.
- [X] Not support readline, maybe caused by ncurses library.
- [X] Not support curl (solved)
- [X] Customize extensions to compile
- [X] php.ini integration
- [X] i18n (including README and scripts)
## Running preview
### Using static binary
<img width="881" alt="未命名" src="https://user-images.githubusercontent.com/20330940/168441751-e62cb8d4-a3c8-42d9-b34e-d804b39756a1.png">
### Using swoole application packed with micro
<img width="937" alt="all" src="https://user-images.githubusercontent.com/20330940/168557743-b8f92263-712f-490e-9fe0-831597741595.png">
## References
- <https://blog.terrywh.net/post/2019/php-static-openssl/>
- <https://stackoverflow.com/a/37245653>
- <http://blog.gaoyuan.xyz/2014/04/09/statically-compile-php/>

237
README.md
View File

@@ -5,62 +5,221 @@ Compile A Statically Linked PHP With Swoole and other Extensions. [English READM
同时可以使用 micro 二进制文件,将 PHP 源码和 PHP 二进制构建为一个文件分发! 同时可以使用 micro 二进制文件,将 PHP 源码和 PHP 二进制构建为一个文件分发!
注:只能编译 CLI 模式,暂不支持 CGI 和 FPM 模式 注:只能编译 CLI 模式,暂不支持 CGI 和 FPM 模式
[![版本](https://img.shields.io/badge/script--version-1.5.2-green.svg)]()
[![License](https://img.shields.io/badge/License-MIT-blue.svg)]() [![License](https://img.shields.io/badge/License-MIT-blue.svg)]()
![Build Actions](https://github.com/crazywhalecc/static-php-cli/actions/workflows/build-php.yml/badge.svg)
## 重构
本分支为旧版。重构版支持很多新特性。下面是目前重构的进度:
- **关于重构版的讨论https://github.com/crazywhalecc/static-php-cli/discussions/29**
- **重构的分支:[refactor](https://github.com/crazywhalecc/static-php-cli/tree/refactor)**
- **重构的问题和待办https://github.com/crazywhalecc/static-php-cli/issues/32**
**目前重构版已经完成本分支的所有扩展,并且还支持更多扩展**
## 编译环境需求 ## 编译环境需求
是的,本项目采用 PHP 编写,编译前需要一个 PHP 环境,比较滑稽。 - 目前支持 arm64、x86_64、armv7l 架构
但本项目默认可通过自身构建的 micro 和 static-php 二进制运行,其他只需要包含 tokenizer 扩展和 PHP 版本大于等于 8.0 即可。 - 需要 Docker也可以直接在 Alpine Linux 上使用)
- 脚本支持编译的 PHP 版本7.2 ~ 8.1
- Linux ## 运行环境需求
- 支持架构: aarch64, amd64
- 支持发行版: alpine, ubuntu, centos
- 依赖工具: make, bison, flex, pkg-config, git, autoconf, automake, tar, unzip, gzip, bzip2, cmake
- macOS
- 支持架构: arm64, x86_64
- 依赖工具: make, bison, flex, pkg-config, git, autoconf, automake, tar, unzip, xz, gzip, bzip2, cmake
- Windows
- 支持架构: x86_64
- 依赖工具: (TODO)
- PHP
- 支持版本: 8.0, 8.1, 8.2
## 使用WIP Linux
> 你正在看的是重构后的 static-php-cli 编译项目,新项目还未完全重构,所以还有大量的扩展没有完成。 ## 直接使用
> 你可以阅读使用 bash 编写的仅为 Linux 系统使用的静态编译脚本和 Docker详见 bash-version 分支。 旧版本未来将会切换为次要版本,提供有限支持。
未来会提供一个直接可使用的 phar 包和一个 phpmicro 打包的二进制文件,你可以直接从 Release 中获取并使用: 1. 可以直接下面的托管服务器或到 `Actions` 找到最新的构建项目下载 Actions 构建的文件。
<https://dl.zhamao.xin/php-bin/file/>
2. 可以直接使用快速脚本 `install-runtime.sh`,将静态 PHP 二进制及 Composer 下载到当前目录的 `runtime/` 子目录下:
```bash ```bash
chmod +x spc # 可以使用 export ZM_DOWN_PHP_VERION=8.0 来切换 PHP 版本
# 拉取所有依赖库 bash <(curl -fsSL https://dl.zhamao.xin/php-bin/install-runtime.sh)
./spc fetch --all
# 构建包含 bcmath,openssl,swoole 扩展的 php-cli 和 micro.sfx
./spc build "bcmath,openssl,swoole" --build-all
``` ```
## 项目支持情况WIP ## PHP 代码打包使用
- [X] 基础结构编写(采用 symfony/console` v1.5.0 脚本开始,脚本新增了对 PHP 代码打包的支持,可以将 PHP 代码打包为一个文件分发,方便在 Linux 系统使用。(仅支持 PHP >= 8.0
- [X] 错误处理
- [X] macOS 支持
- [ ] Windows 支持
- [ ] Linux 支持
- [X] PHP 7.4 支持
## 支持的扩展情况WIP 1. 可以直接在上面的下载链接中下载 `micro-` 开头的文件并解压,获得 `micro.sfx` 文件后,使用以下命令和 PHP 源码或 PHAR 结合:
[扩展支持列表](/ext-support.md) ```bash
echo "<?php echo 'Hello world' . PHP_EOL;" > code.php
cat micro.sfx code.php > single-app && chmod +x single-app
./single-app
## 开源协议 # 如果打包 PHAR 文件,仅需把 code.php 更换为 phar 文件路径即可
```
本项目依据旧版本惯例采用 MIT License 开源,新版本采用了部分项目的源代码做参考,特别感谢: 2. 如果打包项目,可以先将项目打包为 phar + entry然后结合打包 micro 与 phar 文件即可。
- [dixyes/lwmbs](https://github.com/dixyes/lwmbs)(木兰宽松许可证) > 关于如何将项目打包为 phar见项目 [php-cli-helper](https://github.com/crazywhalecc/php-cli-helper)。
- [swoole/swoole-cli](https://github.com/swoole/swoole-cli)Apache 2.0 LICENSE+SWOOLE-CLI LICENSE
因本项目的特殊性,使用项目编译过程中会使用很多其他开源项目,例如 curl、protobuf 等,它们都有各自的开源协议。 > 感谢 <https://github.com/dixyes/phpmicro> 项目提供的支持
请在编译完成后,使用命令 `dump-license`(TODO) 导出项目使用项目的开源协议,并遵守对应项目的 LICENSE。
## 自行编译
可以自己使用 Dockerfile 进行编译构建:
```bash
git clone https://github.com/crazywhalecc/static-php-cli.git
cd static-php-cli/docker
export DOCKER_BUILDKIT=1
docker build -t static-php . --build-arg USE_BACKUP_ADDRESS=no
# 新建一个用于放置构建好的二进制的文件夹
mkdir dist
# 终端会引导你进行编译安装,可选择 PHP 版本、要编译的扩展
docker run --rm -v $(pwd)/dist:/dist/ -it static-php build-php
```
编译之后可以使用下方命令将二进制 PHP 提取出来,用以下方式:
```bash
cd dist
file ./php
# 如果是 PHP 8.0 以上,同时会输出 micro 构建版本
file ./micro.sfx
```
如果你不想使用 Docker想从Alpine环境直接编译可以使用预置相同编译配置的脚本 `fast-compiler.sh`
```bash
cd docker
# 用于切换编译的PHP版本
export VER_PHP="8.1.7"
./fast-compiler.sh
```
如果要选择安装的扩展,可以修改 `docker/extensions.txt` 文件,具体规则如下:
- 文件内使用 `^` 可以表示默认不安装该扩展,`#` 开头可以编写注释
- 扩展名一律使用小写,目前默认状态下文件内所列的扩展为支持的扩展,其他扩展暂不支持,如有需求请提 Issue 添加
## 支持的扩展表
| 是否支持 | PHP 扩展名称 | 支持版本 | 备注 |
| -------- | ------------ | -------- | ------------------------------------------------------- |
| yes, enabled | bcmath | * | |
| yes, enabled | calendar | * | |
| yes, enabled | ctype | * | |
| yes, enabled | curl | * | 自带下载编译 curl 库 |
| yes, enabled | dom | * | |
| yes, enabled | event | >=3.0.8 | 从 BitBucket 作者仓库下载,非 pecl 版本 |
| yes, enabled | exif | * | |
| yes, enabled | filter | * | |
| yes, enabled | fileinfo | * | |
| yes, enabled | gd | * | |
| yes, enabled | hash | * | |
| yes, enabled | iconv | * | |
| yes, enabled | inotify | 3.0.0 | 从 pecl 或镜像站下载的源码 |
| yes, enabled | json | * | |
| yes, enabled | libxml | * | 自带下载编译 libxml2 库 |
| yes, enabled | mbstring | * | |
| yes, enabled | mongodb | >=1.9.1 | 未测试,从 pecl 或镜像站下载的源码 |
| | mysqli | | |
| yes, enabled | mysqlnd | * | |
| yes, enabled | openssl | * | |
| yes, enabled | pcntl | * | |
| yes, enabled | pdo | * | |
| yes, enabled | pdo_mysql | * | |
| yes, enabled | pdo_sqlite | * | |
| | pdo_pgsql | * | |
| yes, enabled | phar | * | |
| yes, enabled | posix | * | |
| yes, not enabled | protobuf | * | 默认不编译 |
| yes, enabled | readline | * | 不支持 `./php -a` |
| yes, enabled | redis | * | 从 pecl 或镜像站下载的源码 |
| yes, enabled | shmop | * | |
| yes, enabled | simplexml | * | |
| yes, enabled | soap | * | |
| yes, enabled | sockets | * | |
| yes, enabled | sqlite3 | * | |
| yes, enabled | swoole | >=4.6.6 | 使用参数 `--enable-openssl --with-openssl --with-openssl-dir=/usr`,从 pecl 或镜像站下载的源码 |
| yes, enabled | tokenizer | * | |
| yes, enabled | xml | * | |
| yes, enabled | xmlreader | * | |
| yes, enabled | xmlwriter | * | |
| yes, enabled | zip | * | 因链接库原因已关闭 `libzip` 库的 `bzip2``lzma` 支持 |
| yes, enabled | zlib | * | |
## 自定义
- 从 Docker 运行编译会依次询问下载地址、PHP 版本、要编译的扩展、编译输出文件夹。
- 如果不想弹出终端交互框,只需按照下方给出的参数加到字符串的后面。
> `参数1`: `original` 代表使用原始地址,如果你位于中国大陆,可使用 `mirror` 加快下载。
>
> `参数2`: `8.1.7` 是你要编译的 PHP 版本。
>
> `参数3`: `all` 代表你要编译所有非反选的扩展,不询问。
>
> `参数4`: `/dist/` 是你编译后输出二进制 PHP 和 micro 的文件夹
>
> 基本例子: `docker run --rm -v $(pwd)/dist:/dist/ -it static-php build-php original 8.1.7 all /dist/`
- `docker/extensions.txt` 指定要编译安装的扩展。
- `docker/extensions.txt` 中,`^` 开头的扩展名为反选,反选的扩展将出现在编译的扩展列表中,但默认不选中。
- `docker/compile-php.sh` 中的 `php_compile_args` 函数来调整 PHP 编译参数。
- `docker/check-extensions.sh` 中的 `check_in_configure` 函数可调整 PHP 扩展编译的参数。
- `docker/config.json` 可调整要下载的扩展和依赖库版本和链接。
- `docker/fast-compiler.sh` 可以在 Alpine Linux 系统下直接运行。
## 目前的问题(对勾为已解决)
- [X] 不支持 event(libevent) 扩展event 扩展的 sockets 支持不能在静态编译中使用,因为静态内嵌编译暂时没办法调整扩展编译顺序,同时其本身也不支持静态编译。
- [ ] Swoole 扩展不支持 `--enable-swoole-curl`,也是因为编译顺序和加载顺序的问题。
- [X] 不支持 readline 扩展readline 扩展安装后无法正常使用 `php -a`,原因还没有弄清楚,可能是静态编译造成的 ncurses 库出现了问题。
- [X] curl/libcurl 扩展静态编译
- [X] 可自行选择不需要编译进入的扩展
- [X] php.ini 内嵌或分发
- [X] i18n国际化脚本本身和 README
如果你对以上问题有解决方案,请提出 Issue 或 PR
如果你对此脚本比较感兴趣,未来会在此编写脚本中涉及内容的解析和说明。
## 运行示例
### 静态 PHP 运行脚本
<img width="881" alt="未命名" src="https://user-images.githubusercontent.com/20330940/168441751-e62cb8d4-a3c8-42d9-b34e-d804b39756a1.png">
### micro 打包运行 Swoole
<img width="937" alt="all" src="https://user-images.githubusercontent.com/20330940/168557743-b8f92263-712f-490e-9fe0-831597741595.png">
## 原理
静态编译是一项比较多见于 Golang 的编译方式,在传统的 Linux 系统下正常的程序和库基本是动态编译链接Dynamically linked也就是说不同程序引用同样的库可以共用减少资源重复。
但是由于不少系统软件环境配置复杂,或者依赖的库版本冲突,一般使用 Docker 等容器技术可以解决这一问题。但 Docker 等容器也需要拉取镜像,体积较大,对于程序有便携需求的人(比如网络安全员做渗透测试等)需要很多程序可以像 Windows 上的绿色程序一样随处打包运行。
PHP 是最好的编程语言,它编写容易,易于部署和开发,倘若将 PHP 编译为静态的文件,并且将 Swoole 或 libevent 等库同样内嵌,那 PHP 不仅将可以编写便携的 Web 服务器,还能做很多想不到的事!
编译静态 PHP 大致分为以下几个步骤:
1. 下载 PHP 源码
2. 下载需要静态编译的额外扩展源码(如 inotify、mongodb、redis 等)
3. 将额外扩展源码放入 PHP 源码中
4. 生成 `configure` 并使用 `-static` 的 FLAG 进行生成 makefile
5. 修改 Makefile 中的编译参数,增加 `-all-static` 和去掉 dynamic 相关的参数
6. 使用 `make` 构建静态 PHP
7. 使用 `make install` 安装到指定目录,再使用 `strip` 去除符号表缩小体积
对于第二步,如果额外扩展中有依赖 Linux 的其他库(比如 curl 依赖 libcurl则需要在第二步之前编译安装对应库的静态版本比如 libxml2.a
而此处出问题最多的部分就是安装额外扩展的依赖上,很多库不支持静态编译,而互联网很难找到对对应库进行静态编译的资料。
脚本和 Dockerfile 统一采用 Alpine 的目的就是apk 包管理下有很多库提供了 `*-static` 静态版本,直接使用包管理安装就可以使用,而即使没有,也可以使用 musl-libc 进行静态编译,避免 glibc 下的 `libnss` 等无法静态编译的问题。
第二种要面对比较棘手的问题就是 PHP 扩展可能本身不支持静态编译(如 curl 扩展),有些通过绕过手段可以静态编译,但有些只能通过对扩展源码进行修改才能使其支持。
所以这个项目中涉及的脚本,最大的问题就在于对其他依赖的处理,而不是 PHP 编译本身。PHP 如果不启用任何扩展(即使用 `--disable-all`),则可以很方便地静态编译。
## 参考资料
- <https://blog.terrywh.net/post/2019/php-static-openssl/>
- <https://stackoverflow.com/a/37245653>
- <http://blog.gaoyuan.xyz/2014/04/09/statically-compile-php/>

16
bin/spc
View File

@@ -1,16 +0,0 @@
#!php
<?php
require_once __DIR__ . '/../vendor/autoload.php';
// 防止 Micro 打包状态下不支持中文的显示(虽然这个项目目前好像没输出过中文?)
if (PHP_OS_FAMILY === 'Windows' && Phar::running()) {
exec('CHCP 65001');
}
// 跑,反正一条命令跑就对了
try {
(new \SPC\ConsoleApplication())->run();
} catch (Exception $e) {
\SPC\exception\ExceptionHandler::getInstance()->handle($e);
}

View File

@@ -1,44 +0,0 @@
{
"pre-push": {
"enabled": true,
"actions": [
{
"action": "composer analyse"
}
]
},
"pre-commit": {
"enabled": true,
"actions": [
{
"action": "composer cs-fix -- --config=.php-cs-fixer.php --dry-run --diff {$STAGED_FILES|of-type:php}",
"conditions": [
{
"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType",
"args": ["php"]
}
]
}
]
},
"post-change": {
"enabled": true,
"actions": [
{
"action": "composer install",
"options": [],
"conditions": [
{
"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileChanged\\Any",
"args": [
[
"composer.json",
"composer.lock"
]
]
}
]
}
]
}
}

View File

@@ -1,44 +0,0 @@
{
"require": {
"php": ">= 8.0",
"ext-tokenizer": "*",
"ext-iconv": "*",
"symfony/console": "^6 || ^5 || ^4",
"zhamao/logger": "^1.0",
"crazywhalecc/cli-helper": "^0.1.0",
"nunomaduro/collision": "*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2 != 3.7.0",
"phpstan/phpstan": "^1.1",
"captainhook/captainhook": "^5.10",
"captainhook/plugin-composer": "^5.3"
},
"autoload": {
"psr-4": {
"SPC\\": "src/SPC"
},
"files": [
"src/globals/defines.php",
"src/globals/functions.php"
]
},
"extra": {
},
"bin": [
"bin/spc"
],
"scripts": {
"analyse": "phpstan analyse --memory-limit 300M",
"cs-fix": "php-cs-fixer fix",
"test": "bin/phpunit --no-coverage"
},
"config": {
"allow-plugins": {
"phpstan/extension-installer": true,
"captainhook/plugin-composer": true
},
"optimize-autoloader": true,
"sort-packages": true
}
}

View File

@@ -1,388 +0,0 @@
{
"bcmath": {
"type": "builtin"
},
"bz2": {
"type": "builtin",
"arg-type": "custom",
"lib-depends": [
"bzip2"
]
},
"calendar": {
"type": "builtin"
},
"ctype": {
"type": "builtin"
},
"curl": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"curl"
]
},
"dba": {
"type": "builtin",
"arg-type-windows": "with"
},
"dom": {
"type": "builtin",
"arg-type-windows": "with",
"lib-depends": [
"libxml2",
"zlib"
]
},
"enchant": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"enchant2"
]
},
"exif": {
"type": "builtin"
},
"ffi": {
"arg-type": "with",
"type": "builtin",
"lib-depends": [
"libffi"
]
},
"fileinfo": {
"type": "builtin"
},
"filter": {
"type": "builtin"
},
"ftp": {
"type": "builtin",
"lib-suggests": [
"openssl"
]
},
"gd": {
"type": "builtin",
"arg-type-windows": "with",
"lib-depends": [
"zlib",
"libpng"
],
"lib-suggests": [
"gd",
"libavif",
"libwebp",
"libjpeg",
"xpm",
"freetype"
],
"lib-depends-windows": [
"libiconv",
"freetype",
"libjpeg",
"zlib",
"libpng",
"xpm"
],
"lib-suggests-windows": [
"gd",
"libavif",
"libwebp"
]
},
"gettext": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"gettext"
]
},
"gmp": {
"type": "builtin",
"arg-type": "none",
"lib-depends": [
"gmp"
]
},
"iconv": {
"type": "builtin",
"arg-type": "with",
"lib-depends-windows": [
"libiconv"
]
},
"imap": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"imap"
],
"lib-suggests": [
"kerberos"
]
},
"intl": {
"type": "builtin",
"lib-depends": [
"icu"
]
},
"ldap": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"ldap"
]
},
"mbregex": {
"type": "builtin",
"lib-depends": [
"onig"
]
},
"mbstring": {
"type": "builtin",
"lib-depends": [
"onig"
]
},
"mysqli": {
"type": "builtin",
"arg-type": "with",
"ext-depends": [
"mysqlnd"
]
},
"mysqlnd": {
"type": "builtin",
"arg-type-windows": "with"
},
"opcache": {
"type": "builtin"
},
"openssl": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"openssl"
]
},
"pcntl": {
"type": "builtin",
"unix-only": true
},
"pdo": {
"type": "builtin"
},
"pdo_mysql": {
"type": "builtin",
"arg-type": "with",
"ext-depends": [
"pdo",
"mysqlnd"
]
},
"pdo_pgsql": {
"type": "builtin",
"arg-type": "with",
"ext-depends": [
"pdo"
],
"lib-depends": [
"pq"
]
},
"pdo_sqlite": {
"type": "builtin",
"arg-type": "with",
"ext-depends": [
"pdo",
"sqlite3"
],
"lib-depends": [
"sqlite"
]
},
"phar": {
"type": "builtin",
"lib-suggests": [
"zlib"
]
},
"posix": {
"type": "builtin",
"unix-only": true
},
"pspell": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"aspell"
]
},
"readline": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"readline"
],
"lib-suggests": [
"libedit",
"ncurses"
]
},
"redis": {
"type": "external",
"source": "redis"
},
"session": {
"type": "builtin"
},
"shmop": {
"type": "builtin"
},
"simplexml": {
"type": "builtin",
"arg-type-windows": "with",
"lib-depends": [
"libxml2"
]
},
"snmp": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"net-snmp"
]
},
"soap": {
"type": "builtin",
"lib-depends": [
"libxml2"
]
},
"sockets": {
"type": "builtin"
},
"sodium": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"sodium"
]
},
"sqlite3": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"sqlite"
]
},
"swoole": {
"type": "external",
"source": "swoole",
"lib-depends": [
"openssl",
"curl"
],
"ext-depends": [
"openssl"
],
"ext-suggests": [
"curl"
],
"unix-only": true
},
"swow": {
"type": "external",
"source": "swow",
"lib-suggests": [
"openssl",
"curl"
],
"ext-suggests": [
"curl"
]
},
"sysvmsg": {
"type": "builtin",
"unix-only": true
},
"sysvsem": {
"type": "builtin",
"unix-only": true
},
"sysvshm": {
"type": "builtin",
"unix-only": true
},
"tidy": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"tidy"
]
},
"tokenizer": {
"type": "builtin"
},
"xml": {
"type": "builtin",
"arg-type-windows": "with",
"lib-depends": [
"libxml2"
]
},
"xmlreader": {
"type": "builtin",
"lib-depends": [
"libxml2"
]
},
"xmlwriter": {
"type": "builtin",
"lib-depends": [
"libxml2"
]
},
"xsl": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"libxslt"
]
},
"yaml": {
"type": "external",
"source": "yaml",
"arg-type": "with",
"lib-depends": [
"libyaml"
]
},
"zip": {
"type": "builtin",
"arg-type": "with",
"arg-type-windows": "enable",
"lib-depends": [
"libzip"
]
},
"zlib": {
"type": "builtin",
"arg-type": "with",
"arg-type-windows": "enable",
"lib-depends": [
"zlib"
]
},
"zstd": {
"type": "external",
"source": "ext-zstd",
"lib-depends": [
"zstd"
]
}
}

View File

@@ -1,365 +0,0 @@
{
"brotli": {
"source": "brotli",
"static-libs-unix": [
"libbrotlidec-static.a",
"libbrotlienc-static.a",
"libbrotlicommon-static.a"
],
"static-libs-windows": [
"brotlicommon-static.lib",
"brotlienc-static.lib",
"brotlidec-static.lib"
],
"headers": [
"brotli"
]
},
"bzip2": {
"source": "bzip2",
"static-libs-unix": [
"libbz2.a"
],
"static-libs-windows": [
[
"libbz2.lib",
"libbz2_a.lib"
]
],
"headers": [
"bzlib.h"
]
},
"curl": {
"source": "curl",
"static-libs-unix": [
"libcurl.a"
],
"static-libs-windows": [
"libcurl.lib"
],
"headers": [
"curl"
],
"lib-depends-unix": [
"zlib"
],
"lib-suggests": [
"libssh2",
"brotli",
"nghttp2",
"zstd",
"openssl",
"idn2",
"psl"
],
"lib-suggests-windows": [
"zlib",
"libssh2",
"brotli",
"nghttp2",
"zstd",
"openssl",
"idn2",
"psl"
],
"frameworks": [
"CoreFoundation",
"SystemConfiguration"
]
},
"gmp": {
"source": "gmp",
"static-libs-unix": [
"libgmp.a"
],
"static-libs-windows": [
"libgmp.lib"
],
"headers": [
"gmp.h"
]
},
"libffi": {
"source": "libffi",
"static-libs-unix": [
"libffi.a"
],
"static-libs-windows": [
"libffi.lib"
],
"headers-unix": [
"ffi.h",
"ffitarget.h"
],
"headers-windows": [
"ffi.h",
"fficonfig.h",
"ffitarget.h"
]
},
"libpng": {
"source": "libpng",
"static-libs-unix": [
"libpng.a"
],
"static-libs-windows": [
"libpng16_static.lib"
],
"headers-unix": [
"png.h",
"pngconf.h",
"pnglibconf.h"
],
"headers-windows": [
"png.h",
"pngconf.h"
],
"lib-depends": [
"zlib"
]
},
"libssh2": {
"source": "libssh2",
"static-libs-unix": [
"libssh2.a"
],
"static-libs-windows": [
"libssh2.lib"
],
"headers": [
"libssh2.h",
"libssh2_publickey.h",
"libssh2_sftp.h"
],
"lib-depends": [
"openssl"
],
"lib-suggests": [
"zlib"
]
},
"libxml2": {
"source": "libxml2",
"static-libs-unix": [
"libxml2.a"
],
"static-libs-windows": [
[
"libxml2s.lib",
"libxml2_a.lib"
]
],
"headers": [
"libxml2"
],
"lib-suggests": [
"icu",
"xz",
"zlib"
],
"lib-suggests-windows": [
"icu",
"xz",
"zlib",
"pthreads4w"
]
},
"libyaml": {
"source": "libyaml",
"static-libs-unix": [
"libyaml.a"
],
"static-libs-windows": [
"yaml.lib"
],
"headers": [
"yaml.h"
]
},
"libzip": {
"source": "libzip",
"static-libs-unix": [
"libzip.a"
],
"static-libs-windows": [
[
"zip.lib",
"libzip_a.lib"
]
],
"headers": [
"zip.h",
"zipconf.h"
],
"lib-depends": [
"zlib"
],
"lib-suggests": [
"bzip2",
"xz",
"zstd",
"openssl"
]
},
"libmcrypt": {
"source": "libmcrypt",
"static-libs-unix": [
"libmcrypt.a"
]
},
"mcrypt": {
"source": "mcrypt",
"static-libs-unix": [
"libmcrypt.a"
]
},
"freetype": {
"source": "freetype",
"static-libs-unix": [
"libfreetype.a"
],
"headers-unix": [
"freetype2/freetype/freetype.h",
"freetype2/ft2build.h"
]
},
"nghttp2": {
"source": "nghttp2",
"static-libs-unix": [
"libnghttp2.a"
],
"static-libs-windows": [
"nghttp2.lib"
],
"headers": [
"nghttp2"
],
"lib-depends": [
"zlib",
"openssl"
],
"lib-suggests": [
"libxml2",
"libev",
"libcares",
"libngtcp2",
"libnghttp3",
"libbpf",
"libevent-openssl",
"jansson",
"jemalloc",
"systemd",
"cunit"
]
},
"onig": {
"source": "onig",
"static-libs-unix": [
"libonig.a"
],
"static-libs-windows": [
[
"onig.lib",
"onig_a.lib"
]
],
"headers": [
"oniggnu.h",
"oniguruma.h"
]
},
"openssl": {
"source": "openssl",
"static-libs-unix": [
"libssl.a",
"libcrypto.a"
],
"static-libs-windows": [
"libssl.lib",
"libcrypto.lib"
],
"headers": [
"openssl"
],
"lib-suggests": [
"zlib"
]
},
"pthreads4w": {
"source": "pthreads4w",
"static-libs-windows": [
"libpthreadVC3.lib"
],
"headers-windows": [
"_ptw32.h",
"pthread.h",
"sched.h",
"semaphore.h"
]
},
"sqlite": {
"source": "sqlite",
"static-libs-unix": [
"libsqlite3.a"
],
"headers-unix": [
"sqlite3.h",
"sqlite3ext.h"
]
},
"xz": {
"source": "xz",
"static-libs-unix": [
"liblzma.a"
],
"static-libs-windows": [
[
"liblzma.lib",
"liblzma_a.lib"
]
],
"headers-unix": [
"lzma"
],
"headers-windows": [
"lzma",
"lzma.h"
]
},
"zlib": {
"source": "zlib",
"static-libs-unix": [
"libz.a"
],
"static-libs-windows": [
"zlib_a.lib"
],
"headers": [
"zlib.h",
"zconf.h"
]
},
"zstd": {
"source": "zstd",
"static-libs-unix": [
"libzstd.a"
],
"static-libs-windows": [
[
"zstd.lib",
"zstd_static.lib"
]
],
"headers-unix": [
"zdict.h",
"zstd.h",
"zstd_errors.h"
],
"headers-windows": [
"zstd.h",
"zstd_errors.h"
]
}
}

File diff suppressed because one or more lines are too long

89
docker/Dockerfile Executable file
View File

@@ -0,0 +1,89 @@
FROM alpine:3.16
# define script basic information
# Version of this Dockerfile
ENV SCRIPT_VERSION=1.5.1
# Download address uses backup address
ARG USE_BACKUP_ADDRESS
# (if downloading slowly, consider set it to yes)
ENV USE_BACKUP="${USE_BACKUP_ADDRESS}"
# APK repositories mirror address, if u r not in China, consider set USE_BACKUP=yes to boost
ENV LINK_APK_REPO='mirrors.ustc.edu.cn'
ENV LINK_APK_REPO_BAK='dl-cdn.alpinelinux.org'
RUN if [ "${USE_BACKUP}" = "" ]; then \
export USE_BACKUP="no" ; \
fi
RUN sed -i.backup 's/dl-cdn.alpinelinux.org/'${LINK_APK_REPO}'/g' /etc/apk/repositories ;
RUN if [ "${USE_BACKUP}" = "no" ]; then cp -f /etc/apk/repositories.backup /etc/apk/repositories; fi
RUN cat /etc/apk/repositories
# build requirements
RUN apk add bash file wget cmake gcc g++ jq autoconf git libstdc++ linux-headers make m4 libgcc binutils ncurses dialog > /dev/null
# php zlib dependencies
RUN apk add zlib-dev zlib-static > /dev/null
# php mbstring dependencies
RUN apk add oniguruma-dev > /dev/null
# php openssl dependencies
RUN apk add openssl-libs-static openssl-dev openssl > /dev/null
# php gd dependencies
RUN apk add libpng-dev libpng-static > /dev/null
# curl c-ares dependencies
RUN apk add c-ares-static c-ares-dev > /dev/null
# php event dependencies
RUN apk add libevent libevent-dev libevent-static > /dev/null
# php sqlite3 dependencies
RUN apk add sqlite sqlite-dev sqlite-libs sqlite-static > /dev/null
# php libzip dependencies
RUN apk add bzip2-dev bzip2-static bzip2 > /dev/null
# php micro ffi dependencies
RUN apk add libffi libffi-dev > /dev/null
# php gd event parent dependencies
RUN apk add zstd-static > /dev/null
# php readline dependencies
RUN apk add readline-static ncurses-static readline-dev > /dev/null
RUN apk add aria2
RUN mkdir /app
WORKDIR /app
ADD ./ /app/
# RUN chmod +x /app/*.sh
# use proxy
# ENV http_proxy=http://192.168.3.26:8015
# ENV https_proxy=http://192.168.3.26:8015
# 提前下载好,就可以跳过两步
# (容器外提前执行 下面两个命令)
RUN sh ./download-library-batch-aria2.sh
RUN sh ./download-extension-batch-aria2.sh
#ENV http_proxy=''
#ENV https_proxy=''
RUN ls -lh source/libraries
RUN ls -lh source/extensions
# quick test complie
# RUN bash ./compile-deps.sh
RUN sh ./download.sh swoole ${USE_BACKUP} && \
sh ./download.sh inotify ${USE_BACKUP} && \
sh ./download.sh mongodb ${USE_BACKUP} && \
sh ./download.sh event ${USE_BACKUP} && \
sh ./download.sh redis ${USE_BACKUP} && \
sh ./download.sh libxml2 ${USE_BACKUP} && \
sh ./download.sh xz ${USE_BACKUP} && \
sh ./download.sh curl ${USE_BACKUP} && \
sh ./download.sh libzip ${USE_BACKUP} && \
sh ./download.sh libiconv ${USE_BACKUP} && \
sh ./download-git.sh dixyes/phpmicro phpmicro ${USE_BACKUP}
RUN bash ./compile-deps.sh
RUN echo -e "#!/usr/bin/env bash\n/app/compile-php.sh \$@" > /bin/build-php && chmod +x /bin/build-php

3
docker/ac_override_1 Executable file
View File

@@ -0,0 +1,3 @@
AC_DEFUN([PHP_CHECK_LIBRARY], [
$3
])

18
docker/ac_override_2 Executable file
View File

@@ -0,0 +1,18 @@
AC_DEFUN([PHP_CHECK_LIBRARY], [
save_old_LDFLAGS=$LDFLAGS
ac_stuff="$5"
save_ext_shared=$ext_shared
ext_shared=yes
PHP_EVAL_LIBLINE([$]ac_stuff, LDFLAGS)
AC_CHECK_LIB([$1],[$2],[
LDFLAGS=$save_old_LDFLAGS
ext_shared=$save_ext_shared
$3
],[
LDFLAGS=$save_old_LDFLAGS
ext_shared=$save_ext_shared
unset ac_cv_lib_$1[]_$2
$4
])dnl
])

200
docker/check-extensions.sh Executable file
View File

@@ -0,0 +1,200 @@
#!/bin/sh
# Here are 3 steps in configuration of extensions
# before_configure
# in_configure
# after_configure
self_dir=$(cd "$(dirname "$0")";pwd)
php_dir=$(find $self_dir/source -name "php-*" -type d | tail -n1)
test -f "$self_dir/extensions_install.txt" && EXT_LIST_FILE="$self_dir/extensions_install.txt" || EXT_LIST_FILE="$self_dir/extensions.txt"
function do_copy_extension() {
ext_dir=$(find $self_dir/source -name "*$1-*" -type d | tail -n1)
mv $ext_dir $php_dir/ext/$1
if [ $? != 0 ]; then
echo "Compile error! ext: $1, ext_dir=$ext_dir"
exit 1
fi
}
function check_before_configure() {
list=$(cat "$EXT_LIST_FILE" | grep -v "^#" | grep -v "^$" | grep -v "^\^")
xml_sign="no"
for loop in $list
do
case $loop in
bcmath) ;;
calendar) ;;
ctype) ;;
exif) ;;
filter) ;;
fileinfo) ;;
gd) ;;
hash) ;;
iconv) ;;
json) ;;
mbstring) ;;
mysqlnd) ;;
openssl) ;;
pcntl) ;;
pdo) ;;
pdo_mysql) ;;
pdo_sqlite) ;;
phar) ;;
posix) ;;
protobuf)
do_copy_extension protobuf
echo '#ifndef PHP_PROTOBUF_H' >> $php_dir/ext/protobuf/php_protobuf.h && \
echo '# define PHP_PROTOBUF_H' >> $php_dir/ext/protobuf/php_protobuf.h && \
echo '#ifdef HAVE_CONFIG_H' >> $php_dir/ext/protobuf/php_protobuf.h && \
echo '# include "config.h"' >> $php_dir/ext/protobuf/php_protobuf.h && \
echo '#endif' >> $php_dir/ext/protobuf/php_protobuf.h && \
echo 'extern zend_module_entry protobuf_module_entry;' >> $php_dir/ext/protobuf/php_protobuf.h && \
echo '# define phpext_protobuf_ptr &protobuf_module_entry' >> $php_dir/ext/protobuf/php_protobuf.h && \
echo '#endif' >> $php_dir/ext/protobuf/php_protobuf.h
;;
readline)
if [ ! -d "/nom" ]; then
mkdir /nom
fi
mv /usr/lib/libreadline.so* /nom/ && \
mv /usr/lib/libncurses*.so* /nom
;;
shmop) ;;
sockets) ;;
sqlite3) ;;
tokenizer) ;;
zlib) ;;
zip) ;;
curl) cat "$self_dir/ac_override_1" "$php_dir/ext/curl/config.m4" "$self_dir/ac_override_2" > /tmp/aa && mv /tmp/aa "$php_dir/ext/curl/config.m4" ;;
dom|xml|libxml|xmlreader|xmlwriter|simplexml|soap) ;;
inotify) do_copy_extension inotify ;;
redis) do_copy_extension redis ;;
swoole) do_copy_extension swoole ;;
mongodb) do_copy_extension mongodb ;;
event) do_copy_extension event ;;
esac
done
case $1 in
8.*)
mv $self_dir/source/phpmicro $php_dir/sapi/micro && \
sed -ie 's/#include "php.h"/#include "php.h"\n#define PHP_MICRO_FAKE_CLI 1/g' $php_dir/sapi/micro/php_micro.c
;;
esac
}
function check_in_configure() {
php_configure=""
list=$(cat "$EXT_LIST_FILE" | sed 's/#.*//g' | sed 's/\^.*//g' | sed -e 's/[ ]*$//g' | grep -v "^\s*$")
for loop in $list
do
case $loop in
bcmath) php_configure="$php_configure --enable-bcmath" ;;
calendar) php_configure="$php_configure --enable-calendar" ;;
ctype) php_configure="$php_configure --enable-ctype" ;;
curl) php_configure="$php_configure --with-curl" ;;
dom) php_configure="$php_configure --enable-dom" ;;
exif) php_configure="$php_configure --enable-exif" ;;
event) php_configure="$php_configure --with-event-libevent-dir=/usr --with-event-core --with-event-extra --with-event-openssl" ;;
filter) php_configure="$php_configure --enable-filter" ;;
fileinfo) php_configure="$php_configure --enable-fileinfo" ;;
gd)
case $1 in
7.3.*|7.2.*) php_configure="$php_configure --with-gd" ;;
7.4.*|8.*) php_configure="$php_configure --enable-gd" ;;
esac
;;
hash)
case $1 in
7.3.*|7.2.*) php_configure="$php_configure --enable-hash" ;;
esac
;;
iconv) php_configure="$php_configure --with-iconv=/usr" ;;
inotify) php_configure="$php_configure --enable-inotify" ;;
json)
case $1 in
7.*) php_configure="$php_configure --enable-json" ;;
esac
;;
libxml)
case $1 in
7.3.*|7.2.*) php_configure="$php_configure --enable-libxml" ;;
7.4.*|8.*) php_configure="$php_configure --with-libxml" ;;
esac
;;
mbstring) php_configure="$php_configure --enable-mbstring" ;;
mongodb) php_configure="$php_configure --enable-mongodb" ;;
mysqlnd) php_configure="$php_configure --enable-mysqlnd" ;;
openssl) php_configure="$php_configure --with-openssl --with-openssl-dir=/usr" ;;
pcntl) php_configure="$php_configure --enable-pcntl" ;;
pdo) php_configure="$php_configure --enable-pdo" ;;
pdo_mysql) php_configure="$php_configure --with-pdo-mysql=mysqlnd" ;;
phar) php_configure="$php_configure --enable-phar" ;;
posix) php_configure="$php_configure --enable-posix" ;;
protobuf) php_configure="$php_configure --enable-protobuf" ;;
readline) php_configure="$php_configure --with-readline" ;;
redis) php_configure="$php_configure --enable-redis --disable-redis-session" ;;
shmop) php_configure="$php_configure --enable-shmop" ;;
simplexml) php_configure="$php_configure --enable-simplexml" ;;
sockets) php_configure="$php_configure --enable-sockets" ;;
soap) php_configure="$php_configure --enable-soap" ;;
sqlite3) php_configure="$php_configure --with-sqlite3" ;;
pdo_sqlite) php_configure="$php_configure --with-pdo-sqlite" ;;
swoole)
php_configure="$php_configure --enable-swoole"
have_openssl=$(echo $list | grep openssl)
if [ "$have_openssl" != "" ]; then
php_configure="$php_configure --enable-openssl --with-openssl --with-openssl-dir=/usr"
fi
have_hash=$(echo $list | grep hash)
if [ "$have_hash" = "" ]; then
case $1 in
7.3.*|7.2.*) php_configure="$php_configure --enable-hash" ;;
esac
fi
;;
tokenizer) php_configure="$php_configure --enable-tokenizer" ;;
xml) php_configure="$php_configure --enable-xml" ;;
xmlreader) php_configure="$php_configure --enable-xmlreader" ;;
xmlwriter) php_configure="$php_configure --enable-xmlwriter" ;;
zlib) php_configure="$php_configure --with-zlib" ;;
zip) php_configure="$php_configure --with-zip" ;;
*)
echo "Unsupported extension '$loop' !" >&2
exit 1
;;
esac
done
case $1 in
8.*) php_configure="$php_configure --with-ffi --enable-micro=all-static" ;;
esac
echo $php_configure
}
function check_after_configure() {
list=$(cat "$EXT_LIST_FILE" | grep -v "^#" | grep -v "^$")
for loop in $list
do
case $loop in
swoole)
sed -ie 's/swoole_clock_gettime(CLOCK_REALTIME/clock_gettime(CLOCK_REALTIME/g' "$php_dir/ext/swoole/include/swoole.h"
sed -ie 's/strcmp("cli", sapi_module.name) == 0/strcmp("cli", sapi_module.name) == 0 || strcmp("micro", sapi_module.name) == 0/g' "$php_dir/ext/swoole/ext-src/php_swoole.cc"
;;
esac
done
case $1 in
8.*) sed -ie 's/$(EXTRA_LIBS:-lresolv=-Wl,-Bstatic,-lresolv,-Bdynamic)/$(EXTRA_LIBS)/g' "$php_dir/Makefile" ;;
esac
}
function finish_compile() {
if [ -d "/nom" ]; then
mv /nom/* /usr/lib/ || echo "Empty directory"
rm -rf /nom/
fi
}
$1 $2

64
docker/compile-deps.sh Executable file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env bash
self_dir=$(cd "$(dirname "$0")";pwd)
function do_xml_compiler() {
cd $self_dir/source/xz-* && \
./configure --enable-static=yes && \
make -j$(cat /proc/cpuinfo | grep processor | wc -l) && \
make install && \
echo "xz compiled!" && \
cd ../libxml2-* && \
./configure --prefix=/usr --with-lzma --without-python && \
make -j$(cat /proc/cpuinfo | grep processor | wc -l) && \
make install && \
echo "libxml2 compiled!"
}
function do_libzip_compiler() {
cd $self_dir/source/libzip-* && \
mkdir build && \
cd build && \
cmake -DBUILD_SHARED_LIBS=no .. -Wno-dev -DENABLE_BZIP2=no -DENABLE_LZMA=no && \
make LDFLAGS="-llzma -lbz2" -j$(cat /proc/cpuinfo | grep processor | wc -l) && \
make install && \
echo "libzip compiled!"
}
function do_curl_compiler() {
cd $self_dir/source/curl-* && \
CC=gcc CXX=g++ CFLAGS=-fPIC CPPFLAGS=-fPIC ./configure \
--without-nghttp2 \
--with-ssl=/usr \
--with-pic=pic \
--enable-ipv6 \
--enable-shared=no \
--without-libidn2 \
--disable-ldap \
--without-libpsl \
--without-lber \
--enable-ares && \
make -j$(cat /proc/cpuinfo | grep processor | wc -l) && \
make install && \
echo "curl compiled!"
}
function do_iconv_compiler() {
cd $self_dir/source/libiconv-* && \
./configure --enable-static=yes --prefix=/usr && \
make -j$(cat /proc/cpuinfo | grep processor | wc -l) && \
make install && \
echo "libiconv compiled!"
}
if [ ! -f "$self_dir/source/.deps-compiled" ]; then
source ${self_dir}/deps-modules/libmcrypt.sh
source ${self_dir}/deps-modules/gmp.sh
do_xml_compiler && \
do_curl_compiler && \
do_libzip_compiler && \
do_iconv_compiler && \
touch "$self_dir/source/.deps-compiled"
else
echo "Skip compilation for dependencies"
fi

125
docker/compile-php.sh Executable file
View File

@@ -0,0 +1,125 @@
#!/usr/bin/env bash
self_dir=$(cd "$(dirname "$0")";pwd)
# 通过 extensions.txt 生成一个 dialog 命令
function generate_ext_dialog_cmd() {
list=$(cat "$self_dir/extensions.txt" | grep -v "^#" | grep -v "^$")
echo -n "dialog --backtitle \"static-php-cli Compile Options\" --checklist \"Please select the extension you don't want to compile.\n\nNOTE: Use <space> to select or deselect items\n\n** Default is compiling all **\" 24 60 20 " > $self_dir/.ask_cmd.sh
for loop in $list
do
case $loop in
^*)
loop=$(echo ${loop:1} | xargs)
echo -n "$loop '$loop Extension' off " >> $self_dir/.ask_cmd.sh
;;
*) echo -n "$loop '$loop Extension' on " >> $self_dir/.ask_cmd.sh ;;
esac
done
echo "2>$self_dir/extensions_install.txt" >> $self_dir/.ask_cmd.sh
}
# PHP 编译参数生成
function php_compile_args() {
_php_arg="--prefix=$self_dir/php-dist"
_php_arg="$_php_arg --disable-all"
_php_arg="$_php_arg --enable-shared=no"
_php_arg="$_php_arg --enable-static=yes"
_php_arg="$_php_arg --enable-inline-optimization"
_php_arg="$_php_arg --with-layout=GNU"
_php_arg="$_php_arg --with-pear=no"
_php_arg="$_php_arg --disable-cgi"
_php_arg="$_php_arg --disable-phpdbg"
_php_arg="$_php_arg --with-config-file-path=/etc"
_php_arg="$_php_arg $($self_dir/check-extensions.sh check_in_configure $1)"
echo $_php_arg
}
# 第一个参数用于使用镜像地址还是原地址mirror为镜像地址original为原地址
if [ "$1" = "" ]; then
dialog --backtitle "static-php-cli Compile Options" --yesno "<Yes>: Use mirror download address, mainland China users recommended.\n\n<No>: Use original address, global users recommended." 10 50
test $? == 0 && USE_BACKUP="no" || USE_BACKUP="yes"
else
test "$1" != "mirror" && USE_BACKUP="yes" || USE_BACKUP="no"
fi
# 第二个参数用于规定编译的 PHP 版本
if [ "$2" = "" ]; then
dialog --backtitle "static-php-cli Compile Options" --inputbox "Please input your PHP version to compile" 10 50 "8.1.7" 2>$self_dir/.phpver
if [ $? != 0 ]; then
clear
echo "canceled Compiling PHP." && rm -f $self_dir/.phpver
exit 1
else
VER_PHP=$(cat $self_dir/.phpver)
rm -f $self_dir/.phpver
fi
else
VER_PHP=$2
fi
# 第三个参数用于是否直接安装,如果留空则询问编译的扩展,如果填入 all则直接编译所有的扩展
if [ "$3" != "all" ]; then
generate_ext_dialog_cmd && cat $self_dir/.ask_cmd.sh && chmod +x $self_dir/.ask_cmd.sh && $self_dir/.ask_cmd.sh
if [ $? != 0 ]; then
clear
echo "canceled Compiling PHP while selecting extensions." && rm -rf $self_dir/.ask_cmd.sh
exit 1
fi
rm -f $self_dir/.ask_cmd.sh
else
cp $self_dir/extensions.txt $self_dir/extensions_install.txt
fi
# 第四个参数用于输出 PHP 和 micro 二进制文件的位置
if [ "$4" = "" ]; then
dialog --backtitle "static-php-cli Compile Options" --inputbox "Please input compiled output directory" 10 50 "/dist/" 2>$self_dir/.outdir
if [ $? != 0 ]; then
clear
echo "canceled setting output dir, compiling PHP stopped." && rm -f $self_dir/.outdir
exit 1
else
OUT_DIR=$(cat $self_dir/.outdir)
rm -f $self_dir/.outdir
fi
else
OUT_DIR=$4
fi
if [ ! -d "$OUT_DIR" ]; then
mkdir -p "$OUT_DIR"
fi
# 下载 PHP
echo "All done. Downloading PHP ..."
if [ -d "$self_dir/source/php-$VER_PHP" ]; then
rm -rf "$self_dir/source/php-$VER_PHP"
fi
$self_dir/download.sh php ${USE_BACKUP} ${VER_PHP} || { echo "Download PHP failed!" && exit 1 ; }
# 选择性编译依赖的库、移动需要安装的扩展到 PHP 目录
$self_dir/check-extensions.sh check_before_configure ${VER_PHP} || { echo "Install required library failed!" && exit 1 ; }
# 编译 PHP
echo "Compiling PHP ..."
php_dir=$(find $self_dir/source -name "php-$VER_PHP" -type d | tail -n1)
cd $php_dir && \
./buildconf --force && \
./configure LDFLAGS=-static $(php_compile_args $VER_PHP) && \
$self_dir/check-extensions.sh check_after_configure ${VER_PHP} && \
sed -ie 's/-export-dynamic//g' "Makefile" && \
sed -ie 's/-o $(SAPI_CLI_PATH)/-all-static -o $(SAPI_CLI_PATH)/g' "Makefile" && \
#sed -ie 's/$(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_MICRO_OBJS)/$(PHP_GLOBAL_OBJS:.lo=.o) $(PHP_BINARY_OBJS:.lo=.o) $(PHP_MICRO_OBJS:.lo=.o)/g' "Makefile" && \
make LDFLAGS="-ldl" -j$(cat /proc/cpuinfo | grep processor | wc -l) && \
make install-cli && \
$self_dir/check-extensions.sh finish_compile && \
strip $self_dir/php-dist/bin/php
if [ $? != 0 ]; then
exit 1
fi
# 将 PHP 和 micro 输出到指定目录
echo "Copying php binary to $OUT_DIR ..." && \
cp $self_dir/php-dist/bin/php $OUT_DIR/ && \
test -f $php_dir/sapi/micro/micro.sfx && \
echo "Copying micro.sfx binary to $OUT_DIR ..." && \
cp $php_dir/sapi/micro/micro.sfx $OUT_DIR/ || { exit 0 ; }

61
docker/config.json Executable file
View File

@@ -0,0 +1,61 @@
{
"php": {
"link": "http://mirrors.zhamao.xin/php/php-{version}.tar.gz",
"link_2": "https://www.php.net/distributions/php-{version}.tar.gz"
},
"protobuf": {
"version": "3.21.2",
"link": "http://mirrors.zhamao.xin/pecl/protobuf-{version}.tgz",
"link_2": "https://pecl.php.net/get/protobuf-{version}.tgz"
},
"swoole": {
"version": "4.8.10",
"link": "http://mirrors.zhamao.xin/pecl/swoole-{version}.tgz",
"link_2": "https://pecl.php.net/get/swoole-{version}.tgz"
},
"mongodb": {
"version": "1.13.0",
"link": "http://mirrors.zhamao.xin/pecl/mongodb-{version}.tgz",
"link_2": "https://pecl.php.net/get/mongodb-{version}.tgz"
},
"inotify": {
"version": "3.0.0",
"link": "http://mirrors.zhamao.xin/pecl/inotify-{version}.tgz",
"link_2": "https://pecl.php.net/get/inotify-{version}.tgz"
},
"event": {
"version": "3.0.8",
"link": "https://mirrors.zhamao.xin/library/php-event/event-{version}.tar.gz",
"link_2": "https://bitbucket.org/osmanov/pecl-event/get/{version}.tar.gz"
},
"redis": {
"version": "5.3.7",
"link": "http://mirrors.zhamao.xin/pecl/redis-{version}.tgz",
"link_2": "https://pecl.php.net/get/redis-{version}.tgz"
},
"libxml2": {
"version": "2.9.12",
"link": "http://mirrors.zhamao.xin/library/libxml2/libxml2-{version}.tar.gz",
"link_2": "http://xmlsoft.org/sources/libxml2-{version}.tar.gz"
},
"curl": {
"version": "7.83.1",
"link": "https://mirrors.zhamao.xin/library/curl/curl-{version}.tar.gz",
"link_2": "https://curl.haxx.se/download/curl-{version}.tar.gz"
},
"xz": {
"version": "5.2.5",
"link": "https://mirrors.zhamao.xin/library/xz/xz-{version}.tar.gz",
"link_2": "https://tukaani.org/xz/xz-{version}.tar.gz"
},
"libzip": {
"version": "1.9.2",
"link": "https://mirrors.zhamao.xin/library/libzip/libzip-{version}.tar.gz",
"link_2": "https://libzip.org/download/libzip-{version}.tar.gz"
},
"libiconv": {
"version": "1.17",
"link": "https://mirrors.zhamao.xin/library/libiconv/libiconv-{version}.tar.gz",
"link_2": "https://ftp.gnu.org/gnu/libiconv/libiconv-{version}.tar.gz"
}
}

View File

@@ -0,0 +1,32 @@
#!/bin/bash
if [ -n "$__MODULE_SH__" ]; then
return
fi
__MODULE_SH__='gmp.sh'
set -exu
__DIR__=$(
cd "$(dirname "$0")"
pwd
)
cd ${__DIR__}
# cpu 核数 前面为linux 后面为macos
cpu_nums=`nproc 2> /dev/null || sysctl -n hw.ncpu`
# cpu_nums=`grep "processor" /proc/cpuinfo | sort -u | wc -l`
function do_gmp_compiler() {
pwd
mkdir -p /app/source/builder_dir/gmp
tar --strip-components=1 -C ${__DIR__}/source/builder_dir/gmp -xf ${__DIR__}/source/libraries/gmp-6.2.1.tar.lz
cd ${__DIR__}/source/builder_dir/gmp
./configure --prefix=/usr/gmp --enable-static --disable-shared
make -j $cpu_nums
echo "gmp compiled!" && \
make install && \
echo "gmp compiled!"
return $?
}
do_gmp_compiler

View File

@@ -0,0 +1,35 @@
#!/bin/bash
if [ -n "$__MODULE_SH__" ]; then
return
fi
__MODULE_SH__='libmcrypt.sh'
set -exu
__DIR__=$(
cd "$(dirname "$0")"
pwd
)
cd ${__DIR__}
# cpu 核数 前面为linux 后面为macos
cpu_nums=`nproc 2> /dev/null || sysctl -n hw.ncpu`
# cpu_nums=`grep "processor" /proc/cpuinfo | sort -u | wc -l`
function do_libmcrypt_compiler() {
pwd
mkdir -p /app/source/builder_dir/libmcrypt
tar --strip-components=1 -C ${__DIR__}/source/builder_dir/libmcrypt -xf ${__DIR__}/source/libraries/libmcrypt-2.5.8-3.4.tar.gz
cd ${__DIR__}/source/builder_dir/libmcrypt
chmod a+x ./install-sh
sh ./configure --prefix=/usr/libmcrypt \
--enable-static=yes \
--enable-shared=no
make -j $cpu_nums
echo "libmcrypt compiled!" && \
make install && \
echo "libmcrypt compiled!"
return $?
}
do_libmcrypt_compiler

View File

@@ -0,0 +1,34 @@
#!/bin/bash
set -exu
__DIR__=$(
cd "$(dirname "$0")"
pwd
)
cd ${__DIR__}
# https://aria2.github.io/manual/en/html/aria2c.html#http-ftp-segmented-downloads
# https://aria2.github.io/manual/en/html/aria2c.html
# -with-config-file-path=/usr/local/php/etc
# -U, --user-agent
# aria2c -h
# aria2c --conf-path=/etc/aria2/aria2.conf
:<<EOF
-c, --continue [true|false]
-s, --split=<N>
-x, --max-connection-per-server=<NUM>
-k, --min-split-size=<SIZE>
-j, --max-concurrent-downloads=<N>
-i, --input-file=<FILE>
EOF
user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
test -f download_extension_urls.txt && aria2c -c -j 10 -s 10 -x 8 -k 10M --allow-overwrite=true --max-tries=30 --retry-wait=15 --user-agent=$user_agent \
-d extensions --input-file=download_extension_urls.txt
mkdir -p source/extensions
awk 'BEGIN { cmd="cp -ri extensions/* source/extensions/" ; print "n" |cmd; }'

20
docker/download-git.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
self_dir=$(cd "$(dirname "$0")";pwd)
test -d "$self_dir/source/cache/" || mkdir -p "$self_dir/source/cache"
test "$3" != "yes" && GITHUB_ADDR="https://gh.api.99988866.xyz/" || GITHUB_ADDR=""
if [ -d "$self_dir/source/cache/$2" ]; then
echo "Using cache for $2"
cp -r "$self_dir/source/cache/$2" "$self_dir/source/"
else
wget -O $self_dir/source/master.zip "$GITHUB_ADDR""https://github.com/$1/archive/master.zip" && \
cd $self_dir/source/ && \
unzip master.zip && \
mv $2-master/ cache/$2 && \
cp -r cache/$2 ./
fi
# git clone https://$GITHUB_ADDR/$1.git --depth=1 $self_dir/source/$2

View File

@@ -0,0 +1,34 @@
#!/bin/bash
set -exu
__DIR__=$(
cd "$(dirname "$0")"
pwd
)
cd ${__DIR__}
# https://aria2.github.io/manual/en/html/aria2c.html#http-ftp-segmented-downloads
# https://aria2.github.io/manual/en/html/aria2c.html
# -with-config-file-path=/usr/local/php/etc
# -U, --user-agent
# aria2c -h
# aria2c --conf-path=/etc/aria2/aria2.conf
:<<EOF
-c, --continue [true|false]
-s, --split=<N>
-x, --max-connection-per-server=<NUM>
-k, --min-split-size=<SIZE>
-j, --max-concurrent-downloads=<N>
-i, --input-file=<FILE>
EOF
user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
test -f download_library_urls.txt && aria2c -c -j 10 -s 10 -x 8 -k 10M --allow-overwrite=true --max-tries=30 --retry-wait=15 --user-agent=$user_agent \
-d libraries --input-file=download_library_urls.txt
mkdir -p source/libraries
awk 'BEGIN { cmd="cp -ri libraries/* source/libraries/" ; print "n" |cmd; }'

60
docker/download.sh Executable file
View File

@@ -0,0 +1,60 @@
#!/bin/sh
_use_backup="$2"
SELF_DIR=$(cd "$(dirname "$0")";pwd)
if [ ! -d "source" ]; then
mkdir source
fi
if [ ! -d "source/cache" ]; then
mkdir source/cache
fi
function readconf() {
cat $SELF_DIR/config.json | jq $@ | sed 's/\"//g'
}
cd source
if [ "$_use_backup" = "yes" ]; then
_use_backup="_2"
else
_use_backup=""
fi
archive_find_tar=$(find cache/ -name "$1.*" | grep -E ".tgz" | tail -n1)
archive_find_zip=$(find cache/ -name "$1.*" | grep -E ".zip" | tail -n1)
if [ "$archive_find_tar" != "" ]; then
echo "Using cache for $1 ($archive_find_tar)"
tar -zxf "$archive_find_tar"
elif [ "$archive_find_zip" != "" ]; then
echo "Using cache for $1 ($archive_find_zip)"
unzip $archive_find_zip -d "$SELF_DIR/source" > /dev/null
else
if [ "$3" != "" ]; then
wget -q "$(readconf ".$1.link$_use_backup" | sed 's/{version}/'$3'/g')"
else
echo "Downloading $1"
wget -q "$(readconf ".$1.link$_use_backup" | sed 's/{version}/'$(readconf ".$1.version")'/g')"
fi
if [ $? == 0 ]; then
archive_file_tar=$(find . -name "*.*" -maxdepth 1 | grep -E ".tar|.gz|.tgz" | tail -n1)
archive_file_zip=$(find . -name "*.zip" -maxdepth 1 | tail -n1)
if [ "$archive_file_tar" != "" ]; then
tar -zxf $archive_file_tar && mv $archive_file_tar $SELF_DIR/source/cache/$1.tgz
elif [ "$archive_file_zip" != "" ]; then
unzip $archive_file_zip && mv $archive_file_zip $SELF_DIR/source/cache/$1.zip > /dev/null
else
find . -name "*$1*.*"
echo "Unable to find downloaded file, only support '.tar.gz', '.tgz', '.zip' file!"
exit 1
fi
else
echo "Download $1 failed! (at $?)"
exit 1
fi
fi

View File

@@ -0,0 +1,22 @@
https://pecl.php.net/get/redis-5.3.7.tgz
out=redis-5.3.7.tgz
https://pecl.php.net/get/yaml-2.2.2.tgz
out=yaml-2.2.2.tgz
https://pecl.php.net/get/imagick-3.6.0.tgz
out=imagick-3.6.0.tgz
https://pecl.php.net/get/mongodb-1.14.2.tgz
out=mongodb-1.14.2.tgz
https://pecl.php.net/get/apcu-5.1.22.tgz
out=apcu-5.1.22.tgz
https://pecl.php.net/get/ds-1.4.0.tgz
out=ds-1.4.0.tgz
https://pecl.php.net/get/inotify-3.0.0.tgz
out=inotify-3.0.0.tgz
https://pecl.php.net/get/xlswriter-1.5.2.tgz
out=xlswriter-1.5.2.tgz
https://pecl.php.net/get/zstd-0.12.1.tgz
out=zstd-0.12.1.tgz
https://pecl.php.net/get/event-3.0.8.tgz
out=event-3.0.8.tgz
https://pecl.php.net/get/mcrypt-1.0.5.tgz
out=mcrypt-1.0.5.tgz

View File

@@ -0,0 +1,4 @@
https://pecl.php.net/get/mcrypt-1.0.5.tgz
out=mcrypt-1.0.5.tgz
https://pecl.php.net/get/apcu-5.1.22.tgz
out=apcu-5.1.22.tgz

View File

@@ -0,0 +1,72 @@
https://www.openssl.org/source/openssl-1.1.1p.tar.gz
out=openssl-1.1.1p.tar.gz
https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.16.tar.gz
out=libiconv-1.16.tar.gz
https://gitlab.gnome.org/GNOME/libxml2/-/archive/v2.9.10/libxml2-v2.9.10.tar.gz
out=libxml2-v2.9.10.tar.gz
https://gitlab.gnome.org/GNOME/libxslt/-/archive/v1.1.34/libxslt-v1.1.34.tar.gz
out=libxslt-v1.1.34.tar.gz
https://github.com/google/brotli/archive/refs/tags/v1.0.9.tar.gz
out=brotli-1.0.9.tar.gz
https://c-ares.org/download/c-ares-1.19.0.tar.gz
out=c-ares-1.19.0.tar.gz
https://gmplib.org/download/gmp/gmp-6.2.1.tar.lz
out=gmp-6.2.1.tar.lz
https://mirrors.tuna.tsinghua.edu.cn/gnu/ncurses/ncurses-6.3.tar.gz https://mirrors.ustc.edu.cn/gnu/ncurses/ncurses-6.3.tar.gz https://ftp.gnu.org/pub/gnu/ncurses/ncurses-6.3.tar.gz
out=ncurses-6.3.tar.gz
https://mirrors.tuna.tsinghua.edu.cn/gnu/readline/readline-8.2.tar.gz https://mirrors.ustc.edu.cn/gnu/readline/readline-8.2.tar.gz https://ftp.gnu.org/gnu/readline/readline-8.2.tar.gz
out=readline-8.2.tar.gz
https://pyyaml.org/download/libyaml/yaml-0.2.5.tar.gz
out=yaml-0.2.5.tar.gz
https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz
out=libsodium-1.0.18.tar.gz
https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz
out=bzip2-1.0.8.tar.gz
https://udomain.dl.sourceforge.net/project/libpng/zlib/1.2.11/zlib-1.2.11.tar.gz
out=zlib-1.2.11.tar.gz
https://github.com/lz4/lz4/archive/refs/tags/v1.9.4.tar.gz
out=lz4-v1.9.4.tar.gz
https://github.com/tukaani-project/xz/releases/download/v5.4.1/xz-5.4.1.tar.gz
out=xz-5.4.1.tar.gz
https://github.com/facebook/zstd/releases/download/v1.5.2/zstd-1.5.2.tar.gz
out=zstd-1.5.2.tar.gz
https://libzip.org/download/libzip-1.9.2.tar.gz
out=libzip-1.9.2.tar.gz
https://www.sqlite.org/2021/sqlite-autoconf-3370000.tar.gz
out=sqlite-autoconf-3370000.tar.gz
https://github.com/unicode-org/icu/releases/download/release-60-3/icu4c-60_3-src.tgz
out=icu4c-60_3-src.tgz
https://codeload.github.com/kkos/oniguruma/tar.gz/refs/tags/v6.9.7
out=oniguruma-6.9.7.tar.gz
https://github.com/microsoft/mimalloc/archive/refs/tags/v2.0.7.tar.gz
out=mimalloc-2.0.7.tar.gz
https://codeload.github.com/libjpeg-turbo/libjpeg-turbo/tar.gz/refs/tags/2.1.2
out=libjpeg-turbo-2.1.2.tar.gz
https://nchc.dl.sourceforge.net/project/giflib/giflib-5.2.1.tar.gz
out=giflib-5.2.1.tar.gz
https://nchc.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.gz
out=libpng-1.6.37.tar.gz
https://codeload.github.com/webmproject/libwebp/tar.gz/refs/tags/v1.2.1
out=libwebp-1.2.1.tar.gz
https://download.savannah.gnu.org/releases/freetype/freetype-2.10.4.tar.gz
out=freetype-2.10.4.tar.gz
https://github.com/ImageMagick/ImageMagick/archive/refs/tags/7.1.0-62.tar.gz
out=ImageMagick-v7.1.0-62.tar.gz
https://ftp.gnu.org/gnu/libidn/libidn2-2.3.4.tar.gz
out=libidn2-2.3.4.tar.gz
https://curl.se/download/curl-7.88.0.tar.gz
out=curl-7.88.0.tar.gz
https://ftp.postgresql.org/pub/source/v15.1/postgresql-15.1.tar.gz
out=postgresql-15.1.tar.gz
https://github.com/libffi/libffi/releases/download/v3.4.4/libffi-3.4.4.tar.gz
out=libffi-3.4.4.tar.gz
https://github.com/winlibs/libmcrypt/archive/refs/tags/libmcrypt-2.5.8-3.4.tar.gz
out=libmcrypt-2.5.8-3.4.tar.gz
https://github.com/jmcnamara/libxlsxwriter/archive/refs/tags/RELEASE_1.1.5.tar.gz
out=libxlsxwriter-1.1.5.tar.gz
https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
out=libevent-2.1.12-stable.tar.gz
https://github.com/libuv/libuv/archive/refs/tags/v1.44.2.tar.gz
out=libuv-v1.44.2.tar.gz
https://github.com/php/php-src/archive/refs/tags/php-8.1.12.tar.gz
out=php-8.1.12.tar.gz

View File

@@ -0,0 +1,4 @@
https://github.com/winlibs/libmcrypt/archive/refs/tags/libmcrypt-2.5.8-3.4.tar.gz
out=libmcrypt-2.5.8-3.4.tar.gz
https://gmplib.org/download/gmp/gmp-6.2.1.tar.lz
out=gmp-6.2.1.tar.lz

44
docker/extensions.txt Executable file
View File

@@ -0,0 +1,44 @@
# Start with '#' is comments
# Start with '^' is deselecting extensions, which is not installed as default
# Each line just leave the extension name or ^ character
bcmath
calendar
ctype
curl
dom
event
exif
fileinfo
filter
gd
hash
iconv
inotify
json
libxml
mbstring
mongodb
mysqlnd
openssl
pcntl
pdo
pdo_mysql
pdo_sqlite
phar
posix
^protobuf
^readline
redis
shmop
simplexml
soap
sockets
sqlite3
swoole
tokenizer
xml
xmlreader
xmlwriter
zlib
zip

63
docker/fast-compiler.sh Executable file
View File

@@ -0,0 +1,63 @@
#!/bin/sh
# This script needs alpine linux system.
self_dir=$(cd "$(dirname "$0")";pwd)
test "$VER_PHP" = "" && VER_PHP="8.1.7"
test "$USE_BACKUP" = "" && USE_BACKUP="no"
test "$ALL_EXTENSIONS" = "" && ALL_EXTENSIONS="all"
LINK_APK_REPO='mirrors.ustc.edu.cn'
LINK_APK_REPO_BAK='dl-cdn.alpinelinux.org'
if [ "${USE_BACKUP}" = "yes" ]; then \
echo "Using backup address..." && sleep 1s
LINK_APK_REPO=${LINK_APK_REPO_BAK}
else
echo "Using original address..." && sleep 1s
fi
sed -i 's/dl-cdn.alpinelinux.org/'${LINK_APK_REPO}'/g' /etc/apk/repositories
# build requirements
apk add bash file wget cmake gcc g++ jq autoconf git libstdc++ linux-headers make m4 libgcc binutils ncurses dialog
# php zlib dependencies
apk add zlib-dev zlib-static
# php mbstring dependencies
apk add oniguruma-dev
# php openssl dependencies
apk add openssl-libs-static openssl-dev openssl
# php gd dependencies
apk add libpng-dev libpng-static
# curl c-ares dependencies
apk add c-ares-static c-ares-dev
# php event dependencies
apk add libevent libevent-dev libevent-static
# php sqlite3 dependencies
apk add sqlite sqlite-dev sqlite-libs sqlite-static
# php libzip dependencies
apk add bzip2-dev bzip2-static bzip2
# php micro ffi dependencies
apk add libffi libffi-dev
# php gd event parent dependencies
apk add zstd-static
# php readline dependencies
apk add readline-static ncurses-static readline-dev
test "$USE_BACKUP" = "no" && PROMPT_1="mirror" || PROMPT_1="original"
$self_dir/download.sh swoole ${USE_BACKUP} && \
$self_dir/download.sh inotify ${USE_BACKUP} && \
$self_dir/download.sh mongodb ${USE_BACKUP} && \
$self_dir/download.sh event ${USE_BACKUP} && \
$self_dir/download.sh redis ${USE_BACKUP} && \
$self_dir/download.sh libxml2 ${USE_BACKUP} && \
$self_dir/download.sh xz ${USE_BACKUP} && \
$self_dir/download.sh protobuf ${USE_BACKUP} && \
$self_dir/download.sh curl ${USE_BACKUP} && \
$self_dir/download.sh libzip ${USE_BACKUP} && \
$self_dir/download.sh libiconv ${USE_BACKUP} && \
$self_dir/download-git.sh dixyes/phpmicro phpmicro ${USE_BACKUP} && \
$self_dir/compile-deps.sh && \
$self_dir/compile-php.sh $PROMPT_1 $VER_PHP $ALL_EXTENSIONS /dist/

View File

@@ -1,70 +0,0 @@
# Extension List
> - yes: supported and tested
> - untested: supported but not tested
> - empty: not supported yet
> - faulty with issue link: not supported yet due to issue
| | Linux | macOS | Windows |
|------------|-------|--------------------------------------------------------------------|---------|
| bcmath | | yes | |
| bz2 | | untested | |
| calendar | | yes | |
| ctype | | yes | |
| curl | | yes | |
| date | | yes | |
| dom | | untested | |
| event | | | |
| exif | | yes | |
| filter | | yes | |
| fileinfo | | | |
| ftp | | yes | |
| gd | | untested | |
| gmp | | untested | |
| hash | | yes | |
| iconv | | | |
| inotify | | | |
| json | | yes | |
| libxml | | yes | |
| mbstring | | yes | |
| mcrypt | | [faulty](https://github.com/crazywhalecc/static-php-cli/issues/32) | |
| mongodb | | | |
| mysqli | | | |
| mysqlnd | | yes | |
| openssl | | yes | |
| pcntl | | untested | |
| pcre | | yes | |
| pdo | | yes | |
| pdo_mysql | | yes | |
| pdo_sqlite | | yes | |
| pdo_pgsql | | | |
| phar | | yes | |
| posix | | yes | |
| protobuf | | | |
| readline | | | |
| redis | | yes | |
| Reflection | | yes | |
| session | | yes | |
| shmop | | | |
| simplexml | | untested | |
| soap | | | |
| sockets | | | |
| sqlite3 | | untested | |
| swow | | | |
| swoole | | [faulty](https://github.com/crazywhalecc/static-php-cli/issues/32) | |
| tokenizer | | yes | |
| xml | | yes | |
| xmlreader | | untested | |
| xmlwriter | | untested | |
| zip | | yes | |
| zlib | | yes | |
## Additional Requirements
- phpmicro requires PHP >= 8.0
- swoole >= 5.0 requires PHP >= 8.0
- swow requires PHP >= 8.0
## Bugs
See [#32](https://github.com/crazywhalecc/static-php-cli/issues/32).

69
install-runtime.sh Normal file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
function download_file() {
downloader="wget"
type wget >/dev/null 2>&1 || { downloader="curl"; }
if [ "$downloader" = "wget" ]; then
_down_prefix="O"
else
_down_prefix="o"
fi
_down_symbol=0
if [ ! -f "$2" ]; then
echo $1
$downloader "$1" -$_down_prefix "$2" >/dev/null 2>&1 && \
echo "完成!" && _down_symbol=1
else
echo "已存在!" && _down_symbol=1
fi
if [ $_down_symbol == 0 ]; then
echo "失败!请检查网络连接!"
rm -rf "$2"
return 1
fi
return 0
}
function test_composer_and_php() {
succ=$("$(pwd)/runtime/composer" -n about | grep Manage)
if [ "$succ" = "" ]; then
echo "Download PHP binary and composer failed!"
return 1
fi
return 0
}
if [ "$(uname -s)" != "Linux" ]; then
echo "Only support Linux!!!"
exit 1
fi
ZM_PHP_VERSION="7.4"
if [ "$ZM_DOWN_PHP_VERSION" != "" ]; then
ZM_PHP_VERSION="$ZM_DOWN_PHP_VERSION"
echo "Using custom PHP version: $ZM_PHP_VERSION"
fi
mkdir "$(pwd)/runtime" >/dev/null 2>&1
if [ ! -f "$(pwd)/runtime/php" ]; then
download_file "https://dl.zhamao.xin/php-bin/down.php?php_ver=$ZM_PHP_VERSION&arch=$(uname -m)" "$(pwd)/runtime/php.tar.gz"
if [ $? -ne 0 ]; then
exit 1
fi
tar -xf "$(pwd)/runtime/php.tar.gz" -C "$(pwd)/runtime/"
fi
if [ ! -f "$(pwd)/runtime/composer" ]; then
download_file "https://mirrors.aliyun.com/composer/composer.phar" "$(pwd)/runtime/composer.phar"
if [ $? -ne 0 ]; then
exit 1
fi
echo '$(dirname $0)/php $(dirname $0)/composer.phar $@' > $(pwd)/runtime/composer
chmod +x $(pwd)/runtime/composer
test_composer_and_php
fi
if [ $? -ne 0 ]; then
exit 1
fi
echo "成功下载!" && \
echo -e "PHP使用\truntime/php -v" && \
echo -e "Composer使用\truntime/composer"

View File

@@ -1,12 +0,0 @@
parameters:
reportUnmatchedIgnoredErrors: false
level: 4
paths:
- ./src/
ignoreErrors:
- '#Constant .* not found#'
- '#Unsafe usage of new static#'
- '#class Fiber#'
- '#Attribute class JetBrains\\PhpStorm\\ArrayShape does not exist#'
dynamicConstantNames:
- PHP_OS_FAMILY

View File

@@ -1,52 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC;
use SPC\command\DeployCommand;
use SPC\store\FileSystem;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
/**
* spc 应用究级入口
*/
class ConsoleApplication extends Application
{
public const VERSION = '2.0-alpha1';
/**
* @throws \ReflectionException
* @throws exception\FileSystemException
*/
public function __construct()
{
parent::__construct('static-php-cli', self::VERSION);
global $argv;
// 生产环境不显示详细的调试错误,只使用 symfony console 自带的错误显示
$this->setCatchExceptions(file_exists(ROOT_DIR . '/.prod') || !in_array('--debug', $argv));
// 通过扫描目录 src/static-php-cli/command/ 添加子命令
$commands = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/command', 'SPC\\command');
$this->addCommands(array_map(function ($x) { return new $x(); }, array_filter($commands, function ($y) {
if (is_a($y, DeployCommand::class, true) && (class_exists('\\Phar') && \Phar::running() || !class_exists('\\Phar'))) {
return false;
}
$reflection = new \ReflectionClass($y);
return !$reflection->isAbstract() && !$reflection->isInterface();
})));
}
/**
* 重载以去除一些不必要的默认命令
*/
protected function getDefaultCommands(): array
{
return [new HelpCommand(), new ListCommand()];
}
}

View File

@@ -1,227 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\util\DependencyUtil;
abstract class BuilderBase
{
/** @var bool 是否启用 ZTS 线程安全 */
public bool $zts = false;
/** @var string 编译目标架构 */
public string $arch;
/** @var string GNU 格式的编译目标架构 */
public string $gnu_arch;
/** @var int 编译进程数 */
public int $concurrency = 1;
/** @var array<string, LibraryBase> 要编译的 libs 列表 */
protected array $libs = [];
/** @var array<string, Extension> 要编译的扩展列表 */
protected array $exts = [];
/** @var bool 本次编译是否只编译 libs不编译 PHP */
protected bool $libs_only = false;
/**
* 构建指定列表的 libs
*
* @throws RuntimeException
* @throws FileSystemException
*/
public function buildLibs(array $libraries): void
{
// 通过扫描目录查找 lib
$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;
}
}
// 如果传入了空则默认检查和安置所有支持的liblibraries为要build的support_lib_list为支持的列表
if ($libraries === [] && $this->isLibsOnly()) {
$libraries = array_keys($support_lib_list);
}
// 排序 libs根据依赖计算一个新的列表出来
$libraries = DependencyUtil::getLibsByDeps($libraries);
// 这里筛选 libraries比如纯静态模式排除掉ffi
if (defined('BUILD_ALL_STATIC') && BUILD_ALL_STATIC) {
$k = array_search('libffi', $libraries, true);
$k !== false && array_splice($libraries, $k, 1);
}
// 过滤不支持的库后添加
foreach ($libraries as $library) {
if (!isset($support_lib_list[$library])) {
throw new RuntimeException('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);
}
// 统计还没 fetch 到本地的库
$this->checkLibsSource();
// 计算依赖,经过这里的遍历,如果没有抛出异常,说明依赖符合要求,可以继续下面的
foreach ($this->libs as $lib) {
$lib->calcDependency();
}
foreach ($this->libs as $lib) {
match ($lib->tryBuild()) {
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'),
};
}
}
/**
* 添加要编译的 Lib 库
*
* @param LibraryBase $library Lib 库对象
*/
public function addLib(LibraryBase $library): void
{
$this->libs[$library::NAME] = $library;
}
/**
* 获取要编译的 Lib 库对象
*
* @param string $name 库名称
*/
public function getLib(string $name): ?LibraryBase
{
return $this->libs[$name] ?? null;
}
/**
* 添加要编译的扩展
*
* @param Extension $extension 扩展对象
*/
public function addExt(Extension $extension): void
{
$this->exts[$extension->getName()] = $extension;
}
/**
* 获取要编译的扩展对象
*
* @param string $name 扩展名称
*/
public function getExt(string $name): ?Extension
{
return $this->exts[$name] ?? null;
}
/**
* 设置本次 Builder 是否为仅编译库的模式
*/
public function setLibsOnly(bool $status = true): void
{
$this->libs_only = $status;
}
/**
* 检验 ext 扩展列表是否合理,并声明 Extension 对象,检查扩展的依赖
*
* @throws FileSystemException
* @throws RuntimeException
*/
public function proveExts(array $extensions): void
{
if (defined('BUILD_ALL_STATIC') && BUILD_ALL_STATIC) {
$k = array_search('ffi', $extensions, true);
$k !== false && array_splice($extensions, $k, 1);
}
foreach ($extensions as $extension) {
$ext = new Extension($extension, $this);
$this->addExt($ext);
}
foreach ($this->exts as $ext) {
// 检查下依赖就行了,作用是导入依赖给 Extension 对象,今后可以对库依赖进行选择性处理
$ext->checkDependency();
}
}
/**
* 开始构建 PHP
* 构建 micro 的规则:
* - BUILD_MICRO_NONE(默认):只编译 cli
* - BUILD_MICRO_ONLY只编译 micro
* - BUILD_MICRO_BOTH同时编译 micro 和 cli
*
* @param int $build_micro_rule 规则
* @param bool $with_clean 是否为新构建?
* @param bool $bloat 保留
*/
abstract public function buildPHP(int $build_micro_rule = BUILD_MICRO_NONE, bool $with_clean = false, bool $bloat = false);
/**
* 生成依赖的扩展编译启用参数
* 例如 --enable-mbstring 等
*
* @throws RuntimeException
* @throws FileSystemException
*/
public function makeExtensionArgs(): string
{
$ret = [];
foreach ($this->exts as $ext) {
$ret[] = $ext->getConfigureArg();
}
logger()->info('Using configure: ' . implode(' ', $ret));
return implode(' ', $ret);
}
/**
* 返回是否只编译 libs 的模式
*/
public function isLibsOnly(): bool
{
return $this->libs_only;
}
/**
* 检查是否存在 lib 库对应的源码,如果不存在,则抛出异常
*
* @throws RuntimeException
*/
protected function checkLibsSource(): void
{
$not_downloaded = [];
foreach ($this->libs as $lib) {
if (!file_exists($lib->getSourceDir())) {
$not_downloaded[] = $lib::NAME;
}
}
if ($not_downloaded !== []) {
throw new RuntimeException(
'"' . implode(', ', $not_downloaded) .
'" totally ' . count($not_downloaded) .
' source' . (count($not_downloaded) === 1 ? '' : 's') .
' not downloaded, maybe you need to "fetch" ' . (count($not_downloaded) === 1 ? 'it' : 'them') . ' first?'
);
}
}
}

View File

@@ -1,42 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder;
use SPC\builder\linux\LinuxBuilder;
use SPC\builder\macos\MacOSBuilder;
use SPC\builder\windows\WindowsBuilder;
use SPC\exception\RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
/**
* 用于生成对应系统环境的 Builder 对象的类
*/
class BuilderProvider
{
/**
* @throws RuntimeException
*/
public static function makeBuilderByInput(InputInterface $input): BuilderBase
{
return match (PHP_OS_FAMILY) {
// 'Windows' => new WindowsBuilder(
// binary_sdk_dir: $input->getOption('with-sdk-binary-dir'),
// vs_ver: $input->getOption('vs-ver'),
// arch: $input->getOption('arch'),
// ),
'Darwin' => new MacOSBuilder(
cc: $input->getOption('cc'),
cxx: $input->getOption('cxx'),
arch: $input->getOption('arch'),
),
// 'Linux' => new LinuxBuilder(
// cc: $input->getOption('cc'),
// cxx: $input->getOption('cxx'),
// arch: $input->getOption('arch'),
// ),
default => throw new RuntimeException('Current OS is not supported yet'),
};
}
}

View File

@@ -1,311 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\Config;
class Extension
{
protected array $dependencies = [];
/**
* @throws RuntimeException
* @throws FileSystemException
*/
public function __construct(protected string $name, protected BuilderBase $builder)
{
$ext_type = Config::getExt($this->name, 'type');
$unix_only = Config::getExt($this->name, 'unix-only', false);
$windows_only = Config::getExt($this->name, 'windows-only', false);
if (PHP_OS_FAMILY !== 'Windows' && $windows_only) {
throw new RuntimeException("{$ext_type} extension {$name} is not supported on Linux and macOS platform");
}
if (PHP_OS_FAMILY === 'Windows' && $unix_only) {
throw new RuntimeException("{$ext_type} extension {$name} is not supported on Windows platform");
}
}
/**
* 获取开启该扩展的 PHP 编译添加的参数
*
* @throws FileSystemException|RuntimeException
*/
public function getConfigureArg(): string
{
$arg = $this->getEnableArg();
switch (PHP_OS_FAMILY) {
case 'Windows':
$arg .= $this->getWindowsConfigureArg();
break;
case 'Darwin':
case 'Linux':
$arg .= $this->getUnixConfigureArg();
break;
}
return $arg;
}
/**
* 根据 ext 的 arg-type 获取对应开启的参数,一般都是 --enable-xxx 和 --with-xxx
*
* @throws FileSystemException
* @throws RuntimeException
*/
public function getEnableArg(): string
{
$_name = str_replace('_', '-', $this->name);
return match ($arg_type = Config::getExt($this->name, 'arg-type', 'enable')) {
'enable' => '--enable-' . $_name,
'with' => '--with-' . $_name,
'none', 'custom' => '',
default => throw new RuntimeException("argType does not accept {$arg_type}, use [enable/with] ."),
};
}
/**
* 导出当前扩展依赖的所有 lib 库生成的 .a 静态编译库文件,以字符串形式导出,用空格分割
*/
public function getLibFilesString(): string
{
$ret = array_map(
fn ($x) => $x->getStaticLibFiles(),
$this->getLibraryDependencies(recursive: true)
);
return implode(' ', $ret);
}
/**
* 检查下依赖就行了,作用是导入依赖给 Extension 对象,今后可以对库依赖进行选择性处理
*
* @throws RuntimeException
* @throws FileSystemException
*/
public function checkDependency(): static
{
foreach (Config::getExt($this->name, 'lib-depends', []) as $name) {
$this->addLibraryDependency($name);
}
foreach (Config::getExt($this->name, 'lib-suggests', []) as $name) {
$this->addLibraryDependency($name, true);
}
foreach (Config::getExt($this->name, 'ext-depends', []) as $name) {
$this->addExtensionDependency($name);
}
foreach (Config::getExt($this->name, 'ext-suggests', []) as $name) {
$this->addExtensionDependency($name, true);
}
return $this;
}
public function getExtensionDependency(): array
{
return array_filter($this->dependencies, fn ($x) => $x instanceof Extension);
}
public function getName(): string
{
return $this->name;
}
/**
* @throws RuntimeException
*/
protected function addLibraryDependency(string $name, bool $optional = false): void
{
$depLib = $this->builder->getLib($name);
if (!$depLib) {
if (!$optional) {
throw new RuntimeException("extension {$this->name} requires library {$name}");
}
logger()->info("enabling {$this->name} without library {$name}");
} else {
$this->dependencies[] = $depLib;
}
}
/**
* @throws RuntimeException
*/
protected function addExtensionDependency(string $name, bool $optional = false): void
{
$depExt = $this->builder->getExt($name);
if (!$depExt) {
if (!$optional) {
throw new RuntimeException("{$this->name} requires extension {$name}");
}
logger()->info("enabling {$this->name} without extension {$name}");
} else {
$this->dependencies[] = $depExt;
}
}
private function getWindowsConfigureArg(): string
{
$arg = '';
switch ($this->name) {
case 'redis':
// $arg = '--enable-redis';
// if ($this->builder->getLib('zstd')) {
// $arg .= ' --enable-redis-zstd --with-libzstd ';
// }
break;
case 'xml':
case 'soap':
case 'xmlreader':
case 'xmlwriter':
case 'dom':
$arg .= ' --with-libxml ';
break;
case 'swow':
if ($this->builder->getLib('openssl')) {
$arg .= ' --enable-swow-ssl';
}
if ($this->builder->getLib('curl')) {
$arg .= ' --enable-swow-curl';
}
break;
}
return $arg;
}
private function getUnixConfigureArg(): string
{
$arg = '';
switch ($this->name) {
/*case 'event':
$arg = ' --with-event-core --with-event-libevent-dir="' . BUILD_ROOT_PATH . '"';
if ($this->builder->getLib('openssl')) {
$arg .= ' --with-event-openssl --with-openssl-dir="' . BUILD_ROOT_PATH . '"';
}
break;*/
case 'mbstring':
$arg = ' --disable-mbregex ONIG_CFLAGS=-I"' . BUILD_ROOT_PATH . '" ' .
'ONIG_LIBS="' . $this->getLibFilesString() . '" ';
break;
case 'gmp':
$arg = ' --with-gmp="' . BUILD_ROOT_PATH . '" ';
break;
case 'sqlite3':
$arg = ' --with-sqlite3="' . BUILD_ROOT_PATH . '" ' .
'SQLITE_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '" ' .
'SQLITE_LIBS="' . $this->getLibFilesString() . '" ';
break;
case 'redis':
$arg = ' --enable-redis --disable-redis-session';
if ($this->builder->getLib('zstd')) {
$arg .= ' --enable-redis-zstd --with-libzstd="' . BUILD_ROOT_PATH . '" ';
}
break;
case 'yaml':
$arg .= ' --with-yaml="' . BUILD_ROOT_PATH . '" ';
break;
case 'zstd':
$arg .= ' --with-libzstd';
break;
case 'bz2':
$arg = ' --with-bz2="' . BUILD_ROOT_PATH . '" ';
break;
case 'openssl':
$arg .= ' ' .
'OPENSSL_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '" ' .
'OPENSSL_LIBS="' . $this->getLibFilesString() . '" ';
break;
case 'curl':
$arg .= ' ' .
'CURL_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '" ' .
'CURL_LIBS="' . $this->getLibFilesString() . '" ';
break;
case 'gd':
if ($this->builder->getLib('freetype')) {
$arg .= ' --with-freetype ' .
'FREETYPE2_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '/freetype2" ' .
'FREETYPE2_LIBS="' . $this->getLibFilesString() . '" ';
}
$arg .= ' ' .
'PNG_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '" ' .
'PNG_LIBS="' . $this->getLibFilesString() . '" ';
break;
// TODO: other libraries
case 'phar':
case 'zlib':
$arg .= ' ' .
'ZLIB_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '" ' .
'ZLIB_LIBS="' . $this->getLibFilesString() . '" ';
break;
case 'xml': // xml may use expat
if ($this->getLibraryDependencies()['expat'] ?? null) {
$arg .= ' --with-expat="' . BUILD_ROOT_PATH . '" ' .
'EXPAT_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '" ' .
'EXPAT_LIBS="' . $this->getLibFilesString() . '" ';
break;
}
// no break
case 'soap':
case 'xmlreader':
case 'xmlwriter':
case 'dom':
$arg .= ' --with-libxml="' . BUILD_ROOT_PATH . '" ' .
'LIBXML_CFLAGS=-I"' . realpath('include/libxml2') . '" ' .
'LIBXML_LIBS="' . $this->getLibFilesString() . '" ';
break;
case 'ffi':
$arg .= ' ' .
'FFI_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '" ' .
'FFI_LIBS="' . $this->getLibFilesString() . '" ';
break;
case 'zip':
$arg .= ' ' .
'LIBZIP_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '" ' .
'LIBZIP_LIBS="' . $this->getLibFilesString() . '" ';
break;
case 'mbregex':
$arg .= ' ' .
'ONIG_CFLAGS=-I"' . BUILD_INCLUDE_PATH . '" ' .
'ONIG_LIBS="' . $this->getLibFilesString() . '" ';
break;
case 'swow':
$arg .= $this->builder->getLib('openssl') ? ' --enable-swow-ssl' : ' --disable-swow-ssl';
$arg .= $this->builder->getLib('curl') ? ' --enable-swow-curl' : ' --disable-swow-curl';
break;
case 'swoole':
if ($this->builder->getLib('openssl')) {
$arg .= ' --enable-openssl';
} else {
$arg .= ' --disable-openssl --without-openssl';
}
}
return $arg;
}
private function getLibraryDependencies(bool $recursive = false): array
{
$ret = array_filter($this->dependencies, fn ($x) => $x instanceof LibraryBase);
if (!$recursive) {
return $ret;
}
$deps = [];
$added = 1;
while ($added !== 0) {
$added = 0;
foreach ($ret as $depName => $dep) {
foreach ($dep->getDependencies(true) as $depdepName => $depdep) {
if (!in_array($depdepName, array_keys($deps), true)) {
$deps[$depdepName] = $depdep;
++$added;
}
}
if (!in_array($depName, array_keys($deps), true)) {
$deps[$depName] = $dep;
}
}
}
return $deps;
}
}

View File

@@ -1,190 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\Config;
/**
* Lib 库的基类操作对象
*/
abstract class LibraryBase
{
/** @var string lib 依赖名称,必须重写 */
public const NAME = 'unknown';
/** @var string lib 依赖的根目录 */
protected string $source_dir;
/** @var array 依赖列表 */
protected array $dependencies = [];
/**
* @throws RuntimeException
*/
public function __construct(?string $source_dir = null)
{
if (static::NAME === 'unknown') {
throw new RuntimeException('no unknown!!!!!');
}
$this->source_dir = $source_dir ?? (SOURCE_PATH . '/' . static::NAME);
}
/**
* 获取 lib 库的根目录
*/
public function getSourceDir(): string
{
return $this->source_dir;
}
/**
* 获取当前 lib 库的所有依赖列表
*
* @param bool $recursive 是否递归获取(默认为 False
* @return array<string, LibraryBase> 依赖的 Map
*/
public function getDependencies(bool $recursive = false): array
{
// 非递归情况下直接返回通过 addLibraryDependency 方法添加的依赖
if (!$recursive) {
return $this->dependencies;
}
// 下面为递归获取依赖列表,根据依赖顺序
$deps = [];
$added = 1;
while ($added !== 0) {
$added = 0;
foreach ($this->dependencies as $depName => $dep) {
foreach ($dep->getDependencies(true) as $depdepName => $depdep) {
if (!in_array($depdepName, array_keys($deps), true)) {
$deps[$depdepName] = $depdep;
++$added;
}
}
if (!in_array($depName, array_keys($deps), true)) {
$deps[$depName] = $dep;
}
}
}
return $deps;
}
/**
* 计算依赖列表,不符合依赖将抛出异常
*
* @throws RuntimeException
* @throws FileSystemException
*/
public function calcDependency(): void
{
// 先从配置文件添加依赖,这里根据不同的操作系统分别选择不同的元信息
/*
选择规则:
如果是 Windows 系统,则依次尝试有无 lib-depends-windows、lib-depends-win、lib-depends。
如果是 macOS 系统,则依次尝试 lib-depends-darwin、lib-depends-unix、lib-depends。
如果是 Linux 系统,则依次尝试 lib-depends-linux、lib-depends-unix、lib-depends。
*/
foreach (Config::getLib(static::NAME, 'lib-depends', []) as $dep_name) {
$this->addLibraryDependency($dep_name);
}
foreach (Config::getLib(static::NAME, 'lib-suggests', []) as $dep_name) {
$this->addLibraryDependency($dep_name, true);
}
}
/**
* 获取当前库编译出来获取到的静态库文件列表
*
* @return string[] 获取编译出来后的需要的静态库文件列表
* @throws FileSystemException
* @throws RuntimeException
*/
public function getStaticLibs(): array
{
return Config::getLib(static::NAME, 'static-libs', []);
}
/**
* 获取当前 lib 编译出来的 C Header 文件列表
*
* @return string[] 获取编译出来后需要的 C Header 文件列表
* @throws FileSystemException
* @throws RuntimeException
*/
public function getHeaders(): array
{
return Config::getLib(static::NAME, 'headers', []);
}
/**
* 证明该库是否已编译好且就绪,如果没有就绪,内部会调用 build 来进行构建该库
*
* @throws RuntimeException
* @throws FileSystemException
*/
public function tryBuild(bool $force_build = false): int
{
// 传入 true表明直接编译
if ($force_build) {
$this->build();
return BUILD_STATUS_OK;
}
// 看看这些库是不是存在,如果不存在,则调用编译并返回结果状态
foreach ($this->getStaticLibs() as $name) {
if (!file_exists(BUILD_LIB_PATH . "/{$name}")) {
$this->tryBuild(true);
return BUILD_STATUS_OK;
}
}
// 头文件同理
foreach ($this->getHeaders() as $name) {
if (!file_exists(BUILD_INCLUDE_PATH . "/{$name}")) {
$this->tryBuild(true);
return BUILD_STATUS_OK;
}
}
// 到这里说明所有的文件都存在,就跳过编译
return BUILD_STATUS_ALREADY;
}
/**
* 获取构建当前 lib 的 Builder 对象
*/
abstract public function getBuilder(): BuilderBase;
/**
* 构建该库需要调用的命令和操作
*
* @throws RuntimeException
*/
abstract protected function build();
/**
* 添加 lib 库的依赖库
*
* @param string $name 依赖名称
* @param bool $optional 是否是可选依赖(默认为 False
* @throws RuntimeException
*/
protected function addLibraryDependency(string $name, bool $optional = false): void
{
// Log::i("add $name as dep of {$this->name}");
$dep_lib = $this->getBuilder()->getLib($name);
if (!$dep_lib) {
if (!$optional) {
throw new RuntimeException(static::NAME . " requires library {$name}");
}
logger()->debug('enabling ' . static::NAME . " without {$name}");
} else {
$this->dependencies[$name] = $dep_lib;
}
}
}

View File

@@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder;
interface LibraryInterface
{
public function getName(): string;
}

View File

@@ -1,273 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\macos;
use SPC\builder\BuilderBase;
use SPC\builder\macos\library\MacOSLibraryBase;
use SPC\builder\traits\UnixBuilderTrait;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\util\Patcher;
/**
* macOS 系统环境下的构建器
* 源于 Config但因为感觉叫 Config 不太合适,就换成了 Builder
*/
class MacOSBuilder extends BuilderBase
{
/** 编译的 Unix 工具集 */
use UnixBuilderTrait;
/** @var string[] MacOS 环境下编译依赖的命令 */
public const REQUIRED_COMMANDS = ['make', 'bison', 'flex', 'pkg-config', 'git', 'autoconf', 'automake', 'tar', 'unzip', 'xz', 'gzip', 'bzip2', 'cmake'];
/** @var bool 标记是否 patch 了 phar */
private bool $phar_patched = false;
/**
* @throws RuntimeException
*/
public function __construct(?string $cc = null, ?string $cxx = null, ?string $arch = null)
{
// 如果是 Debug 模式,才使用 set -x 显示每条执行的命令
$this->set_x = defined('DEBUG_MODE') ? 'set -x' : 'true';
// 初始化一些默认参数
$this->cc = $cc ?? 'clang';
$this->cxx = $cxx ?? 'clang++';
$this->arch = $arch ?? php_uname('m');
$this->gnu_arch = arch2gnu($this->arch);
// 根据 CPU 线程数设置编译进程数
$this->concurrency = SystemUtil::getCpuCount();
// 设置 cflags
$this->arch_c_flags = SystemUtil::getArchCFlags($this->arch);
$this->arch_cxx_flags = SystemUtil::getArchCFlags($this->arch);
// 设置 cmake
$this->cmake_toolchain_file = SystemUtil::makeCmakeToolchainFile('Darwin', $this->arch, $this->arch_c_flags);
// 设置 configure 依赖的环境变量
$this->configure_env =
'PKG_CONFIG_PATH="' . BUILD_LIB_PATH . '/pkgconfig/" ' .
"CC='{$this->cc}' " .
"CXX='{$this->cxx}' " .
"CFLAGS='{$this->arch_c_flags} -Wimplicit-function-declaration'";
// 保存丢失的命令
$missing = [];
foreach (self::REQUIRED_COMMANDS as $cmd) {
if (SystemUtil::findCommand($cmd) === null) {
$missing[] = $cmd;
}
}
if (!empty($missing)) {
throw new RuntimeException('missing system commands: ' . implode(', ', $missing));
}
// 创立 pkg-config 和放头文件的目录
f_mkdir(BUILD_LIB_PATH . '/pkgconfig', recursive: true);
f_mkdir(BUILD_INCLUDE_PATH, recursive: true);
}
/**
* 生成库构建采用的 autoconf 参数列表
*
* @param string $name 要构建的 lib 库名,传入仅供输出日志
* @param array $lib_specs 依赖的 lib 库的 autoconf 文件
*/
public function makeAutoconfArgs(string $name, array $lib_specs): string
{
$ret = '';
foreach ($lib_specs as $libName => $arr) {
$lib = $this->getLib($libName);
$arr = $arr ?? [];
$disableArgs = $arr[0] ?? null;
$prefix = $arr[1] ?? null;
if ($lib instanceof MacOSLibraryBase) {
logger()->info("{$name} \033[32;1mwith\033[0;1m {$libName} support");
$ret .= $lib->makeAutoconfEnv($prefix) . ' ';
} else {
logger()->info("{$name} \033[31;1mwithout\033[0;1m {$libName} support");
$ret .= ($disableArgs ?? "--with-{$libName}=no") . ' ';
}
}
return rtrim($ret);
}
/**
* 返回 macOS 系统依赖的框架列表
*
* @param bool $asString 是否以字符串形式返回(默认为 False
*/
public function getFrameworks(bool $asString = false): array|string
{
$libs = [];
// reorder libs
foreach ($this->libs as $lib) {
foreach ($lib->getDependencies() as $dep) {
$libs[] = $dep;
}
$libs[] = $lib;
}
$frameworks = [];
/** @var MacOSLibraryBase $lib */
foreach ($libs as $lib) {
array_push($frameworks, ...$lib->getFrameworks());
}
if ($asString) {
return implode(' ', array_map(fn ($x) => "-framework {$x}", $frameworks));
}
return $frameworks;
}
/**
* @throws RuntimeException
* @throws FileSystemException
*/
public function buildPHP(int $build_micro_rule = BUILD_MICRO_NONE, bool $with_clean = false, bool $bloat = false): void
{
$extra_libs = $this->getFrameworks(true) . ' ' . ($this->getExt('swoole') ? '-lc++ ' : '');
if (!$bloat) {
$extra_libs .= implode(' ', $this->getAllStaticLibFiles());
} else {
logger()->info('bloat linking');
$extra_libs .= implode(
' ',
array_map(
fn ($x) => "-Wl,-force_load,{$x}",
array_filter($this->getAllStaticLibFiles())
)
);
}
// patch before configure
Patcher::patchPHPBeforeConfigure($this);
f_passthru(
$this->set_x . ' && ' .
'cd ' . SOURCE_PATH . '/php-src && ' .
'./buildconf --force'
);
Patcher::patchPHPConfigure($this);
if ($this->getLib('libxml2') || $this->getExt('iconv')) {
$extra_libs .= ' -liconv';
}
f_passthru(
$this->set_x . ' && ' .
'cd ' . SOURCE_PATH . '/php-src && ' .
'./configure ' .
'--prefix= ' .
'--with-valgrind=no ' . // 不检测内存泄漏
'--enable-shared=no ' .
'--enable-static=yes ' .
"--host={$this->gnu_arch}-apple-darwin " .
"CFLAGS='{$this->arch_c_flags} -Werror=unknown-warning-option' " .
'--disable-all ' .
'--disable-cgi ' .
'--disable-phpdbg ' .
'--enable-cli ' .
'--enable-micro ' .
($this->zts ? '--enable-zts' : '') . ' ' .
$this->makeExtensionArgs() . ' ' .
$this->configure_env
);
if ($with_clean) {
logger()->info('cleaning up');
f_passthru(
$this->set_x . ' && ' .
'cd ' . SOURCE_PATH . '/php-src && ' .
'make clean'
);
}
switch ($build_micro_rule) {
case BUILD_MICRO_NONE:
logger()->info('building cli');
$this->buildCli($extra_libs);
break;
case BUILD_MICRO_ONLY:
logger()->info('building micro');
$this->buildMicro($extra_libs);
break;
case BUILD_MICRO_BOTH:
logger()->info('building cli and micro');
$this->buildCli($extra_libs);
$this->buildMicro($extra_libs);
break;
}
if (php_uname('m') === $this->arch) {
$this->sanityCheck($build_micro_rule);
}
if ($this->phar_patched) {
f_passthru('cd ' . SOURCE_PATH . '/php-src && patch -p1 -R < sapi/micro/patches/phar.patch');
}
}
/**
* 构建 phpmicro
*
* @throws RuntimeException
*/
public function buildMicro(string $extra_libs): void
{
if ($this->getPHPVersionID() < 80000) {
throw new RuntimeException('phpmicro only support PHP >= 8.0!');
}
if ($this->getExt('phar')) {
$this->phar_patched = true;
try {
f_passthru('cd ' . SOURCE_PATH . '/php-src && patch -p1 < sapi/micro/patches/phar.patch');
} catch (RuntimeException $e) {
logger()->error('failed to patch phat due to patch exit with code ' . $e->getCode());
$this->phar_patched = false;
}
}
f_passthru(
$this->set_x . ' && ' .
'cd ' . SOURCE_PATH . '/php-src && ' .
"make -j{$this->concurrency} " .
'EXTRA_CFLAGS="-g -Os -fno-ident" ' .
"EXTRA_LIBS=\"{$extra_libs} -lresolv\" " .
'STRIP="dsymutil -f " ' .
// TODO: comment things
'micro'
);
}
/**
* 构建 cli
*
* @throws RuntimeException
*/
public function buildCli(string $extra_libs): void
{
f_passthru(
$this->set_x . ' && ' .
'cd ' . SOURCE_PATH . '/php-src && ' .
"make -j{$this->concurrency} " .
'EXTRA_CFLAGS="-g -Os -fno-ident" ' . // 生成调试信息、优化编译后的尺寸、禁用标识符(如变量、函数名)缩短
"EXTRA_LIBS=\"{$extra_libs} -lresolv\" " .
// TODO: comment things
'cli &&' .
'dsymutil -f sapi/cli/php &&' .
'strip sapi/cli/php'
);
}
public function getPHPVersionID(): int
{
$file = file_get_contents(SOURCE_PATH . '/php-src/main/php_version.h');
preg_match('/PHP_VERSION_ID (\d+)/', $file, $match);
return intval($match[1]);
}
}

View File

@@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\macos;
use SPC\builder\traits\UnixSystemUtilTrait;
use SPC\exception\RuntimeException;
class SystemUtil
{
/** macOS 兼容 unix 的系统工具 */
use UnixSystemUtilTrait;
/**
* 获取系统 CPU 逻辑内核数
*
* @throws RuntimeException
*/
public static function getCpuCount(): int
{
f_exec('sysctl -n hw.ncpu', $output, $ret);
if ($ret !== 0) {
throw new RuntimeException('Failed to get cpu count');
}
return (int) $output[0];
}
/**
* 获取不同架构对应的 cflags 参数
*
* @throws RuntimeException
*/
public static function getArchCFlags(string $arch): string
{
return match ($arch) {
'x86_64' => '--target=x86_64-apple-darwin',
'arm64','aarch64' => '--target=arm64-apple-darwin',
default => throw new RuntimeException('unsupported arch: ' . $arch),
};
}
}

View File

@@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\macos\library;
use SPC\builder\BuilderBase;
use SPC\builder\LibraryBase;
use SPC\builder\macos\MacOSBuilder;
use SPC\builder\traits\UnixLibraryTrait;
use SPC\store\Config;
abstract class MacOSLibraryBase extends LibraryBase
{
use UnixLibraryTrait;
protected array $static_libs;
protected array $headers;
/**
* 依赖的名字及是否可选例如curl => true代表依赖 curl 但可选
*/
protected array $dep_names;
public function __construct(protected MacOSBuilder $builder)
{
parent::__construct();
}
public function getBuilder(): BuilderBase
{
return $this->builder;
}
/**
* 获取当前 lib 库依赖的 macOS framework
*/
public function getFrameworks(): array
{
return Config::getLib(static::NAME, 'frameworks', []);
}
}

View File

@@ -1,49 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class brotli extends MacOSLibraryBase
{
public const NAME = 'brotli';
protected function build()
{
[$lib, $include, $destdir] = SEPARATED_PATH;
f_passthru(
"{$this->builder->set_x} && " .
"cd {$this->source_dir} && " .
'rm -rf build && ' .
'mkdir -p build && ' .
'cd build && ' .
"{$this->builder->configure_env} " . ' cmake ' .
// '--debug-find ' .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DCMAKE_INSTALL_PREFIX=/ ' .
"-DCMAKE_INSTALL_LIBDIR={$lib} " .
"-DCMAKE_INSTALL_INCLUDEDIR={$include} " .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'.. && ' .
"cmake --build . -j {$this->builder->concurrency} && " .
'make install DESTDIR="' . $destdir . '"'
);
}
}

View File

@@ -1,39 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class bzip2 extends MacOSLibraryBase
{
public const NAME = 'bzip2';
protected function build()
{
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"make {$this->builder->configure_env} PREFIX='" . BUILD_ROOT_PATH . "' clean" . ' && ' .
"make -j{$this->builder->concurrency} {$this->builder->configure_env} PREFIX='" . BUILD_ROOT_PATH . "' libbz2.a" . ' && ' .
// make install may fail when cross-compiling, so we copy files.
'cp libbz2.a ' . BUILD_LIB_PATH . ' && ' .
'cp bzlib.h ' . BUILD_INCLUDE_PATH
);
}
}

View File

@@ -1,118 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
use SPC\exception\RuntimeException;
class curl extends MacOSLibraryBase
{
public const NAME = 'curl';
protected function build()
{
$extra = '';
// lib:openssl
$openssl = $this->getBuilder()->getLib('openssl');
if ($openssl instanceof MacOSLibraryBase) {
$extra .= '-DCURL_USE_OPENSSL=ON ';
} else {
$extra .= '-DCURL_USE_OPENSSL=OFF -DCURL_ENABLE_SSL=OFF ';
}
// lib:zlib
$zlib = $this->getBuilder()->getLib('zlib');
if ($zlib instanceof MacOSLibraryBase) {
$extra .= '-DZLIB_LIBRARY="' . $zlib->getStaticLibFiles(style: 'cmake') . '" ' .
'-DZLIB_INCLUDE_DIR=' . BUILD_INCLUDE_PATH . ' ';
}
// lib:libssh2
$libssh2 = $this->builder->getLib('libssh2');
if ($libssh2 instanceof MacOSLibraryBase) {
$extra .= '-DLIBSSH2_LIBRARY="' . $libssh2->getStaticLibFiles(style: 'cmake') . '" ' .
'-DLIBSSH2_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" ';
} else {
$extra .= '-DCURL_USE_LIBSSH2=OFF ';
}
// lib:brotli
$brotli = $this->builder->getLib('brotli');
if ($brotli) {
$extra .= '-DCURL_BROTLI=ON ' .
'-DBROTLIDEC_LIBRARY="' . realpath(BUILD_LIB_PATH . '/libbrotlidec-static.a') . ';' . realpath(BUILD_LIB_PATH . '/libbrotlicommon-static.a') . '" ' .
'-DBROTLICOMMON_LIBRARY="' . realpath(BUILD_LIB_PATH . '/libbrotlicommon-static.a') . '" ' .
'-DBROTLI_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" ';
} else {
$extra .= '-DCURL_BROTLI=OFF ';
}
// lib:nghttp2
$nghttp2 = $this->builder->getLib('nghttp2');
if ($nghttp2 instanceof MacOSLibraryBase) {
$extra .= '-DUSE_NGHTTP2=ON ' .
'-DNGHTTP2_LIBRARY="' . $nghttp2->getStaticLibFiles(style: 'cmake') . '" ' .
'-DNGHTTP2_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" ';
} else {
$extra .= '-DUSE_NGHTTP2=OFF ';
}
// lib:ldap
$ldap = $this->builder->getLib('ldap');
if ($ldap instanceof MacOSLibraryBase) {
// $extra .= '-DCURL_DISABLE_LDAP=OFF ';
// TODO: LDAP support
throw new RuntimeException('LDAP support is not implemented yet');
}
$extra .= '-DCURL_DISABLE_LDAP=ON ';
// lib:zstd
$zstd = $this->builder->getLib('zstd');
if ($zstd instanceof MacOSLibraryBase) {
$extra .= '-DCURL_ZSTD=ON ' .
'-DZstd_LIBRARY="' . $zstd->getStaticLibFiles(style: 'cmake') . '" ' .
'-DZstd_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" ';
} else {
$extra .= '-DCURL_ZSTD=OFF ';
}
// lib:idn2
$idn2 = $this->builder->getLib('idn2');
$extra .= $idn2 instanceof MacOSLibraryBase ? '-DUSE_LIBIDN2=ON ' : '-DUSE_LIBIDN2=OFF ';
// lib:psl
$libpsl = $this->builder->getLib('psl');
$extra .= $libpsl instanceof MacOSLibraryBase ? '-DCURL_USE_LIBPSL=ON ' : '-DCURL_USE_LIBPSL=OFF ';
[$lib, $include, $destdir] = SEPARATED_PATH;
// compile
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
'rm -rf build && ' .
'mkdir -p build && ' .
'cd build && ' .
"{$this->builder->configure_env} " . ' cmake ' .
// '--debug-find ' .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_SHARED_LIBS=OFF ' .
$extra .
'-DCMAKE_INSTALL_PREFIX= ' .
"-DCMAKE_INSTALL_LIBDIR={$lib} " .
"-DCMAKE_INSTALL_INCLUDEDIR={$include} " .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'.. && ' .
"make -j{$this->builder->concurrency} && " .
'make install DESTDIR="' . $destdir . '"'
);
}
}

View File

@@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\macos\library;
/**
* is a template library class for unix
*/
class freetype extends MacOSLibraryBase
{
public const NAME = 'freetype';
protected function build()
{
[,,$destdir] = SEPARATED_PATH;
$suggested = '';
$suggested .= ($this->builder->getLib('libpng') instanceof MacOSLibraryBase) ? ('--with-png=' . BUILD_ROOT_PATH) : '--without-png';
$suggested .= ' ';
$suggested .= ($this->builder->getLib('bzip2') instanceof MacOSLibraryBase) ? ('--with-bzip2=' . BUILD_ROOT_PATH) : '--without-bzip2';
$suggested .= ' ';
$suggested .= ($this->builder->getLib('brotli') instanceof MacOSLibraryBase) ? ('--with-brotli=' . BUILD_ROOT_PATH) : '--without-brotli';
$suggested .= ' ';
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"{$this->builder->configure_env} ./configure " .
'--enable-static --disable-shared --without-harfbuzz ' .
$suggested .
'--prefix= && ' . // use prefix=/
'make clean && ' .
"make -j{$this->builder->concurrency} && " .
'make install DESTDIR=' . $destdir
);
}
}

View File

@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\macos\library;
/**
* gmp is a template library class for unix
*/
class gmp extends MacOSLibraryBase
{
public const NAME = 'gmp';
protected function build()
{
[,,$destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"{$this->builder->configure_env} ./configure " .
'--enable-static --disable-shared ' .
'--prefix= && ' . // use prefix=/
'make clean && ' .
"make -j{$this->builder->concurrency} && " .
'make install DESTDIR=' . $destdir
);
}
}

View File

@@ -1,45 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class libffi extends MacOSLibraryBase
{
public const NAME = 'libffi';
protected function build()
{
[$lib, $include, $destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"{$this->builder->configure_env} ./configure " .
'--enable-static ' .
'--disable-shared ' .
"--host={$this->builder->arch}-apple-darwin " .
"--target={$this->builder->arch}-apple-darwin " .
'--prefix= ' . // use prefix=/
"--libdir={$lib} && " .
'make clean && ' .
"make -j{$this->builder->concurrency} && " .
"make install DESTDIR={$destdir}"
);
}
}

View File

@@ -1,65 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\util\Patcher;
class libpng extends MacOSLibraryBase
{
public const NAME = 'libpng';
/**
* @throws FileSystemException
* @throws RuntimeException
*/
protected function build()
{
// 不同架构的专属优化
$optimizations = match ($this->builder->arch) {
'x86_64' => '--enable-intel-sse ',
'arm64' => '--enable-arm-neon ',
default => '',
};
// patch configure
Patcher::patchUnixLibpng();
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"{$this->builder->configure_env} " .
'./configure ' .
"--host={$this->builder->gnu_arch}-apple-darwin " .
'--disable-shared ' .
'--enable-static ' .
'--enable-hardware-optimizations ' .
$optimizations .
'--prefix= && ' . // use prefix=/
'make clean && ' .
"make -j{$this->builder->concurrency} DEFAULT_INCLUDES='-I. -I" . BUILD_INCLUDE_PATH . "' LIBS= libpng16.la && " .
'make install-libLTLIBRARIES install-data-am DESTDIR=' . BUILD_ROOT_PATH . ' && ' .
'cd ' . BUILD_LIB_PATH . ' && ' .
'ln -sf libpng16.a libpng.a'
);
}
}

View File

@@ -1,56 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class libssh2 extends MacOSLibraryBase
{
public const NAME = 'libssh2';
protected function build()
{
// lib:zlib
$enable_zlib = $this->builder->getLib('zlib') !== null ? 'ON' : 'OFF';
[$lib, $include, $destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
'rm -rf build && ' .
'mkdir -p build && ' .
'cd build && ' .
"{$this->builder->configure_env} " . ' cmake ' .
// '--debug-find ' .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DBUILD_EXAMPLES=OFF ' .
'-DBUILD_TESTING=OFF ' .
"-DENABLE_ZLIB_COMPRESSION={$enable_zlib} " .
'-DCMAKE_INSTALL_PREFIX=/ ' .
"-DCMAKE_INSTALL_LIBDIR={$lib} " .
"-DCMAKE_INSTALL_INCLUDEDIR={$include} " .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'.. && ' .
"cmake --build . -j {$this->builder->concurrency} --target libssh2 && " .
'make install DESTDIR="' . $destdir . '"'
);
}
}

View File

@@ -1,66 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
use SPC\exception\RuntimeException;
class libxml2 extends MacOSLibraryBase
{
public const NAME = 'libxml2';
/**
* @throws RuntimeException
*/
protected function build()
{
$enable_zlib = $this->builder->getLib('zlib') ? 'ON' : 'OFF';
$enable_icu = $this->builder->getLib('icu') ? 'ON' : 'OFF';
$enable_xz = $this->builder->getLib('xz') ? 'ON' : 'OFF';
[$lib, $include, $destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
'rm -rf build && ' .
'mkdir -p build && ' .
'cd build && ' .
"{$this->builder->configure_env} " . ' cmake ' .
// '--debug-find ' .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DLIBXML2_WITH_ICONV=ON ' .
"-DLIBXML2_WITH_ZLIB={$enable_zlib} " .
"-DLIBXML2_WITH_ICU={$enable_icu} " .
"-DLIBXML2_WITH_LZMA={$enable_xz} " .
'-DLIBXML2_WITH_PYTHON=OFF ' .
'-DLIBXML2_WITH_PROGRAMS=OFF ' .
'-DLIBXML2_WITH_TESTS=OFF ' .
'-DCMAKE_INSTALL_PREFIX=/ ' .
"-DCMAKE_INSTALL_LIBDIR={$lib} " .
"-DCMAKE_INSTALL_INCLUDEDIR={$include} " .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'.. && ' .
"cmake --build . -j {$this->builder->concurrency} && " .
'make install DESTDIR="' . $destdir . '"'
);
}
}

View File

@@ -1,95 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
use SPC\exception\RuntimeException;
class libyaml extends MacOSLibraryBase
{
public const NAME = 'libyaml';
/**
* @throws RuntimeException
*/
public function build()
{
// prepare cmake/config.h.in
if (!is_file(SOURCE_PATH . '/libyaml/cmake/config.h.in')) {
f_mkdir(SOURCE_PATH . '/libyaml/cmake');
file_put_contents(
SOURCE_PATH . '/libyaml/cmake/config.h.in',
<<<'EOF'
#define YAML_VERSION_MAJOR @YAML_VERSION_MAJOR@
#define YAML_VERSION_MINOR @YAML_VERSION_MINOR@
#define YAML_VERSION_PATCH @YAML_VERSION_PATCH@
#define YAML_VERSION_STRING "@YAML_VERSION_STRING@"
EOF
);
}
// prepare yamlConfig.cmake.in
if (!is_file(SOURCE_PATH . '/libyaml/yamlConfig.cmake.in')) {
file_put_contents(
SOURCE_PATH . '/libyaml/yamlConfig.cmake.in',
<<<'EOF'
# Config file for the yaml library.
#
# It defines the following variables:
# yaml_LIBRARIES - libraries to link against
@PACKAGE_INIT@
set_and_check(yaml_TARGETS "@PACKAGE_CONFIG_DIR_CONFIG@/yamlTargets.cmake")
if(NOT yaml_TARGETS_IMPORTED)
set(yaml_TARGETS_IMPORTED 1)
include(${yaml_TARGETS})
endif()
set(yaml_LIBRARIES yaml)
EOF
);
}
[$lib, $include, $destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
'rm -rf build && ' .
'mkdir -p build && ' .
'cd build && ' .
"{$this->builder->configure_env} " . ' cmake ' .
// '--debug-find ' .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DBUILD_TESTING=OFF ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DCMAKE_INSTALL_PREFIX=/ ' .
"-DCMAKE_INSTALL_LIBDIR={$lib} " .
"-DCMAKE_INSTALL_INCLUDEDIR={$include} " .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'.. && ' .
"make -j{$this->builder->concurrency} && " .
'make install DESTDIR=' . $destdir
);
}
}

View File

@@ -1,101 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class libzip extends MacOSLibraryBase
{
public const NAME = 'libzip';
protected function build()
{
$extra = '';
// lib:zlib
$zlib = $this->builder->getLib('zlib');
if ($zlib instanceof MacOSLibraryBase) {
$extra .= '-DZLIB_LIBRARY="' . $zlib->getStaticLibFiles(style: 'cmake') . '" ' .
'-DZLIB_INCLUDE_DIR=' . BUILD_INCLUDE_PATH . ' ';
}
// lib:bzip2
$libbzip2 = $this->builder->getLib('bzip2');
if ($libbzip2 instanceof MacOSLibraryBase) {
$extra .= '-DENABLE_BZIP2=ON ' .
'-DBZIP2_LIBRARIES="' . $libbzip2->getStaticLibFiles(style: 'cmake') . '" ' .
'-DBZIP2_INCLUDE_DIR=' . BUILD_INCLUDE_PATH . ' ';
} else {
$extra .= '-DENABLE_BZIP2=OFF ';
}
// lib:xz
$xz = $this->builder->getLib('xz');
if ($xz instanceof MacOSLibraryBase) {
$extra .= '-DENABLE_LZMA=ON ' .
'-DLIBLZMA_LIBRARY="' . $xz->getStaticLibFiles(style: 'cmake') . '" ' .
'-DLIBLZMA_INCLUDE_DIR=' . BUILD_INCLUDE_PATH . ' ';
} else {
$extra .= '-DENABLE_LZMA=OFF ';
}
// lib:zstd
$libzstd = $this->builder->getLib('zstd');
if ($libzstd instanceof MacOSLibraryBase) {
$extra .= '-DENABLE_ZSTD=ON ' .
'-DZstd_LIBRARY="' . $libzstd->getStaticLibFiles(style: 'cmake') . '" ' .
'-DZstd_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" ';
} else {
$extra .= '-DENABLE_ZSTD=OFF ';
}
// lib:openssl
$libopenssl = $this->builder->getLib('openssl');
if ($libopenssl instanceof MacOSLibraryBase) {
$extra .= '-DENABLE_OPENSSL=ON ' .
'-DOpenSSL_LIBRARY="' . $libopenssl->getStaticLibFiles(style: 'cmake') . '" ' .
'-DOpenSSL_INCLUDE_DIR="' . BUILD_INCLUDE_PATH . '" ';
} else {
$extra .= '-DENABLE_OPENSSL=OFF ';
}
[$lib, $include, $destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
'rm -rf build && ' .
'mkdir -p build && ' .
'cd build && ' .
"{$this->builder->configure_env} " . ' cmake ' .
// '--debug-find ' .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DENABLE_GNUTLS=OFF ' .
'-DENABLE_MBEDTLS=OFF ' .
'-DBUILD_SHARED_LIBS=OFF ' .
'-DBUILD_DOC=OFF ' .
'-DBUILD_EXAMPLES=OFF ' .
'-DBUILD_REGRESS=OFF ' .
'-DBUILD_TOOLS=OFF ' .
$extra .
'-DCMAKE_INSTALL_PREFIX=/ ' .
"-DCMAKE_INSTALL_LIBDIR={$lib} " .
"-DCMAKE_INSTALL_INCLUDEDIR={$include} " .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'.. && ' .
"make -j{$this->builder->concurrency} && " .
'make install DESTDIR=' . $destdir
);
}
}

View File

@@ -1,63 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class nghttp2 extends MacOSLibraryBase
{
public const NAME = 'nghttp2';
protected function build()
{
$args = $this->builder->makeAutoconfArgs(static::NAME, [
'zlib' => null,
'openssl' => null,
'libxml2' => null,
'libev' => null,
'libcares' => null,
'libngtcp2' => null,
'libnghttp3' => null,
'libbpf' => null,
'libevent-openssl' => null,
'jansson' => null,
'jemalloc' => null,
'systemd' => null,
'cunit' => null,
]);
[,,$destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"{$this->builder->configure_env} " . ' ./configure ' .
'--enable-static ' .
'--disable-shared ' .
"--host={$this->builder->gnu_arch}-apple-darwin " .
'--enable-lib-only ' .
'--with-boost=no ' .
$args . ' ' .
'--prefix= && ' . // use prefix=/
'make clean && ' .
"make -j{$this->builder->concurrency} && " .
"make install DESTDIR={$destdir}"
);
}
}

View File

@@ -1,44 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class onig extends MacOSLibraryBase
{
public const NAME = 'onig';
protected function build()
{
[,,$destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"{$this->builder->configure_env} " . ' ./configure ' .
'--enable-static ' .
'--disable-shared ' .
"--host={$this->builder->arch}-apple-darwin " .
'--prefix= && ' . // use prefix=/
'make clean && ' .
"make -j{$this->builder->concurrency} && " .
'make install DESTDIR=' . $destdir
);
}
}

View File

@@ -1,52 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class openssl extends MacOSLibraryBase
{
public const NAME = 'openssl';
protected function build()
{
[$lib,,$destdir] = SEPARATED_PATH;
// lib:zlib
$extra = '';
$ex_lib = '';
$zlib = $this->builder->getLib('zlib');
if ($zlib instanceof MacOSLibraryBase) {
$extra = 'zlib';
$ex_lib = trim($zlib->getStaticLibFiles() . ' ' . $ex_lib);
}
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"{$this->builder->configure_env} ./Configure no-shared {$extra} " .
'--prefix=/ ' . // use prefix=/
"--libdir={$lib} " .
" darwin64-{$this->builder->arch}-cc && " .
'make clean && ' .
"make -j{$this->builder->concurrency} CNF_EX_LIBS=\"{$ex_lib}\" && " .
'make install_sw DESTDIR=' . $destdir
);
}
}

View File

@@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\macos\library;
class sqlite extends MacOSLibraryBase
{
public const NAME = 'sqlite';
protected function build()
{
[,,$destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"{$this->builder->configure_env} ./configure " .
'--enable-static --disable-shared ' .
'--prefix= && ' . // use prefix=/
'make clean && ' .
"make -j{$this->builder->concurrency} && " .
'make install DESTDIR=' . $destdir
);
}
}

View File

@@ -1,52 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class xz extends MacOSLibraryBase
{
public const NAME = 'xz';
protected function build()
{
[,,$destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
'autoreconf -i --force && ' .
"{$this->builder->configure_env} ./configure " .
'--enable-static ' .
'--disable-shared ' .
"--host={$this->builder->gnu_arch}-apple-darwin " .
'--disable-xz ' .
'--disable-xzdec ' .
'--disable-lzmadec ' .
'--disable-lzmainfo ' .
'--disable-scripts ' .
'--disable-doc ' .
'--with-libiconv ' .
'--prefix= && ' . // use prefix=/
'make clean && ' .
"make -j{$this->builder->concurrency} && " .
'make install DESTDIR=' . $destdir
);
}
}

View File

@@ -1,42 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class zlib extends MacOSLibraryBase
{
public const NAME = 'zlib';
protected function build()
{
[,,$destdir] = SEPARATED_PATH;
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"{$this->builder->configure_env} ./configure " .
'--static ' .
'--prefix= && ' . // use prefix=/
'make clean && ' .
"make -j{$this->builder->concurrency} && " .
'make install DESTDIR=' . $destdir
);
}
}

View File

@@ -1,41 +0,0 @@
<?php
/**
* Copyright (c) 2022 Yun Dou <dixyes@gmail.com>
*
* lwmbs is licensed under Mulan PSL v2. You can use this
* software according to the terms and conditions of the
* Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
*/
declare(strict_types=1);
namespace SPC\builder\macos\library;
class zstd extends MacOSLibraryBase
{
public const NAME = 'zstd';
protected function build()
{
f_passthru(
$this->builder->set_x . ' && ' .
"cd {$this->source_dir} && " .
"make {$this->builder->configure_env} PREFIX='" . BUILD_ROOT_PATH . "' clean" . ' && ' .
"make -j{$this->builder->concurrency} " .
"{$this->builder->configure_env} " .
"PREFIX='" . BUILD_ROOT_PATH . "' " .
'-C lib libzstd.a CPPFLAGS_STATLIB=-DZSTD_MULTITHREAD && ' .
'cp lib/libzstd.a ' . BUILD_LIB_PATH . ' && ' .
'cp lib/zdict.h lib/zstd_errors.h lib/zstd.h ' . BUILD_INCLUDE_PATH
);
}
}

View File

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

View File

@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\traits;
/**
* 仅供 Command 使用,如果使用了该 Trait则在执行对应命令时不打印 motd
*/
trait NoMotdTrait
{
protected bool $no_motd = true;
}

View File

@@ -1,102 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\traits;
use SPC\exception\RuntimeException;
trait UnixBuilderTrait
{
/** @var string 设置的命令前缀,设置为 set -x 可以在终端打印命令 */
public string $set_x = 'set -x';
/** @var string C 编译器命令 */
public string $cc;
/** @var string C++ 编译器命令 */
public string $cxx;
/** @var string cflags 参数 */
public string $arch_c_flags;
/** @var string C++ flags 参数 */
public string $arch_cxx_flags;
/** @var string cmake toolchain file */
public string $cmake_toolchain_file;
/** @var string configure 环境依赖的变量 */
public string $configure_env;
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 array_map(fn ($x) => realpath(BUILD_LIB_PATH . "/{$x}"), $libFiles);
}
/**
* @throws RuntimeException
*/
public function sanityCheck(int $build_micro_rule): void
{
logger()->info('running sanity check');
if ($build_micro_rule !== BUILD_MICRO_ONLY) {
f_exec(
$this->set_x . ' && ' .
SOURCE_PATH . '/php-src/sapi/cli/php -r "echo \"hello\";"',
$output,
$ret
);
if ($ret !== 0 || trim(implode('', $output)) !== 'hello') {
throw new RuntimeException('cli failed sanity check');
}
foreach ($this->exts as $ext) {
logger()->debug('checking ext: ' . $ext->getName());
if (file_exists(ROOT_DIR . '/src/globals/tests/' . $ext->getName() . '.php')) {
f_exec(
$this->set_x . ' && ' . SOURCE_PATH . '/php-src/sapi/cli/php ' . ROOT_DIR . '/src/globals/tests/' . $ext->getName() . '.php',
$output,
$ret
);
if ($ret !== 0) {
throw new RuntimeException('extension ' . $ext->getName() . ' failed sanity check');
}
}
}
}
if ($build_micro_rule !== BUILD_MICRO_NONE) {
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);
f_exec(SOURCE_PATH . '/hello.exe', $output2, $ret);
if ($ret !== 0 || trim($out = implode('', $output2)) !== 'hello') {
throw new RuntimeException('micro failed sanity check, ret[' . $ret . '], out[' . ($out ?? 'NULL') . ']');
}
}
}
}

View File

@@ -1,53 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\traits;
use SPC\builder\LibraryBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
trait UnixLibraryTrait
{
use LibraryTrait;
/**
* @throws RuntimeException
* @throws FileSystemException
*/
public function getStaticLibFiles(string $style = 'autoconf', bool $recursive = true): string
{
$libs = [$this];
if ($recursive) {
array_unshift($libs, ...array_values($this->getDependencies(recursive: true)));
}
$sep = match ($style) {
'autoconf' => ' ',
'cmake' => ';',
default => throw new RuntimeException('style only support autoconf and cmake'),
};
$ret = [];
/** @var LibraryBase $lib */
foreach ($libs as $lib) {
$libFiles = [];
foreach ($lib->getStaticLibs() as $name) {
$name = str_replace(' ', '\ ', realpath(BUILD_LIB_PATH . "/{$name}"));
$name = str_replace('"', '\"', $name);
$libFiles[] = $name;
}
array_unshift($ret, implode($sep, $libFiles));
}
return implode($sep, $ret);
}
public function makeAutoconfEnv(string $prefix = null): string
{
if ($prefix === null) {
$prefix = str_replace('-', '_', strtoupper(static::NAME));
}
return $prefix . '_CFLAGS="-I' . BUILD_INCLUDE_PATH . '" ' .
$prefix . '_LIBS="' . $this->getStaticLibFiles() . '"';
}
}

View File

@@ -1,68 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\builder\traits;
/**
* Unix 系统的工具函数 Trait适用于 Linux、macOS
*/
trait UnixSystemUtilTrait
{
/**
* 生成 toolchain.cmake用于 cmake 构建
*
* @param string $os 操作系统代号
* @param string $target_arch 目标架构
* @param string $cflags CFLAGS 参数
* @param null|string $cc CC 参数(默认空)
* @param null|string $cxx CXX 参数(默认空)
*/
public static function makeCmakeToolchainFile(
string $os,
string $target_arch,
string $cflags,
?string $cc = null,
?string $cxx = null
): string {
logger()->debug("making cmake tool chain file for {$os} {$target_arch} with CFLAGS='{$cflags}'");
$root = BUILD_ROOT_PATH;
$ccLine = '';
if ($cc) {
$ccLine = 'SET(CMAKE_C_COMPILER ' . self::findCommand($cc) . ')';
}
$cxxLine = '';
if ($cxx) {
$cxxLine = 'SET(CMAKE_CXX_COMPILER ' . self::findCommand($cxx) . ')';
}
$toolchain = <<<CMAKE
SET(CMAKE_SYSTEM_NAME {$os})
SET(CMAKE_SYSTEM_PROCESSOR {$target_arch})
{$ccLine}
{$cxxLine}
SET(CMAKE_C_FLAGS "{$cflags}")
SET(CMAKE_CXX_FLAGS "{$cflags}")
SET(CMAKE_FIND_ROOT_PATH "{$root}")
CMAKE;
file_put_contents(SOURCE_PATH . '/toolchain.cmake', $toolchain);
return realpath(SOURCE_PATH . '/toolchain.cmake');
}
/**
* @param string $name 命令名称
* @param array $paths 寻找的目标路径(如果不传入,则使用环境变量 PATH
* @return null|string 找到了返回命令路径,找不到返回 null
*/
public static function findCommand(string $name, array $paths = []): ?string
{
if (!$paths) {
$paths = explode(PATH_SEPARATOR, getenv('PATH'));
}
foreach ($paths as $path) {
if (file_exists($path . DIRECTORY_SEPARATOR . $name)) {
return $path . DIRECTORY_SEPARATOR . $name;
}
}
return null;
}
}

View File

@@ -1,59 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use Psr\Log\LogLevel;
use SPC\ConsoleApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Logger\ConsoleLogger;
abstract class BaseCommand extends Command
{
public function __construct(string $name = null)
{
parent::__construct($name);
$this->addOption('debug', null, null, 'Enable debug mode');
}
public function initialize(InputInterface $input, OutputInterface $output)
{
// 注册全局错误处理器
set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) {
$tips = [
E_WARNING => ['PHP Warning: ', 'warning'],
E_NOTICE => ['PHP Notice: ', 'notice'],
E_USER_ERROR => ['PHP Error: ', 'error'],
E_USER_WARNING => ['PHP Warning: ', 'warning'],
E_USER_NOTICE => ['PHP Notice: ', 'notice'],
E_STRICT => ['PHP Strict: ', 'notice'],
E_RECOVERABLE_ERROR => ['PHP Recoverable Error: ', 'error'],
E_DEPRECATED => ['PHP Deprecated: ', 'notice'],
E_USER_DEPRECATED => ['PHP User Deprecated: ', 'notice'],
];
$level_tip = $tips[$error_no] ?? ['PHP Unknown: ', 'error'];
$error = $level_tip[0] . $error_msg . ' in ' . $error_file . ' on ' . $error_line;
logger()->{$level_tip[1]}($error);
// 如果 return false 则错误会继续递交给 PHP 标准错误处理
return true;
}, E_ALL | E_STRICT);
if ($input->getOption('debug')) {
global $ob_logger;
$ob_logger = new ConsoleLogger(LogLevel::DEBUG);
define('DEBUG_MODE', true);
}
$version = ConsoleApplication::VERSION;
if (!isset($this->no_motd)) {
echo " _ _ _ _
___| |_ __ _| |_(_) ___ _ __ | |__ _ __
/ __| __/ _` | __| |/ __|____| '_ \\| '_ \\| '_ \\
\\__ \\ || (_| | |_| | (_|_____| |_) | | | | |_) |
|___/\\__\\__,_|\\__|_|\\___| | .__/|_| |_| .__/ v{$version}
|_| |_|
";
}
}
}

View File

@@ -1,86 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\builder\BuilderProvider;
use SPC\exception\ExceptionHandler;
use SPC\util\DependencyUtil;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/** @noinspection PhpUnused */
class BuildCliCommand extends BuildCommand
{
protected static $defaultName = 'build';
public function configure()
{
$this->setDescription('Build CLI binary');
$this->addArgument('extensions', InputArgument::REQUIRED, 'The extensions will be compiled, comma separated');
$this->addOption('with-libs', null, InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', '');
$this->addOption('build-micro', null, null, 'build micro only');
$this->addOption('build-all', null, null, 'build both cli and micro');
}
public function execute(InputInterface $input, OutputInterface $output): int
{
// 从参数中获取要编译的 libraries并转换为数组
$libraries = array_map('trim', array_filter(explode(',', $input->getOption('with-libs'))));
// 从参数中获取要编译的 extensions并转换为数组
$extensions = array_map('trim', array_filter(explode(',', $input->getArgument('extensions'))));
define('BUILD_ALL_STATIC', true);
if ($input->getOption('build-all')) {
$rule = BUILD_MICRO_BOTH;
logger()->info('Builder will build php-cli and phpmicro SAPI');
} elseif ($input->getOption('build-micro')) {
$rule = BUILD_MICRO_ONLY;
logger()->info('Builder will build phpmicro SAPI');
} else {
$rule = BUILD_MICRO_NONE;
logger()->info('Builder will build php-cli SAPI');
}
try {
// 构建对象
$builder = BuilderProvider::makeBuilderByInput($input);
// 根据提供的扩展列表获取依赖库列表并编译
[$extensions, $libraries, $not_included] = DependencyUtil::getExtLibsByDeps($extensions, $libraries);
logger()->info('Enabled extensions: ' . implode(', ', $extensions));
logger()->info('Required libraries: ' . implode(', ', $libraries));
if (!empty($not_included)) {
logger()->warning('some extensions will be enabled due to dependencies: ' . implode(',', $not_included));
}
sleep(2);
// 编译和检查库是否完整
$builder->buildLibs($libraries);
// 执行扩展检测
$builder->proveExts($extensions);
// 构建
$builder->buildPHP($rule, $input->getOption('with-clean'), $input->getOption('bloat'));
// 统计时间
$time = round(microtime(true) - START_TIME, 3);
logger()->info('Build complete, used ' . $time . ' s !');
if ($rule !== BUILD_MICRO_ONLY) {
logger()->info('Static php binary path: ' . SOURCE_PATH . '/php-src/sapi/cli/php');
}
if ($rule !== BUILD_MICRO_NONE) {
logger()->info('phpmicro binary path: ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx');
}
return 0;
} catch (\Throwable $e) {
if ($input->getOption('debug')) {
ExceptionHandler::getInstance()->handle($e);
} else {
logger()->critical('Build failed, please check terminal output, or build with --debug option to see more details.');
logger()->critical($e->getMessage());
}
return 1;
}
}
}

View File

@@ -1,37 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use Symfony\Component\Console\Input\InputOption;
abstract class BuildCommand extends BaseCommand
{
public function __construct(string $name = null)
{
parent::__construct($name);
// 根据运行的操作系统分配允许不同的命令行参数Windows 需要额外的 VS 和 SDK等*nix 需要提供架构
switch (PHP_OS_FAMILY) {
case 'Windows':
$this->addOption('with-sdk-binary-dir', null, InputOption::VALUE_REQUIRED, 'path to binary sdk');
$this->addOption('vs-ver', null, InputOption::VALUE_REQUIRED, 'vs version, e.g. "17" for Visual Studio 2022');
$this->addOption('arch', null, InputOption::VALUE_REQUIRED, 'architecture, "x64" or "arm64"', 'x64');
break;
case 'Linux':
$this->addOption('no-system-static', null, null, 'do not use system static libraries');
// no break
case 'Darwin':
$this->addOption('cc', null, InputOption::VALUE_REQUIRED, 'C compiler');
$this->addOption('cxx', null, InputOption::VALUE_REQUIRED, 'C++ compiler');
$this->addOption('arch', null, InputOption::VALUE_REQUIRED, 'architecture', php_uname('m'));
break;
}
// 是否在编译 make 前清除旧的文件
$this->addOption('with-clean', null, null, 'fresh build, `make clean` before `make`');
// 是否采用强制链接,让链接器强制加载静态库文件
$this->addOption('bloat', null, null, 'add all libraries into binary');
}
}

View File

@@ -1,69 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\builder\BuilderProvider;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/** @noinspection PhpUnused */
class BuildLibsCommand extends BuildCommand
{
protected static $defaultName = 'build:libs';
public function configure()
{
$this->setDescription('Build dependencies');
$this->addArgument('libraries', InputArgument::REQUIRED, 'The libraries will be compiled, comma separated');
$this->addOption('clean', null, null, 'Clean old download cache and source before fetch');
$this->addOption('all', 'A', null, 'Build all libs that static-php-cli needed');
}
public function initialize(InputInterface $input, OutputInterface $output)
{
// --all 等于 ""
if ($input->getOption('all')) {
$input->setArgument('libraries', '');
}
}
/**
* @throws RuntimeException
* @throws FileSystemException
*/
public function execute(InputInterface $input, OutputInterface $output): int
{
// 从参数中获取要编译的 libraries并转换为数组
$libraries = array_map('trim', array_filter(explode(',', $input->getArgument('libraries'))));
// 删除旧资源
if ($input->getOption('clean')) {
logger()->warning('You are doing some operations that not recoverable: removing directories below');
logger()->warning(BUILD_ROOT_PATH);
logger()->warning('I will remove these dir after you press [Enter] !');
echo 'Confirm operation? [Yes] ';
fgets(STDIN);
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('rmdir /s /q ' . BUILD_ROOT_PATH);
} else {
f_passthru('rm -rf ' . BUILD_ROOT_PATH);
}
}
// 构建对象
$builder = BuilderProvider::makeBuilderByInput($input);
// 只编译 library 的情况下,标记
$builder->setLibsOnly();
// 编译和检查库完整
$builder->buildLibs($libraries);
$time = round(microtime(true) - START_TIME, 3);
logger()->info('Build libs complete, used ' . $time . ' s !');
return 0;
}
}

View File

@@ -1,116 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use CliHelper\Tools\ArgFixer;
use CliHelper\Tools\DataProvider;
use CliHelper\Tools\SeekableArrayIterator;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/** @noinspection PhpUnused */
class DeployCommand extends BaseCommand
{
protected static $defaultName = 'deploy-self';
public function configure()
{
$this->setDescription('Deploy static-php-cli self to an .phar application');
$this->addArgument('target', InputArgument::OPTIONAL, 'The file or directory to pack.');
$this->addOption('auto-phar-fix', null, InputOption::VALUE_NONE, 'Automatically fix ini option.');
$this->addOption('overwrite', 'W', InputOption::VALUE_NONE, 'Overwrite existing files.');
$this->addOption('disable-gzip', 'z', InputOption::VALUE_NONE, 'disable gzip archive mode');
}
public function execute(InputInterface $input, OutputInterface $output): int
{
// 第一阶段流程如果没有写path将会提示输入要打包的path
$prompt = new ArgFixer($input, $output);
// 首先得确认是不是关闭了readonly模式
if (ini_get('phar.readonly') == 1) {
if ($input->getOption('auto-phar-fix')) {
$ask = true;
} else {
$ask = $prompt->requireBool('<comment>pack command needs "phar.readonly" = "Off" !</comment>' . PHP_EOL . 'If you want to automatically set it and continue, just Enter', true);
}
$output->writeln('<info>Now running command in child process.</info>');
if ($ask) {
global $argv;
passthru(PHP_BINARY . ' -d phar.readonly=0 ' . implode(' ', $argv), $retcode);
exit($retcode);
}
}
// 获取路径
$path = WORKING_DIR;
// 如果是目录,则将目录下的所有文件打包
$phar_path = $prompt->requireArgument('target', 'Please input the phar target filename', 'static-php-cli.phar');
if (DataProvider::isRelativePath($phar_path)) {
$phar_path = '/tmp/' . $phar_path;
}
if (file_exists($phar_path)) {
$ask = $input->getOption('overwrite') ? true : $prompt->requireBool('<comment>The file "' . $phar_path . '" already exists, do you want to overwrite it?</comment>' . PHP_EOL . 'If you want to, just Enter');
if (!$ask) {
$output->writeln('<comment>User canceled.</comment>');
return 1;
}
@unlink($phar_path);
}
$phar = new \Phar($phar_path);
$phar->startBuffering();
$all = DataProvider::scanDirFiles($path, true, true);
$all = array_filter($all, function ($x) {
$dirs = preg_match('/(^(bin|config|src|vendor)\\/|^(composer\\.json|README\\.md|source\\.json|LICENSE|README-en\\.md)$)/', $x);
return !($dirs !== 1);
});
sort($all);
$map = [];
foreach ($all as $v) {
$map[$v] = $path . '/' . $v;
}
$output->writeln('<info>Start packing files...</info>');
try {
$phar->buildFromIterator(new SeekableArrayIterator($map, new ProgressBar($output)));
$phar->addFromString(
'.phar-entry.php',
str_replace(
'/../vendor/autoload.php',
'/vendor/autoload.php',
file_get_contents(ROOT_DIR . '/bin/spc')
)
);
$stub = '.phar-entry.php';
$phar->setStub($phar->createDefaultStub($stub));
} catch (\Throwable $e) {
$output->writeln($e);
return 1;
}
$phar->addFromString('.prod', 'true');
if (!$input->getOption('disable-gzip')) {
$phar->compressFiles(\Phar::GZ);
}
$phar->stopBuffering();
$output->writeln(PHP_EOL . 'Done! Phar file is generated at "' . $phar_path . '".');
if (file_exists(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx')) {
$output->writeln('Detected you have already compiled micro binary, I will make executable now for you!');
file_put_contents(
$phar_path . '.exe',
file_get_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx') .
file_get_contents($phar_path)
);
chmod($phar_path . '.exe', 0755);
$output->writeln('<info>Static: ' . $phar_path . '.exe</info>');
}
chmod($phar_path, 0755);
$output->writeln('<info>Phar: ' . $phar_path . '</info>');
return 0;
}
}

View File

@@ -1,29 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* 修改 config 后对其 kv 进行排序的操作
*/
class DumpLicenseCommand extends BaseCommand
{
protected static $defaultName = 'dump-license';
public function configure()
{
$this->setDescription('Dump licenses for required libraries');
$this->addArgument('config-name', InputArgument::REQUIRED, 'Your config to be sorted, you can sort "lib", "source" and "ext".');
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('<info>not implemented</info>');
return 1;
}
}

View File

@@ -1,238 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\exception\DownloaderException;
use SPC\exception\ExceptionHandler;
use SPC\exception\FileSystemException;
use SPC\exception\InvalidArgumentException;
use SPC\exception\RuntimeException;
use SPC\store\Config;
use SPC\store\Downloader;
use SPC\util\Patcher;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/** @noinspection PhpUnused */
class FetchSourceCommand extends BaseCommand
{
protected static $defaultName = 'fetch';
protected string $php_major_ver;
protected InputInterface $input;
public function configure()
{
$this->setDescription('Fetch required sources');
$this->addArgument('extensions', InputArgument::REQUIRED, 'The extensions will be compiled, comma separated');
$this->addArgument('libraries', InputArgument::REQUIRED, 'The libraries will be compiled, comma separated');
$this->addOption('hash', null, null, 'Hash only');
$this->addOption('shallow-clone', null, null, 'Clone shallow');
$this->addOption('with-openssl11', null, null, 'Use openssl 1.1');
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'version in major.minor format like 8.1', '8.1');
$this->addOption('clean', null, null, 'Clean old download cache and source before fetch');
$this->addOption('all', 'A', null, 'Fetch all sources that static-php-cli needed');
}
public function initialize(InputInterface $input, OutputInterface $output)
{
// --all 等于 "" "",也就是所有东西都要下载
if ($input->getOption('all')) {
$input->setArgument('extensions', '');
$input->setArgument('libraries', '');
}
parent::initialize($input, $output);
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
try {
// 匹配版本
$ver = $this->php_major_ver = $input->getOption('with-php') ?? '8.1';
preg_match('/^\d+\.\d+$/', $ver, $matches);
if (!$matches) {
logger()->error("bad version arg: {$ver}, x.y required!");
return 1;
}
// 删除旧资源
if ($input->getOption('clean')) {
logger()->warning('You are doing some operations that not recoverable: removing directories below');
logger()->warning(SOURCE_PATH);
logger()->warning(DOWNLOAD_PATH);
logger()->warning('I will remove these dir after you press [Enter] !');
echo 'Confirm operation? [Yes] ';
$r = strtolower(trim(fgets(STDIN)));
if ($r !== 'yes' && $r !== '') {
logger()->notice('Operation canceled.');
return 1;
}
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('rmdir /s /q ' . SOURCE_PATH);
f_passthru('rmdir /s /q ' . DOWNLOAD_PATH);
} else {
f_passthru('rm -rf ' . SOURCE_PATH);
f_passthru('rm -rf ' . DOWNLOAD_PATH);
}
}
// 使用浅克隆可以减少调用 git 命令下载资源时的存储空间占用
if ($input->getOption('shallow-clone')) {
define('GIT_SHALLOW_CLONE', true);
}
// 读取源配置随便读一个source用于缓存 source 配置
Config::getSource('openssl');
// 是否启用openssl11
if ($input->getOption('with-openssl11')) {
logger()->debug('Using openssl 1.1');
// 手动修改配置
Config::$source['openssl']['regex'] = '/href="(?<file>openssl-(?<version>1.[^"]+)\.tar\.gz)\"/';
}
// 默认预选 phpmicro
$chosen_sources = ['micro'];
// 从参数中获取要编译的 libraries并转换为数组
$libraries = array_map('trim', array_filter(explode(',', $input->getArgument('libraries'))));
if ($libraries) {
foreach ($libraries as $lib) {
// 从 lib 的 config 中找到对应 source 资源名称,组成一个 lib 的 source 列表
$src_name = Config::getLib($lib, 'source');
$chosen_sources[] = $src_name;
}
} else { // 如果传入了空串,那么代表 fetch 所有包
$chosen_sources = [...$chosen_sources, ...array_map(fn ($x) => $x['source'], array_values(Config::getLibs()))];
}
// 从参数中获取要编译的 extensions并转换为数组
$extensions = array_map('trim', array_filter(explode(',', $input->getArgument('extensions'))));
if ($extensions) {
foreach ($extensions as $lib) {
if (Config::getExt($lib, 'type') !== 'builtin') {
$src_name = Config::getExt($lib, 'source');
$chosen_sources[] = $src_name;
}
}
} else {
foreach (Config::getExts() as $ext) {
if ($ext['type'] !== 'builtin') {
$chosen_sources[] = $ext['source'];
}
}
}
$chosen_sources = array_unique($chosen_sources);
// 是否只hash不下载资源
if ($input->getOption('hash')) {
$hash = $this->doHash($chosen_sources);
$output->writeln($hash);
return 0;
}
// 创建目录
f_mkdir(SOURCE_PATH);
f_mkdir(DOWNLOAD_PATH);
// 下载 PHP
logger()->info('Fetching PHP source');
Downloader::fetchSource('php-src', Downloader::getLatestPHPInfo($ver));
// 下载别的依赖资源
$cnt = count($chosen_sources);
$ni = 0;
foreach ($chosen_sources as $name) {
++$ni;
logger()->info("Fetching source {$name} [{$ni}/{$cnt}]");
Downloader::fetchSource($name, Config::getSource($name));
}
// patch 每份资源只需一次如果已经下载好的资源已经patch了就标记一下不patch了
if (!file_exists(SOURCE_PATH . '/.patched')) {
$this->doPatch();
} else {
logger()->notice('sources already patched');
}
// 打印拉取资源用时
$time = round(microtime(true) - START_TIME, 3);
logger()->info('Fetch complete, used ' . $time . ' s !');
return 0;
} catch (\Throwable $e) {
// 不开 debug 模式就不要再显示复杂的调试栈信息了
if ($input->getOption('debug')) {
ExceptionHandler::getInstance()->handle($e);
} else {
logger()->emergency($e->getMessage() . ', previous message: ' . $e->getPrevious()->getMessage());
}
return 1;
}
}
/**
* 计算资源名称列表的 Hash
*
* @param array $chosen_sources 要计算 hash 的资源名称列表
* @throws InvalidArgumentException
* @throws DownloaderException
* @throws FileSystemException
*/
private function doHash(array $chosen_sources): string
{
$files = [];
foreach ($chosen_sources as $name) {
$source = Config::getSource($name);
$filename = match ($source['type']) {
'ghtar' => Downloader::getLatestGithubTarball($name, $source)[1],
'ghtagtar' => Downloader::getLatestGithubTarball($name, $source, 'tags')[1],
'ghrel' => Downloader::getLatestGithubRelease($name, $source)[1],
'filelist' => Downloader::getFromFileList($name, $source)[1],
'url' => $source['filename'] ?? basename($source['url']),
'git' => null,
default => throw new InvalidArgumentException('unknown source type: ' . $source['type']),
};
if ($filename !== null) {
logger()->info("found {$name} source: {$filename}");
$files[] = $filename;
}
}
return hash('sha256', implode('|', $files));
}
/**
* 在拉回资源后,需要对一些文件做一些补丁 patch
*
* @throws FileSystemException
* @throws RuntimeException
*/
private function doPatch(): void
{
// swow 需要软链接内部的文件夹才能正常编译
if (!file_exists(SOURCE_PATH . '/php-src/ext/swow')) {
Patcher::patchSwow();
}
// patch 一些 PHP 的资源,以便编译
Patcher::patchPHPDepFiles();
// openssl 3 需要 patch 额外的东西
if (!$this->input->getOption('with-openssl11') && $this->php_major_ver === '8.0') {
Patcher::patchOpenssl3();
}
// openssl1.1.1q 在 MacOS 上直接编译会报错patch 一下
// @see: https://github.com/openssl/openssl/issues/18720
if ($this->input->getOption('with-openssl11') && file_exists(SOURCE_PATH . '/openssl/test/v3ext.c') && PHP_OS_FAMILY === 'Darwin') {
Patcher::patchDarwinOpenssl11();
}
// 标记 patch 完成,避免重复 patch
file_put_contents(SOURCE_PATH . '/.patched', '');
}
}

View File

@@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\builder\traits\NoMotdTrait;
use SPC\store\Config;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ListExtCommand extends BaseCommand
{
use NoMotdTrait;
protected static $defaultName = 'list-ext';
public function configure()
{
$this->setDescription('List supported extensions');
}
public function execute(InputInterface $input, OutputInterface $output)
{
foreach (Config::getExts() as $ext => $meta) {
echo $ext . PHP_EOL;
}
return 0;
}
}

View File

@@ -1,60 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\exception\FileSystemException;
use SPC\exception\ValidationException;
use SPC\store\FileSystem;
use SPC\util\ConfigValidator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* 修改 config 后对其 kv 进行排序的操作
*/
class SortConfigCommand extends BaseCommand
{
protected static $defaultName = 'sort-config';
public function configure()
{
$this->setDescription('After config edited, sort it by alphabet');
$this->addArgument('config-name', InputArgument::REQUIRED, 'Your config to be sorted, you can sort "lib", "source" and "ext".');
}
/**
* @throws ValidationException
* @throws FileSystemException
*/
public function execute(InputInterface $input, OutputInterface $output): int
{
switch ($name = $input->getArgument('config-name')) {
case 'lib':
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/lib.json'), true);
ConfigValidator::validateLibs($file);
ksort($file);
file_put_contents(ROOT_DIR . '/config/lib.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
break;
case 'source':
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/source.json'), true);
ConfigValidator::validateSource($file);
ksort($file);
file_put_contents(ROOT_DIR . '/config/source.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
break;
case 'ext':
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/ext.json'), true);
ConfigValidator::validateExts($file);
ksort($file);
file_put_contents(ROOT_DIR . '/config/ext.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
break;
default:
$output->writeln("<error>invalid config name: {$name}</error>");
return 1;
}
$output->writeln('<info>sort success</info>');
return 0;
}
}

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\exception;
class DownloaderException extends \Exception
{
}

View File

@@ -1,53 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\exception;
class ExceptionHandler
{
protected $whoops;
private static $obj;
private function __construct()
{
$whoops_class = 'Whoops\Run';
$collision_class = 'NunoMaduro\Collision\Handler';
if (class_exists($collision_class) && class_exists($whoops_class)) {
/* @phpstan-ignore-next-line */
$this->whoops = new $whoops_class();
$this->whoops->allowQuit(false);
$this->whoops->writeToOutput(false);
$this->whoops->pushHandler(new $collision_class());
$this->whoops->register();
}
}
public static function getInstance(): ExceptionHandler
{
if (self::$obj === null) {
self::$obj = new self();
}
return self::$obj;
}
public function getWhoops()
{
return $this->whoops;
}
/**
* 处理异常
*/
public function handle(\Throwable $e): void
{
if (is_null($this->whoops)) {
logger()->error('Uncaught ' . get_class($e) . ': ' . $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')');
logger()->error($e->getTraceAsString());
return;
}
$this->whoops->handleException($e);
}
}

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\exception;
class FileSystemException extends \Exception
{
}

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\exception;
class InvalidArgumentException extends \Exception
{
}

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\exception;
class RuntimeException extends \Exception
{
}

View File

@@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\exception;
class ValidationException extends \Exception
{
}

View File

@@ -1,126 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\store;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
/**
* 一个读取 config 配置的操作类
*/
class Config
{
public static ?array $source = null;
public static ?array $lib = null;
public static ?array $ext = null;
/**
* 从配置文件读取一个资源(source)的元信息
*
* @throws FileSystemException
*/
public static function getSource(string $name): ?array
{
if (self::$source === null) {
self::$source = FileSystem::loadConfigArray('source');
}
return self::$source[$name] ?? null;
}
/**
* 根据不同的操作系统分别选择不同的 lib 库依赖项
* 如果 key 为 null那么直接返回整个 meta。
* 如果 key 不为 null则可以使用的 key 有 static-libs、headers、lib-depends、lib-suggests。
* 对于 macOS 平台,支持 frameworks。
*
* @throws FileSystemException
* @throws RuntimeException
*/
public static function getLib(string $name, ?string $key = null, mixed $default = null)
{
if (self::$lib === null) {
self::$lib = FileSystem::loadConfigArray('lib');
}
if (!isset(self::$lib[$name])) {
throw new RuntimeException('lib [' . $name . '] is not supported yet for get');
}
$supported_sys_based = ['static-libs', 'headers', 'lib-depends', 'lib-suggests', 'frameworks'];
if ($key !== null && in_array($key, $supported_sys_based)) {
$m_key = match (PHP_OS_FAMILY) {
'Windows' => ['-windows', '-win', ''],
'Darwin' => ['-macos', '-unix', ''],
'Linux' => ['-linux', '-unix', ''],
default => throw new RuntimeException('OS ' . PHP_OS_FAMILY . ' is not supported'),
};
foreach ($m_key as $v) {
if (isset(self::$lib[$name][$key . $v])) {
return self::$lib[$name][$key . $v];
}
}
return $default;
}
if ($key !== null) {
return self::$lib[$name][$key] ?? $default;
}
return self::$lib[$name];
}
/**
* @throws FileSystemException
*/
public static function getLibs(): array
{
if (self::$lib === null) {
self::$lib = FileSystem::loadConfigArray('lib');
}
return self::$lib;
}
/**
* @throws FileSystemException
* @throws RuntimeException
*/
public static function getExt(string $name, ?string $key = null, mixed $default = null)
{
if (self::$ext === null) {
self::$ext = FileSystem::loadConfigArray('ext');
}
if (!isset(self::$ext[$name])) {
throw new RuntimeException('ext [' . $name . '] is not supported yet for get');
}
$supported_sys_based = ['lib-depends', 'lib-suggests', 'ext-depends', 'ext-suggests', 'arg-type'];
if ($key !== null && in_array($key, $supported_sys_based)) {
$m_key = match (PHP_OS_FAMILY) {
'Windows' => ['-windows', '-win', ''],
'Darwin' => ['-macos', '-unix', ''],
'Linux' => ['-linux', '-unix', ''],
default => throw new RuntimeException('OS ' . PHP_OS_FAMILY . ' is not supported'),
};
foreach ($m_key as $v) {
if (isset(self::$ext[$name][$key . $v])) {
return self::$ext[$name][$key . $v];
}
}
return $default;
}
if ($key !== null) {
return self::$ext[$name][$key] ?? $default;
}
return self::$ext[$name];
}
/**
* @throws FileSystemException
*/
public static function getExts(): array
{
if (self::$ext === null) {
self::$ext = FileSystem::loadConfigArray('ext');
}
return self::$ext;
}
}

View File

@@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\store;
class CurlHook
{
/**
* 执行 GitHub Token 的 Curl 头添加
*
* @param string $method 修改的 method
* @param string $url 修改的链接
* @param array $headers 修改的 headers
*/
public static function setupGithubToken(string &$method, string &$url, array &$headers): void
{
if (!getenv('GITHUB_TOKEN')) {
return;
}
if (getenv('GITHUB_USER')) {
$auth = base64_encode(getenv('GITHUB_USER') . ':' . getenv('GITHUB_TOKEN'));
$headers[] = "Authorization: Basic {$auth}";
logger()->info("using basic github token for {$method} {$url}");
} else {
$auth = getenv('GITHUB_TOKEN');
$headers[] = "Authorization: Bearer {$auth}";
logger()->info("using bearer github token for {$method} {$url}");
}
}
}

View File

@@ -1,367 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\store;
use JetBrains\PhpStorm\ArrayShape;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
/**
* 资源下载器
*/
class Downloader
{
/**
* 获取 BitBucket 仓库的最新 Tag
*
* @param string $name 资源名称
* @param array $source 资源的元信息,包含字段 repo
* @return array<int, string> 返回下载 url 链接和文件名
* @throws DownloaderException
*/
public static function getLatestBitbucketTag(string $name, array $source): array
{
logger()->debug("finding {$name} source from bitbucket tag");
$data = json_decode(self::curlExec(
url: "https://api.bitbucket.org/2.0/repositories/{$source['repo']}/refs/tags"
), true);
$ver = $data['values'][0]['name'];
if (!$ver) {
throw new DownloaderException("failed to find {$name} bitbucket source");
}
$url = "https://bitbucket.org/{$source['repo']}/get/{$ver}.tar.gz";
$headers = self::curlExec(
url: $url,
method: 'HEAD'
);
preg_match('/^content-disposition:\s+attachment;\s*filename=("?)(?<filename>.+\.tar\.gz)\1/im', $headers, $matches);
if ($matches) {
$filename = $matches['filename'];
} else {
$filename = "{$name}-{$data['tag_name']}.tar.gz";
}
return [$url, $filename];
}
/**
* 获取 GitHub 最新的打包地址和文件名
*
* @param string $name 包名称
* @param array $source 源信息
* @throws DownloaderException
*/
public static function getLatestGithubTarball(string $name, array $source, string $type = 'releases'): array
{
logger()->debug("finding {$name} source from github {$type} tarball");
$data = json_decode(self::curlExec(
url: "https://api.github.com/repos/{$source['repo']}/{$type}",
hooks: [[CurlHook::class, 'setupGithubToken']]
), true);
$url = $data[0]['tarball_url'];
if (!$url) {
throw new DownloaderException("failed to find {$name} source");
}
$headers = self::curlExec(
url: $url,
method: 'HEAD',
hooks: [[CurlHook::class, 'setupGithubToken']],
);
preg_match('/^content-disposition:\s+attachment;\s*filename=("?)(?<filename>.+\.tar\.gz)\1/im', $headers, $matches);
if ($matches) {
$filename = $matches['filename'];
} else {
$filename = "{$name}-" . ($type === 'releases' ? $data['tag_name'] : $data['name']) . '.tar.gz';
}
return [$url, $filename];
}
/**
* 获取 GitHub 最新的 Release 下载信息
*
* @param string $name 资源名
* @param array $source 资源的元信息,包含字段 repo、match
* @throws DownloaderException
*/
public static function getLatestGithubRelease(string $name, array $source): array
{
logger()->debug("finding {$name} source from github releases assests");
$data = json_decode(self::curlExec(
url: "https://api.github.com/repos/{$source['repo']}/releases",
hooks: [[CurlHook::class, 'setupGithubToken']],
), true);
$url = null;
foreach ($data[0]['assets'] as $asset) {
if (preg_match('|' . $source['match'] . '|', $asset['name'])) {
$url = $asset['browser_download_url'];
break;
}
}
if (!$url) {
throw new DownloaderException("failed to find {$name} source");
}
$filename = basename($url);
return [$url, $filename];
}
/**
* 获取文件列表的资源链接和名称
*
* @param string $name 资源名称
* @param array $source 资源元信息,包含 url、regex
* @throws DownloaderException
*/
public static function getFromFileList(string $name, array $source): array
{
logger()->debug("finding {$name} source from file list");
$page = self::curlExec($source['url']);
preg_match_all($source['regex'], $page, $matches);
if (!$matches) {
throw new DownloaderException("Failed to get {$name} version");
}
$versions = [];
foreach ($matches['version'] as $i => $version) {
$lowerVersion = strtolower($version);
foreach ([
'alpha',
'beta',
'rc',
'pre',
'nightly',
'snapshot',
'dev',
] as $betaVersion) {
if (str_contains($lowerVersion, $betaVersion)) {
continue 2;
}
}
$versions[$version] = $matches['file'][$i];
}
uksort($versions, 'version_compare');
return [$source['url'] . end($versions), end($versions)];
}
/**
* 通过链接下载资源到本地并解压
*
* @param string $name 资源名称
* @param string $url 下载链接
* @param string $filename 下载到下载目录的目标文件名称,例如 xz.tar.gz
* @param null|string $path 如果指定了此参数,则会移动该资源目录到目标目录
* @throws FileSystemException
* @throws RuntimeException
* @throws DownloaderException
*/
public static function downloadUrl(string $name, string $url, string $filename, ?string $path = null): void
{
if (!file_exists(DOWNLOAD_PATH . "/{$filename}")) {
logger()->debug("downloading {$url}");
self::curlDown(url: $url, path: DOWNLOAD_PATH . "/{$filename}");
} else {
logger()->notice("{$filename} already exists");
}
FileSystem::extractSource($name, DOWNLOAD_PATH . "/{$filename}");
if ($path) {
$path = FileSystem::convertPath(SOURCE_PATH . "/{$path}");
$src_path = FileSystem::convertPath(SOURCE_PATH . "/{$name}");
switch (PHP_OS_FAMILY) {
case 'Windows':
f_passthru('move "' . $src_path . '" "' . $path . '"');
break;
case 'Linux':
case 'Darwin':
f_passthru('mv "' . $src_path . '" "' . $path . '"');
break;
}
}
}
public static function downloadGit(string $name, string $url, string $branch, ?string $path = null): void
{
if ($path !== null) {
$path = SOURCE_PATH . "/{$path}";
} else {
$path = DOWNLOAD_PATH . "/{$name}";
}
$download_path = DOWNLOAD_PATH . "/{$name}";
if (file_exists($download_path)) {
logger()->notice("{$name} git source already fetched");
} else {
logger()->debug("cloning {$name} source");
$check = !defined('DEBUG_MODE') ? ' -q' : '';
f_passthru(
'git clone' . $check .
' --config core.autocrlf=false ' .
"--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\""
);
}
// 复制目录过去
if ($path !== $download_path) {
$dst_path = FileSystem::convertPath($path);
$src_path = FileSystem::convertPath($download_path);
switch (PHP_OS_FAMILY) {
case 'Windows':
f_passthru('xcopy "' . $src_path . '" "' . $dst_path . '" /s/e/v/y/i');
break;
case 'Linux':
case 'Darwin':
f_passthru('cp -r "' . $src_path . '" "' . $dst_path . '"');
break;
}
}
}
/**
* 拉取资源
*
* @param string $name 资源名称
* @param array $source 资源参数,包含 type、path、rev、url、filename、regex、license
* @throws DownloaderException
* @throws RuntimeException
* @throws FileSystemException
*/
public static function fetchSource(string $name, array $source): void
{
// 避免重复 fetch
if (!isset($source['path']) && !is_dir(FileSystem::convertPath(DOWNLOAD_PATH . "/{$name}")) || isset($source['path']) && is_dir(FileSystem::convertPath(SOURCE_PATH . "/{$source['path']}"))) {
logger()->notice("{$name} source already extracted");
return;
}
try {
switch ($source['type']) {
case 'bitbuckettag': // 从 BitBucket 的 Tag 拉取
[$url, $filename] = self::getLatestBitbucketTag($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
break;
case 'ghtar': // 从 GitHub 的 TarBall 拉取
[$url, $filename] = self::getLatestGithubTarball($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
break;
case 'ghtagtar': // 根据 GitHub 的 Tag 拉取相应版本的 Tar
[$url, $filename] = self::getLatestGithubTarball($name, $source, 'tags');
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
break;
case 'ghrel': // 通过 GitHub Release 来拉取
[$url, $filename] = self::getLatestGithubRelease($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
break;
case 'filelist': // 通过网站提供的 filelist 使用正则提取后拉取
[$url, $filename] = self::getFromFileList($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
break;
case 'url': // 通过直链拉取
$url = $source['url'];
$filename = $source['filename'] ?? basename($source['url']);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
break;
case 'git': // 通过拉回 Git 仓库的形式拉取
self::downloadGit($name, $source['url'], $source['rev'], $source['path'] ?? null);
break;
default:
throw new DownloaderException('unknown source type: ' . $source['type']);
}
} catch (RuntimeException $e) {
// 因为某些时候通过命令行下载的文件在失败后不会删除,这里检测到文件存在需要手动删一下
if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) {
logger()->warning('Deleting download file: ' . $filename);
unlink(DOWNLOAD_PATH . '/' . $filename);
}
throw $e;
}
}
/**
* 获取 PHP x.y 的具体版本号,例如通过 8.1 来获取 8.1.10
*
* @throws DownloaderException
*/
#[ArrayShape(['type' => 'string', 'path' => 'string', 'rev' => 'string', 'url' => 'string'])]
public static function getLatestPHPInfo(string $major_version): array
{
// 查找最新的小版本号
$info = json_decode(self::curlExec(url: "https://www.php.net/releases/index.php?json&version={$major_version}"), true);
$version = $info['version'];
// 从官网直接下载
return [
'type' => 'url',
// 'url' => "https://www.php.net/distributions/php-{$version}.tar.gz",
'url' => "https://mirrors.zhamao.xin/php/php-{$version}.tar.gz",
];
}
/**
* 使用 curl 命令拉取元信息
*
* @throws DownloaderException
*/
public static function curlExec(string $url, string $method = 'GET', array $headers = [], array $hooks = []): string
{
foreach ($hooks as $hook) {
$hook($method, $url, $headers);
}
FileSystem::findCommandPath('curl');
$methodArg = match ($method) {
'GET' => '',
'HEAD' => '-I',
default => "-X \"{$method}\"",
};
$headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$cmd = "curl -sfSL {$methodArg} {$headerArg} \"{$url}\"";
if (getenv('CACHE_API_EXEC') === 'yes') {
if (!file_exists(SOURCE_PATH . '/.curl_exec_cache')) {
$cache = [];
} else {
$cache = json_decode(file_get_contents(SOURCE_PATH . '/.curl_exec_cache'), true);
}
if (isset($cache[$cmd]) && $cache[$cmd]['expire'] >= time()) {
return $cache[$cmd]['cache'];
}
f_exec($cmd, $output, $ret);
if ($ret !== 0) {
throw new DownloaderException('failed http fetch');
}
$cache[$cmd]['cache'] = implode("\n", $output);
$cache[$cmd]['expire'] = time() + 3600;
file_put_contents(SOURCE_PATH . '/.curl_exec_cache', json_encode($cache));
return $cache[$cmd]['cache'];
}
f_exec($cmd, $output, $ret);
if ($ret !== 0) {
throw new DownloaderException('failed http fetch');
}
return implode("\n", $output);
}
/**
* 使用 curl 命令下载文件
*
* @throws DownloaderException
* @throws RuntimeException
*/
public static function curlDown(string $url, string $path, string $method = 'GET', array $headers = [], array $hooks = []): void
{
foreach ($hooks as $hook) {
$hook($method, $url, $headers);
}
$methodArg = match ($method) {
'GET' => '',
'HEAD' => '-I',
default => "-X \"{$method}\"",
};
$headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$check = !defined('DEBUG_MODE') ? 's' : '#';
$cmd = "curl -{$check}fSL -o \"{$path}\" {$methodArg} {$headerArg} \"{$url}\"";
f_passthru($cmd);
}
}

View File

@@ -1,381 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\store;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
class FileSystem
{
/**
* @throws FileSystemException
*/
public static function loadConfigArray(string $config): array
{
$whitelist = ['ext', 'lib', 'source'];
if (!in_array($config, $whitelist)) {
throw new FileSystemException('Reading ' . $config . '.json is not allowed');
}
$tries = [
WORKING_DIR . '/config/' . $config . '.json',
ROOT_DIR . '/config/' . $config . '.json',
];
foreach ($tries as $try) {
if (file_exists($try)) {
$json = json_decode(self::readFile($try), true);
if (!is_array($json)) {
throw new FileSystemException('Reading ' . $try . ' failed');
}
return $json;
}
}
throw new FileSystemException('Reading ' . $config . '.json failed');
}
/**
* 读取文件,读不出来直接抛出异常
*
* @param string $filename 文件路径
* @throws FileSystemException
*/
public static function readFile(string $filename): string
{
// logger()->debug('Reading file: ' . $filename);
$r = file_get_contents(self::convertPath($filename));
if ($r === false) {
throw new FileSystemException('Reading file ' . $filename . ' failed');
}
return $r;
}
/**
* @throws FileSystemException
*/
public static function replaceFile(string $filename, int $replace_type = REPLACE_FILE_STR, mixed $callback_or_search = null, mixed $to_replace = null): bool|int
{
logger()->debug('Replacing file with type[' . $replace_type . ']: ' . $filename);
$file = self::readFile($filename);
switch ($replace_type) {
case REPLACE_FILE_STR:
default:
$file = str_replace($callback_or_search, $to_replace, $file);
break;
case REPLACE_FILE_PREG:
$file = preg_replace($callback_or_search, $to_replace, $file);
break;
case REPLACE_FILE_USER:
$file = $callback_or_search($file);
break;
}
return file_put_contents($filename, $file);
}
/**
* 获取文件后缀
*
* @param string $fn 文件名
*/
public static function extname(string $fn): string
{
$parts = explode('.', basename($fn));
if (count($parts) < 2) {
return '';
}
return array_pop($parts);
}
/**
* 寻找命令的真实路径,效果类似 which
*
* @param string $name 命令名称
* @param array $paths 路径列表,如果为空则默认从 PATH 系统变量搜索
*/
public static function findCommandPath(string $name, array $paths = []): ?string
{
if (!$paths) {
$paths = explode(PATH_SEPARATOR, getenv('PATH'));
}
if (PHP_OS_FAMILY === 'Windows') {
foreach ($paths as $path) {
foreach (['.exe', '.bat', '.cmd'] as $suffix) {
if (file_exists($path . DIRECTORY_SEPARATOR . $name . $suffix)) {
return $path . DIRECTORY_SEPARATOR . $name . $suffix;
}
}
}
return null;
}
foreach ($paths as $path) {
if (file_exists($path . DIRECTORY_SEPARATOR . $name)) {
return $path . DIRECTORY_SEPARATOR . $name;
}
}
return null;
}
public static function copyDir(string $from, string $to): void
{
$iterator = new \RecursiveIteratorIterator(new RecursiveDirectoryIterator($from, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_FILEINFO), \RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $item) {
/**
* @var \SplFileInfo $item
*/
$target = $to . substr($item->getPathname(), strlen($from));
if ($item->isDir()) {
logger()->info("mkdir {$target}");
f_mkdir($target, recursive: true);
} else {
logger()->info("copying {$item} to {$target}");
@f_mkdir(dirname($target), recursive: true);
copy($item->getPathname(), $target);
}
}
}
/**
* 解压缩下载的资源包到 source 目录
*
* @param string $name 资源名
* @param string $filename 文件名
* @throws FileSystemException
* @throws RuntimeException
*/
public static function extractSource(string $name, string $filename): void
{
logger()->info("extracting {$name} source");
try {
if (PHP_OS_FAMILY !== 'Windows') {
if (f_mkdir(directory: SOURCE_PATH . "/{$name}", recursive: true) !== true) {
throw new FileSystemException('create ' . $name . 'source dir failed');
}
switch (self::extname($filename)) {
case 'xz':
case 'txz':
f_passthru("tar -xf {$filename} -C " . SOURCE_PATH . "/{$name} --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 " . SOURCE_PATH . "/{$name} --strip-components 1");
break;
case 'bz2':
f_passthru("tar -xjf {$filename} -C " . SOURCE_PATH . "/{$name} --strip-components 1");
break;
case 'zip':
f_passthru("unzip {$filename} -d " . SOURCE_PATH . "/{$name}");
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 " . SOURCE_PATH . "/{$name} --strip-components 1");
break;
default:
throw new FileSystemException('unknown archive format: ' . $filename);
}
} else {
// find 7z
$_7zExe = self::findCommandPath('7z', [
'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);
switch (self::extname($filename)) {
case 'zstd':
case 'zst':
if (!str_contains($_7zExe, 'Zstandard')) {
throw new FileSystemException("zstd is not supported: {$filename}");
}
// no break
case 'xz':
case 'txz':
case 'gz':
case 'tgz':
case 'bz2':
f_passthru("\"{$_7zExe}\" x -so {$filename} | tar -f - -x -C " . SOURCE_PATH . "/{$name} --strip-components 1");
break;
case 'tar':
f_passthru("tar -xf {$filename} -C " . SOURCE_PATH . "/{$name} --strip-components 1");
break;
case 'zip':
f_passthru("\"{$_7zExe}\" x {$filename} -o" . SOURCE_PATH . "/{$name}");
break;
default:
throw new FileSystemException("unknown archive format: {$filename}");
}
}
} catch (RuntimeException $e) {
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('rmdir /s /q ' . SOURCE_PATH . "/{$name}");
} else {
f_passthru('rm -r ' . SOURCE_PATH . "/{$name}");
}
throw new FileSystemException('Cannot extract source ' . $name, $e->getCode(), $e);
}
}
/**
* 根据系统环境的不同,自动转换路径的分隔符
*
* @param string $path 路径
*/
public static function convertPath(string $path): string
{
if (str_starts_with($path, 'phar://')) {
return $path;
}
return str_replace('/', DIRECTORY_SEPARATOR, $path);
}
/**
* 递归或非递归扫描目录,可返回相对目录的文件列表或绝对目录的文件列表
*
* @param string $dir 目录
* @param bool $recursive 是否递归扫描子目录
* @param bool|string $relative 是否返回相对目录如果为true则返回相对目录如果为false则返回绝对目录
* @param bool $include_dir 非递归模式下,是否包含目录
* @return array|false
* @since 2.5
*/
public static function scanDirFiles(string $dir, bool $recursive = true, bool|string $relative = false, bool $include_dir = false): bool|array
{
$dir = self::convertPath($dir);
// 不是目录不扫,直接 false 处理
if (!is_dir($dir)) {
logger()->warning('Scan dir failed, no such directory.');
return false;
}
logger()->debug('scanning directory ' . $dir);
// 套上 zm_dir
$scan_list = scandir($dir);
if ($scan_list === false) {
logger()->warning('Scan dir failed, cannot scan directory: ' . $dir);
return false;
}
$list = [];
// 将 relative 置为相对目录的前缀
if ($relative === true) {
$relative = $dir;
}
// 遍历目录
foreach ($scan_list as $v) {
// Unix 系统排除这俩目录
if ($v == '.' || $v == '..') {
continue;
}
$sub_file = self::convertPath($dir . '/' . $v);
if (is_dir($sub_file) && $recursive) {
# 如果是 目录 且 递推 , 则递推添加下级文件
$list = array_merge($list, self::scanDirFiles($sub_file, $recursive, $relative));
} elseif (is_file($sub_file) || is_dir($sub_file) && !$recursive && $include_dir) {
# 如果是 文件 或 (是 目录 且 不递推 且 包含目录)
if (is_string($relative) && mb_strpos($sub_file, $relative) === 0) {
$list[] = ltrim(mb_substr($sub_file, mb_strlen($relative)), '/\\');
} elseif ($relative === false) {
$list[] = $sub_file;
}
}
}
return $list;
}
/**
* 获取该路径下的所有类名,根据 psr-4 方式
*
* @param string $dir 目录
* @param string $base_namespace 基类命名空间
* @param null|mixed $rule 规则回调
* @param bool|string $return_path_value 是否返回路径对应的数组,默认只返回类名列表
* @throws FileSystemException
*/
public static function getClassesPsr4(string $dir, string $base_namespace, mixed $rule = null, bool|string $return_path_value = false): array
{
$classes = [];
// 扫描目录使用递归模式相对路径模式因为下面此路径要用作转换成namespace
$files = FileSystem::scanDirFiles($dir, true, true);
if ($files === false) {
throw new FileSystemException('Cannot scan dir files during get classes psr-4 from dir: ' . $dir);
}
foreach ($files as $v) {
$pathinfo = pathinfo($v);
if (($pathinfo['extension'] ?? '') == 'php') {
$path = rtrim($dir, '/') . '/' . rtrim($pathinfo['dirname'], './') . '/' . $pathinfo['basename'];
// 过滤不包含类的文件
$tokens = token_get_all(self::readFile($path));
$found = false;
foreach ($tokens as $token) {
if (!is_array($token)) {
continue;
}
if ($token[0] === T_CLASS) {
$found = true;
break;
}
}
if (!$found) {
continue;
}
if ($rule === null) { // 规则未设置回调时候,使用默认的识别过滤规则
/*if (substr(file_get_contents($dir . '/' . $v), 6, 6) == '#plain') {
continue;
}*/
if (file_exists($dir . '/' . $pathinfo['basename'] . '.ignore')) {
continue;
}
if (mb_substr($pathinfo['basename'], 0, 7) == 'global_' || mb_substr($pathinfo['basename'], 0, 7) == 'script_') {
continue;
}
} elseif (is_callable($rule) && !$rule($dir, $pathinfo)) {
continue;
}
$dirname = $pathinfo['dirname'] == '.' ? '' : (str_replace('/', '\\', $pathinfo['dirname']) . '\\');
$class_name = $base_namespace . '\\' . $dirname . $pathinfo['filename'];
if (is_string($return_path_value)) {
$classes[$class_name] = $return_path_value . '/' . $v;
} else {
$classes[] = $class_name;
}
}
}
return $classes;
}
/**
* 删除目录及目录下的所有文件(危险操作)
*
* @throws FileSystemException
*/
public static function removeDir(string $dir, bool $throw_on_fail = false): bool
{
$dir = FileSystem::convertPath($dir);
logger()->warning('Removing path recursively: "' . $dir . '"');
switch (PHP_OS_FAMILY) {
case 'Windows':
case 'WINNT':
case 'Cygwin':
f_exec('rmdir /s /g "' . $dir . '"', $out, $ret);
break;
case 'Darwin':
case 'Linux':
f_exec('rm -rf ' . escapeshellarg($dir), $out, $ret);
break;
default:
throw new FileSystemException('Unsupported OS type: ' . PHP_OS_FAMILY);
}
if ($ret !== 0 && $throw_on_fail) {
throw new FileSystemException('Cannot remove dir "' . $dir . '"');
}
return $ret === 0;
}
}

View File

@@ -1,71 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\util;
use SPC\exception\ValidationException;
class ConfigValidator
{
/**
* 验证 source.json
*
* @param array $data source.json 加载后的数据
* @throws ValidationException
*/
public static function validateSource(array $data): void
{
foreach ($data as $name => $src) {
isset($src['type']) || throw new ValidationException("source {$name} must have prop: [type]");
is_string($src['type']) || throw new ValidationException("source {$name} type prop must be string");
in_array($src['type'], ['filelist', 'git', 'ghtagtar', 'ghtar', 'ghrel', 'url']) || throw new ValidationException("source {$name} type [{$src['type']}] is invalid");
switch ($src['type']) {
case 'filelist':
isset($src['url'], $src['regex']) || throw new ValidationException("source {$name} needs [url] and [regex] props");
is_string($src['url']) && is_string($src['regex']) || throw new ValidationException("source {$name} [url] and [regex] must be string");
break;
case 'git':
isset($src['url'], $src['rev']) || throw new ValidationException("source {$name} needs [url] and [rev] props");
is_string($src['url']) && is_string($src['rev']) || throw new ValidationException("source {$name} [url] and [rev] must be string");
is_string($src['path'] ?? '') || throw new ValidationException("source {$name} [path] must be string");
break;
case 'ghtagtar':
case 'ghtar':
isset($src['repo']) || throw new ValidationException("source {$name} needs [repo] prop");
is_string($src['repo']) || throw new ValidationException("source {$name} [repo] must be string");
is_string($src['path'] ?? '') || throw new ValidationException("source {$name} [path] must be string");
break;
case 'ghrel':
isset($src['repo'], $src['match']) || throw new ValidationException("source {$name} needs [repo] and [match] props");
is_string($src['repo']) && is_string($src['match']) || throw new ValidationException("source {$name} [repo] and [match] must be string");
break;
case 'url':
isset($src['url']) || throw new ValidationException("source {$name} needs [url] prop");
is_string($src['url']) || throw new ValidationException("source {$name} [url] must be string");
break;
}
}
}
/**
* @param mixed $data
* @throws ValidationException
*/
public static function validateLibs($data, array $source_data = []): void
{
is_array($data) || throw new ValidationException('lib.json is broken');
foreach ($data as $name => $lib) {
isset($lib['source']) || throw new ValidationException("lib {$name} does not assign any source");
is_string($lib['source']) || throw new ValidationException("lib {$name} source must be string");
empty($source_data) || isset($source_data[$lib['source']]) || throw new ValidationException("lib {$name} assigns an invalid source: {$lib['source']}");
!isset($lib['lib-depends']) || !is_assoc_array($lib['lib-depends']) || throw new ValidationException("lib {$name} dependencies must be a list");
!isset($lib['lib-suggests']) || !is_assoc_array($lib['lib-suggests']) || throw new ValidationException("lib {$name} suggested dependencies must be a list");
}
}
public static function validateExts($data, array $source_data = []): void
{
is_array($data) || throw new ValidationException('ext.json is broken');
}
}

View File

@@ -1,106 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\util;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\Config;
/**
* 依赖处理工具类,包含处理扩展、库的依赖列表顺序等
*/
class DependencyUtil
{
/**
* 根据需要的 ext 列表获取依赖的 lib 列表,同时根据依赖关系排序
*
* @param array $exts 要获取 libs 依赖的列表
* @param array $additional_libs 额外要添加的库列表,用于激活 lib-suggests 触发的额外库特性
* @return array 返回一个包含三个数组的数组,第一个是排序后的 ext 列表,第二个是排序后的 lib 列表,第三个是没有传入但是依赖了的 ext 列表
* @throws FileSystemException
* @throws RuntimeException
*/
public static function getExtLibsByDeps(array $exts, array $additional_libs = []): array
{
// 先对扩展列表进行一个依赖筛选
$sorted = [];
$visited = [];
$not_included_exts = [];
foreach ($exts as $ext) {
if (!isset($visited[$ext])) {
self::visitExtDeps($ext, $visited, $sorted);
}
}
$libs = $additional_libs;
// 遍历每一个 ext 的 libs
foreach ($sorted as $ext) {
if (!in_array($ext, $exts)) {
$not_included_exts[] = $ext;
}
foreach (Config::getExt($ext, 'lib-depends', []) as $lib) {
if (!in_array($lib, $libs)) {
$libs[] = $lib;
}
}
}
return [$sorted, self::getLibsByDeps($libs), $not_included_exts];
}
/**
* 根据 lib 库的依赖关系进行一个排序,同时返回多出来的依赖列表
*
* @param array $libs 要排序的 libs 列表
* @return array 排序后的列表
* @throws FileSystemException
* @throws RuntimeException
*/
public static function getLibsByDeps(array $libs): array
{
$sorted = [];
$visited = [];
// 遍历所有
foreach ($libs as $lib) {
if (!isset($visited[$lib])) {
self::visitLibDeps($lib, $visited, $sorted);
}
}
return $sorted;
}
/**
* @throws RuntimeException
* @throws FileSystemException
*/
private static function visitLibDeps(string $lib_name, array &$visited, array &$sorted): void
{
// 如果已经识别到了,那就不管
if (isset($visited[$lib_name])) {
return;
}
$visited[$lib_name] = true;
// 遍历该依赖的所有依赖(此处的 getLib 如果检测到当前库不存在的话,会抛出异常)
foreach (Config::getLib($lib_name, 'lib-depends', []) as $dep) {
self::visitLibDeps($dep, $visited, $sorted);
}
$sorted[] = $lib_name;
}
/**
* @throws RuntimeException
* @throws FileSystemException
*/
private static function visitExtDeps(string $ext_name, array &$visited, array &$sorted): void
{
if (isset($visited[$ext_name])) {
return;
}
$visited[$ext_name] = true;
foreach (Config::getExt($ext_name, 'ext-depends', []) as $dep) {
self::visitExtDeps($dep, $visited, $sorted);
}
$sorted[] = $ext_name;
}
}

View File

@@ -1,271 +0,0 @@
<?php
declare(strict_types=1);
namespace SPC\util;
use SPC\builder\BuilderBase;
use SPC\builder\macos\MacOSBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
class Patcher
{
/**
* @throws FileSystemException
* @throws RuntimeException
*/
public static function patchPHPDepFiles(): void
{
$ver_file = SOURCE_PATH . '/php-src/main/php_version.h';
if (!file_exists($ver_file)) {
throw new FileSystemException('Patch failed, cannot find php source files');
}
$version_h = FileSystem::readFile(SOURCE_PATH . '/php-src/main/php_version.h');
preg_match('/#\s*define\s+PHP_MAJOR_VERSION\s+(\d+)\s+#\s*define\s+PHP_MINOR_VERSION\s+(\d+)\s+/m', $version_h, $match);
// $ver = "{$match[1]}.{$match[2]}";
logger()->info('Patching php');
$major_ver = $match[1] . $match[2];
$check = !defined('DEBUG_MODE') ? ' -q' : '';
// f_passthru('cd ' . SOURCE_PATH . '/php-src && git checkout' . $check . ' HEAD');
$patch_list = [
'static_opcache',
'static_extensions_win32',
'cli_checks',
'disable_huge_page',
'vcruntime140',
'win32',
'zend_stream',
];
$patch_list = array_merge($patch_list, match (PHP_OS_FAMILY) {
'Windows' => [
'cli_static',
],
'Darwin' => [
'macos_iconv',
],
default => [],
});
$patches = [];
$serial = ['80', '81', '82'];
foreach ($patch_list as $patchName) {
if (file_exists(SOURCE_PATH . "/php-src/sapi/micro/patches/{$patchName}.patch")) {
$patches[] = "sapi/micro/patches/{$patchName}.patch";
continue;
}
for ($i = array_search($major_ver, $serial, true); $i >= 0; --$i) {
$tryMajMin = $serial[$i];
if (!file_exists(SOURCE_PATH . "/php-src/sapi/micro/patches/{$patchName}_{$tryMajMin}.patch")) {
continue;
}
$patches[] = "sapi/micro/patches/{$patchName}_{$tryMajMin}.patch";
continue 2;
}
throw new RuntimeException("failed finding patch {$patchName}");
}
$patchesStr = str_replace('/', DIRECTORY_SEPARATOR, implode(' ', $patches));
f_passthru(
'cd ' . SOURCE_PATH . '/php-src && ' .
(PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . $patchesStr . ' | patch -p1'
);
}
public static function patchOpenssl3(): void
{
logger()->info('Patching PHP with openssl 3.0');
$openssl_c = file_get_contents(SOURCE_PATH . '/php-src/ext/openssl/openssl.c');
$openssl_c = preg_replace('/REGISTER_LONG_CONSTANT\s*\(\s*"OPENSSL_SSLV23_PADDING"\s*.+;/', '', $openssl_c);
file_put_contents(SOURCE_PATH . '/php-src/ext/openssl/openssl.c', $openssl_c);
}
/**
* @throws RuntimeException
*/
public static function patchSwow(): void
{
logger()->info('Patching swow');
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('cd ' . SOURCE_PATH . '/php-src/ext && mklink /D swow swow-src\ext');
} else {
f_passthru('cd ' . SOURCE_PATH . '/php-src/ext && ln -s swow-src/ext swow');
}
}
public static function patchPHPBeforeConfigure(BuilderBase $builder): void
{
if ($builder->getExt('curl')) {
logger()->info('patching before-configure for curl checks');
$file1 = "AC_DEFUN([PHP_CHECK_LIBRARY], [\n $3\n])";
$files = FileSystem::readFile(SOURCE_PATH . '/php-src/ext/curl/config.m4');
$file2 = 'AC_DEFUN([PHP_CHECK_LIBRARY], [
save_old_LDFLAGS=$LDFLAGS
ac_stuff="$5"
save_ext_shared=$ext_shared
ext_shared=yes
PHP_EVAL_LIBLINE([$]ac_stuff, LDFLAGS)
AC_CHECK_LIB([$1],[$2],[
LDFLAGS=$save_old_LDFLAGS
ext_shared=$save_ext_shared
$3
],[
LDFLAGS=$save_old_LDFLAGS
ext_shared=$save_ext_shared
unset ac_cv_lib_$1[]_$2
$4
])dnl
])';
file_put_contents(SOURCE_PATH . '/php-src/ext/curl/config.m4', $file1 . "\n" . $files . "\n" . $file2);
}
// if ($builder->getExt('pdo_sqlite')) {
// FileSystem::replaceFile()
// }
}
/**
* @throws FileSystemException
* @throws RuntimeException
*/
public static function patchPHPConfigure(BuilderBase $builder): void
{
$frameworks = $builder instanceof MacOSBuilder ? ' ' . $builder->getFrameworks(true) . ' ' : '';
$curl = $builder->getExt('curl');
if ($curl) {
logger()->info('patching configure for curl checks');
FileSystem::replaceFile(
SOURCE_PATH . '/php-src/configure',
REPLACE_FILE_PREG,
'/-lcurl/',
$curl->getLibFilesString() . $frameworks
);
}
$bzip2 = $builder->getExt('bz2');
if ($bzip2) {
logger()->info('patching configure for bzip2 checks');
FileSystem::replaceFile(
SOURCE_PATH . '/php-src/configure',
REPLACE_FILE_PREG,
'/-lbz2/',
$bzip2->getLibFilesString() . $frameworks
);
}
$pdo_sqlite = $builder->getExt('pdo_sqlite');
if ($pdo_sqlite) {
logger()->info('patching configure for pdo_sqlite linking');
FileSystem::replaceFile(
SOURCE_PATH . '/php-src/configure',
REPLACE_FILE_PREG,
'/sqlite3_column_table_name=yes/',
'sqlite3_column_table_name=no'
);
}
logger()->info('patching configure for disable capstone');
FileSystem::replaceFile(
SOURCE_PATH . '/php-src/configure',
REPLACE_FILE_PREG,
'/have_capstone="yes"/',
'have_capstone="no"'
);
if (property_exists($builder, 'arch') && php_uname('m') !== $builder->arch) {
// cross-compiling
switch ($builder->arch) {
case 'aarch64':
case 'arm64':
// almost all bsd/linux supports this
logger()->info('patching configure for shm mmap checks (cross-compiling)');
FileSystem::replaceFile(
SOURCE_PATH . '/php-src/configure',
REPLACE_FILE_PREG,
'/have_shm_mmap_anon=no/',
'have_shm_mmap_anon=yes'
);
FileSystem::replaceFile(
SOURCE_PATH . '/php-src/configure',
REPLACE_FILE_PREG,
'/have_shm_mmap_posix=no/',
'have_shm_mmap_posix=yes'
);
break;
case 'x86_64':
break;
default:
throw new RuntimeException('unsupported arch: ' . $builder->arch);
}
}
}
/**
* @throws FileSystemException
*/
public static function patchUnixLibpng(): void
{
FileSystem::replaceFile(
SOURCE_PATH . '/libpng/configure',
REPLACE_FILE_STR,
'-lz',
BUILD_LIB_PATH . '/libz.a'
);
}
/**
* @throws FileSystemException
*/
public static function patchDarwinOpenssl11(): void
{
FileSystem::replaceFile(
SOURCE_PATH . '/openssl/test/v3ext.c',
REPLACE_FILE_STR,
'#include <stdio.h>',
'#include <stdio.h>' . PHP_EOL . '#include <string.h>'
);
}
public static function patchLinuxPkgConfig(string $path): void
{
logger()->info("fixing pc {$path}");
$workspace = BUILD_ROOT_PATH;
if ($workspace === '/') {
$workspace = '';
}
$content = file_get_contents($path);
$content = preg_replace('/^prefix=.+$/m', "prefix={$workspace}", $content);
$content = preg_replace('/^libdir=.+$/m', 'libdir=${prefix}/lib', $content);
$content = preg_replace('/^includedir=.+$/m', 'includedir=${prefix}/include', $content);
file_put_contents($path, $content);
}
/**
* @throws FileSystemException
* @throws RuntimeException
*/
public static function patchLinuxConfigHeader(string $libc): void
{
switch ($libc) {
case 'musl_wrapper':
// bad checks
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_STRLCPY 1$/m', '');
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_STRLCAT 1$/m', '');
// no break
case 'musl':
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_FUNC_ATTRIBUTE_IFUNC 1$/m', '');
break;
case 'glibc':
// avoid lcrypt dependency
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_CRYPT 1$/m', '');
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_CRYPT_R 1$/m', '');
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_CRYPT_H 1$/m', '');
break;
default:
throw new RuntimeException('not implemented');
}
}
}

View File

@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
// 工作目录
use ZM\Logger\ConsoleLogger;
define('WORKING_DIR', getcwd());
const ROOT_DIR = __DIR__ . '/../..';
// 程序启动时间
define('START_TIME', microtime(true));
// 规定目录
define('BUILD_ROOT_PATH', 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('DOWNLOAD_PATH', is_string($a = getenv('DOWNLOAD_PATH')) ? $a : (WORKING_DIR . '/downloads'));
define('BUILD_LIB_PATH', is_string($a = getenv('INSTALL_LIB_PATH')) ? $a : (BUILD_ROOT_PATH . '/lib'));
const BUILD_DEPS_PATH = BUILD_ROOT_PATH;
define('BUILD_INCLUDE_PATH', is_string($a = getenv('INSTALL_INCLUDE_PATH')) ? $a : (BUILD_ROOT_PATH . '/include'));
define('SEPARATED_PATH', [
'/' . pathinfo(BUILD_LIB_PATH)['basename'], // lib
'/' . pathinfo(BUILD_INCLUDE_PATH)['basename'], // include
BUILD_ROOT_PATH,
]);
// 危险的命令额外用 notice 级别提醒
const DANGER_CMD = [
'rm',
'rmdir',
];
// 替换方案
const REPLACE_FILE_STR = 1;
const REPLACE_FILE_PREG = 2;
const REPLACE_FILE_USER = 3;
// 编译输出类型
const BUILD_MICRO_NONE = 0;
const BUILD_MICRO_ONLY = 1;
const BUILD_MICRO_BOTH = 2;
// 编译状态
const BUILD_STATUS_OK = 0;
const BUILD_STATUS_ALREADY = 1;
const BUILD_STATUS_FAILED = 2;
ConsoleLogger::$date_format = 'H:i:s';

View File

@@ -1,97 +0,0 @@
<?php
declare(strict_types=1);
use Psr\Log\LoggerInterface;
use ZM\Logger\ConsoleLogger;
/**
* 判断传入的数组是否为关联数组
* @param mixed $array
*/
function is_assoc_array($array): bool
{
return is_array($array) && (!empty($array) && array_keys($array) !== range(0, count($array) - 1));
}
/**
* 助手方法,返回一个 Logger 实例
*/
function logger(): LoggerInterface
{
global $ob_logger;
if ($ob_logger === null) {
return new ConsoleLogger();
}
return $ob_logger;
}
/**
* @throws \SPC\exception\RuntimeException
*/
function arch2gnu(string $arch): string
{
$arch = strtolower($arch);
return match ($arch) {
'x86_64', 'x64', 'amd64' => 'x86_64',
'arm64', 'aarch64' => 'aarch64',
default => throw new \SPC\exception\RuntimeException('Not support arch: ' . $arch),
// 'armv7' => 'arm',
};
}
function quote(string $str, string $quote = '"'): string
{
return $quote . $str . $quote;
}
function osfamily2dir(): string
{
return match (PHP_OS_FAMILY) {
/* @phpstan-ignore-next-line */
'Windows', 'WINNT', 'Cygwin' => 'windows',
'Darwin' => 'macos',
'Linux' => 'linux',
default => throw new \SPC\exception\RuntimeException('Not support os: ' . PHP_OS_FAMILY),
};
}
/**
* @throws \SPC\exception\RuntimeException
*/
function f_passthru(string $cmd): ?bool
{
$danger = false;
foreach (DANGER_CMD as $danger_cmd) {
if (str_starts_with($cmd, $danger_cmd . ' ')) {
$danger = true;
break;
}
}
if ($danger) {
logger()->notice('Running dangerous command: ' . $cmd);
} else {
logger()->debug('Running command with direct output: ' . $cmd);
}
$ret = passthru($cmd, $code);
if ($code !== 0) {
throw new \SPC\exception\RuntimeException('Command run failed with code[' . $code . ']: ' . $cmd, $code);
}
return $ret;
}
function f_exec(string $command, &$output, &$result_code)
{
logger()->debug('Running command (no output) : ' . $command);
return exec($command, $output, $result_code);
}
function f_mkdir(string $directory, int $permissions = 0777, bool $recursive = false): bool
{
if (file_exists($directory)) {
logger()->debug("Dir {$directory} already exists, ignored");
return true;
}
logger()->debug('Making new directory ' . ($recursive ? 'recursive' : '') . ': ' . $directory);
return mkdir($directory, $permissions, $recursive);
}

View File

@@ -1,8 +0,0 @@
<?php
/** @noinspection PhpComposerExtensionStubsInspection */
declare(strict_types=1);
bcscale(3);
exit(bcdiv('105', '6.55957') === '16.007' ? 0 : 1);

View File

@@ -1,7 +0,0 @@
<?php
/** @noinspection PhpComposerExtensionStubsInspection */
declare(strict_types=1);
exit(function_exists('cal_info') && is_array(cal_info(0)) ? 0 : 1);

View File

@@ -1,5 +0,0 @@
<?php
declare(strict_types=1);
exit(function_exists('curl_init') ? 0 : 1);

View File

@@ -1,5 +0,0 @@
<?php
declare(strict_types=1);
exit(class_exists('\\DOMDocument') ? 0 : 1);

View File

@@ -1,5 +0,0 @@
<?php
declare(strict_types=1);
exit(function_exists('filter_var') ? 0 : 1);

View File

@@ -1,5 +0,0 @@
<?php
declare(strict_types=1);
exit(class_exists('\\Redis') ? 0 : 1);

View File

@@ -1,5 +0,0 @@
<?php
declare(strict_types=1);
exit(class_exists('\\ZipArchive') ? 0 : 1);

View File

@@ -1,5 +0,0 @@
<?php
declare(strict_types=1);
exit(function_exists('gzcompress') ? 0 : 1);