mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-09 01:45:36 +08:00
Compare commits
60 Commits
4b19f4ec95
...
feat/pgo-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b8abda8c9 | ||
|
|
5747a5661e | ||
|
|
d52ba59edc | ||
|
|
2d5abd31c1 | ||
|
|
69d0f9b8cc | ||
|
|
d846db2ef2 | ||
|
|
f06891155c | ||
|
|
15deecd34f | ||
|
|
7ae5d742c2 | ||
|
|
95be291a84 | ||
|
|
d89add106a | ||
|
|
a36129c6cd | ||
|
|
3f3edd62a1 | ||
|
|
82b77af317 | ||
|
|
df26b93e58 | ||
|
|
d1b4c05381 | ||
|
|
e1658bc0e6 | ||
|
|
5053620b61 | ||
|
|
9366cbacd9 | ||
|
|
fe4803cfaf | ||
|
|
80d81079db | ||
|
|
6ab52a5181 | ||
|
|
5bdcd3f562 | ||
|
|
203fed65d9 | ||
|
|
1ae989df59 | ||
|
|
e7fb1e203f | ||
|
|
713f8255af | ||
|
|
3c24c92d61 | ||
|
|
d93fdd9707 | ||
|
|
c40d069b0c | ||
|
|
9d508f1d39 | ||
|
|
a23ad55fe2 | ||
|
|
735f12648e | ||
|
|
6fe55a4d6b | ||
|
|
b8ce9f7787 | ||
|
|
6772403d81 | ||
|
|
60a91a82e1 | ||
|
|
4a274b69ac | ||
|
|
e4edd0a00f | ||
|
|
ae26ef3bdb | ||
|
|
049ecfe0a5 | ||
|
|
01a4d872ad | ||
|
|
a9e54bb725 | ||
|
|
f1525c0ca7 | ||
|
|
ad9b9ec69f | ||
|
|
8a37303925 | ||
|
|
52d234f1f4 | ||
|
|
a3c39576df | ||
|
|
433043c0c8 | ||
|
|
b38c7b274f | ||
|
|
efa7946c14 | ||
|
|
7bb4a09a3c | ||
|
|
3eca044895 | ||
|
|
32da708f54 | ||
|
|
f27ec773a1 | ||
|
|
ce70c0df6a | ||
|
|
fdc75cb9fe | ||
|
|
697040b918 | ||
|
|
19d1379f7d | ||
|
|
07aae79cae |
@@ -5,7 +5,7 @@
|
||||
[](https://github.com/crazywhalecc/static-php-cli/releases)
|
||||
[](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
|
||||
[](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
|
||||
[](https://discord.gg/nrSRbpMJ)
|
||||
[](https://discord.gg/xf6Rd4pEAk)
|
||||
|
||||
**StaticPHP** 是一个强大的工具,用于构建可移植的可执行文件,包括 PHP、扩展等。
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[](https://github.com/crazywhalecc/static-php-cli/releases)
|
||||
[](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
|
||||
[](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
|
||||
[](https://discord.gg/nrSRbpMJ)
|
||||
[](https://discord.gg/xf6Rd4pEAk)
|
||||
|
||||
**StaticPHP** is a powerful tool designed for building portable executables including PHP, extensions, and more.
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@ ncurses:
|
||||
- COPYING
|
||||
source:
|
||||
type: filelist
|
||||
url: 'https://ftp.gnu.org/pub/gnu/ncurses/'
|
||||
url: 'https://ftpmirror.gnu.org/gnu/ncurses/'
|
||||
regex: '/href="(?<file>ncurses-(?<version>[^"]+)\.tar\.gz)"/'
|
||||
|
||||
@@ -103,7 +103,7 @@ AR=${SPC_DEFAULT_AR}
|
||||
RANLIB=${SPC_DEFAULT_RANLIB}
|
||||
LD=${SPC_DEFAULT_LD}
|
||||
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
|
||||
SPC_DEFAULT_CFLAGS="-fPIC -O3 -pipe -fno-plt -fno-semantic-interposition -fstack-clash-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections"
|
||||
SPC_DEFAULT_CFLAGS="-fPIC -O3 -pipe -fno-plt -fno-semantic-interposition -fstack-clash-protection -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections -Wno-unused-command-line-argument"
|
||||
SPC_DEFAULT_CXXFLAGS="${SPC_DEFAULT_CFLAGS}"
|
||||
SPC_DEFAULT_LDFLAGS="-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -Wl,-z,noexecstack -Wl,--gc-sections"
|
||||
; upx executable path
|
||||
@@ -146,7 +146,7 @@ AR=${SPC_DEFAULT_AR}
|
||||
RANLIB=${SPC_DEFAULT_RANLIB}
|
||||
LD=${SPC_DEFAULT_LD}
|
||||
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
|
||||
SPC_DEFAULT_CFLAGS="--target=${MAC_ARCH}-apple-darwin -O3 -fno-plt -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections"
|
||||
SPC_DEFAULT_CFLAGS="--target=${MAC_ARCH}-apple-darwin -O3 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections"
|
||||
SPC_DEFAULT_CXXFLAGS="${SPC_DEFAULT_CFLAGS}"
|
||||
SPC_DEFAULT_LDFLAGS="-Wl,-dead_strip"
|
||||
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches
|
||||
|
||||
20
config/pkg/ext/ext-fastchart.yml
Normal file
20
config/pkg/ext/ext-fastchart.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
ext-fastchart:
|
||||
type: php-extension
|
||||
artifact:
|
||||
source:
|
||||
type: ghtar
|
||||
repo: iliaal/fastchart
|
||||
extract: php-src/ext/fastchart
|
||||
prefer-stable: true
|
||||
metadata:
|
||||
license-files: [LICENSE]
|
||||
depends:
|
||||
- freetype
|
||||
suggests:
|
||||
- libpng
|
||||
- libjpeg
|
||||
- libwebp
|
||||
php-extension:
|
||||
os:
|
||||
- Linux
|
||||
- Darwin
|
||||
14
config/pkg/ext/ext-fastjson.yml
Normal file
14
config/pkg/ext/ext-fastjson.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
ext-fastjson:
|
||||
type: php-extension
|
||||
artifact:
|
||||
source:
|
||||
type: ghtar
|
||||
repo: iliaal/fastjson
|
||||
extract: php-src/ext/fastjson
|
||||
prefer-stable: true
|
||||
metadata:
|
||||
license-files: [LICENSE]
|
||||
php-extension:
|
||||
os:
|
||||
- Linux
|
||||
- Darwin
|
||||
@@ -4,6 +4,6 @@ gettext-win:
|
||||
source:
|
||||
type: git
|
||||
url: 'https://github.com/winlibs/gettext.git'
|
||||
rev: master
|
||||
rev: '0.18'
|
||||
static-libs@windows:
|
||||
- libintl_a.lib
|
||||
|
||||
@@ -3,7 +3,7 @@ gettext:
|
||||
artifact:
|
||||
source:
|
||||
type: filelist
|
||||
url: 'https://ftp.gnu.org/pub/gnu/gettext/'
|
||||
url: 'https://ftpmirror.gnu.org/gnu/gettext/'
|
||||
regex: '/href="(?<file>gettext-(?<version>[^"]+)\.tar\.xz)"/'
|
||||
metadata:
|
||||
license-files: [gettext-runtime/intl/COPYING.LIB]
|
||||
|
||||
@@ -3,7 +3,7 @@ gmp:
|
||||
artifact:
|
||||
source:
|
||||
type: filelist
|
||||
url: 'https://ftp.gnu.org/gnu/gmp/'
|
||||
url: 'https://ftpmirror.gnu.org/gnu/gmp/'
|
||||
regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/'
|
||||
source-mirror:
|
||||
type: url
|
||||
|
||||
@@ -3,7 +3,7 @@ idn2:
|
||||
artifact:
|
||||
source:
|
||||
type: filelist
|
||||
url: 'https://ftp.gnu.org/gnu/libidn/'
|
||||
url: 'https://ftpmirror.gnu.org/gnu/libidn/'
|
||||
regex: '/href="(?<file>libidn2-(?<version>[^"]+)\.tar\.gz)"/'
|
||||
metadata:
|
||||
license-files: [COPYING.LESSERv3]
|
||||
|
||||
@@ -3,7 +3,7 @@ libiconv:
|
||||
artifact:
|
||||
source:
|
||||
type: filelist
|
||||
url: 'https://ftp.gnu.org/gnu/libiconv/'
|
||||
url: 'https://ftpmirror.gnu.org/gnu/libiconv/'
|
||||
regex: '/href="(?<file>libiconv-(?<version>[^"]+)\.tar\.gz)"/'
|
||||
metadata:
|
||||
license-files: [COPYING.LIB]
|
||||
|
||||
@@ -3,7 +3,7 @@ libunistring:
|
||||
artifact:
|
||||
source:
|
||||
type: filelist
|
||||
url: 'https://ftp.gnu.org/gnu/libunistring/'
|
||||
url: 'https://ftpmirror.gnu.org/gnu/libunistring/'
|
||||
regex: '/href="(?<file>libunistring-(?<version>[^"]+)\.tar\.gz)"/'
|
||||
metadata:
|
||||
license-files: [COPYING.LIB]
|
||||
|
||||
@@ -3,7 +3,7 @@ readline:
|
||||
artifact:
|
||||
source:
|
||||
type: filelist
|
||||
url: 'https://ftp.gnu.org/pub/gnu/readline/'
|
||||
url: 'https://ftpmirror.gnu.org/gnu/readline/'
|
||||
regex: '/href="(?<file>readline-(?<version>[^"]+)\.tar\.gz)"/'
|
||||
metadata:
|
||||
license-files: [COPYING]
|
||||
|
||||
6
config/pkg/target/llvm-compiler-rt.yml
Normal file
6
config/pkg/target/llvm-compiler-rt.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
llvm-compiler-rt:
|
||||
type: target
|
||||
artifact:
|
||||
binary: custom
|
||||
depends:
|
||||
- zig
|
||||
6
config/pkg/target/llvm-tools.yml
Normal file
6
config/pkg/target/llvm-tools.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
llvm-tools:
|
||||
type: target
|
||||
artifact:
|
||||
binary: custom
|
||||
depends:
|
||||
- zig
|
||||
@@ -47,7 +47,7 @@ Build your CLI with [symfony/console](https://symfony.com/doc/current/components
|
||||
|
||||
## Community
|
||||
|
||||
Join our [Discord server](https://discord.gg/nrSRbpMJ) to ask questions, share your builds, and connect with other StaticPHP users.
|
||||
Join our [Discord server](https://discord.gg/xf6Rd4pEAk) to ask questions, share your builds, and connect with other StaticPHP users.
|
||||
|
||||
## Next steps
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ StaticPHP 支持将 FrankenPHP 连同所需扩展一起静态编译,
|
||||
|
||||
## 社区
|
||||
|
||||
加入我们的 [Discord 服务器](https://discord.gg/nrSRbpMJ),提问、分享构建成果,与其他 StaticPHP 用户交流。
|
||||
加入我们的 [Discord 服务器](https://discord.gg/xf6Rd4pEAk),提问、分享构建成果,与其他 StaticPHP 用户交流。
|
||||
|
||||
## 接下来
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ class go_xcaddy
|
||||
'GOROOT' => "{$target_path}",
|
||||
'GOBIN' => "{$target_path}/bin",
|
||||
'GOPATH' => "{$target_path}/go",
|
||||
])->exec('CGO_ENABLED=0 go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest');
|
||||
])->exec('CGO_ENABLED=0 go install github.com/caddyserver/xcaddy/cmd/xcaddy@master');
|
||||
GlobalEnvManager::addPathIfNotExists("{$target_path}/bin");
|
||||
}
|
||||
}
|
||||
|
||||
193
src/Package/Artifact/llvm_compiler_rt.php
Normal file
193
src/Package/Artifact/llvm_compiler_rt.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Package\Artifact;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
|
||||
use StaticPHP\Artifact\Downloader\Type\GitHubTokenSetupTrait;
|
||||
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
|
||||
use StaticPHP\Attribute\Artifact\CustomBinary;
|
||||
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
|
||||
use StaticPHP\Exception\BuildFailureException;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
|
||||
/**
|
||||
* Builds the compiler-rt bits zig ships without — libclang_rt.profile.a (PGO instrumentation)
|
||||
* and clang_rt.crtbegin.o/crtend.o (__dso_handle for shared libs). Target-arch specific:
|
||||
* libs land in PKG_ROOT_PATH/zig/lib/{triple}.
|
||||
* Also builds libclang_rt.cpu_model.a (__cpu_model / __cpu_indicator_init, the libgcc-compatible
|
||||
* globals that __builtin_cpu_supports()/__builtin_cpu_init() reference) for arches that have it.
|
||||
*/
|
||||
class llvm_compiler_rt
|
||||
{
|
||||
use GitHubTokenSetupTrait;
|
||||
|
||||
#[CustomBinary('llvm-compiler-rt', [
|
||||
'linux-x86_64',
|
||||
'linux-aarch64',
|
||||
'macos-x86_64',
|
||||
'macos-aarch64',
|
||||
])]
|
||||
public function downBinary(ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
$llvmVersion = $this->detectZigLlvmVersion()
|
||||
?? throw new DownloaderException('llvm-compiler-rt: could not detect bundled clang version from zig cc --version');
|
||||
$tarball = "compiler-rt-{$llvmVersion}.src.tar.xz";
|
||||
$url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-{$llvmVersion}/{$tarball}";
|
||||
$tarballPath = DOWNLOAD_PATH . '/' . $tarball;
|
||||
default_shell()->executeCurlDownload($url, $tarballPath, headers: $this->getGitHubTokenHeaders(), retries: $downloader->getRetry());
|
||||
return DownloadResult::archive($tarball, ['url' => $url, 'version' => $llvmVersion], extract: '{source_path}/llvm-compiler-rt', verified: false, version: $llvmVersion);
|
||||
}
|
||||
|
||||
#[CustomBinaryCheckUpdate('llvm-compiler-rt', [
|
||||
'linux-x86_64',
|
||||
'linux-aarch64',
|
||||
'macos-x86_64',
|
||||
'macos-aarch64',
|
||||
])]
|
||||
public function checkUpdateBinary(?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
|
||||
{
|
||||
$llvmVersion = $this->detectZigLlvmVersion()
|
||||
?? throw new DownloaderException('llvm-compiler-rt: could not detect bundled clang version from zig cc --version');
|
||||
return new CheckUpdateResult(
|
||||
old: $old_version,
|
||||
new: $llvmVersion,
|
||||
needUpdate: $old_version === null || $llvmVersion !== $old_version,
|
||||
);
|
||||
}
|
||||
|
||||
#[AfterBinaryExtract('llvm-compiler-rt', [
|
||||
'linux-x86_64',
|
||||
'linux-aarch64',
|
||||
'macos-x86_64',
|
||||
'macos-aarch64',
|
||||
])]
|
||||
public function postExtract(string $target_path): void
|
||||
{
|
||||
$this->buildForTriple($target_path);
|
||||
}
|
||||
|
||||
public function buildForTriple(?string $sourceDir = null, ?string $triple = null): void
|
||||
{
|
||||
$sourceDir ??= SOURCE_PATH . '/llvm-compiler-rt';
|
||||
$triple ??= SystemTarget::getCanonicalTriple();
|
||||
$libDir = zig::path() . '/lib/' . $triple;
|
||||
if ($this->isBuilt($libDir)) {
|
||||
return;
|
||||
}
|
||||
if (!is_dir($sourceDir) || !is_dir("{$sourceDir}/lib/profile")) {
|
||||
throw new BuildFailureException("llvm-compiler-rt: missing source at {$sourceDir} (extraction layout changed?)");
|
||||
}
|
||||
f_mkdir($libDir, recursive: true);
|
||||
$profileLib = "{$libDir}/libclang_rt.profile.a";
|
||||
$crtBegin = "{$libDir}/clang_rt.crtbegin.o";
|
||||
$crtEnd = "{$libDir}/clang_rt.crtend.o";
|
||||
if (!file_exists($profileLib)) {
|
||||
$this->buildProfileRuntime($sourceDir, $profileLib, $triple);
|
||||
}
|
||||
if (!file_exists($crtBegin) || !file_exists($crtEnd)) {
|
||||
$this->buildCrtObjects($sourceDir, $crtBegin, $crtEnd, $triple);
|
||||
}
|
||||
$cpuModelLib = "{$libDir}/libclang_rt.cpu_model.a";
|
||||
if (self::cpuModelArch($triple) !== null && !file_exists($cpuModelLib)) {
|
||||
$this->buildCpuModelBuiltins($sourceDir, $cpuModelLib, $triple);
|
||||
}
|
||||
}
|
||||
|
||||
public function isBuilt(string $libDir): bool
|
||||
{
|
||||
return file_exists("{$libDir}/libclang_rt.profile.a")
|
||||
&& file_exists("{$libDir}/clang_rt.crtbegin.o")
|
||||
&& file_exists("{$libDir}/clang_rt.crtend.o")
|
||||
&& (self::cpuModelArch(basename($libDir)) === null || file_exists("{$libDir}/libclang_rt.cpu_model.a"));
|
||||
}
|
||||
|
||||
private function detectZigLlvmVersion(): ?string
|
||||
{
|
||||
if (!zig::isInstalled()) {
|
||||
return null;
|
||||
}
|
||||
[$rc, $out] = shell()->execWithResult(escapeshellarg(zig::binary()) . ' cc --version', false);
|
||||
if ($rc !== 0) {
|
||||
return null;
|
||||
}
|
||||
return preg_match('/clang version (\d+\.\d+\.\d+)/', implode("\n", $out), $m) ? $m[1] : null;
|
||||
}
|
||||
|
||||
private function buildProfileRuntime(string $srcRoot, string $libPath, string $triple): void
|
||||
{
|
||||
$profileSrc = "{$srcRoot}/lib/profile";
|
||||
$profileInc = "{$srcRoot}/include";
|
||||
// Skip OS-specific sources we can't satisfy without their SDKs.
|
||||
$skip = ['/PlatformAIX', '/PlatformDarwin', '/PlatformFuchsia', '/PlatformOther', '/PlatformWindows', '/WindowsMMap'];
|
||||
$sources = array_filter(
|
||||
array_merge(glob("{$profileSrc}/*.c") ?: [], glob("{$profileSrc}/*.cpp") ?: []),
|
||||
fn ($f) => !array_any($skip, fn ($s) => str_contains($f, $s)),
|
||||
);
|
||||
|
||||
$objDir = "{$srcRoot}/obj-profile-{$triple}";
|
||||
f_mkdir($objDir, recursive: true);
|
||||
$cflags = "-target {$triple} -c -O2 -fPIC -fvisibility=hidden "
|
||||
. '-I' . escapeshellarg($profileInc) . ' '
|
||||
. '-DCOMPILER_RT_HAS_ATOMICS=1 -DCOMPILER_RT_HAS_FCNTL_LCK=1 -DCOMPILER_RT_HAS_UNAME=1';
|
||||
$srcArgs = implode(' ', array_map('escapeshellarg', $sources));
|
||||
shell()->cd($objDir)->exec("zig cc {$cflags} {$srcArgs}");
|
||||
shell()->cd($objDir)->exec('zig ar rcs ' . escapeshellarg($libPath) . ' *.o');
|
||||
}
|
||||
|
||||
private function buildCrtObjects(string $srcRoot, string $crtBegin, string $crtEnd, string $triple): void
|
||||
{
|
||||
$beginSrc = "{$srcRoot}/lib/builtins/crtbegin.c";
|
||||
$endSrc = "{$srcRoot}/lib/builtins/crtend.c";
|
||||
if (!is_file($beginSrc) || !is_file($endSrc)) {
|
||||
throw new BuildFailureException("llvm-compiler-rt: crtbegin/crtend source missing under {$srcRoot}/lib/builtins");
|
||||
}
|
||||
$cflags = "-target {$triple} -c -O2 -fPIC -fvisibility=hidden -DCRT_HAS_INITFINI_ARRAY";
|
||||
foreach ([[$beginSrc, $crtBegin], [$endSrc, $crtEnd]] as [$src, $dst]) {
|
||||
shell()->exec("zig cc {$cflags} -o " . escapeshellarg($dst) . ' ' . escapeshellarg($src));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build libclang_rt.cpu_model.a, provides
|
||||
* the globals that __builtin_cpu_supports() reference.
|
||||
*/
|
||||
private function buildCpuModelBuiltins(string $srcRoot, string $libPath, string $triple): void
|
||||
{
|
||||
$builtins = "{$srcRoot}/lib/builtins";
|
||||
$family = self::cpuModelArch($triple);
|
||||
$cpuModelDir = "{$builtins}/cpu_model";
|
||||
if (is_dir($cpuModelDir)) {
|
||||
$src = "{$cpuModelDir}/{$family}.c";
|
||||
$includes = '-I' . escapeshellarg($builtins) . ' -I' . escapeshellarg($cpuModelDir);
|
||||
} else {
|
||||
$src = "{$builtins}/cpu_model.c";
|
||||
$includes = '-I' . escapeshellarg($builtins);
|
||||
}
|
||||
if (!is_file($src)) {
|
||||
throw new BuildFailureException("llvm-compiler-rt: cpu_model source not found for {$triple} under {$builtins}");
|
||||
}
|
||||
|
||||
$objDir = "{$srcRoot}/obj-cpu-model-{$triple}";
|
||||
f_mkdir($objDir, recursive: true);
|
||||
$obj = "{$objDir}/cpu_model.o";
|
||||
$cflags = "-target {$triple} -c -O2 -fPIC {$includes}";
|
||||
shell()->exec('zig cc ' . $cflags . ' -o ' . escapeshellarg($obj) . ' ' . escapeshellarg($src));
|
||||
shell()->exec('zig ar rcs ' . escapeshellarg($libPath) . ' ' . escapeshellarg($obj));
|
||||
}
|
||||
|
||||
private static function cpuModelArch(string $triple): ?string
|
||||
{
|
||||
$arch = explode('-', $triple)[0];
|
||||
return match (true) {
|
||||
in_array($arch, ['x86_64', 'amd64', 'i386', 'i686', 'x86'], true) => 'x86',
|
||||
in_array($arch, ['aarch64', 'arm64'], true) => 'aarch64',
|
||||
str_starts_with($arch, 'riscv') => 'riscv',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
149
src/Package/Artifact/llvm_tools.php
Normal file
149
src/Package/Artifact/llvm_tools.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Package\Artifact;
|
||||
|
||||
use StaticPHP\Artifact\ArtifactDownloader;
|
||||
use StaticPHP\Artifact\Downloader\DownloadResult;
|
||||
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
|
||||
use StaticPHP\Artifact\Downloader\Type\GitHubTokenSetupTrait;
|
||||
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
|
||||
use StaticPHP\Attribute\Artifact\CustomBinary;
|
||||
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
|
||||
use StaticPHP\DI\ApplicationContext;
|
||||
use StaticPHP\Exception\BuildFailureException;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
use StaticPHP\Package\PackageBuilder;
|
||||
|
||||
class llvm_tools
|
||||
{
|
||||
use GitHubTokenSetupTrait;
|
||||
|
||||
public const array TOOLS = ['llvm-objcopy', 'llvm-strip', 'llvm-profdata'];
|
||||
|
||||
#[CustomBinary('llvm-tools', [
|
||||
'linux-x86_64',
|
||||
'linux-aarch64',
|
||||
'macos-x86_64',
|
||||
'macos-aarch64',
|
||||
])]
|
||||
public function downBinary(ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
$llvmVersion = $this->detectLlvmVersion()
|
||||
?? throw new DownloaderException('Could not detect a clang version on host; install zig or clang first');
|
||||
$tarball = "llvm-project-{$llvmVersion}.src.tar.xz";
|
||||
$url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-{$llvmVersion}/{$tarball}";
|
||||
$tarballPath = DOWNLOAD_PATH . '/' . $tarball;
|
||||
default_shell()->executeCurlDownload($url, $tarballPath, headers: $this->getGitHubTokenHeaders(), retries: $downloader->getRetry());
|
||||
return DownloadResult::archive($tarball, ['url' => $url, 'version' => $llvmVersion], extract: '{source_path}/llvm-tools', verified: false, version: $llvmVersion);
|
||||
}
|
||||
|
||||
#[CustomBinaryCheckUpdate('llvm-tools', [
|
||||
'linux-x86_64',
|
||||
'linux-aarch64',
|
||||
'macos-x86_64',
|
||||
'macos-aarch64',
|
||||
])]
|
||||
public function checkUpdateBinary(?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
|
||||
{
|
||||
$llvmVersion = $this->detectLlvmVersion()
|
||||
?? throw new DownloaderException('Could not detect a clang version on host; install zig or clang first');
|
||||
return new CheckUpdateResult(
|
||||
old: $old_version,
|
||||
new: $llvmVersion,
|
||||
needUpdate: $old_version === null || $llvmVersion !== $old_version,
|
||||
);
|
||||
}
|
||||
|
||||
#[AfterBinaryExtract('llvm-tools', [
|
||||
'linux-x86_64',
|
||||
'linux-aarch64',
|
||||
'macos-x86_64',
|
||||
'macos-aarch64',
|
||||
])]
|
||||
public function postExtract(string $target_path): void
|
||||
{
|
||||
$this->buildForHost($target_path);
|
||||
}
|
||||
|
||||
public function buildForHost(?string $sourceRoot = null): void
|
||||
{
|
||||
$sourceRoot ??= SOURCE_PATH . '/llvm-tools';
|
||||
$binDir = PKG_ROOT_PATH . '/llvm-tools/bin';
|
||||
if ($this->allBuilt($binDir)) {
|
||||
return;
|
||||
}
|
||||
$llvmDir = "{$sourceRoot}/llvm";
|
||||
if (!is_dir($llvmDir)) {
|
||||
throw new BuildFailureException("llvm-tools: missing source at {$llvmDir} (extraction layout changed?)");
|
||||
}
|
||||
$buildDir = "{$sourceRoot}/build";
|
||||
$installDir = PKG_ROOT_PATH . '/llvm-tools';
|
||||
f_mkdir($buildDir, recursive: true);
|
||||
f_mkdir($binDir, recursive: true);
|
||||
|
||||
$cmakeArgs = implode(' ', array_map('escapeshellarg', [
|
||||
'-S', $llvmDir,
|
||||
'-B', $buildDir,
|
||||
'-DCMAKE_BUILD_TYPE=Release',
|
||||
'-DLLVM_ENABLE_PROJECTS=',
|
||||
'-DLLVM_TARGETS_TO_BUILD=',
|
||||
'-DLLVM_INCLUDE_BENCHMARKS=OFF',
|
||||
'-DLLVM_INCLUDE_TESTS=OFF',
|
||||
'-DLLVM_INCLUDE_EXAMPLES=OFF',
|
||||
'-DLLVM_INCLUDE_DOCS=OFF',
|
||||
'-DLLVM_ENABLE_ZLIB=OFF',
|
||||
'-DLLVM_ENABLE_ZSTD=OFF',
|
||||
'-DLLVM_ENABLE_LIBXML2=OFF',
|
||||
'-DLLVM_ENABLE_TERMINFO=OFF',
|
||||
'-DLLVM_ENABLE_LIBEDIT=OFF',
|
||||
'-DLLVM_ENABLE_LIBPFM=OFF',
|
||||
'-DLLVM_BUILD_LLVM_DYLIB=OFF',
|
||||
'-DLLVM_LINK_LLVM_DYLIB=OFF',
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DCMAKE_C_COMPILER=' . zig::binary('zig-cc'),
|
||||
'-DCMAKE_CXX_COMPILER=' . zig::binary('zig-c++'),
|
||||
'-DCMAKE_INSTALL_PREFIX=' . $installDir,
|
||||
]));
|
||||
$jobs = ApplicationContext::get(PackageBuilder::class)->concurrency;
|
||||
$targetArgs = implode(' ', array_map(fn ($t) => '--target ' . escapeshellarg($t), self::TOOLS));
|
||||
|
||||
shell()
|
||||
->setEnv(['SPC_TARGET' => GNU_ARCH . '-linux-musl'])
|
||||
->exec('cmake ' . $cmakeArgs)
|
||||
->exec('cmake --build ' . escapeshellarg($buildDir) . ' ' . $targetArgs . " -j {$jobs}");
|
||||
|
||||
foreach (self::TOOLS as $t) {
|
||||
$built = "{$buildDir}/bin/{$t}";
|
||||
if (!is_file($built)) {
|
||||
throw new BuildFailureException("llvm-tools: missing build output {$built}");
|
||||
}
|
||||
copy($built, "{$binDir}/{$t}");
|
||||
chmod("{$binDir}/{$t}", 0755);
|
||||
}
|
||||
}
|
||||
|
||||
public function allBuilt(string $binDir): bool
|
||||
{
|
||||
foreach (self::TOOLS as $t) {
|
||||
$p = "{$binDir}/{$t}";
|
||||
if (!is_file($p) || !is_executable($p)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function detectLlvmVersion(): ?string
|
||||
{
|
||||
if (!zig::isInstalled()) {
|
||||
return null;
|
||||
}
|
||||
[$rc, $out] = shell()->execWithResult(escapeshellarg(zig::binary()) . ' cc --version', false);
|
||||
if ($rc !== 0) {
|
||||
return null;
|
||||
}
|
||||
return preg_match('/clang version (\d+\.\d+\.\d+)/', implode("\n", $out), $m) ? $m[1] : null;
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,26 @@ use StaticPHP\Attribute\Artifact\CustomBinary;
|
||||
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
|
||||
use StaticPHP\Exception\DownloaderException;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
|
||||
class zig
|
||||
{
|
||||
/** Directory zig extracts into. */
|
||||
public static function path(): string
|
||||
{
|
||||
return PKG_ROOT_PATH . '/zig';
|
||||
}
|
||||
|
||||
/** Path to a binary inside the zig install dir (zig, zig-cc, zig-c++, zig-ar, …). */
|
||||
public static function binary(string $name = 'zig'): string
|
||||
{
|
||||
return self::path() . '/' . $name;
|
||||
}
|
||||
|
||||
public static function isInstalled(): bool
|
||||
{
|
||||
return is_file(self::binary());
|
||||
}
|
||||
|
||||
#[CustomBinary('zig', [
|
||||
'linux-x86_64',
|
||||
'linux-aarch64',
|
||||
@@ -111,172 +127,24 @@ class zig
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($all_exist) {
|
||||
return;
|
||||
}
|
||||
if (!$all_exist) {
|
||||
$script_path = ROOT_DIR . '/src/globals/scripts/zig-cc.sh';
|
||||
$script_content = file_get_contents($script_path);
|
||||
|
||||
$script_path = ROOT_DIR . '/src/globals/scripts/zig-cc.sh';
|
||||
$script_content = file_get_contents($script_path);
|
||||
file_put_contents("{$target_path}/zig-cc", $script_content);
|
||||
chmod("{$target_path}/zig-cc", 0755);
|
||||
|
||||
file_put_contents("{$target_path}/zig-cc", $script_content);
|
||||
chmod("{$target_path}/zig-cc", 0755);
|
||||
|
||||
$script_content = str_replace('zig cc', 'zig c++', $script_content);
|
||||
file_put_contents("{$target_path}/zig-c++", $script_content);
|
||||
file_put_contents("{$target_path}/zig-ar", "#!/usr/bin/env bash\nexec zig ar $@");
|
||||
file_put_contents("{$target_path}/zig-ld.lld", "#!/usr/bin/env bash\nexec zig ld.lld $@");
|
||||
file_put_contents("{$target_path}/zig-ranlib", "#!/usr/bin/env bash\nexec zig ranlib $@");
|
||||
file_put_contents("{$target_path}/zig-objcopy", "#!/usr/bin/env bash\nexec zig objcopy $@");
|
||||
chmod("{$target_path}/zig-c++", 0755);
|
||||
chmod("{$target_path}/zig-ar", 0755);
|
||||
chmod("{$target_path}/zig-ld.lld", 0755);
|
||||
chmod("{$target_path}/zig-ranlib", 0755);
|
||||
chmod("{$target_path}/zig-objcopy", 0755);
|
||||
|
||||
// Build the clang runtime bits zig 0.15+ doesn't ship: profile runtime
|
||||
// (so -fprofile-generate actually emits .profraw) and crtbegin/crtend
|
||||
// (so shared libs get __dso_handle and the __cxa_finalize atexit hook).
|
||||
// These get auto-linked by the zig-cc wrapper when the right flags fly past.
|
||||
$this->buildClangRuntimeBits($target_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the bundled clang version and build the missing clang runtime
|
||||
* archives into `<zig>/lib/`. Compiles from a 2 MB compiler-rt-<llvm>.src
|
||||
* tarball — far cheaper than fetching the 2 GB prebuilt LLVM tarball.
|
||||
*/
|
||||
private function buildClangRuntimeBits(string $zig_bin_dir): void
|
||||
{
|
||||
if (PHP_OS_FAMILY !== 'Linux') {
|
||||
return;
|
||||
$script_content = str_replace('zig cc', 'zig c++', $script_content);
|
||||
file_put_contents("{$target_path}/zig-c++", $script_content);
|
||||
file_put_contents("{$target_path}/zig-ar", "#!/usr/bin/env bash\nexec zig ar $@");
|
||||
file_put_contents("{$target_path}/zig-ld.lld", "#!/usr/bin/env bash\nexec zig ld.lld $@");
|
||||
file_put_contents("{$target_path}/zig-ranlib", "#!/usr/bin/env bash\nexec zig ranlib $@");
|
||||
file_put_contents("{$target_path}/zig-objcopy", "#!/usr/bin/env bash\nexec zig objcopy $@");
|
||||
chmod("{$target_path}/zig-c++", 0755);
|
||||
chmod("{$target_path}/zig-ar", 0755);
|
||||
chmod("{$target_path}/zig-ld.lld", 0755);
|
||||
chmod("{$target_path}/zig-ranlib", 0755);
|
||||
chmod("{$target_path}/zig-objcopy", 0755);
|
||||
}
|
||||
$libDir = "{$zig_bin_dir}/lib";
|
||||
$profileLib = "{$libDir}/libclang_rt.profile.a";
|
||||
$crtBegin = "{$libDir}/clang_rt.crtbegin.o";
|
||||
$crtEnd = "{$libDir}/clang_rt.crtend.o";
|
||||
if (file_exists($profileLib) && file_exists($crtBegin) && file_exists($crtEnd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$zig = "{$zig_bin_dir}/zig";
|
||||
$verLine = trim((string) shell_exec(escapeshellarg($zig) . ' cc --version 2>/dev/null'));
|
||||
if (!preg_match('/clang version (\d+\.\d+\.\d+)/', $verLine, $m)) {
|
||||
logger()->warning('[zig] could not detect bundled clang version; skipping runtime bit build (PGO + shared libs without __dso_handle will fail to link)');
|
||||
return;
|
||||
}
|
||||
$llvmVersion = $m[1];
|
||||
logger()->info("Building clang runtime bits for LLVM {$llvmVersion} (zig's bundled clang)");
|
||||
|
||||
$srcRoot = $this->fetchCompilerRtSource($llvmVersion);
|
||||
if ($srcRoot === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
f_mkdir($libDir, recursive: true);
|
||||
if (!file_exists($profileLib)) {
|
||||
$this->buildProfileRuntime($zig, $srcRoot, $profileLib);
|
||||
}
|
||||
if (!file_exists($crtBegin) || !file_exists($crtEnd)) {
|
||||
$this->buildCrtObjects($zig, $srcRoot, $crtBegin, $crtEnd);
|
||||
}
|
||||
FileSystem::removeDir($srcRoot);
|
||||
}
|
||||
|
||||
private function fetchCompilerRtSource(string $llvmVersion): ?string
|
||||
{
|
||||
$tarball = "compiler-rt-{$llvmVersion}.src.tar.xz";
|
||||
$url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-{$llvmVersion}/{$tarball}";
|
||||
$tarballPath = DOWNLOAD_PATH . '/' . $tarball;
|
||||
if (!file_exists($tarballPath)) {
|
||||
try {
|
||||
default_shell()->executeCurlDownload($url, $tarballPath);
|
||||
} catch (\Throwable $e) {
|
||||
logger()->warning("[zig] failed to download {$tarball}: {$e->getMessage()}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
$srcRoot = PKG_ROOT_PATH . "/compiler-rt-src-{$llvmVersion}";
|
||||
FileSystem::removeDir($srcRoot);
|
||||
FileSystem::createDir($srcRoot);
|
||||
try {
|
||||
default_shell()->executeTarExtract($tarballPath, $srcRoot, 'xz');
|
||||
} catch (\Throwable $e) {
|
||||
logger()->warning("[zig] failed to extract {$tarball}: {$e->getMessage()}");
|
||||
return null;
|
||||
}
|
||||
return $srcRoot;
|
||||
}
|
||||
|
||||
private function buildProfileRuntime(string $zig, string $srcRoot, string $libPath): void
|
||||
{
|
||||
$profileSrc = "{$srcRoot}/lib/profile";
|
||||
$profileInc = "{$srcRoot}/include";
|
||||
if (!is_dir($profileSrc)) {
|
||||
logger()->warning("[zig] profile src dir missing at {$profileSrc} — PGO will not work");
|
||||
return;
|
||||
}
|
||||
$sources = array_merge(
|
||||
glob("{$profileSrc}/*.c") ?: [],
|
||||
glob("{$profileSrc}/*.cpp") ?: []
|
||||
);
|
||||
// Keep Linux-only compilation units; the others bring in OS-specific headers
|
||||
// we can't satisfy without their respective SDKs.
|
||||
$skip = ['/PlatformAIX', '/PlatformDarwin', '/PlatformFuchsia', '/PlatformOther', '/PlatformWindows', '/WindowsMMap'];
|
||||
$sources = array_filter($sources, function ($f) use ($skip) {
|
||||
foreach ($skip as $s) {
|
||||
if (str_contains($f, $s)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
$objDir = "{$srcRoot}/obj-profile";
|
||||
f_mkdir($objDir, recursive: true);
|
||||
$cflags = '-c -O2 -fPIC -fvisibility=hidden ' .
|
||||
'-I' . escapeshellarg($profileInc) . ' ' .
|
||||
'-DCOMPILER_RT_HAS_ATOMICS=1 -DCOMPILER_RT_HAS_FCNTL_LCK=1 -DCOMPILER_RT_HAS_UNAME=1';
|
||||
$objs = [];
|
||||
foreach ($sources as $src) {
|
||||
$obj = $objDir . '/' . pathinfo($src, PATHINFO_FILENAME) . '.o';
|
||||
$cmd = escapeshellarg($zig) . ' cc ' . $cflags . ' -o ' . escapeshellarg($obj) . ' ' . escapeshellarg($src) . ' 2>&1';
|
||||
if (!$this->runZigCmd($cmd, $obj, "failed to compile {$src}")) {
|
||||
return;
|
||||
}
|
||||
$objs[] = $obj;
|
||||
}
|
||||
$arCmd = escapeshellarg($zig) . ' ar rcs ' . escapeshellarg($libPath) . ' ' . implode(' ', array_map('escapeshellarg', $objs)) . ' 2>&1';
|
||||
if (!$this->runZigCmd($arCmd, $libPath, 'zig ar failed')) {
|
||||
return;
|
||||
}
|
||||
logger()->info('[zig] libclang_rt.profile.a installed (' . filesize($libPath) . ' bytes)');
|
||||
}
|
||||
|
||||
private function buildCrtObjects(string $zig, string $srcRoot, string $crtBegin, string $crtEnd): void
|
||||
{
|
||||
$beginSrc = "{$srcRoot}/lib/builtins/crtbegin.c";
|
||||
$endSrc = "{$srcRoot}/lib/builtins/crtend.c";
|
||||
if (!is_file($beginSrc) || !is_file($endSrc)) {
|
||||
logger()->error("[zig] crtbegin/crtend source missing under {$srcRoot}/lib/builtins — shared libs will lack __dso_handle");
|
||||
return;
|
||||
}
|
||||
$cflags = '-c -O2 -fPIC -fvisibility=hidden -DCRT_HAS_INITFINI_ARRAY';
|
||||
foreach ([[$beginSrc, $crtBegin], [$endSrc, $crtEnd]] as [$src, $dst]) {
|
||||
$cmd = escapeshellarg($zig) . ' cc ' . $cflags . ' -o ' . escapeshellarg($dst) . ' ' . escapeshellarg($src) . ' 2>&1';
|
||||
if (!$this->runZigCmd($cmd, $dst, "failed to compile {$src}")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
logger()->info('[zig] clang_rt.crtbegin.o + clang_rt.crtend.o installed (' . filesize($crtBegin) . ' + ' . filesize($crtEnd) . ' bytes)');
|
||||
}
|
||||
|
||||
private function runZigCmd(string $cmd, string $dst, string $errPrefix): bool
|
||||
{
|
||||
exec($cmd, $out, $rc);
|
||||
if ($rc !== 0 || !is_file($dst)) {
|
||||
logger()->warning("[zig] {$errPrefix}: " . implode("\n", $out));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,29 +4,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace Package\Extension;
|
||||
|
||||
use Package\Target\php;
|
||||
use StaticPHP\Attribute\Package\BeforeStage;
|
||||
use StaticPHP\Attribute\Package\CustomPhpConfigureArg;
|
||||
use StaticPHP\Attribute\Package\Extension;
|
||||
use StaticPHP\Attribute\PatchDescription;
|
||||
use StaticPHP\Package\PackageInstaller;
|
||||
use StaticPHP\Package\PhpExtensionPackage;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
|
||||
#[Extension('clickhouse')]
|
||||
class clickhouse extends PhpExtensionPackage
|
||||
{
|
||||
#[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-clickhouse')]
|
||||
#[PatchDescription('Replace THIS_DIR=`dirname $0` with PHP_EXT_SRCDIR() in config.m4 so include paths resolve to the ext source dir during PHP main configure (dirname $0 returns "." when run from php-src root).')]
|
||||
public function patchBeforeBuildconfUnix(): void
|
||||
{
|
||||
FileSystem::replaceFileRegex(
|
||||
"{$this->getSourceDir()}/config.m4",
|
||||
'/^(\s*)THIS_DIR=.*/m',
|
||||
'$1THIS_DIR=PHP_EXT_SRCDIR()',
|
||||
);
|
||||
}
|
||||
|
||||
#[CustomPhpConfigureArg('Darwin')]
|
||||
#[CustomPhpConfigureArg('Linux')]
|
||||
public function getUnixConfigureArg(bool $shared, PackageInstaller $installer): string
|
||||
|
||||
@@ -16,6 +16,14 @@ use StaticPHP\Util\FileSystem;
|
||||
#[Extension('mongodb')]
|
||||
class mongodb extends PhpExtensionPackage
|
||||
{
|
||||
#[BeforeStage('php', [php::class, 'configureForUnix'], 'ext-mongodb')]
|
||||
#[PatchDescription('Export PHP_VERSION_ID so mongo-php-driver >= 2.3.3 skips its php-config lookup (in-tree builds have no php-config).')]
|
||||
public function exportPhpVersionIdForUnix(): void
|
||||
{
|
||||
$id = php::getPHPVersionID();
|
||||
f_putenv("PHP_VERSION_ID={$id}");
|
||||
}
|
||||
|
||||
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-mongodb')]
|
||||
#[PatchDescription('Add /utf-8 flag to CFLAGS_MONGODB for Windows build to fix compilation error on non-English Windows.')]
|
||||
public function patchBeforeBuild(): void
|
||||
|
||||
@@ -27,7 +27,7 @@ class password_argon2 extends PhpExtensionPackage
|
||||
#[CustomPhpConfigureArg('Darwin')]
|
||||
public function getConfigureArg(PackageInstaller $installer, PackageBuilder $builder): string
|
||||
{
|
||||
if ($installer->getPhpExtensionPackage('openssl')?->isBuildStatic() || $this->isBuildShared()) {
|
||||
if ($installer->getPhpExtensionPackage('openssl')?->isBuildStatic()) {
|
||||
if (php::getPHPVersionID() >= 80500 || (php::getPHPVersionID() >= 80400 && !$builder->getOption('enable-zts'))) {
|
||||
return '--without-password-argon2'; // use --with-openssl-argon2 in openssl extension instead
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ class imagemagick
|
||||
->addConfigureArgs(
|
||||
'--disable-openmp',
|
||||
'--without-x',
|
||||
// implicit --with-gcc-arch
|
||||
// bleeds host cpu features into built binaries
|
||||
'--without-gcc-arch',
|
||||
);
|
||||
|
||||
// special: linux-static target needs `-static`
|
||||
|
||||
@@ -9,8 +9,10 @@ use StaticPHP\Attribute\Package\Library;
|
||||
use StaticPHP\Package\LibraryPackage;
|
||||
use StaticPHP\Runtime\Executor\UnixCMakeExecutor;
|
||||
use StaticPHP\Runtime\Executor\WindowsCMakeExecutor;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Toolchain\Interface\ToolchainInterface;
|
||||
use StaticPHP\Toolchain\ZigToolchain;
|
||||
use StaticPHP\Util\System\UnixUtil;
|
||||
|
||||
#[Library('libaom')]
|
||||
class libaom extends LibraryPackage
|
||||
@@ -39,9 +41,23 @@ class libaom extends LibraryPackage
|
||||
$new = trim($extra . ' -D_GNU_SOURCE');
|
||||
f_putenv("SPC_COMPILER_EXTRA={$new}");
|
||||
}
|
||||
$targetCpu = SystemTarget::getTargetArch();
|
||||
if (str_starts_with($targetCpu, 'aarch')) {
|
||||
$targetCpu = str_replace('aarch', 'arm', $targetCpu);
|
||||
}
|
||||
if (!UnixUtil::findCommand('nasm') && !UnixUtil::findCommand('yasm')) {
|
||||
$targetCpu = 'generic';
|
||||
}
|
||||
UnixCMakeExecutor::create($this)
|
||||
->setBuildDir("{$this->getSourceDir()}/builddir")
|
||||
->addConfigureArgs('-DAOM_TARGET_CPU=generic')
|
||||
->addConfigureArgs(
|
||||
"-DAOM_TARGET_CPU={$targetCpu}",
|
||||
'-DCONFIG_RUNTIME_CPU_DETECT=1',
|
||||
'-DENABLE_EXAMPLES=OFF',
|
||||
'-DENABLE_TESTS=OFF',
|
||||
'-DENABLE_TOOLS=OFF',
|
||||
'-DENABLE_DOCS=OFF',
|
||||
)
|
||||
->build();
|
||||
f_putenv("SPC_COMPILER_EXTRA={$extra}");
|
||||
$this->patchPkgconfPrefix(['aom.pc']);
|
||||
|
||||
@@ -8,6 +8,7 @@ use StaticPHP\Attribute\Package\BuildFor;
|
||||
use StaticPHP\Attribute\Package\Library;
|
||||
use StaticPHP\Package\LibraryPackage;
|
||||
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
|
||||
#[Library('libffi')]
|
||||
class libffi extends LibraryPackage
|
||||
@@ -28,7 +29,7 @@ class libffi extends LibraryPackage
|
||||
#[BuildFor('Darwin')]
|
||||
public function buildDarwin(): void
|
||||
{
|
||||
$arch = getenv('SPC_ARCH');
|
||||
$arch = SystemTarget::getTargetArch();
|
||||
UnixAutoconfExecutor::create($this)
|
||||
->configure(
|
||||
"--host={$arch}-apple-darwin",
|
||||
|
||||
@@ -24,6 +24,17 @@ class libheif
|
||||
'list(APPEND REQUIRES_PRIVATE "libbrotlidec")' . "\n" . ' list(APPEND REQUIRES_PRIVATE "libbrotlienc")'
|
||||
);
|
||||
}
|
||||
// libheif 1.22+ ships a C-incompatible header: `struct heif_bad_pixel`
|
||||
$heif_properties = $lib->getSourceDir() . '/libheif/api/libheif/heif_properties.h';
|
||||
if (file_exists($heif_properties)
|
||||
&& str_contains(file_get_contents($heif_properties), 'struct heif_bad_pixel { uint32_t row; uint32_t column; };')
|
||||
) {
|
||||
FileSystem::replaceFileStr(
|
||||
$heif_properties,
|
||||
'struct heif_bad_pixel { uint32_t row; uint32_t column; };',
|
||||
'typedef struct heif_bad_pixel { uint32_t row; uint32_t column; } heif_bad_pixel;'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[BuildFor('Darwin')]
|
||||
|
||||
@@ -9,6 +9,7 @@ use StaticPHP\Attribute\Package\Library;
|
||||
use StaticPHP\Package\LibraryPackage;
|
||||
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
|
||||
use StaticPHP\Runtime\Executor\WindowsCMakeExecutor;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
|
||||
#[Library('libpng')]
|
||||
@@ -24,7 +25,7 @@ class libpng
|
||||
];
|
||||
|
||||
// Enable architecture-specific optimizations
|
||||
match (getenv('SPC_ARCH')) {
|
||||
match (SystemTarget::getTargetArch()) {
|
||||
'x86_64' => $args[] = '--enable-intel-sse',
|
||||
'aarch64' => $args[] = '--enable-arm-neon',
|
||||
default => null,
|
||||
|
||||
@@ -65,6 +65,7 @@ class ncurses
|
||||
'--without-tests',
|
||||
'--without-dlsym',
|
||||
'--without-debug',
|
||||
'--disable-stripping',
|
||||
'--enable-symlinks',
|
||||
"--with-terminfo-dirs={$terminfo_dirs}",
|
||||
"--bindir={$package->getBinDir()}",
|
||||
|
||||
@@ -76,7 +76,7 @@ class openssl
|
||||
public function buildForDarwin(LibraryPackage $pkg): void
|
||||
{
|
||||
$zlib_libs = $pkg->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles();
|
||||
$arch = getenv('SPC_ARCH');
|
||||
$arch = SystemTarget::getTargetArch();
|
||||
|
||||
shell()->cd($pkg->getSourceDir())->initializeEnv($pkg)
|
||||
->exec(
|
||||
@@ -95,12 +95,7 @@ class openssl
|
||||
#[BuildFor('Linux')]
|
||||
public function build(LibraryPackage $lib): void
|
||||
{
|
||||
$arch = getenv('SPC_ARCH');
|
||||
|
||||
$env = "CC='" . getenv('CC') . ' -idirafter ' . BUILD_INCLUDE_PATH .
|
||||
' -idirafter /usr/include/ ' .
|
||||
' -idirafter /usr/include/' . getenv('SPC_ARCH') . '-linux-gnu/ ' .
|
||||
"' ";
|
||||
$arch = SystemTarget::getTargetArch();
|
||||
|
||||
$ex_lib = trim($lib->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles()) . ' -ldl -pthread';
|
||||
$zlib_extra =
|
||||
@@ -119,7 +114,7 @@ class openssl
|
||||
|
||||
shell()->cd($lib->getSourceDir())->initializeEnv($lib)
|
||||
->exec(
|
||||
"{$env} ./Configure no-shared zlib " .
|
||||
'./Configure no-shared zlib ' .
|
||||
"--prefix={$lib->getBuildRootPath()} " .
|
||||
"--libdir={$lib->getLibDir()} " .
|
||||
"--openssldir={$openssl_dir} " .
|
||||
|
||||
@@ -93,6 +93,9 @@ class curl
|
||||
if (str_contains(FileSystem::readFile($pc_path), '-lgssapi_krb5')) {
|
||||
FileSystem::replaceFileRegex($pc_path, '/-lcom_err$/m', '-lcom_err -lkrb5support');
|
||||
}
|
||||
// FindThreads can put '-lpthread' into INTERFACE_LINK_LIBRARIES; curl's pc generator
|
||||
// prepends '-l' to each entry, producing '-l-lpthread'. Strip the extra '-l'.
|
||||
FileSystem::replaceFileRegex($pc_path, '/-l(-l\S+)/', '$1');
|
||||
shell()->cd("{$lib->getLibDir()}/cmake/CURL/")
|
||||
->exec("sed -ie 's|\"/lib/libcurl.a\"|\"{$lib->getLibDir()}/libcurl.a\"|g' CURLTargets-release.cmake");
|
||||
|
||||
|
||||
@@ -270,12 +270,14 @@ class php extends TargetPackage
|
||||
}
|
||||
}
|
||||
// linux does not support loading shared libraries when target is pure static
|
||||
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
|
||||
if (SystemTarget::getTargetOS() === 'Linux' && ApplicationContext::get(ToolchainInterface::class)->isStatic() && $embed_type === 'shared') {
|
||||
throw new WrongUsageException(
|
||||
'Linux does not support loading shared libraries when linking libc statically. ' .
|
||||
'Change SPC_CMD_VAR_PHP_EMBED_TYPE to static.'
|
||||
);
|
||||
if ($package->getName() === 'php-embed') {
|
||||
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
|
||||
if (SystemTarget::getTargetOS() === 'Linux' && ApplicationContext::get(ToolchainInterface::class)->isStatic() && $embed_type === 'shared') {
|
||||
throw new WrongUsageException(
|
||||
'Linux does not support loading shared libraries when linking libc statically. ' .
|
||||
'Change SPC_CMD_VAR_PHP_EMBED_TYPE to static.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +336,7 @@ class php extends TargetPackage
|
||||
logger()->info("Adding hardcoded INI [{$source_name} = {$ini_value}]");
|
||||
}
|
||||
if (!empty($custom_ini)) {
|
||||
ApplicationContext::invoke([SourcePatcher::class, 'patchHardcodedINI'], [$package->getSourceDir(), $custom_ini]);
|
||||
ApplicationContext::invoke([SourcePatcher::class, 'patchHardcodedINI'], ['php_source_dir' => $package->getSourceDir(), 'ini' => $custom_ini]);
|
||||
}
|
||||
|
||||
// Patch StaticPHP version
|
||||
|
||||
@@ -73,10 +73,7 @@ trait frankenphp
|
||||
$staticFlags = '';
|
||||
}
|
||||
|
||||
$resolved = array_keys($installer->getResolvedPackages());
|
||||
// remove self from deps
|
||||
$resolved = array_filter($resolved, fn ($pkg_name) => $pkg_name !== $package->getName());
|
||||
$config = new SPCConfigUtil()->config($resolved);
|
||||
$config = new SPCConfigUtil()->config(['frankenphp']);
|
||||
$cflags = "{$package->getLibExtraCFlags()} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . " -DFRANKENPHP_VERSION={$frankenphp_version}";
|
||||
$libs = $config['libs'];
|
||||
|
||||
@@ -104,11 +101,12 @@ trait frankenphp
|
||||
"-tags={$muslTags}nobadger,nomysql,nopgx{$no_brotli}{$no_watcher}",
|
||||
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
|
||||
];
|
||||
$pgo = file_exists("{$source_dir}/caddy/frankenphp/default.pgo") ? "--pgo {$source_dir}/caddy/frankenphp/default.pgo " : '';
|
||||
InteractiveTerm::setMessage('Building frankenphp: ' . ConsoleColor::yellow('building with xcaddy'));
|
||||
shell()->cd(BUILD_LIB_PATH)
|
||||
->setEnv($env)
|
||||
->exec('go clean -cache') // fix stale include evaluation
|
||||
->exec("xcaddy build --output frankenphp {$xcaddy_modules}");
|
||||
->exec("xcaddy build --output frankenphp {$pgo}{$xcaddy_modules}");
|
||||
|
||||
$builder->deployBinary(BUILD_LIB_PATH . '/frankenphp', BUILD_BIN_PATH . '/frankenphp');
|
||||
$package->setOutput('Binary path for FrankenPHP SAPI', BUILD_BIN_PATH . '/frankenphp');
|
||||
|
||||
@@ -42,6 +42,15 @@ trait unix
|
||||
// php-src patches from micro (reads SPC_MICRO_PATCHES env var)
|
||||
SourcePatcher::patchPhpSrc();
|
||||
|
||||
$microFileinfo = "{$package->getSourceDir()}/sapi/micro/php_micro_fileinfo.c";
|
||||
if (is_file($microFileinfo) && !str_contains((string) file_get_contents($microFileinfo), 'Elf32_Shdr')) {
|
||||
FileSystem::replaceFileStr(
|
||||
$microFileinfo,
|
||||
'typedef Elf32_Ehdr Elf_Ehdr;',
|
||||
'typedef Elf32_Ehdr Elf_Ehdr; typedef Elf32_Shdr Elf_Shdr;',
|
||||
);
|
||||
}
|
||||
|
||||
// patch configure.ac for musl and musl-toolchain
|
||||
$musl = SystemTarget::getTargetOS() === 'Linux' && SystemTarget::getLibc() === 'musl';
|
||||
FileSystem::backupFile(SOURCE_PATH . '/php-src/configure.ac');
|
||||
@@ -128,6 +137,10 @@ trait unix
|
||||
$args[] = $installer->isPackageResolved('php-cgi') ? '--enable-cgi' : '--disable-cgi';
|
||||
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
|
||||
$args[] = $installer->isPackageResolved('php-embed') ? "--enable-embed={$embed_type}" : '--disable-embed';
|
||||
// Cross-compile: pass --host so configure picks the correct fiber asm file and host_cpu logic
|
||||
if ($host_triple = SystemTarget::getAutoconfHostTriple()) {
|
||||
$args[] = "--host={$host_triple}";
|
||||
}
|
||||
$args[] = getenv('SPC_EXTRA_PHP_VARS') ?: null;
|
||||
$args = implode(' ', array_filter($args));
|
||||
|
||||
@@ -540,6 +553,8 @@ trait unix
|
||||
$package->runStage([$this, 'makeForUnix']);
|
||||
}
|
||||
|
||||
// shared extensions build before frankenphp so their undefined references are
|
||||
// collected into libphp's exported dynamic-symbol list.
|
||||
$package->runStage([$this, 'unixBuildSharedExt']);
|
||||
}
|
||||
|
||||
@@ -652,7 +667,7 @@ trait unix
|
||||
copy(ROOT_DIR . '/src/globals/common-tests/embed.c', $sample_file_path . '/embed.c');
|
||||
copy(ROOT_DIR . '/src/globals/common-tests/embed.php', $sample_file_path . '/embed.php');
|
||||
|
||||
$config = new SPCConfigUtil()->config($installer->getAvailableResolvedPackageNames());
|
||||
$config = new SPCConfigUtil()->config(['php']);
|
||||
$lens = "{$config['cflags']} {$config['ldflags']} {$config['libs']}";
|
||||
if ($toolchain->isStatic()) {
|
||||
$lens .= ' -static';
|
||||
@@ -739,7 +754,7 @@ trait unix
|
||||
*/
|
||||
private function makeVars(PackageInstaller $installer): array
|
||||
{
|
||||
$config = new SPCConfigUtil(['libs_only_deps' => true])->config($installer->getAvailableResolvedPackageNames());
|
||||
$config = new SPCConfigUtil(['libs_only_deps' => true])->config(['php']);
|
||||
$static = ApplicationContext::get(ToolchainInterface::class)->isStatic() ? '-all-static' : '';
|
||||
$pie = SystemTarget::getTargetOS() === 'Linux' ? '-pie' : '';
|
||||
$lib = BUILD_LIB_PATH;
|
||||
|
||||
@@ -27,12 +27,18 @@ class Artifact
|
||||
/** @var null|callable Bind custom source fetcher callback */
|
||||
protected mixed $custom_source_callback = null;
|
||||
|
||||
/** @var null|string Display label describing where the custom source callback came from */
|
||||
protected ?string $custom_source_callback_origin = null;
|
||||
|
||||
/** @var null|callable Bind custom source check-update callback */
|
||||
protected mixed $custom_source_check_update_callback = null;
|
||||
|
||||
/** @var array<string, callable> Bind custom binary fetcher callbacks */
|
||||
protected mixed $custom_binary_callbacks = [];
|
||||
|
||||
/** @var array<string, string> Display label per platform describing where the custom binary callback came from */
|
||||
protected array $custom_binary_callback_origins = [];
|
||||
|
||||
/** @var array<string, callable> Bind custom binary check-update callbacks */
|
||||
protected array $custom_binary_check_update_callbacks = [];
|
||||
|
||||
@@ -331,7 +337,7 @@ class Artifact
|
||||
public function getSourceRoot(): string
|
||||
{
|
||||
if (isset($this->config['metadata']['source-root'])) {
|
||||
return $this->getSourceDir() . '/' . ltrim($this->config['metadata']['source-root'], '/');
|
||||
return FileSystem::convertPath($this->getSourceDir() . '/' . ltrim($this->config['metadata']['source-root'], '/'));
|
||||
}
|
||||
return $this->getSourceDir();
|
||||
}
|
||||
@@ -411,10 +417,13 @@ class Artifact
|
||||
|
||||
/**
|
||||
* Set custom source fetcher callback.
|
||||
*
|
||||
* @param string $origin Short label shown in progress output (e.g. 'package downloader', 'custom url')
|
||||
*/
|
||||
public function setCustomSourceCallback(callable $callback): void
|
||||
public function setCustomSourceCallback(callable $callback, string $origin = 'package downloader'): void
|
||||
{
|
||||
$this->custom_source_callback = $callback;
|
||||
$this->custom_source_callback_origin = $origin;
|
||||
}
|
||||
|
||||
public function getCustomSourceCallback(): ?callable
|
||||
@@ -422,6 +431,11 @@ class Artifact
|
||||
return $this->custom_source_callback ?? null;
|
||||
}
|
||||
|
||||
public function getCustomSourceCallbackOrigin(): ?string
|
||||
{
|
||||
return $this->custom_source_callback_origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom source check-update callback.
|
||||
*/
|
||||
@@ -456,11 +470,19 @@ class Artifact
|
||||
*
|
||||
* @param string $target_os Target OS platform string (e.g. linux-x86_64)
|
||||
* @param callable $callback Custom binary fetcher callback
|
||||
* @param string $origin Short label shown in progress output (e.g. 'package downloader')
|
||||
*/
|
||||
public function setCustomBinaryCallback(string $target_os, callable $callback): void
|
||||
public function setCustomBinaryCallback(string $target_os, callable $callback, string $origin = 'package downloader'): void
|
||||
{
|
||||
ConfigValidator::validatePlatformString($target_os);
|
||||
$this->custom_binary_callbacks[$target_os] = $callback;
|
||||
$this->custom_binary_callback_origins[$target_os] = $origin;
|
||||
}
|
||||
|
||||
public function getCustomBinaryCallbackOrigin(): ?string
|
||||
{
|
||||
$current_platform = SystemTarget::getCurrentPlatformString();
|
||||
return $this->custom_binary_callback_origins[$current_platform] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -223,8 +223,8 @@ class ArtifactCache
|
||||
public function getCacheFullPath(array $cache_info): string
|
||||
{
|
||||
return match ($cache_info['cache_type']) {
|
||||
'archive', 'file' => DOWNLOAD_PATH . '/' . $cache_info['filename'],
|
||||
'git' => DOWNLOAD_PATH . '/' . $cache_info['dirname'],
|
||||
'archive', 'file' => FileSystem::convertPath(DOWNLOAD_PATH . '/' . $cache_info['filename']),
|
||||
'git' => FileSystem::convertPath(DOWNLOAD_PATH . '/' . $cache_info['dirname']),
|
||||
'local' => $cache_info['dirname'], // local dirname is absolute path
|
||||
default => throw new SPCInternalException("Unknown cache type: {$cache_info['cache_type']}"),
|
||||
};
|
||||
|
||||
@@ -317,7 +317,10 @@ class ArtifactDownloader
|
||||
if (!is_dir(DOWNLOAD_PATH)) {
|
||||
FileSystem::createDir(DOWNLOAD_PATH);
|
||||
}
|
||||
logger()->info('Downloading' . implode(', ', array_map(fn ($x) => " '{$x->getName()}'", $this->artifacts)) . " with concurrency {$this->parallel} ...");
|
||||
$pending = array_values(array_filter($this->artifacts, fn ($a) => $this->generateQueue($a) !== []));
|
||||
if ($pending !== []) {
|
||||
logger()->info('Downloading' . implode(', ', array_map(fn ($x) => " '{$x->getName()}'", $pending)) . " with concurrency {$this->parallel} ...");
|
||||
}
|
||||
// Download artifacts parallelly
|
||||
if ($this->parallel > 1) {
|
||||
$this->downloadWithConcurrency();
|
||||
@@ -551,8 +554,8 @@ class ArtifactDownloader
|
||||
$instance = null;
|
||||
$call = $this->downloaders[$item['config']['type']] ?? null;
|
||||
$type_display_name = match (true) {
|
||||
$item['lock'] === 'source' && ($callback = $artifact->getCustomSourceCallback()) !== null => 'user defined source downloader',
|
||||
$item['lock'] === 'binary' && ($callback = $artifact->getCustomBinaryCallback()) !== null => 'user defined binary downloader',
|
||||
$item['lock'] === 'source' && $artifact->getCustomSourceCallback() !== null => $artifact->getCustomSourceCallbackOrigin() ?? 'source package downloader',
|
||||
$item['lock'] === 'binary' && $artifact->getCustomBinaryCallback() !== null => $artifact->getCustomBinaryCallbackOrigin() ?? 'binary package downloader',
|
||||
default => SPC_DOWNLOAD_TYPE_DISPLAY_NAME[$item['config']['type']] ?? $item['config']['type'],
|
||||
};
|
||||
$try_h = $try ? 'Try downloading' : 'Downloading';
|
||||
@@ -835,21 +838,21 @@ class ArtifactDownloader
|
||||
if (isset($this->artifacts[$artifact_name])) {
|
||||
$this->artifacts[$artifact_name]->setCustomSourceCallback(function (ArtifactDownloader $downloader) use ($artifact_name, $custom_url) {
|
||||
return (new Url())->download($artifact_name, ['url' => $custom_url], $downloader);
|
||||
});
|
||||
}, 'custom url');
|
||||
}
|
||||
}
|
||||
foreach ($this->custom_gits as $artifact_name => [$branch, $git_url]) {
|
||||
if (isset($this->artifacts[$artifact_name])) {
|
||||
$this->artifacts[$artifact_name]->setCustomSourceCallback(function (ArtifactDownloader $downloader) use ($artifact_name, $branch, $git_url) {
|
||||
return (new Git())->download($artifact_name, ['rev' => $branch, 'url' => $git_url], $downloader);
|
||||
});
|
||||
}, 'custom git');
|
||||
}
|
||||
}
|
||||
foreach ($this->custom_locals as $artifact_name => $local_path) {
|
||||
if (isset($this->artifacts[$artifact_name])) {
|
||||
$this->artifacts[$artifact_name]->setCustomSourceCallback(function (ArtifactDownloader $downloader) use ($artifact_name, $local_path) {
|
||||
return (new LocalDir())->download($artifact_name, ['dirname' => $local_path], $downloader);
|
||||
});
|
||||
}, 'custom local dir');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@ trait GitHubTokenSetupTrait
|
||||
// GITHUB_TOKEN support
|
||||
if (($token = getenv('GITHUB_TOKEN')) !== false && ($user = getenv('GITHUB_USER')) !== false) {
|
||||
logger()->debug("Using 'GITHUB_TOKEN' with user {$user} for authentication");
|
||||
spc_add_log_filter([$user, $token]);
|
||||
return ['Authorization: Basic ' . base64_encode("{$user}:{$token}")];
|
||||
$encoded = base64_encode("{$user}:{$token}");
|
||||
spc_add_log_filter([$user, $token, $encoded]);
|
||||
return ["Authorization: Basic {$encoded}"];
|
||||
}
|
||||
if (($token = getenv('GITHUB_TOKEN')) !== false) {
|
||||
logger()->debug("Using 'GITHUB_TOKEN' for authentication");
|
||||
|
||||
@@ -10,8 +10,10 @@ use StaticPHP\Exception\ValidationException;
|
||||
use StaticPHP\Package\PackageBuilder;
|
||||
use StaticPHP\Package\PackageInstaller;
|
||||
use StaticPHP\Registry\PackageLoader;
|
||||
use StaticPHP\Toolchain\ToolchainManager;
|
||||
use StaticPHP\Util\DependencyResolver;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
use StaticPHP\Util\GlobalEnvManager;
|
||||
use StaticPHP\Util\Pgo\PgoContext;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
@@ -44,6 +46,10 @@ class CraftCommand extends BaseCommand
|
||||
// apply env
|
||||
array_walk($craft['extra-env'], fn ($v, $k) => f_putenv("{$k}={$v}"));
|
||||
|
||||
// re-substitute env.ini's CC=${SPC_DEFAULT_CC} bindings.
|
||||
ToolchainManager::initToolchain();
|
||||
GlobalEnvManager::reapplyOsIni();
|
||||
|
||||
// stash craft for doctor checks that depend on what's being built (e.g. frankenphp → go-xcaddy)
|
||||
ApplicationContext::set('craft', $craft);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use StaticPHP\DI\ApplicationContext;
|
||||
use StaticPHP\Registry\ArtifactLoader;
|
||||
use StaticPHP\Registry\PackageLoader;
|
||||
use StaticPHP\Util\DependencyResolver;
|
||||
use StaticPHP\Util\GlobalEnvManager;
|
||||
use StaticPHP\Util\InteractiveTerm;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -34,6 +35,7 @@ class ExtractCommand extends BaseCommand
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
GlobalEnvManager::afterInit();
|
||||
$cache = ApplicationContext::get(ArtifactCache::class);
|
||||
$extractor = new ArtifactExtractor($cache);
|
||||
$force_source = (bool) $this->getOption('source-only');
|
||||
|
||||
@@ -44,7 +44,7 @@ class SPCConfigCommand extends BaseCommand
|
||||
'absolute_libs' => (bool) $this->getOption('absolute-libs'),
|
||||
]);
|
||||
$packages = array_merge(array_map(fn ($x) => "ext-{$x}", $extensions), $libraries);
|
||||
$config = $util->config($packages, $include_suggests);
|
||||
$config = $util->config($packages);
|
||||
|
||||
$this->output->writeln(match (true) {
|
||||
$this->getOption('includes') => $config['cflags'],
|
||||
|
||||
48
src/StaticPHP/Doctor/Item/LlvmCompilerRtCheck.php
Normal file
48
src/StaticPHP/Doctor/Item/LlvmCompilerRtCheck.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Doctor\Item;
|
||||
|
||||
use Package\Artifact\llvm_compiler_rt;
|
||||
use Package\Artifact\zig;
|
||||
use StaticPHP\Attribute\Doctor\CheckItem;
|
||||
use StaticPHP\Attribute\Doctor\FixItem;
|
||||
use StaticPHP\Attribute\Doctor\OptionalCheck;
|
||||
use StaticPHP\DI\ApplicationContext;
|
||||
use StaticPHP\Doctor\CheckResult;
|
||||
use StaticPHP\Package\PackageInstaller;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Toolchain\Interface\ToolchainInterface;
|
||||
use StaticPHP\Toolchain\ZigToolchain;
|
||||
|
||||
#[OptionalCheck([self::class, 'optionalCheck'])]
|
||||
class LlvmCompilerRtCheck
|
||||
{
|
||||
public static function optionalCheck(): bool
|
||||
{
|
||||
return ApplicationContext::get(ToolchainInterface::class) instanceof ZigToolchain;
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
#[CheckItem('if llvm-compiler-rt is built for current target', level: 799)]
|
||||
public function checkLlvmCompilerRt(): CheckResult
|
||||
{
|
||||
$libDir = zig::path() . '/lib/' . SystemTarget::getCanonicalTriple();
|
||||
if (new llvm_compiler_rt()->isBuilt($libDir)) {
|
||||
return CheckResult::ok($libDir);
|
||||
}
|
||||
return CheckResult::fail('llvm-compiler-rt is not built for ' . SystemTarget::getCanonicalTriple(), 'build-llvm-compiler-rt');
|
||||
}
|
||||
|
||||
#[FixItem('build-llvm-compiler-rt')]
|
||||
public function fixLlvmCompilerRt(): bool
|
||||
{
|
||||
$installer = new PackageInstaller(interactive: false);
|
||||
$installer->addInstallPackage('llvm-compiler-rt');
|
||||
$installer->run(true);
|
||||
new llvm_compiler_rt()->buildForTriple();
|
||||
$libDir = zig::path() . '/lib/' . SystemTarget::getCanonicalTriple();
|
||||
return new llvm_compiler_rt()->isBuilt($libDir);
|
||||
}
|
||||
}
|
||||
45
src/StaticPHP/Doctor/Item/LlvmToolsCheck.php
Normal file
45
src/StaticPHP/Doctor/Item/LlvmToolsCheck.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Doctor\Item;
|
||||
|
||||
use Package\Artifact\llvm_tools;
|
||||
use StaticPHP\Attribute\Doctor\CheckItem;
|
||||
use StaticPHP\Attribute\Doctor\FixItem;
|
||||
use StaticPHP\Attribute\Doctor\OptionalCheck;
|
||||
use StaticPHP\DI\ApplicationContext;
|
||||
use StaticPHP\Doctor\CheckResult;
|
||||
use StaticPHP\Package\PackageInstaller;
|
||||
use StaticPHP\Toolchain\Interface\ToolchainInterface;
|
||||
use StaticPHP\Toolchain\ZigToolchain;
|
||||
|
||||
#[OptionalCheck([self::class, 'optionalCheck'])]
|
||||
class LlvmToolsCheck
|
||||
{
|
||||
public static function optionalCheck(): bool
|
||||
{
|
||||
return ApplicationContext::get(ToolchainInterface::class) instanceof ZigToolchain;
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
#[CheckItem('if llvm-tools (objcopy/strip/profdata) are built', level: 798)]
|
||||
public function checkLlvmTools(): CheckResult
|
||||
{
|
||||
$binDir = PKG_ROOT_PATH . '/llvm-tools/bin';
|
||||
if (new llvm_tools()->allBuilt($binDir)) {
|
||||
return CheckResult::ok($binDir);
|
||||
}
|
||||
return CheckResult::fail('llvm-tools are not built', 'build-llvm-tools');
|
||||
}
|
||||
|
||||
#[FixItem('build-llvm-tools')]
|
||||
public function fixLlvmTools(): bool
|
||||
{
|
||||
$installer = new PackageInstaller(interactive: false);
|
||||
$installer->addInstallPackage('llvm-tools');
|
||||
$installer->run(true);
|
||||
new llvm_tools()->buildForHost();
|
||||
return new llvm_tools()->allBuilt(PKG_ROOT_PATH . '/llvm-tools/bin');
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Doctor\Item;
|
||||
|
||||
use Package\Artifact\zig;
|
||||
use StaticPHP\Attribute\Doctor\CheckItem;
|
||||
use StaticPHP\Attribute\Doctor\FixItem;
|
||||
use StaticPHP\Attribute\Doctor\OptionalCheck;
|
||||
@@ -26,7 +27,7 @@ class ZigCheck
|
||||
public function checkZig(): CheckResult
|
||||
{
|
||||
if (new PackageInstaller()->addInstallPackage('zig')->isPackageInstalled('zig')) {
|
||||
return CheckResult::ok(PKG_ROOT_PATH . '/zig/zig');
|
||||
return CheckResult::ok(zig::binary());
|
||||
}
|
||||
return CheckResult::fail('zig is not installed', 'install-zig');
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ use StaticPHP\Exception\SPCInternalException;
|
||||
use StaticPHP\Exception\WrongUsageException;
|
||||
use StaticPHP\Runtime\Shell\Shell;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Toolchain\Interface\ToolchainInterface;
|
||||
use StaticPHP\Toolchain\ZigToolchain;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
use StaticPHP\Util\GlobalPathTrait;
|
||||
use StaticPHP\Util\InteractiveTerm;
|
||||
@@ -178,14 +180,15 @@ class PackageBuilder
|
||||
if (SystemTarget::getTargetOS() === 'Darwin') {
|
||||
shell()->exec("dsymutil -f {$binary_path} -o {$debug_file}");
|
||||
} elseif (SystemTarget::getTargetOS() === 'Linux') {
|
||||
$objcopy = getenv('OBJCOPY') ?: 'objcopy';
|
||||
if ($eu_strip = LinuxUtil::findCommand('eu-strip')) {
|
||||
shell()
|
||||
->exec("{$eu_strip} -f {$debug_file} {$binary_path}")
|
||||
->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}");
|
||||
->exec("{$objcopy} --add-gnu-debuglink={$debug_file} {$binary_path}");
|
||||
} else {
|
||||
shell()
|
||||
->exec("objcopy --only-keep-debug {$binary_path} {$debug_file}")
|
||||
->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}");
|
||||
->exec("{$objcopy} --only-keep-debug {$binary_path} {$debug_file}")
|
||||
->exec("{$objcopy} --add-gnu-debuglink={$debug_file} {$binary_path}");
|
||||
}
|
||||
} else {
|
||||
logger()->debug('extractDebugInfo is only supported on Linux and macOS');
|
||||
@@ -199,9 +202,12 @@ class PackageBuilder
|
||||
*/
|
||||
public function stripBinary(string $binary_path): void
|
||||
{
|
||||
$strip = ApplicationContext::tryGet(ToolchainInterface::class) instanceof ZigToolchain
|
||||
? PKG_ROOT_PATH . '/llvm-tools/bin/llvm-strip'
|
||||
: 'strip';
|
||||
shell()->exec(match (SystemTarget::getTargetOS()) {
|
||||
'Darwin' => "strip -S {$binary_path}",
|
||||
'Linux' => "strip --strip-unneeded {$binary_path}",
|
||||
'Darwin' => "{$strip} -S {$binary_path}",
|
||||
'Linux' => "{$strip} --strip-unneeded {$binary_path}",
|
||||
'Windows' => 'echo "Skip strip on Windows"', // Windows strip is not available for now
|
||||
default => throw new SPCInternalException('stripBinary is only supported on Linux and macOS'),
|
||||
});
|
||||
|
||||
@@ -155,6 +155,7 @@ class PackageInstaller
|
||||
}
|
||||
|
||||
$this->reconcilePhpSrcVersion();
|
||||
$this->emitPreInstallEvents();
|
||||
|
||||
if ($this->interactive && !$disable_delay_msg) {
|
||||
// show install or build options in terminal with beautiful output
|
||||
@@ -361,6 +362,15 @@ class PackageInstaller
|
||||
return false;
|
||||
}
|
||||
|
||||
public function emitPreInstallEvents(): void
|
||||
{
|
||||
foreach ($this->packages as $package) {
|
||||
if ($package->hasStage('preInstall')) {
|
||||
$package->runStage('preInstall');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function emitPostInstallEvents(): void
|
||||
{
|
||||
foreach ($this->packages as $package) {
|
||||
|
||||
@@ -286,7 +286,7 @@ class PhpExtensionPackage extends Package
|
||||
}
|
||||
return [
|
||||
'CFLAGS' => $config['cflags'],
|
||||
'CXXFLAGS' => $config['cflags'],
|
||||
'CXXFLAGS' => $config['cxxflags'],
|
||||
'LDFLAGS' => $ldflags,
|
||||
'LIBS' => clean_spaces("{$preStatic} {$staticLibs} {$postStatic} {$sharedLibs}"),
|
||||
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
|
||||
|
||||
@@ -36,6 +36,13 @@ class ArtifactLoader
|
||||
public static function getArtifactInstance(string $artifact_name): ?Artifact
|
||||
{
|
||||
self::initArtifactInstances();
|
||||
if (!isset(self::$artifacts[$artifact_name])) {
|
||||
// Artifact may have been registered after initArtifactInstances() ran (e.g., from a vendor registry)
|
||||
$config = ArtifactConfig::get($artifact_name);
|
||||
if ($config !== null) {
|
||||
self::$artifacts[$artifact_name] = new Artifact($artifact_name, $config);
|
||||
}
|
||||
}
|
||||
return self::$artifacts[$artifact_name] ?? null;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use StaticPHP\Package\LibraryPackage;
|
||||
use StaticPHP\Package\PackageBuilder;
|
||||
use StaticPHP\Package\PackageInstaller;
|
||||
use StaticPHP\Runtime\Shell\UnixShell;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Util\InteractiveTerm;
|
||||
use ZM\Logger\ConsoleColor;
|
||||
|
||||
@@ -149,13 +150,17 @@ class UnixAutoconfExecutor extends Executor
|
||||
*/
|
||||
private function getDefaultConfigureArgs(): array
|
||||
{
|
||||
return [
|
||||
$args = [
|
||||
'--disable-shared',
|
||||
'--enable-static',
|
||||
"--prefix={$this->package->getBuildRootPath()}",
|
||||
'--with-pic',
|
||||
'--enable-pic',
|
||||
];
|
||||
if ($host_triple = SystemTarget::getAutoconfHostTriple()) {
|
||||
$args[] = "--host={$host_triple}";
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Runtime;
|
||||
|
||||
use StaticPHP\Toolchain\ZigToolchain;
|
||||
use StaticPHP\Util\System\LinuxUtil;
|
||||
|
||||
/**
|
||||
@@ -16,7 +17,7 @@ class SystemTarget
|
||||
*/
|
||||
public static function getLibc(): ?string
|
||||
{
|
||||
if ($target = getenv('SPC_TARGET')) {
|
||||
if ($target = self::target()) {
|
||||
if (str_contains($target, '-gnu')) {
|
||||
return 'glibc';
|
||||
}
|
||||
@@ -57,7 +58,7 @@ class SystemTarget
|
||||
public static function getLibcVersion(): ?string
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Linux') {
|
||||
$target = (string) getenv('SPC_TARGET');
|
||||
$target = self::target();
|
||||
if (str_contains($target, '-gnu.2.')) {
|
||||
return preg_match('/-gnu\.(2\.\d+)/', $target, $matches) ? $matches[1] : null;
|
||||
}
|
||||
@@ -75,7 +76,7 @@ class SystemTarget
|
||||
*/
|
||||
public static function getTargetOS(): string
|
||||
{
|
||||
$target = (string) getenv('SPC_TARGET');
|
||||
$target = self::target();
|
||||
return match (true) {
|
||||
str_contains($target, '-linux') => 'Linux',
|
||||
str_contains($target, '-macos') => 'Darwin',
|
||||
@@ -91,7 +92,7 @@ class SystemTarget
|
||||
*/
|
||||
public static function getTargetArch(): string
|
||||
{
|
||||
$target = (string) getenv('SPC_TARGET');
|
||||
$target = self::target();
|
||||
return match (true) {
|
||||
str_contains($target, 'x86_64') || str_contains($target, 'amd64') => 'x86_64',
|
||||
str_contains($target, 'aarch64') || str_contains($target, 'arm64') => 'aarch64',
|
||||
@@ -127,4 +128,70 @@ class SystemTarget
|
||||
{
|
||||
return in_array(self::getTargetOS(), ['Linux', 'Darwin', 'BSD']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical target triple (arch-os-abi) for per-target build
|
||||
* artifacts. Always returns a non-null triple, falling back to a host-derived
|
||||
* triple when SPC_TARGET is unset or names 'native'.
|
||||
* Strips libc version suffix (-gnu.2.17 → -gnu) and trailing flags (' -dynamic').
|
||||
*/
|
||||
public static function getCanonicalTriple(): string
|
||||
{
|
||||
$target = self::target();
|
||||
if ($target !== '' && !str_contains($target, 'native')) {
|
||||
$cleaned = (string) preg_replace('/(-gnu|-musl)\.[\d.]+/', '$1', $target);
|
||||
$cleaned = preg_split('/\s+/', trim($cleaned))[0] ?? '';
|
||||
if ($cleaned !== '') {
|
||||
return $cleaned;
|
||||
}
|
||||
}
|
||||
$arch = self::getTargetArch();
|
||||
return match (self::getTargetOS()) {
|
||||
'Linux' => $arch . '-linux-' . (self::getLibc() === 'musl' ? 'musl' : 'gnu'),
|
||||
'Darwin' => $arch . '-macos-none',
|
||||
'Windows' => $arch . '-windows-gnu',
|
||||
default => $arch . '-unknown-unknown',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a GNU host triple for autoconf --host= when SPC_TARGET names an
|
||||
* architecture different from the build host (true cross-compile).
|
||||
* Returns null for same-arch builds.
|
||||
* Strips libc version suffix (-gnu.2.17 → -gnu) and trailing flags (e.g. ' -dynamic').
|
||||
*/
|
||||
public static function getAutoconfHostTriple(): ?string
|
||||
{
|
||||
$target = self::target();
|
||||
if ($target === '' || str_contains($target, 'native')) {
|
||||
return null;
|
||||
}
|
||||
$cleaned = preg_split('/\s+/', trim((string) preg_replace('/(-gnu|-musl)\.[\d.]+/', '$1', $target)))[0];
|
||||
if ($cleaned === '') {
|
||||
return null;
|
||||
}
|
||||
// Only emit --host for true cross-arch builds; same-arch (incl. cross-libc) lets autoconf detect.
|
||||
$target_arch_token = explode('-', $cleaned)[0];
|
||||
$arch_aliases = [
|
||||
'x86_64' => ['x86_64', 'amd64'],
|
||||
'aarch64' => ['aarch64', 'arm64'],
|
||||
'arm' => ['arm', 'armv6', 'armv7', 'armhf', 'armel'],
|
||||
'i386' => ['i386', 'i486', 'i586', 'i686'],
|
||||
];
|
||||
$host_arch = GNU_ARCH;
|
||||
if (array_any($arch_aliases, fn ($aliases) => in_array($target_arch_token, $aliases, true) && in_array($host_arch, $aliases, true))) {
|
||||
return null;
|
||||
}
|
||||
return $cleaned;
|
||||
}
|
||||
|
||||
/** native toolchains ignore SPC_TARGET */
|
||||
private static function target(): string
|
||||
{
|
||||
$tc = (string) getenv('SPC_TOOLCHAIN');
|
||||
if ($tc !== '' && $tc !== ZigToolchain::class) {
|
||||
return '';
|
||||
}
|
||||
return (string) getenv('SPC_TARGET');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,22 @@ declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Toolchain;
|
||||
|
||||
use Package\Artifact\llvm_compiler_rt;
|
||||
use Package\Artifact\zig;
|
||||
use StaticPHP\DI\ApplicationContext;
|
||||
use StaticPHP\Package\PackageBuilder;
|
||||
use StaticPHP\Package\PackageInstaller;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Toolchain\Interface\UnixToolchainInterface;
|
||||
use StaticPHP\Util\GlobalEnvManager;
|
||||
use StaticPHP\Util\InteractiveTerm;
|
||||
use StaticPHP\Util\System\LinuxUtil;
|
||||
use ZM\Logger\ConsoleColor;
|
||||
|
||||
class ZigToolchain implements UnixToolchainInterface
|
||||
{
|
||||
private static bool $afterInitDone = false;
|
||||
|
||||
public function initEnv(): void
|
||||
{
|
||||
// Set environment variables for zig toolchain
|
||||
@@ -18,12 +28,25 @@ class ZigToolchain implements UnixToolchainInterface
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_AR=zig-ar');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=zig-ranlib');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_LD=zig-ld.lld');
|
||||
GlobalEnvManager::addPathIfNotExists($this->getPath());
|
||||
}
|
||||
|
||||
public function afterInit(): void
|
||||
{
|
||||
GlobalEnvManager::addPathIfNotExists($this->getPath());
|
||||
f_passthru('ulimit -n 2048'); // zig opens extra file descriptors, so when a lot of extensions are built statically, 1024 is not enough
|
||||
if (self::$afterInitDone) {
|
||||
return;
|
||||
}
|
||||
self::$afterInitDone = true;
|
||||
// zig opens extra file descriptors, so when a lot of extensions are built statically, 1024 is not enough
|
||||
if (function_exists('posix_setrlimit') && function_exists('posix_getrlimit')) {
|
||||
$limits = posix_getrlimit();
|
||||
$hard = (int) ($limits['hardopenfiles'] ?? 2048);
|
||||
$soft = (int) ($limits['softopenfiles'] ?? 1024);
|
||||
$target = min(max($soft, 2048), $hard > 0 ? $hard : 2048);
|
||||
if ($target > $soft) {
|
||||
posix_setrlimit(POSIX_RLIMIT_NOFILE, $target, $hard);
|
||||
}
|
||||
}
|
||||
$cflags = getenv('SPC_DEFAULT_CFLAGS') ?: '';
|
||||
$cxxflags = getenv('SPC_DEFAULT_CXXFLAGS') ?: '';
|
||||
$extraCflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') ?: '';
|
||||
@@ -34,7 +57,8 @@ class ZigToolchain implements UnixToolchainInterface
|
||||
GlobalEnvManager::putenv("SPC_DEFAULT_CXXFLAGS={$cxxflags}");
|
||||
GlobalEnvManager::putenv("SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS={$extraCflags}");
|
||||
GlobalEnvManager::putenv('RANLIB=zig-ranlib');
|
||||
GlobalEnvManager::putenv('OBJCOPY=zig-objcopy');
|
||||
GlobalEnvManager::putenv('SPC_COMPILER_RT_DIR=' . zig::path() . '/lib/' . SystemTarget::getCanonicalTriple());
|
||||
GlobalEnvManager::putenv('OBJCOPY=' . PKG_ROOT_PATH . '/llvm-tools/bin/llvm-objcopy');
|
||||
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
|
||||
if (!str_contains($extra_libs, '-lunwind')) {
|
||||
// Add unwind library if not already present
|
||||
@@ -50,6 +74,8 @@ class ZigToolchain implements UnixToolchainInterface
|
||||
// zig-cc/clang treats strlcpy/strlcat as compiler builtins, so configure link tests pass (HAVE_STRLCPY=1)
|
||||
$extra_vars = getenv('SPC_EXTRA_PHP_VARS') ?: '';
|
||||
GlobalEnvManager::putenv("SPC_EXTRA_PHP_VARS=ac_cv_func_strlcpy=no ac_cv_func_strlcat=no {$extra_vars}");
|
||||
|
||||
$this->ensureCompilerRt();
|
||||
}
|
||||
|
||||
public function getCompilerInfo(): ?string
|
||||
@@ -85,8 +111,53 @@ class ZigToolchain implements UnixToolchainInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
private function ensureCompilerRt(): void
|
||||
{
|
||||
if (!zig::isInstalled()) {
|
||||
return;
|
||||
}
|
||||
$rt = new llvm_compiler_rt();
|
||||
$triple = SystemTarget::getCanonicalTriple();
|
||||
$libDir = zig::path() . '/lib/' . $triple;
|
||||
if ($rt->isBuilt($libDir)) {
|
||||
return;
|
||||
}
|
||||
if (!is_dir(SOURCE_PATH . '/llvm-compiler-rt/lib/profile')) {
|
||||
// Source not yet downloaded; install via nested PackageInstaller. The recursion guard
|
||||
// on afterInit prevents the nested run from re-entering this method. Save the outer
|
||||
// installer/builder in the container so executors keep seeing the outer one after.
|
||||
// The PackageInstaller surfaces its own spinner for the install; AfterBinaryExtract
|
||||
// builds for the current triple, so we're done after run().
|
||||
$outerInstaller = ApplicationContext::tryGet(PackageInstaller::class);
|
||||
$outerBuilder = ApplicationContext::tryGet(PackageBuilder::class);
|
||||
try {
|
||||
new PackageInstaller()
|
||||
->addInstallPackage('llvm-compiler-rt')
|
||||
->run(true);
|
||||
} finally {
|
||||
if ($outerInstaller !== null) {
|
||||
ApplicationContext::set(PackageInstaller::class, $outerInstaller);
|
||||
}
|
||||
if ($outerBuilder !== null) {
|
||||
ApplicationContext::set(PackageBuilder::class, $outerBuilder);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Source already extracted from a previous run on a different triple; rebuild here with our
|
||||
// own progress spinner since we're outside the PackageInstaller flow.
|
||||
InteractiveTerm::indicateProgress('Building llvm-compiler-rt for ' . ConsoleColor::yellow($triple));
|
||||
try {
|
||||
$rt->buildForTriple();
|
||||
} catch (\Throwable $e) {
|
||||
InteractiveTerm::finish('Build llvm-compiler-rt for ' . ConsoleColor::red($triple) . ' failed', false);
|
||||
throw $e;
|
||||
}
|
||||
InteractiveTerm::finish('Built llvm-compiler-rt for ' . ConsoleColor::green($triple));
|
||||
}
|
||||
|
||||
private function getPath(): string
|
||||
{
|
||||
return PKG_ROOT_PATH . '/zig';
|
||||
return zig::path();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +244,7 @@ class FileSystem
|
||||
*/
|
||||
public static function getClassesPsr4(string $dir, string $base_namespace, mixed $rule = null, bool|string $return_path_value = false, bool $auto_require = false): array
|
||||
{
|
||||
$base_namespace = rtrim($base_namespace, '\\');
|
||||
$classes = [];
|
||||
$files = FileSystem::scanDirFiles($dir, true, true);
|
||||
if ($files === false) {
|
||||
|
||||
@@ -123,6 +123,24 @@ class GlobalEnvManager
|
||||
}
|
||||
}
|
||||
|
||||
/** Re-substitute env.ini's CC=${SPC_DEFAULT_CC} bindings after a toolchain swap. */
|
||||
public static function reapplyOsIni(): void
|
||||
{
|
||||
$ini = self::readIniFile();
|
||||
$os_ini = match (PHP_OS_FAMILY) {
|
||||
'Windows' => $ini['windows'] ?? [],
|
||||
'Darwin' => $ini['macos'] ?? [],
|
||||
'Linux' => $ini['linux'] ?? [],
|
||||
'BSD' => $ini['freebsd'] ?? [],
|
||||
default => [],
|
||||
};
|
||||
foreach (['CC', 'CXX', 'AR', 'RANLIB', 'LD'] as $k) {
|
||||
if (isset($os_ini[$k])) {
|
||||
self::putenv("{$k}={$os_ini[$k]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the toolchain after the environment variables are set.
|
||||
* The toolchain or environment availability check is done here.
|
||||
|
||||
@@ -221,9 +221,6 @@ final class PgoContext
|
||||
|
||||
public function mergeProfiles(): void
|
||||
{
|
||||
if (trim((string) shell_exec('command -v llvm-profdata 2>/dev/null')) === '') {
|
||||
throw new WrongUsageException('PGO --phase=use: llvm-profdata not on PATH');
|
||||
}
|
||||
foreach ($this->trainableSapis as $sapi) {
|
||||
$this->mergeSapi($sapi);
|
||||
}
|
||||
@@ -248,7 +245,8 @@ final class PgoContext
|
||||
$out = $this->profDataFile($sapi);
|
||||
$inputs = array_merge($raws, $csRaws);
|
||||
$argv = implode(' ', array_map('escapeshellarg', $inputs));
|
||||
shell()->exec('llvm-profdata merge --failure-mode=warn -output=' . escapeshellarg($out) . ' ' . $argv);
|
||||
$profdata = PKG_ROOT_PATH . '/llvm-tools/bin/llvm-profdata';
|
||||
shell()->exec(escapeshellarg($profdata) . ' merge --failure-mode=warn -output=' . escapeshellarg($out) . ' ' . $argv);
|
||||
if (!is_file($out) || filesize($out) === 0) {
|
||||
throw new WrongUsageException("PGO --phase=use: empty merge output for {$sapi}");
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use StaticPHP\Exception\WrongUsageException;
|
||||
use StaticPHP\Package\LibraryPackage;
|
||||
use StaticPHP\Package\PackageInstaller;
|
||||
use StaticPHP\Package\PhpExtensionPackage;
|
||||
use StaticPHP\Package\TargetPackage;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
|
||||
class SPCConfigUtil
|
||||
@@ -34,86 +35,39 @@ class SPCConfigUtil
|
||||
$this->absolute_libs = $options['absolute_libs'] ?? false;
|
||||
}
|
||||
|
||||
public function config(array $packages = [], bool $include_suggests = false): array
|
||||
public function config(array $packages = []): array
|
||||
{
|
||||
// if have php, make php as all extension's dependency
|
||||
// Walk depends+suggests within the resolved set; reaching `php` fans out to its
|
||||
// effective link closure (resolved static exts + virtual-target SAPIs).
|
||||
$installer = ApplicationContext::get(PackageInstaller::class);
|
||||
$resolved_set = array_flip(array_keys($installer->getResolvedPackages()));
|
||||
|
||||
$php_extras = [];
|
||||
if (!$this->no_php) {
|
||||
$dep_override = ['php' => array_filter($packages, fn ($y) => str_starts_with($y, 'ext-'))];
|
||||
} else {
|
||||
$dep_override = [];
|
||||
}
|
||||
$resolved = DependencyResolver::resolve($packages, $dep_override, $include_suggests);
|
||||
|
||||
$ldflags = $this->getLdflagsString();
|
||||
$cflags = $this->getIncludesString($resolved);
|
||||
$libs = $this->getLibsString($resolved, !$this->absolute_libs);
|
||||
|
||||
// additional OS-specific libraries (e.g. macOS -lresolv)
|
||||
// embed
|
||||
if ($extra_libs = SystemTarget::getRuntimeLibs()) {
|
||||
$libs .= " {$extra_libs}";
|
||||
}
|
||||
|
||||
$extra_env = getenv('SPC_EXTRA_LIBS');
|
||||
if (is_string($extra_env) && !empty($extra_env)) {
|
||||
$libs .= " {$extra_env}";
|
||||
}
|
||||
// package frameworks
|
||||
if (SystemTarget::getTargetOS() === 'Darwin') {
|
||||
$libs .= " {$this->getFrameworksString($resolved)}";
|
||||
}
|
||||
// C++
|
||||
if ($this->hasCpp($resolved)) {
|
||||
$target_os = SystemTarget::getTargetOS();
|
||||
if ($target_os === 'Darwin') {
|
||||
$libcpp = '-lc++';
|
||||
$libs = str_replace($libcpp, '', $libs) . " {$libcpp}";
|
||||
} elseif ($target_os !== 'Windows') {
|
||||
// Linux and other Unix-like systems use libstdc++
|
||||
$libcpp = '-lstdc++';
|
||||
$libs = str_replace($libcpp, '', $libs) . " {$libcpp}";
|
||||
foreach ($installer->getResolvedPackages(PhpExtensionPackage::class) as $ext) {
|
||||
if ($ext->isBuildStatic()) {
|
||||
$php_extras[] = $ext->getName();
|
||||
}
|
||||
}
|
||||
// Windows (MSVC): C++ runtime is linked automatically, no explicit lib needed
|
||||
}
|
||||
|
||||
if ($this->libs_only_deps) {
|
||||
// mimalloc must come first
|
||||
if (in_array('mimalloc', $resolved) && file_exists(BUILD_LIB_PATH . '/libmimalloc.a')) {
|
||||
$libs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace([BUILD_LIB_PATH . '/libmimalloc.a', '-lmimalloc'], ['', ''], $libs);
|
||||
}
|
||||
return [
|
||||
'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags),
|
||||
'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags),
|
||||
'libs' => clean_spaces(getenv('LIBS') . ' ' . $libs),
|
||||
];
|
||||
}
|
||||
|
||||
// embed
|
||||
if (!$this->no_php) {
|
||||
if (SystemTarget::getTargetOS() === 'Windows') {
|
||||
// Windows: use php8embed.lib directly (either full path or short name)
|
||||
$major = intdiv(PHP_VERSION_ID, 10000);
|
||||
$php_lib = $this->absolute_libs ? BUILD_LIB_PATH . "\\php{$major}embed.lib" : "php{$major}embed.lib";
|
||||
// Windows system libs required by PHP
|
||||
// Use same system libs as PHP Makefile: LIBS=kernel32.lib ole32.lib user32.lib advapi32.lib shell32.lib ws2_32.lib Dnsapi.lib psapi.lib bcrypt.lib
|
||||
$libs = "{$php_lib} {$libs} kernel32.lib ole32.lib user32.lib advapi32.lib shell32.lib ws2_32.lib dnsapi.lib psapi.lib bcrypt.lib";
|
||||
} else {
|
||||
$libs = "-lphp {$libs} -lc";
|
||||
foreach ($installer->getResolvedPackages(TargetPackage::class) as $target) {
|
||||
if ($target->isVirtualTarget()) {
|
||||
$php_extras[] = $target->getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allLibs = getenv('LIBS') . ' ' . $libs;
|
||||
|
||||
// mimalloc must come first
|
||||
if (in_array('mimalloc', $resolved) && file_exists(BUILD_LIB_PATH . '/libmimalloc.a')) {
|
||||
$allLibs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace([BUILD_LIB_PATH . '/libmimalloc.a', '-lmimalloc'], ['', ''], $allLibs);
|
||||
$sorted = [];
|
||||
$visited = [];
|
||||
foreach ($packages as $pkg) {
|
||||
self::visitConfigDeps(
|
||||
is_string($pkg) ? $pkg : $pkg->getName(),
|
||||
$resolved_set,
|
||||
$php_extras,
|
||||
$visited,
|
||||
$sorted,
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags),
|
||||
'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags),
|
||||
'libs' => clean_spaces($allLibs),
|
||||
];
|
||||
return $this->configWithResolvedPackages($sorted);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,6 +81,7 @@ class SPCConfigUtil
|
||||
* @param array|PhpExtensionPackage $extension_packages Extension instance or list
|
||||
* @return array{
|
||||
* cflags: string,
|
||||
* cxxflags: string,
|
||||
* ldflags: string,
|
||||
* libs: string
|
||||
* }
|
||||
@@ -150,6 +105,7 @@ class SPCConfigUtil
|
||||
* @param array|LibraryPackage $lib Library instance or list
|
||||
* @return array{
|
||||
* cflags: string,
|
||||
* cxxflags: string,
|
||||
* ldflags: string,
|
||||
* libs: string
|
||||
* }
|
||||
@@ -180,6 +136,7 @@ class SPCConfigUtil
|
||||
* @param string[] $resolved_packages The full resolved package list
|
||||
* @return array{
|
||||
* cflags: string,
|
||||
* cxxflags: string,
|
||||
* ldflags: string,
|
||||
* libs: string
|
||||
* }
|
||||
@@ -192,6 +149,7 @@ class SPCConfigUtil
|
||||
if (empty($sub_deps)) {
|
||||
return [
|
||||
'cflags' => '',
|
||||
'cxxflags' => '',
|
||||
'ldflags' => '',
|
||||
'libs' => '',
|
||||
];
|
||||
@@ -212,11 +170,12 @@ class SPCConfigUtil
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration using already-resolved packages (skip dependency resolution).
|
||||
* Build cflags/cxxflags/ldflags/libs for an already-resolved package set (skip dependency resolution).
|
||||
*
|
||||
* @param string[] $resolved_packages Already resolved package names in build order
|
||||
* @param string[] $resolved_packages Resolved package names in build order
|
||||
* @return array{
|
||||
* cflags: string,
|
||||
* cxxflags: string,
|
||||
* ldflags: string,
|
||||
* libs: string
|
||||
* }
|
||||
@@ -224,9 +183,14 @@ class SPCConfigUtil
|
||||
public function configWithResolvedPackages(array $resolved_packages): array
|
||||
{
|
||||
$ldflags = $this->getLdflagsString();
|
||||
$cflags = $this->getIncludesString($resolved_packages);
|
||||
$includes = $this->getIncludesString($resolved_packages);
|
||||
$libs = $this->getLibsString($resolved_packages, !$this->absolute_libs);
|
||||
|
||||
// includes (-I…) are language-agnostic — same source for cflags/cxxflags, swap only the env names
|
||||
$cflags = deduplicate_flags(clean_spaces((getenv('SPC_DEFAULT_CFLAGS') ?: '') . ' ' . getenv('CFLAGS') . ' ' . $includes));
|
||||
$cxxflags = deduplicate_flags(clean_spaces((getenv('SPC_DEFAULT_CXXFLAGS') ?: '') . ' ' . getenv('CXXFLAGS') . ' ' . $includes));
|
||||
$ldflags = deduplicate_flags(clean_spaces((getenv('SPC_DEFAULT_LDFLAGS') ?: '') . ' ' . getenv('LDFLAGS') . ' ' . $ldflags));
|
||||
|
||||
// additional OS-specific libraries (e.g. macOS -lresolv)
|
||||
if ($extra_libs = SystemTarget::getRuntimeLibs()) {
|
||||
$libs .= " {$extra_libs}";
|
||||
@@ -262,15 +226,25 @@ class SPCConfigUtil
|
||||
$libs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace([BUILD_LIB_PATH . '/libmimalloc.a', '-lmimalloc'], ['', ''], $libs);
|
||||
}
|
||||
return [
|
||||
'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags),
|
||||
'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags),
|
||||
'cflags' => $cflags,
|
||||
'cxxflags' => $cxxflags,
|
||||
'ldflags' => $ldflags,
|
||||
'libs' => clean_spaces(getenv('LIBS') . ' ' . $libs),
|
||||
];
|
||||
}
|
||||
|
||||
// embed
|
||||
if (!$this->no_php) {
|
||||
$libs = "-lphp {$libs} -lc";
|
||||
if (SystemTarget::getTargetOS() === 'Windows') {
|
||||
// Windows: use php8embed.lib directly (either full path or short name)
|
||||
$major = intdiv(PHP_VERSION_ID, 10000);
|
||||
$php_lib = $this->absolute_libs ? BUILD_LIB_PATH . "\\php{$major}embed.lib" : "php{$major}embed.lib";
|
||||
// Windows system libs required by PHP
|
||||
// Use same system libs as PHP Makefile: LIBS=kernel32.lib ole32.lib user32.lib advapi32.lib shell32.lib ws2_32.lib Dnsapi.lib psapi.lib bcrypt.lib
|
||||
$libs = "{$php_lib} {$libs} kernel32.lib ole32.lib user32.lib advapi32.lib shell32.lib ws2_32.lib dnsapi.lib psapi.lib bcrypt.lib";
|
||||
} else {
|
||||
$libs = "-lphp {$libs} -lc";
|
||||
}
|
||||
}
|
||||
|
||||
$allLibs = getenv('LIBS') . ' ' . $libs;
|
||||
@@ -281,8 +255,9 @@ class SPCConfigUtil
|
||||
}
|
||||
|
||||
return [
|
||||
'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags),
|
||||
'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags),
|
||||
'cflags' => $cflags,
|
||||
'cxxflags' => $cxxflags,
|
||||
'ldflags' => $ldflags,
|
||||
'libs' => clean_spaces($allLibs),
|
||||
];
|
||||
}
|
||||
@@ -301,6 +276,32 @@ class SPCConfigUtil
|
||||
return implode(' ', $list);
|
||||
}
|
||||
|
||||
private static function visitConfigDeps(
|
||||
string $name,
|
||||
array $resolved_set,
|
||||
array $php_extras,
|
||||
array &$visited,
|
||||
array &$sorted,
|
||||
): void {
|
||||
if (isset($visited[$name]) || !isset($resolved_set[$name])) {
|
||||
return;
|
||||
}
|
||||
$visited[$name] = true;
|
||||
|
||||
$deps = array_merge(
|
||||
PackageConfig::get($name, 'depends', []),
|
||||
PackageConfig::get($name, 'suggests', []),
|
||||
);
|
||||
if ($name === 'php') {
|
||||
$deps = array_merge($deps, $php_extras);
|
||||
}
|
||||
|
||||
foreach ($deps as $dep) {
|
||||
self::visitConfigDeps($dep, $resolved_set, $php_extras, $visited, $sorted);
|
||||
}
|
||||
$sorted[] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each input package name, gather its transitive deps within the installer's resolved
|
||||
* set (walking depends + suggests edges), plus the package itself, deduped and in build order.
|
||||
|
||||
@@ -168,6 +168,9 @@ class SourcePatcher
|
||||
*/
|
||||
public static function patchMicroPhar(int $version_id): void
|
||||
{
|
||||
if (file_exists(SOURCE_PATH . '/php-src/ext/phar/phar.c.bak')) {
|
||||
return;
|
||||
}
|
||||
FileSystem::backupFile(SOURCE_PATH . '/php-src/ext/phar/phar.c');
|
||||
FileSystem::replaceFileStr(
|
||||
SOURCE_PATH . '/php-src/ext/phar/phar.c',
|
||||
@@ -193,6 +196,12 @@ class SourcePatcher
|
||||
|
||||
public static function unpatchMicroPhar(): void
|
||||
{
|
||||
// Tolerate a missing .bak: both drivers call this, and the first restore
|
||||
// consumes the backup. Without this guard the second call throws
|
||||
// "Cannot find bak file". No .bak means the source is already pristine.
|
||||
if (!file_exists(SOURCE_PATH . '/php-src/ext/phar/phar.c.bak')) {
|
||||
return;
|
||||
}
|
||||
FileSystem::restoreBackupFile(SOURCE_PATH . '/php-src/ext/phar/phar.c');
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ class WindowsUtil
|
||||
}
|
||||
$args = [
|
||||
'-latest',
|
||||
'-products', '*',
|
||||
'-format', 'json',
|
||||
'-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
||||
];
|
||||
|
||||
@@ -33,6 +33,23 @@ ConsoleLogger::$date_format = 'H:i:s';
|
||||
ConsoleLogger::$format = '[%date% %level_long%] %body%';
|
||||
$ob_logger = new ConsoleLogger(LogLevel::WARNING);
|
||||
|
||||
$ob_logger->addLogCallback(function ($level, &$output, &$message, &$context, bool $shouldLog) {
|
||||
global $spc_log_filters;
|
||||
if (!is_array($spc_log_filters)) {
|
||||
$spc_log_filters = [];
|
||||
}
|
||||
// filter message and context
|
||||
$output = str_replace($spc_log_filters, '***', $output);
|
||||
$message = str_replace($spc_log_filters, '***', $message);
|
||||
$context = array_map(function ($item) use ($spc_log_filters) {
|
||||
if (is_string($item)) {
|
||||
return str_replace($spc_log_filters, '***', $item);
|
||||
}
|
||||
return $item;
|
||||
}, $context);
|
||||
return true;
|
||||
});
|
||||
|
||||
// setup log file
|
||||
if (filter_var(getenv('SPC_ENABLE_LOG_FILE'), FILTER_VALIDATE_BOOLEAN)) {
|
||||
// init spc log files
|
||||
@@ -60,5 +77,9 @@ if (filter_var(getenv('SPC_ENABLE_LOG_FILE'), FILTER_VALIDATE_BOOLEAN)) {
|
||||
|
||||
// load core registry
|
||||
Registry::loadRegistry(ROOT_DIR . '/spc.registry.yml');
|
||||
// in vendor mode, auto-load the local working directory registry if it exists
|
||||
if (spc_mode(SPC_MODE_VENDOR) && file_exists(WORKING_DIR . '/spc.registry.yml')) {
|
||||
Registry::loadRegistry(WORKING_DIR . '/spc.registry.yml');
|
||||
}
|
||||
// load registries from environment variable SPC_REGISTRIES
|
||||
Registry::loadFromEnvOrOption();
|
||||
|
||||
@@ -72,7 +72,6 @@ putenv('PKG_ROOT_PATH=' . PKG_ROOT_PATH);
|
||||
putenv('SOURCE_PATH=' . SOURCE_PATH);
|
||||
putenv('DOWNLOAD_PATH=' . DOWNLOAD_PATH);
|
||||
putenv('CPU_COUNT=' . CPU_COUNT);
|
||||
putenv('SPC_ARCH=' . php_uname('m'));
|
||||
putenv('GNU_ARCH=' . GNU_ARCH);
|
||||
putenv('MAC_ARCH=' . MAC_ARCH);
|
||||
|
||||
|
||||
@@ -27,16 +27,22 @@ while [[ $# -gt 0 ]]; do
|
||||
-march=*|-mcpu=*)
|
||||
OPT_NAME="${1%%=*}"
|
||||
OPT_VALUE="${1#*=}"
|
||||
# Skip armv8- flags entirely as Zig doesn't support them
|
||||
if [[ "$OPT_VALUE" == armv8-* ]]; then
|
||||
shift
|
||||
continue
|
||||
# zig rejects -march=armv8-a but accepts -mcpu=generic+v8a; rewrite
|
||||
# armv<X>[.<Y>]-a[+feat] -> generic+v<X>[_<Y>]a[+feat] so it goes through.
|
||||
if [[ "$OPT_VALUE" =~ ^armv([89])(\.([0-9]+))?-a(\+.*)?$ ]]; then
|
||||
arch_feat="v${BASH_REMATCH[1]}"
|
||||
[[ -n "${BASH_REMATCH[3]}" ]] && arch_feat="${arch_feat}_${BASH_REMATCH[3]}"
|
||||
OPT_VALUE="generic+${arch_feat}a${BASH_REMATCH[4]}"
|
||||
fi
|
||||
# replace -march=x86-64 with -march=x86_64
|
||||
# zig uses snake_case in CPU/feature names (x86-64 -> x86_64).
|
||||
OPT_VALUE="${OPT_VALUE//-/_}"
|
||||
PARSED_ARGS+=("${OPT_NAME}=${OPT_VALUE}")
|
||||
shift
|
||||
;;
|
||||
-mtune=generic)
|
||||
PARSED_ARGS+=("-mtune=baseline")
|
||||
shift
|
||||
;;
|
||||
-Wlogical-op|-Wduplicated-cond|-Wduplicated-branches|-Wno-clobbered|-Wjump-misses-init|-Wformat-truncation|-Warray-bounds=*|-Wimplicit-fallthrough=*)
|
||||
# GCC-only warning flags that clang/zig doesn't recognize; drop to silence -Wunknown-warning-option noise
|
||||
shift
|
||||
@@ -59,11 +65,16 @@ for _a in "${PARSED_ARGS[@]}"; do
|
||||
esac
|
||||
done
|
||||
[[ "$SPC_COMPILER_EXTRA" == *-fprofile-generate* || "$SPC_COMPILER_EXTRA" == *-fcs-profile-generate* ]] && NEED_PROFILE_RT=1
|
||||
if [[ $IS_LINK -eq 1 && $NEED_PROFILE_RT -eq 1 && -f "$SCRIPT_DIR/lib/libclang_rt.profile.a" ]]; then
|
||||
PARSED_ARGS+=("$SCRIPT_DIR/lib/libclang_rt.profile.a" "-Wl,-u,__llvm_profile_runtime")
|
||||
|
||||
RT_DIR="${SPC_COMPILER_RT_DIR:-}"
|
||||
if [[ $IS_LINK -eq 1 && $NEED_PROFILE_RT -eq 1 && -n "$RT_DIR" && -f "$RT_DIR/libclang_rt.profile.a" ]]; then
|
||||
PARSED_ARGS+=("$RT_DIR/libclang_rt.profile.a" "-Wl,-u,__llvm_profile_runtime")
|
||||
fi
|
||||
if [[ $IS_LINK -eq 1 && $NEED_CRT -eq 1 && -f "$SCRIPT_DIR/lib/clang_rt.crtbegin.o" && -f "$SCRIPT_DIR/lib/clang_rt.crtend.o" ]]; then
|
||||
PARSED_ARGS+=("$SCRIPT_DIR/lib/clang_rt.crtbegin.o" "$SCRIPT_DIR/lib/clang_rt.crtend.o")
|
||||
if [[ $IS_LINK -eq 1 && $NEED_CRT -eq 1 && -n "$RT_DIR" && -f "$RT_DIR/clang_rt.crtbegin.o" && -f "$RT_DIR/clang_rt.crtend.o" ]]; then
|
||||
PARSED_ARGS+=("$RT_DIR/clang_rt.crtbegin.o" "$RT_DIR/clang_rt.crtend.o")
|
||||
fi
|
||||
if [[ $IS_LINK -eq 1 && -n "$RT_DIR" && -f "$RT_DIR/libclang_rt.cpu_model.a" ]]; then
|
||||
PARSED_ARGS+=("$RT_DIR/libclang_rt.cpu_model.a")
|
||||
fi
|
||||
|
||||
[[ -n "$SPC_TARGET" ]] && TARGET="-target $SPC_TARGET" || TARGET=""
|
||||
|
||||
104
tests/GlobalsFunctionsTest.php
Normal file
104
tests/GlobalsFunctionsTest.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LogLevel;
|
||||
use ZM\Logger\ConsoleLogger;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class GlobalsFunctionsTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
$GLOBALS['spc_log_filters'] = null;
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$GLOBALS['spc_log_filters'] = null;
|
||||
}
|
||||
|
||||
public function testAddLogFilterDeduplicates(): void
|
||||
{
|
||||
spc_add_log_filter('secret-value');
|
||||
spc_add_log_filter('secret-value');
|
||||
spc_add_log_filter(['secret-value', 'other']);
|
||||
|
||||
$this->assertSame(['secret-value', 'other'], $GLOBALS['spc_log_filters']);
|
||||
}
|
||||
|
||||
public function testWriteLogMasksRegisteredValues(): void
|
||||
{
|
||||
spc_add_log_filter(['octocat', 'ghp_abcdef1234567890']);
|
||||
|
||||
$stream = fopen('php://memory', 'r+');
|
||||
spc_write_log($stream, 'user=octocat token=ghp_abcdef1234567890');
|
||||
rewind($stream);
|
||||
$written = stream_get_contents($stream);
|
||||
fclose($stream);
|
||||
|
||||
$this->assertSame('user=*** token=***', $written);
|
||||
}
|
||||
|
||||
public function testLoggerCallbackMasksOutput(): void
|
||||
{
|
||||
$token = 'ghp_abcdef1234567890';
|
||||
spc_add_log_filter($token);
|
||||
|
||||
$stream = fopen('php://memory', 'r+');
|
||||
$logger = new ConsoleLogger(LogLevel::DEBUG, $stream, false);
|
||||
$logger->addLogCallback(function ($level, &$output, &$message, &$context, bool $shouldLog) {
|
||||
global $spc_log_filters;
|
||||
if (!is_array($spc_log_filters)) {
|
||||
$spc_log_filters = [];
|
||||
}
|
||||
$output = str_replace($spc_log_filters, '***', $output);
|
||||
$message = str_replace($spc_log_filters, '***', $message);
|
||||
$context = array_map(function ($item) use ($spc_log_filters) {
|
||||
if (is_string($item)) {
|
||||
return str_replace($spc_log_filters, '***', $item);
|
||||
}
|
||||
return $item;
|
||||
}, $context);
|
||||
return true;
|
||||
});
|
||||
|
||||
$logger->debug("[PASSTHRU] curl -H\"Authorization: Bearer {$token}\" https://api.github.com/x");
|
||||
|
||||
rewind($stream);
|
||||
$written = stream_get_contents($stream);
|
||||
fclose($stream);
|
||||
|
||||
$this->assertStringNotContainsString($token, $written);
|
||||
$this->assertStringContainsString('***', $written);
|
||||
}
|
||||
|
||||
public function testGitHubTokenTraitRegistersEncodedBasicAuthBlob(): void
|
||||
{
|
||||
$user = 'octocat';
|
||||
$token = 'ghp_abcdef1234567890';
|
||||
$original_token = getenv('GITHUB_TOKEN');
|
||||
$original_user = getenv('GITHUB_USER');
|
||||
|
||||
putenv("GITHUB_TOKEN={$token}");
|
||||
putenv("GITHUB_USER={$user}");
|
||||
|
||||
try {
|
||||
$headers = \StaticPHP\Artifact\Downloader\Type\GitHubRelease::getGitHubTokenHeadersStatic();
|
||||
|
||||
$encoded = base64_encode("{$user}:{$token}");
|
||||
$this->assertSame(["Authorization: Basic {$encoded}"], $headers);
|
||||
$this->assertContains($user, $GLOBALS['spc_log_filters']);
|
||||
$this->assertContains($token, $GLOBALS['spc_log_filters']);
|
||||
$this->assertContains($encoded, $GLOBALS['spc_log_filters']);
|
||||
} finally {
|
||||
$original_token === false ? putenv('GITHUB_TOKEN') : putenv("GITHUB_TOKEN={$original_token}");
|
||||
$original_user === false ? putenv('GITHUB_USER') : putenv("GITHUB_USER={$original_user}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,11 +254,16 @@ class ArtifactTest extends TestCase
|
||||
ApplicationContext::initialize();
|
||||
ApplicationContext::set(ArtifactCache::class, $cache);
|
||||
|
||||
// Use a platform-appropriate absolute path: a Unix-style /tmp/... isn't
|
||||
// absolute on Windows (no drive letter), so the test would otherwise
|
||||
// fall through into the SOURCE_PATH-prefixed branch on Windows.
|
||||
$extract = DIRECTORY_SEPARATOR === '\\' ? 'C:\tmp\my-pkg-extract' : '/tmp/my-pkg-extract';
|
||||
|
||||
$artifact = new Artifact('my-pkg', [
|
||||
'source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz', 'extract' => '/tmp/my-pkg-extract'],
|
||||
'source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz', 'extract' => $extract],
|
||||
]);
|
||||
|
||||
$expected = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, '/tmp/my-pkg-extract');
|
||||
$expected = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $extract);
|
||||
$this->assertSame($expected, $artifact->getSourceDir());
|
||||
}
|
||||
|
||||
@@ -703,11 +708,7 @@ class ArtifactTest extends TestCase
|
||||
'Windows' => 'windows',
|
||||
default => 'linux',
|
||||
};
|
||||
$arch = php_uname('m');
|
||||
if ($arch === 'arm64') {
|
||||
$arch = 'aarch64';
|
||||
}
|
||||
return "{$os}-{$arch}";
|
||||
return "{$os}-" . arch2gnu(php_uname('m'));
|
||||
}
|
||||
|
||||
private function injectArtifactConfig(string $name, array $config): void
|
||||
|
||||
Reference in New Issue
Block a user