Compare commits

...

8 Commits

Author SHA1 Message Date
crazywhalecc
d73cefc280
update readme and version 2023-04-30 14:29:56 +08:00
crazywhalecc
6f4aebfc4c
update ext-support.md 2023-04-30 14:23:20 +08:00
crazywhalecc
cac3ffc7d7
add event support 2023-04-30 14:22:59 +08:00
crazywhalecc
893fa5bfd6
update README 2023-04-30 12:55:49 +08:00
crazywhalecc
40102a661d
update docker interactive 2023-04-30 12:52:24 +08:00
crazywhalecc
d7305c434e
update workflow 2023-04-30 12:47:00 +08:00
crazywhalecc
0bed76da11
refactor download 2023-04-30 12:42:19 +08:00
crazywhalecc
117cd93e8f
update gitignore 2023-04-30 11:45:34 +08:00
41 changed files with 1175 additions and 713 deletions

106
.github/workflows/build-linux-arm.yml vendored Normal file
View File

@ -0,0 +1,106 @@
name: CI on arm linux
on:
workflow_dispatch:
inputs:
operating-system:
required: true
description: Compile target arch (Linux only)
type: choice
options:
- aarch64
- armv7l
version:
required: true
description: php version to compile
default: '8.2'
type: choice
options:
- '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 ${{ inputs.operating-system }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Cache downloaded source
- id: cache-download
uses: actions/cache@v3
with:
path: downloads
key: php-${{ inputs.version }}-dependencies
# 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
- run: SPC_USE_ARCH=${{ inputs.operating-system }} ./bin/spc-alpine-docker download --with-php=${{ inputs.version }} --all ${{ env.SPC_BUILD_DEBUG }}
# Run build command
- run: SPC_USE_ARCH=${{ inputs.operating-system }} ./bin/spc-alpine-docker 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 }}-linux-${{ inputs.operating-system }}
path: buildroot/bin/php
# Upload micro self-extracted executable
- if: ${{ inputs.build-micro == true }}
uses: actions/upload-artifact@v3
with:
name: micro-${{ inputs.version }}-linux-${{ inputs.operating-system }}
path: buildroot/bin/micro.sfx
# Upload fpm executable
- if: ${{ inputs.build-fpm == true }}
uses: actions/upload-artifact@v3
with:
name: php-fpm-${{ inputs.version }}-linux-${{ inputs.operating-system }}
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

@ -1,15 +1,8 @@
name: CI
name: CI on x86_64 linux
on:
workflow_dispatch:
inputs:
operating-system:
required: true
description: Compile target OS
type: choice
options:
- ubuntu-latest
- macos-latest
version:
required: true
description: php version to compile
@ -42,20 +35,13 @@ env:
jobs:
build:
name: build ${{ inputs.version }} on ${{ inputs.operating-system }}
runs-on: ${{ inputs.operating-system }}
name: build ${{ inputs.version }} on Linux x86_64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Install macOS missing packages and mark os suffix
- if: ${{ inputs.operating-system == 'macos-latest' }}
run: |
brew install automake gzip
echo "SPC_BUILD_OS=macos" >> $GITHUB_ENV
# Install Ubuntu missing packages and mark os suffix
- if: ${{ inputs.operating-system == 'ubuntu-latest' }}
run: |
- run: |
sudo apt install musl-tools -y
echo "SPC_BUILD_OS=linux" >> $GITHUB_ENV
@ -90,7 +76,7 @@ jobs:
run: echo "SPC_BUILD_FPM=--build-fpm" >> $GITHUB_ENV
# If there's no dependencies cache, fetch sources, with or without debug
- run: CACHE_API_EXEC=yes ./bin/spc fetch --with-php=${{ inputs.version }} --all ${{ env.SPC_BUILD_DEBUG }}
- run: CACHE_API_EXEC=yes ./bin/spc download --with-php=${{ inputs.version }} --all ${{ 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 }}

115
.github/workflows/build-macos-x86_64.yml vendored Normal file
View File

