mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-19 13:24:51 +08:00
Merge pull request #767 from crazywhalecc/docs
patchelf and rename -release tagged extensions from php configure build
This commit is contained in:
commit
a2f0640b1b
@ -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` 来获取编译时的配置。
|
||||
|
||||
另外,有关如何使用此功能的高级示例,请查看[如何使用它构建 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 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
|
||||
|
||||
|
||||
@ -84,7 +84,8 @@ RUN apk update; \
|
||||
wget \
|
||||
xz \
|
||||
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 && \
|
||||
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 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 && \
|
||||
mkdir /cmake && \
|
||||
tar -xzf cmake.tgz -C /cmake --strip-components 1
|
||||
@ -89,7 +94,7 @@ ENV PATH="/app/bin:/cmake/bin:$PATH"
|
||||
ENV SPC_LIBC=glibc
|
||||
|
||||
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 && \
|
||||
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 '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 '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_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
|
||||
|
||||
@ -42,6 +42,9 @@ SPC_CONCURRENCY=${CPU_COUNT}
|
||||
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")
|
||||
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
|
||||
; only useful for builds targeting not pure-static linking
|
||||
; default paths
|
||||
@ -66,7 +69,7 @@ SPC_LIBC=musl
|
||||
CC=${SPC_LINUX_DEFAULT_CC}
|
||||
CXX=${SPC_LINUX_DEFAULT_CXX}
|
||||
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
|
||||
SPC_DEFAULT_C_FLAGS="-fPIC -Os"
|
||||
SPC_DEFAULT_CXX_FLAGS="-fPIC -Os"
|
||||
|
||||
@ -854,5 +854,14 @@
|
||||
"zstd.h",
|
||||
"zstd_errors.h"
|
||||
]
|
||||
},
|
||||
"watcher": {
|
||||
"source": "watcher",
|
||||
"static-libs-unix": [
|
||||
"libwatcher-c.a"
|
||||
],
|
||||
"headers": [
|
||||
"wtr/watcher-c.h"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,5 +42,17 @@
|
||||
"extract-files": {
|
||||
"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",
|
||||
"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`.
|
||||
- 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).
|
||||
- frankenphp: The build result is `buildroot/bin/frankenphp`.
|
||||
|
||||
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.
|
||||
@ -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-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-frankenphp`: Build a [FrankenPHP](https://github.com/php/frankenphp) executable
|
||||
- `--build-all`: build all above sapi
|
||||
|
||||
```bash
|
||||
@ -509,6 +511,8 @@ When `bin/spc doctor` automatically repairs the Windows environment, tools such
|
||||
Here is an example of installing the tool:
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
@ -145,6 +145,7 @@ bin/spc craft --debug
|
||||
- fpm: 构建结果为 `buildroot/bin/php-fpm`。
|
||||
- micro: 构建结果为 `buildroot/bin/micro.sfx`,如需进一步与 PHP 代码打包,请查看 [打包 micro 二进制](./manual-build#命令-micro-combine-打包-micro-二进制)。
|
||||
- embed: 参见 [embed 使用](./manual-build#embed-使用)。
|
||||
- frankenphp: 构建结果为 `buildroot/bin/frankenphp`。
|
||||
|
||||
如果中途构建出错,你可以使用 `--debug` 参数查看详细的错误信息,或者使用 `--with-clean` 参数清除旧的编译结果,重新编译。
|
||||
|
||||
@ -250,6 +251,7 @@ bin/spc doctor --auto-fix
|
||||
- `--build-fpm`: 构建一个 fpm sapi(php-fpm,用于和其他传统的 fpm 架构的软件如 nginx 配合使用)
|
||||
- `--build-micro`: 构建一个 micro sapi(用于构建一个包含 PHP 代码的独立可执行二进制)
|
||||
- `--build-embed`: 构建一个 embed sapi(用于嵌入到其他 C 语言程序中)
|
||||
- `--build-frankenphp`: 构建一个 [frankenphp](https://github.com/php/frankenphp) 二进制
|
||||
- `--build-all`: 构建以上所有 sapi
|
||||
|
||||
```bash
|
||||
@ -457,6 +459,8 @@ bin/spc dev:sort-config ext
|
||||
下面是安装工具的示例:
|
||||
|
||||
- 下载安装 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 - 删除已下载的资源
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ use Symfony\Component\Console\Application;
|
||||
*/
|
||||
final class ConsoleApplication extends Application
|
||||
{
|
||||
public const VERSION = '2.6.0';
|
||||
public const VERSION = '2.6.1';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
@ -12,6 +12,7 @@ use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\LockFile;
|
||||
use SPC\store\SourceManager;
|
||||
use SPC\util\CustomExt;
|
||||
|
||||
@ -262,17 +263,6 @@ abstract class BuilderBase
|
||||
if (!$ext->isBuildShared()) {
|
||||
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();
|
||||
}
|
||||
} catch (RuntimeException $e) {
|
||||
@ -361,15 +351,11 @@ abstract class BuilderBase
|
||||
public function getPHPVersionFromArchive(?string $file = null): false|string
|
||||
{
|
||||
if ($file === null) {
|
||||
$lock = file_exists(DOWNLOAD_PATH . '/.lock.json') ? file_get_contents(DOWNLOAD_PATH . '/.lock.json') : false;
|
||||
if ($lock === false) {
|
||||
return false;
|
||||
}
|
||||
$lock = json_decode($lock, true);
|
||||
$file = $lock['php-src']['filename'] ?? null;
|
||||
if ($file === null) {
|
||||
$lock = LockFile::get('php-src');
|
||||
if ($lock === null) {
|
||||
return false;
|
||||
}
|
||||
$file = LockFile::getLockFullPath($lock);
|
||||
}
|
||||
if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) {
|
||||
return $match[1];
|
||||
@ -415,6 +401,9 @@ abstract class BuilderBase
|
||||
if (($type & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED) {
|
||||
$ls[] = 'embed';
|
||||
}
|
||||
if (($type & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) {
|
||||
$ls[] = 'frankenphp';
|
||||
}
|
||||
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.
|
||||
*/
|
||||
|
||||
@ -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 check failed, throw RuntimeException
|
||||
$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) {
|
||||
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')) {
|
||||
@ -328,6 +331,17 @@ class Extension
|
||||
*/
|
||||
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)');
|
||||
foreach ($this->dependencies as $dependency) {
|
||||
if (!$dependency instanceof Extension) {
|
||||
@ -357,11 +371,16 @@ class Extension
|
||||
{
|
||||
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
|
||||
[$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 = [
|
||||
'CFLAGS' => $config['cflags'],
|
||||
'CXXFLAGS' => $config['cflags'],
|
||||
'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,
|
||||
];
|
||||
|
||||
@ -382,6 +401,7 @@ class Extension
|
||||
'--enable-shared --disable-static'
|
||||
);
|
||||
|
||||
// some extensions don't define their dependencies well, this patch is only needed for a few
|
||||
FileSystem::replaceFileRegex(
|
||||
$this->source_dir . '/Makefile',
|
||||
'/^(.*_SHARED_LIBADD\s*=.*)$/m',
|
||||
@ -468,7 +488,7 @@ class Extension
|
||||
*
|
||||
* @return array [staticLibString, sharedLibString]
|
||||
*/
|
||||
private function getStaticAndSharedLibs(): array
|
||||
protected function getStaticAndSharedLibs(): array
|
||||
{
|
||||
$config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true);
|
||||
$sharedLibString = '';
|
||||
@ -483,7 +503,7 @@ class Extension
|
||||
continue;
|
||||
}
|
||||
$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 . ' ')) {
|
||||
$staticLibString .= '-l' . $lib . ' ';
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\LockFile;
|
||||
use SPC\store\SourceManager;
|
||||
use SPC\util\GlobalValueTrait;
|
||||
|
||||
@ -46,12 +47,11 @@ abstract class LibraryBase
|
||||
*/
|
||||
public function setup(bool $force = false): int
|
||||
{
|
||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
||||
$source = Config::getLib(static::NAME, 'source');
|
||||
// if source is locked as pre-built, we just tryInstall it
|
||||
$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) {
|
||||
return $this->tryInstall($lock[$pre_built_name], $force);
|
||||
if (($lock = LockFile::get($pre_built_name)) && $lock['lock_as'] === SPC_DOWNLOAD_PRE_BUILT) {
|
||||
return $this->tryInstall($lock, $force);
|
||||
}
|
||||
return $this->tryBuild($force);
|
||||
}
|
||||
@ -215,6 +215,19 @@ abstract class LibraryBase
|
||||
*/
|
||||
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')) {
|
||||
$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';
|
||||
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;
|
||||
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
||||
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
||||
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
|
||||
|
||||
shell()->cd(SOURCE_PATH . '/php-src')
|
||||
->exec(
|
||||
@ -143,6 +144,10 @@ class BSDBuilder extends UnixBuilderBase
|
||||
}
|
||||
$this->buildEmbed();
|
||||
}
|
||||
if ($enableFrankenphp) {
|
||||
logger()->info('building frankenphp');
|
||||
$this->buildFrankenphp();
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
$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\"");
|
||||
GlobalEnvManager::putenv("PATH=/usr/local/musl/bin:/usr/local/musl/{$arch}-linux-musl/bin:" . getenv('PATH'));
|
||||
$configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE');
|
||||
$configure = "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\" " . $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) ?
|
||||
('--with-config-file-scan-dir=' . $this->getOption('with-config-file-scan-dir') . ' ') : '';
|
||||
|
||||
$enable_cli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
|
||||
$enable_fpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
|
||||
$enable_micro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
||||
$enable_embed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
||||
$enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI;
|
||||
$enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM;
|
||||
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
||||
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
||||
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
|
||||
|
||||
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
|
||||
// prepare build php envs
|
||||
@ -125,7 +125,7 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
]);
|
||||
|
||||
// process micro upx patch if micro sapi enabled
|
||||
if ($enable_micro) {
|
||||
if ($enableMicro) {
|
||||
if (version_compare($this->getMicroVersion(), '0.2.0') < 0) {
|
||||
// for phpmicro 0.1.x
|
||||
$this->processMicroUPXLegacy();
|
||||
@ -137,10 +137,10 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
shell()->cd(SOURCE_PATH . '/php-src')
|
||||
->exec(
|
||||
getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
|
||||
($enable_cli ? '--enable-cli ' : '--disable-cli ') .
|
||||
($enable_fpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') .
|
||||
($enable_embed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
|
||||
($enable_micro ? '--enable-micro=all-static ' : '--disable-micro ') .
|
||||
($enableCli ? '--enable-cli ' : '--disable-cli ') .
|
||||
($enableFpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') .
|
||||
($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') .
|
||||
($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') .
|
||||
$config_file_path .
|
||||
$config_file_scan_dir .
|
||||
$disable_jit .
|
||||
@ -156,25 +156,29 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
|
||||
$this->cleanMake();
|
||||
|
||||
if ($enable_cli) {
|
||||
if ($enableCli) {
|
||||
logger()->info('building cli');
|
||||
$this->buildCli();
|
||||
}
|
||||
if ($enable_fpm) {
|
||||
if ($enableFpm) {
|
||||
logger()->info('building fpm');
|
||||
$this->buildFpm();
|
||||
}
|
||||
if ($enable_micro) {
|
||||
if ($enableMicro) {
|
||||
logger()->info('building micro');
|
||||
$this->buildMicro();
|
||||
}
|
||||
if ($enable_embed) {
|
||||
if ($enableEmbed) {
|
||||
logger()->info('building embed');
|
||||
if ($enable_micro) {
|
||||
if ($enableMicro) {
|
||||
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la');
|
||||
}
|
||||
$this->buildEmbed();
|
||||
}
|
||||
if ($enableFrankenphp) {
|
||||
logger()->info('building frankenphp');
|
||||
$this->buildFrankenphp();
|
||||
}
|
||||
}
|
||||
|
||||
public function testPHP(int $build_target = BUILD_TARGET_NONE)
|
||||
@ -293,6 +297,24 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
$cwd = getcwd();
|
||||
chdir(BUILD_LIB_PATH);
|
||||
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);
|
||||
}
|
||||
$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;
|
||||
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
||||
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
||||
$enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP;
|
||||
|
||||
// prepare build php envs
|
||||
$mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : '';
|
||||
@ -180,9 +181,10 @@ class MacOSBuilder extends UnixBuilderBase
|
||||
}
|
||||
$this->buildEmbed();
|
||||
}
|
||||
|
||||
$this->emitPatchPoint('before-sanity-check');
|
||||
$this->sanityCheck($build_target);
|
||||
if ($enableFrankenphp) {
|
||||
logger()->info('building frankenphp');
|
||||
$this->buildFrankenphp();
|
||||
}
|
||||
}
|
||||
|
||||
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\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\CurlHook;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\util\DependencyUtil;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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-cli', null, null, 'Build cli SAPI');
|
||||
$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('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');
|
||||
@ -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-fpm\tBuild php-fpm SAPI</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;
|
||||
}
|
||||
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'])) {
|
||||
$indent_texts['UPX Pack'] = 'enabled';
|
||||
}
|
||||
try {
|
||||
$ver = $builder->getPHPVersion();
|
||||
$indent_texts['PHP Version'] = $ver;
|
||||
} catch (\Throwable) {
|
||||
if (($ver = $builder->getPHPVersionFromArchive()) !== false) {
|
||||
$indent_texts['PHP Version'] = $ver;
|
||||
}
|
||||
}
|
||||
|
||||
$ver = $builder->getPHPVersionFromArchive() ?: $builder->getPHPVersion();
|
||||
$indent_texts['PHP Version'] = $ver;
|
||||
|
||||
if (!empty($not_included)) {
|
||||
$indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included);
|
||||
@ -183,6 +180,9 @@ class BuildPHPCommand extends BuildCommand
|
||||
// validate libs and extensions
|
||||
$builder->validateLibsAndExts();
|
||||
|
||||
// check some things before building all the things
|
||||
$builder->checkBeforeBuildPHP($rule);
|
||||
|
||||
// clean builds and sources
|
||||
if ($this->input->getOption('with-clean')) {
|
||||
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-micro') ? BUILD_TARGET_MICRO : 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);
|
||||
return $rule;
|
||||
}
|
||||
|
||||
@ -66,6 +66,15 @@ class CraftCommand extends BaseCommand
|
||||
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
|
||||
if ($craft['craft-options']['download']) {
|
||||
$sharedAppend = $shared_extensions ? ',' . $shared_extensions : '';
|
||||
|
||||
@ -9,6 +9,7 @@ use SPC\exception\FileSystemException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\LockFile;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
@ -46,29 +47,29 @@ class DeleteDownloadCommand extends BaseCommand
|
||||
return static::SUCCESS;
|
||||
}
|
||||
$chosen_sources = $sources;
|
||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
||||
|
||||
$deleted_sources = [];
|
||||
foreach ($chosen_sources as $source) {
|
||||
$source = trim($source);
|
||||
foreach ([$source, Downloader::getPreBuiltLockName($source)] as $name) {
|
||||
if (isset($lock[$name])) {
|
||||
if (LockFile::get($name)) {
|
||||
$deleted_sources[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($deleted_sources as $lock_name) {
|
||||
$lock = LockFile::get($lock_name);
|
||||
// remove download file/dir if exists
|
||||
if ($lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE) {
|
||||
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']))) {
|
||||
if ($lock['source_type'] === SPC_SOURCE_ARCHIVE) {
|
||||
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['filename']))) {
|
||||
logger()->info('Deleting file ' . $path);
|
||||
unlink($path);
|
||||
} else {
|
||||
logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file.");
|
||||
}
|
||||
} 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);
|
||||
FileSystem::removeDir($path);
|
||||
} else {
|
||||
@ -76,9 +77,8 @@ class DeleteDownloadCommand extends BaseCommand
|
||||
}
|
||||
}
|
||||
// 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!');
|
||||
return static::SUCCESS;
|
||||
} catch (DownloaderException $e) {
|
||||
|
||||
@ -12,6 +12,7 @@ use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\store\LockFile;
|
||||
use SPC\util\DependencyUtil;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@ -301,7 +302,7 @@ class DownloadCommand extends BaseCommand
|
||||
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/"');
|
||||
}
|
||||
} catch (RuntimeException $e) {
|
||||
|
||||
@ -7,6 +7,7 @@ namespace SPC\command;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\LockFile;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@ -40,16 +41,9 @@ class SwitchPhpVersionCommand extends BaseCommand
|
||||
}
|
||||
}
|
||||
|
||||
// detect if downloads/.lock.json exists
|
||||
$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)) {
|
||||
if (LockFile::isLockFileExists('php-src')) {
|
||||
$this->output->writeln('<info>Removing old PHP source...</info>');
|
||||
unlink($file);
|
||||
LockFile::put('php-src', null);
|
||||
}
|
||||
|
||||
// Download new PHP source
|
||||
|
||||
@ -14,6 +14,7 @@ use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\LockFile;
|
||||
use SPC\util\DependencyUtil;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@ -47,9 +48,8 @@ class PackLibCommand extends BuildCommand
|
||||
$lib->setup();
|
||||
} else {
|
||||
// Get lock info
|
||||
$lock = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
||||
$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.");
|
||||
return static::FAILURE;
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ class LinuxToolCheckList
|
||||
'bzip2', 'cmake', 'gcc',
|
||||
'g++', 'patch', 'binutils-gold',
|
||||
'libtoolize', 'which',
|
||||
'patchelf',
|
||||
];
|
||||
|
||||
public const TOOLS_DEBIAN = [
|
||||
@ -30,6 +31,7 @@ class LinuxToolCheckList
|
||||
'tar', 'unzip', 'gzip',
|
||||
'bzip2', 'cmake', 'patch',
|
||||
'xz', 'libtoolize', 'which',
|
||||
'patchelf',
|
||||
];
|
||||
|
||||
public const TOOLS_RHEL = [
|
||||
@ -38,6 +40,7 @@ class LinuxToolCheckList
|
||||
'tar', 'unzip', 'gzip', 'gcc',
|
||||
'bzip2', 'cmake', 'patch', 'which',
|
||||
'xz', 'libtool', 'gettext-devel',
|
||||
'perl', 'patchelf',
|
||||
];
|
||||
|
||||
public const TOOLS_ARCH = [
|
||||
|
||||
@ -9,6 +9,7 @@ use SPC\exception\DownloaderException;
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\pkg\CustomPackage;
|
||||
use SPC\store\source\CustomSourceBase;
|
||||
|
||||
/**
|
||||
@ -208,34 +209,7 @@ class Downloader
|
||||
if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
|
||||
$name = self::getPreBuiltLockName($name);
|
||||
}
|
||||
self::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));
|
||||
LockFile::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -281,7 +255,7 @@ class Downloader
|
||||
}
|
||||
// Lock
|
||||
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':
|
||||
// Local directory, do nothing, just lock it
|
||||
logger()->debug("Locking local source {$name}");
|
||||
self::lockSource($name, [
|
||||
LockFile::lockSource($name, [
|
||||
'source_type' => SPC_SOURCE_LOCAL,
|
||||
'dirname' => $pkg['dirname'],
|
||||
'move_path' => $pkg['extract'] ?? null,
|
||||
@ -385,10 +359,13 @@ class Downloader
|
||||
]);
|
||||
break;
|
||||
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) {
|
||||
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
|
||||
(new $class())->fetch($force);
|
||||
if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) {
|
||||
$cls = new $class();
|
||||
if (in_array($name, $cls->getSupportName())) {
|
||||
(new $class())->fetch($name, $force, $pkg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -493,7 +470,7 @@ class Downloader
|
||||
case 'local':
|
||||
// Local directory, do nothing, just lock it
|
||||
logger()->debug("Locking local source {$name}");
|
||||
self::lockSource($name, [
|
||||
LockFile::lockSource($name, [
|
||||
'source_type' => SPC_SOURCE_LOCAL,
|
||||
'dirname' => $source['dirname'],
|
||||
'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');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -689,33 +629,30 @@ class Downloader
|
||||
|
||||
/**
|
||||
* @throws FileSystemException
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool
|
||||
{
|
||||
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
|
||||
$lock = [];
|
||||
} else {
|
||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
||||
}
|
||||
// If lock file exists, skip downloading 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']));
|
||||
// If the lock file exists, skip downloading for source mode
|
||||
$lock_item = LockFile::get($name);
|
||||
if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && $lock_item !== null) {
|
||||
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
|
||||
logger()->notice("Source [{$name}] already downloaded: {$path}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If lock file exists for current arch and glibc target, skip downloading
|
||||
|
||||
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && isset($lock[$lock_name = self::getPreBuiltLockName($name)])) {
|
||||
$lock_name = self::getPreBuiltLockName($name);
|
||||
$lock_item = LockFile::get($lock_name);
|
||||
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && $lock_item !== null) {
|
||||
// lock name with env
|
||||
if (
|
||||
$lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']) ||
|
||||
$lock[$lock_name]['source_type'] === SPC_SOURCE_GIT && is_dir(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname'])
|
||||
) {
|
||||
logger()->notice("Pre-built content [{$name}] already downloaded: " . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname']));
|
||||
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
|
||||
logger()->notice("Pre-built content [{$name}] already downloaded: {$path}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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\WrongUsageException;
|
||||
use SPC\store\pkg\CustomPackage;
|
||||
|
||||
class PackageManager
|
||||
{
|
||||
@ -32,11 +33,26 @@ class PackageManager
|
||||
|
||||
// Download package
|
||||
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
|
||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
|
||||
$source_type = $lock[$pkg_name]['source_type'];
|
||||
$filename = DOWNLOAD_PATH . '/' . ($lock[$pkg_name]['filename'] ?? $lock[$pkg_name]['dirname']);
|
||||
$extract = $lock[$pkg_name]['move_path'] === null ? (PKG_ROOT_PATH . '/' . $pkg_name) : $lock[$pkg_name]['move_path'];
|
||||
$lock = LockFile::get($pkg_name);
|
||||
$source_type = $lock['source_type'];
|
||||
$filename = LockFile::getLockFullPath($lock);
|
||||
$extract = LockFile::getExtractPath($pkg_name, PKG_ROOT_PATH . '/' . $pkg_name);
|
||||
|
||||
FileSystem::extractPackage($pkg_name, $source_type, $filename, $extract);
|
||||
|
||||
// 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
|
||||
{
|
||||
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
|
||||
throw new WrongUsageException('Download lock file "downloads/.lock.json" not found, maybe you need to download sources first ?');
|
||||
}
|
||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
|
||||
|
||||
$sources_extracted = [];
|
||||
// source check exist
|
||||
if (is_array($sources)) {
|
||||
@ -56,8 +51,8 @@ class SourceManager
|
||||
}
|
||||
// check source downloaded
|
||||
$pre_built_name = Downloader::getPreBuiltLockName($source);
|
||||
if ($source_only || !isset($lock[$pre_built_name])) {
|
||||
if (!isset($lock[$source])) {
|
||||
if ($source_only || LockFile::get($pre_built_name) === null) {
|
||||
if (LockFile::get($source) === null) {
|
||||
throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !");
|
||||
}
|
||||
$lock_name = $source;
|
||||
@ -65,20 +60,23 @@ class SourceManager
|
||||
$lock_name = $pre_built_name;
|
||||
}
|
||||
|
||||
$lock_content = LockFile::get($lock_name);
|
||||
|
||||
// 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)) {
|
||||
logger()->debug('Extracting source [' . $source . '] to ' . $check . ' ...');
|
||||
$filename = self::getSourceFullPath($lock[$lock_name]);
|
||||
FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']);
|
||||
Downloader::putLockSourceHash($lock[$lock_name], $check);
|
||||
$filename = LockFile::getLockFullPath($lock_content);
|
||||
FileSystem::extractSource($source, $lock_content['source_type'], $filename, $check);
|
||||
LockFile::putLockSourceHash($lock_content, $check);
|
||||
continue;
|
||||
}
|
||||
// if a lock file does not have hash, calculate with the current source (backward compatibility)
|
||||
if (!isset($lock[$lock_name]['hash'])) {
|
||||
$hash = Downloader::getLockSourceHash($lock[$lock_name]);
|
||||
if (!isset($lock_content['hash'])) {
|
||||
$hash = LockFile::getLockSourceHash($lock_content);
|
||||
} 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
|
||||
@ -90,18 +88,10 @@ class SourceManager
|
||||
// if not, remove the source dir and extract again
|
||||
logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ...");
|
||||
FileSystem::removeDir($check);
|
||||
$filename = self::getSourceFullPath($lock[$lock_name]);
|
||||
FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']);
|
||||
Downloader::putLockSourceHash($lock[$lock_name], $check);
|
||||
$filename = LockFile::getLockFullPath($lock_content);
|
||||
$move_path = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source);
|
||||
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
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::getLibcVersionIfExists() >= '2.17') {
|
||||
if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::getLibcVersionIfExists() > '2.17') {
|
||||
return false;
|
||||
}
|
||||
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
|
||||
if (PHP_OS_FAMILY === 'Linux') {
|
||||
$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_CXX=g++');
|
||||
self::putenv('SPC_LINUX_DEFAULT_AR=ar');
|
||||
self::putenv('SPC_LINUX_DEFAULT_LD=ld.gold');
|
||||
} else {
|
||||
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_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 */
|
||||
logger()->info(ConsoleColor::yellow('[EXEC] ') . ConsoleColor::green($cmd));
|
||||
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;
|
||||
}
|
||||
$cmd = $this->getExecString($cmd);
|
||||
if (!$this->debug) {
|
||||
$cmd .= ' 1>/dev/null 2>&1';
|
||||
}
|
||||
@ -99,10 +92,7 @@ class UnixShell
|
||||
/* @phpstan-ignore-next-line */
|
||||
logger()->debug(ConsoleColor::blue('[EXEC] ') . ConsoleColor::gray($cmd));
|
||||
}
|
||||
logger()->debug('Executed at: ' . debug_backtrace()[0]['file'] . ':' . debug_backtrace()[0]['line']);
|
||||
if ($this->cd !== null) {
|
||||
$cmd = 'cd ' . escapeshellarg($this->cd) . ' && ' . $cmd;
|
||||
}
|
||||
$cmd = $this->getExecString($cmd);
|
||||
exec($cmd, $out, $code);
|
||||
return [$code, $out];
|
||||
}
|
||||
@ -126,4 +116,17 @@ class UnixShell
|
||||
}
|
||||
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_FPM = 4; // build fpm
|
||||
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
|
||||
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
|
||||
{
|
||||
/* @noinspection PhpUnhandledExceptionInspection */
|
||||
|
||||
@ -24,7 +24,7 @@ $test_os = [
|
||||
'macos-13',
|
||||
// 'macos-14',
|
||||
'macos-15',
|
||||
// 'ubuntu-latest',
|
||||
'ubuntu-latest',
|
||||
'ubuntu-22.04',
|
||||
'ubuntu-24.04',
|
||||
'ubuntu-22.04-arm',
|
||||
@ -40,12 +40,15 @@ $no_strip = false;
|
||||
// compress with upx
|
||||
$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_pre_built = false;
|
||||
|
||||
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
|
||||
$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',
|
||||
};
|
||||
|
||||
@ -208,7 +211,13 @@ switch ($argv[1] ?? null) {
|
||||
passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode);
|
||||
break;
|
||||
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;
|
||||
case 'doctor_cmd':
|
||||
passthru($prefix . $doctor_cmd, $retcode);
|
||||
|
||||
@ -13,6 +13,7 @@ use SPC\builder\LibraryBase;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\LockFile;
|
||||
use SPC\util\CustomExt;
|
||||
use SPC\util\DependencyUtil;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
@ -117,7 +118,7 @@ class BuilderTest extends TestCase
|
||||
|
||||
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) {
|
||||
$this->assertFalse($this->builder->getPHPVersionFromArchive());
|
||||
} else {
|
||||
@ -161,7 +162,8 @@ class BuilderTest extends TestCase
|
||||
[BUILD_TARGET_FPM, 'fpm'],
|
||||
[BUILD_TARGET_MICRO, 'micro'],
|
||||
[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'],
|
||||
];
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ namespace SPC\Tests\store;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\store\LockFile;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -57,9 +58,9 @@ class DownloaderTest extends TestCase
|
||||
|
||||
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']);
|
||||
$this->assertFileExists(DOWNLOAD_PATH . '/.lock.json');
|
||||
$json = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true);
|
||||
LockFile::lockSource('fake-file', ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']);
|
||||
$this->assertFileExists(LockFile::LOCK_FILE);
|
||||
$json = json_decode(file_get_contents(LockFile::LOCK_FILE), true);
|
||||
$this->assertIsArray($json);
|
||||
$this->assertArrayHasKey('fake-file', $json);
|
||||
$this->assertArrayHasKey('source_type', $json['fake-file']);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user