Compare commits

...

102 Commits
2.0.0 ... 2.1.3

Author SHA1 Message Date
Jerry Ma
96dd5ba87b Add amqp/librabbitmq support for linux, macos, windows (#366)
* add amqp/librabbitmq support for linux, macos, windows

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

* test

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

* fix command option not working on *nix

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

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

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

* add tests

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

* cs fix

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

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

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

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

* add test

* add sysvshm support

* add quote

* add debug

* use mingw target

* fix windows tar

* fix windows tar

* fix windows tar

* fix windows tar

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

* add curl and ssh2 test for windows

* cs fix

* update README [skip ci]

* update README [skip ci]

* update README [skip ci]

* update README [skip ci]

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

* add event test

* add download retry for download command

* use test token

* use test token

* use test token

* use test token

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

* Remove Discord badge

* Fix badges in README-zh
2024-02-12 19:59:51 +08:00
Viktor Szépe
b977543c72 Update README.md 2024-02-12 19:12:43 +08:00
Viktor Szépe
4d87cd11cc Update README-zh.md 2024-02-12 19:12:43 +08:00
Viktor Szépe
957daf0547 Update README.md 2024-02-12 19:12:43 +08:00
Viktor Szépe
3dae904122 Update README-zh.md 2024-02-12 19:12:43 +08:00
Viktor Szépe
e835196972 Add language badges to README 2024-02-12 19:12:43 +08:00
crazywhalecc
52ed0e2cee add glibtoolize check for macos 2024-02-07 01:19:54 +08:00
crazywhalecc
1e898d271d add with-suggested-libs and with-suggested-exts 2024-02-06 16:06:09 +08:00
crazywhalecc
39754cde59 add micro patcher for php84 2024-02-06 15:56:47 +08:00
Peter Kokot
a6f7b938e1 Fix PHP version ID 2024-02-05 20:05:54 +08:00
Peter Kokot
0ad501af9a Patch for PHP >= 8.4
FIBER_ASSEMBLER and FIBER_ASM_ARCH Makefile variables in Windows build
system PHP 8.4 have been removed in favor of the PHP_ASSEMBLER and
FIBER_ASM_ABI.
2024-02-05 20:05:54 +08:00
crazywhalecc
73035067e3 fix swoole-hook 2024-02-04 18:00:35 +08:00
crazywhalecc
421b3830b6 fix mbregex test for micro 2024-02-04 18:00:35 +08:00
crazywhalecc
59dcb905fe add test 2024-02-04 18:00:35 +08:00
crazywhalecc
d222190fc7 fix curl bug #327 2024-02-04 18:00:35 +08:00
crazywhalecc
06e9864d19 fix swoole hook check 2024-02-04 18:00:35 +08:00
crazywhalecc
0b1a321615 change with-micro-ext-test to without 2024-02-04 18:00:35 +08:00
crazywhalecc
9ee7d7769a add arm macos test 2024-02-04 11:22:53 +08:00
crazywhalecc
2440a65d8e fix windows test 2024-02-04 11:22:53 +08:00
crazywhalecc
839931d65f add static-php-cli.version ini for php-src 2024-02-04 11:22:53 +08:00
Jerry Ma
2591b48abe Update README-zh.md 2024-02-02 09:45:55 +08:00
Jerry Ma
2549597871 Update README.md 2024-02-02 09:44:37 +08:00
Jerry Ma
2064172bed Update build-macos-aarch64.yml 2024-01-31 11:58:56 +08:00
Jerry Ma
dc19d0c61d Create build-macos-aarch64.yml 2024-01-31 11:26:40 +08:00
crazywhalecc
05c6dc6dab add more tests 2024-01-29 09:55:20 +08:00
Jerry Ma
4b653bc293 Update README-zh.md 2024-01-11 10:08:32 +08:00
Jerry Ma
1c3476b8e9 Update README.md 2024-01-11 10:08:02 +08:00
Jerry Ma
61ca501c2a Update README.md 2024-01-11 09:51:48 +08:00
crazywhalecc
d916feec62 test bulk 2024-01-11 09:48:28 +08:00
crazywhalecc
0dcecf6ca4 fix windows mbstring 2024-01-11 09:48:28 +08:00
crazywhalecc
a04deb458f fix test build 2024-01-11 09:48:28 +08:00
crazywhalecc
34f810571e fix phpunit test 2024-01-11 09:48:28 +08:00
crazywhalecc
5ab4d140d5 fix phpstan 2024-01-11 09:48:28 +08:00
crazywhalecc
104778d17a add windows support 2024-01-11 09:48:28 +08:00
crazywhalecc
5f8641f417 update README 2024-01-10 11:13:47 +08:00
crazywhalecc
17b69ec1e9 update README 2024-01-10 11:11:45 +08:00
Jerry Ma
8de942c274 Adjust function to protected (#315)
* add `--with-added-patch` command

* add BuilderProvider::getBuilder() function

* cs fix

* add builder() and patch_point() global functions

* bump version to 2.0.1

* adjust function prefix

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

View File

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

View File

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

View File

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

3
.gitignore vendored
View File

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

View File

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

205
README.md
View File

@@ -1,28 +1,41 @@
# static-php-cli
Build single static PHP binary, with PHP project together, with popular extensions included.
[![Chinese readme](https://img.shields.io/badge/README-%E4%B8%AD%E6%96%87%20%F0%9F%87%A8%F0%9F%87%B3-moccasin?style=flat-square)](README-zh.md)
[![English readme](https://img.shields.io/badge/README-English%20%F0%9F%87%AC%F0%9F%87%A7-moccasin?style=flat-square)](README.md)
[![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
[![Extensions](https://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)](https://static-php.dev/en/guide/extensions.html)
🌐 **[中文](README-zh.md)** | **[English](README.md)**
**static-php-cli** is a powerful tool designed for building static, standalone PHP runtime
with popular extensions.
> 2.0 Release is coming soon, windows support will be added in v2.1.
Static PHP built by **static-php-cli** supports `cli`, `fpm`, `embed` and `micro` SAPI.
The project name is static-php-cli, but it actually supports cli, fpm, micro and embed SAPI 😎
**static-php-cli** also has the ability to package PHP projects
along with the PHP interpreter into one single executable file.
Compile a purely static php-cli binary file with various extensions to make PHP applications more portable! (cli SAPI)
## Features
<img width="600" alt="2023-05-02 15 53 13" src="https://user-images.githubusercontent.com/20330940/235610282-23e58d68-bd35-4092-8465-171cff2d5ba8.png">
static-php-cli (you can call it `spc`) has a lot of features:
You can also use the micro binary file to combine php binary and php source code into one for distribution! (micro SAPI)
- :handbag: Build single-file php executable, without any dependencies
- :hamburger: Build **[phpmicro](https://github.com/dixyes/phpmicro)** self-extracted executable (glue php binary and php source code into one file)
- :pill: Automatic build environment checker (Doctor module)
- :zap: `Linux`, `macOS`, `FreeBSD`, `Windows` support
- :wrench: Configurable source code patches
- :books: Build dependency management
- 📦 Provide `spc` own standalone executable (built by spc and [box](https://github.com/box-project/box))
- :fire: Support many popular [extensions](https://static-php.dev/en/guide/extensions.html)
- :floppy_disk: UPX integration (significantly reduces binary size)
<img width="600" alt="2023-05-02 15 52 33" src="https://user-images.githubusercontent.com/20330940/235610318-2ef4e3f1-278b-4ca4-99f4-b38120efc395.png">
**Single-file standalone php-cli:**
> This SAPI feature is from the [Fork](https://github.com/static-php/phpmicro) of [dixyes/phpmicro](https://github.com/dixyes/phpmicro).
<img width="700" alt="out1" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/01a2e60f-13b0-4242-a645-f7afa4936396">
[![Version](https://img.shields.io/badge/Version-2.0.0-green.svg?style=flat-square)]()
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)]()
[![](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![](https://img.shields.io/badge/Extension%20Counter-75+-yellow.svg?style=flat-square)]([https://static-php.dev/](https://static-php.dev/en/guide/extensions.html))
[![](https://img.shields.io/github/search/crazywhalecc/static-php-cli/TODO?label=TODO%20Counter&style=flat-square)]()
**Combine PHP code with PHP interpreter using phpmicro:**
<img width="700" alt="out2" src="https://github.com/crazywhalecc/static-php-cli/assets/20330940/46b7128d-fb72-4169-957e-48564c3ff3e2">
## Documentation
@@ -31,7 +44,7 @@ see <https://static-php.dev> .
## Direct Download
If you don't want to compile yourself, you can download example pre-compiled artifact from [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-common.yml), or from self-hosted server.
If you don't want to build or want to test first, you can download example pre-compiled artifact from [Actions](https://github.com/static-php/static-php-cli-hosted/actions/workflows/build-php-bulk.yml), or from self-hosted server.
Below are several precompiled static-php binaries with different extension combinations,
which can be downloaded directly according to your needs.
@@ -40,26 +53,47 @@ which can be downloaded directly according to your needs.
- [Extension-Combination - bulk](https://dl.static-php.dev/static-php-cli/bulk/): `bulk` contains [50+](https://dl.static-php.dev/static-php-cli/bulk/README.txt) extensions and is about 70MB in size.
- [Extension-Combination - minimal](https://dl.static-php.dev/static-php-cli/minimal/): `minimal` contains [5](https://dl.static-php.dev/static-php-cli/minimal/README.txt) extensions and is about 6MB in size.
## Use static-php-cli to build PHP
For Windows systems, there are currently fewer extensions supported,
so only `cli` and `micro` that run the minimum extension combination of SPC itself are provided: [Extension-Combination - spc-min](https://dl.static-php.dev/static-php-cli/windows/spc-min/).
## Build
### Compilation Requirements
Yes, this project is written in PHP, pretty funny.
But static-php-cli runtime only requires an environment above PHP 8.1 and `mbstring`, `pcntl` extension.
You can say I made a PHP builder written in PHP, pretty funny.
But static-php-cli runtime only requires an environment above PHP 8.1 and extensions mentioned below.
Here is the architecture support status, where :octocat: represents support for GitHub Action builds,
- PHP >= 8.1 (This is the version required by spc itself, not the build version)
- Extension: `mbstring,tokenizer,phar`
- Supported OS with `curl` and `git` installed
Here is the supported OS and arch, where :octocat: represents support for GitHub Action builds,
:computer: represents support for local manual builds, and blank represents not currently supported.
| | x86_64 | aarch64 |
|---------|----------------------|----------------------|
| macOS | :octocat: :computer: | :computer: |
| macOS | :octocat: :computer: | :octocat: :computer: |
| Linux | :octocat: :computer: | :octocat: :computer: |
| Windows | | |
| Windows | :computer: | |
| FreeBSD | :computer: | :computer: |
> macOS-arm64 is not supported for GitHub Actions, if you are going to build on arm, you can build it manually on your own machine.
Currently supported PHP versions for compilation:
Currently supported PHP versions for compilation are: `7.3`, `7.4`, `8.0`, `8.1`, `8.2`, `8.3`.
> :warning: supported but not maintained
>
> :heavy_check_mark: supported
>
> :x: not supported
| PHP Version | Status | Comment |
|-------------|--------------------|---------------------------------------------------|
| 7.2 | :x: | |
| 7.3 | :warning: | phpmicro and some extensions not supported on 7.x |
| 7.4 | :warning: | phpmicro and some extensions not supported on 7.x |
| 8.0 | :heavy_check_mark: | PHP official has stopped maintenance of 8.0 |
| 8.1 | :heavy_check_mark: | |
| 8.2 | :heavy_check_mark: | |
| 8.3 | :heavy_check_mark: | |
### Supported Extensions
@@ -72,7 +106,7 @@ Please first select the extension you want to compile based on the extension lis
Here is the current planned roadmap for extension support: [#152](https://github.com/crazywhalecc/static-php-cli/issues/152) .
### GitHub Actions Build
### Build Online (using GitHub Actions)
Use GitHub Action to easily build a statically compiled PHP,
and at the same time define the extensions to be compiled by yourself.
@@ -84,80 +118,85 @@ and at the same time define the extensions to be compiled by yourself.
If you enable `debug`, all logs will be output at build time, including compiled logs, for troubleshooting.
- When using ubuntu-latest, it will build linux-x86_64 binary.
- When using macos-latest, it will build macOS-x86_64 binary.
### Build Locally (using SPC binary)
### Manual build (using SPC binary)
This project provides a binary file of static-php-cli.
You can directly download the binary file of the corresponding platform and then use it to build static PHP.
This project provides a binary file of static-php-cli: `spc`.
You can use `spc` binary instead of installing any runtime like golang app.
Currently, the platforms supported by `spc` binary are Linux and macOS.
Here's how to download from GitHub Actions:
1. Enter [GitHub Actions](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml) or [self-hosted nightly builds](https://dl.static-php.dev/static-php-cli/spc-bin/nightly/).
2. If you download from GHA, select the latest build task, select `Artifacts`, and download the binary file of the corresponding platform.
3. If you download from GHA, unzip the `.zip` file. After decompressing, add execution permissions to it: `chmod +x ./spc`.
4. If you download from self-hosted server, download `spc-$os-$arch` file and just use it (don't forget `chmod +x`).
> SPC single-file binary is built by phpmicro and box, and it doesn't need to install PHP. Just treat `spc` as a standalone executable.
### Manual build (using source code)
Clone repo first:
Download from self-hosted nightly builds using commands below:
```bash
# Download from self-hosted nightly builds (sync with main branch)
# For Linux x86_64
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
# For Linux aarch64
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
# macOS x86_64 (Intel)
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
# macOS aarch64 (Apple)
curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
# Windows (x86_64, win10 build 17063 or later)
curl.exe -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
# Add execute perm (Linux and macOS only)
chmod +x ./spc
# Run (Linux and macOS)
./spc --version
# Run (Windows powershell)
.\spc.exe --version
```
Self-hosted `spc` is built by GitHub Actions, you can also download from Actions artifacts [here](https://github.com/crazywhalecc/static-php-cli/actions/workflows/release-build.yml).
### Build Locally (using git source)
```bash
# just clone me!
git clone https://github.com/crazywhalecc/static-php-cli.git
```
If you have not installed php on your system, you can use package management to install PHP (such as brew, apt, yum, apk etc.).
And you can also download single-file php binary and composer using command `bin/setup-runtime`.
The PHP runtime for static-php-cli itself will be downloaded at `bin/php`, and composer is at `bin/composer`.
If you have not installed php on your system, we recommend that you use the built-in setup-runtime to install PHP and Composer automatically.
```bash
cd static-php-cli
chmod +x bin/setup-runtime
# It will download php-cli from self-hosted server and composer from getcomposer.org
./bin/setup-runtime
# Use this php runtime to run static-php-cli compiler
./bin/php bin/spc
# Use composer
./bin/php bin/composer
# Initialize this project
cd static-php-cli
composer update
# it will download static php (from self-hosted server) and composer (from getcomposer)
bin/setup-runtime
# initialize composer deps
bin/composer install
# chmod
chmod +x bin/spc
bin/spc --version
```
### Use static-php-cli
### Start Building PHP
Basic usage for building php and micro with some extensions:
Basic usage for building php with some extensions:
> If you are using the packaged `spc` binary, you need to replace `bin/spc` with `./spc` in the following commands.
> If you are using the packaged standalone `spc` binary, you need to replace `bin/spc` with `./spc` or `.\spc.exe` in the following commands.
```bash
# Check system tool dependencies, fix them if possible
./bin/spc doctor
# Check system tool dependencies, auto-fix them if possible
./bin/spc doctor --auto-fix
# fetch all libraries
./bin/spc download --all
# only fetch necessary sources by needed extensions
./bin/spc download --for-extensions=openssl,pcntl,mbstring,pdo_sqlite
# only fetch necessary sources by needed extensions (recommended)
./bin/spc download --for-extensions="openssl,pcntl,mbstring,pdo_sqlite"
# download different PHP version (--with-php=x.y, recommend 7.3 ~ 8.3)
./bin/spc download --for-extensions="openssl,curl,mbstring" --with-php=8.1
# with bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl extension, build both CLI and phpmicro SAPI
./bin/spc build bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl --build-cli --build-micro
./bin/spc build "bcmath,openssl,tokenizer,sqlite3,pdo_sqlite,ftp,curl" --build-cli --build-micro
# build thread-safe (ZTS) version (--enable-zts)
./bin/spc build "curl,phar" --enable-zts --build-cli
# build, pack executable with UPX (--with-upx-pack) (reduce binary size for 30~50%)
./bin/spc build "curl,phar" --enable-zts --build-cli --with-upx-pack
```
You can also use the parameter `--with-php=x.y` to specify the downloaded PHP version, currently supports 7.4 ~ 8.3:
```bash
# Using PHP >= 8.0 is recommended, because PHP7 cannot use phpmicro
./bin/spc fetch --with-php=8.2 --all
```
Now we support `cli`, `micro`, `fpm`, you can use one or more of the following parameters to specify the compiled SAPI:
Now we support `cli`, `micro`, `fpm` and `embed` SAPI. You can use one or more of the following parameters to specify the compiled SAPI:
- `--build-cli`: build static cli executable
- `--build-micro`: build static phpmicro self-extracted executable
@@ -168,18 +207,10 @@ Now we support `cli`, `micro`, `fpm`, you can use one or more of the following p
If anything goes wrong, use `--debug` option to display full terminal output:
```bash
./bin/spc build openssl,pcntl,mbstring --debug --build-all
./bin/spc fetch --all --debug
./bin/spc build "openssl,pcntl,mbstring" --debug --build-all
./bin/spc download --all --debug
```
In addition, we build NTS (non-thread-safe) by default. If you are going to build ZTS version, just add `--enable-zts` option.
```bash
./bin/spc build openssl,pcntl --build-all --enable-zts
```
Adding option `--no-strip` can produce binaries with debug symbols, in order to debug (using gdb). Disabling strip will increase the size of static binary.
## Different SAPI Usage
### Use cli
@@ -269,7 +300,7 @@ Now there is a [static-php](https://github.com/static-php) organization, which i
## Sponsor this project
You can sponsor my project on [this page](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md).
You can sponsor my project on [this page](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md). A portion of your donation will be used to maintain the **static-php.dev** server.
## Open-Source License
@@ -282,10 +313,12 @@ These are similar projects:
- [dixyes/lwmbs](https://github.com/dixyes/lwmbs)
- [swoole/swoole-cli](https://github.com/swoole/swoole-cli)
The project uses some code from [dixyes/lwmbs](https://github.com/dixyes/lwmbs), such as windows static build target and libiconv support.
lwmbs is licensed under the [Mulan PSL 2](http://license.coscl.org.cn/MulanPSL2).
Due to the special nature of this project,
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 `bin/spc dump-license` command to export the open source licenses used in the project after compilation,
and comply with the corresponding project's LICENSE.

View File

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

View File

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

View File

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

View File

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

View File

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

940
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,15 @@
{
"amqp": {
"type": "external",
"arg-type": "custom",
"source": "amqp",
"lib-depends": [
"librabbitmq"
],
"ext-depends-windows": [
"openssl"
]
},
"apcu": {
"type": "external",
"source": "apcu"
@@ -24,6 +35,10 @@
"arg-type": "with",
"lib-depends": [
"curl"
],
"ext-depends-windows": [
"zlib",
"openssl"
]
},
"dba": {
@@ -59,8 +74,11 @@
"ffi": {
"arg-type": "custom",
"type": "builtin",
"lib-depends": [
"lib-depends-unix": [
"libffi"
],
"lib-depends-windows": [
"libffi-win"
]
},
"fileinfo": {
@@ -95,7 +113,7 @@
},
"gettext": {
"type": "builtin",
"arg-type": "with",
"arg-type": "with-prefix",
"lib-depends": [
"gettext"
]
@@ -106,7 +124,8 @@
"source": "ext-glfw",
"lib-depends": [
"glfw"
]
],
"lib-depends-windows": []
},
"gmp": {
"type": "builtin",
@@ -118,8 +137,12 @@
"iconv": {
"type": "builtin",
"arg-type": "with-prefix",
"lib-depends": [
"arg-type-windows": "with",
"lib-depends-unix": [
"libiconv"
],
"lib-depends-windows": [
"libiconv-win"
]
},
"igbinary": {
@@ -238,6 +261,7 @@
"openssl": {
"type": "builtin",
"arg-type": "custom",
"arg-type-windows": "with",
"lib-depends": [
"openssl",
"zlib"
@@ -311,13 +335,6 @@
"type": "external",
"source": "protobuf"
},
"pspell": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"aspell"
]
},
"rar": {
"type": "external",
"source": "rar",
@@ -352,9 +369,11 @@
"simplexml": {
"type": "builtin",
"arg-type": "custom",
"arg-type-windows": "with",
"lib-depends": [
"libxml2"
],
"ext-depends-windows": [
"xml"
]
},
"snappy": {
@@ -369,18 +388,14 @@
"apcu"
]
},
"snmp": {
"type": "builtin",
"arg-type": "with",
"lib-depends": [
"net-snmp"
]
},
"soap": {
"type": "builtin",
"arg-type": "custom",
"lib-depends": [
"libxml2"
],
"ext-depends-windows": [
"xml"
]
},
"sockets": {
@@ -396,6 +411,7 @@
"sqlite3": {
"type": "builtin",
"arg-type": "with-prefix",
"arg-type-windows": "with",
"lib-depends": [
"sqlite"
]
@@ -415,8 +431,13 @@
"type": "external",
"source": "ext-ssh2",
"arg-type": "with-prefix",
"arg-type-windows": "with",
"lib-depends": [
"libssh2"
],
"ext-depends-windows": [
"openssl",
"zlib"
]
},
"swoole": {
@@ -491,8 +512,7 @@
"unix-only": true
},
"sysvshm": {
"type": "builtin",
"unix-only": true
"type": "builtin"
},
"tidy": {
"type": "builtin",
@@ -504,6 +524,14 @@
"tokenizer": {
"type": "builtin"
},
"uuid": {
"type": "external",
"source": "ext-uuid",
"arg-type": "with-prefix",
"lib-depends": [
"libuuid"
]
},
"uv": {
"type": "external",
"source": "ext-uv",
@@ -537,6 +565,9 @@
"arg-type-windows": "with",
"lib-depends": [
"libxml2"
],
"ext-depends-windows": [
"iconv"
]
},
"xmlreader": {
@@ -544,6 +575,10 @@
"arg-type": "custom",
"lib-depends": [
"libxml2"
],
"ext-depends-windows": [
"xml",
"dom"
]
},
"xmlwriter": {
@@ -551,6 +586,9 @@
"arg-type": "custom",
"lib-depends": [
"libxml2"
],
"ext-depends-windows": [
"xml"
]
},
"xsl": {

View File

@@ -45,21 +45,21 @@
"openssl",
"zlib"
],
"lib-suggests": [
"lib-depends-windows": [
"openssl",
"zlib",
"libssh2",
"nghttp2"
],
"lib-suggests-unix": [
"libssh2",
"brotli",
"nghttp2",
"zstd"
],
"lib-suggests-windows": [
"zlib",
"libssh2",
"brotli",
"nghttp2",
"zstd",
"openssl",
"idn2",
"psl"
"zstd"
],
"frameworks": [
"CoreFoundation",
@@ -85,6 +85,22 @@
"brotli"
]
},
"gettext": {
"source": "gettext",
"static-libs-unix": [
"libintl.a"
],
"lib-depends": [
"libiconv"
],
"lib-suggests": [
"ncurses",
"libxml2"
],
"frameworks": [
"CoreFoundation"
]
},
"glfw": {
"source": "ext-glfw",
"static-libs-unix": [
@@ -131,7 +147,8 @@
"libpng",
"libjpeg",
"libwebp",
"freetype"
"freetype",
"libtiff"
],
"lib-suggests": [
"zstd",
@@ -217,6 +234,17 @@
"ffitarget.h"
]
},
"libffi-win": {
"source": "libffi-win",
"static-libs-windows": [
"libffi.lib"
],
"headers-windows": [
"ffi.h",
"ffitarget.h",
"fficonfig.h"
]
},
"libiconv": {
"source": "libiconv",
"static-libs-unix": [
@@ -229,6 +257,13 @@
"localcharset.h"
]
},
"libiconv-win": {
"source": "libiconv-win",
"static-libs-windows": [
"libiconv.lib",
"libiconv_a.lib"
]
},
"libjpeg": {
"source": "libjpeg",
"static-libs-unix": [
@@ -270,6 +305,18 @@
"zlib"
]
},
"librabbitmq": {
"source": "librabbitmq",
"static-libs-unix": [
"librabbitmq.a"
],
"static-libs-windows": [
"rabbitmq.4.lib"
],
"lib-depends": [
"openssl"
]
},
"libsodium": {
"source": "libsodium",
"static-libs-unix": [
@@ -296,6 +343,18 @@
"zlib"
]
},
"libtiff": {
"source": "libtiff",
"static-libs-unix": [
"libtiff.a"
]
},
"libuuid": {
"source": "libuuid",
"static-libs-unix": [
"libuuid.a"
]
},
"libuv": {
"source": "libuv",
"static-libs-unix": [
@@ -318,21 +377,25 @@
"libxml2.a"
],
"static-libs-windows": [
[
"libxml2s.lib",
"libxml2_a.lib"
]
"libxml2s.lib",
"libxml2_a.lib"
],
"headers": [
"libxml2"
],
"lib-depends": [
"lib-depends-unix": [
"libiconv"
],
"lib-suggests": [
"lib-suggests-unix": [
"xz",
"icu",
"zlib"
],
"lib-depends-windows": [
"libiconv-win"
],
"lib-suggests-windows": [
"zlib"
]
},
"libxslt": {
@@ -413,10 +476,8 @@
"libonig.a"
],
"static-libs-windows": [
[
"onig.lib",
"onig_a.lib"
]
"onig.lib",
"onig_a.lib"
],
"headers": [
"oniggnu.h",
@@ -493,7 +554,10 @@
"static-libs-unix": [
"libsqlite3.a"
],
"headers-unix": [
"static-libs-windows": [
"libsqlite3_a.lib"
],
"headers": [
"sqlite3.h",
"sqlite3ext.h"
]

46
config/pkg.json Normal file
View File

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

View File

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

View File

@@ -14,4 +14,5 @@ parameters:
excludePaths:
analyseAndScan:
- ./src/globals/tests/swoole.php
- ./src/globals/tests/swoole.phpt
- ./src/globals/tests/swoole.phpt
- ./src/globals/test-extensions.php

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,11 @@ use SPC\util\CustomExt;
#[CustomExt('mbregex')]
class mbregex extends Extension
{
public function getDistName(): string
{
return 'mbstring';
}
public function getConfigureArg(): string
{
return '';
@@ -19,11 +24,23 @@ class mbregex extends Extension
/**
* mbregex is not an extension, we need to overwrite the default check.
*/
public function runCliCheck(): void
public function runCliCheckUnix(): void
{
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "mbstring" | grep regex', false);
if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli mbstring extension does not contain regex !');
}
}
public function runCliCheckWindows(): void
{
[$ret, $out] = cmd()->execWithResult(BUILD_ROOT_PATH . '/bin/php --ri "mbstring"', false);
if ($ret !== 0) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli does not contain mbstring !');
}
$out = implode("\n", $out);
if (!str_contains($out, 'regex')) {
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: compiled php-cli mbstring extension does not contain regex !');
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,20 +4,16 @@ declare(strict_types=1);
namespace SPC\builder\linux;
use SPC\builder\BuilderBase;
use SPC\builder\linux\library\LinuxLibraryBase;
use SPC\builder\traits\UnixBuilderTrait;
use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
use SPC\store\SourcePatcher;
class LinuxBuilder extends BuilderBase
class LinuxBuilder extends UnixBuilderBase
{
/** Unix compatible builder methods */
use UnixBuilderTrait;
/** @var array Tune cflags */
public array $tune_c_flags;
@@ -147,10 +143,13 @@ class LinuxBuilder extends BuilderBase
'LDFLAGS' => '-L' . BUILD_LIB_PATH,
'LIBS' => '-ldl -lpthread',
]);
$this->emitPatchPoint('before-php-buildconf');
SourcePatcher::patchBeforeBuildconf($this);
shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force');
$this->emitPatchPoint('before-php-configure');
SourcePatcher::patchBeforeConfigure($this);
$phpVersionID = $this->getPHPVersionID();
@@ -170,6 +169,27 @@ class LinuxBuilder extends BuilderBase
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
// upx pack and strip for micro
if ($this->getOption('with-upx-pack', false)) {
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
'/POST_MICRO_BUILD_COMMANDS=.*/',
'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH) && ' . $this->getOption('upx-exec') . ' --best \$(SAPI_MICRO_PATH)',
);
} elseif (!$this->getOption('no-strip', false)) {
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
'/POST_MICRO_BUILD_COMMANDS=.*/',
'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH)',
);
} else {
FileSystem::replaceFileRegex(
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
'/POST_MICRO_BUILD_COMMANDS=.*/',
'POST_MICRO_BUILD_COMMANDS=true',
);
}
shell()->cd(SOURCE_PATH . '/php-src')
->exec(
"{$this->getOption('ld_library_path')} " .
@@ -193,6 +213,7 @@ class LinuxBuilder extends BuilderBase
' ' . $envs_build_php . ' '
);
$this->emitPatchPoint('before-php-make');
SourcePatcher::patchBeforeMake($this);
$this->cleanMake();
@@ -218,6 +239,7 @@ class LinuxBuilder extends BuilderBase
}
if (php_uname('m') === $this->getOption('arch')) {
$this->emitPatchPoint('before-sanity-check');
$this->sanityCheck($build_target);
}
}
@@ -228,14 +250,18 @@ class LinuxBuilder extends BuilderBase
* @throws RuntimeException
* @throws FileSystemException
*/
public function buildCli(): void
protected function buildCli(): void
{
$vars = SystemUtil::makeEnvVarString($this->getBuildVars());
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec("make -j{$this->concurrency} {$vars} cli");
if (!$this->getOption('no-strip', false)) {
if ($this->getOption('with-upx-pack')) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')
->exec('strip --strip-all php')
->exec($this->getOption('upx-exec') . ' --best php');
} elseif (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-all php');
}
@@ -249,7 +275,7 @@ class LinuxBuilder extends BuilderBase
* @throws RuntimeException
* @throws WrongUsageException
*/
public function buildMicro(): void
protected function buildMicro(): void
{
if ($this->getPHPVersionID() < 80000) {
throw new WrongUsageException('phpmicro only support PHP >= 8.0!');
@@ -266,10 +292,6 @@ class LinuxBuilder extends BuilderBase
->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec("make -j{$this->concurrency} {$vars} micro");
if (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/micro')->exec('strip --strip-all micro.sfx');
}
$this->deployBinary(BUILD_TARGET_MICRO);
if ($this->phar_patched) {
@@ -283,17 +305,20 @@ class LinuxBuilder extends BuilderBase
* @throws FileSystemException
* @throws RuntimeException
*/
public function buildFpm(): void
protected function buildFpm(): void
{
$vars = SystemUtil::makeEnvVarString($this->getBuildVars());
shell()->cd(SOURCE_PATH . '/php-src')
->exec('sed -i "s|//lib|/lib|g" Makefile')
->exec("make -j{$this->concurrency} {$vars} fpm");
if (!$this->getOption('no-strip', false)) {
if ($this->getOption('with-upx-pack')) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')
->exec('strip --strip-all php-fpm')
->exec($this->getOption('upx-exec') . ' --best php-fpm');
} elseif (!$this->getOption('no-strip', false)) {
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-all php-fpm');
}
$this->deployBinary(BUILD_TARGET_FPM);
}
@@ -302,7 +327,7 @@ class LinuxBuilder extends BuilderBase
*
* @throws RuntimeException
*/
public function buildEmbed(): void
protected function buildEmbed(): void
{
$vars = SystemUtil::makeEnvVarString($this->getBuildVars());

View File

@@ -110,11 +110,7 @@ class SystemUtil
public static function getTuneCFlags(string $arch): array
{
return match ($arch) {
'x86_64' => [
'-march=corei7',
'-mtune=core-avx2',
],
'arm64', 'aarch64' => [],
'x86_64', 'arm64', 'aarch64' => [],
default => throw new RuntimeException('unsupported arch: ' . $arch),
};
}
@@ -209,4 +205,21 @@ class SystemUtil
}
return $ret;
}
/**
* Get fully-supported linux distros.
*
* @return string[] List of supported Linux distro name for doctor
*/
public static function getSupportedDistros(): array
{
return [
// debian-like
'debian', 'ubuntu', 'Deepin',
// rhel-like
'redhat',
// alpine
'alpine',
];
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ class libxml2 extends LinuxLibraryBase
*/
public function build(): void
{
$enable_zlib = $this->builder->getLib('zlib') ? 'ON' : 'OFF';
$enable_zlib = $this->builder->getLib('zlib') ? ('ON -DZLIB_LIBRARY=' . BUILD_LIB_PATH . '/libz.a -DZLIB_INCLUDE_DIR=' . BUILD_INCLUDE_PATH) : 'OFF';
$enable_icu = $this->builder->getLib('icu') ? 'ON' : 'OFF';
$enable_xz = $this->builder->getLib('xz') ? 'ON' : 'OFF';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ class libxml2 extends MacOSLibraryBase
*/
protected function build(): void
{
$enable_zlib = $this->builder->getLib('zlib') ? 'ON' : 'OFF';
$enable_zlib = $this->builder->getLib('zlib') ? ('ON -DZLIB_LIBRARY=' . BUILD_LIB_PATH . '/libz.a -DZLIB_INCLUDE_DIR=' . BUILD_INCLUDE_PATH) : 'OFF';
$enable_icu = $this->builder->getLib('icu') ? 'ON' : 'OFF';
$enable_xz = $this->builder->getLib('xz') ? 'ON' : 'OFF';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace SPC\builder\windows\library;
use SPC\builder\BuilderBase;
use SPC\builder\LibraryBase;
use SPC\builder\windows\WindowsBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\FileSystem;
abstract class WindowsLibraryBase extends LibraryBase
{
public function __construct(protected WindowsBuilder $builder)
{
parent::__construct();
}
public function getBuilder(): BuilderBase
{
return $this->builder;
}
/**
* @throws RuntimeException
* @throws FileSystemException
* @throws WrongUsageException
*/
public function getStaticLibFiles(string $style = 'autoconf', bool $recursive = true): string
{
$libs = [$this];
if ($recursive) {
array_unshift($libs, ...array_values($this->getDependencies(recursive: true)));
}
$sep = match ($style) {
'autoconf' => ' ',
'cmake' => ';',
default => throw new RuntimeException('style only support autoconf and cmake'),
};
$ret = [];
foreach ($libs as $lib) {
$libFiles = [];
foreach ($lib->getStaticLibs() as $name) {
$name = str_replace(' ', '\ ', FileSystem::convertPath(BUILD_LIB_PATH . "/{$name}"));
$name = str_replace('"', '\"', $name);
$libFiles[] = $name;
}
array_unshift($ret, implode($sep, $libFiles));
}
return implode($sep, $ret);
}
/**
* Create a nmake wrapper file.
*
* @param string $content nmake wrapper content
* @param string $default_filename default nmake wrapper filename
* @throws FileSystemException
*/
public function makeNmakeWrapper(string $content, string $default_filename = ''): string
{
if ($default_filename === '') {
$default_filename = $this->source_dir . '\nmake_wrapper.bat';
}
FileSystem::writeFile($default_filename, $content);
return 'nmake_wrapper.bat';
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,11 +56,6 @@ abstract class BaseCommand extends Command
// 如果 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 (!$this->no_motd) {
echo " _ _ _ _
@@ -83,6 +78,14 @@ abstract class BaseCommand extends Command
$this->input = $input;
$this->output = $output;
global $ob_logger;
if ($input->getOption('debug')) {
$ob_logger = new ConsoleLogger(LogLevel::DEBUG, decorated: !$input->getOption('no-ansi'));
define('DEBUG_MODE', true);
} else {
$ob_logger = new ConsoleLogger(decorated: !$input->getOption('no-ansi'));
}
// windows fallback
Prompt::fallbackWhen(PHP_OS_FAMILY === 'Windows');
ConfirmPrompt::fallbackUsing(function (ConfirmPrompt $prompt) use ($input, $output) {

View File

@@ -23,18 +23,22 @@ class BuildCliCommand extends BuildCommand
{
$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');
$this->addOption('build-cli', null, null, 'build cli');
$this->addOption('build-fpm', null, null, 'build fpm');
$this->addOption('build-embed', null, null, 'build embed');
$this->addOption('build-all', null, null, 'build cli, micro, fpm, embed');
$this->addOption('build-micro', null, null, 'Build micro SAPI');
$this->addOption('build-cli', null, null, 'Build cli SAPI');
$this->addOption('build-fpm', null, null, 'Build fpm SAPI');
$this->addOption('build-embed', null, null, 'Build embed SAPI');
$this->addOption('build-all', null, null, 'Build all SAPI');
$this->addOption('no-strip', null, null, 'build without strip, in order to debug and load external extensions');
$this->addOption('enable-zts', null, null, 'enable ZTS support');
$this->addOption('disable-opcache-jit', null, null, 'disable opcache jit');
$this->addOption('with-hardcoded-ini', 'I', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Patch PHP source code, inject hardcoded INI');
$this->addOption('with-micro-fake-cli', null, null, 'Enable phpmicro fake cli');
$this->addOption('with-micro-fake-cli', null, null, 'Let phpmicro\'s PHP_SAPI use "cli" instead of "micro"');
$this->addOption('with-suggested-libs', 'L', null, 'Build with suggested libs for selected exts and libs');
$this->addOption('with-suggested-exts', 'E', null, 'Build with suggested extensions for selected exts');
$this->addOption('with-added-patch', 'P', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Inject patch script outside');
$this->addOption('without-micro-ext-test', null, null, 'Disable phpmicro with extension test code');
$this->addOption('with-upx-pack', null, null, 'Compress / pack binary using UPX tool (linux/windows only)');
$this->addOption('with-micro-logo', null, InputOption::VALUE_REQUIRED, 'Use custom .ico for micro.sfx (windows only)');
}
public function handle(): int
@@ -56,11 +60,45 @@ class BuildCliCommand extends BuildCommand
$this->output->writeln("<comment>\t--build-all\tBuild all SAPI: cli, micro, fpm, embed</comment>");
return static::FAILURE;
}
if ($rule === BUILD_TARGET_ALL) {
logger()->warning('--build-all option makes `--no-strip` always true, be aware!');
}
if (($rule & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO && $this->getOption('with-micro-logo')) {
$logo = $this->getOption('with-micro-logo');
if (!file_exists($logo)) {
logger()->error('Logo file ' . $logo . ' not exist !');
return static::FAILURE;
}
}
// Check upx
$suffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : '';
if ($this->getOption('with-upx-pack')) {
// only available for linux for now
if (!in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
logger()->error('UPX is only available on Linux and Windows!');
return static::FAILURE;
}
// need to install this manually
if (!file_exists(PKG_ROOT_PATH . '/bin/upx' . $suffix)) {
global $argv;
logger()->error('upx does not exist, please install it first:');
logger()->error('');
logger()->error("\t" . $argv[0] . ' install-pkg upx');
logger()->error('');
return static::FAILURE;
}
// exclusive with no-strip
if ($this->getOption('no-strip')) {
logger()->warning('--with-upx-pack conflicts with --no-strip, --no-strip won\'t work!');
}
}
try {
// create builder
$builder = BuilderProvider::makeBuilderByInput($this->input);
// calculate dependencies
[$extensions, $libraries, $not_included] = DependencyUtil::getExtLibsByDeps($extensions, $libraries);
$include_suggest_ext = $this->getOption('with-suggested-exts');
$include_suggest_lib = $this->getOption('with-suggested-libs');
[$extensions, $libraries, $not_included] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
// print info
$indent_texts = [
@@ -74,12 +112,27 @@ class BuildCliCommand extends BuildCommand
if (!empty($this->input->getOption('with-hardcoded-ini'))) {
$indent_texts['Hardcoded INI'] = $this->input->getOption('with-hardcoded-ini');
}
$this->printFormatInfo($indent_texts);
if ($this->input->getOption('disable-opcache-jit')) {
$indent_texts['Opcache JIT'] = 'disabled';
}
if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
$indent_texts['UPX Pack'] = 'enabled';
$builder->setOption('upx-exec', FileSystem::convertPath(PKG_ROOT_PATH . '/bin/upx' . $suffix));
}
try {
$ver = $builder->getPHPVersion();
$indent_texts['PHP Version'] = $ver;
} catch (\Throwable) {
if (($ver = $builder->getPHPVersionFromArchive()) !== false) {
$indent_texts['PHP Version'] = $ver;
}
}
if (!empty($not_included)) {
logger()->warning('Some extensions will be enabled due to dependencies: ' . implode(',', $not_included));
$indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included);
}
logger()->info('Build will start after 2s ...');
$this->printFormatInfo($indent_texts);
logger()->notice('Build will start after 2s ...');
sleep(2);
if ($this->input->getOption('with-clean')) {
@@ -102,6 +155,9 @@ class BuildCliCommand extends BuildCommand
SourcePatcher::patchHardcodedINI($custom_ini);
}
// add static-php-cli.version to main.c, in order to debug php failure more easily
SourcePatcher::patchSPCVersionToPHP($this->getApplication()->getVersion());
// start to build
$builder->buildPHP($rule);
@@ -119,13 +175,17 @@ class BuildCliCommand extends BuildCommand
$fixed = ' (host system)';
}
if (($rule & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
logger()->info('Static php binary path' . $fixed . ': ' . $build_root_path . '/bin/php');
$win_suffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : '';
$path = FileSystem::convertPath("{$build_root_path}/bin/php{$win_suffix}");
logger()->info("Static php binary path{$fixed}: {$path}");
}
if (($rule & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
logger()->info('phpmicro binary path' . $fixed . ': ' . $build_root_path . '/bin/micro.sfx');
$path = FileSystem::convertPath("{$build_root_path}/bin/micro.sfx");
logger()->info("phpmicro binary path{$fixed}: {$path}");
}
if (($rule & BUILD_TARGET_FPM) === BUILD_TARGET_FPM) {
logger()->info('Static php-fpm binary path' . $fixed . ': ' . $build_root_path . '/bin/php-fpm');
if (($rule & BUILD_TARGET_FPM) === BUILD_TARGET_FPM && PHP_OS_FAMILY !== 'Windows') {
$path = FileSystem::convertPath("{$build_root_path}/bin/php-fpm");
logger()->info("Static php-fpm binary path{$fixed}: {$path}");
}
// export metadata
@@ -134,7 +194,8 @@ class BuildCliCommand extends BuildCommand
// export licenses
$dumper = new LicenseDumper();
$dumper->addExts($extensions)->addLibs($libraries)->addSources(['php-src'])->dump(BUILD_ROOT_PATH . '/license');
logger()->info('License path' . $fixed . ': ' . $build_root_path . '/license/');
$path = FileSystem::convertPath("{$build_root_path}/license/");
logger()->info("License path{$fixed}: {$path}");
return static::SUCCESS;
} catch (WrongUsageException $e) {
// WrongUsageException is not an exception, it's a user error, so we just print the error message

View File

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

View File

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

View File

@@ -116,6 +116,7 @@ class DownloadCommand extends BaseCommand
// get source list that will be downloaded
$sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
if (empty($sources)) {
logger()->notice('Downloading with --all option will take more times to download, we recommend you to download with --for-extensions option !');
$sources = array_keys(Config::getSources());
}
}
@@ -188,7 +189,8 @@ class DownloadCommand extends BaseCommand
// create downloads
try {
if (PHP_OS_FAMILY !== 'Windows') {
f_passthru('mkdir ' . DOWNLOAD_PATH . ' && cd ' . DOWNLOAD_PATH . ' && unzip ' . escapeshellarg($path));
$abs_path = realpath($path);
f_passthru('mkdir ' . DOWNLOAD_PATH . ' && cd ' . DOWNLOAD_PATH . ' && unzip ' . escapeshellarg($abs_path));
}
// Windows TODO
@@ -212,7 +214,7 @@ class DownloadCommand extends BaseCommand
*/
private function calculateSourcesByExt(array $extensions, bool $include_suggests = true): array
{
[$extensions, $libraries] = $include_suggests ? DependencyUtil::getAllExtLibsByDeps($extensions) : DependencyUtil::getExtLibsByDeps($extensions);
[$extensions, $libraries] = $include_suggests ? DependencyUtil::getExtsAndLibs($extensions, [], true, true) : DependencyUtil::getExtsAndLibs($extensions);
$sources = [];
foreach ($extensions as $extension) {
if (Config::getExt($extension, 'type') === 'external') {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,10 +9,22 @@ use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem;
use SPC\doctor\CheckResult;
use SPC\exception\RuntimeException;
use SPC\store\FileSystem;
use SPC\store\PackageManager;
class WindowsToolCheckList
{
#[AsCheckItem('if git are installed', limit_os: 'Windows', level: 999)]
#[AsCheckItem('if Visual Studio are installed', limit_os: 'Windows', level: 999)]
public function checkVS(): ?CheckResult
{
$vs_ver = SystemUtil::findVisualStudio();
if ($vs_ver === false) {
return CheckResult::fail('Visual Studio not installed, please install VS 2022/2019.');
}
return CheckResult::ok($vs_ver['version'] . ' ' . $vs_ver['dir']);
}
#[AsCheckItem('if git are installed', limit_os: 'Windows', level: 998)]
public function checkGit(): ?CheckResult
{
if (SystemUtil::findCommand('git.exe') === null) {
@@ -22,7 +34,7 @@ class WindowsToolCheckList
return CheckResult::ok();
}
#[AsCheckItem('if php-sdk-binary-tools are downloaded', limit_os: 'Windows', level: 998)]
#[AsCheckItem('if php-sdk-binary-tools are downloaded', limit_os: 'Windows', level: 997)]
public function checkSDK(): ?CheckResult
{
if (!file_exists(PHP_SDK_PATH . DIRECTORY_SEPARATOR . 'phpsdk-starter.bat')) {
@@ -31,14 +43,64 @@ class WindowsToolCheckList
return CheckResult::ok(PHP_SDK_PATH);
}
#[AsCheckItem('if git associated command exists', limit_os: 'Windows', level: 996)]
public function checkGitPatch(): ?CheckResult
{
if (($path = SystemUtil::findCommand('patch.exe')) === null) {
return CheckResult::fail('Git patch (minGW command) not found in path. You need to add "C:\Program Files\Git\usr\bin" in Path.');
}
return CheckResult::ok();
}
#[AsCheckItem('if nasm installed', limit_os: 'Windows', level: 995)]
public function checkNasm(): ?CheckResult
{
if (SystemUtil::findCommand('nasm.exe', include_sdk_bin: true) === null) {
return CheckResult::fail('nasm.exe not found in path.', 'install-nasm');
}
return CheckResult::ok();
}
#[AsCheckItem('if perl(strawberry) installed', limit_os: 'Windows', level: 994)]
public function checkPerl(): ?CheckResult
{
$arch = arch2gnu(php_uname('m'));
if (file_exists(PKG_ROOT_PATH . '\strawberry-perl-' . $arch . '-win\perl\bin\perl.exe')) {
return CheckResult::ok(PKG_ROOT_PATH . '\strawberry-perl-' . $arch . '-win\perl\bin\perl.exe');
}
if (($path = SystemUtil::findCommand('perl.exe')) === null) {
return CheckResult::fail('perl not found in path.', 'install-perl');
}
if (!str_contains(implode('', cmd()->execWithResult(quote($path) . ' -v')[1]), 'MSWin32')) {
return CheckResult::fail($path . ' is not built for msvc.', 'install-perl');
}
return CheckResult::ok();
}
#[AsFixItem('install-php-sdk')]
public function installPhpSdk(): bool
{
try {
cmd(true)->exec('git clone https://github.com/php/php-sdk-binary-tools.git ' . PHP_SDK_PATH);
FileSystem::removeDir(PHP_SDK_PATH);
cmd(true)->exec('git.exe clone --depth 1 https://github.com/php/php-sdk-binary-tools.git ' . PHP_SDK_PATH);
} catch (RuntimeException) {
return false;
}
return true;
}
#[AsFixItem('install-nasm')]
public function installNasm(): bool
{
PackageManager::installPackage('nasm-x86_64-win');
return true;
}
#[AsFixItem('install-perl')]
public function installPerl(): bool
{
$arch = arch2gnu(php_uname('m'));
PackageManager::installPackage("strawberry-perl-{$arch}-win");
return true;
}
}

View File

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

View File

@@ -168,14 +168,15 @@ class Downloader
public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null): void
{
logger()->debug("Downloading {$url}");
pcntl_signal(SIGINT, function () use ($filename) {
if (file_exists(DOWNLOAD_PATH . '/' . $filename)) {
$cancel_func = function () use ($filename) {
if (file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/' . $filename))) {
logger()->warning('Deleting download file: ' . $filename);
unlink(DOWNLOAD_PATH . '/' . $filename);
unlink(FileSystem::convertPath(DOWNLOAD_PATH . '/' . $filename));
}
});
self::curlDown(url: $url, path: DOWNLOAD_PATH . "/{$filename}");
pcntl_signal(SIGINT, SIG_IGN);
};
self::registerCancelEvent($cancel_func);
self::curlDown(url: $url, path: FileSystem::convertPath(DOWNLOAD_PATH . "/{$filename}"));
self::unregisterCancelEvent();
logger()->debug("Locking {$filename}");
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path]);
}
@@ -187,7 +188,7 @@ class Downloader
*/
public static function lockSource(string $name, array $data): void
{
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.lock.json'))) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
@@ -204,24 +205,25 @@ class Downloader
*/
public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null): void
{
$download_path = DOWNLOAD_PATH . "/{$name}";
$download_path = FileSystem::convertPath(DOWNLOAD_PATH . "/{$name}");
if (file_exists($download_path)) {
FileSystem::removeDir($download_path);
}
logger()->debug("cloning {$name} source");
$check = !defined('DEBUG_MODE') ? ' -q' : '';
pcntl_signal(SIGINT, function () use ($download_path) {
$cancel_func = function () use ($download_path) {
if (is_dir($download_path)) {
logger()->warning('Removing path ' . $download_path);
FileSystem::removeDir($download_path);
}
});
};
self::registerCancelEvent($cancel_func);
f_passthru(
'git clone' . $check .
SPC_GIT_EXEC . ' clone' . $check .
' --config core.autocrlf=false ' .
"--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\""
);
pcntl_signal(SIGINT, SIG_IGN);
self::unregisterCancelEvent();
// Lock
logger()->debug("Locking git source {$name}");
@@ -244,6 +246,92 @@ class Downloader
}*/
}
public static function downloadPackage(string $name, ?array $pkg = null, bool $force = false): void
{
if ($pkg === null) {
$pkg = Config::getPkg($name);
}
if ($pkg === null) {
logger()->warning('Package {name} unknown. Skipping.', ['name' => $name]);
return;
}
if (!is_dir(DOWNLOAD_PATH)) {
FileSystem::createDir(DOWNLOAD_PATH);
}
// load lock file
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading
if (isset($lock[$name]) && !$force) {
if ($lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename'])) {
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['filename']);
return;
}
if ($lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])) {
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['dirname']);
return;
}
}
try {
switch ($pkg['type']) {
case 'bitbuckettag': // BitBucket Tag
[$url, $filename] = self::getLatestBitbucketTag($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'ghtar': // GitHub Release (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'ghtagtar': // GitHub Tag (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $pkg, 'tags');
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'ghrel': // GitHub Release (uploaded)
[$url, $filename] = self::getLatestGithubRelease($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'filelist': // Basic File List (regex based crawler)
[$url, $filename] = self::getFromFileList($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'url': // Direct download URL
$url = $pkg['url'];
$filename = $pkg['filename'] ?? basename($pkg['url']);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
break;
case 'git': // Git repo
self::downloadGit($name, $pkg['url'], $pkg['rev'], $pkg['extract'] ?? null);
break;
case 'custom': // Custom download method, like API-based download or other
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\\store\\source');
foreach ($classes as $class) {
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
(new $class())->fetch();
break;
}
}
break;
default:
throw new DownloaderException('unknown source type: ' . $pkg['type']);
}
} catch (RuntimeException $e) {
// Because sometimes files downloaded through the command line are not automatically deleted after a failure.
// Here we need to manually delete the file if it is detected to exist.
if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) {
logger()->warning('Deleting download file: ' . $filename);
unlink(DOWNLOAD_PATH . '/' . $filename);
}
throw new DownloaderException('Download failed! ' . $e->getMessage());
}
}
/**
* Download source by name and meta.
*
@@ -251,7 +339,6 @@ class Downloader
* @param null|array $source source meta info: [type, path, rev, url, filename, regex, license]
* @throws DownloaderException
* @throws FileSystemException
* @throws RuntimeException
*/
public static function downloadSource(string $name, ?array $source = null, bool $force = false): void
{
@@ -359,12 +446,12 @@ class Downloader
};
$headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$cmd = "curl -sfSL {$methodArg} {$headerArg} \"{$url}\"";
$cmd = SPC_CURL_EXEC . " -sfSL {$methodArg} {$headerArg} \"{$url}\"";
if (getenv('CACHE_API_EXEC') === 'yes') {
if (!file_exists(DOWNLOAD_PATH . '/.curl_exec_cache')) {
if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.curl_exec_cache'))) {
$cache = [];
} else {
$cache = json_decode(file_get_contents(DOWNLOAD_PATH . '/.curl_exec_cache'), true);
$cache = json_decode(file_get_contents(FileSystem::convertPath(DOWNLOAD_PATH . '/.curl_exec_cache')), true);
}
if (isset($cache[$cmd]) && $cache[$cmd]['expire'] >= time()) {
return $cache[$cmd]['cache'];
@@ -375,7 +462,7 @@ class Downloader
}
$cache[$cmd]['cache'] = implode("\n", $output);
$cache[$cmd]['expire'] = time() + 3600;
file_put_contents(DOWNLOAD_PATH . '/.curl_exec_cache', json_encode($cache));
file_put_contents(FileSystem::convertPath(DOWNLOAD_PATH . '/.curl_exec_cache'), json_encode($cache));
return $cache[$cmd]['cache'];
}
f_exec($cmd, $output, $ret);
@@ -404,7 +491,35 @@ class Downloader
};
$headerArg = implode(' ', array_map(fn ($v) => '"-H' . $v . '"', $headers));
$check = !defined('DEBUG_MODE') ? 's' : '#';
$cmd = "curl -{$check}fSL -o \"{$path}\" {$methodArg} {$headerArg} \"{$url}\"";
$cmd = SPC_CURL_EXEC . " -{$check}fSL -o \"{$path}\" {$methodArg} {$headerArg} \"{$url}\"";
f_passthru($cmd);
}
/**
* Register CTRL+C event for different OS.
*
* @param callable $callback callback function
*/
private static function registerCancelEvent(callable $callback): void
{
if (PHP_OS_FAMILY === 'Windows') {
sapi_windows_set_ctrl_handler($callback);
} elseif (extension_loaded('pcntl')) {
pcntl_signal(SIGINT, $callback);
} else {
logger()->debug('You have not enabled `pcntl` extension, cannot prevent download file corruption when Ctrl+C');
}
}
/**
* Unegister CTRL+C event for different OS.
*/
private static function unregisterCancelEvent(): void
{
if (PHP_OS_FAMILY === 'Windows') {
sapi_windows_set_ctrl_handler(null);
} elseif (extension_loaded('pcntl')) {
pcntl_signal(SIGINT, SIG_IGN);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More