mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-02 22:35:43 +08:00
Compare commits
25 Commits
2.1.0-beta
...
2.1.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
097ecd3fb0 | ||
|
|
b9359759dd | ||
|
|
50fe366c42 | ||
|
|
9dd89e6b02 | ||
|
|
c03220d1ee | ||
|
|
9db843ab66 | ||
|
|
304973d9bc | ||
|
|
ab386f820c | ||
|
|
4adf1f5e2e | ||
|
|
b4ae87585c | ||
|
|
ae3298472d | ||
|
|
d241cb993e | ||
|
|
8376122634 | ||
|
|
a30e054d7d | ||
|
|
78f4317660 | ||
|
|
3ac3dab6c8 | ||
|
|
1a7e436ee1 | ||
|
|
f11b36ab3c | ||
|
|
e2ef195a84 | ||
|
|
faed569e8a | ||
|
|
49ddb3ec13 | ||
|
|
71c0387ab0 | ||
|
|
e5d2d5e689 | ||
|
|
227bf73870 | ||
|
|
05e3898e7a |
2
.github/workflows/release-build.yml
vendored
2
.github/workflows/release-build.yml
vendored
@@ -104,7 +104,7 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
files: ${{ env.filename }}
|
||||
files: dist/${{ env.filename }}
|
||||
|
||||
- name: "Deploy to Self-Hosted Server"
|
||||
if: github.repository == 'crazywhalecc/static-php-cli'
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,6 +16,9 @@ docker/source/
|
||||
# default source build root directory
|
||||
/buildroot/
|
||||
|
||||
# default package root directory
|
||||
/pkgroot/
|
||||
|
||||
# tools cache files
|
||||
.php-cs-fixer.cache
|
||||
.phpunit.result.cache
|
||||
|
||||
@@ -25,6 +25,7 @@ static-php-cli(简称 `spc`)有许多特性:
|
||||
- :books: 自带编译依赖管理
|
||||
- 📦 提供由自身编译的独立 `spc` 二进制(使用 spc 和 [box](https://github.com/box-project/box) 构建)
|
||||
- :fire: 支持大量 [扩展](https://static-php.dev/zh/guide/extensions.html)
|
||||
- :floppy_disk: 整合 UPX 工具(减小二进制文件体积)
|
||||
|
||||
**静态 php-cli:**
|
||||
|
||||
@@ -248,7 +249,7 @@ bin/spc micro:combine my-app.phar -I "memory_limit=4G" -I "disable_functions=sys
|
||||
|
||||
## 赞助本项目
|
||||
|
||||
你可以在 [我的个人赞助页](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md) 支持我和我的项目。
|
||||
你可以在 [我的个人赞助页](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md) 支持我和我的项目。你捐赠的一部分将会被用于维护 **static-php.dev** 服务器。
|
||||
|
||||
## 开源协议
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ static-php-cli (you can call it `spc`) has a lot of features:
|
||||
- :books: Build dependency management
|
||||
- 📦 Provide `spc` own standalone executable (built by spc and [box](https://github.com/box-project/box))
|
||||
- :fire: Support many popular [extensions](https://static-php.dev/en/guide/extensions.html)
|
||||
- :floppy_disk: UPX integration (significantly reduces binary size)
|
||||
|
||||
**Single-file standalone php-cli:**
|
||||
|
||||
@@ -272,7 +273,7 @@ Now there is a [static-php](https://github.com/static-php) organization, which i
|
||||
|
||||
## Sponsor this project
|
||||
|
||||
You can sponsor my project on [this page](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md).
|
||||
You can sponsor my project on [this page](https://github.com/crazywhalecc/crazywhalecc/blob/master/FUNDING.md). A portion of your donation will be used to maintain the **static-php.dev** server.
|
||||
|
||||
## Open-Source License
|
||||
|
||||
|
||||
@@ -93,6 +93,13 @@
|
||||
"freetype"
|
||||
]
|
||||
},
|
||||
"gettext": {
|
||||
"type": "builtin",
|
||||
"arg-type": "with-prefix",
|
||||
"lib-depends": [
|
||||
"gettext"
|
||||
]
|
||||
},
|
||||
"glfw": {
|
||||
"type": "external",
|
||||
"arg-type": "custom",
|
||||
|
||||
@@ -83,6 +83,22 @@
|
||||
"brotli"
|
||||
]
|
||||
},
|
||||
"gettext": {
|
||||
"source": "gettext",
|
||||
"static-libs-unix": [
|
||||
"libintl.a"
|
||||
],
|
||||
"lib-depends": [
|
||||
"libiconv"
|
||||
],
|
||||
"lib-suggests": [
|
||||
"ncurses",
|
||||
"libxml2"
|
||||
],
|
||||
"frameworks": [
|
||||
"CoreFoundation"
|
||||
]
|
||||
},
|
||||
"glfw": {
|
||||
"source": "ext-glfw",
|
||||
"static-libs-unix": [
|
||||
|
||||
46
config/pkg.json
Normal file
46
config/pkg.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"musl-toolchain-aarch64-linux": {
|
||||
"type": "url",
|
||||
"url": "https://dl.static-php.dev/static-php-cli/deps/musl-toolchain/aarch64-musl-toolchain.tgz"
|
||||
},
|
||||
"musl-toolchain-x86_64-linux": {
|
||||
"type": "url",
|
||||
"url": "https://dl.static-php.dev/static-php-cli/deps/musl-toolchain/x86_64-musl-toolchain.tgz"
|
||||
},
|
||||
"nasm-x86_64-win": {
|
||||
"type": "url",
|
||||
"url": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip",
|
||||
"extract-files": {
|
||||
"nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe",
|
||||
"nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"
|
||||
}
|
||||
},
|
||||
"strawberry-perl-x86_64-win": {
|
||||
"type": "url",
|
||||
"url": "https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip"
|
||||
},
|
||||
"upx-aarch64-linux": {
|
||||
"type": "ghrel",
|
||||
"repo": "upx/upx",
|
||||
"match": "upx.+-arm64_linux\\.tar\\.xz",
|
||||
"extract-files": {
|
||||
"upx": "{pkg_root_path}/bin/upx"
|
||||
}
|
||||
},
|
||||
"upx-x86_64-linux": {
|
||||
"type": "ghrel",
|
||||
"repo": "upx/upx",
|
||||
"match": "upx.+-amd64_linux\\.tar\\.xz",
|
||||
"extract-files": {
|
||||
"upx": "{pkg_root_path}/bin/upx"
|
||||
}
|
||||
},
|
||||
"upx-x86_64-win": {
|
||||
"type": "ghrel",
|
||||
"repo": "upx/upx",
|
||||
"match": "upx.+-win64\\.zip",
|
||||
"extract-files": {
|
||||
"upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,6 +129,15 @@
|
||||
"path": "LICENSE.TXT"
|
||||
}
|
||||
},
|
||||
"gettext": {
|
||||
"type": "filelist",
|
||||
"url": "https://ftp.gnu.org/pub/gnu/gettext/",
|
||||
"regex": "/href=\"(?<file>gettext-(?<version>[^\"]+)\\.tar\\.xz)\"/",
|
||||
"license": {
|
||||
"type": "file",
|
||||
"path": "COPYING"
|
||||
}
|
||||
},
|
||||
"gmp": {
|
||||
"type": "ghtagtar",
|
||||
"repo": "alisw/GMP",
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace SPC;
|
||||
|
||||
use SPC\command\BuildCliCommand;
|
||||
use SPC\command\BuildLibsCommand;
|
||||
use SPC\command\DeleteDownloadCommand;
|
||||
use SPC\command\dev\AllExtCommand;
|
||||
use SPC\command\dev\PhpVerCommand;
|
||||
use SPC\command\dev\SortConfigCommand;
|
||||
@@ -13,6 +14,7 @@ use SPC\command\DoctorCommand;
|
||||
use SPC\command\DownloadCommand;
|
||||
use SPC\command\DumpLicenseCommand;
|
||||
use SPC\command\ExtractCommand;
|
||||
use SPC\command\InstallPkgCommand;
|
||||
use SPC\command\MicroCombineCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\HelpCommand;
|
||||
@@ -23,7 +25,7 @@ use Symfony\Component\Console\Command\ListCommand;
|
||||
*/
|
||||
final class ConsoleApplication extends Application
|
||||
{
|
||||
public const VERSION = '2.1.0-beta.3';
|
||||
public const VERSION = '2.1.0-beta.4';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -35,6 +37,8 @@ final class ConsoleApplication extends Application
|
||||
new BuildLibsCommand(),
|
||||
new DoctorCommand(),
|
||||
new DownloadCommand(),
|
||||
new InstallPkgCommand(),
|
||||
new DeleteDownloadCommand(),
|
||||
new DumpLicenseCommand(),
|
||||
new ExtractCommand(),
|
||||
new MicroCombineCommand(),
|
||||
|
||||
@@ -9,7 +9,7 @@ use SPC\exception\FileSystemException;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\SourceExtractor;
|
||||
use SPC\store\SourceManager;
|
||||
use SPC\util\CustomExt;
|
||||
|
||||
abstract class BuilderBase
|
||||
@@ -144,15 +144,15 @@ abstract class BuilderBase
|
||||
{
|
||||
CustomExt::loadCustomExt();
|
||||
$this->emitPatchPoint('before-php-extract');
|
||||
SourceExtractor::initSource(sources: ['php-src']);
|
||||
SourceManager::initSource(sources: ['php-src']);
|
||||
$this->emitPatchPoint('after-php-extract');
|
||||
if ($this->getPHPVersionID() >= 80000) {
|
||||
$this->emitPatchPoint('before-micro-extract');
|
||||
SourceExtractor::initSource(sources: ['micro']);
|
||||
SourceManager::initSource(sources: ['micro']);
|
||||
$this->emitPatchPoint('after-micro-extract');
|
||||
}
|
||||
$this->emitPatchPoint('before-exts-extract');
|
||||
SourceExtractor::initSource(exts: $extensions);
|
||||
SourceManager::initSource(exts: $extensions);
|
||||
$this->emitPatchPoint('after-exts-extract');
|
||||
foreach ($extensions as $extension) {
|
||||
$class = CustomExt::getExtClass($extension);
|
||||
|
||||
@@ -185,8 +185,11 @@ class Extension
|
||||
file_get_contents(ROOT_DIR . '/src/globals/tests/' . $this->getName() . '.php')
|
||||
);
|
||||
|
||||
[$ret] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "' . trim($test) . '"');
|
||||
[$ret, $out] = shell()->execWithResult(BUILD_ROOT_PATH . '/bin/php -r "' . trim($test) . '"');
|
||||
if ($ret !== 0) {
|
||||
if ($this->builder->getOption('debug')) {
|
||||
var_dump($out);
|
||||
}
|
||||
throw new RuntimeException('extension ' . $this->getName() . ' failed sanity check');
|
||||
}
|
||||
}
|
||||
|
||||
40
src/SPC/builder/extension/gettext.php
Normal file
40
src/SPC/builder/extension/gettext.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\builder\extension;
|
||||
|
||||
use SPC\builder\Extension;
|
||||
use SPC\builder\macos\MacOSBuilder;
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\util\CustomExt;
|
||||
|
||||
#[CustomExt('gettext')]
|
||||
class gettext extends Extension
|
||||
{
|
||||
/**
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public function patchBeforeBuildconf(): bool
|
||||
{
|
||||
if ($this->builder instanceof MacOSBuilder) {
|
||||
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/ext/gettext/config.m4', 'AC_CHECK_LIB($GETTEXT_CHECK_IN_LIB', 'AC_CHECK_LIB(intl');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws WrongUsageException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public function patchBeforeConfigure(): bool
|
||||
{
|
||||
if ($this->builder instanceof MacOSBuilder) {
|
||||
$frameworks = ' ' . $this->builder->getFrameworks(true) . ' ';
|
||||
FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/configure', '-lintl', $this->getLibFilesString() . $frameworks);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -169,6 +169,27 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
$enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO;
|
||||
$enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED;
|
||||
|
||||
// upx pack and strip for micro
|
||||
if ($this->getOption('with-upx-pack', false)) {
|
||||
FileSystem::replaceFileRegex(
|
||||
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
|
||||
'/POST_MICRO_BUILD_COMMANDS=.*/',
|
||||
'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH) && ' . $this->getOption('upx-exec') . ' --best \$(SAPI_MICRO_PATH)',
|
||||
);
|
||||
} elseif (!$this->getOption('no-strip', false)) {
|
||||
FileSystem::replaceFileRegex(
|
||||
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
|
||||
'/POST_MICRO_BUILD_COMMANDS=.*/',
|
||||
'POST_MICRO_BUILD_COMMANDS=true',
|
||||
);
|
||||
} else {
|
||||
FileSystem::replaceFileRegex(
|
||||
SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag',
|
||||
'/POST_MICRO_BUILD_COMMANDS=.*/',
|
||||
'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH)',
|
||||
);
|
||||
}
|
||||
|
||||
shell()->cd(SOURCE_PATH . '/php-src')
|
||||
->exec(
|
||||
"{$this->getOption('ld_library_path')} " .
|
||||
@@ -238,6 +259,10 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
|
||||
if (!$this->getOption('no-strip', false)) {
|
||||
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-all php');
|
||||
} elseif ($this->getOption('with-upx-pack')) {
|
||||
shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')
|
||||
->exec('strip --strip-all php')
|
||||
->exec($this->getOption('upx-exec') . ' --best php');
|
||||
}
|
||||
|
||||
$this->deployBinary(BUILD_TARGET_CLI);
|
||||
@@ -267,10 +292,6 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
->exec('sed -i "s|//lib|/lib|g" Makefile')
|
||||
->exec("make -j{$this->concurrency} {$vars} micro");
|
||||
|
||||
if (!$this->getOption('no-strip', false)) {
|
||||
shell()->cd(SOURCE_PATH . '/php-src/sapi/micro')->exec('strip --strip-all micro.sfx');
|
||||
}
|
||||
|
||||
$this->deployBinary(BUILD_TARGET_MICRO);
|
||||
|
||||
if ($this->phar_patched) {
|
||||
@@ -293,6 +314,10 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
|
||||
if (!$this->getOption('no-strip', false)) {
|
||||
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-all php-fpm');
|
||||
} elseif ($this->getOption('with-upx-pack')) {
|
||||
shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')
|
||||
->exec('strip --strip-all php-fpm')
|
||||
->exec($this->getOption('upx-exec') . ' --best php-fpm');
|
||||
}
|
||||
|
||||
$this->deployBinary(BUILD_TARGET_FPM);
|
||||
|
||||
@@ -110,11 +110,7 @@ class SystemUtil
|
||||
public static function getTuneCFlags(string $arch): array
|
||||
{
|
||||
return match ($arch) {
|
||||
'x86_64' => [
|
||||
'-march=corei7',
|
||||
'-mtune=core-avx2',
|
||||
],
|
||||
'arm64', 'aarch64' => [],
|
||||
'x86_64', 'arm64', 'aarch64' => [],
|
||||
default => throw new RuntimeException('unsupported arch: ' . $arch),
|
||||
};
|
||||
}
|
||||
|
||||
12
src/SPC/builder/linux/library/gettext.php
Normal file
12
src/SPC/builder/linux/library/gettext.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\builder\linux\library;
|
||||
|
||||
class gettext extends LinuxLibraryBase
|
||||
{
|
||||
use \SPC\builder\unix\library\gettext;
|
||||
|
||||
public const NAME = 'gettext';
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class libxml2 extends LinuxLibraryBase
|
||||
*/
|
||||
public function build(): void
|
||||
{
|
||||
$enable_zlib = $this->builder->getLib('zlib') ? 'ON' : 'OFF';
|
||||
$enable_zlib = $this->builder->getLib('zlib') ? ('ON -DZLIB_LIBRARY=' . BUILD_LIB_PATH . '/libz.a -DZLIB_INCLUDE_DIR=' . BUILD_INCLUDE_PATH) : 'OFF';
|
||||
$enable_icu = $this->builder->getLib('icu') ? 'ON' : 'OFF';
|
||||
$enable_xz = $this->builder->getLib('xz') ? 'ON' : 'OFF';
|
||||
|
||||
|
||||
12
src/SPC/builder/macos/library/gettext.php
Normal file
12
src/SPC/builder/macos/library/gettext.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\builder\macos\library;
|
||||
|
||||
class gettext extends MacOSLibraryBase
|
||||
{
|
||||
use \SPC\builder\unix\library\gettext;
|
||||
|
||||
public const NAME = 'gettext';
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class libxml2 extends MacOSLibraryBase
|
||||
*/
|
||||
protected function build(): void
|
||||
{
|
||||
$enable_zlib = $this->builder->getLib('zlib') ? 'ON' : 'OFF';
|
||||
$enable_zlib = $this->builder->getLib('zlib') ? ('ON -DZLIB_LIBRARY=' . BUILD_LIB_PATH . '/libz.a -DZLIB_INCLUDE_DIR=' . BUILD_INCLUDE_PATH) : 'OFF';
|
||||
$enable_icu = $this->builder->getLib('icu') ? 'ON' : 'OFF';
|
||||
$enable_xz = $this->builder->getLib('xz') ? 'ON' : 'OFF';
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\SourceExtractor;
|
||||
use SPC\store\SourceManager;
|
||||
use SPC\util\DependencyUtil;
|
||||
|
||||
abstract class UnixBuilderBase extends BuilderBase
|
||||
@@ -134,7 +134,7 @@ abstract class UnixBuilderBase extends BuilderBase
|
||||
$this->emitPatchPoint('before-libs-extract');
|
||||
|
||||
// extract sources
|
||||
SourceExtractor::initSource(libs: $sorted_libraries);
|
||||
SourceManager::initSource(libs: $sorted_libraries);
|
||||
|
||||
$this->emitPatchPoint('after-libs-extract');
|
||||
|
||||
|
||||
28
src/SPC/builder/unix/library/gettext.php
Normal file
28
src/SPC/builder/unix/library/gettext.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\builder\unix\library;
|
||||
|
||||
trait gettext
|
||||
{
|
||||
protected function build(): void
|
||||
{
|
||||
$extra = $this->builder->getLib('ncurses') ? ('--with-libncurses-prefix=' . BUILD_ROOT_PATH . ' ') : '';
|
||||
$extra .= $this->builder->getLib('libxml2') ? ('--with-libxml2-prefix=' . BUILD_ROOT_PATH . ' ') : '';
|
||||
shell()->cd($this->source_dir)
|
||||
->exec(
|
||||
'./configure ' .
|
||||
'--enable-static ' .
|
||||
'--disable-shared ' .
|
||||
'--disable-java ' .
|
||||
'--disable-c+ ' .
|
||||
$extra .
|
||||
'--with-libiconv-prefix=' . BUILD_ROOT_PATH . ' ' .
|
||||
'--prefix=' . BUILD_ROOT_PATH
|
||||
)
|
||||
->exec('make clean')
|
||||
->exec("make -j{$this->builder->concurrency}")
|
||||
->exec('make install');
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\SourceExtractor;
|
||||
use SPC\store\SourceManager;
|
||||
use SPC\store\SourcePatcher;
|
||||
use SPC\util\DependencyUtil;
|
||||
|
||||
@@ -72,6 +72,19 @@ class WindowsBuilder extends BuilderBase
|
||||
|
||||
$zts = $this->zts ? '--enable-zts=yes ' : '--enable-zts=no ';
|
||||
|
||||
// with-upx-pack for phpmicro
|
||||
$makefile = FileSystem::convertPath(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag.w32');
|
||||
if ($this->getOption('with-upx-pack', false)) {
|
||||
if (!file_exists($makefile . '.originfile')) {
|
||||
copy($makefile, $makefile . '.originfile');
|
||||
FileSystem::replaceFileStr($makefile, '$(MICRO_SFX):', "_MICRO_UPX = {$this->getOption('upx-exec')} --best $(MICRO_SFX)\n$(MICRO_SFX):");
|
||||
FileSystem::replaceFileStr($makefile, '@$(_MICRO_MT)', "@$(_MICRO_MT)\n\t@$(_MICRO_UPX)");
|
||||
}
|
||||
} elseif (file_exists($makefile . '.originfile')) {
|
||||
copy($makefile . '.originfile', $makefile);
|
||||
unlink($makefile . '.originfile');
|
||||
}
|
||||
|
||||
cmd()->cd(SOURCE_PATH . '\php-src')
|
||||
->exec(
|
||||
"{$this->sdk_prefix} configure.bat --task-args \"" .
|
||||
@@ -211,7 +224,7 @@ class WindowsBuilder extends BuilderBase
|
||||
}
|
||||
|
||||
// extract sources
|
||||
SourceExtractor::initSource(libs: $sorted_libraries);
|
||||
SourceManager::initSource(libs: $sorted_libraries);
|
||||
|
||||
// build all libs
|
||||
foreach ($this->libs as $lib) {
|
||||
@@ -293,6 +306,12 @@ class WindowsBuilder extends BuilderBase
|
||||
BUILD_TARGET_MICRO => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\micro.sfx",
|
||||
default => throw new RuntimeException('Deployment does not accept type ' . $type),
|
||||
};
|
||||
|
||||
// with-upx-pack for cli
|
||||
if ($this->getOption('with-upx-pack', false) && $type === BUILD_TARGET_CLI) {
|
||||
cmd()->exec($this->getOption('upx-exec') . ' --best ' . escapeshellarg($src));
|
||||
}
|
||||
|
||||
logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file');
|
||||
FileSystem::createDir(BUILD_ROOT_PATH . '\bin');
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ class openssl extends WindowsLibraryBase
|
||||
|
||||
protected function build(): void
|
||||
{
|
||||
$perl = file_exists(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe') ? (BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe') : SystemUtil::findCommand('perl.exe');
|
||||
$perl_path_native = PKG_ROOT_PATH . '\strawberry-perl-' . arch2gnu(php_uname('m')) . '-win\perl\bin\perl.exe';
|
||||
$perl = file_exists($perl_path_native) ? ($perl_path_native) : SystemUtil::findCommand('perl.exe');
|
||||
if ($perl === null) {
|
||||
throw new RuntimeException('You need to install perl first! (easiest way is using static-php-cli command "doctor")');
|
||||
}
|
||||
|
||||
@@ -56,11 +56,6 @@ abstract class BaseCommand extends Command
|
||||
// 如果 return false 则错误会继续递交给 PHP 标准错误处理
|
||||
return true;
|
||||
}, E_ALL | E_STRICT);
|
||||
if ($input->getOption('debug')) {
|
||||
global $ob_logger;
|
||||
$ob_logger = new ConsoleLogger(LogLevel::DEBUG);
|
||||
define('DEBUG_MODE', true);
|
||||
}
|
||||
$version = ConsoleApplication::VERSION;
|
||||
if (!$this->no_motd) {
|
||||
echo " _ _ _ _
|
||||
@@ -83,6 +78,14 @@ abstract class BaseCommand extends Command
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
|
||||
global $ob_logger;
|
||||
if ($input->getOption('debug')) {
|
||||
$ob_logger = new ConsoleLogger(LogLevel::DEBUG, decorated: !$input->getOption('no-ansi'));
|
||||
define('DEBUG_MODE', true);
|
||||
} else {
|
||||
$ob_logger = new ConsoleLogger(decorated: !$input->getOption('no-ansi'));
|
||||
}
|
||||
|
||||
// windows fallback
|
||||
Prompt::fallbackWhen(PHP_OS_FAMILY === 'Windows');
|
||||
ConfirmPrompt::fallbackUsing(function (ConfirmPrompt $prompt) use ($input, $output) {
|
||||
|
||||
@@ -32,11 +32,13 @@ class BuildCliCommand extends BuildCommand
|
||||
$this->addOption('enable-zts', null, null, 'enable ZTS support');
|
||||
$this->addOption('disable-opcache-jit', null, null, 'disable opcache jit');
|
||||
$this->addOption('with-hardcoded-ini', 'I', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Patch PHP source code, inject hardcoded INI');
|
||||
$this->addOption('with-micro-fake-cli', null, null, 'Enable phpmicro fake cli');
|
||||
$this->addOption('with-micro-fake-cli', null, null, 'Let phpmicro\'s PHP_SAPI use "cli" instead of "micro"');
|
||||
$this->addOption('with-suggested-libs', 'L', null, 'Build with suggested libs for selected exts and libs');
|
||||
$this->addOption('with-suggested-exts', 'E', null, 'Build with suggested extensions for selected exts');
|
||||
$this->addOption('with-added-patch', 'P', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Inject patch script outside');
|
||||
$this->addOption('without-micro-ext-test', null, null, 'Disable phpmicro with extension test code');
|
||||
|
||||
$this->addOption('with-upx-pack', null, null, 'Compress / pack binary using UPX tool (linux/windows only)');
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
@@ -58,6 +60,31 @@ class BuildCliCommand extends BuildCommand
|
||||
$this->output->writeln("<comment>\t--build-all\tBuild all SAPI: cli, micro, fpm, embed</comment>");
|
||||
return static::FAILURE;
|
||||
}
|
||||
if ($rule === BUILD_TARGET_ALL) {
|
||||
logger()->warning('--build-all option makes `--no-strip` always true, be aware!');
|
||||
}
|
||||
// Check upx
|
||||
$suffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : '';
|
||||
if ($this->getOption('with-upx-pack')) {
|
||||
// only available for linux for now
|
||||
if (!in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
|
||||
logger()->error('UPX is only available on Linux and Windows!');
|
||||
return static::FAILURE;
|
||||
}
|
||||
// need to install this manually
|
||||
if (!file_exists(PKG_ROOT_PATH . '/bin/upx' . $suffix)) {
|
||||
global $argv;
|
||||
logger()->error('upx does not exist, please install it first:');
|
||||
logger()->error('');
|
||||
logger()->error("\t" . $argv[0] . ' install-pkg upx');
|
||||
logger()->error('');
|
||||
return static::FAILURE;
|
||||
}
|
||||
// exclusive with no-strip
|
||||
if ($this->getOption('no-strip')) {
|
||||
logger()->warning('--with-upx-pack conflicts with --no-strip, --no-strip won\'t work!');
|
||||
}
|
||||
}
|
||||
try {
|
||||
// create builder
|
||||
$builder = BuilderProvider::makeBuilderByInput($this->input);
|
||||
@@ -80,6 +107,10 @@ class BuildCliCommand extends BuildCommand
|
||||
if ($this->input->getOption('disable-opcache-jit')) {
|
||||
$indent_texts['Opcache JIT'] = 'disabled';
|
||||
}
|
||||
if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) {
|
||||
$indent_texts['UPX Pack'] = 'enabled';
|
||||
$builder->setOption('upx-exec', FileSystem::convertPath(PKG_ROOT_PATH . '/bin/upx' . $suffix));
|
||||
}
|
||||
try {
|
||||
$ver = $builder->getPHPVersion();
|
||||
$indent_texts['PHP Version'] = $ver;
|
||||
|
||||
86
src/SPC/command/DeleteDownloadCommand.php
Normal file
86
src/SPC/command/DeleteDownloadCommand.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use SPC\exception\DownloaderException;
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\FileSystem;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
#[AsCommand('del-download', 'Remove locked download source or package using name', ['delete-download', 'del-down'])]
|
||||
class DeleteDownloadCommand extends BaseCommand
|
||||
{
|
||||
public function configure(): void
|
||||
{
|
||||
$this->addArgument('sources', InputArgument::REQUIRED, 'The sources/packages will be deleted, comma separated');
|
||||
$this->addOption('all', 'A', null, 'Delete all downloaded and locked sources/packages');
|
||||
}
|
||||
|
||||
public function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
if ($input->getOption('all')) {
|
||||
$input->setArgument('sources', '');
|
||||
}
|
||||
parent::initialize($input, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
try {
|
||||
// get source list that will be downloaded
|
||||
$sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources'))));
|
||||
if (empty($sources)) {
|
||||
logger()->notice('Removing downloads/ directory ...');
|
||||
FileSystem::removeDir(DOWNLOAD_PATH);
|
||||
logger()->info('Removed downloads/ dir!');
|
||||
return static::SUCCESS;
|
||||
}
|
||||
$chosen_sources = $sources;
|
||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
||||
|
||||
foreach ($chosen_sources as $source) {
|
||||
$source = trim($source);
|
||||
if (!isset($lock[$source])) {
|
||||
logger()->warning("Source/Package [{$source}] not locked or not downloaded, skipped.");
|
||||
continue;
|
||||
}
|
||||
// remove download file/dir if exists
|
||||
if ($lock[$source]['source_type'] === 'archive') {
|
||||
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$source]['filename']))) {
|
||||
logger()->info('Deleting file ' . $path);
|
||||
unlink($path);
|
||||
} else {
|
||||
logger()->warning("Source/Package [{$source}] file not found, skip deleting file.");
|
||||
}
|
||||
} else {
|
||||
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$source]['dirname']))) {
|
||||
logger()->info('Deleting dir ' . $path);
|
||||
FileSystem::removeDir($path);
|
||||
} else {
|
||||
logger()->warning("Source/Package [{$source}] directory not found, skip deleting dir.");
|
||||
}
|
||||
}
|
||||
// remove locked sources
|
||||
unset($lock[$source]);
|
||||
}
|
||||
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
logger()->info('Delete success!');
|
||||
return static::SUCCESS;
|
||||
} catch (DownloaderException $e) {
|
||||
logger()->error($e->getMessage());
|
||||
return static::FAILURE;
|
||||
} catch (WrongUsageException $e) {
|
||||
logger()->critical($e->getMessage());
|
||||
return static::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,11 @@ use SPC\builder\traits\UnixSystemUtilTrait;
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\SourceExtractor;
|
||||
use SPC\store\SourceManager;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
#[AsCommand('extract', 'Extract required sources')]
|
||||
#[AsCommand('extract', 'Extract required sources', ['extract-source'])]
|
||||
class ExtractCommand extends BaseCommand
|
||||
{
|
||||
use UnixSystemUtilTrait;
|
||||
@@ -34,7 +34,7 @@ class ExtractCommand extends BaseCommand
|
||||
$this->output->writeln('<error>sources cannot be empty, at least contain one !</error>');
|
||||
return static::FAILURE;
|
||||
}
|
||||
SourceExtractor::initSource(sources: $sources);
|
||||
SourceManager::initSource(sources: $sources);
|
||||
logger()->info('Extract done !');
|
||||
return static::SUCCESS;
|
||||
}
|
||||
|
||||
86
src/SPC/command/InstallPkgCommand.php
Normal file
86
src/SPC/command/InstallPkgCommand.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\command;
|
||||
|
||||
use SPC\builder\traits\UnixSystemUtilTrait;
|
||||
use SPC\exception\DownloaderException;
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Config;
|
||||
use SPC\store\PackageManager;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
#[AsCommand('install-pkg', 'Install additional packages', ['i', 'install-package'])]
|
||||
class InstallPkgCommand extends BaseCommand
|
||||
{
|
||||
use UnixSystemUtilTrait;
|
||||
|
||||
public function configure(): void
|
||||
{
|
||||
$this->addArgument('packages', InputArgument::REQUIRED, 'The packages will be installed, comma separated');
|
||||
$this->addOption('shallow-clone', null, null, 'Clone shallow');
|
||||
$this->addOption('custom-url', 'U', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Specify custom source download url, e.g "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
try {
|
||||
// Use shallow-clone can reduce git resource download
|
||||
if ($this->getOption('shallow-clone')) {
|
||||
define('GIT_SHALLOW_CLONE', true);
|
||||
}
|
||||
|
||||
// Process -U options
|
||||
$custom_urls = [];
|
||||
foreach ($this->input->getOption('custom-url') as $value) {
|
||||
[$pkg_name, $url] = explode(':', $value, 2);
|
||||
$custom_urls[$pkg_name] = $url;
|
||||
}
|
||||
|
||||
$chosen_pkgs = array_map('trim', array_filter(explode(',', $this->getArgument('packages'))));
|
||||
|
||||
// Download them
|
||||
f_mkdir(DOWNLOAD_PATH);
|
||||
$ni = 0;
|
||||
$cnt = count($chosen_pkgs);
|
||||
|
||||
foreach ($chosen_pkgs as $pkg) {
|
||||
++$ni;
|
||||
if (isset($custom_urls[$pkg])) {
|
||||
$config = Config::getPkg($pkg);
|
||||
$new_config = [
|
||||
'type' => 'url',
|
||||
'url' => $custom_urls[$pkg],
|
||||
];
|
||||
if (isset($config['extract'])) {
|
||||
$new_config['extract'] = $config['extract'];
|
||||
}
|
||||
if (isset($config['filename'])) {
|
||||
$new_config['filename'] = $config['filename'];
|
||||
}
|
||||
logger()->info("Installing source {$pkg} from custom url [{$ni}/{$cnt}]");
|
||||
PackageManager::installPackage($pkg, $new_config);
|
||||
} else {
|
||||
logger()->info("Fetching package {$pkg} [{$ni}/{$cnt}]");
|
||||
PackageManager::installPackage($pkg, Config::getPkg($pkg));
|
||||
}
|
||||
}
|
||||
$time = round(microtime(true) - START_TIME, 3);
|
||||
logger()->info('Install packages complete, used ' . $time . ' s !');
|
||||
return static::SUCCESS;
|
||||
} catch (DownloaderException $e) {
|
||||
logger()->error($e->getMessage());
|
||||
return static::FAILURE;
|
||||
} catch (WrongUsageException $e) {
|
||||
logger()->critical($e->getMessage());
|
||||
return static::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,15 @@ class SortConfigCommand extends BaseCommand
|
||||
return static::FAILURE;
|
||||
}
|
||||
break;
|
||||
case 'pkg':
|
||||
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/pkg.json'), true);
|
||||
ConfigValidator::validatePkgs($file);
|
||||
ksort($file);
|
||||
if (!file_put_contents(ROOT_DIR . '/config/pkg.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n")) {
|
||||
$this->output->writeln('<error>Write file pkg.json failed!</error>');
|
||||
return static::FAILURE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$this->output->writeln("<error>invalid config name: {$name}</error>");
|
||||
return 1;
|
||||
|
||||
@@ -57,7 +57,7 @@ class BSDToolCheckList
|
||||
$prefix = '';
|
||||
}
|
||||
try {
|
||||
shell(true)->exec("ASSUME_ALWAYS_YES=yes {$prefix} pkg install -y " . implode(' ', $missing));
|
||||
shell(true)->exec("ASSUME_ALWAYS_YES=yes {$prefix}pkg install -y " . implode(' ', $missing));
|
||||
} catch (RuntimeException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\PackageManager;
|
||||
|
||||
class LinuxMuslCheck
|
||||
{
|
||||
@@ -84,7 +85,6 @@ class LinuxMuslCheck
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
/**
|
||||
* @throws DownloaderException
|
||||
* @throws FileSystemException
|
||||
* @throws WrongUsageException
|
||||
*/
|
||||
@@ -98,15 +98,10 @@ class LinuxMuslCheck
|
||||
logger()->warning('Current user is not root, using sudo for running command');
|
||||
}
|
||||
$arch = arch2gnu(php_uname('m'));
|
||||
$musl_compile_source = [
|
||||
'type' => 'url',
|
||||
'url' => "https://dl.static-php.dev/static-php-cli/deps/musl-toolchain/{$arch}-musl-toolchain.tgz",
|
||||
];
|
||||
logger()->info('Downloading ' . $musl_compile_source['url']);
|
||||
Downloader::downloadSource('musl-compile', $musl_compile_source);
|
||||
logger()->info('Extracting musl-cross');
|
||||
FileSystem::extractSource('musl-compile', DOWNLOAD_PATH . "/{$arch}-musl-toolchain.tgz");
|
||||
shell()->exec($prefix . 'cp -rf ' . SOURCE_PATH . '/musl-compile/* /usr/local/musl');
|
||||
PackageManager::installPackage("musl-toolchain-{$arch}-linux");
|
||||
$pkg_root = PKG_ROOT_PATH . "/musl-toolchain-{$arch}-linux";
|
||||
shell()->exec("{$prefix}cp -rf {$pkg_root}/* /usr/local/musl");
|
||||
FileSystem::removeDir($pkg_root);
|
||||
return true;
|
||||
} catch (RuntimeException) {
|
||||
return false;
|
||||
|
||||
@@ -9,8 +9,8 @@ use SPC\doctor\AsCheckItem;
|
||||
use SPC\doctor\AsFixItem;
|
||||
use SPC\doctor\CheckResult;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\store\Downloader;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\PackageManager;
|
||||
|
||||
class WindowsToolCheckList
|
||||
{
|
||||
@@ -64,8 +64,9 @@ class WindowsToolCheckList
|
||||
#[AsCheckItem('if perl(strawberry) installed', limit_os: 'Windows', level: 994)]
|
||||
public function checkPerl(): ?CheckResult
|
||||
{
|
||||
if (file_exists(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe')) {
|
||||
return CheckResult::ok(BUILD_ROOT_PATH . '\perl\perl\bin\perl.exe');
|
||||
$arch = arch2gnu(php_uname('m'));
|
||||
if (file_exists(PKG_ROOT_PATH . '\strawberry-perl-' . $arch . '-win\perl\bin\perl.exe')) {
|
||||
return CheckResult::ok(PKG_ROOT_PATH . '\strawberry-perl-' . $arch . '-win\perl\bin\perl.exe');
|
||||
}
|
||||
if (($path = SystemUtil::findCommand('perl.exe')) === null) {
|
||||
return CheckResult::fail('perl not found in path.', 'install-perl');
|
||||
@@ -91,32 +92,15 @@ class WindowsToolCheckList
|
||||
#[AsFixItem('install-nasm')]
|
||||
public function installNasm(): bool
|
||||
{
|
||||
// The hardcoded version here is to be consistent with the version compiled by `musl-cross-toolchain`.
|
||||
$nasm_ver = '2.16.01';
|
||||
$nasm_dist = "nasm-{$nasm_ver}";
|
||||
$source = [
|
||||
'type' => 'url',
|
||||
'url' => "https://www.nasm.us/pub/nasm/releasebuilds/{$nasm_ver}/win64/{$nasm_dist}-win64.zip",
|
||||
];
|
||||
logger()->info('Downloading ' . $source['url']);
|
||||
Downloader::downloadSource('nasm', $source);
|
||||
FileSystem::extractSource('nasm', DOWNLOAD_PATH . "\\{$nasm_dist}-win64.zip");
|
||||
copy(SOURCE_PATH . "\\nasm\\{$nasm_dist}\\nasm.exe", PHP_SDK_PATH . '\bin\nasm.exe');
|
||||
copy(SOURCE_PATH . "\\nasm\\{$nasm_dist}\\ndisasm.exe", PHP_SDK_PATH . '\bin\ndisasm.exe');
|
||||
PackageManager::installPackage('nasm-x86_64-win');
|
||||
return true;
|
||||
}
|
||||
|
||||
#[AsFixItem('install-perl')]
|
||||
public function installPerl(): bool
|
||||
{
|
||||
$url = 'https://github.com/StrawberryPerl/Perl-Dist-Strawberry/releases/download/SP_5380_5361/strawberry-perl-5.38.0.1-64bit-portable.zip';
|
||||
$source = [
|
||||
'type' => 'url',
|
||||
'url' => $url,
|
||||
];
|
||||
logger()->info("Downloading {$url}");
|
||||
Downloader::downloadSource('strawberry-perl', $source);
|
||||
FileSystem::extractSource('strawberry-perl', DOWNLOAD_PATH . '\strawberry-perl-5.38.0.1-64bit-portable.zip', '../buildroot/perl');
|
||||
$arch = arch2gnu(php_uname('m'));
|
||||
PackageManager::installPackage("strawberry-perl-{$arch}-win");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ use SPC\exception\WrongUsageException;
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
public static ?array $pkg = null;
|
||||
|
||||
public static ?array $source = null;
|
||||
|
||||
public static ?array $lib = null;
|
||||
@@ -31,6 +33,19 @@ class Config
|
||||
return self::$source[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read pkg from pkg.json
|
||||
*
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function getPkg(string $name): ?array
|
||||
{
|
||||
if (self::$pkg === null) {
|
||||
self::$pkg = FileSystem::loadConfigArray('pkg');
|
||||
}
|
||||
return self::$pkg[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据不同的操作系统分别选择不同的 lib 库依赖项
|
||||
* 如果 key 为 null,那么直接返回整个 meta。
|
||||
|
||||
@@ -246,6 +246,92 @@ class Downloader
|
||||
}*/
|
||||
}
|
||||
|
||||
public static function downloadPackage(string $name, ?array $pkg = null, bool $force = false): void
|
||||
{
|
||||
if ($pkg === null) {
|
||||
$pkg = Config::getPkg($name);
|
||||
}
|
||||
|
||||
if ($pkg === null) {
|
||||
logger()->warning('Package {name} unknown. Skipping.', ['name' => $name]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_dir(DOWNLOAD_PATH)) {
|
||||
FileSystem::createDir(DOWNLOAD_PATH);
|
||||
}
|
||||
|
||||
// load lock file
|
||||
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
|
||||
$lock = [];
|
||||
} else {
|
||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
||||
}
|
||||
// If lock file exists, skip downloading
|
||||
if (isset($lock[$name]) && !$force) {
|
||||
if ($lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename'])) {
|
||||
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['filename']);
|
||||
return;
|
||||
}
|
||||
if ($lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])) {
|
||||
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['dirname']);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($pkg['type']) {
|
||||
case 'bitbuckettag': // BitBucket Tag
|
||||
[$url, $filename] = self::getLatestBitbucketTag($name, $pkg);
|
||||
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
|
||||
break;
|
||||
case 'ghtar': // GitHub Release (tar)
|
||||
[$url, $filename] = self::getLatestGithubTarball($name, $pkg);
|
||||
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
|
||||
break;
|
||||
case 'ghtagtar': // GitHub Tag (tar)
|
||||
[$url, $filename] = self::getLatestGithubTarball($name, $pkg, 'tags');
|
||||
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
|
||||
break;
|
||||
case 'ghrel': // GitHub Release (uploaded)
|
||||
[$url, $filename] = self::getLatestGithubRelease($name, $pkg);
|
||||
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
|
||||
break;
|
||||
case 'filelist': // Basic File List (regex based crawler)
|
||||
[$url, $filename] = self::getFromFileList($name, $pkg);
|
||||
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
|
||||
break;
|
||||
case 'url': // Direct download URL
|
||||
$url = $pkg['url'];
|
||||
$filename = $pkg['filename'] ?? basename($pkg['url']);
|
||||
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null);
|
||||
break;
|
||||
case 'git': // Git repo
|
||||
self::downloadGit($name, $pkg['url'], $pkg['rev'], $pkg['extract'] ?? null);
|
||||
break;
|
||||
case 'custom': // Custom download method, like API-based download or other
|
||||
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\\store\\source');
|
||||
foreach ($classes as $class) {
|
||||
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
|
||||
(new $class())->fetch();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new DownloaderException('unknown source type: ' . $pkg['type']);
|
||||
}
|
||||
} catch (RuntimeException $e) {
|
||||
// Because sometimes files downloaded through the command line are not automatically deleted after a failure.
|
||||
// Here we need to manually delete the file if it is detected to exist.
|
||||
if (isset($filename) && file_exists(DOWNLOAD_PATH . '/' . $filename)) {
|
||||
logger()->warning('Deleting download file: ' . $filename);
|
||||
unlink(DOWNLOAD_PATH . '/' . $filename);
|
||||
}
|
||||
throw new DownloaderException('Download failed! ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download source by name and meta.
|
||||
*
|
||||
|
||||
@@ -16,7 +16,7 @@ class FileSystem
|
||||
*/
|
||||
public static function loadConfigArray(string $config, ?string $config_dir = null): array
|
||||
{
|
||||
$whitelist = ['ext', 'lib', 'source'];
|
||||
$whitelist = ['ext', 'lib', 'source', 'pkg'];
|
||||
if (!in_array($config, $whitelist)) {
|
||||
throw new FileSystemException('Reading ' . $config . '.json is not allowed');
|
||||
}
|
||||
@@ -138,6 +138,37 @@ class FileSystem
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
public static function extractPackage(string $name, string $filename, ?string $extract_path = null): void
|
||||
{
|
||||
if ($extract_path !== null) {
|
||||
// replace
|
||||
$extract_path = self::replacePathVariable($extract_path);
|
||||
$extract_path = self::isRelativePath($extract_path) ? (WORKING_DIR . '/' . $extract_path) : $extract_path;
|
||||
} else {
|
||||
$extract_path = PKG_ROOT_PATH . '/' . $name;
|
||||
}
|
||||
logger()->info("extracting {$name} package to {$extract_path} ...");
|
||||
$target = self::convertPath($extract_path);
|
||||
|
||||
if (!is_dir($dir = dirname($target))) {
|
||||
self::createDir($dir);
|
||||
}
|
||||
try {
|
||||
self::extractArchive($filename, $target);
|
||||
} catch (RuntimeException $e) {
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
f_passthru('rmdir /s /q ' . $target);
|
||||
} else {
|
||||
f_passthru('rm -rf ' . $target);
|
||||
}
|
||||
throw new FileSystemException('Cannot extract package ' . $name, $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩下载的资源包到 source 目录
|
||||
*
|
||||
@@ -152,52 +183,24 @@ class FileSystem
|
||||
if (self::$_extract_hook === []) {
|
||||
SourcePatcher::init();
|
||||
}
|
||||
if (!is_dir(SOURCE_PATH)) {
|
||||
self::createDir(SOURCE_PATH);
|
||||
}
|
||||
if ($move_path !== null) {
|
||||
$move_path = SOURCE_PATH . '/' . $move_path;
|
||||
}
|
||||
logger()->info("extracting {$name} source to " . ($move_path ?? SOURCE_PATH . "/{$name}") . ' ...');
|
||||
logger()->info("extracting {$name} source to " . ($move_path ?? (SOURCE_PATH . "/{$name}")) . ' ...');
|
||||
$target = self::convertPath($move_path ?? (SOURCE_PATH . "/{$name}"));
|
||||
if (!is_dir($dir = dirname($target))) {
|
||||
self::createDir($dir);
|
||||
}
|
||||
try {
|
||||
$target = self::convertPath($move_path ?? (SOURCE_PATH . "/{$name}"));
|
||||
// Git source, just move
|
||||
if (is_dir(self::convertPath($filename))) {
|
||||
self::copyDir(self::convertPath($filename), $target);
|
||||
self::emitSourceExtractHook($name);
|
||||
return;
|
||||
}
|
||||
if (f_mkdir(directory: $target, recursive: true) !== true) {
|
||||
throw new FileSystemException('create ' . $name . 'source dir failed');
|
||||
}
|
||||
|
||||
if (in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD'])) {
|
||||
match (self::extname($filename)) {
|
||||
'tar', 'xz', 'txz' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
|
||||
'tgz', 'gz' => f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1"),
|
||||
'bz2' => f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1"),
|
||||
'zip' => f_passthru("unzip {$filename} -d {$target}"),
|
||||
default => throw new FileSystemException('unknown archive format: ' . $filename),
|
||||
};
|
||||
} elseif (PHP_OS_FAMILY === 'Windows') {
|
||||
// use php-sdk-binary-tools/bin/7za.exe
|
||||
$_7z = self::convertPath(PHP_SDK_PATH . '/bin/7za.exe');
|
||||
f_mkdir(SOURCE_PATH . "/{$name}", recursive: true);
|
||||
match (self::extname($filename)) {
|
||||
'tar' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
|
||||
'xz', 'txz', 'gz', 'tgz', 'bz2' => f_passthru("\"{$_7z}\" x -so {$filename} | tar -f - -x -C {$target} --strip-components 1"),
|
||||
'zip' => f_passthru("\"{$_7z}\" x {$filename} -o{$target} -y"),
|
||||
default => throw new FileSystemException("unknown archive format: {$filename}"),
|
||||
};
|
||||
}
|
||||
self::extractArchive($filename, $target);
|
||||
self::emitSourceExtractHook($name);
|
||||
} catch (RuntimeException $e) {
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
f_passthru('rmdir /s /q ' . SOURCE_PATH . "/{$name}");
|
||||
f_passthru('rmdir /s /q ' . $target);
|
||||
} else {
|
||||
f_passthru('rm -r ' . SOURCE_PATH . "/{$name}");
|
||||
f_passthru('rm -rf ' . $target);
|
||||
}
|
||||
throw new FileSystemException('Cannot extract source ' . $name, $e->getCode(), $e);
|
||||
throw new FileSystemException('Cannot extract source ' . $name . ': ' . $e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,6 +414,54 @@ class FileSystem
|
||||
return strlen($path) > 0 && $path[0] !== '/';
|
||||
}
|
||||
|
||||
public static function replacePathVariable(string $path): string
|
||||
{
|
||||
$replacement = [
|
||||
'{pkg_root_path}' => PKG_ROOT_PATH,
|
||||
'{php_sdk_path}' => defined('PHP_SDK_PATH') ? PHP_SDK_PATH : WORKING_DIR . '/php-sdk-binary-tools',
|
||||
'{working_dir}' => WORKING_DIR,
|
||||
'{download_path}' => DOWNLOAD_PATH,
|
||||
'{source_path}' => SOURCE_PATH,
|
||||
];
|
||||
return str_replace(array_keys($replacement), array_values($replacement), $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
private static function extractArchive(string $filename, string $target): void
|
||||
{
|
||||
// Git source, just move
|
||||
if (is_dir(self::convertPath($filename))) {
|
||||
self::copyDir(self::convertPath($filename), $target);
|
||||
return;
|
||||
}
|
||||
// Create base dir
|
||||
if (f_mkdir(directory: $target, recursive: true) !== true) {
|
||||
throw new FileSystemException('create ' . $target . ' dir failed');
|
||||
}
|
||||
|
||||
if (in_array(PHP_OS_FAMILY, ['Darwin', 'Linux', 'BSD'])) {
|
||||
match (self::extname($filename)) {
|
||||
'tar', 'xz', 'txz' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
|
||||
'tgz', 'gz' => f_passthru("tar -xzf {$filename} -C {$target} --strip-components 1"),
|
||||
'bz2' => f_passthru("tar -xjf {$filename} -C {$target} --strip-components 1"),
|
||||
'zip' => f_passthru("unzip {$filename} -d {$target}"),
|
||||
default => throw new FileSystemException('unknown archive format: ' . $filename),
|
||||
};
|
||||
} elseif (PHP_OS_FAMILY === 'Windows') {
|
||||
// use php-sdk-binary-tools/bin/7za.exe
|
||||
$_7z = self::convertPath(PHP_SDK_PATH . '/bin/7za.exe');
|
||||
match (self::extname($filename)) {
|
||||
'tar' => f_passthru("tar -xf {$filename} -C {$target} --strip-components 1"),
|
||||
'xz', 'txz', 'gz', 'tgz', 'bz2' => f_passthru("\"{$_7z}\" x -so {$filename} | tar -f - -x -C {$target} --strip-components 1"),
|
||||
'zip' => f_passthru("\"{$_7z}\" x {$filename} -o{$target} -y"),
|
||||
default => throw new FileSystemException("unknown archive format: {$filename}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FileSystemException
|
||||
*/
|
||||
|
||||
67
src/SPC/store/PackageManager.php
Normal file
67
src/SPC/store/PackageManager.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace SPC\store;
|
||||
|
||||
use SPC\exception\FileSystemException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
|
||||
class PackageManager
|
||||
{
|
||||
public static function installPackage(string $pkg_name, ?array $config = null, bool $force = false): void
|
||||
{
|
||||
if ($config === null) {
|
||||
$config = Config::getPkg($pkg_name);
|
||||
}
|
||||
if ($config === null) {
|
||||
$arch = arch2gnu(php_uname('m'));
|
||||
$os = match (PHP_OS_FAMILY) {
|
||||
'Linux' => 'linux',
|
||||
'Windows' => 'win',
|
||||
'BSD' => 'freebsd',
|
||||
'Darwin' => 'macos',
|
||||
default => throw new WrongUsageException('Unsupported OS!'),
|
||||
};
|
||||
$config = Config::getPkg("{$pkg_name}-{$arch}-{$os}");
|
||||
}
|
||||
if ($config === null) {
|
||||
throw new WrongUsageException("Package [{$pkg_name}] does not exist, please check the name and correct it !");
|
||||
}
|
||||
|
||||
// Download package
|
||||
Downloader::downloadPackage($pkg_name, $config, $force);
|
||||
// After download, read lock file name
|
||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
|
||||
$filename = DOWNLOAD_PATH . '/' . ($lock[$pkg_name]['filename'] ?? $lock[$pkg_name]['dirname']);
|
||||
$extract = $lock[$pkg_name]['move_path'] === null ? (PKG_ROOT_PATH . '/' . $pkg_name) : $lock[$pkg_name]['move_path'];
|
||||
FileSystem::extractPackage($pkg_name, $filename, $extract);
|
||||
|
||||
// if contains extract-files, we just move this file to destination, and remove extract dir
|
||||
if (is_array($config['extract-files'] ?? null) && is_assoc_array($config['extract-files'])) {
|
||||
$scandir = FileSystem::scanDirFiles($extract, true, true);
|
||||
foreach ($config['extract-files'] as $file => $target) {
|
||||
$target = FileSystem::convertPath(FileSystem::replacePathVariable($target));
|
||||
if (!is_dir($dir = dirname($target))) {
|
||||
f_mkdir($dir, 0755, true);
|
||||
}
|
||||
logger()->debug("Moving package [{$pkg_name}] file {$file} to {$target}");
|
||||
// match pattern, needs to scan dir
|
||||
$file = FileSystem::convertPath($file);
|
||||
$found = false;
|
||||
foreach ($scandir as $item) {
|
||||
if (match_pattern($file, $item)) {
|
||||
$file = $item;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($found === false) {
|
||||
throw new FileSystemException('Unable to find extract-files item: ' . $file);
|
||||
}
|
||||
rename(FileSystem::convertPath($extract . '/' . $file), $target);
|
||||
}
|
||||
FileSystem::removeDir($extract);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use SPC\exception\FileSystemException;
|
||||
use SPC\exception\RuntimeException;
|
||||
use SPC\exception\WrongUsageException;
|
||||
|
||||
class SourceExtractor
|
||||
class SourceManager
|
||||
{
|
||||
/**
|
||||
* @throws WrongUsageException
|
||||
@@ -70,4 +70,12 @@ class ConfigValidator
|
||||
{
|
||||
is_array($data) || throw new ValidationException('ext.json is broken');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public static function validatePkgs(mixed $data): void
|
||||
{
|
||||
is_array($data) || throw new ValidationException('pkg.json is broken');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ define('START_TIME', microtime(true));
|
||||
define('BUILD_ROOT_PATH', FileSystem::convertPath(is_string($a = getenv('BUILD_ROOT_PATH')) ? $a : (WORKING_DIR . '/buildroot')));
|
||||
define('SOURCE_PATH', FileSystem::convertPath(is_string($a = getenv('SOURCE_PATH')) ? $a : (WORKING_DIR . '/source')));
|
||||
define('DOWNLOAD_PATH', FileSystem::convertPath(is_string($a = getenv('DOWNLOAD_PATH')) ? $a : (WORKING_DIR . '/downloads')));
|
||||
define('PKG_ROOT_PATH', FileSystem::convertPath(is_string($a = getenv('PKG_ROOT_PATH')) ? $a : (WORKING_DIR . '/pkgroot')));
|
||||
define('BUILD_BIN_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_BIN_PATH')) ? $a : (BUILD_ROOT_PATH . '/bin')));
|
||||
define('BUILD_LIB_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_LIB_PATH')) ? $a : (BUILD_ROOT_PATH . '/lib')));
|
||||
define('BUILD_INCLUDE_PATH', FileSystem::convertPath(is_string($a = getenv('INSTALL_INCLUDE_PATH')) ? $a : (BUILD_ROOT_PATH . '/include')));
|
||||
|
||||
@@ -47,6 +47,13 @@ function arch2gnu(string $arch): string
|
||||
};
|
||||
}
|
||||
|
||||
function match_pattern(string $pattern, string $subject): bool
|
||||
{
|
||||
$pattern = str_replace(['\*', '\\\\.*'], ['.*', '\*'], preg_quote($pattern, '/'));
|
||||
$pattern = '/^' . $pattern . '$/i';
|
||||
return preg_match($pattern, $subject) === 1;
|
||||
}
|
||||
|
||||
function quote(string $str, string $quote = '"'): string
|
||||
{
|
||||
return $quote . $str . $quote;
|
||||
|
||||
@@ -13,7 +13,7 @@ declare(strict_types=1);
|
||||
|
||||
// If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`).
|
||||
$extensions = match (PHP_OS_FAMILY) {
|
||||
'Linux', 'Darwin' => '',
|
||||
'Linux', 'Darwin' => 'event,gettext',
|
||||
'Windows' => 'mbstring',
|
||||
};
|
||||
|
||||
|
||||
25
src/globals/tests/gettext.php
Normal file
25
src/globals/tests/gettext.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
assert(function_exists('gettext'));
|
||||
assert(function_exists('bindtextdomain'));
|
||||
assert(function_exists('textdomain'));
|
||||
assert(function_exists('bind_textdomain_codeset'));
|
||||
|
||||
if (!is_dir('locale/en_US/LC_MESSAGES/')) {
|
||||
mkdir('locale/en_US/LC_MESSAGES/', 0755, true);
|
||||
}
|
||||
if (!file_exists('locale/en_US/LC_MESSAGES/test.mo')) {
|
||||
$mo = '3hIElQAAAAACAAAAHAAAACwAAAAFAAAAPAAAAAAAAABQAAAABgAAAFEAAAAXAQAAWAAAAAcAAABwAQAAAQAAAAAAAAAAAAAAAgAAAAAAAAAA56S65L6LAFByb2plY3QtSWQtVmVyc2lvbjogUEFDS0FHRSBWRVJTSU9OClJlcG9ydC1Nc2dpZC1CdWdzLVRvOiAKUE8tUmV2aXNpb24tRGF0ZTogWUVBUi1NTy1EQSBITzpNSStaT05FCkxhc3QtVHJhbnNsYXRvcjogRlVMTCBOQU1FIDxFTUFJTEBBRERSRVNTPgpMYW5ndWFnZS1UZWFtOiBMQU5HVUFHRSA8TExAbGkub3JnPgpMYW5ndWFnZTogCk1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbjsgY2hhcnNldD1VVEYtOApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA4Yml0CgBFeGFtcGxlAA==';
|
||||
file_put_contents('locale/en_US/LC_MESSAGES/test.mo', base64_decode($mo));
|
||||
}
|
||||
putenv('LANG=en_US');
|
||||
setlocale(LC_ALL, 'en_US');
|
||||
|
||||
$domain = 'test';
|
||||
bindtextdomain($domain, 'locale/');
|
||||
bind_textdomain_codeset($domain, 'UTF-8');
|
||||
textdomain($domain);
|
||||
|
||||
assert(gettext(json_decode('"\u793a\u4f8b"', true)) === 'Example');
|
||||
Reference in New Issue
Block a user