@ -0,0 +1,115 @@
name: CI on x86_64 macOS
on:
workflow_dispatch:
inputs:
version:
required: true
description: php version to compile
default: '8.2'
type: choice
options:
- '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 x86_64
runs-on: macos-latest
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
# 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
# Cache downloaded source
- id: cache-download
uses: actions/cache@v3
with:
path: downloads
key: php-${{ inputs.version }}-dependencies
# 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
- run: CACHE_API_EXEC=yes ./bin/spc download --with-php=${{ inputs.version }} --all ${{ 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

1
.gitignore vendored
View File

@ -24,3 +24,4 @@ composer.lock
/bin/*
!/bin/spc
!/bin/setup-runtime
!/bin/spc-alpine-docker

View File

@ -11,7 +11,8 @@ This function is provided by [dixyes/phpmicro](https://github.com/dixyes/phpmicr
[![Version](https://img.shields.io/badge/Version-2.0--beta1-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/build.yml?branch=refactor&label=Actions%20Build&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/build.yml)
[![](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/build-linux-x86_64.yml?branch=refactor&label=Linux%20Build&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/build.yml)
[![](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/build-macos-x86_64.yml?branch=refactor&label=macOS%20Build&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/build.yml)
[![](https://img.shields.io/github/search/crazywhalecc/static-php-cli/TODO?label=TODO%20Counter&style=flat-square)]()
## Compilation Requirements
@ -22,13 +23,13 @@ But static-php-cli runtime only requires an environment above PHP 8.0 and `token
Here is the architecture support status, where `CI` represents support for GitHub Action builds,
`Local` represents support for local builds, and blank represents not currently supported.
| | x86_64 | aarch64 |
|---------|-----------|---------|
| macOS | CI, Local | Local |
| Linux | CI, Local | Local |
| Windows | | |
| | x86_64 | aarch64 | armv7l |
|---------|-----------|-----------|-----------|
| macOS | CI, Local | Local | |
| Linux | CI, Local | CI, Local | CI, Local |
| Windows | | | |
> linux-aarch64 and 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.
> macOS-arm64 is not supported for GitHub Actions, if you are going to build on arm, you can build it manually on your own machine.
Currently supported PHP versions for compilation are: `7.4`, `8.0`, `8.1`, `8.2`.

View File

@ -12,7 +12,8 @@ If you are using English, see [English README](README-en.md).
[![Version](https://img.shields.io/badge/Version-2.0--beta1-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/build.yml?branch=refactor&label=Actions%20Build&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/build.yml)
[![](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/build-linux-x86_64.yml?branch=refactor&label=Linux%20Build&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/build.yml)
[![](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/build-macos-x86_64.yml?branch=refactor&label=macOS%20Build&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/build.yml)
[![](https://img.shields.io/github/search/crazywhalecc/static-php-cli/TODO?label=TODO%20Counter&style=flat-square)]()
## 编译环境需求
@ -22,13 +23,13 @@ If you are using English, see [English README](README-en.md).
下面是架构支持情况,`CI` 代表支持 GitHub Action 构建,`Local` 代表支持本地构建,空 代表暂不支持。
| | x86_64 | aarch64 |
|---------|-----------|---------|
| macOS | CI, Local | Local |
| Linux | CI, Local | Local |
| Windows | | |
| | x86_64 | aarch64 | armv7l |
|---------|-----------|-----------|-----------|
| macOS | CI, Local | Local | |
| Linux | CI, Local | CI, Local | CI, Local |
| Windows | | | |
> linux-aarch64 and macOS-arm64 因 GitHub 暂未提供 arm runner如果要构建 arm 二进制,可以使用手动构建。
> macOS-arm64 因 GitHub 暂未提供 arm runner如果要构建 arm 二进制,可以使用手动构建。
目前支持编译的 PHP 版本为:`7.4``8.0``8.1``8.2`

87
bin/spc-alpine-docker Executable file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env sh
# This file is using docker to run commands
self_dir=$(cd "$(dirname "$0")";pwd)
# Detect docker can run
if ! which docker >/dev/null; then
echo "Docker is not installed, please install docker first !"
exit 1
fi
DOCKER_EXECUTABLE="docker"
if [ $(id -u) -ne 0 ]; then
if ! docker info > /dev/null 2>&1; then
if [ "$SPC_USE_SUDO" != "yes" ]; then
echo "Docker command requires sudo"
echo -n 'To use sudo to run docker, run "export SPC_USE_SUDO=yes" and run command again'
exit 1
fi
DOCKER_EXECUTABLE="sudo docker"
fi
fi
# to check if qemu-docker run
if [ "$SPC_USE_ARCH" = "" ]; then
SPC_USE_ARCH=x86_64
fi
case $SPC_USE_ARCH in
x86_64)
ALPINE_FROM=alpine:edge
;;
aarch64)
ALPINE_FROM=multiarch/alpine:aarch64-edge
echo -e "\e[033m* Using different arch needs to setup qemu-static for docker !\e[0m"
$DOCKER_EXECUTABLE run --rm --privileged multiarch/qemu-user-static:register --reset > /dev/null
;;
armv7l)
ALPINE_FROM=multiarch/alpine:armv7-edge
echo -e "\e[033m* Using different arch needs to setup qemu-static for docker !\e[0m"
$DOCKER_EXECUTABLE run --rm --privileged multiarch/qemu-user-static:register --reset > /dev/null
;;
*)
echo "Current arch is not supported to run in docker: $SPC_USE_ARCH"
exit 1
;;
esac
if [ "$SPC_USE_MIRROR" = "yes" ]; then
SPC_USE_MIRROR="RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories"
else
SPC_USE_MIRROR="RUN echo 'Using original repo'"
fi
# Detect docker env is setup
if ! $DOCKER_EXECUTABLE images | grep -q cwcc-spc-$SPC_USE_ARCH; then
echo "Docker container does not exist. Building docker image ..."
ALPINE_DOCKERFILE=$(cat << EOF
FROM $ALPINE_FROM
$SPC_USE_MIRROR
RUN apk update
RUN apk add bash file wget cmake gcc g++ jq autoconf git libstdc++ linux-headers make m4 libgcc binutils bison flex pkgconfig automake curl
RUN apk add build-base xz php81 php81-common php81-pcntl php81-tokenizer php81-phar php81-posix php81-xml composer
RUN mkdir /app
WORKDIR /app
ADD ./src /app/src
ADD ./composer.json /app/composer.json
ADD ./bin /app/bin
RUN composer update --no-dev
EOF
)
echo "$ALPINE_DOCKERFILE" > $(pwd)/Dockerfile
$DOCKER_EXECUTABLE build -t cwcc-spc-$SPC_USE_ARCH .
rm $(pwd)/Dockerfile
fi
# Check if in ci (local terminal can execute with -it)
if [ -t 0 ]; then
INTERACT=-it
else
INTERACT=''
fi
# Run docker
$DOCKER_EXECUTABLE run --rm $INTERACT -e SPC_FIX_DEPLOY_ROOT=$(pwd) -v $(pwd)/config:/app/config -v $(pwd)/src:/app/src -v $(pwd)/buildroot:/app/buildroot -v $(pwd)/source:/app/source -v $(pwd)/downloads:/app/downloads cwcc-spc-$SPC_USE_ARCH bin/spc $@

View File

@ -15,7 +15,8 @@
"symfony/console": "^6 || ^5 || ^4",
"zhamao/logger": "^1.0",
"crazywhalecc/cli-helper": "^0.1.0",
"nunomaduro/collision": "*"
"nunomaduro/collision": "*",
"ext-pcntl": "*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2 != 3.7.0",

View File

@ -35,6 +35,20 @@
"zlib"
]
},
"event": {
"type": "external",
"source": "ext-event",
"arg-type": "custom",
"lib-depends": [
"libevent"
],
"ext-depends": [
"openssl"
],
"ext-suggests": [
"sockets"
]
},
"exif": {
"type": "builtin"
},

View File

@ -102,6 +102,18 @@
"libavif.a"
]
},
"libevent": {
"source": "libevent",
"static-libs-unix": [
"libevent.a",
"libevent_core.a",
"libevent_extra.a",
"libevent_openssl.a"
],
"lib-depends": [
"openssl"
]
},
"libffi": {
"source": "libffi",
"static-libs-unix": [

View File

@ -1,4 +1,11 @@
{
"php-src": {
"type": "custom",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"brotli": {
"type": "ghtar",
"repo": "google/brotli",
@ -25,6 +32,15 @@
"path": "COPYING"
}
},
"ext-event": {
"type": "url",
"url": "https://bitbucket.org/osmanov/pecl-event/get/3.0.8.tar.gz",
"path": "php-src/ext/event",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"ext-zstd": {
"type": "git",
"path": "php-src/ext/zstd",
@ -71,6 +87,15 @@
"path": "LICENSE"
}
},
"libevent": {
"type": "ghrel",
"repo": "libevent/libevent",
"match": "libevent.+\\.tar\\.gz",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"libffi": {
"type": "ghrel",
"repo": "libffi/libffi",
@ -132,14 +157,6 @@
"path": "COPYING"
}
},
"libuv": {
"type": "ghtar",
"repo": "libuv/libuv",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"libwebp": {
"type": "ghtagtar",
"repo": "webmproject/libwebp",
@ -229,13 +246,6 @@
"path": "LICENSE.txt"
}
},
"php-src": {
"type": "custom",
"license": {
"type": "file",
"path": "LICENSE"
}
},
"postgresql": {
"type": "custom",
"license": {

View File

@ -6,58 +6,58 @@
> - no with issue link: not supported yet due to issue
> - partial with issue link: supported but not perfect due to issue
| | Linux | macOS | Windows |
|------------|----------------------------------------------------------------|---------------------------------------------------------------------|---------|
| bcmath | yes | yes | |
| bz2 | yes | yes | |
| calendar | yes | yes | |
| ctype | yes | yes | |
| curl | yes | yes | |
| dba | yes | yes | |
| dom | yes | yes | |
| enchant | | | |
| event | | | |
| exif | yes | yes | |
| filter | yes | yes | |
| fileinfo | yes | yes | |
| ftp | yes | yes | |
| gd | yes | yes | |
| gettext | | | |
| gmp | yes | yes | |
| iconv | yes | yes | |
| inotify | yes | yes | |
| mbstring | yes | yes | |
| mbregex | yes | yes | |
| mcrypt | | [faulty](https://github.com/crazywhalecc/static-php-cli/issues/32) | |
| mongodb | yes | yes | |
| mysqli | yes | yes | |
| mysqlnd | yes | yes | |
| openssl | yes | yes | |
| pcntl | yes | yes | |
| pdo | yes | yes | |
| pdo_mysql | yes | yes | |
| pdo_sqlite | yes | yes | |
| pdo_pgsql | | | |
| phar | yes | yes | |
| posix | yes | yes | |
| protobuf | yes | yes | |
| readline | | | |
| redis | yes | yes | |
| session | yes | yes | |
| shmop | yes | yes | |
| simplexml | yes | yes | |
| soap | yes | yes | |
| sockets | yes | yes | |
| sqlite3 | yes | yes | |
| swow | yes | [no](https://github.com/crazywhalecc/static-php-cli/issues/32) | |
| swoole | [no](https://github.com/crazywhalecc/static-php-cli/issues/32) | [partial](https://github.com/crazywhalecc/static-php-cli/issues/32) | |
| tokenizer | yes | yes | |
| xml | yes | yes | |
| xmlreader | yes, untested | yes, untested | |
| xmlwriter | yes, untested | yes, untested | |
| zip | yes, untested | yes | |
| zlib | yes | yes | |
| zstd | yes | yes | |
| | Linux | macOS | Windows |
|------------|---------------------------------------------------------------------|--------------------------------------------------------------------|---------|
| bcmath | yes | yes | |
| bz2 | yes | yes | |
| calendar | yes | yes | |
| ctype | yes | yes | |
| curl | yes | yes | |
| dba | yes | yes | |
| dom | yes | yes | |
| enchant | | | |
| event | yes | yes | |
| exif | yes | yes | |
| filter | yes | yes | |
| fileinfo | yes | yes | |
| ftp | yes | yes | |
| gd | yes | yes | |
| gettext | | | |
| gmp | yes | yes | |
| iconv | yes | yes | |
| inotify | yes | yes | |
| mbstring | yes | yes | |
| mbregex | yes | yes | |
| mcrypt | | [faulty](https://github.com/crazywhalecc/static-php-cli/issues/32) | |
| mongodb | yes | yes | |
| mysqli | yes | yes | |
| mysqlnd | yes | yes | |
| openssl | yes | yes | |
| pcntl | yes | yes | |
| pdo | yes | yes | |
| pdo_mysql | yes | yes | |
| pdo_sqlite | yes | yes | |
| pdo_pgsql | | | |
| phar | yes | yes | |
| posix | yes | yes | |
| protobuf | yes | yes | |
| readline | | | |
| redis | yes | yes | |
| session | yes | yes | |
| shmop | yes | yes | |
| simplexml | yes | yes | |
| soap | yes | yes | |
| sockets | yes | yes | |
| sqlite3 | yes | yes | |
| swow | yes | yes | |
| swoole | [partial](https://github.com/crazywhalecc/static-php-cli/issues/32) | yes | |
| tokenizer | yes | yes | |
| xml | yes | yes | |
| xmlreader | yes, untested | yes, untested | |
| xmlwriter | yes, untested | yes, untested | |
| zip | yes, untested | yes | |
| zlib | yes | yes | |
| zstd | yes | yes | |
## Additional Requirements

View File

@ -16,7 +16,7 @@ use Symfony\Component\Console\Command\ListCommand;
*/
class ConsoleApplication extends Application
{
public const VERSION = '2.0-beta2';
public const VERSION = '2.0-beta3';
/**
* @throws \ReflectionException

View File

@ -6,6 +6,7 @@ namespace SPC\builder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\util\CustomExt;
@ -40,8 +41,9 @@ abstract class BuilderBase
/**
* 构建指定列表的 libs
*
* @throws RuntimeException
* @throws FileSystemException
* @throws RuntimeException
* @throws WrongUsageException
*/
public function buildLibs(array $libraries): void
{
@ -80,13 +82,14 @@ abstract class BuilderBase
$this->addLib($lib);
}
// 统计还没 fetch 到本地的库
$this->checkLibsSource();
// 计算依赖,经过这里的遍历,如果没有抛出异常,说明依赖符合要求,可以继续下面的
foreach ($this->libs as $lib) {
$lib->calcDependency();
}
$this->initSource(libs: $libraries);
// 构建库
foreach ($this->libs as $lib) {
match ($lib->tryBuild()) {
BUILD_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] build success'),
@ -154,6 +157,11 @@ abstract class BuilderBase
public function proveExts(array $extensions): void
{
CustomExt::loadCustomExt();
$this->initSource(sources: ['php-src']);
if ($this->getPHPVersionID() >= 80000) {
$this->initSource(sources: ['micro']);
}
$this->initSource(exts: $extensions);
foreach ($extensions as $extension) {
$class = CustomExt::getExtClass($extension);
$ext = new $class($extension, $this);
@ -248,4 +256,52 @@ abstract class BuilderBase
);
}
}
protected function initSource(?array $sources = null, ?array $libs = null, ?array $exts = null): void
{
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
throw new WrongUsageException('Download lock file "downloads/.lock.json" not found, maybe you need to download sources first ?');
}
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
$sources_extracted = [];
// source check exist
if (is_array($sources)) {
foreach ($sources as $source) {
$sources_extracted[$source] = true;
}
}
// lib check source exist
if (is_array($libs)) {
foreach ($libs as $lib) {
// get source name for lib
$source = Config::getLib($lib, 'source');
$sources_extracted[$source] = true;
}
}
// ext check source exist
if (is_array($exts)) {
foreach ($exts as $ext) {
// get source name for ext
if (Config::getExt($ext, 'type') !== 'external') {
continue;
}
$source = Config::getExt($ext, 'source');
$sources_extracted[$source] = true;
}
}
// start check
foreach ($sources_extracted as $source => $item) {
if (!isset($lock[$source])) {
throw new WrongUsageException('Source [' . $source . '] not downloaded, you should download it first !');
}
// check source dir exist
$check = $lock[$source]['move_path'] === null ? SOURCE_PATH . '/' . $source : SOURCE_PATH . '/' . $lock[$source]['move_path'];
if (!is_dir($check)) {
FileSystem::extractSource($source, DOWNLOAD_PATH . '/' . ($lock[$source]['filename'] ?? $lock[$source]['dirname']), $lock[$source]['move_path']);
}
}
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace SPC\builder\extension;
use SPC\builder\Extension;
use SPC\util\CustomExt;
#[CustomExt('event')]
class event extends Extension
{
public function getUnixConfigureArg(): string
{
$arg = '--with-event-core --with-event-extra --with-event-libevent-dir=' . BUILD_ROOT_PATH;
if ($this->builder->getLib('openssl')) {
$arg .= ' --with-event-openssl=' . BUILD_ROOT_PATH;
}
if ($this->builder->getExt('sockets')) {
$arg .= ' --enable-event-sockets';
} else {
$arg .= ' --disable-event-sockets';
}
return $arg;
}
}

View File

@ -13,11 +13,8 @@ class swoole extends Extension
public function getUnixConfigureArg(): string
{
$arg = '--enable-swoole';
if ($this->builder->getLib('openssl')) {
$arg .= ' --enable-openssl';
} else {
$arg .= ' --disable-openssl --without-openssl';
}
$arg .= $this->builder->getLib('openssl') ? ' --enable-openssl' : ' --disable-openssl --without-openssl';
$arg .= $this->builder->getLib('brotli') ? (' --enable-brotli --with-brotli-dir=' . BUILD_ROOT_PATH) : '';
// curl hook is buggy for static php
$arg .= ' --disable-swoole-curl';
return $arg;

View File

@ -10,7 +10,7 @@ use SPC\builder\traits\UnixBuilderTrait;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\util\Patcher;
use SPC\store\SourcePatcher;
/**
* Linux 系统环境下的构建器
@ -46,7 +46,10 @@ class LinuxBuilder extends BuilderBase
public function __construct(?string $cc = null, ?string $cxx = null, ?string $arch = null)
{
// 初始化一些默认参数
$this->cc = $cc ?? 'musl-gcc';
$this->cc = $cc ?? match (SystemUtil::getOSRelease()['dist']) {
'alpine' => 'gcc',
default => 'musl-gcc'
};
$this->cxx = $cxx ?? 'g++';
$this->arch = $arch ?? php_uname('m');
$this->gnu_arch = arch2gnu($this->arch);
@ -135,6 +138,9 @@ class LinuxBuilder extends BuilderBase
)
);
}
if ($this->getExt('swoole')) {
$extra_libs .= ' -lstdc++';
}
$envs = $this->pkgconf_env . ' ' .
"CC='{$this->cc}' " .
@ -158,11 +164,11 @@ class LinuxBuilder extends BuilderBase
$envs = "{$envs} CFLAGS='{$cflags}' LIBS='-ldl -lpthread'";
Patcher::patchPHPBeforeConfigure($this);
SourcePatcher::patchPHPBuildconf($this);
shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force');
Patcher::patchPHPConfigure($this);
SourcePatcher::patchPHPConfigure($this);
shell()->cd(SOURCE_PATH . '/php-src')
->exec(
@ -183,6 +189,8 @@ class LinuxBuilder extends BuilderBase
$envs
);
SourcePatcher::patchPHPAfterConfigure($this);
file_put_contents('/tmp/comment', $this->note_section);
// 清理

View File

@ -13,44 +13,6 @@ class SystemUtil
{
use UnixSystemUtilTrait;
/**
* 查找并选择编译器命令
*
* @throws RuntimeException
*/
public static function selectCC(): string
{
logger()->debug('Choose cc');
if (self::findCommand('clang')) {
logger()->info('using clang');
return 'clang';
}
if (self::findCommand('gcc')) {
logger()->info('using gcc');
return 'gcc';
}
throw new RuntimeException('no supported cc found');
}
/**
* 查找并选择编译器命令
*
* @throws RuntimeException
*/
public static function selectCXX(): string
{
logger()->debug('Choose cxx');
if (self::findCommand('clang++')) {
logger()->info('using clang++');
return 'clang++';
}
if (self::findCommand('g++')) {
logger()->info('using g++');
return 'g++';
}
return self::selectCC();
}
#[ArrayShape(['dist' => 'mixed|string', 'ver' => 'mixed|string'])]
public static function getOSRelease(): array
{

View File

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

View File

@ -22,7 +22,7 @@ namespace SPC\builder\linux\library;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\util\Patcher;
use SPC\store\SourcePatcher;
class libpng extends LinuxLibraryBase
{
@ -41,7 +41,7 @@ class libpng extends LinuxLibraryBase
};
// patch configure
Patcher::patchUnixLibpng();
SourcePatcher::patchUnixLibpng();
shell()->cd($this->source_dir)
->exec('chmod +x ./configure')

View File

@ -10,7 +10,7 @@ use SPC\builder\traits\UnixBuilderTrait;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\util\Patcher;
use SPC\store\SourcePatcher;
/**
* macOS 系统环境下的构建器
@ -136,11 +136,11 @@ class MacOSBuilder extends BuilderBase
}
// patch before configure
Patcher::patchPHPBeforeConfigure($this);
SourcePatcher::patchPHPBuildconf($this);
shell()->cd(SOURCE_PATH . '/php-src')->exec('./buildconf --force');
Patcher::patchPHPConfigure($this);
SourcePatcher::patchPHPConfigure($this);
if ($this->getLib('libxml2') || $this->getExt('iconv')) {
$extra_libs .= ' -liconv';
@ -166,6 +166,8 @@ class MacOSBuilder extends BuilderBase
$this->configure_env
);
SourcePatcher::patchPHPAfterConfigure($this);
$this->cleanMake();
if (($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {

View File

@ -20,7 +20,7 @@ declare(strict_types=1);
namespace SPC\builder\macos\library;
use SPC\util\Patcher;
use SPC\store\SourcePatcher;
class curl extends MacOSLibraryBase
{
@ -32,7 +32,7 @@ class curl extends MacOSLibraryBase
protected function build()
{
Patcher::patchCurlMacOS();
SourcePatcher::patchCurlMacOS();
$this->unixBuild();
}
}

View File

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

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\store\FileSystem;
trait libevent
{
protected function build()
{
// CMake needs a clean build directory
FileSystem::resetDir($this->source_dir . '/build');
// Start build
shell()->cd($this->source_dir . '/build')
->exec(
"{$this->builder->configure_env} cmake " .
'-DCMAKE_INSTALL_PREFIX=' . BUILD_ROOT_PATH . ' ' .
"-DCMAKE_TOOLCHAIN_FILE={$this->builder->cmake_toolchain_file} " .
'-DCMAKE_BUILD_TYPE=Release ' .
'-DEVENT__LIBRARY_TYPE=STATIC ' .
'-DEVENT__DISABLE_BENCHMARK=ON ' .
'-DEVENT__DISABLE_THREAD_SUPPORT=ON ' .
'-DEVENT__DISABLE_TESTS=ON ' .
'-DEVENT__DISABLE_SAMPLES=ON ' .
'..'
)
->exec("cmake --build . -j {$this->builder->concurrency}")
->exec('make install');
// patch pkgconfig
}
}

View File

@ -6,6 +6,8 @@ namespace SPC\command;
use Psr\Log\LogLevel;
use SPC\ConsoleApplication;
use SPC\exception\ExceptionHandler;
use SPC\exception\WrongUsageException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@ -84,12 +86,23 @@ abstract class BaseCommand extends Command
if ($this->shouldExecute()) {
try {
return $this->handle();
} catch (\Throwable $e) {
} catch (WrongUsageException $e) {
$msg = explode("\n", $e->getMessage());
foreach ($msg as $v) {
logger()->error($v);
}
return self::FAILURE;
} catch (\Throwable $e) {
// 不开 debug 模式就不要再显示复杂的调试栈信息了
if ($this->getOption('debug')) {
ExceptionHandler::getInstance()->handle($e);
} else {
$msg = explode("\n", $e->getMessage());
foreach ($msg as $v) {
logger()->error($v);
}
}
return self::FAILURE;
}
}
return self::SUCCESS;

View File

@ -73,14 +73,22 @@ class BuildCliCommand extends BuildCommand
// 统计时间
$time = round(microtime(true) - START_TIME, 3);
logger()->info('Build complete, used ' . $time . ' s !');
$build_root_path = BUILD_ROOT_PATH;
$cwd = getcwd();
$fixed = '';
if (!empty(getenv('SPC_FIX_DEPLOY_ROOT'))) {
str_replace($cwd, '', $build_root_path);
$build_root_path = getenv('SPC_FIX_DEPLOY_ROOT') . $build_root_path;
$fixed = ' (host system)';
}
if (($rule & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) {
logger()->info('Static php binary path: ' . BUILD_ROOT_PATH . '/bin/php');
logger()->info('Static php binary path' . $fixed . ': ' . $build_root_path . '/bin/php');
}
if (($rule & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) {
logger()->info('phpmicro binary path: ' . BUILD_ROOT_PATH . '/bin/micro.sfx');
logger()->info('phpmicro binary path' . $fixed . ': ' . $build_root_path . '/bin/micro.sfx');
}
if (($rule & BUILD_TARGET_FPM) === BUILD_TARGET_FPM) {
logger()->info('Static php-fpm binary path: ' . BUILD_ROOT_PATH . '/bin/php-fpm');
logger()->info('Static php-fpm binary path' . $fixed . ': ' . $build_root_path . '/bin/php-fpm');
}
// 导出相关元数据
file_put_contents(BUILD_ROOT_PATH . '/build-extensions.json', json_encode($extensions, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
@ -88,7 +96,7 @@ class BuildCliCommand extends BuildCommand
// 导出 LICENSE
$dumper = new LicenseDumper();
$dumper->addExts($extensions)->addLibs($libraries)->addSources(['php-src'])->dump(BUILD_ROOT_PATH . '/license');
logger()->info('License path: ' . BUILD_ROOT_PATH . '/license/');
logger()->info('License path' . $fixed . ': ' . $build_root_path . '/license/');
return 0;
} catch (WrongUsageException $e) {
logger()->critical($e->getMessage());

View File

@ -18,6 +18,7 @@ class DoctorCommand extends BaseCommand
$this->output->writeln('<info>Doctor check complete !</info>');
} catch (\Throwable $e) {
$this->output->writeln('<error>' . $e->getMessage() . '</error>');
pcntl_signal(SIGINT, SIG_IGN);
return 1;
}
return 0;

View File

@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace SPC\command;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\store\Config;
use SPC\store\Downloader;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand('download', 'Download required sources', ['fetch'])]
class DownloadCommand extends BaseCommand
{
protected string $php_major_ver;
public function configure()
{
$this->addArgument('sources', InputArgument::REQUIRED, 'The sources will be compiled, comma separated');
$this->addOption('shallow-clone', null, null, 'Clone shallow');
$this->addOption('with-openssl11', null, null, 'Use openssl 1.1');
$this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'version in major.minor format like 8.1', '8.1');
$this->addOption('clean', null, null, 'Clean old download cache and source before fetch');
$this->addOption('all', 'A', null, 'Fetch all sources that static-php-cli needed');
}
public function initialize(InputInterface $input, OutputInterface $output)
{
// --all 等于 "" "",也就是所有东西都要下载
if ($input->getOption('all') || $input->getOption('clean')) {
$input->setArgument('sources', '');
}
parent::initialize($input, $output);
}
/**
* @throws DownloaderException
* @throws RuntimeException
* @throws FileSystemException
*/
public function handle(): int
{
// 删除旧资源
if ($this->getOption('clean')) {
logger()->warning('You are doing some operations that not recoverable: removing directories below');
logger()->warning(SOURCE_PATH);
logger()->warning(DOWNLOAD_PATH);
logger()->warning(BUILD_ROOT_PATH);
logger()->alert('I will remove these dir after 5 seconds !');
sleep(5);
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('rmdir /s /q ' . SOURCE_PATH);
f_passthru('rmdir /s /q ' . DOWNLOAD_PATH);
f_passthru('rmdir /s /q ' . BUILD_ROOT_PATH);
} else {
f_passthru('rm -rf ' . SOURCE_PATH . '/*');
f_passthru('rm -rf ' . DOWNLOAD_PATH . '/*');
f_passthru('rm -rf ' . BUILD_ROOT_PATH . '/*');
}
return 0;
}
// Define PHP major version
$ver = $this->php_major_ver = $this->getOption('with-php') ?? '8.1';
define('SPC_BUILD_PHP_VERSION', $ver);
preg_match('/^\d+\.\d+$/', $ver, $matches);
if (!$matches) {
logger()->error("bad version arg: {$ver}, x.y required!");
return 1;
}
// Use shallow-clone can reduce git resource download
if ($this->getOption('shallow-clone')) {
define('GIT_SHALLOW_CLONE', true);
}
// To read config
Config::getSource('openssl');
// use openssl 1.1
if ($this->getOption('with-openssl11')) {
logger()->debug('Using openssl 1.1');
// 手动修改配置
Config::$source['openssl']['regex'] = '/href="(?<file>openssl-(?<version>1.[^"]+)\.tar\.gz)\"/';
}
// get source list that will be downloaded
$sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
if (empty($sources)) {
$sources = array_keys(Config::getSources());
}
$chosen_sources = $sources;
// Download them
f_mkdir(DOWNLOAD_PATH);
$cnt = count($chosen_sources);
$ni = 0;
foreach ($chosen_sources as $source) {
++$ni;
logger()->info("Fetching source {$source} [{$ni}/{$cnt}]");
Downloader::downloadSource($source, Config::getSource($source));
}
// 打印拉取资源用时
$time = round(microtime(true) - START_TIME, 3);
logger()->info('Download complete, used ' . $time . ' s !');
return 0;
}
}

View File

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

View File

@ -38,7 +38,7 @@ class SortConfigCommand extends BaseCommand
case 'source':
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/source.json'), true);
ConfigValidator::validateSource($file);
ksort($file);
uksort($file, fn ($a, $b) => $a === 'php-src' ? -1 : ($b === 'php-src' ? 1 : ($a < $b ? -1 : 1)));
file_put_contents(ROOT_DIR . '/config/source.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
break;
case 'ext':

View File

@ -116,8 +116,12 @@ class CheckListHandler
private function emitFix(CheckResult $result)
{
pcntl_signal(SIGINT, function () {
$this->output->writeln('<error>You cancelled fix</error>');
});
$fix = $this->fix_map[$result->getFixItem()];
$fix_result = call_user_func($fix, ...$result->getFixParams());
pcntl_signal(SIGINT, SIG_IGN);
if ($fix_result) {
$this->output->writeln('<info>Fix done</info>');
} else {

View File

@ -39,4 +39,10 @@ class CheckResult
{
return empty($this->message);
}
public function setFixItem(string $fix_item = '', array $fix_params = [])
{
$this->fix_item = $fix_item;
$this->fix_params = $fix_params;
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace SPC\doctor\item;
use SPC\builder\linux\SystemUtil;
use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem;
use SPC\doctor\CheckResult;
use SPC\exception\RuntimeException;
class LinuxMuslCheck
{
#[AsCheckItem('if musl-libc is installed', limit_os: 'Linux')]
public function checkMusl(): ?CheckResult
{
$file = '/lib/ld-musl-x86_64.so.1';
$result = null;
if (file_exists($file)) {
return CheckResult::ok();
}
// non-exist, need to recognize distro
$distro = SystemUtil::getOSRelease();
return match ($distro['dist']) {
'ubuntu', 'alpine', 'debian' => CheckResult::fail('musl-libc is not installed on your system', 'fix-musl', [$distro]),
default => CheckResult::fail('musl-libc is not installed on your system'),
};
}
#[AsFixItem('fix-musl')]
public function fixMusl(array $distro): bool
{
$install_cmd = match ($distro['dist']) {
'ubuntu', 'debian' => 'apt install musl musl-tools -y',
'alpine' => 'apk add musl musl-utils musl-dev',
default => throw new RuntimeException('Current linux distro is not supported for auto-install musl packages'),
};
$prefix = '';
if (get_current_user() !== 'root') {
$prefix = 'sudo ';
logger()->warning('Current user is not root, using sudo for running command');
}
try {
shell(true)->exec($prefix . $install_cmd);
return true;
} catch (RuntimeException) {
return false;
}
}
}

View File

@ -125,4 +125,12 @@ class Config
}
return self::$ext;
}
public static function getSources(): array
{
if (self::$source === null) {
self::$source = FileSystem::loadConfigArray('source');
}
return self::$source;
}
}

View File

@ -148,6 +148,37 @@ class Downloader
return [$source['url'] . end($versions), end($versions), key($versions)];
}
/**
* @throws DownloaderException
* @throws RuntimeException
* @throws FileSystemException
*/
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)) {
logger()->warning('Deleting download file: ' . $filename);
unlink(DOWNLOAD_PATH . '/' . $filename);
}
});
self::curlDown(url: $url, path: DOWNLOAD_PATH . "/{$filename}");
pcntl_signal(SIGINT, SIG_IGN);
logger()->debug("Locking {$filename}");
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path]);
}
public static function lockSource(string $name, array $data): void
{
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
$lock[$name] = $data;
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
}
/**
* 通过链接下载资源到本地并解压
*
@ -183,25 +214,32 @@ class Downloader
}
}
public static function downloadGit(string $name, string $url, string $branch, ?string $path = null): void
public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null): void
{
if ($path !== null) {
$path = SOURCE_PATH . "/{$path}";
} else {
$path = DOWNLOAD_PATH . "/{$name}";
}
$download_path = DOWNLOAD_PATH . "/{$name}";
if (file_exists($download_path)) {
logger()->notice("{$name} git source already fetched");
} else {
logger()->debug("cloning {$name} source");
$check = !defined('DEBUG_MODE') ? ' -q' : '';
f_passthru(
'git clone' . $check .
' --config core.autocrlf=false ' .
"--branch \"{$branch}\" " . (defined('GIT_SHALLOW_CLONE') ? '--depth 1 --single-branch' : '') . " --recursive \"{$url}\" \"{$download_path}\""
);
FileSystem::removeDir($download_path);
}
logger()->debug("cloning {$name} source");
$check = !defined('DEBUG_MODE') ? ' -q' : '';
pcntl_signal(SIGINT, function () use ($download_path) {
if (is_dir($download_path)) {
logger()->warning('Removing path ' . $download_path);
FileSystem::removeDir($download_path);
}
});
f_passthru(
'git 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);
// Lock
logger()->debug("Locking git source {$name}");
self::lockSource($name, ['source_type' => 'dir', 'dirname' => $name, 'move_path' => $move_path]);
/*
// 复制目录过去
if ($path !== $download_path) {
$dst_path = FileSystem::convertPath($path);
@ -215,64 +253,78 @@ class Downloader
f_passthru('cp -r "' . $src_path . '" "' . $dst_path . '"');
break;
}
}
}*/
}
/**
* 拉取资源
*
* @param string $name 资源名称
* @param array $source 资源参数,包含 type、path、rev、url、filename、regex、license
* @param null|array $source 资源参数,包含 type、path、rev、url、filename、regex、license
* @throws DownloaderException
* @throws RuntimeException
* @throws FileSystemException
* @throws RuntimeException
*/
public static function fetchSource(string $name, array $source): void
public static function downloadSource(string $name, ?array $source = null): void
{
// 如果是 git 类型,且没有设置 path那我就需要指定一下 path
if ($source['type'] === 'git' && !isset($source['path'])) {
$source['path'] = $name;
if ($source === null) {
$source = Config::getSource($name);
}
// 避免重复 fetch
if (!isset($source['path']) && is_dir(FileSystem::convertPath(DOWNLOAD_PATH . "/{$name}")) || isset($source['path']) && is_dir(FileSystem::convertPath(SOURCE_PATH . "/{$source['path']}"))) {
logger()->notice("{$name} source already extracted");
return;
// 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])) {
if ($lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename'])) {
logger()->notice("source [{$name}] already downloaded: " . $lock[$name]['filename']);
return;
}
if ($lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])) {
logger()->notice("source [{$name}] already downloaded: " . $lock[$name]['dirname']);
return;
}
}
try {
switch ($source['type']) {
case 'bitbuckettag': // 从 BitBucket 的 Tag 拉取
[$url, $filename] = self::getLatestBitbucketTag($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'ghtar': // 从 GitHub 的 TarBall 拉取
[$url, $filename] = self::getLatestGithubTarball($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'ghtagtar': // 根据 GitHub 的 Tag 拉取相应版本的 Tar
[$url, $filename] = self::getLatestGithubTarball($name, $source, 'tags');
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'ghrel': // 通过 GitHub Release 来拉取
[$url, $filename] = self::getLatestGithubRelease($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'filelist': // 通过网站提供的 filelist 使用正则提取后拉取
[$url, $filename] = self::getFromFileList($name, $source);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'url': // 通过直链拉取
$url = $source['url'];
$filename = $source['filename'] ?? basename($source['url']);
self::downloadUrl($name, $url, $filename, $source['path'] ?? null);
self::downloadFile($name, $url, $filename, $source['path'] ?? null);
break;
case 'git': // 通过拉回 Git 仓库的形式拉取
self::downloadGit($name, $source['url'], $source['rev'], $source['path'] ?? null);
break;
case 'custom':
case 'custom': // 自定义,可能是通过复杂 API 形式获取的文件,需要手写 crawler
$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;

View File

@ -6,10 +6,11 @@ namespace SPC\store;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
class FileSystem
{
private static array $_extract_hook = [];
/**
* @throws FileSystemException
*/
@ -118,20 +119,16 @@ class FileSystem
public static function copyDir(string $from, string $to): void
{
$iterator = new \RecursiveIteratorIterator(new RecursiveDirectoryIterator($from, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_FILEINFO), \RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $item) {
/**
* @var \SplFileInfo $item
*/
$target = $to . substr($item->getPathname(), strlen($from));
if ($item->isDir()) {
logger()->info("mkdir {$target}");
f_mkdir($target, recursive: true);
} else {
logger()->info("copying {$item} to {$target}");
@f_mkdir(dirname($target), recursive: true);
copy($item->getPathname(), $target);
}
$dst_path = FileSystem::convertPath($to);
$src_path = FileSystem::convertPath($from);
switch (PHP_OS_FAMILY) {
case 'Windows':
f_passthru('xcopy "' . $src_path . '" "' . $dst_path . '" /s/e/v/y/i');
break;
case 'Linux':
case 'Darwin':
f_passthru('cp -r "' . $src_path . '" "' . $dst_path . '"');
break;
}
}
@ -143,41 +140,56 @@ class FileSystem
* @throws FileSystemException
* @throws RuntimeException
*/
public static function extractSource(string $name, string $filename): void
public static function extractSource(string $name, string $filename, ?string $move_path = null): void
{
// if source hook is empty, load it
if (self::$_extract_hook === []) {
SourcePatcher::init();
}
if ($move_path !== null) {
$move_path = SOURCE_PATH . '/' . $move_path;
}
logger()->info("extracting {$name} source");
try {
if (PHP_OS_FAMILY !== 'Windows') {
if (f_mkdir(directory: SOURCE_PATH . "/{$name}", recursive: true) !== true) {
$target = $move_path ?? (SOURCE_PATH . "/{$name}");
// Git source, just move
if (is_dir($filename)) {
self::copyDir($filename, $target);
self::emitSourceExtractHook($name);
return;
}
if (PHP_OS_FAMILY === 'Darwin' || PHP_OS_FAMILY === 'Linux') {
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 " . SOURCE_PATH . "/{$name} --strip-components 1");
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 " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1");
break;
case 'bz2':
f_passthru("tar -xjf {$filename} -C " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1");
break;
case 'zip':
f_passthru("unzip {$filename} -d " . SOURCE_PATH . "/{$name}");
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 " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
break;
default:
throw new FileSystemException('unknown archive format: ' . $filename);
}
} else {
} elseif (PHP_OS_FAMILY === 'Windows') {
// find 7z
$_7zExe = self::findCommandPath('7z', [
'C:\Program Files\7-Zip-Zstandard',
@ -201,13 +213,13 @@ class FileSystem
case 'gz':
case 'tgz':
case 'bz2':
f_passthru("\"{$_7zExe}\" x -so {$filename} | tar -f - -x -C " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("\"{$_7zExe}\" x -so {$filename} | tar -f - -x -C {$target} --strip-components 1");
break;
case 'tar':
f_passthru("tar -xf {$filename} -C " . SOURCE_PATH . "/{$name} --strip-components 1");
f_passthru("tar -xf {$filename} -C {$target} --strip-components 1");
break;
case 'zip':
f_passthru("\"{$_7zExe}\" x {$filename} -o" . SOURCE_PATH . "/{$name}");
f_passthru("\"{$_7zExe}\" x {$filename} -o{$target}");
break;
default:
throw new FileSystemException("unknown archive format: {$filename}");
@ -421,4 +433,18 @@ class FileSystem
}
self::createDir($dir_name);
}
public static function addSourceExtractHook(string $name, callable $callback)
{
self::$_extract_hook[$name][] = $callback;
}
private static function emitSourceExtractHook(string $name)
{
foreach ((self::$_extract_hook[$name] ?? []) as $hook) {
if ($hook($name) === true) {
logger()->info('Patched source [' . $name . '] after extracted');
}
}
}
}

View File

@ -0,0 +1,216 @@
<?php
declare(strict_types=1);
namespace SPC\store;
use SPC\builder\BuilderBase;
use SPC\builder\linux\LinuxBuilder;
use SPC\builder\macos\MacOSBuilder;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\util\Util;
class SourcePatcher
{
public static function init()
{
FileSystem::addSourceExtractHook('swow', [SourcePatcher::class, 'patchSwow']);
FileSystem::addSourceExtractHook('micro', [SourcePatcher::class, 'patchMicro']);
FileSystem::addSourceExtractHook('openssl', [SourcePatcher::class, 'patchOpenssl3']);
FileSystem::addSourceExtractHook('openssl', [SourcePatcher::class, 'patchOpenssl11Darwin']);
}
public static function patchPHPBuildconf(BuilderBase $builder): void
{
if ($builder->getExt('curl')) {
logger()->info('patching before-configure for curl checks');
$file1 = "AC_DEFUN([PHP_CHECK_LIBRARY], [\n $3\n])";
$files = FileSystem::readFile(SOURCE_PATH . '/php-src/ext/curl/config.m4');
$file2 = 'AC_DEFUN([PHP_CHECK_LIBRARY], [
save_old_LDFLAGS=$LDFLAGS
ac_stuff="$5"
save_ext_shared=$ext_shared
ext_shared=yes
PHP_EVAL_LIBLINE([$]ac_stuff, LDFLAGS)
AC_CHECK_LIB([$1],[$2],[
LDFLAGS=$save_old_LDFLAGS
ext_shared=$save_ext_shared
$3
],[
LDFLAGS=$save_old_LDFLAGS
ext_shared=$save_ext_shared
unset ac_cv_lib_$1[]_$2
$4
])dnl
])';
file_put_contents(SOURCE_PATH . '/php-src/ext/curl/config.m4', $file1 . "\n" . $files . "\n" . $file2);
}
// if ($builder->getExt('pdo_sqlite')) {
// FileSystem::replaceFile()
// }
}
public static function patchSwow(): bool
{
if (Util::getPHPVersionID() >= 80000) {
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('cd ' . SOURCE_PATH . '/php-src/ext && mklink /D swow swow-src\ext');
} else {
f_passthru('cd ' . SOURCE_PATH . '/php-src/ext && ln -s swow-src/ext swow');
}
return true;
}
return false;
}
public static function patchPHPConfigure(BuilderBase $builder): void
{
$frameworks = $builder instanceof MacOSBuilder ? ' ' . $builder->getFrameworks(true) . ' ' : '';
$patch = [];
if ($curl = $builder->getExt('curl')) {
$patch[] = ['curl check', '/-lcurl/', $curl->getLibFilesString() . $frameworks];
}
if ($bzip2 = $builder->getExt('bz2')) {
$patch[] = ['bzip2 check', '/-lbz2/', $bzip2->getLibFilesString() . $frameworks];
}
if ($pdo_sqlite = $builder->getExt('pdo_sqlite')) {
$patch[] = ['pdo_sqlite linking', '/sqlite3_column_table_name=yes/', 'sqlite3_column_table_name=no'];
}
if ($event = $builder->getExt('event')) {
$patch[] = ['event check', '/-levent_openssl/', $event->getLibFilesString()];
}
$patch[] = ['disable capstone', '/have_capstone="yes"/', 'have_capstone="no"'];
foreach ($patch as $item) {
logger()->info('Patching configure: ' . $item[0]);
FileSystem::replaceFile(SOURCE_PATH . '/php-src/configure', REPLACE_FILE_PREG, $item[1], $item[2]);
}
}
public static function patchUnixLibpng(): void
{
FileSystem::replaceFile(
SOURCE_PATH . '/libpng/configure',
REPLACE_FILE_STR,
'-lz',
BUILD_LIB_PATH . '/libz.a'
);
}
public static function patchCurlMacOS(): void
{
FileSystem::replaceFile(
SOURCE_PATH . '/curl/CMakeLists.txt',
REPLACE_FILE_PREG,
'/NOT COREFOUNDATION_FRAMEWORK/m',
'FALSE'
);
FileSystem::replaceFile(
SOURCE_PATH . '/curl/CMakeLists.txt',
REPLACE_FILE_PREG,
'/NOT SYSTEMCONFIGURATION_FRAMEWORK/m',
'FALSE'
);
}
public static function patchMicro(): bool
{
if (!file_exists(SOURCE_PATH . '/php-src/sapi/micro/php_micro.c')) {
return false;
}
$ver_file = SOURCE_PATH . '/php-src/main/php_version.h';
if (!file_exists($ver_file)) {
throw new FileSystemException('Patch failed, cannot find php source files');
}
$version_h = FileSystem::readFile(SOURCE_PATH . '/php-src/main/php_version.h');
preg_match('/#\s*define\s+PHP_MAJOR_VERSION\s+(\d+)\s+#\s*define\s+PHP_MINOR_VERSION\s+(\d+)\s+/m', $version_h, $match);
// $ver = "{$match[1]}.{$match[2]}";
$major_ver = $match[1] . $match[2];
if ($major_ver === '74') {
return false;
}
$check = !defined('DEBUG_MODE') ? ' -q' : '';
// f_passthru('cd ' . SOURCE_PATH . '/php-src && git checkout' . $check . ' HEAD');
$patch_list = [
'static_opcache',
'static_extensions_win32',
'cli_checks',
'disable_huge_page',
'vcruntime140',
'win32',
'zend_stream',
];
$patch_list = array_merge($patch_list, match (PHP_OS_FAMILY) {
'Windows' => [
'cli_static',
],
'Darwin' => [
'macos_iconv',
],
default => [],
});
$patches = [];
$serial = ['80', '81', '82'];
foreach ($patch_list as $patchName) {
if (file_exists(SOURCE_PATH . "/php-src/sapi/micro/patches/{$patchName}.patch")) {
$patches[] = "sapi/micro/patches/{$patchName}.patch";
continue;
}
for ($i = array_search($major_ver, $serial, true); $i >= 0; --$i) {
$tryMajMin = $serial[$i];
if (!file_exists(SOURCE_PATH . "/php-src/sapi/micro/patches/{$patchName}_{$tryMajMin}.patch")) {
continue;
}
$patches[] = "sapi/micro/patches/{$patchName}_{$tryMajMin}.patch";
continue 2;
}
throw new RuntimeException("failed finding patch {$patchName}");
}
$patchesStr = str_replace('/', DIRECTORY_SEPARATOR, implode(' ', $patches));
f_passthru(
'cd ' . SOURCE_PATH . '/php-src && ' .
(PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . $patchesStr . ' | patch -p1'
);
return true;
}
public static function patchOpenssl3(): bool
{
if (file_exists(SOURCE_PATH . '/openssl/VERSION.dat') && Util::getPHPVersionID() >= 80000) {
$openssl_c = file_get_contents(SOURCE_PATH . '/php-src/ext/openssl/openssl.c');
$openssl_c = preg_replace('/REGISTER_LONG_CONSTANT\s*\(\s*"OPENSSL_SSLV23_PADDING"\s*.+;/', '', $openssl_c);
file_put_contents(SOURCE_PATH . '/php-src/ext/openssl/openssl.c', $openssl_c);
return true;
}
return false;
}
public static function patchOpenssl11Darwin(): bool
{
if (PHP_OS_FAMILY === 'Darwin' && !file_exists(SOURCE_PATH . '/openssl/VERSION.dat') && file_exists(SOURCE_PATH . '/openssl/test/v3ext.c')) {
FileSystem::replaceFile(
SOURCE_PATH . '/openssl/test/v3ext.c',
REPLACE_FILE_STR,
'#include <stdio.h>',
'#include <stdio.h>' . PHP_EOL . '#include <string.h>'
);
return true;
}
return false;
}
public static function patchPHPAfterConfigure(BuilderBase $param)
{
if ($param instanceof LinuxBuilder) {
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_STRLCPY 1$/m', '');
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_STRLCAT 1$/m', '');
}
FileSystem::replaceFile(SOURCE_PATH . '/php-src/main/php_config.h', REPLACE_FILE_PREG, '/^#define HAVE_OPENPTY 1$/m', '');
}
}

View File

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

View File

@ -12,7 +12,7 @@ class PostgreSQLSource extends CustomSourceBase
public function fetch()
{
Downloader::fetchSource('postgresql', self::getLatestInfo());
Downloader::downloadSource('postgresql', self::getLatestInfo());
}
public function getLatestInfo(): array

View File

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

View File

@ -57,4 +57,9 @@ const PKGCONF_PATCH_LIBDIR = 4;
const PKGCONF_PATCH_INCLUDEDIR = 8;
const PKGCONF_PATCH_ALL = 15;
// Custom download type
const DOWNLOAD_TYPE_NONE = 0;
const DOWNLOAD_TYPE_ARCHIVE = 1;
const DOWNLOAD_TYPE_DIR = 2;
ConsoleLogger::$date_format = 'H:i:s';