mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-11 19:05:35 +08:00
Merge pull request #767 from crazywhalecc/docs
patchelf and rename -release tagged extensions from php configure build
This commit is contained in:
@@ -278,7 +278,7 @@ bin/spc micro:combine my-app.phar -I "memory_limit=4G" -I "disable_functions=sys
|
|||||||
|
|
||||||
如果你知道 [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed),你应该知道如何使用它。对于有可能编译用到引入其他库的问题,你可以使用 `buildroot/bin/php-config` 来获取编译时的配置。
|
如果你知道 [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed),你应该知道如何使用它。对于有可能编译用到引入其他库的问题,你可以使用 `buildroot/bin/php-config` 来获取编译时的配置。
|
||||||
|
|
||||||
另外,有关如何使用此功能的高级示例,请查看[如何使用它构建 FrankenPHP 的静态版本](https://github.com/dunglas/frankenphp/blob/main/docs/static.md)。
|
另外,有关如何使用此功能的高级示例,请查看[如何使用它构建 FrankenPHP 的静态版本](https://github.com/php/frankenphp/blob/main/docs/static.md)。
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ If you know [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed),
|
|||||||
You may require the introduction of other libraries during compilation,
|
You may require the introduction of other libraries during compilation,
|
||||||
you can use `buildroot/bin/php-config` to obtain the compile-time configuration.
|
you can use `buildroot/bin/php-config` to obtain the compile-time configuration.
|
||||||
|
|
||||||
For an advanced example of how to use this feature, take a look at [how to use it to build a static version of FrankenPHP](https://github.com/dunglas/frankenphp/blob/main/docs/static.md).
|
For an advanced example of how to use this feature, take a look at [how to use it to build a static version of FrankenPHP](https://github.com/php/frankenphp/blob/main/docs/static.md).
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ RUN apk update; \
|
|||||||
wget \
|
wget \
|
||||||
xz \
|
xz \
|
||||||
gettext-dev \
|
gettext-dev \
|
||||||
binutils-gold
|
binutils-gold \
|
||||||
|
patchelf
|
||||||
|
|
||||||
RUN curl -#fSL https://dl.static-php.dev/static-php-cli/bulk/php-8.4.4-cli-linux-\$(uname -m).tar.gz | tar -xz -C /usr/local/bin && \
|
RUN curl -#fSL https://dl.static-php.dev/static-php-cli/bulk/php-8.4.4-cli-linux-\$(uname -m).tar.gz | tar -xz -C /usr/local/bin && \
|
||||||
chmod +x /usr/local/bin/php
|
chmod +x /usr/local/bin/php
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ RUN echo "source scl_source enable devtoolset-10" >> /etc/bashrc
|
|||||||
RUN source /etc/bashrc
|
RUN source /etc/bashrc
|
||||||
RUN yum install -y which
|
RUN yum install -y which
|
||||||
|
|
||||||
|
RUN curl -fsSL -o patchelf.tgz https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-$BASE_ARCH.tar.gz && \
|
||||||
|
mkdir -p /patchelf && \
|
||||||
|
tar -xzf patchelf.tgz -C /patchelf --strip-components=1 && \
|
||||||
|
cp /patchelf/bin/patchelf /usr/bin/
|
||||||
|
|
||||||
RUN curl -o cmake.tgz -fsSL https://github.com/Kitware/CMake/releases/download/v3.31.4/cmake-3.31.4-linux-$BASE_ARCH.tar.gz && \
|
RUN curl -o cmake.tgz -fsSL https://github.com/Kitware/CMake/releases/download/v3.31.4/cmake-3.31.4-linux-$BASE_ARCH.tar.gz && \
|
||||||
mkdir /cmake && \
|
mkdir /cmake && \
|
||||||
tar -xzf cmake.tgz -C /cmake --strip-components 1
|
tar -xzf cmake.tgz -C /cmake --strip-components 1
|
||||||
@@ -89,7 +94,7 @@ ENV PATH="/app/bin:/cmake/bin:$PATH"
|
|||||||
ENV SPC_LIBC=glibc
|
ENV SPC_LIBC=glibc
|
||||||
|
|
||||||
ADD ./config/env.ini /app/config/env.ini
|
ADD ./config/env.ini /app/config/env.ini
|
||||||
RUN bin/spc doctor --auto-fix --debug
|
RUN CC=gcc bin/spc doctor --auto-fix --debug
|
||||||
|
|
||||||
RUN curl -o make.tgz -fsSL https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && \
|
RUN curl -o make.tgz -fsSL https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && \
|
||||||
tar -zxvf make.tgz && \
|
tar -zxvf make.tgz && \
|
||||||
@@ -136,7 +141,7 @@ echo 'CC=/opt/rh/devtoolset-10/root/usr/bin/gcc' > /tmp/spc-gnu-docker.env
|
|||||||
echo 'CXX=/opt/rh/devtoolset-10/root/usr/bin/g++' >> /tmp/spc-gnu-docker.env
|
echo 'CXX=/opt/rh/devtoolset-10/root/usr/bin/g++' >> /tmp/spc-gnu-docker.env
|
||||||
echo 'AR=/opt/rh/devtoolset-10/root/usr/bin/ar' >> /tmp/spc-gnu-docker.env
|
echo 'AR=/opt/rh/devtoolset-10/root/usr/bin/ar' >> /tmp/spc-gnu-docker.env
|
||||||
echo 'LD=/opt/rh/devtoolset-10/root/usr/bin/ld' >> /tmp/spc-gnu-docker.env
|
echo 'LD=/opt/rh/devtoolset-10/root/usr/bin/ld' >> /tmp/spc-gnu-docker.env
|
||||||
echo 'SPC_DEFAULT_C_FLAGS=-fPIE -fPIC' >> /tmp/spc-gnu-docker.env
|
echo 'SPC_DEFAULT_C_FLAGS=-fPIC' >> /tmp/spc-gnu-docker.env
|
||||||
echo 'SPC_LIBC=glibc' >> /tmp/spc-gnu-docker.env
|
echo 'SPC_LIBC=glibc' >> /tmp/spc-gnu-docker.env
|
||||||
echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-Wl,-O1 -pie"' >> /tmp/spc-gnu-docker.env
|
echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-Wl,-O1 -pie"' >> /tmp/spc-gnu-docker.env
|
||||||
echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm -lresolv -lutil -lrt"' >> /tmp/spc-gnu-docker.env
|
echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm -lresolv -lutil -lrt"' >> /tmp/spc-gnu-docker.env
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ SPC_CONCURRENCY=${CPU_COUNT}
|
|||||||
SPC_SKIP_PHP_VERSION_CHECK="no"
|
SPC_SKIP_PHP_VERSION_CHECK="no"
|
||||||
; Ignore some check item for bin/spc doctor command, comma separated (e.g. SPC_SKIP_DOCTOR_CHECK_ITEMS="if homebrew has installed")
|
; Ignore some check item for bin/spc doctor command, comma separated (e.g. SPC_SKIP_DOCTOR_CHECK_ITEMS="if homebrew has installed")
|
||||||
SPC_SKIP_DOCTOR_CHECK_ITEMS=""
|
SPC_SKIP_DOCTOR_CHECK_ITEMS=""
|
||||||
|
; extra modules that xcaddy will include in the FrankenPHP build
|
||||||
|
SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/frankenphp/caddy --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli"
|
||||||
|
|
||||||
; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them
|
; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them
|
||||||
; only useful for builds targeting not pure-static linking
|
; only useful for builds targeting not pure-static linking
|
||||||
; default paths
|
; default paths
|
||||||
@@ -66,7 +69,7 @@ SPC_LIBC=musl
|
|||||||
CC=${SPC_LINUX_DEFAULT_CC}
|
CC=${SPC_LINUX_DEFAULT_CC}
|
||||||
CXX=${SPC_LINUX_DEFAULT_CXX}
|
CXX=${SPC_LINUX_DEFAULT_CXX}
|
||||||
AR=${SPC_LINUX_DEFAULT_AR}
|
AR=${SPC_LINUX_DEFAULT_AR}
|
||||||
LD=ld.gold
|
LD=${SPC_LINUX_DEFAULT_LD}
|
||||||
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
|
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
|
||||||
SPC_DEFAULT_C_FLAGS="-fPIC -Os"
|
SPC_DEFAULT_C_FLAGS="-fPIC -Os"
|
||||||
SPC_DEFAULT_CXX_FLAGS="-fPIC -Os"
|
SPC_DEFAULT_CXX_FLAGS="-fPIC -Os"
|
||||||
|
|||||||
@@ -854,5 +854,14 @@
|
|||||||
"zstd.h",
|
"zstd.h",
|
||||||
"zstd_errors.h"
|
"zstd_errors.h"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"watcher": {
|
||||||
|
"source": "watcher",
|
||||||
|
"static-libs-unix": [
|
||||||
|
"libwatcher-c.a"
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
"wtr/watcher-c.h"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,5 +42,17 @@
|
|||||||
"extract-files": {
|
"extract-files": {
|
||||||
"upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe"
|
"upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"go-xcaddy-x86_64-linux": {
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
"go-xcaddy-aarch64-linux": {
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
"go-xcaddy-x86_64-macos": {
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
"go-xcaddy-aarch64-macos": {
|
||||||
|
"type": "custom"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1070,5 +1070,14 @@
|
|||||||
"type": "file",
|
"type": "file",
|
||||||
"path": "LICENSE"
|
"path": "LICENSE"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"watcher": {
|
||||||
|
"type": "ghtar",
|
||||||
|
"repo": "e-dant/watcher",
|
||||||
|
"prefer-stable": true,
|
||||||
|
"license": {
|
||||||
|
"type": "file",
|
||||||
|
"path": "license"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ If the build is successful, you will see the `buildroot/bin` directory in the cu
|
|||||||
- fpm: The build result is `buildroot/bin/php-fpm`.
|
- fpm: The build result is `buildroot/bin/php-fpm`.
|
||||||
- micro: The build result is `buildroot/bin/micro.sfx`. If you need to further package it with PHP code, please refer to [Packaging micro binary](./manual-build#command-micro-combine).
|
- micro: The build result is `buildroot/bin/micro.sfx`. If you need to further package it with PHP code, please refer to [Packaging micro binary](./manual-build#command-micro-combine).
|
||||||
- embed: See [Using embed](./manual-build#embed-usage).
|
- embed: See [Using embed](./manual-build#embed-usage).
|
||||||
|
- frankenphp: The build result is `buildroot/bin/frankenphp`.
|
||||||
|
|
||||||
If the build fails, you can use the `--debug` parameter to view detailed error information,
|
If the build fails, you can use the `--debug` parameter to view detailed error information,
|
||||||
or use the `--with-clean` to clear the old compilation results and recompile.
|
or use the `--with-clean` to clear the old compilation results and recompile.
|
||||||
@@ -290,6 +291,7 @@ You need to specify a compilation target, choose from the following parameters:
|
|||||||
- `--build-fpm`: Build a fpm sapi (php-fpm, used in conjunction with other traditional fpm architecture software such as nginx)
|
- `--build-fpm`: Build a fpm sapi (php-fpm, used in conjunction with other traditional fpm architecture software such as nginx)
|
||||||
- `--build-micro`: Build a micro sapi (used to build a standalone executable binary containing PHP code)
|
- `--build-micro`: Build a micro sapi (used to build a standalone executable binary containing PHP code)
|
||||||
- `--build-embed`: Build an embed sapi (used to embed into other C language programs)
|
- `--build-embed`: Build an embed sapi (used to embed into other C language programs)
|
||||||
|
- `--build-frankenphp`: Build a [FrankenPHP](https://github.com/php/frankenphp) executable
|
||||||
- `--build-all`: build all above sapi
|
- `--build-all`: build all above sapi
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -509,6 +511,8 @@ When `bin/spc doctor` automatically repairs the Windows environment, tools such
|
|||||||
Here is an example of installing the tool:
|
Here is an example of installing the tool:
|
||||||
|
|
||||||
- Download and install UPX (Linux and Windows only): `bin/spc install-pkg upx`
|
- Download and install UPX (Linux and Windows only): `bin/spc install-pkg upx`
|
||||||
|
- Download and install nasm (Windows only): `bin/spc install-pkg nasm`
|
||||||
|
- Download and install go-xcaddy: `bin/spc install-pkg go-xcaddy`
|
||||||
|
|
||||||
## Command - del-download
|
## Command - del-download
|
||||||
|
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ bin/spc craft --debug
|
|||||||
- fpm: 构建结果为 `buildroot/bin/php-fpm`。
|
- fpm: 构建结果为 `buildroot/bin/php-fpm`。
|
||||||
- micro: 构建结果为 `buildroot/bin/micro.sfx`,如需进一步与 PHP 代码打包,请查看 [打包 micro 二进制](./manual-build#命令-micro-combine-打包-micro-二进制)。
|
- micro: 构建结果为 `buildroot/bin/micro.sfx`,如需进一步与 PHP 代码打包,请查看 [打包 micro 二进制](./manual-build#命令-micro-combine-打包-micro-二进制)。
|
||||||
- embed: 参见 [embed 使用](./manual-build#embed-使用)。
|
- embed: 参见 [embed 使用](./manual-build#embed-使用)。
|
||||||
|
- frankenphp: 构建结果为 `buildroot/bin/frankenphp`。
|
||||||
|
|
||||||
如果中途构建出错,你可以使用 `--debug` 参数查看详细的错误信息,或者使用 `--with-clean` 参数清除旧的编译结果,重新编译。
|
如果中途构建出错,你可以使用 `--debug` 参数查看详细的错误信息,或者使用 `--with-clean` 参数清除旧的编译结果,重新编译。
|
||||||
|
|
||||||
@@ -250,6 +251,7 @@ bin/spc doctor --auto-fix
|
|||||||
- `--build-fpm`: 构建一个 fpm sapi(php-fpm,用于和其他传统的 fpm 架构的软件如 nginx 配合使用)
|
- `--build-fpm`: 构建一个 fpm sapi(php-fpm,用于和其他传统的 fpm 架构的软件如 nginx 配合使用)
|
||||||
- `--build-micro`: 构建一个 micro sapi(用于构建一个包含 PHP 代码的独立可执行二进制)
|
- `--build-micro`: 构建一个 micro sapi(用于构建一个包含 PHP 代码的独立可执行二进制)
|
||||||
- `--build-embed`: 构建一个 embed sapi(用于嵌入到其他 C 语言程序中)
|
- `--build-embed`: 构建一个 embed sapi(用于嵌入到其他 C 语言程序中)
|
||||||
|
- `--build-frankenphp`: 构建一个 [frankenphp](https://github.com/php/frankenphp) 二进制
|
||||||
- `--build-all`: 构建以上所有 sapi
|
- `--build-all`: 构建以上所有 sapi
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -457,6 +459,8 @@ bin/spc dev:sort-config ext
|
|||||||
下面是安装工具的示例:
|
下面是安装工具的示例:
|
||||||
|
|
||||||
- 下载安装 UPX(仅限 Linux 和 Windows): `bin/spc install-pkg upx`
|
- 下载安装 UPX(仅限 Linux 和 Windows): `bin/spc install-pkg upx`
|
||||||
|
- 下载安装 nasm(仅限 Windows): `bin/spc install-pkg nasm`
|
||||||
|
- 下载安装 go-xcaddy: `bin/spc install-pkg go-xcaddy`
|
||||||
|
|
||||||
## 命令 del-download - 删除已下载的资源
|
## 命令 del-download - 删除已下载的资源
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ use Symfony\Component\Console\Application;
|
|||||||
*/
|
*/
|
||||||
final class ConsoleApplication extends Application
|
final class ConsoleApplication extends Application
|
||||||
{
|
{
|
||||||
public const VERSION = '2.6.0';
|
public const VERSION = '2.6.1';
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use SPC\exception\RuntimeException;
|
|||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use SPC\store\SourceManager;
|
use SPC\store\SourceManager;
|
||||||
use SPC\util\CustomExt;
|
use SPC\util\CustomExt;
|
||||||
|
|
||||||
@@ -262,17 +263,6 @@ abstract class BuilderBase
|
|||||||
if (!$ext->isBuildShared()) {
|
if (!$ext->isBuildShared()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (Config::getExt($ext->getName(), 'type') === 'builtin' || Config::getExt($ext->getName(), 'build-with-php') === true) {
|
|
||||||
if (file_exists(BUILD_MODULES_PATH . '/' . $ext->getName() . '.so')) {
|
|
||||||
logger()->info('Shared extension [' . $ext->getName() . '] was already built by php-src/configure (' . $ext->getName() . '.so)');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (Config::getExt($ext->getName(), 'build-with-php') === true) {
|
|
||||||
logger()->warning('Shared extension [' . $ext->getName() . '] did not build with php-src/configure (' . $ext->getName() . '.so)');
|
|
||||||
logger()->warning('Try deleting your build and source folders and running `spc build`` again.');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$ext->buildShared();
|
$ext->buildShared();
|
||||||
}
|
}
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
@@ -361,15 +351,11 @@ abstract class BuilderBase
|
|||||||
public function getPHPVersionFromArchive(?string $file = null): false|string
|
public function getPHPVersionFromArchive(?string $file = null): false|string
|
||||||
{
|
{
|
||||||
if ($file === null) {
|
if ($file === null) {
|
||||||
$lock = file_exists(DOWNLOAD_PATH . '/.lock.json') ? file_get_contents(DOWNLOAD_PATH . '/.lock.json') : false;
|
$lock = LockFile::get('php-src');
|
||||||
if ($lock === false) {
|
if ($lock === null) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$lock = json_decode($lock, true);
|
|
||||||
$file = $lock['php-src']['filename'] ?? null;
|
|
||||||
if ($file === null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$file = LockFile::getLockFullPath($lock);
|
||||||
}
|
}
|
||||||
if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) {
|
if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) {
|
||||||
return $match[1];
|
return $match[1];
|
||||||
@@ -415,6 +401,9 @@ abstract class BuilderBase
|
|||||||
if (($type & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED) {
|
if (($type & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED) {
|
||||||
$ls[] = 'embed';
|
$ls[] = 'embed';
|
||||||
}
|
}
|
||||||
|
if (($type & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) {
|
||||||
|
$ls[] = 'frankenphp';
|
||||||
|
}
|
||||||
return implode(', ', $ls);
|
return implode(', ', $ls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,6 +510,29 @@ abstract class BuilderBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function checkBeforeBuildPHP(int $rule): void
|
||||||
|
{
|
||||||
|
if (($rule & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) {
|
||||||
|
if (!$this->getOption('enable-zts')) {
|
||||||
|
throw new WrongUsageException('FrankenPHP SAPI requires ZTS enabled PHP, build with `--enable-zts`!');
|
||||||
|
}
|
||||||
|
// frankenphp doesn't support windows, BSD is currently not supported by static-php-cli
|
||||||
|
if (!in_array(PHP_OS_FAMILY, ['Linux', 'Darwin'])) {
|
||||||
|
throw new WrongUsageException('FrankenPHP SAPI is only available on Linux and macOS!');
|
||||||
|
}
|
||||||
|
// frankenphp needs package go-xcaddy installed
|
||||||
|
$pkg_dir = PKG_ROOT_PATH . '/go-xcaddy-' . arch2gnu(php_uname('m')) . '-' . osfamily2shortname();
|
||||||
|
if (!file_exists("{$pkg_dir}/bin/go") || !file_exists("{$pkg_dir}/bin/xcaddy")) {
|
||||||
|
global $argv;
|
||||||
|
throw new WrongUsageException("FrankenPHP SAPI requires the go-xcaddy package, please install it first: {$argv[0]} install-pkg go-xcaddy");
|
||||||
|
}
|
||||||
|
// frankenphp needs libxml2 lib on macos, see: https://github.com/php/frankenphp/blob/main/frankenphp.go#L17
|
||||||
|
if (PHP_OS_FAMILY === 'Darwin' && !$this->getLib('libxml2')) {
|
||||||
|
throw new WrongUsageException('FrankenPHP SAPI for macOS requires libxml2 library, please include the `xml` extension in your build.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate micro extension test php code.
|
* Generate micro extension test php code.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -264,9 +264,12 @@ class Extension
|
|||||||
// If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php
|
// If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php
|
||||||
// If check failed, throw RuntimeException
|
// If check failed, throw RuntimeException
|
||||||
$sharedExtensions = $this->getSharedExtensionLoadString();
|
$sharedExtensions = $this->getSharedExtensionLoadString();
|
||||||
[$ret] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"');
|
[$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"');
|
||||||
if ($ret !== 0) {
|
if ($ret !== 0) {
|
||||||
throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret);
|
throw new RuntimeException(
|
||||||
|
'extension ' . $this->getName() . ' failed runtime check: php-cli returned ' . $ret . "\n" .
|
||||||
|
join("\n", $out)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_exists(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php')) {
|
if (file_exists(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php')) {
|
||||||
@@ -328,6 +331,17 @@ class Extension
|
|||||||
*/
|
*/
|
||||||
public function buildShared(): void
|
public function buildShared(): void
|
||||||
{
|
{
|
||||||
|
if (Config::getExt($this->getName(), 'type') === 'builtin' || Config::getExt($this->getName(), 'build-with-php') === true) {
|
||||||
|
if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) {
|
||||||
|
logger()->info('Shared extension [' . $this->getName() . '] was already built by php-src/configure (' . $this->getName() . '.so)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Config::getExt($this->getName(), 'build-with-php') === true) {
|
||||||
|
logger()->warning('Shared extension [' . $this->getName() . '] did not build with php-src/configure (' . $this->getName() . '.so)');
|
||||||
|
logger()->warning('Try deleting your build and source folders and running `spc build`` again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)');
|
logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)');
|
||||||
foreach ($this->dependencies as $dependency) {
|
foreach ($this->dependencies as $dependency) {
|
||||||
if (!$dependency instanceof Extension) {
|
if (!$dependency instanceof Extension) {
|
||||||
@@ -357,11 +371,16 @@ class Extension
|
|||||||
{
|
{
|
||||||
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
|
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
|
||||||
[$staticLibString, $sharedLibString] = $this->getStaticAndSharedLibs();
|
[$staticLibString, $sharedLibString] = $this->getStaticAndSharedLibs();
|
||||||
|
|
||||||
|
// macOS ld64 doesn't understand these, while Linux and BSD do
|
||||||
|
// use them to make sure that all symbols are picked up, even if a library has already been visited before
|
||||||
|
$preStatic = PHP_OS_FAMILY !== 'Darwin' ? '-Wl,-Bstatic -Wl,--start-group ' : '';
|
||||||
|
$postStatic = PHP_OS_FAMILY !== 'Darwin' ? ' -Wl,--end-group -Wl,-Bdynamic ' : ' ';
|
||||||
$env = [
|
$env = [
|
||||||
'CFLAGS' => $config['cflags'],
|
'CFLAGS' => $config['cflags'],
|
||||||
'CXXFLAGS' => $config['cflags'],
|
'CXXFLAGS' => $config['cflags'],
|
||||||
'LDFLAGS' => $config['ldflags'],
|
'LDFLAGS' => $config['ldflags'],
|
||||||
'LIBS' => '-Wl,-Bstatic -Wl,--start-group ' . $staticLibString . ' -Wl,--end-group -Wl,-Bdynamic ' . $sharedLibString,
|
'LIBS' => $preStatic . $staticLibString . $postStatic . $sharedLibString,
|
||||||
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
|
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -382,6 +401,7 @@ class Extension
|
|||||||
'--enable-shared --disable-static'
|
'--enable-shared --disable-static'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// some extensions don't define their dependencies well, this patch is only needed for a few
|
||||||
FileSystem::replaceFileRegex(
|
FileSystem::replaceFileRegex(
|
||||||
$this->source_dir . '/Makefile',
|
$this->source_dir . '/Makefile',
|
||||||
'/^(.*_SHARED_LIBADD\s*=.*)$/m',
|
'/^(.*_SHARED_LIBADD\s*=.*)$/m',
|
||||||
@@ -468,7 +488,7 @@ class Extension
|
|||||||
*
|
*
|
||||||
* @return array [staticLibString, sharedLibString]
|
* @return array [staticLibString, sharedLibString]
|
||||||
*/
|
*/
|
||||||
private function getStaticAndSharedLibs(): array
|
protected function getStaticAndSharedLibs(): array
|
||||||
{
|
{
|
||||||
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
|
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
|
||||||
$sharedLibString = '';
|
$sharedLibString = '';
|
||||||
@@ -483,7 +503,7 @@ class Extension
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$static_lib = 'lib' . $lib . '.a';
|
$static_lib = 'lib' . $lib . '.a';
|
||||||
if (file_exists(BUILD_LIB_PATH . '/' . $static_lib)) {
|
if (file_exists(BUILD_LIB_PATH . '/' . $static_lib) && !str_contains($static_lib, 'libphp')) {
|
||||||
if (!str_contains($staticLibString, '-l' . $lib . ' ')) {
|
if (!str_contains($staticLibString, '-l' . $lib . ' ')) {
|
||||||
$staticLibString .= '-l' . $lib . ' ';
|
$staticLibString .= '-l' . $lib . ' ';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use SPC\exception\WrongUsageException;
|
|||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\Downloader;
|
use SPC\store\Downloader;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use SPC\store\SourceManager;
|
use SPC\store\SourceManager;
|
||||||
use SPC\util\GlobalValueTrait;
|
use SPC\util\GlobalValueTrait;
|
||||||
|
|
||||||
@@ -46,12 +47,11 @@ abstract class LibraryBase
|
|||||||
*/
|
*/
|
||||||
public function setup(bool $force = false): int
|
public function setup(bool $force = false): int
|
||||||
{
|
{
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
|
||||||
$source = Config::getLib(static::NAME, 'source');
|
$source = Config::getLib(static::NAME, 'source');
|
||||||
// if source is locked as pre-built, we just tryInstall it
|
// if source is locked as pre-built, we just tryInstall it
|
||||||
$pre_built_name = Downloader::getPreBuiltLockName($source);
|
$pre_built_name = Downloader::getPreBuiltLockName($source);
|
||||||
if (isset($lock[$pre_built_name]) && ($lock[$pre_built_name]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) {
|
if (($lock = LockFile::get($pre_built_name)) && $lock['lock_as'] === SPC_DOWNLOAD_PRE_BUILT) {
|
||||||
return $this->tryInstall($lock[$pre_built_name], $force);
|
return $this->tryInstall($lock, $force);
|
||||||
}
|
}
|
||||||
return $this->tryBuild($force);
|
return $this->tryBuild($force);
|
||||||
}
|
}
|
||||||
@@ -215,6 +215,19 @@ abstract class LibraryBase
|
|||||||
*/
|
*/
|
||||||
public function tryBuild(bool $force_build = false): int
|
public function tryBuild(bool $force_build = false): int
|
||||||
{
|
{
|
||||||
|
if (str_contains((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), '-release')) {
|
||||||
|
FileSystem::replaceFileLineContainsString(
|
||||||
|
SOURCE_PATH . '/php-src/ext/standard/info.c',
|
||||||
|
'#ifdef CONFIGURE_COMMAND',
|
||||||
|
'#ifdef NO_CONFIGURE_COMMAND',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
FileSystem::replaceFileLineContainsString(
|
||||||
|
SOURCE_PATH . '/php-src/ext/standard/info.c',
|
||||||
|
'#ifdef NO_CONFIGURE_COMMAND',
|
||||||
|
'#ifdef CONFIGURE_COMMAND',
|
||||||
|
);
|
||||||
|
}
|
||||||
if (file_exists($this->source_dir . '/.spc.patched')) {
|
if (file_exists($this->source_dir . '/.spc.patched')) {
|
||||||
$this->patched = true;
|
$this->patched = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,4 +29,15 @@ class imagick extends Extension
|
|||||||
$disable_omp = !(getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) ? '' : ' ac_cv_func_omp_pause_resource_all=no';
|
$disable_omp = !(getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) ? '' : ' ac_cv_func_omp_pause_resource_all=no';
|
||||||
return '--with-imagick=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $disable_omp;
|
return '--with-imagick=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $disable_omp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getStaticAndSharedLibs(): array
|
||||||
|
{
|
||||||
|
// on centos 7, it will use the symbol _ZTINSt6thread6_StateE, which is not defined in system libstdc++.so.6
|
||||||
|
[$static, $shared] = parent::getStaticAndSharedLibs();
|
||||||
|
if (getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) {
|
||||||
|
$static .= ' -lstdc++';
|
||||||
|
$shared = str_replace('-lstdc++', '', $shared);
|
||||||
|
}
|
||||||
|
return [$static, $shared];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ class BSDBuilder extends UnixBuilderBase
|
|||||||
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
|
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
|
||||||
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
||||||
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
||||||
|
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
|
||||||
|
|
||||||
shell()->cd(SOURCE_PATH . '/php-src')
|
shell()->cd(SOURCE_PATH . '/php-src')
|
||||||
->exec(
|
->exec(
|
||||||
@@ -143,6 +144,10 @@ class BSDBuilder extends UnixBuilderBase
|
|||||||
}
|
}
|
||||||
$this->buildEmbed();
|
$this->buildEmbed();
|
||||||
}
|
}
|
||||||
|
if ($enableFrankenphp) {
|
||||||
|
logger()->info('building frankenphp');
|
||||||
|
$this->buildFrankenphp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPHP(int $build_target = BUILD_TARGET_NONE)
|
public function testPHP(int $build_target = BUILD_TARGET_NONE)
|
||||||
|
|||||||
12
src/SPC/builder/freebsd/library/watcher.php
Normal file
12
src/SPC/builder/freebsd/library/watcher.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SPC\builder\freebsd\library;
|
||||||
|
|
||||||
|
class watcher extends BSDLibraryBase
|
||||||
|
{
|
||||||
|
use \SPC\builder\unix\library\watcher;
|
||||||
|
|
||||||
|
public const NAME = 'watcher';
|
||||||
|
}
|
||||||
@@ -33,7 +33,6 @@ class LinuxBuilder extends UnixBuilderBase
|
|||||||
if (getenv('SPC_LIBC') === 'musl' && !SystemUtil::isMuslDist()) {
|
if (getenv('SPC_LIBC') === 'musl' && !SystemUtil::isMuslDist()) {
|
||||||
$this->setOptionIfNotExist('library_path', "LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\"");
|
$this->setOptionIfNotExist('library_path', "LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\"");
|
||||||
$this->setOptionIfNotExist('ld_library_path', "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\"");
|
$this->setOptionIfNotExist('ld_library_path', "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\"");
|
||||||
GlobalEnvManager::putenv("PATH=/usr/local/musl/bin:/usr/local/musl/{$arch}-linux-musl/bin:" . getenv('PATH'));
|
|
||||||
$configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE');
|
$configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE');
|
||||||
$configure = "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\" " . $configure;
|
$configure = "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\" " . $configure;
|
||||||
GlobalEnvManager::putenv("SPC_CMD_PREFIX_PHP_CONFIGURE={$configure}");
|
GlobalEnvManager::putenv("SPC_CMD_PREFIX_PHP_CONFIGURE={$configure}");
|
||||||
@@ -110,10 +109,11 @@ class LinuxBuilder extends UnixBuilderBase
|
|||||||
$config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ?
|
$config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ?
|
||||||
('--with-config-file-scan-dir=' . $this->getOption('with-config-file-scan-dir') . ' ') : '';
|
('--with-config-file-scan-dir=' . $this->getOption('with-config-file-scan-dir') . ' ') : '';
|
||||||
|
|
||||||
$enable_cli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
|
$enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
|
||||||
$enable_fpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
|
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
|
||||||
$enable_micro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
||||||
$enable_embed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
||||||
|
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
|
||||||
|
|
||||||
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
|
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
|
||||||
// prepare build php envs
|
// prepare build php envs
|
||||||
@@ -125,7 +125,7 @@ class LinuxBuilder extends UnixBuilderBase
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// process micro upx patch if micro sapi enabled
|
// process micro upx patch if micro sapi enabled
|
||||||
if ($enable_micro) {
|
if ($enableMicro) {
|
||||||
if (version_compare($this->getMicroVersion(), '0.2.0') < 0) {
|
if (version_compare($this->getMicroVersion(), '0.2.0') < 0) {
|
||||||
// for phpmicro 0.1.x
|
// for phpmicro 0.1.x
|
||||||
$this->processMicroUPXLegacy();
|
$this->processMicroUPXLegacy();
|
||||||
@@ -137,10 +137,10 @@ class LinuxBuilder extends UnixBuilderBase
|
|||||||
shell()->cd(SOURCE_PATH . '/php-src')
|
shell()->cd(SOURCE_PATH . '/php-src')
|
||||||
->exec(
|
->exec(
|
||||||
getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
|
getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
|
||||||
($enable_cli ? '--enable-cli ' : '--disable-cli ') .
|
($enableCli ? '--enable-cli ' : '--disable-cli ') .
|
||||||
($enable_fpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') .
|
($enableFpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') .
|
||||||
($enable_embed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
|
($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
|
||||||
($enable_micro ? '--enable-micro=all-static ' : '--disable-micro ') .
|
($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
|
||||||
$config_file_path .
|
$config_file_path .
|
||||||
$config_file_scan_dir .
|
$config_file_scan_dir .
|
||||||
$disable_jit .
|
$disable_jit .
|
||||||
@@ -156,25 +156,29 @@ class LinuxBuilder extends UnixBuilderBase
|
|||||||
|
|
||||||
$this->cleanMake();
|
$this->cleanMake();
|
||||||
|
|
||||||
if ($enable_cli) {
|
if ($enableCli) {
|
||||||
logger()->info('building cli');
|
logger()->info('building cli');
|
||||||
$this->buildCli();
|
$this->buildCli();
|
||||||
}
|
}
|
||||||
if ($enable_fpm) {
|
if ($enableFpm) {
|
||||||
logger()->info('building fpm');
|
logger()->info('building fpm');
|
||||||
$this->buildFpm();
|
$this->buildFpm();
|
||||||
}
|
}
|
||||||
if ($enable_micro) {
|
if ($enableMicro) {
|
||||||
logger()->info('building micro');
|
logger()->info('building micro');
|
||||||
$this->buildMicro();
|
$this->buildMicro();
|
||||||
}
|
}
|
||||||
if ($enable_embed) {
|
if ($enableEmbed) {
|
||||||
logger()->info('building embed');
|
logger()->info('building embed');
|
||||||
if ($enable_micro) {
|
if ($enableMicro) {
|
||||||
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la');
|
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la');
|
||||||
}
|
}
|
||||||
$this->buildEmbed();
|
$this->buildEmbed();
|
||||||
}
|
}
|
||||||
|
if ($enableFrankenphp) {
|
||||||
|
logger()->info('building frankenphp');
|
||||||
|
$this->buildFrankenphp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPHP(int $build_target = BUILD_TARGET_NONE)
|
public function testPHP(int $build_target = BUILD_TARGET_NONE)
|
||||||
@@ -293,6 +297,24 @@ class LinuxBuilder extends UnixBuilderBase
|
|||||||
$cwd = getcwd();
|
$cwd = getcwd();
|
||||||
chdir(BUILD_LIB_PATH);
|
chdir(BUILD_LIB_PATH);
|
||||||
symlink($realLibName, 'libphp.so');
|
symlink($realLibName, 'libphp.so');
|
||||||
|
chdir(BUILD_MODULES_PATH);
|
||||||
|
foreach ($this->getExts() as $ext) {
|
||||||
|
if (!$ext->isBuildShared()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = $ext->getName();
|
||||||
|
$versioned = "{$name}-{$release}.so";
|
||||||
|
$unversioned = "{$name}.so";
|
||||||
|
if (is_file(BUILD_MODULES_PATH . "/{$versioned}")) {
|
||||||
|
rename(BUILD_MODULES_PATH . "/{$versioned}", BUILD_MODULES_PATH . "/{$unversioned}");
|
||||||
|
shell()->cd(BUILD_MODULES_PATH)
|
||||||
|
->exec(sprintf(
|
||||||
|
'patchelf --set-soname %s %s',
|
||||||
|
escapeshellarg($unversioned),
|
||||||
|
escapeshellarg($unversioned)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
chdir($cwd);
|
chdir($cwd);
|
||||||
}
|
}
|
||||||
$this->patchPhpScripts();
|
$this->patchPhpScripts();
|
||||||
|
|||||||
12
src/SPC/builder/linux/library/watcher.php
Normal file
12
src/SPC/builder/linux/library/watcher.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SPC\builder\linux\library;
|
||||||
|
|
||||||
|
class watcher extends LinuxLibraryBase
|
||||||
|
{
|
||||||
|
use \SPC\builder\unix\library\watcher;
|
||||||
|
|
||||||
|
public const NAME = 'watcher';
|
||||||
|
}
|
||||||
@@ -122,6 +122,7 @@ class MacOSBuilder extends UnixBuilderBase
|
|||||||
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
|
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
|
||||||
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
||||||
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
||||||
|
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
|
||||||
|
|
||||||
// prepare build php envs
|
// prepare build php envs
|
||||||
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
|
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
|
||||||
@@ -180,9 +181,10 @@ class MacOSBuilder extends UnixBuilderBase
|
|||||||
}
|
}
|
||||||
$this->buildEmbed();
|
$this->buildEmbed();
|
||||||
}
|
}
|
||||||
|
if ($enableFrankenphp) {
|
||||||
$this->emitPatchPoint('before-sanity-check');
|
logger()->info('building frankenphp');
|
||||||
$this->sanityCheck($build_target);
|
$this->buildFrankenphp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPHP(int $build_target = BUILD_TARGET_NONE)
|
public function testPHP(int $build_target = BUILD_TARGET_NONE)
|
||||||
|
|||||||
12
src/SPC/builder/macos/library/watcher.php
Normal file
12
src/SPC/builder/macos/library/watcher.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SPC\builder\macos\library;
|
||||||
|
|
||||||
|
class watcher extends MacOSLibraryBase
|
||||||
|
{
|
||||||
|
use \SPC\builder\unix\library\watcher;
|
||||||
|
|
||||||
|
public const NAME = 'watcher';
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ use SPC\exception\FileSystemException;
|
|||||||
use SPC\exception\RuntimeException;
|
use SPC\exception\RuntimeException;
|
||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
|
use SPC\store\CurlHook;
|
||||||
|
use SPC\store\Downloader;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
use SPC\util\DependencyUtil;
|
use SPC\util\DependencyUtil;
|
||||||
use SPC\util\SPCConfigUtil;
|
use SPC\util\SPCConfigUtil;
|
||||||
@@ -218,6 +220,21 @@ abstract class UnixBuilderBase extends BuilderBase
|
|||||||
throw new RuntimeException('embed failed sanity check: run failed. Error message: ' . implode("\n", $output));
|
throw new RuntimeException('embed failed sanity check: run failed. Error message: ' . implode("\n", $output));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sanity check for frankenphp
|
||||||
|
if (($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) {
|
||||||
|
logger()->info('running frankenphp sanity check');
|
||||||
|
$frankenphp = BUILD_BIN_PATH . '/frankenphp';
|
||||||
|
if (!file_exists($frankenphp)) {
|
||||||
|
throw new RuntimeException('FrankenPHP binary not found: ' . $frankenphp);
|
||||||
|
}
|
||||||
|
[$ret, $output] = shell()
|
||||||
|
->setEnv(['LD_LIBRARY_PATH' => BUILD_LIB_PATH])
|
||||||
|
->execWithResult("{$frankenphp} version");
|
||||||
|
if ($ret !== 0 || !str_contains(implode('', $output), 'FrankenPHP')) {
|
||||||
|
throw new RuntimeException('FrankenPHP failed sanity check: ret[' . $ret . ']. out[' . implode('', $output) . ']');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -277,4 +294,70 @@ abstract class UnixBuilderBase extends BuilderBase
|
|||||||
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
|
FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws WrongUsageException
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
protected function buildFrankenphp(): void
|
||||||
|
{
|
||||||
|
$os = match (PHP_OS_FAMILY) {
|
||||||
|
'Linux' => 'linux',
|
||||||
|
'Windows' => 'win',
|
||||||
|
'Darwin' => 'macos',
|
||||||
|
'BSD' => 'freebsd',
|
||||||
|
default => throw new RuntimeException('Unsupported OS: ' . PHP_OS_FAMILY),
|
||||||
|
};
|
||||||
|
$arch = arch2gnu(php_uname('m'));
|
||||||
|
|
||||||
|
// define executables for go and xcaddy
|
||||||
|
$xcaddy_exec = PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin/xcaddy";
|
||||||
|
|
||||||
|
$nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : '';
|
||||||
|
$nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : '';
|
||||||
|
$xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES');
|
||||||
|
// make it possible to build from a different frankenphp directory!
|
||||||
|
if (!str_contains($xcaddyModules, '--with github.com/dunglas/frankenphp')) {
|
||||||
|
$xcaddyModules = '--with github.com/dunglas/frankenphp ' . $xcaddyModules;
|
||||||
|
}
|
||||||
|
if ($this->getLib('brotli') === null && str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) {
|
||||||
|
logger()->warning('caddy-cbrotli module is enabled, but brotli library is not built. Disabling caddy-cbrotli.');
|
||||||
|
$xcaddyModules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddyModules);
|
||||||
|
}
|
||||||
|
$lrt = PHP_OS_FAMILY === 'Linux' ? '-lrt' : '';
|
||||||
|
$releaseInfo = json_decode(Downloader::curlExec('https://api.github.com/repos/php/frankenphp/releases/latest', retries: 3, hooks: [[CurlHook::class, 'setupGithubToken']]), true);
|
||||||
|
$frankenPhpVersion = $releaseInfo['tag_name'];
|
||||||
|
$libphpVersion = $this->getPHPVersion();
|
||||||
|
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') {
|
||||||
|
$libphpVersion = preg_replace('/\.\d$/', '', $libphpVersion);
|
||||||
|
}
|
||||||
|
$debugFlags = $this->getOption('no-strip') ? "'-w -s' " : '';
|
||||||
|
$extLdFlags = "-extldflags '-pie'";
|
||||||
|
$muslTags = '';
|
||||||
|
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') {
|
||||||
|
$extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'";
|
||||||
|
$muslTags = 'static_build,';
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list, with_dependencies: true);
|
||||||
|
|
||||||
|
$env = [
|
||||||
|
'PATH' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin:" . getenv('PATH'),
|
||||||
|
'GOROOT' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}",
|
||||||
|
'GOBIN' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin",
|
||||||
|
'GOPATH' => PKG_ROOT_PATH . '/go',
|
||||||
|
'CGO_ENABLED' => '1',
|
||||||
|
'CGO_CFLAGS' => $config['cflags'],
|
||||||
|
'CGO_LDFLAGS' => "{$config['ldflags']} {$config['libs']} {$lrt}",
|
||||||
|
'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' .
|
||||||
|
'-ldflags \"-linkmode=external ' . $extLdFlags . ' ' . $debugFlags .
|
||||||
|
'-X \'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ' .
|
||||||
|
"{$frankenPhpVersion} PHP {$libphpVersion} Caddy'\\\" " .
|
||||||
|
"-tags={$muslTags}nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}",
|
||||||
|
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
|
||||||
|
];
|
||||||
|
shell()->cd(BUILD_BIN_PATH)
|
||||||
|
->setEnv($env)
|
||||||
|
->exec("{$xcaddy_exec} build --output frankenphp {$xcaddyModules}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/SPC/builder/unix/library/watcher.php
Normal file
28
src/SPC/builder/unix/library/watcher.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SPC\builder\unix\library;
|
||||||
|
|
||||||
|
use SPC\exception\FileSystemException;
|
||||||
|
use SPC\exception\RuntimeException;
|
||||||
|
use SPC\store\FileSystem;
|
||||||
|
|
||||||
|
trait watcher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @throws RuntimeException
|
||||||
|
* @throws FileSystemException
|
||||||
|
*/
|
||||||
|
protected function build(): void
|
||||||
|
{
|
||||||
|
shell()->cd($this->source_dir . '/watcher-c')
|
||||||
|
->initializeEnv($this)
|
||||||
|
->exec(getenv('CC') . ' -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra -fPIC')
|
||||||
|
->exec(getenv('AR') . ' rcs libwatcher-c.a libwatcher-c.o');
|
||||||
|
|
||||||
|
copy($this->source_dir . '/watcher-c/libwatcher-c.a', BUILD_LIB_PATH . '/libwatcher-c.a');
|
||||||
|
FileSystem::createDir(BUILD_INCLUDE_PATH . '/wtr');
|
||||||
|
copy($this->source_dir . '/watcher-c/include/wtr/watcher-c.h', BUILD_INCLUDE_PATH . '/wtr/watcher-c.h');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,8 @@ class BuildPHPCommand extends BuildCommand
|
|||||||
$this->addOption('build-micro', null, null, 'Build micro SAPI');
|
$this->addOption('build-micro', null, null, 'Build micro SAPI');
|
||||||
$this->addOption('build-cli', null, null, 'Build cli SAPI');
|
$this->addOption('build-cli', null, null, 'Build cli SAPI');
|
||||||
$this->addOption('build-fpm', null, null, 'Build fpm SAPI (not available on Windows)');
|
$this->addOption('build-fpm', null, null, 'Build fpm SAPI (not available on Windows)');
|
||||||
$this->addOption('build-embed', null, null, 'Build embed SAPI (not available on Windows)');
|
$this->addOption('build-embed', null, InputOption::VALUE_OPTIONAL, 'Build embed SAPI (not available on Windows)');
|
||||||
|
$this->addOption('build-frankenphp', null, null, 'Build FrankenPHP SAPI (not available on Windows)');
|
||||||
$this->addOption('build-all', null, null, 'Build all 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('no-strip', null, null, 'build without strip, in order to debug and load external extensions');
|
||||||
$this->addOption('disable-opcache-jit', null, null, 'disable opcache jit');
|
$this->addOption('disable-opcache-jit', null, null, 'disable opcache jit');
|
||||||
@@ -82,7 +83,8 @@ class BuildPHPCommand extends BuildCommand
|
|||||||
$this->output->writeln("<comment>\t--build-micro\tBuild phpmicro SAPI</comment>");
|
$this->output->writeln("<comment>\t--build-micro\tBuild phpmicro SAPI</comment>");
|
||||||
$this->output->writeln("<comment>\t--build-fpm\tBuild php-fpm SAPI</comment>");
|
$this->output->writeln("<comment>\t--build-fpm\tBuild php-fpm SAPI</comment>");
|
||||||
$this->output->writeln("<comment>\t--build-embed\tBuild embed SAPI/libphp</comment>");
|
$this->output->writeln("<comment>\t--build-embed\tBuild embed SAPI/libphp</comment>");
|
||||||
$this->output->writeln("<comment>\t--build-all\tBuild all SAPI: cli, micro, fpm, embed</comment>");
|
$this->output->writeln("<comment>\t--build-frankenphp\tBuild FrankenPHP SAPI/libphp</comment>");
|
||||||
|
$this->output->writeln("<comment>\t--build-all\tBuild all SAPI: cli, micro, fpm, embed, frankenphp</comment>");
|
||||||
return static::FAILURE;
|
return static::FAILURE;
|
||||||
}
|
}
|
||||||
if ($rule === BUILD_TARGET_ALL) {
|
if ($rule === BUILD_TARGET_ALL) {
|
||||||
@@ -158,14 +160,9 @@ class BuildPHPCommand extends BuildCommand
|
|||||||
if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
|
if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
|
||||||
$indent_texts['UPX Pack'] = 'enabled';
|
$indent_texts['UPX Pack'] = 'enabled';
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
$ver = $builder->getPHPVersion();
|
$ver = $builder->getPHPVersionFromArchive() ?: $builder->getPHPVersion();
|
||||||
$indent_texts['PHP Version'] = $ver;
|
$indent_texts['PHP Version'] = $ver;
|
||||||
} catch (\Throwable) {
|
|
||||||
if (($ver = $builder->getPHPVersionFromArchive()) !== false) {
|
|
||||||
$indent_texts['PHP Version'] = $ver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($not_included)) {
|
if (!empty($not_included)) {
|
||||||
$indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included);
|
$indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included);
|
||||||
@@ -183,6 +180,9 @@ class BuildPHPCommand extends BuildCommand
|
|||||||
// validate libs and extensions
|
// validate libs and extensions
|
||||||
$builder->validateLibsAndExts();
|
$builder->validateLibsAndExts();
|
||||||
|
|
||||||
|
// check some things before building all the things
|
||||||
|
$builder->checkBeforeBuildPHP($rule);
|
||||||
|
|
||||||
// clean builds and sources
|
// clean builds and sources
|
||||||
if ($this->input->getOption('with-clean')) {
|
if ($this->input->getOption('with-clean')) {
|
||||||
logger()->info('Cleaning source and previous build dir...');
|
logger()->info('Cleaning source and previous build dir...');
|
||||||
@@ -292,7 +292,18 @@ class BuildPHPCommand extends BuildCommand
|
|||||||
$rule |= ($this->getOption('build-cli') ? BUILD_TARGET_CLI : BUILD_TARGET_NONE);
|
$rule |= ($this->getOption('build-cli') ? BUILD_TARGET_CLI : BUILD_TARGET_NONE);
|
||||||
$rule |= ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE);
|
$rule |= ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE);
|
||||||
$rule |= ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : BUILD_TARGET_NONE);
|
$rule |= ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : BUILD_TARGET_NONE);
|
||||||
$rule |= ($this->getOption('build-embed') || !empty($shared_extensions) ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE);
|
$embed = $this->getOption('build-embed');
|
||||||
|
if (!$embed && !empty($shared_extensions)) {
|
||||||
|
$embed = true;
|
||||||
|
}
|
||||||
|
if ($embed) {
|
||||||
|
if ($embed === true) {
|
||||||
|
$embed = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
|
||||||
|
}
|
||||||
|
$rule |= BUILD_TARGET_EMBED;
|
||||||
|
f_putenv('SPC_CMD_VAR_PHP_EMBED_TYPE=' . ($embed === 'static' ? 'static' : 'shared'));
|
||||||
|
}
|
||||||
|
$rule |= ($this->getOption('build-frankenphp') ? (BUILD_TARGET_FRANKENPHP | BUILD_TARGET_EMBED) : BUILD_TARGET_NONE);
|
||||||
$rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE);
|
$rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE);
|
||||||
return $rule;
|
return $rule;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,15 @@ class CraftCommand extends BaseCommand
|
|||||||
return static::FAILURE;
|
return static::FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// install go and xcaddy for frankenphp
|
||||||
|
if (in_array('frankenphp', $craft['sapi'])) {
|
||||||
|
$retcode = $this->runCommand('install-pkg', 'go-xcaddy');
|
||||||
|
if ($retcode !== 0) {
|
||||||
|
$this->output->writeln('<error>craft go-xcaddy failed</error>');
|
||||||
|
$this->log("craft go-xcaddy failed with code: {$retcode}", true);
|
||||||
|
return static::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
// craft download
|
// craft download
|
||||||
if ($craft['craft-options']['download']) {
|
if ($craft['craft-options']['download']) {
|
||||||
$sharedAppend = $shared_extensions ? ',' . $shared_extensions : '';
|
$sharedAppend = $shared_extensions ? ',' . $shared_extensions : '';
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use SPC\exception\FileSystemException;
|
|||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Downloader;
|
use SPC\store\Downloader;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
@@ -46,29 +47,29 @@ class DeleteDownloadCommand extends BaseCommand
|
|||||||
return static::SUCCESS;
|
return static::SUCCESS;
|
||||||
}
|
}
|
||||||
$chosen_sources = $sources;
|
$chosen_sources = $sources;
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
|
||||||
|
|
||||||
$deleted_sources = [];
|
$deleted_sources = [];
|
||||||
foreach ($chosen_sources as $source) {
|
foreach ($chosen_sources as $source) {
|
||||||
$source = trim($source);
|
$source = trim($source);
|
||||||
foreach ([$source, Downloader::getPreBuiltLockName($source)] as $name) {
|
foreach ([$source, Downloader::getPreBuiltLockName($source)] as $name) {
|
||||||
if (isset($lock[$name])) {
|
if (LockFile::get($name)) {
|
||||||
$deleted_sources[] = $name;
|
$deleted_sources[] = $name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($deleted_sources as $lock_name) {
|
foreach ($deleted_sources as $lock_name) {
|
||||||
|
$lock = LockFile::get($lock_name);
|
||||||
// remove download file/dir if exists
|
// remove download file/dir if exists
|
||||||
if ($lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE) {
|
if ($lock['source_type'] === SPC_SOURCE_ARCHIVE) {
|
||||||
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']))) {
|
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['filename']))) {
|
||||||
logger()->info('Deleting file ' . $path);
|
logger()->info('Deleting file ' . $path);
|
||||||
unlink($path);
|
unlink($path);
|
||||||
} else {
|
} else {
|
||||||
logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file.");
|
logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname']))) {
|
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['dirname']))) {
|
||||||
logger()->info('Deleting dir ' . $path);
|
logger()->info('Deleting dir ' . $path);
|
||||||
FileSystem::removeDir($path);
|
FileSystem::removeDir($path);
|
||||||
} else {
|
} else {
|
||||||
@@ -76,9 +77,8 @@ class DeleteDownloadCommand extends BaseCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remove locked sources
|
// remove locked sources
|
||||||
unset($lock[$lock_name]);
|
LockFile::put($lock_name, null);
|
||||||
}
|
}
|
||||||
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
||||||
logger()->info('Delete success!');
|
logger()->info('Delete success!');
|
||||||
return static::SUCCESS;
|
return static::SUCCESS;
|
||||||
} catch (DownloaderException $e) {
|
} catch (DownloaderException $e) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use SPC\exception\RuntimeException;
|
|||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\Downloader;
|
use SPC\store\Downloader;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use SPC\util\DependencyUtil;
|
use SPC\util\DependencyUtil;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
@@ -301,7 +302,7 @@ class DownloadCommand extends BaseCommand
|
|||||||
throw new WrongUsageException('Windows currently does not support --from-zip !');
|
throw new WrongUsageException('Windows currently does not support --from-zip !');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
|
if (!file_exists(LockFile::LOCK_FILE)) {
|
||||||
throw new RuntimeException('.lock.json not exist in "downloads/"');
|
throw new RuntimeException('.lock.json not exist in "downloads/"');
|
||||||
}
|
}
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace SPC\command;
|
|||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\Downloader;
|
use SPC\store\Downloader;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
@@ -40,16 +41,9 @@ class SwitchPhpVersionCommand extends BaseCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// detect if downloads/.lock.json exists
|
if (LockFile::isLockFileExists('php-src')) {
|
||||||
$lock_file = DOWNLOAD_PATH . '/.lock.json';
|
|
||||||
// parse php-src part of lock file
|
|
||||||
$lock_data = json_decode(file_get_contents($lock_file), true);
|
|
||||||
// get php-src downloaded file name
|
|
||||||
$php_src = $lock_data['php-src'];
|
|
||||||
$file = DOWNLOAD_PATH . '/' . ($php_src['filename'] ?? '.donot.delete.me');
|
|
||||||
if (file_exists($file)) {
|
|
||||||
$this->output->writeln('<info>Removing old PHP source...</info>');
|
$this->output->writeln('<info>Removing old PHP source...</info>');
|
||||||
unlink($file);
|
LockFile::put('php-src', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download new PHP source
|
// Download new PHP source
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use SPC\exception\RuntimeException;
|
|||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use SPC\util\DependencyUtil;
|
use SPC\util\DependencyUtil;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
@@ -47,9 +48,8 @@ class PackLibCommand extends BuildCommand
|
|||||||
$lib->setup();
|
$lib->setup();
|
||||||
} else {
|
} else {
|
||||||
// Get lock info
|
// Get lock info
|
||||||
$lock = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
|
||||||
$source = Config::getLib($lib->getName(), 'source');
|
$source = Config::getLib($lib->getName(), 'source');
|
||||||
if (!isset($lock[$source]) || ($lock[$source]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) {
|
if (($lock = LockFile::get($source)) === null || ($lock['lock_as'] === SPC_DOWNLOAD_PRE_BUILT)) {
|
||||||
logger()->critical("The library {$lib->getName()} is downloaded as pre-built, we need to build it instead of installing pre-built.");
|
logger()->critical("The library {$lib->getName()} is downloaded as pre-built, we need to build it instead of installing pre-built.");
|
||||||
return static::FAILURE;
|
return static::FAILURE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class LinuxToolCheckList
|
|||||||
'bzip2', 'cmake', 'gcc',
|
'bzip2', 'cmake', 'gcc',
|
||||||
'g++', 'patch', 'binutils-gold',
|
'g++', 'patch', 'binutils-gold',
|
||||||
'libtoolize', 'which',
|
'libtoolize', 'which',
|
||||||
|
'patchelf',
|
||||||
];
|
];
|
||||||
|
|
||||||
public const TOOLS_DEBIAN = [
|
public const TOOLS_DEBIAN = [
|
||||||
@@ -30,6 +31,7 @@ class LinuxToolCheckList
|
|||||||
'tar', 'unzip', 'gzip',
|
'tar', 'unzip', 'gzip',
|
||||||
'bzip2', 'cmake', 'patch',
|
'bzip2', 'cmake', 'patch',
|
||||||
'xz', 'libtoolize', 'which',
|
'xz', 'libtoolize', 'which',
|
||||||
|
'patchelf',
|
||||||
];
|
];
|
||||||
|
|
||||||
public const TOOLS_RHEL = [
|
public const TOOLS_RHEL = [
|
||||||
@@ -38,6 +40,7 @@ class LinuxToolCheckList
|
|||||||
'tar', 'unzip', 'gzip', 'gcc',
|
'tar', 'unzip', 'gzip', 'gcc',
|
||||||
'bzip2', 'cmake', 'patch', 'which',
|
'bzip2', 'cmake', 'patch', 'which',
|
||||||
'xz', 'libtool', 'gettext-devel',
|
'xz', 'libtool', 'gettext-devel',
|
||||||
|
'perl', 'patchelf',
|
||||||
];
|
];
|
||||||
|
|
||||||
public const TOOLS_ARCH = [
|
public const TOOLS_ARCH = [
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use SPC\exception\DownloaderException;
|
|||||||
use SPC\exception\FileSystemException;
|
use SPC\exception\FileSystemException;
|
||||||
use SPC\exception\RuntimeException;
|
use SPC\exception\RuntimeException;
|
||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
|
use SPC\store\pkg\CustomPackage;
|
||||||
use SPC\store\source\CustomSourceBase;
|
use SPC\store\source\CustomSourceBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,34 +209,7 @@ class Downloader
|
|||||||
if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
|
if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
|
||||||
$name = self::getPreBuiltLockName($name);
|
$name = self::getPreBuiltLockName($name);
|
||||||
}
|
}
|
||||||
self::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
|
LockFile::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to lock source.
|
|
||||||
*
|
|
||||||
* @param string $name Source name
|
|
||||||
* @param array{
|
|
||||||
* source_type: string,
|
|
||||||
* dirname: ?string,
|
|
||||||
* filename: ?string,
|
|
||||||
* move_path: ?string,
|
|
||||||
* lock_as: int
|
|
||||||
* } $data Source data
|
|
||||||
* @throws FileSystemException
|
|
||||||
*/
|
|
||||||
public static function lockSource(string $name, array $data): void
|
|
||||||
{
|
|
||||||
if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.lock.json'))) {
|
|
||||||
$lock = [];
|
|
||||||
} else {
|
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
|
||||||
}
|
|
||||||
// calculate hash
|
|
||||||
$hash = self::getLockSourceHash($data);
|
|
||||||
$data['hash'] = $hash;
|
|
||||||
$lock[$name] = $data;
|
|
||||||
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -281,7 +255,7 @@ class Downloader
|
|||||||
}
|
}
|
||||||
// Lock
|
// Lock
|
||||||
logger()->debug("Locking git source {$name}");
|
logger()->debug("Locking git source {$name}");
|
||||||
self::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]);
|
LockFile::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// 复制目录过去
|
// 复制目录过去
|
||||||
@@ -377,7 +351,7 @@ class Downloader
|
|||||||
case 'local':
|
case 'local':
|
||||||
// Local directory, do nothing, just lock it
|
// Local directory, do nothing, just lock it
|
||||||
logger()->debug("Locking local source {$name}");
|
logger()->debug("Locking local source {$name}");
|
||||||
self::lockSource($name, [
|
LockFile::lockSource($name, [
|
||||||
'source_type' => SPC_SOURCE_LOCAL,
|
'source_type' => SPC_SOURCE_LOCAL,
|
||||||
'dirname' => $pkg['dirname'],
|
'dirname' => $pkg['dirname'],
|
||||||
'move_path' => $pkg['extract'] ?? null,
|
'move_path' => $pkg['extract'] ?? null,
|
||||||
@@ -385,10 +359,13 @@ class Downloader
|
|||||||
]);
|
]);
|
||||||
break;
|
break;
|
||||||
case 'custom': // Custom download method, like API-based download or other
|
case 'custom': // Custom download method, like API-based download or other
|
||||||
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source');
|
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg');
|
||||||
foreach ($classes as $class) {
|
foreach ($classes as $class) {
|
||||||
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
|
if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) {
|
||||||
(new $class())->fetch($force);
|
$cls = new $class();
|
||||||
|
if (in_array($name, $cls->getSupportName())) {
|
||||||
|
(new $class())->fetch($name, $force, $pkg);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,7 +470,7 @@ class Downloader
|
|||||||
case 'local':
|
case 'local':
|
||||||
// Local directory, do nothing, just lock it
|
// Local directory, do nothing, just lock it
|
||||||
logger()->debug("Locking local source {$name}");
|
logger()->debug("Locking local source {$name}");
|
||||||
self::lockSource($name, [
|
LockFile::lockSource($name, [
|
||||||
'source_type' => SPC_SOURCE_LOCAL,
|
'source_type' => SPC_SOURCE_LOCAL,
|
||||||
'dirname' => $source['dirname'],
|
'dirname' => $source['dirname'],
|
||||||
'move_path' => $source['extract'] ?? null,
|
'move_path' => $source['extract'] ?? null,
|
||||||
@@ -617,43 +594,6 @@ class Downloader
|
|||||||
return "{$source}-" . PHP_OS_FAMILY . '-' . getenv('GNU_ARCH') . '-' . (getenv('SPC_LIBC') ?: 'default') . '-' . (SystemUtil::getLibcVersionIfExists() ?? 'default');
|
return "{$source}-" . PHP_OS_FAMILY . '-' . getenv('GNU_ARCH') . '-' . (getenv('SPC_LIBC') ?: 'default') . '-' . (SystemUtil::getLibcVersionIfExists() ?? 'default');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the hash of the lock source based on the lock options.
|
|
||||||
*
|
|
||||||
* @param array $lock_options Lock options
|
|
||||||
* @return string Hash of the lock source
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public static function getLockSourceHash(array $lock_options): string
|
|
||||||
{
|
|
||||||
$result = match ($lock_options['source_type']) {
|
|
||||||
SPC_SOURCE_ARCHIVE => sha1_file(DOWNLOAD_PATH . '/' . $lock_options['filename']),
|
|
||||||
SPC_SOURCE_GIT => exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $lock_options['dirname']) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD'),
|
|
||||||
SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT',
|
|
||||||
default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new RuntimeException("Unknown source type: {$lock_options['source_type']}"),
|
|
||||||
};
|
|
||||||
if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) {
|
|
||||||
throw new RuntimeException("Failed to get hash for source: {$lock_options['source_type']}");
|
|
||||||
}
|
|
||||||
return $result ?: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $lock_options Lock options
|
|
||||||
* @param string $destination Target directory
|
|
||||||
* @throws FileSystemException
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public static function putLockSourceHash(array $lock_options, string $destination): void
|
|
||||||
{
|
|
||||||
$hash = self::getLockSourceHash($lock_options);
|
|
||||||
if ($lock_options['source_type'] === SPC_SOURCE_LOCAL) {
|
|
||||||
logger()->debug("Source [{$lock_options['dirname']}] is local, no hash will be written.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FileSystem::writeFile("{$destination}/.spc-hash", $hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register CTRL+C event for different OS.
|
* Register CTRL+C event for different OS.
|
||||||
*
|
*
|
||||||
@@ -689,33 +629,30 @@ class Downloader
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws FileSystemException
|
* @throws FileSystemException
|
||||||
|
* @throws WrongUsageException
|
||||||
*/
|
*/
|
||||||
private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool
|
private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool
|
||||||
{
|
{
|
||||||
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
|
// If the lock file exists, skip downloading for source mode
|
||||||
$lock = [];
|
$lock_item = LockFile::get($name);
|
||||||
} else {
|
if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && $lock_item !== null) {
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
|
||||||
}
|
logger()->notice("Source [{$name}] already downloaded: {$path}");
|
||||||
// If lock file exists, skip downloading for source mode
|
|
||||||
if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && isset($lock[$name])) {
|
|
||||||
if (
|
|
||||||
$lock[$name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename']) ||
|
|
||||||
$lock[$name]['source_type'] === SPC_SOURCE_GIT && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])
|
|
||||||
) {
|
|
||||||
logger()->notice("Source [{$name}] already downloaded: " . ($lock[$name]['filename'] ?? $lock[$name]['dirname']));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If lock file exists for current arch and glibc target, skip downloading
|
$lock_name = self::getPreBuiltLockName($name);
|
||||||
|
$lock_item = LockFile::get($lock_name);
|
||||||
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && isset($lock[$lock_name = self::getPreBuiltLockName($name)])) {
|
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && $lock_item !== null) {
|
||||||
// lock name with env
|
// lock name with env
|
||||||
if (
|
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
|
||||||
$lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']) ||
|
logger()->notice("Pre-built content [{$name}] already downloaded: {$path}");
|
||||||
$lock[$lock_name]['source_type'] === SPC_SOURCE_GIT && is_dir(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname'])
|
return true;
|
||||||
) {
|
}
|
||||||
logger()->notice("Pre-built content [{$name}] already downloaded: " . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname']));
|
}
|
||||||
|
if (!$force && $download_as === SPC_DOWNLOAD_PACKAGE && $lock_item !== null) {
|
||||||
|
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
|
||||||
|
logger()->notice("Source [{$name}] already downloaded: {$path}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
227
src/SPC/store/LockFile.php
Normal file
227
src/SPC/store/LockFile.php
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SPC\store;
|
||||||
|
|
||||||
|
use SPC\exception\FileSystemException;
|
||||||
|
use SPC\exception\RuntimeException;
|
||||||
|
use SPC\exception\WrongUsageException;
|
||||||
|
|
||||||
|
class LockFile
|
||||||
|
{
|
||||||
|
public const string LOCK_FILE = DOWNLOAD_PATH . '/.lock.json';
|
||||||
|
|
||||||
|
private static ?array $lock_file_content = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a lock entry by its name.
|
||||||
|
*
|
||||||
|
* @param string $lock_name Lock name to retrieve
|
||||||
|
* @return null|array{
|
||||||
|
* source_type: string,
|
||||||
|
* filename: ?string,
|
||||||
|
* dirname: ?string,
|
||||||
|
* move_path: ?string,
|
||||||
|
* lock_as: int,
|
||||||
|
* hash: string
|
||||||
|
* } Returns the lock entry as an associative array if it exists, or null if it does not
|
||||||
|
*/
|
||||||
|
public static function get(string $lock_name): ?array
|
||||||
|
{
|
||||||
|
self::init();
|
||||||
|
|
||||||
|
// Return the specific lock entry if it exists, otherwise return an empty array
|
||||||
|
$result = self::$lock_file_content[$lock_name] ?? null;
|
||||||
|
|
||||||
|
// Add old `dir` compatibility
|
||||||
|
if (($result['source_type'] ?? null) === 'dir') {
|
||||||
|
logger()->warning("Lock entry for '{$lock_name}' has 'source_type' set to 'dir', which is deprecated. Please re-download your dependencies.");
|
||||||
|
$result['source_type'] = SPC_SOURCE_GIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a lock file exists for a given lock name.
|
||||||
|
*
|
||||||
|
* @param string $lock_name Lock name to check
|
||||||
|
*/
|
||||||
|
public static function isLockFileExists(string $lock_name): bool
|
||||||
|
{
|
||||||
|
return match (self::get($lock_name)['source_type'] ?? null) {
|
||||||
|
SPC_SOURCE_ARCHIVE => file_exists(DOWNLOAD_PATH . '/' . (self::get($lock_name)['filename'] ?? '.never-exist-file')),
|
||||||
|
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => is_dir(DOWNLOAD_PATH . '/' . (self::get($lock_name)['dirname'] ?? '.never-exist-dir')),
|
||||||
|
default => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a lock entry into the lock file.
|
||||||
|
*
|
||||||
|
* @param string $lock_name Lock name to set or remove
|
||||||
|
* @param null|array $lock_content lock content to set, or null to remove the lock entry
|
||||||
|
* @throws FileSystemException
|
||||||
|
* @throws WrongUsageException
|
||||||
|
*/
|
||||||
|
public static function put(string $lock_name, ?array $lock_content): void
|
||||||
|
{
|
||||||
|
self::init();
|
||||||
|
|
||||||
|
if ($lock_content === null && isset(self::$lock_file_content[$lock_name])) {
|
||||||
|
self::removeLockFileIfExists(self::$lock_file_content[$lock_name]);
|
||||||
|
unset(self::$lock_file_content[$lock_name]);
|
||||||
|
} else {
|
||||||
|
self::$lock_file_content[$lock_name] = $lock_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the updated lock data back to the file
|
||||||
|
FileSystem::createDir(dirname(self::LOCK_FILE));
|
||||||
|
file_put_contents(self::LOCK_FILE, json_encode(self::$lock_file_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full path of a lock file or directory based on the lock options.
|
||||||
|
*
|
||||||
|
* @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname'
|
||||||
|
* @return string the absolute path to the lock file or directory
|
||||||
|
* @throws WrongUsageException
|
||||||
|
*/
|
||||||
|
public static function getLockFullPath(array $lock_options): string
|
||||||
|
{
|
||||||
|
return match ($lock_options['source_type']) {
|
||||||
|
SPC_SOURCE_ARCHIVE => FileSystem::isRelativePath($lock_options['filename']) ? (DOWNLOAD_PATH . '/' . $lock_options['filename']) : $lock_options['filename'],
|
||||||
|
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => FileSystem::isRelativePath($lock_options['dirname']) ? (DOWNLOAD_PATH . '/' . $lock_options['dirname']) : $lock_options['dirname'],
|
||||||
|
default => throw new WrongUsageException("Unknown source type: {$lock_options['source_type']}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getExtractPath(string $lock_name, string $default_path): ?string
|
||||||
|
{
|
||||||
|
$lock = self::get($lock_name);
|
||||||
|
if ($lock === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If move_path is set, use it; otherwise, use the default extract directory
|
||||||
|
if (isset($lock['move_path'])) {
|
||||||
|
if (FileSystem::isRelativePath($lock['move_path'])) {
|
||||||
|
// If move_path is relative, prepend the default extract directory
|
||||||
|
return match ($lock['lock_as']) {
|
||||||
|
SPC_DOWNLOAD_SOURCE, SPC_DOWNLOAD_PRE_BUILT => FileSystem::convertPath(SOURCE_PATH . '/' . $lock['move_path']),
|
||||||
|
SPC_DOWNLOAD_PACKAGE => FileSystem::convertPath(PKG_ROOT_PATH . '/' . $lock['move_path']),
|
||||||
|
default => throw new WrongUsageException("Unknown lock type: {$lock['lock_as']}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return FileSystem::convertPath($lock['move_path']);
|
||||||
|
}
|
||||||
|
return FileSystem::convertPath($default_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the hash of the lock source based on the lock options.
|
||||||
|
*
|
||||||
|
* @param array $lock_options Lock options
|
||||||
|
* @return string Hash of the lock source
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public static function getLockSourceHash(array $lock_options): string
|
||||||
|
{
|
||||||
|
$result = match ($lock_options['source_type']) {
|
||||||
|
SPC_SOURCE_ARCHIVE => sha1_file(DOWNLOAD_PATH . '/' . $lock_options['filename']),
|
||||||
|
SPC_SOURCE_GIT => exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $lock_options['dirname']) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD'),
|
||||||
|
SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT',
|
||||||
|
default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new RuntimeException("Unknown source type: {$lock_options['source_type']}"),
|
||||||
|
};
|
||||||
|
if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) {
|
||||||
|
throw new RuntimeException("Failed to get hash for source: {$lock_options['source_type']}");
|
||||||
|
}
|
||||||
|
return $result ?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $lock_options Lock options
|
||||||
|
* @param string $destination Target directory
|
||||||
|
* @throws FileSystemException
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public static function putLockSourceHash(array $lock_options, string $destination): void
|
||||||
|
{
|
||||||
|
$hash = LockFile::getLockSourceHash($lock_options);
|
||||||
|
if ($lock_options['source_type'] === SPC_SOURCE_LOCAL) {
|
||||||
|
logger()->debug("Source [{$lock_options['dirname']}] is local, no hash will be written.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FileSystem::writeFile("{$destination}/.spc-hash", $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to lock source with hash.
|
||||||
|
*
|
||||||
|
* @param string $name Source name
|
||||||
|
* @param array{
|
||||||
|
* source_type: string,
|
||||||
|
* dirname: ?string,
|
||||||
|
* filename: ?string,
|
||||||
|
* move_path: ?string,
|
||||||
|
* lock_as: int
|
||||||
|
* } $data Source data
|
||||||
|
* @throws FileSystemException
|
||||||
|
* @throws RuntimeException
|
||||||
|
* @throws WrongUsageException
|
||||||
|
*/
|
||||||
|
public static function lockSource(string $name, array $data): void
|
||||||
|
{
|
||||||
|
// calculate hash
|
||||||
|
$hash = LockFile::getLockSourceHash($data);
|
||||||
|
$data['hash'] = $hash;
|
||||||
|
self::put($name, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function init(): void
|
||||||
|
{
|
||||||
|
if (self::$lock_file_content === null) {
|
||||||
|
// Initialize the lock file content if it hasn't been loaded yet
|
||||||
|
if (!file_exists(self::LOCK_FILE)) {
|
||||||
|
logger()->debug('Lock file does not exist: ' . self::LOCK_FILE . ', initializing empty lock file.');
|
||||||
|
self::$lock_file_content = [];
|
||||||
|
file_put_contents(self::LOCK_FILE, json_encode(self::$lock_file_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||||
|
} else {
|
||||||
|
$file_content = file_get_contents(self::LOCK_FILE);
|
||||||
|
self::$lock_file_content = json_decode($file_content, true);
|
||||||
|
if (self::$lock_file_content === null) {
|
||||||
|
throw new \RuntimeException('Failed to decode lock file: ' . self::LOCK_FILE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the lock file or directory if it exists.
|
||||||
|
*
|
||||||
|
* @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname'
|
||||||
|
* @throws WrongUsageException
|
||||||
|
* @throws FileSystemException
|
||||||
|
*/
|
||||||
|
private static function removeLockFileIfExists(array $lock_options): void
|
||||||
|
{
|
||||||
|
if ($lock_options['source_type'] === SPC_SOURCE_ARCHIVE) {
|
||||||
|
$path = self::getLockFullPath($lock_options);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
logger()->info('Removing file ' . $path);
|
||||||
|
unlink($path);
|
||||||
|
} else {
|
||||||
|
logger()->debug("Lock file [{$lock_options['filename']}] not found, skip removing file.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$path = self::getLockFullPath($lock_options);
|
||||||
|
if (is_dir($path)) {
|
||||||
|
logger()->info('Removing directory ' . $path);
|
||||||
|
FileSystem::removeDir($path);
|
||||||
|
} else {
|
||||||
|
logger()->debug("Lock directory [{$lock_options['dirname']}] not found, skip removing directory.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ namespace SPC\store;
|
|||||||
|
|
||||||
use SPC\exception\FileSystemException;
|
use SPC\exception\FileSystemException;
|
||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
|
use SPC\store\pkg\CustomPackage;
|
||||||
|
|
||||||
class PackageManager
|
class PackageManager
|
||||||
{
|
{
|
||||||
@@ -32,11 +33,26 @@ class PackageManager
|
|||||||
|
|
||||||
// Download package
|
// Download package
|
||||||
Downloader::downloadPackage($pkg_name, $config, $force);
|
Downloader::downloadPackage($pkg_name, $config, $force);
|
||||||
|
if (Config::getPkg($pkg_name)['type'] === 'custom') {
|
||||||
|
// Custom extract function
|
||||||
|
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg');
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) {
|
||||||
|
$cls = new $class();
|
||||||
|
if (in_array($pkg_name, $cls->getSupportName())) {
|
||||||
|
(new $class())->extract($pkg_name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
// After download, read lock file name
|
// After download, read lock file name
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
|
$lock = LockFile::get($pkg_name);
|
||||||
$source_type = $lock[$pkg_name]['source_type'];
|
$source_type = $lock['source_type'];
|
||||||
$filename = DOWNLOAD_PATH . '/' . ($lock[$pkg_name]['filename'] ?? $lock[$pkg_name]['dirname']);
|
$filename = LockFile::getLockFullPath($lock);
|
||||||
$extract = $lock[$pkg_name]['move_path'] === null ? (PKG_ROOT_PATH . '/' . $pkg_name) : $lock[$pkg_name]['move_path'];
|
$extract = LockFile::getExtractPath($pkg_name, PKG_ROOT_PATH . '/' . $pkg_name);
|
||||||
|
|
||||||
FileSystem::extractPackage($pkg_name, $source_type, $filename, $extract);
|
FileSystem::extractPackage($pkg_name, $source_type, $filename, $extract);
|
||||||
|
|
||||||
// if contains extract-files, we just move this file to destination, and remove extract dir
|
// if contains extract-files, we just move this file to destination, and remove extract dir
|
||||||
|
|||||||
@@ -17,11 +17,6 @@ class SourceManager
|
|||||||
*/
|
*/
|
||||||
public static function initSource(?array $sources = null, ?array $libs = null, ?array $exts = null, bool $source_only = false): void
|
public static function initSource(?array $sources = null, ?array $libs = null, ?array $exts = null, bool $source_only = false): 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 = [];
|
$sources_extracted = [];
|
||||||
// source check exist
|
// source check exist
|
||||||
if (is_array($sources)) {
|
if (is_array($sources)) {
|
||||||
@@ -56,8 +51,8 @@ class SourceManager
|
|||||||
}
|
}
|
||||||
// check source downloaded
|
// check source downloaded
|
||||||
$pre_built_name = Downloader::getPreBuiltLockName($source);
|
$pre_built_name = Downloader::getPreBuiltLockName($source);
|
||||||
if ($source_only || !isset($lock[$pre_built_name])) {
|
if ($source_only || LockFile::get($pre_built_name) === null) {
|
||||||
if (!isset($lock[$source])) {
|
if (LockFile::get($source) === null) {
|
||||||
throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !");
|
throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !");
|
||||||
}
|
}
|
||||||
$lock_name = $source;
|
$lock_name = $source;
|
||||||
@@ -65,20 +60,23 @@ class SourceManager
|
|||||||
$lock_name = $pre_built_name;
|
$lock_name = $pre_built_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$lock_content = LockFile::get($lock_name);
|
||||||
|
|
||||||
// check source dir exist
|
// check source dir exist
|
||||||
$check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']);
|
$check = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source);
|
||||||
|
// $check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']);
|
||||||
if (!is_dir($check)) {
|
if (!is_dir($check)) {
|
||||||
logger()->debug('Extracting source [' . $source . '] to ' . $check . ' ...');
|
logger()->debug('Extracting source [' . $source . '] to ' . $check . ' ...');
|
||||||
$filename = self::getSourceFullPath($lock[$lock_name]);
|
$filename = LockFile::getLockFullPath($lock_content);
|
||||||
FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']);
|
FileSystem::extractSource($source, $lock_content['source_type'], $filename, $check);
|
||||||
Downloader::putLockSourceHash($lock[$lock_name], $check);
|
LockFile::putLockSourceHash($lock_content, $check);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// if a lock file does not have hash, calculate with the current source (backward compatibility)
|
// if a lock file does not have hash, calculate with the current source (backward compatibility)
|
||||||
if (!isset($lock[$lock_name]['hash'])) {
|
if (!isset($lock_content['hash'])) {
|
||||||
$hash = Downloader::getLockSourceHash($lock[$lock_name]);
|
$hash = LockFile::getLockSourceHash($lock_content);
|
||||||
} else {
|
} else {
|
||||||
$hash = $lock[$lock_name]['hash'];
|
$hash = $lock_content['hash'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// when source already extracted, detect if the extracted source hash is the same as the lock file one
|
// when source already extracted, detect if the extracted source hash is the same as the lock file one
|
||||||
@@ -90,18 +88,10 @@ class SourceManager
|
|||||||
// if not, remove the source dir and extract again
|
// if not, remove the source dir and extract again
|
||||||
logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ...");
|
logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ...");
|
||||||
FileSystem::removeDir($check);
|
FileSystem::removeDir($check);
|
||||||
$filename = self::getSourceFullPath($lock[$lock_name]);
|
$filename = LockFile::getLockFullPath($lock_content);
|
||||||
FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']);
|
$move_path = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source);
|
||||||
Downloader::putLockSourceHash($lock[$lock_name], $check);
|
FileSystem::extractSource($source, $lock_content['source_type'], $filename, $move_path);
|
||||||
|
LockFile::putLockSourceHash($lock_content, $check);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function getSourceFullPath(array $lock_options): string
|
|
||||||
{
|
|
||||||
return match ($lock_options['source_type']) {
|
|
||||||
SPC_SOURCE_ARCHIVE => FileSystem::isRelativePath($lock_options['filename']) ? (DOWNLOAD_PATH . '/' . $lock_options['filename']) : $lock_options['filename'],
|
|
||||||
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => FileSystem::isRelativePath($lock_options['dirname']) ? (DOWNLOAD_PATH . '/' . $lock_options['dirname']) : $lock_options['dirname'],
|
|
||||||
default => throw new WrongUsageException("Unknown source type: {$lock_options['source_type']}"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ class SourcePatcher
|
|||||||
|
|
||||||
public static function patchFfiCentos7FixO3strncmp(): bool
|
public static function patchFfiCentos7FixO3strncmp(): bool
|
||||||
{
|
{
|
||||||
if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::getLibcVersionIfExists() >= '2.17') {
|
if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::getLibcVersionIfExists() > '2.17') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) {
|
if (!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) {
|
||||||
|
|||||||
17
src/SPC/store/pkg/CustomPackage.php
Normal file
17
src/SPC/store/pkg/CustomPackage.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SPC\store\pkg;
|
||||||
|
|
||||||
|
abstract class CustomPackage
|
||||||
|
{
|
||||||
|
abstract public function getSupportName(): array;
|
||||||
|
|
||||||
|
abstract public function fetch(string $name, bool $force = false, ?array $config = null): void;
|
||||||
|
|
||||||
|
public function extract(string $name): void
|
||||||
|
{
|
||||||
|
throw new \RuntimeException("Extract method not implemented for package: {$name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/SPC/store/pkg/GoXcaddy.php
Normal file
76
src/SPC/store/pkg/GoXcaddy.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SPC\store\pkg;
|
||||||
|
|
||||||
|
use SPC\store\Downloader;
|
||||||
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
|
use SPC\util\GlobalEnvManager;
|
||||||
|
|
||||||
|
class GoXcaddy extends CustomPackage
|
||||||
|
{
|
||||||
|
public function getSupportName(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'go-xcaddy-x86_64-linux',
|
||||||
|
'go-xcaddy-x86_64-macos',
|
||||||
|
'go-xcaddy-aarch64-linux',
|
||||||
|
'go-xcaddy-aarch64-macos',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetch(string $name, bool $force = false, ?array $config = null): void
|
||||||
|
{
|
||||||
|
$pkgroot = PKG_ROOT_PATH;
|
||||||
|
$go_exec = "{$pkgroot}/{$name}/bin/go";
|
||||||
|
$xcaddy_exec = "{$pkgroot}/{$name}/bin/xcaddy";
|
||||||
|
if (file_exists($go_exec) && file_exists($xcaddy_exec)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$arch = match (explode('-', $name)[2]) {
|
||||||
|
'x86_64' => 'amd64',
|
||||||
|
'aarch64' => 'arm64',
|
||||||
|
default => throw new \InvalidArgumentException('Unsupported architecture: ' . $name),
|
||||||
|
};
|
||||||
|
$os = match (explode('-', $name)[3]) {
|
||||||
|
'linux' => 'linux',
|
||||||
|
'macos' => 'darwin',
|
||||||
|
default => throw new \InvalidArgumentException('Unsupported OS: ' . $name),
|
||||||
|
};
|
||||||
|
$go_version = '1.24.4';
|
||||||
|
$config = [
|
||||||
|
'type' => 'url',
|
||||||
|
'url' => "https://go.dev/dl/go{$go_version}.{$os}-{$arch}.tar.gz",
|
||||||
|
];
|
||||||
|
Downloader::downloadPackage($name, $config, $force);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extract(string $name): void
|
||||||
|
{
|
||||||
|
$pkgroot = PKG_ROOT_PATH;
|
||||||
|
$go_exec = "{$pkgroot}/{$name}/bin/go";
|
||||||
|
$xcaddy_exec = "{$pkgroot}/{$name}/bin/xcaddy";
|
||||||
|
if (file_exists($go_exec) && file_exists($xcaddy_exec)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true);
|
||||||
|
$source_type = $lock[$name]['source_type'];
|
||||||
|
$filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']);
|
||||||
|
$extract = $lock[$name]['move_path'] === null ? "{$pkgroot}/{$name}" : $lock[$name]['move_path'];
|
||||||
|
|
||||||
|
FileSystem::extractPackage($name, $source_type, $filename, $extract);
|
||||||
|
|
||||||
|
GlobalEnvManager::init();
|
||||||
|
// install xcaddy
|
||||||
|
shell()
|
||||||
|
->appendEnv([
|
||||||
|
'PATH' => "{$pkgroot}/{$name}/bin:" . getenv('PATH'),
|
||||||
|
'GOROOT' => "{$pkgroot}/{$name}",
|
||||||
|
'GOBIN' => "{$pkgroot}/{$name}/bin",
|
||||||
|
'GOPATH' => "{$pkgroot}/go",
|
||||||
|
])
|
||||||
|
->exec("{$go_exec} install github.com/caddyserver/xcaddy/cmd/xcaddy@latest");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,14 +45,17 @@ class GlobalEnvManager
|
|||||||
// Define env vars for linux
|
// Define env vars for linux
|
||||||
if (PHP_OS_FAMILY === 'Linux') {
|
if (PHP_OS_FAMILY === 'Linux') {
|
||||||
$arch = getenv('GNU_ARCH');
|
$arch = getenv('GNU_ARCH');
|
||||||
if (SystemUtil::isMuslDist()) {
|
if (SystemUtil::isMuslDist() || getenv('SPC_LIBC') === 'glibc') {
|
||||||
self::putenv('SPC_LINUX_DEFAULT_CC=gcc');
|
self::putenv('SPC_LINUX_DEFAULT_CC=gcc');
|
||||||
self::putenv('SPC_LINUX_DEFAULT_CXX=g++');
|
self::putenv('SPC_LINUX_DEFAULT_CXX=g++');
|
||||||
self::putenv('SPC_LINUX_DEFAULT_AR=ar');
|
self::putenv('SPC_LINUX_DEFAULT_AR=ar');
|
||||||
|
self::putenv('SPC_LINUX_DEFAULT_LD=ld.gold');
|
||||||
} else {
|
} else {
|
||||||
self::putenv("SPC_LINUX_DEFAULT_CC={$arch}-linux-musl-gcc");
|
self::putenv("SPC_LINUX_DEFAULT_CC={$arch}-linux-musl-gcc");
|
||||||
self::putenv("SPC_LINUX_DEFAULT_CXX={$arch}-linux-musl-g++");
|
self::putenv("SPC_LINUX_DEFAULT_CXX={$arch}-linux-musl-g++");
|
||||||
self::putenv("SPC_LINUX_DEFAULT_AR={$arch}-linux-musl-ar");
|
self::putenv("SPC_LINUX_DEFAULT_AR={$arch}-linux-musl-ar");
|
||||||
|
self::putenv("SPC_LINUX_DEFAULT_LD={$arch}-linux-musl-ld");
|
||||||
|
GlobalEnvManager::putenv("PATH=/usr/local/musl/bin:/usr/local/musl/{$arch}-linux-musl/bin:" . getenv('PATH'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,14 +44,7 @@ class UnixShell
|
|||||||
{
|
{
|
||||||
/* @phpstan-ignore-next-line */
|
/* @phpstan-ignore-next-line */
|
||||||
logger()->info(ConsoleColor::yellow('[EXEC] ') . ConsoleColor::green($cmd));
|
logger()->info(ConsoleColor::yellow('[EXEC] ') . ConsoleColor::green($cmd));
|
||||||
logger()->debug('Executed at: ' . debug_backtrace()[0]['file'] . ':' . debug_backtrace()[0]['line']);
|
$cmd = $this->getExecString($cmd);
|
||||||
$env_str = $this->getEnvString();
|
|
||||||
if (!empty($env_str)) {
|
|
||||||
$cmd = "{$env_str} {$cmd}";
|
|
||||||
}
|
|
||||||
if ($this->cd !== null) {
|
|
||||||
$cmd = 'cd ' . escapeshellarg($this->cd) . ' && ' . $cmd;
|
|
||||||
}
|
|
||||||
if (!$this->debug) {
|
if (!$this->debug) {
|
||||||
$cmd .= ' 1>/dev/null 2>&1';
|
$cmd .= ' 1>/dev/null 2>&1';
|
||||||
}
|
}
|
||||||
@@ -99,10 +92,7 @@ class UnixShell
|
|||||||
/* @phpstan-ignore-next-line */
|
/* @phpstan-ignore-next-line */
|
||||||
logger()->debug(ConsoleColor::blue('[EXEC] ') . ConsoleColor::gray($cmd));
|
logger()->debug(ConsoleColor::blue('[EXEC] ') . ConsoleColor::gray($cmd));
|
||||||
}
|
}
|
||||||
logger()->debug('Executed at: ' . debug_backtrace()[0]['file'] . ':' . debug_backtrace()[0]['line']);
|
$cmd = $this->getExecString($cmd);
|
||||||
if ($this->cd !== null) {
|
|
||||||
$cmd = 'cd ' . escapeshellarg($this->cd) . ' && ' . $cmd;
|
|
||||||
}
|
|
||||||
exec($cmd, $out, $code);
|
exec($cmd, $out, $code);
|
||||||
return [$code, $out];
|
return [$code, $out];
|
||||||
}
|
}
|
||||||
@@ -126,4 +116,17 @@ class UnixShell
|
|||||||
}
|
}
|
||||||
return trim($str);
|
return trim($str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getExecString(string $cmd): string
|
||||||
|
{
|
||||||
|
logger()->debug('Executed at: ' . debug_backtrace()[0]['file'] . ':' . debug_backtrace()[0]['line']);
|
||||||
|
$env_str = $this->getEnvString();
|
||||||
|
if (!empty($env_str)) {
|
||||||
|
$cmd = "{$env_str} {$cmd}";
|
||||||
|
}
|
||||||
|
if ($this->cd !== null) {
|
||||||
|
$cmd = 'cd ' . escapeshellarg($this->cd) . ' && ' . $cmd;
|
||||||
|
}
|
||||||
|
return $cmd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ const BUILD_TARGET_CLI = 1; // build cli
|
|||||||
const BUILD_TARGET_MICRO = 2; // build micro
|
const BUILD_TARGET_MICRO = 2; // build micro
|
||||||
const BUILD_TARGET_FPM = 4; // build fpm
|
const BUILD_TARGET_FPM = 4; // build fpm
|
||||||
const BUILD_TARGET_EMBED = 8; // build embed
|
const BUILD_TARGET_EMBED = 8; // build embed
|
||||||
const BUILD_TARGET_ALL = 15; // build all
|
const BUILD_TARGET_FRANKENPHP = 16; // build frankenphp
|
||||||
|
const BUILD_TARGET_ALL = BUILD_TARGET_CLI | BUILD_TARGET_MICRO | BUILD_TARGET_FPM | BUILD_TARGET_EMBED | BUILD_TARGET_FRANKENPHP; // build all
|
||||||
|
|
||||||
// doctor error fix policy
|
// doctor error fix policy
|
||||||
const FIX_POLICY_DIE = 1; // die directly
|
const FIX_POLICY_DIE = 1; // die directly
|
||||||
|
|||||||
@@ -102,6 +102,17 @@ function osfamily2dir(): string
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function osfamily2shortname(): string
|
||||||
|
{
|
||||||
|
return match (PHP_OS_FAMILY) {
|
||||||
|
'Windows' => 'win',
|
||||||
|
'Darwin' => 'macos',
|
||||||
|
'Linux' => 'linux',
|
||||||
|
'BSD' => 'bsd',
|
||||||
|
default => throw new WrongUsageException('Not support os: ' . PHP_OS_FAMILY),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function shell(?bool $debug = null): UnixShell
|
function shell(?bool $debug = null): UnixShell
|
||||||
{
|
{
|
||||||
/* @noinspection PhpUnhandledExceptionInspection */
|
/* @noinspection PhpUnhandledExceptionInspection */
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ $test_os = [
|
|||||||
'macos-13',
|
'macos-13',
|
||||||
// 'macos-14',
|
// 'macos-14',
|
||||||
'macos-15',
|
'macos-15',
|
||||||
// 'ubuntu-latest',
|
'ubuntu-latest',
|
||||||
'ubuntu-22.04',
|
'ubuntu-22.04',
|
||||||
'ubuntu-24.04',
|
'ubuntu-24.04',
|
||||||
'ubuntu-22.04-arm',
|
'ubuntu-22.04-arm',
|
||||||
@@ -40,12 +40,15 @@ $no_strip = false;
|
|||||||
// compress with upx
|
// compress with upx
|
||||||
$upx = false;
|
$upx = false;
|
||||||
|
|
||||||
|
// whether to test frankenphp build, only available for macos and linux
|
||||||
|
$frankenphp = true;
|
||||||
|
|
||||||
// prefer downloading pre-built packages to speed up the build process
|
// prefer downloading pre-built packages to speed up the build process
|
||||||
$prefer_pre_built = false;
|
$prefer_pre_built = false;
|
||||||
|
|
||||||
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
|
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
|
||||||
$extensions = match (PHP_OS_FAMILY) {
|
$extensions = match (PHP_OS_FAMILY) {
|
||||||
'Linux', 'Darwin' => 'dom,mongodb',
|
'Linux', 'Darwin' => 'apcu,ast,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,iconv,libxml,mbregex,mbstring,opcache,openssl,pcntl,phar,posix,readline,session,simplexml,sockets,sodium,tokenizer,xml,xmlreader,xmlwriter,zip,zlib',
|
||||||
'Windows' => 'xlswriter,openssl',
|
'Windows' => 'xlswriter,openssl',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -208,7 +211,13 @@ switch ($argv[1] ?? null) {
|
|||||||
passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode);
|
passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode);
|
||||||
break;
|
break;
|
||||||
case 'build_embed_cmd':
|
case 'build_embed_cmd':
|
||||||
passthru($prefix . $build_cmd . (str_starts_with($argv[2], 'windows-') ? ' --build-cli' : ' --build-embed'), $retcode);
|
if ($frankenphp) {
|
||||||
|
passthru("{$prefix}install-pkg go-xcaddy --debug", $retcode);
|
||||||
|
if ($retcode !== 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
passthru($prefix . $build_cmd . (str_starts_with($argv[2], 'windows-') ? ' --build-cli' : (' --build-embed' . ($frankenphp ? ' --build-frankenphp' : ''))), $retcode);
|
||||||
break;
|
break;
|
||||||
case 'doctor_cmd':
|
case 'doctor_cmd':
|
||||||
passthru($prefix . $doctor_cmd, $retcode);
|
passthru($prefix . $doctor_cmd, $retcode);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use SPC\builder\LibraryBase;
|
|||||||
use SPC\exception\RuntimeException;
|
use SPC\exception\RuntimeException;
|
||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use SPC\util\CustomExt;
|
use SPC\util\CustomExt;
|
||||||
use SPC\util\DependencyUtil;
|
use SPC\util\DependencyUtil;
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
@@ -117,7 +118,7 @@ class BuilderTest extends TestCase
|
|||||||
|
|
||||||
public function testGetPHPVersionFromArchive()
|
public function testGetPHPVersionFromArchive()
|
||||||
{
|
{
|
||||||
$lock = file_exists(DOWNLOAD_PATH . '/.lock.json') ? file_get_contents(DOWNLOAD_PATH . '/.lock.json') : false;
|
$lock = file_exists(LockFile::LOCK_FILE) ? file_get_contents(LockFile::LOCK_FILE) : false;
|
||||||
if ($lock === false) {
|
if ($lock === false) {
|
||||||
$this->assertFalse($this->builder->getPHPVersionFromArchive());
|
$this->assertFalse($this->builder->getPHPVersionFromArchive());
|
||||||
} else {
|
} else {
|
||||||
@@ -161,7 +162,8 @@ class BuilderTest extends TestCase
|
|||||||
[BUILD_TARGET_FPM, 'fpm'],
|
[BUILD_TARGET_FPM, 'fpm'],
|
||||||
[BUILD_TARGET_MICRO, 'micro'],
|
[BUILD_TARGET_MICRO, 'micro'],
|
||||||
[BUILD_TARGET_EMBED, 'embed'],
|
[BUILD_TARGET_EMBED, 'embed'],
|
||||||
[BUILD_TARGET_ALL, 'cli, micro, fpm, embed'],
|
[BUILD_TARGET_FRANKENPHP, 'frankenphp'],
|
||||||
|
[BUILD_TARGET_ALL, 'cli, micro, fpm, embed, frankenphp'],
|
||||||
[BUILD_TARGET_CLI | BUILD_TARGET_EMBED, 'cli, embed'],
|
[BUILD_TARGET_CLI | BUILD_TARGET_EMBED, 'cli, embed'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace SPC\Tests\store;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Downloader;
|
use SPC\store\Downloader;
|
||||||
|
use SPC\store\LockFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@@ -57,9 +58,9 @@ class DownloaderTest extends TestCase
|
|||||||
|
|
||||||
public function testLockSource()
|
public function testLockSource()
|
||||||
{
|
{
|
||||||
Downloader::lockSource('fake-file', ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']);
|
LockFile::lockSource('fake-file', ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']);
|
||||||
$this->assertFileExists(DOWNLOAD_PATH . '/.lock.json');
|
$this->assertFileExists(LockFile::LOCK_FILE);
|
||||||
$json = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true);
|
$json = json_decode(file_get_contents(LockFile::LOCK_FILE), true);
|
||||||
$this->assertIsArray($json);
|
$this->assertIsArray($json);
|
||||||
$this->assertArrayHasKey('fake-file', $json);
|
$this->assertArrayHasKey('fake-file', $json);
|
||||||
$this->assertArrayHasKey('source_type', $json['fake-file']);
|
$this->assertArrayHasKey('source_type', $json['fake-file']);
|
||||||
|
|||||||
Reference in New Issue
Block a user