mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-02 22:35:43 +08:00
Compare commits
16 Commits
feat/pgo-v
...
v3c/artifa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a1fd1f388 | ||
|
|
48d6e9ebc2 | ||
|
|
6cab47db67 | ||
|
|
ec3fd0f4b0 | ||
|
|
e72f9aa623 | ||
|
|
91cf4f83b5 | ||
|
|
c666cd6cd0 | ||
|
|
b8dd508148 | ||
|
|
582a88ef60 | ||
|
|
153003b75c | ||
|
|
899555a964 | ||
|
|
891a222c39 | ||
|
|
39beb68024 | ||
|
|
0807e9e253 | ||
|
|
1a779be028 | ||
|
|
bdfd3eb269 |
@@ -100,10 +100,9 @@ SPC_TARGET=${GNU_ARCH}-linux-musl
|
||||
CC=${SPC_DEFAULT_CC}
|
||||
CXX=${SPC_DEFAULT_CXX}
|
||||
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 -Wno-unused-command-line-argument"
|
||||
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_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
|
||||
@@ -126,8 +125,6 @@ SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fno-ident -fPIE
|
||||
SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS="-g -fstack-protector-strong -fno-ident -fPIE -fvisibility=hidden -fvisibility-inlines-hidden ${SPC_DEFAULT_CXXFLAGS}"
|
||||
; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so
|
||||
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS=""
|
||||
; EXTRA_LDFLAGS_PROGRAM for `make` php; appended only to SAPI executable links (cli/fpm/cgi/micro/embed). Used by PGO to inject -fprofile-use= without polluting libphp.{a,so}.
|
||||
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM=""
|
||||
|
||||
; optional, path to openssl conf. This affects where openssl will look for the default CA.
|
||||
; default on Debian/Alpine: /etc/ssl, default on RHEL: /etc/pki/tls
|
||||
@@ -143,7 +140,6 @@ SPC_USE_LLVM=system
|
||||
CC=${SPC_DEFAULT_CC}
|
||||
CXX=${SPC_DEFAULT_CXX}
|
||||
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-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections"
|
||||
@@ -167,7 +163,5 @@ SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie -fvis
|
||||
SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS="-g -fstack-protector-strong -fno-ident -fpie -fvisibility=hidden -fvisibility-inlines-hidden -Werror=unknown-warning-option ${SPC_DEFAULT_CXXFLAGS}"
|
||||
; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.dylib
|
||||
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS=""
|
||||
; EXTRA_LDFLAGS_PROGRAM for `make` php; appended only to SAPI executable links (cli/fpm/cgi/micro/embed). Used by PGO to inject -fprofile-use= without polluting libphp.{a,dylib}.
|
||||
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM=""
|
||||
; minimum compatible macOS version (LLVM vars, availability not guaranteed)
|
||||
MACOSX_DEPLOYMENT_TARGET=12.0
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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
|
||||
@@ -1,14 +0,0 @@
|
||||
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
|
||||
@@ -1,6 +0,0 @@
|
||||
llvm-compiler-rt:
|
||||
type: target
|
||||
artifact:
|
||||
binary: custom
|
||||
depends:
|
||||
- zig
|
||||
@@ -1,6 +0,0 @@
|
||||
llvm-tools:
|
||||
type: target
|
||||
artifact:
|
||||
binary: custom
|
||||
depends:
|
||||
- zig
|
||||
@@ -15,6 +15,23 @@ use StaticPHP\Util\GlobalEnvManager;
|
||||
|
||||
class go_win
|
||||
{
|
||||
/** GOROOT for the Windows Go toolchain. */
|
||||
public static function path(): string
|
||||
{
|
||||
return PKG_ROOT_PATH . '/go-win';
|
||||
}
|
||||
|
||||
/** Path to a binary inside go-win's bin/ (go.exe, gofmt.exe, …). */
|
||||
public static function binary(string $name = 'go.exe'): string
|
||||
{
|
||||
return self::path() . '/bin/' . $name;
|
||||
}
|
||||
|
||||
public static function isInstalled(): bool
|
||||
{
|
||||
return is_file(self::binary());
|
||||
}
|
||||
|
||||
#[CustomBinary('go-win', [
|
||||
'windows-x86_64',
|
||||
])]
|
||||
|
||||
@@ -17,6 +17,23 @@ use StaticPHP\Util\System\LinuxUtil;
|
||||
|
||||
class go_xcaddy
|
||||
{
|
||||
/** GOROOT for the bundled Go toolchain used to build xcaddy. */
|
||||
public static function path(): string
|
||||
{
|
||||
return PKG_ROOT_PATH . '/go-xcaddy';
|
||||
}
|
||||
|
||||
/** Path to a binary inside go-xcaddy's bin/ (xcaddy, go, …). */
|
||||
public static function binary(string $name = 'xcaddy'): string
|
||||
{
|
||||
return self::path() . '/bin/' . $name;
|
||||
}
|
||||
|
||||
public static function isInstalled(): bool
|
||||
{
|
||||
return is_file(self::binary());
|
||||
}
|
||||
|
||||
#[CustomBinary('go-xcaddy', [
|
||||
'linux-x86_64',
|
||||
'linux-aarch64',
|
||||
@@ -25,6 +42,7 @@ class go_xcaddy
|
||||
])]
|
||||
public function downBinary(ArtifactDownloader $downloader): DownloadResult
|
||||
{
|
||||
$pkgroot = PKG_ROOT_PATH;
|
||||
$name = SystemTarget::getCurrentPlatformString();
|
||||
$arch = match (explode('-', $name)[1]) {
|
||||
'x86_64' => 'amd64',
|
||||
@@ -63,7 +81,7 @@ class go_xcaddy
|
||||
if ($file_hash !== $hash) {
|
||||
throw new DownloaderException("Hash mismatch for downloaded go-xcaddy binary. Expected {$hash}, got {$file_hash}");
|
||||
}
|
||||
return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $version], extract: '{pkg_root_path}/go-xcaddy', verified: true, version: $version);
|
||||
return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $version], extract: "{$pkgroot}/go-xcaddy", verified: true, version: $version);
|
||||
}
|
||||
|
||||
#[CustomBinaryCheckUpdate('go-xcaddy', [
|
||||
@@ -108,7 +126,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@master');
|
||||
])->exec('CC=cc go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest');
|
||||
GlobalEnvManager::addPathIfNotExists("{$target_path}/bin");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
<?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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,23 @@ use StaticPHP\Util\System\LinuxUtil;
|
||||
|
||||
class rust
|
||||
{
|
||||
/** Install prefix the rust tarball's install.sh writes into. */
|
||||
public static function path(): string
|
||||
{
|
||||
return PKG_ROOT_PATH . '/rust';
|
||||
}
|
||||
|
||||
/** Path to a binary inside the rust install dir (cargo, rustc, rustup, …). */
|
||||
public static function binary(string $name = 'cargo'): string
|
||||
{
|
||||
return self::path() . '/bin/' . $name;
|
||||
}
|
||||
|
||||
public static function isInstalled(): bool
|
||||
{
|
||||
return is_file(self::binary());
|
||||
}
|
||||
|
||||
#[CustomBinary('rust', [
|
||||
'linux-x86_64',
|
||||
'linux-aarch64',
|
||||
|
||||
@@ -78,7 +78,7 @@ class zig
|
||||
if ($file_hash !== $sha256) {
|
||||
throw new DownloaderException("Hash mismatch for downloaded Zig binary. Expected {$sha256}, got {$file_hash}");
|
||||
}
|
||||
return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $latest_version], extract: '{pkg_root_path}/zig', verified: true, version: $latest_version);
|
||||
return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $latest_version], extract: PKG_ROOT_PATH . '/zig', verified: true, version: $latest_version);
|
||||
}
|
||||
|
||||
#[CustomBinaryCheckUpdate('zig', [
|
||||
@@ -127,24 +127,26 @@ class zig
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$all_exist) {
|
||||
$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);
|
||||
|
||||
$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);
|
||||
if ($all_exist) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use StaticPHP\Attribute\Package\Extension;
|
||||
use StaticPHP\Attribute\PatchDescription;
|
||||
use StaticPHP\Package\PackageInstaller;
|
||||
use StaticPHP\Package\PhpExtensionPackage;
|
||||
use StaticPHP\Util\GlobalEnvManager;
|
||||
use StaticPHP\Util\SourcePatcher;
|
||||
|
||||
#[Extension('xlswriter')]
|
||||
@@ -21,20 +20,14 @@ class xlswriter extends PhpExtensionPackage
|
||||
#[CustomPhpConfigureArg('Linux')]
|
||||
public function getUnixConfigureArg(bool $shared, PackageInstaller $installer): string
|
||||
{
|
||||
$arg = '--with-xlswriter --enable-reader';
|
||||
$shared = $shared ? '=shared' : '';
|
||||
$arg = "--with-xlswriter{$shared} --enable-reader";
|
||||
if ($installer->getLibraryPackage('openssl')) {
|
||||
$arg .= ' --with-openssl=' . $installer->getLibraryPackage('openssl')->getBuildRootPath();
|
||||
}
|
||||
return $arg;
|
||||
}
|
||||
|
||||
#[BeforeStage('php', [php::class, 'makeForUnix'], 'ext-xlswriter')]
|
||||
#[PatchDescription('Fix Unix build: add -std=gnu17 to CFLAGS to fix build errors on older GCC versions')]
|
||||
public function patchBeforeUnixMake(): void
|
||||
{
|
||||
GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . ' -std=gnu17');
|
||||
}
|
||||
|
||||
#[BeforeStage('php', [php::class, 'makeForWindows'], 'ext-xlswriter')]
|
||||
#[PatchDescription('Fix Windows build: apply win32 patch and add UTF-8 BOM to theme.c')]
|
||||
public function patchBeforeMakeForWindows(): void
|
||||
@@ -47,11 +40,4 @@ class xlswriter extends PhpExtensionPackage
|
||||
file_put_contents($this->getSourceDir() . '/library/libxlsxwriter/src/theme.c', $bom . $content);
|
||||
}
|
||||
}
|
||||
|
||||
public function getSharedExtensionEnv(): array
|
||||
{
|
||||
$parent = parent::getSharedExtensionEnv();
|
||||
$parent['CFLAGS'] .= ' -std=gnu17';
|
||||
return $parent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@ class bzip2
|
||||
#[PatchBeforeBuild]
|
||||
public function patchBeforeBuild(LibraryPackage $lib): void
|
||||
{
|
||||
// Makefile pins -O2 -fPIC; inject SPC_DEFAULT_CFLAGS
|
||||
$extra = deduplicate_flags(trim((string) getenv('SPC_DEFAULT_CFLAGS')) . ' -fPIC -Wall');
|
||||
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'CFLAGS=-Wall', "CFLAGS={$extra}");
|
||||
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'CFLAGS=-Wall', 'CFLAGS=-fPIC -Wall');
|
||||
}
|
||||
|
||||
#[BuildFor('Windows')]
|
||||
|
||||
@@ -18,11 +18,9 @@ class fastlz
|
||||
{
|
||||
$cc = getenv('CC') ?: 'cc';
|
||||
$ar = getenv('AR') ?: 'ar';
|
||||
$extra = trim((string) getenv('SPC_DEFAULT_CFLAGS'));
|
||||
$extra = $extra !== '' ? $extra . ' -fPIC' : '-O3 -fPIC';
|
||||
|
||||
shell()->cd($lib->getSourceDir())->initializeEnv($lib)
|
||||
->exec("{$cc} -c {$extra} fastlz.c -o fastlz.o")
|
||||
->exec("{$cc} -c -O3 -fPIC fastlz.c -o fastlz.o")
|
||||
->exec("{$ar} rcs libfastlz.a fastlz.o");
|
||||
|
||||
// Copy header file
|
||||
|
||||
@@ -24,12 +24,9 @@ class icu
|
||||
#[BuildFor('Linux')]
|
||||
public function buildLinux(LibraryPackage $lib, ToolchainInterface $toolchain, PackageBuilder $builder): void
|
||||
{
|
||||
// runConfigureICU bakes CXXFLAGS/LDFLAGS, apply user flags too
|
||||
$userCxxFlags = trim((string) getenv('SPC_DEFAULT_CXXFLAGS'));
|
||||
$userLdFlags = trim((string) getenv('SPC_DEFAULT_LDFLAGS'));
|
||||
$cppflags = 'CPPFLAGS="-DU_CHARSET_IS_UTF8=1 -DU_USING_ICU_NAMESPACE=1 -DU_STATIC_IMPLEMENTATION=1 -DPIC -fPIC"';
|
||||
$cxxflags = "CXXFLAGS=\"-std=c++17 -DPIC -fPIC -fno-ident {$userCxxFlags}\"";
|
||||
$ldflags = $toolchain->isStatic() ? "LDFLAGS=\"-static {$userLdFlags}\"" : "LDFLAGS=\"{$userLdFlags}\"";
|
||||
$cxxflags = 'CXXFLAGS="-std=c++17 -DPIC -fPIC -fno-ident"';
|
||||
$ldflags = $toolchain->isStatic() ? 'LDFLAGS="-static"' : '';
|
||||
shell()->cd($lib->getSourceDir() . '/source')->initializeEnv($lib)
|
||||
->exec(
|
||||
"{$cppflags} {$cxxflags} {$ldflags} " .
|
||||
|
||||
@@ -17,9 +17,7 @@ class jbig
|
||||
#[PatchBeforeBuild]
|
||||
public function patchBeforeBuild(LibraryPackage $lib): void
|
||||
{
|
||||
$extra = trim((string) getenv('SPC_DEFAULT_CFLAGS'));
|
||||
$cflags = ($extra !== '' ? $extra : '-O2') . ' -W -Wno-unused-result -fPIC';
|
||||
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'CFLAGS = -O2 -W -Wno-unused-result', "CFLAGS = {$cflags}");
|
||||
FileSystem::replaceFileStr($lib->getSourceDir() . '/Makefile', 'CFLAGS = -O2 -W -Wno-unused-result', 'CFLAGS = -O2 -W -Wno-unused-result -fPIC');
|
||||
}
|
||||
|
||||
#[BuildFor('Darwin')]
|
||||
|
||||
@@ -27,7 +27,7 @@ class krb5
|
||||
|
||||
$resolved = array_keys($installer->getResolvedPackages());
|
||||
$spc = new SPCConfigUtil(['no_php' => true, 'libs_only_deps' => true]);
|
||||
$config = $spc->getPackageDepsConfig($lib->getName(), $resolved);
|
||||
$config = $spc->getPackageDepsConfig($lib->getName(), $resolved, include_suggests: true);
|
||||
$extraEnv = [
|
||||
'CFLAGS' => '-fcommon',
|
||||
'LIBS' => $config['libs'],
|
||||
|
||||
@@ -9,10 +9,8 @@ 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
|
||||
@@ -41,23 +39,9 @@ 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={$targetCpu}",
|
||||
'-DCONFIG_RUNTIME_CPU_DETECT=1',
|
||||
'-DENABLE_EXAMPLES=OFF',
|
||||
'-DENABLE_TESTS=OFF',
|
||||
'-DENABLE_TOOLS=OFF',
|
||||
'-DENABLE_DOCS=OFF',
|
||||
)
|
||||
->addConfigureArgs('-DAOM_TARGET_CPU=generic')
|
||||
->build();
|
||||
f_putenv("SPC_COMPILER_EXTRA={$extra}");
|
||||
$this->patchPkgconfPrefix(['aom.pc']);
|
||||
|
||||
@@ -8,7 +8,6 @@ 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
|
||||
@@ -29,7 +28,7 @@ class libffi extends LibraryPackage
|
||||
#[BuildFor('Darwin')]
|
||||
public function buildDarwin(): void
|
||||
{
|
||||
$arch = SystemTarget::getTargetArch();
|
||||
$arch = getenv('SPC_ARCH');
|
||||
UnixAutoconfExecutor::create($this)
|
||||
->configure(
|
||||
"--host={$arch}-apple-darwin",
|
||||
|
||||
@@ -24,17 +24,6 @@ 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')]
|
||||
|
||||
@@ -17,17 +17,10 @@ use StaticPHP\Util\FileSystem;
|
||||
class liblz4
|
||||
{
|
||||
#[PatchBeforeBuild]
|
||||
#[PatchDescription('Compile lib sources individually so -flto -c with multiple inputs works under zig-cc/clang')]
|
||||
#[PatchDescription('Fix Makefile install target for static liblz4')]
|
||||
public function patchBeforeBuild(LibraryPackage $lib): void
|
||||
{
|
||||
// `-flto -c` with multiple input files only writes a .o for the
|
||||
// first source — the others are silently dropped, leaving liblz4.a with a
|
||||
// single object. Compile each source individually so all .o files exist.
|
||||
FileSystem::replaceFileStr(
|
||||
$lib->getSourceDir() . '/lib/Makefile',
|
||||
"liblz4.a: \$(SRCFILES)\nifeq (\$(BUILD_STATIC),yes) # can be disabled on command line\n\t@echo compiling static library\n\t\$(COMPILE.c) \$^\n\t\$(AR) rcs \$@ *.o\nendif",
|
||||
"liblz4.a: \$(SRCFILES:.c=.o)\nifeq (\$(BUILD_STATIC),yes) # can be disabled on command line\n\t@echo compiling static library\n\t\$(AR) rcs \$@ \$^\nendif"
|
||||
);
|
||||
FileSystem::replaceFileStr($lib->getSourceDir() . '/programs/Makefile', 'install: lz4', "install: lz4\n\ninstallewfwef: lz4");
|
||||
}
|
||||
|
||||
#[BuildFor('Windows')]
|
||||
|
||||
@@ -9,7 +9,6 @@ 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')]
|
||||
@@ -25,7 +24,7 @@ class libpng
|
||||
];
|
||||
|
||||
// Enable architecture-specific optimizations
|
||||
match (SystemTarget::getTargetArch()) {
|
||||
match (getenv('SPC_ARCH')) {
|
||||
'x86_64' => $args[] = '--enable-intel-sse',
|
||||
'aarch64' => $args[] = '--enable-arm-neon',
|
||||
default => null,
|
||||
|
||||
@@ -6,8 +6,6 @@ namespace Package\Library;
|
||||
|
||||
use StaticPHP\Attribute\Package\BuildFor;
|
||||
use StaticPHP\Attribute\Package\Library;
|
||||
use StaticPHP\Attribute\Package\PatchBeforeBuild;
|
||||
use StaticPHP\Attribute\PatchDescription;
|
||||
use StaticPHP\Package\LibraryPackage;
|
||||
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
|
||||
use StaticPHP\Toolchain\Interface\ToolchainInterface;
|
||||
@@ -18,24 +16,6 @@ use StaticPHP\Util\FileSystem;
|
||||
#[Library('ncursesw')]
|
||||
class ncurses
|
||||
{
|
||||
#[PatchBeforeBuild]
|
||||
#[PatchDescription('Filter clang/zig "N warning(s) generated." line out of MKlib_gen.sh preprocessor pipe')]
|
||||
public function patchBeforeBuild(LibraryPackage $lib): void
|
||||
{
|
||||
// MKlib_gen.sh feeds the C preprocessor's stdout through a sed/awk
|
||||
// pipeline into lib_gen.c. zig-cc/clang emits "N warning(s) generated."
|
||||
// on stdout (not stderr), and that line ends up as invalid C in the
|
||||
// generated source. Filter it out of the pipe before sed sees it.
|
||||
$mklibGen = $lib->getSourceDir() . '/ncurses/base/MKlib_gen.sh';
|
||||
if (is_file($mklibGen) && !str_contains((string) file_get_contents($mklibGen), "| grep -v ' generated")) {
|
||||
FileSystem::replaceFileStr(
|
||||
$mklibGen,
|
||||
'$preprocessor $TMP 2>/dev/null \\',
|
||||
"\$preprocessor \$TMP 2>/dev/null \\\n| grep -v ' generated\\.\$' \\",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[BuildFor('Darwin')]
|
||||
#[BuildFor('Linux')]
|
||||
public function build(LibraryPackage $package, ToolchainInterface $toolchain): void
|
||||
@@ -65,7 +45,6 @@ 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 = SystemTarget::getTargetArch();
|
||||
$arch = getenv('SPC_ARCH');
|
||||
|
||||
shell()->cd($pkg->getSourceDir())->initializeEnv($pkg)
|
||||
->exec(
|
||||
@@ -95,7 +95,12 @@ class openssl
|
||||
#[BuildFor('Linux')]
|
||||
public function build(LibraryPackage $lib): void
|
||||
{
|
||||
$arch = SystemTarget::getTargetArch();
|
||||
$arch = getenv('SPC_ARCH');
|
||||
|
||||
$env = "CC='" . getenv('CC') . ' -idirafter ' . BUILD_INCLUDE_PATH .
|
||||
' -idirafter /usr/include/ ' .
|
||||
' -idirafter /usr/include/' . getenv('SPC_ARCH') . '-linux-gnu/ ' .
|
||||
"' ";
|
||||
|
||||
$ex_lib = trim($lib->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles()) . ' -ldl -pthread';
|
||||
$zlib_extra =
|
||||
@@ -106,15 +111,9 @@ class openssl
|
||||
$openssl_dir ??= LinuxUtil::getOSRelease()['dist'] === 'redhat' ? '/etc/pki/tls' : '/etc/ssl';
|
||||
$ex_lib = trim($ex_lib);
|
||||
|
||||
// anything we want included (PGO -fprofile-*, LTO, custom hardening)
|
||||
// has to be appended on the command line *after* the target name.
|
||||
$userCFlags = trim((string) getenv('SPC_DEFAULT_CFLAGS'));
|
||||
$userLdFlags = trim((string) getenv('SPC_DEFAULT_LDFLAGS'));
|
||||
$userExtra = trim($userCFlags . ' ' . $userLdFlags);
|
||||
|
||||
shell()->cd($lib->getSourceDir())->initializeEnv($lib)
|
||||
->exec(
|
||||
'./Configure no-shared zlib ' .
|
||||
"{$env} ./Configure no-shared zlib " .
|
||||
"--prefix={$lib->getBuildRootPath()} " .
|
||||
"--libdir={$lib->getLibDir()} " .
|
||||
"--openssldir={$openssl_dir} " .
|
||||
@@ -122,8 +121,7 @@ class openssl
|
||||
'enable-pie ' .
|
||||
'no-legacy ' .
|
||||
'no-tests ' .
|
||||
"linux-{$arch} " .
|
||||
$userExtra
|
||||
"linux-{$arch}"
|
||||
)
|
||||
->exec('make clean')
|
||||
->exec("make -j{$lib->getBuilder()->concurrency} CNF_EX_LIBS=\"{$ex_lib}\"")
|
||||
|
||||
@@ -58,7 +58,7 @@ class postgresql extends LibraryPackage
|
||||
public function buildUnix(PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
$spc_config = new SPCConfigUtil(['no_php' => true, 'libs_only_deps' => true]);
|
||||
$config = $spc_config->getPackageDepsConfig('postgresql', array_keys($installer->getResolvedPackages()));
|
||||
$config = $spc_config->getPackageDepsConfig('postgresql', array_keys($installer->getResolvedPackages()), include_suggests: $builder->getOption('with-suggests', false));
|
||||
|
||||
$env_vars = [
|
||||
'CFLAGS' => $config['cflags'] . ' -std=c17',
|
||||
|
||||
@@ -20,11 +20,6 @@ class qdbm
|
||||
{
|
||||
$ac = UnixAutoconfExecutor::create($lib)->configure();
|
||||
FileSystem::replaceFileRegex($lib->getSourceDir() . '/Makefile', '/MYLIBS = libqdbm.a.*/m', 'MYLIBS = libqdbm.a');
|
||||
// Makefile pins -O3, replace with SPC_DEFAULT_CFLAGS
|
||||
$extra = trim((string) getenv('SPC_DEFAULT_CFLAGS'));
|
||||
if ($extra !== '') {
|
||||
FileSystem::replaceFileRegex($lib->getSourceDir() . '/Makefile', '/^CFLAGS = .*$/m', "CFLAGS = -Wall {$extra}");
|
||||
}
|
||||
$ac->make(SystemTarget::getTargetOS() === 'Darwin' ? 'mac' : '');
|
||||
$lib->patchPkgconfPrefix(['qdbm.pc']);
|
||||
}
|
||||
|
||||
@@ -27,20 +27,7 @@ class unixodbc extends LibraryPackage
|
||||
'Linux' => '/etc',
|
||||
default => throw new WrongUsageException("Unsupported OS: {$os}"),
|
||||
};
|
||||
|
||||
// unixodbc bundles libltdl; libltdl is incompatible with -flto
|
||||
// (https://bugs.gentoo.org/532672).
|
||||
$stripLto = static fn (string $s): string => clean_spaces((string) preg_replace('/(^|\s)-flto(=\S+)?(?=\s|$)/', ' ', $s));
|
||||
$cflags = $stripLto((string) getenv('SPC_DEFAULT_CFLAGS'));
|
||||
$cxxflags = $stripLto((string) getenv('SPC_DEFAULT_CXXFLAGS'));
|
||||
$ldflags = $stripLto((string) getenv('SPC_DEFAULT_LDFLAGS'));
|
||||
|
||||
$make = UnixAutoconfExecutor::create($this)
|
||||
->setEnv([
|
||||
'CFLAGS' => $cflags,
|
||||
'CXXFLAGS' => $cxxflags,
|
||||
'LDFLAGS' => $ldflags,
|
||||
])
|
||||
UnixAutoconfExecutor::create($this)
|
||||
->configure(
|
||||
'--disable-debug',
|
||||
'--disable-dependency-tracking',
|
||||
@@ -48,15 +35,8 @@ class unixodbc extends LibraryPackage
|
||||
'--with-included-ltdl',
|
||||
"--sysconfdir={$sysconf_selector}",
|
||||
'--enable-gui=no',
|
||||
);
|
||||
|
||||
// The exe/ subdirectory builds odbcinst/iusql/etc, turn it into a no-op
|
||||
file_put_contents(
|
||||
"{$this->getSourceDir()}/exe/Makefile",
|
||||
".PHONY: all install clean check distclean install-strip\nall install clean check distclean install-strip:\n\t@true\n",
|
||||
);
|
||||
|
||||
$make->make();
|
||||
)
|
||||
->make();
|
||||
$this->patchPkgconfPrefix(['odbc.pc', 'odbccr.pc', 'odbcinst.pc']);
|
||||
$this->patchLaDependencyPrefix();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Package\Target;
|
||||
|
||||
use Package\Target\php\frankenphp;
|
||||
use Package\Target\php\pgo;
|
||||
use Package\Target\php\unix;
|
||||
use Package\Target\php\windows;
|
||||
use StaticPHP\Artifact\ArtifactCache;
|
||||
@@ -49,7 +48,6 @@ class php extends TargetPackage
|
||||
use unix;
|
||||
use windows;
|
||||
use frankenphp;
|
||||
use pgo;
|
||||
|
||||
/** @var string[] Supported major PHP versions */
|
||||
public const array SUPPORTED_MAJOR_VERSIONS = ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'];
|
||||
@@ -270,14 +268,12 @@ class php extends TargetPackage
|
||||
}
|
||||
}
|
||||
// linux does not support loading shared libraries when target is pure 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.'
|
||||
);
|
||||
}
|
||||
$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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +332,7 @@ class php extends TargetPackage
|
||||
logger()->info("Adding hardcoded INI [{$source_name} = {$ini_value}]");
|
||||
}
|
||||
if (!empty($custom_ini)) {
|
||||
ApplicationContext::invoke([SourcePatcher::class, 'patchHardcodedINI'], ['php_source_dir' => $package->getSourceDir(), 'ini' => $custom_ini]);
|
||||
ApplicationContext::invoke([SourcePatcher::class, 'patchHardcodedINI'], [$package->getSourceDir(), $custom_ini]);
|
||||
}
|
||||
|
||||
// Patch StaticPHP version
|
||||
|
||||
@@ -73,7 +73,10 @@ trait frankenphp
|
||||
$staticFlags = '';
|
||||
}
|
||||
|
||||
$config = new SPCConfigUtil()->config(['frankenphp']);
|
||||
$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);
|
||||
$cflags = "{$package->getLibExtraCFlags()} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . " -DFRANKENPHP_VERSION={$frankenphp_version}";
|
||||
$libs = $config['libs'];
|
||||
|
||||
@@ -85,13 +88,10 @@ trait frankenphp
|
||||
$libs .= ' -lgcov';
|
||||
}
|
||||
|
||||
$extraLdProgram = clean_spaces((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM'));
|
||||
$env = [
|
||||
'CGO_ENABLED' => '1',
|
||||
'CGO_CFLAGS' => clean_spaces($cflags),
|
||||
'CGO_LDFLAGS' => clean_spaces("{$package->getLibExtraLdFlags()} {$staticFlags} {$config['ldflags']} {$libs} {$extraLdProgram}"),
|
||||
// cgo strips flags not on its safe allowlist; widen it
|
||||
'CGO_LDFLAGS_ALLOW' => '-Wl,-z,.*|-Wl,--.*|-flto.*|-fprofile-.*',
|
||||
'CGO_LDFLAGS' => "{$package->getLibExtraLdFlags()} {$staticFlags} {$config['ldflags']} {$libs}",
|
||||
'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' .
|
||||
'-ldflags \"-linkmode=external ' . $extLdFlags . ' ' .
|
||||
'-X \'github.com/caddyserver/caddy/v2/modules/caddyhttp.ServerHeader=FrankenPHP Caddy\' ' .
|
||||
@@ -101,12 +101,10 @@ 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 {$pgo}{$xcaddy_modules}");
|
||||
->exec("xcaddy build --output frankenphp {$xcaddy_modules}");
|
||||
|
||||
$builder->deployBinary(BUILD_LIB_PATH . '/frankenphp', BUILD_BIN_PATH . '/frankenphp');
|
||||
$package->setOutput('Binary path for FrankenPHP SAPI', BUILD_BIN_PATH . '/frankenphp');
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Package\Target\php;
|
||||
|
||||
use StaticPHP\Attribute\Package\AfterStage;
|
||||
use StaticPHP\Attribute\Package\BeforeStage;
|
||||
use StaticPHP\Attribute\Package\ConditionalOn;
|
||||
use StaticPHP\Attribute\PatchDescription;
|
||||
use StaticPHP\Exception\WrongUsageException;
|
||||
use StaticPHP\Package\TargetPackage;
|
||||
use StaticPHP\Util\Pgo\PgoContext;
|
||||
use StaticPHP\Util\SourcePatcher;
|
||||
|
||||
trait pgo
|
||||
{
|
||||
#[ConditionalOn(PgoContext::class)]
|
||||
#[BeforeStage('php', [self::class, 'buildconfForUnix'], 'php')]
|
||||
#[PatchDescription('Inject __llvm_profile_write_file() flush at php/frankenphp shutdown for instrumented builds')]
|
||||
public function pgoApplyShutdownPatches(PgoContext $pgo): void
|
||||
{
|
||||
if (!$pgo->isInstrument() && !$pgo->isCsInstrument()) {
|
||||
return;
|
||||
}
|
||||
foreach (PgoContext::SHUTDOWN_PATCHES as $dir => $patch) {
|
||||
$cwd = SOURCE_PATH . '/' . $dir;
|
||||
if (!is_dir($cwd)) {
|
||||
continue;
|
||||
}
|
||||
if (!SourcePatcher::patchFile($patch, $cwd)) {
|
||||
throw new WrongUsageException("PGO --phase=instrument: patch {$patch} failed to apply in {$cwd}");
|
||||
}
|
||||
logger()->info("PGO --phase=instrument: applied {$patch}");
|
||||
}
|
||||
}
|
||||
|
||||
#[ConditionalOn(PgoContext::class)]
|
||||
#[AfterStage('php', [self::class, 'configureForUnix'], 'php')]
|
||||
#[PatchDescription('Patch libtool to passthrough -fcs-profile-* for context-sensitive PGO')]
|
||||
public function pgoPatchLibtoolForCsInstrument(PgoContext $pgo): void
|
||||
{
|
||||
if (!$pgo->isCsInstrument()) {
|
||||
return;
|
||||
}
|
||||
$libtool = SOURCE_PATH . '/php-src/libtool';
|
||||
if (!is_file($libtool)) {
|
||||
return;
|
||||
}
|
||||
$contents = (string) file_get_contents($libtool);
|
||||
if (str_contains($contents, '-fcs-profile-*')) {
|
||||
return;
|
||||
}
|
||||
$patched = str_replace('-fprofile-*|-F*', '-fprofile-*|-fcs-profile-*|-F*', $contents);
|
||||
if ($patched === $contents) {
|
||||
logger()->warning('PGO --phase=cs-instrument: could not patch libtool for -fcs-profile-* passthrough');
|
||||
return;
|
||||
}
|
||||
file_put_contents($libtool, $patched);
|
||||
logger()->info('PGO --phase=cs-instrument: patched libtool for -fcs-profile-* passthrough');
|
||||
}
|
||||
|
||||
#[ConditionalOn(PgoContext::class)]
|
||||
#[BeforeStage('php', [self::class, 'configureForUnix'], 'php')]
|
||||
public function pgoApplyConfigureFlags(PgoContext $pgo): void
|
||||
{
|
||||
$sapis = $pgo->trainableSapis();
|
||||
if ($sapis === []) {
|
||||
return;
|
||||
}
|
||||
$pgo->applyEnvFor($sapis[0]);
|
||||
}
|
||||
|
||||
#[ConditionalOn(PgoContext::class)]
|
||||
#[BeforeStage('php', [self::class, 'makeCliForUnix'], 'php')]
|
||||
public function pgoBeforeMakeCli(PgoContext $pgo, TargetPackage $package): void
|
||||
{
|
||||
$this->pgoBeforeSapiMake($pgo, $package, 'cli');
|
||||
}
|
||||
|
||||
#[ConditionalOn(PgoContext::class)]
|
||||
#[BeforeStage('php', [self::class, 'makeCgiForUnix'], 'php')]
|
||||
public function pgoBeforeMakeCgi(PgoContext $pgo, TargetPackage $package): void
|
||||
{
|
||||
$this->pgoBeforeSapiMake($pgo, $package, 'cgi');
|
||||
}
|
||||
|
||||
#[ConditionalOn(PgoContext::class)]
|
||||
#[BeforeStage('php', [self::class, 'makeFpmForUnix'], 'php')]
|
||||
public function pgoBeforeMakeFpm(PgoContext $pgo, TargetPackage $package): void
|
||||
{
|
||||
$this->pgoBeforeSapiMake($pgo, $package, 'fpm');
|
||||
}
|
||||
|
||||
#[ConditionalOn(PgoContext::class)]
|
||||
#[BeforeStage('php', [self::class, 'makeMicroForUnix'], 'php')]
|
||||
public function pgoBeforeMakeMicro(PgoContext $pgo, TargetPackage $package): void
|
||||
{
|
||||
$this->pgoBeforeSapiMake($pgo, $package, 'micro');
|
||||
}
|
||||
|
||||
#[ConditionalOn(PgoContext::class)]
|
||||
#[BeforeStage('php', [self::class, 'makeEmbedForUnix'], 'php')]
|
||||
public function pgoBeforeMakeEmbed(PgoContext $pgo, TargetPackage $package): void
|
||||
{
|
||||
$this->pgoBeforeSapiMake($pgo, $package, 'embed');
|
||||
}
|
||||
|
||||
#[ConditionalOn(PgoContext::class)]
|
||||
#[BeforeStage('php', [self::class, 'buildFrankenphpForUnix'], 'php')]
|
||||
public function pgoBeforeBuildFrankenphp(PgoContext $pgo): void
|
||||
{
|
||||
$pgo->applyEnvFor('frankenphp');
|
||||
logger()->info("PGO {$pgo->mode}: applying flags for frankenphp");
|
||||
}
|
||||
|
||||
private function pgoBeforeSapiMake(PgoContext $pgo, TargetPackage $package, string $sapi): void
|
||||
{
|
||||
$resolved = $pgo->resolveSapi($sapi);
|
||||
if (!in_array($resolved, $pgo->trainableSapis(), true)) {
|
||||
return;
|
||||
}
|
||||
shell()->cd($package->getSourceDir())->exec('make clean');
|
||||
$pgo->applyEnvFor($sapi);
|
||||
logger()->info("PGO {$pgo->mode}: applying flags for {$sapi}");
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ use StaticPHP\Package\PhpExtensionPackage;
|
||||
use StaticPHP\Package\TargetPackage;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Toolchain\Interface\ToolchainInterface;
|
||||
use StaticPHP\Toolchain\ToolchainManager;
|
||||
use StaticPHP\Toolchain\ZigToolchain;
|
||||
use StaticPHP\Util\DirDiff;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
@@ -42,15 +41,6 @@ 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');
|
||||
@@ -69,7 +59,7 @@ trait unix
|
||||
}
|
||||
|
||||
if (self::getPHPVersionID() >= 80300 && self::getPHPVersionID() < 80400) {
|
||||
SourcePatcher::patchFile('spc_fix_avx512_cache_before_80400.patch', SOURCE_PATH . '/php-src');
|
||||
SourcePatcher::patchFile('spc_fix_avx512_cache_before_80400.patch', $this->getSourceDir());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +92,12 @@ trait unix
|
||||
$args = [];
|
||||
$version_id = self::getPHPVersionID();
|
||||
|
||||
// disable undefined behavior sanitizer for zig, trips up on lua minijit and opcache-jit
|
||||
if (SystemTarget::getTargetOS() === 'Linux' && ToolchainManager::getToolchainClass() === ZigToolchain::class) {
|
||||
$compiler_extra = getenv('SPC_COMPILER_EXTRA') ?: '';
|
||||
GlobalEnvManager::putenv('SPC_COMPILER_EXTRA=' . trim($compiler_extra . ' -fno-sanitize=undefined'));
|
||||
// disable undefined behavior sanitizer when opcache JIT is enabled (Linux only)
|
||||
if (SystemTarget::getTargetOS() === 'Linux' && !$package->getBuildOption('disable-opcache-jit', false)) {
|
||||
if ($version_id >= 80500 || $installer->isPackageResolved('ext-opcache')) {
|
||||
$compiler_extra = getenv('SPC_COMPILER_EXTRA') ?: '';
|
||||
GlobalEnvManager::putenv('SPC_COMPILER_EXTRA=' . trim($compiler_extra . ' -fno-sanitize=undefined'));
|
||||
}
|
||||
}
|
||||
// PHP JSON extension is built-in since PHP 8.0
|
||||
if ($version_id < 80000) {
|
||||
@@ -137,10 +129,6 @@ 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));
|
||||
|
||||
@@ -153,7 +141,7 @@ trait unix
|
||||
$this->seekPhpSrcLogFileOnException(fn () => shell()->cd($package->getSourceDir())->setEnv([
|
||||
'CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'),
|
||||
'CPPFLAGS' => "-I{$package->getIncludeDir()}",
|
||||
'LDFLAGS' => "-L{$package->getLibDir()}",
|
||||
'LDFLAGS' => "-L{$package->getLibDir()} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'),
|
||||
'LIBS' => $vars['EXTRA_LIBS'] ?? '',
|
||||
])->exec("{$cmd} {$args} {$static_extension_str}"), $package->getSourceDir());
|
||||
}
|
||||
@@ -180,7 +168,7 @@ trait unix
|
||||
|
||||
#[BeforeStage('php', [self::class, 'makeForUnix'], 'php')]
|
||||
#[PatchDescription('Patch Makefile to fix //lib path for Linux builds')]
|
||||
#[PatchDescription('Under CI: patch BUILD_CC to system cc — zig-cc-built minilua segfaults there for reasons we cannot reproduce locally')]
|
||||
#[PatchDescription('Patch BUILD_CC to use system cc instead of zig-cc (prevents minilua crash)')]
|
||||
public function tryPatchMakefileUnix(TargetPackage $package, ToolchainInterface $toolchain): void
|
||||
{
|
||||
if (SystemTarget::getTargetOS() !== 'Linux') {
|
||||
@@ -190,8 +178,7 @@ trait unix
|
||||
// replace //lib with /lib in Makefile
|
||||
shell()->cd($package->getSourceDir())->exec('sed -i "s|//lib|/lib|g" Makefile');
|
||||
|
||||
// CI escape hatch: in CI, zig-cc-built minilua segfaults
|
||||
if ($toolchain instanceof ZigToolchain && getenv('CI')) {
|
||||
if ($toolchain instanceof ZigToolchain) {
|
||||
$makefile = "{$package->getSourceDir()}/Makefile";
|
||||
FileSystem::replaceFileRegex($makefile, '/^BUILD_CC\s*=\s*zig-cc\s*$/m', 'BUILD_CC = cc');
|
||||
}
|
||||
@@ -242,7 +229,6 @@ trait unix
|
||||
#[Stage]
|
||||
public function makeCliForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
$start = microtime(true);
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cli'));
|
||||
$concurrency = $builder->concurrency;
|
||||
$vars = $this->makeVars($installer);
|
||||
@@ -253,13 +239,11 @@ trait unix
|
||||
|
||||
$builder->deployBinary("{$package->getSourceDir()}/sapi/cli/php", BUILD_BIN_PATH . '/php');
|
||||
$package->setOutput('Binary path for cli SAPI', BUILD_BIN_PATH . '/php');
|
||||
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-cli'), true, $start);
|
||||
}
|
||||
|
||||
#[Stage]
|
||||
public function makeCgiForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
$start = microtime(true);
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cgi'));
|
||||
$concurrency = $builder->concurrency;
|
||||
$vars = $this->makeVars($installer);
|
||||
@@ -270,13 +254,11 @@ trait unix
|
||||
|
||||
$builder->deployBinary("{$package->getSourceDir()}/sapi/cgi/php-cgi", BUILD_BIN_PATH . '/php-cgi');
|
||||
$package->setOutput('Binary path for cgi SAPI', BUILD_BIN_PATH . '/php-cgi');
|
||||
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-cgi'), true, $start);
|
||||
}
|
||||
|
||||
#[Stage]
|
||||
public function makeFpmForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
$start = microtime(true);
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make fpm'));
|
||||
$concurrency = $builder->concurrency;
|
||||
$vars = $this->makeVars($installer);
|
||||
@@ -287,58 +269,43 @@ trait unix
|
||||
|
||||
$builder->deployBinary("{$package->getSourceDir()}/sapi/fpm/php-fpm", BUILD_BIN_PATH . '/php-fpm');
|
||||
$package->setOutput('Binary path for fpm SAPI', BUILD_BIN_PATH . '/php-fpm');
|
||||
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-fpm'), true, $start);
|
||||
}
|
||||
|
||||
#[Stage]
|
||||
#[PatchDescription('Patch phar extension for micro SAPI to support compressed phar')]
|
||||
public function makeMicroForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
$start = microtime(true);
|
||||
$phar_patched = false;
|
||||
try {
|
||||
if ($installer->isPackageResolved('ext-phar')) {
|
||||
$phar_patched = true;
|
||||
SourcePatcher::patchMicroPhar(self::getPHPVersionID());
|
||||
}
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make micro'));
|
||||
// apply --with-micro-fake-cli option
|
||||
$vars = $this->makeVars($installer);
|
||||
$vars['EXTRA_CFLAGS'] .= $package->getBuildOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : '';
|
||||
$makeArgs = $this->makeVarsToArgs($vars);
|
||||
// build
|
||||
shell()->cd($package->getSourceDir())
|
||||
->setEnv($vars)
|
||||
->exec("make -j{$builder->concurrency} {$makeArgs} micro");
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make micro'));
|
||||
// apply --with-micro-fake-cli option
|
||||
$vars = $this->makeVars($installer);
|
||||
$vars['EXTRA_CFLAGS'] .= $package->getBuildOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : '';
|
||||
$makeArgs = $this->makeVarsToArgs($vars);
|
||||
// build
|
||||
shell()->cd($package->getSourceDir())
|
||||
->setEnv($vars)
|
||||
->exec("make -j{$builder->concurrency} {$makeArgs} micro");
|
||||
|
||||
$dst = BUILD_BIN_PATH . '/micro.sfx';
|
||||
$builder->deployBinary($package->getSourceDir() . '/sapi/micro/micro.sfx', $dst);
|
||||
// patch after UPX-ed micro.sfx (Linux only)
|
||||
if (SystemTarget::getTargetOS() === 'Linux' && $builder->getOption('with-upx-pack')) {
|
||||
// cut binary with readelf to remove UPX extra segment
|
||||
[$ret, $out] = shell()->execWithResult("readelf -l {$dst} | awk '/LOAD|GNU_STACK/ {getline; print \\$1, \\$2, \\$3, \\$4, \\$6, \\$7}'");
|
||||
$out[1] = explode(' ', $out[1]);
|
||||
$offset = $out[1][0];
|
||||
if ($ret !== 0 || !str_starts_with($offset, '0x')) {
|
||||
throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output');
|
||||
}
|
||||
$offset = hexdec($offset);
|
||||
// remove upx extra wastes
|
||||
file_put_contents($dst, substr(file_get_contents($dst), 0, $offset));
|
||||
}
|
||||
$package->setOutput('Binary path for micro SAPI', $dst);
|
||||
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-micro'), true, $start);
|
||||
} finally {
|
||||
if ($phar_patched) {
|
||||
SourcePatcher::unpatchMicroPhar();
|
||||
$dst = BUILD_BIN_PATH . '/micro.sfx';
|
||||
$builder->deployBinary($package->getSourceDir() . '/sapi/micro/micro.sfx', $dst);
|
||||
// patch after UPX-ed micro.sfx (Linux only)
|
||||
if (SystemTarget::getTargetOS() === 'Linux' && $builder->getOption('with-upx-pack')) {
|
||||
// cut binary with readelf to remove UPX extra segment
|
||||
[$ret, $out] = shell()->execWithResult("readelf -l {$dst} | awk '/LOAD|GNU_STACK/ {getline; print \\$1, \\$2, \\$3, \\$4, \\$6, \\$7}'");
|
||||
$out[1] = explode(' ', $out[1]);
|
||||
$offset = $out[1][0];
|
||||
if ($ret !== 0 || !str_starts_with($offset, '0x')) {
|
||||
throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output');
|
||||
}
|
||||
$offset = hexdec($offset);
|
||||
// remove upx extra wastes
|
||||
file_put_contents($dst, substr(file_get_contents($dst), 0, $offset));
|
||||
}
|
||||
$package->setOutput('Binary path for micro SAPI', $dst);
|
||||
}
|
||||
|
||||
#[Stage]
|
||||
public function makeEmbedForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void
|
||||
{
|
||||
$start = microtime(true);
|
||||
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make embed'));
|
||||
$shared_exts = array_filter(
|
||||
$installer->getResolvedPackages(),
|
||||
@@ -352,43 +319,22 @@ trait unix
|
||||
$root = BUILD_ROOT_PATH;
|
||||
$sed_prefix = SystemTarget::getTargetOS() === 'Darwin' ? 'sed -i ""' : 'sed -i';
|
||||
|
||||
$vars = $this->makeVars($installer);
|
||||
$makeArgs = $this->makeVarsToArgs($vars);
|
||||
shell()->cd($package->getSourceDir())
|
||||
->setEnv($vars)
|
||||
->setEnv($this->makeVars($installer))
|
||||
->exec("{$sed_prefix} \"s|^EXTENSION_DIR = .*|EXTENSION_DIR = /" . basename(BUILD_MODULES_PATH) . '|" Makefile')
|
||||
->exec("make -j{$builder->concurrency} {$makeArgs} INSTALL_ROOT={$root} install-sapi {$install_modules} install-build install-headers install-programs");
|
||||
|
||||
// install-modules deref'd libtool's `$ext.so → $ext-X.so` symlink for each built-with-php ext; restore them.
|
||||
$release = null;
|
||||
if (preg_match('/-release\s+(\S+)/', (string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), $m)) {
|
||||
$release = $m[1];
|
||||
foreach ($shared_exts as $ext) {
|
||||
$name = $ext->getExtensionName();
|
||||
$u = BUILD_MODULES_PATH . "/{$name}.so";
|
||||
$v = BUILD_MODULES_PATH . "/{$name}-{$release}.so";
|
||||
if (file_exists($v) && file_exists($u) && !is_link($u)) {
|
||||
unlink($u);
|
||||
symlink(basename($v), $u);
|
||||
}
|
||||
}
|
||||
}
|
||||
->exec("make -j{$builder->concurrency} INSTALL_ROOT={$root} install-sapi {$install_modules} install-build install-headers install-programs");
|
||||
|
||||
// ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=shared -------------
|
||||
|
||||
// INSTALL_IT for embed copies through libtool's symlink, leaving only unversioned libphp.{so,dylib} — rename and symlink back so shared exts can `-lphp`. (static libphp.a is never versioned, even with -release.)
|
||||
// process libphp.so for shared embed
|
||||
$suffix = SystemTarget::getTargetOS() === 'Darwin' ? 'dylib' : 'so';
|
||||
$libphp_so = "{$package->getLibDir()}/libphp.{$suffix}";
|
||||
if (file_exists($libphp_so)) {
|
||||
if ($release !== null) {
|
||||
$versioned = "{$package->getLibDir()}/libphp-{$release}.{$suffix}";
|
||||
if (file_exists($versioned)) {
|
||||
@unlink($versioned);
|
||||
}
|
||||
rename($libphp_so, $versioned);
|
||||
symlink(basename($versioned), $libphp_so);
|
||||
$libphp_so = $versioned;
|
||||
// rename libphp.so if -release is set
|
||||
if (SystemTarget::getTargetOS() === 'Linux') {
|
||||
$this->processLibphpSoFile($libphp_so, $installer);
|
||||
}
|
||||
// deploy
|
||||
$builder->deployBinary($libphp_so, $libphp_so, false);
|
||||
$package->setOutput('Library path for embed SAPI', $libphp_so);
|
||||
}
|
||||
@@ -397,9 +343,6 @@ trait unix
|
||||
$increment_files = $diff->getChangedFiles();
|
||||
$files = [];
|
||||
foreach ($increment_files as $increment_file) {
|
||||
if (is_link($increment_file) || !file_exists($increment_file)) {
|
||||
continue;
|
||||
}
|
||||
$builder->deployBinary($increment_file, $increment_file, false);
|
||||
$files[] = basename($increment_file);
|
||||
}
|
||||
@@ -407,11 +350,6 @@ trait unix
|
||||
$package->setOutput('Built shared extensions', implode(', ', $files));
|
||||
}
|
||||
|
||||
// phpize needs prefix patched whether libphp is .a or .so
|
||||
$package->runStage([$this, 'patchUnixEmbedScripts']);
|
||||
|
||||
InteractiveTerm::success('Built SAPI: ' . ConsoleColor::green('php-embed'), true, $start);
|
||||
|
||||
// ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=static -------------
|
||||
|
||||
// process libphp.a for static embed (only when present)
|
||||
@@ -421,6 +359,9 @@ trait unix
|
||||
shell()->exec("{$ar} -t {$libphp_a} | grep '\\.a$' | xargs -n1 {$ar} d {$libphp_a}");
|
||||
UnixUtil::exportDynamicSymbols($libphp_a);
|
||||
}
|
||||
|
||||
// deploy embed php scripts
|
||||
$package->runStage([$this, 'patchUnixEmbedScripts']);
|
||||
}
|
||||
|
||||
#[Stage]
|
||||
@@ -453,15 +394,8 @@ trait unix
|
||||
try {
|
||||
logger()->debug('Building shared extensions...');
|
||||
foreach ($shared_extensions as $extension) {
|
||||
$ext_start = microtime(true);
|
||||
InteractiveTerm::setMessage('Building shared extension: ' . ConsoleColor::yellow($extension->getName()));
|
||||
try {
|
||||
$extension->buildShared();
|
||||
} catch (\Throwable $e) {
|
||||
InteractiveTerm::error('Building shared extension failed: ' . ConsoleColor::red($extension->getName()));
|
||||
throw $e;
|
||||
}
|
||||
InteractiveTerm::success('Built shared extension: ' . ConsoleColor::green($extension->getName()), true, $ext_start);
|
||||
InteractiveTerm::setMessage('Building shared PHP extension: ' . ConsoleColor::yellow($extension->getName()));
|
||||
$extension->buildShared();
|
||||
}
|
||||
} finally {
|
||||
// restore php-config
|
||||
@@ -553,8 +487,6 @@ 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']);
|
||||
}
|
||||
|
||||
@@ -667,7 +599,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(['php']);
|
||||
$config = new SPCConfigUtil()->config($installer->getAvailableResolvedPackageNames());
|
||||
$lens = "{$config['cflags']} {$config['ldflags']} {$config['libs']}";
|
||||
if ($toolchain->isStatic()) {
|
||||
$lens .= ' -static';
|
||||
@@ -748,37 +680,96 @@ trait unix
|
||||
return $php;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename libphp.so to libphp-<release>.so if -release is set in LDFLAGS.
|
||||
*/
|
||||
private function processLibphpSoFile(string $libphpSo, PackageInstaller $installer): void
|
||||
{
|
||||
$ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: '';
|
||||
$libDir = BUILD_LIB_PATH;
|
||||
$modulesDir = BUILD_MODULES_PATH;
|
||||
$realLibName = 'libphp.so';
|
||||
$cwd = getcwd();
|
||||
|
||||
if (preg_match('/-release\s+(\S+)/', $ldflags, $matches)) {
|
||||
$release = $matches[1];
|
||||
$realLibName = "libphp-{$release}.so";
|
||||
$libphpRelease = "{$libDir}/{$realLibName}";
|
||||
if (!file_exists($libphpRelease) && file_exists($libphpSo)) {
|
||||
rename($libphpSo, $libphpRelease);
|
||||
}
|
||||
if (file_exists($libphpRelease)) {
|
||||
chdir($libDir);
|
||||
if (file_exists($libphpSo)) {
|
||||
unlink($libphpSo);
|
||||
}
|
||||
symlink($realLibName, 'libphp.so');
|
||||
shell()->exec(sprintf(
|
||||
'patchelf --set-soname %s %s',
|
||||
escapeshellarg($realLibName),
|
||||
escapeshellarg($libphpRelease)
|
||||
));
|
||||
}
|
||||
if (is_dir($modulesDir)) {
|
||||
chdir($modulesDir);
|
||||
foreach ($installer->getResolvedPackages(PhpExtensionPackage::class) as $ext) {
|
||||
if (!$ext->isBuildShared()) {
|
||||
continue;
|
||||
}
|
||||
$name = $ext->getName();
|
||||
$versioned = "{$name}-{$release}.so";
|
||||
$unversioned = "{$name}.so";
|
||||
$src = "{$modulesDir}/{$versioned}";
|
||||
$dst = "{$modulesDir}/{$unversioned}";
|
||||
if (is_file($src)) {
|
||||
rename($src, $dst);
|
||||
shell()->exec(sprintf(
|
||||
'patchelf --set-soname %s %s',
|
||||
escapeshellarg($unversioned),
|
||||
escapeshellarg($dst)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
chdir($cwd);
|
||||
}
|
||||
|
||||
$target = "{$libDir}/{$realLibName}";
|
||||
if (file_exists($target)) {
|
||||
[, $output] = shell()->execWithResult('readelf -d ' . escapeshellarg($target));
|
||||
$output = implode("\n", $output);
|
||||
if (preg_match('/SONAME.*\[(.+)]/', $output, $sonameMatch)) {
|
||||
$currentSoname = $sonameMatch[1];
|
||||
if ($currentSoname !== basename($target)) {
|
||||
shell()->exec(sprintf(
|
||||
'patchelf --set-soname %s %s',
|
||||
escapeshellarg(basename($target)),
|
||||
escapeshellarg($target)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make environment variables for php make.
|
||||
* This will call SPCConfigUtil to generate proper LDFLAGS and LIBS for static linking.
|
||||
*/
|
||||
private function makeVars(PackageInstaller $installer): array
|
||||
{
|
||||
$config = new SPCConfigUtil(['libs_only_deps' => true])->config(['php']);
|
||||
$config = new SPCConfigUtil(['libs_only_deps' => true])->config($installer->getAvailableResolvedPackageNames());
|
||||
$static = ApplicationContext::get(ToolchainInterface::class)->isStatic() ? '-all-static' : '';
|
||||
$pie = SystemTarget::getTargetOS() === 'Linux' ? '-pie' : '';
|
||||
$lib = BUILD_LIB_PATH;
|
||||
|
||||
// Append SPC_EXTRA_LIBS to libs for dynamic linking support (e.g., X11)
|
||||
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
|
||||
$libs = trim($config['libs'] . ' ' . $extra_libs);
|
||||
|
||||
// libtool input (libphp.la). `make EXTRA_LDFLAGS=…` cmdline overrides fully replace the Makefile value, so re-include $config['ldflags'] for -L paths.
|
||||
$extra_ldflags = clean_spaces($config['ldflags'] . ' ' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'));
|
||||
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared'
|
||||
&& !str_contains($extra_ldflags, '-avoid-version')
|
||||
&& !preg_match('/-release\s+\S+/', $extra_ldflags)) {
|
||||
$extra_ldflags = trim($extra_ldflags . ' -avoid-version -module');
|
||||
}
|
||||
|
||||
$extra_ldflags_program_env = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM') ?: '';
|
||||
$extra_ldflags_program = clean_spaces("-L{$lib} {$static} {$pie} {$extra_ldflags_program_env}");
|
||||
|
||||
return array_filter([
|
||||
'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'),
|
||||
'EXTRA_CXXFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS'),
|
||||
'EXTRA_LDFLAGS' => $extra_ldflags,
|
||||
'EXTRA_LDFLAGS_PROGRAM' => $extra_ldflags_program,
|
||||
'EXTRA_LDFLAGS_PROGRAM' => deduplicate_flags(getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') . " {$config['ldflags']} {$static} {$pie}"),
|
||||
'EXTRA_LDFLAGS' => $config['ldflags'],
|
||||
'EXTRA_LIBS' => $libs,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -27,18 +27,12 @@ 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 = [];
|
||||
|
||||
@@ -291,19 +285,15 @@ class Artifact
|
||||
* Get source extraction directory.
|
||||
*
|
||||
* Rules:
|
||||
* 1. If cache_type is 'local': use the absolute dirname recorded at download time (no symlink/copy).
|
||||
* 2. If extract is not specified: SOURCE_PATH/{artifact_name}
|
||||
* 3. If extract is relative path: SOURCE_PATH/{value}
|
||||
* 4. If extract is absolute path: {value}
|
||||
* 5. If extract is array (dict): handled by extractor (selective extraction)
|
||||
* 1. If extract is not specified: SOURCE_PATH/{artifact_name}
|
||||
* 2. If extract is relative path: SOURCE_PATH/{value}
|
||||
* 3. If extract is absolute path: {value}
|
||||
* 4. If extract is array (dict): handled by extractor (selective extraction)
|
||||
*/
|
||||
public function getSourceDir(): string
|
||||
{
|
||||
// Prefer cache extract path, fall back to config
|
||||
$cache_info = ApplicationContext::get(ArtifactCache::class)->getSourceInfo($this->name);
|
||||
if (($cache_info['cache_type'] ?? null) === 'local' && isset($cache_info['dirname'])) {
|
||||
return FileSystem::convertPath($cache_info['dirname']);
|
||||
}
|
||||
$extract = is_string($cache_info['extract'] ?? null)
|
||||
? $cache_info['extract']
|
||||
: ($this->config['source']['extract'] ?? null);
|
||||
@@ -417,13 +407,10 @@ 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, string $origin = 'package downloader'): void
|
||||
public function setCustomSourceCallback(callable $callback): void
|
||||
{
|
||||
$this->custom_source_callback = $callback;
|
||||
$this->custom_source_callback_origin = $origin;
|
||||
}
|
||||
|
||||
public function getCustomSourceCallback(): ?callable
|
||||
@@ -431,11 +418,6 @@ class Artifact
|
||||
return $this->custom_source_callback ?? null;
|
||||
}
|
||||
|
||||
public function getCustomSourceCallbackOrigin(): ?string
|
||||
{
|
||||
return $this->custom_source_callback_origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom source check-update callback.
|
||||
*/
|
||||
@@ -470,19 +452,11 @@ 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, string $origin = 'package downloader'): void
|
||||
public function setCustomBinaryCallback(string $target_os, callable $callback): 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -317,10 +317,7 @@ class ArtifactDownloader
|
||||
if (!is_dir(DOWNLOAD_PATH)) {
|
||||
FileSystem::createDir(DOWNLOAD_PATH);
|
||||
}
|
||||
$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} ...");
|
||||
}
|
||||
logger()->info('Downloading' . implode(', ', array_map(fn ($x) => " '{$x->getName()}'", $this->artifacts)) . " with concurrency {$this->parallel} ...");
|
||||
// Download artifacts parallelly
|
||||
if ($this->parallel > 1) {
|
||||
$this->downloadWithConcurrency();
|
||||
@@ -554,8 +551,8 @@ class ArtifactDownloader
|
||||
$instance = null;
|
||||
$call = $this->downloaders[$item['config']['type']] ?? null;
|
||||
$type_display_name = match (true) {
|
||||
$item['lock'] === 'source' && $artifact->getCustomSourceCallback() !== null => $artifact->getCustomSourceCallbackOrigin() ?? 'source package downloader',
|
||||
$item['lock'] === 'binary' && $artifact->getCustomBinaryCallback() !== null => $artifact->getCustomBinaryCallbackOrigin() ?? 'binary package downloader',
|
||||
$item['lock'] === 'source' && ($callback = $artifact->getCustomSourceCallback()) !== null => 'user defined source downloader',
|
||||
$item['lock'] === 'binary' && ($callback = $artifact->getCustomBinaryCallback()) !== null => 'user defined binary downloader',
|
||||
default => SPC_DOWNLOAD_TYPE_DISPLAY_NAME[$item['config']['type']] ?? $item['config']['type'],
|
||||
};
|
||||
$try_h = $try ? 'Try downloading' : 'Downloading';
|
||||
@@ -734,16 +731,6 @@ class ArtifactDownloader
|
||||
$binary_downloaded = $artifact->isBinaryDownloaded(compare_hash: true);
|
||||
$source_downloaded = $artifact->isSourceDownloaded(compare_hash: true);
|
||||
|
||||
if ($source_downloaded && $artifact->getName() === 'php-src' && ($requested = $this->getOption('with-php'))) {
|
||||
$info = ApplicationContext::get(ArtifactCache::class)->getSourceInfo('php-src');
|
||||
$cv = $info['version'] ?? null;
|
||||
$ct = $info['cache_type'] ?? null;
|
||||
$matches = $requested === 'git' ? $ct === 'git' : ($cv !== null && $ct !== 'git' && ($cv === $requested || str_starts_with($cv, $requested . '.')));
|
||||
if (!$matches) {
|
||||
$source_downloaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
$item_source = ['display' => 'source', 'lock' => 'source', 'config' => $artifact->getDownloadConfig('source')];
|
||||
$item_source_mirror = ['display' => 'source (mirror)', 'lock' => 'source', 'config' => $artifact->getDownloadConfig('source-mirror')];
|
||||
|
||||
@@ -838,21 +825,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');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,12 +136,6 @@ class ArtifactExtractor
|
||||
throw new WrongUsageException("Artifact source [{$name}] not downloaded, please download it first!");
|
||||
}
|
||||
|
||||
// Local (--custom-local): source lives in place at $cache_info['dirname'].
|
||||
if (($cache_info['cache_type'] ?? null) === 'local') {
|
||||
$artifact->emitAfterSourceExtract($artifact->getSourceDir());
|
||||
return SPC_STATUS_ALREADY_EXTRACTED;
|
||||
}
|
||||
|
||||
$source_file = $this->cache->getCacheFullPath($cache_info);
|
||||
$target_path = $artifact->getSourceDir();
|
||||
|
||||
@@ -177,12 +171,8 @@ class ArtifactExtractor
|
||||
return SPC_STATUS_ALREADY_EXTRACTED;
|
||||
}
|
||||
|
||||
// Remove old directory if hash mismatch.
|
||||
// Guard: a symlink at $target_path (left over from older local-source handling) must be
|
||||
// unlinked directly — never recurse into the link target, that would wipe the user's tree.
|
||||
if (is_link($target_path)) {
|
||||
@unlink($target_path);
|
||||
} elseif (is_dir($target_path)) {
|
||||
// Remove old directory if hash mismatch
|
||||
if (is_dir($target_path)) {
|
||||
logger()->notice("Source [{$name}] hash mismatch, re-extracting...");
|
||||
FileSystem::removeDir($target_path);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,7 @@ use StaticPHP\Doctor\Doctor;
|
||||
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;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
@@ -26,8 +21,6 @@ class CraftCommand extends BaseCommand
|
||||
public function configure(): void
|
||||
{
|
||||
$this->addArgument('craft', null, 'Path to craft.yml file', WORKING_DIR . '/craft.yml');
|
||||
$this->addOption('libs-only', null, null, 'Build only the libraries needed by the configured extensions (skip PHP and SAPI build).');
|
||||
PgoContext::registerOptions($this);
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
@@ -46,13 +39,6 @@ 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);
|
||||
|
||||
// run doctor
|
||||
if ($craft['craft-options']['doctor']) {
|
||||
$doctor = new Doctor($this->output, FIX_POLICY_AUTOFIX);
|
||||
@@ -97,67 +83,23 @@ class CraftCommand extends BaseCommand
|
||||
FileSystem::resetDir(SOURCE_PATH);
|
||||
}
|
||||
|
||||
$pgo = $this->getOption('libs-only') ? null : PgoContext::tryFromInput($this->input, $craft['sapi'], $build_options);
|
||||
|
||||
$starttime = microtime(true);
|
||||
// run installer
|
||||
$installer = new PackageInstaller($build_options);
|
||||
ApplicationContext::get(PackageBuilder::class)->setArgument('extensions', implode(',', $craft['extensions']));
|
||||
|
||||
if ($this->getOption('libs-only')) {
|
||||
$with_suggests = (bool) ($craft['build-options']['with-suggests'] ?? false);
|
||||
$libs = $this->resolveLibsForExtensions($craft, $with_suggests);
|
||||
if ($libs === []) {
|
||||
$this->output->writeln('<comment>No libraries needed for the configured extensions; nothing to do.</comment>');
|
||||
return static::SUCCESS;
|
||||
}
|
||||
foreach ($libs as $lib) {
|
||||
$installer->addBuildPackage($lib);
|
||||
}
|
||||
} else {
|
||||
$installer->addBuildPackage('php');
|
||||
}
|
||||
$installer->addBuildPackage('php');
|
||||
$installer->run(true);
|
||||
|
||||
$usedtime = round(microtime(true) - $starttime, 1);
|
||||
$tag = $pgo !== null ? " (PGO {$pgo->mode})" : '';
|
||||
$this->output->writeln("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||
$this->output->writeln("<info>✔ BUILD SUCCESSFUL{$tag} ({$usedtime} s)</info>");
|
||||
$this->output->writeln("<info>✔ BUILD SUCCESSFUL ({$usedtime} s)</info>");
|
||||
$this->output->writeln("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
||||
|
||||
if ($pgo !== null && $pgo->isInstrument()) {
|
||||
$this->output->writeln("<comment>Next: exercise the instrumented binary, then re-run craft with --pgo to consume {$pgo->profileRoot}.</comment>");
|
||||
}
|
||||
|
||||
$installer->printBuildPackageOutputs();
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
|
||||
/** @return list<string> library package names transitively required by the configured extensions */
|
||||
private function resolveLibsForExtensions(array $craft, bool $include_suggests): array
|
||||
{
|
||||
$exts = array_merge($craft['extensions'], $craft['shared-extensions'] ?? []);
|
||||
$ext_pkgs = array_map(fn ($x) => "ext-{$x}", $exts);
|
||||
$extra = $craft['packages'] ?? [];
|
||||
|
||||
$resolved = DependencyResolver::resolve(
|
||||
array_merge($ext_pkgs, $extra),
|
||||
include_suggests: $include_suggests,
|
||||
);
|
||||
|
||||
$libs = [];
|
||||
foreach ($resolved as $pkg_name) {
|
||||
if (str_starts_with($pkg_name, 'ext-') || !PackageLoader::hasPackage($pkg_name)) {
|
||||
continue;
|
||||
}
|
||||
if (PackageLoader::getPackage($pkg_name)->getType() === 'library') {
|
||||
$libs[] = $pkg_name;
|
||||
}
|
||||
}
|
||||
return $libs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and parse craft.yml file to array.
|
||||
*
|
||||
|
||||
@@ -10,7 +10,6 @@ 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;
|
||||
@@ -35,7 +34,6 @@ 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);
|
||||
$config = $util->config($packages, $include_suggests);
|
||||
|
||||
$this->output->writeln(match (true) {
|
||||
$this->getOption('includes') => $config['cflags'],
|
||||
|
||||
@@ -98,25 +98,6 @@ class ApplicationContext
|
||||
return self::getContainer()->has($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve $id, returning null if it can't be constructed.
|
||||
* PHP-DI's has() returns true for any autowirable class even when get()
|
||||
* would throw on missing scalar args — for "is this resolvable right now"
|
||||
* semantics use this.
|
||||
*
|
||||
* @template T
|
||||
* @param class-string<T> $id
|
||||
* @return null|T
|
||||
*/
|
||||
public static function tryGet(string $id): mixed
|
||||
{
|
||||
try {
|
||||
return self::getContainer()->get($id);
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a service in the container.
|
||||
* Use sparingly - prefer configuration-based definitions.
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Doctor\Item;
|
||||
|
||||
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;
|
||||
|
||||
#[OptionalCheck([self::class, 'optionalCheck'])]
|
||||
class GoXcaddyCheck
|
||||
{
|
||||
public static function optionalCheck(): bool
|
||||
{
|
||||
if (!ApplicationContext::has('craft')) {
|
||||
return false;
|
||||
}
|
||||
/** @var null|array $craft */
|
||||
$craft = ApplicationContext::get('craft');
|
||||
return in_array('frankenphp', $craft['sapi'] ?? [], true);
|
||||
}
|
||||
|
||||
#[CheckItem('if go-xcaddy is installed', level: 800)]
|
||||
public function check(): CheckResult
|
||||
{
|
||||
if (!new PackageInstaller()->addInstallPackage('go-xcaddy')->isPackageInstalled('go-xcaddy')) {
|
||||
return CheckResult::fail('go-xcaddy is not installed', 'install-go-xcaddy');
|
||||
}
|
||||
return CheckResult::ok(PKG_ROOT_PATH . '/go-xcaddy/bin/xcaddy');
|
||||
}
|
||||
|
||||
#[FixItem('install-go-xcaddy')]
|
||||
public function installGoXcaddy(): bool
|
||||
{
|
||||
$installer = new PackageInstaller(interactive: false);
|
||||
$installer->addInstallPackage('go-xcaddy');
|
||||
$installer->run(true);
|
||||
return $installer->isPackageInstalled('go-xcaddy');
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?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,7 +4,6 @@ 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;
|
||||
@@ -27,7 +26,7 @@ class ZigCheck
|
||||
public function checkZig(): CheckResult
|
||||
{
|
||||
if (new PackageInstaller()->addInstallPackage('zig')->isPackageInstalled('zig')) {
|
||||
return CheckResult::ok(zig::binary());
|
||||
return CheckResult::ok();
|
||||
}
|
||||
return CheckResult::fail('zig is not installed', 'install-zig');
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ 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;
|
||||
@@ -180,15 +178,14 @@ 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');
|
||||
@@ -202,12 +199,9 @@ 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'),
|
||||
});
|
||||
|
||||
@@ -154,9 +154,6 @@ class PackageInstaller
|
||||
$this->resolvePackages();
|
||||
}
|
||||
|
||||
$this->reconcilePhpSrcVersion();
|
||||
$this->emitPreInstallEvents();
|
||||
|
||||
if ($this->interactive && !$disable_delay_msg) {
|
||||
// show install or build options in terminal with beautiful output
|
||||
$this->printInstallerInfo();
|
||||
@@ -218,7 +215,7 @@ class PackageInstaller
|
||||
if (!$is_to_build && $should_use_binary) {
|
||||
// install binary
|
||||
if ($this->interactive) {
|
||||
InteractiveTerm::indicateProgress('Installing ' . $this->kindLabel($package) . ': ' . ConsoleColor::yellow($package->getName()));
|
||||
InteractiveTerm::indicateProgress('Installing package: ' . ConsoleColor::yellow($package->getName()));
|
||||
}
|
||||
try {
|
||||
// Start tracking for binary installation
|
||||
@@ -230,17 +227,17 @@ class PackageInstaller
|
||||
// Stop tracking on error
|
||||
$this->tracker?->stopTracking();
|
||||
if ($this->interactive) {
|
||||
InteractiveTerm::finish('Installing ' . $this->kindLabel($package) . ' failed: ' . ConsoleColor::red($package->getName()), false);
|
||||
InteractiveTerm::finish('Installing binary package failed: ' . ConsoleColor::red($package->getName()), false);
|
||||
echo PHP_EOL;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
if ($this->interactive) {
|
||||
InteractiveTerm::finish('Installed ' . $this->kindLabel($package) . ': ' . ConsoleColor::green($package->getName()) . ($status === SPC_STATUS_ALREADY_INSTALLED ? ' (already installed, skipped)' : ''));
|
||||
InteractiveTerm::finish('Installed binary package: ' . ConsoleColor::green($package->getName()) . ($status === SPC_STATUS_ALREADY_INSTALLED ? ' (already installed, skipped)' : ''));
|
||||
}
|
||||
} elseif ($is_to_build && $has_build_stage || $has_source && $has_build_stage) {
|
||||
if ($this->interactive) {
|
||||
InteractiveTerm::indicateProgress('Building ' . $this->kindLabel($package) . ': ' . ConsoleColor::yellow($package->getName()));
|
||||
InteractiveTerm::indicateProgress('Building package: ' . ConsoleColor::yellow($package->getName()));
|
||||
}
|
||||
try {
|
||||
// Start tracking for build
|
||||
@@ -263,13 +260,13 @@ class PackageInstaller
|
||||
// Stop tracking on error
|
||||
$this->tracker?->stopTracking();
|
||||
if ($this->interactive) {
|
||||
InteractiveTerm::finish('Building ' . $this->kindLabel($package) . ' failed: ' . ConsoleColor::red($package->getName()), false);
|
||||
InteractiveTerm::finish('Building package failed: ' . ConsoleColor::red($package->getName()), false);
|
||||
echo PHP_EOL;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
if ($this->interactive) {
|
||||
InteractiveTerm::finish('Built ' . $this->kindLabel($package) . ': ' . ConsoleColor::green($package->getName()) . ($status === SPC_STATUS_ALREADY_BUILT ? ' (already built, skipped)' : ''));
|
||||
InteractiveTerm::finish('Built package: ' . ConsoleColor::green($package->getName()) . ($status === SPC_STATUS_ALREADY_BUILT ? ' (already built, skipped)' : ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,15 +359,6 @@ 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) {
|
||||
@@ -583,77 +571,6 @@ class PackageInstaller
|
||||
return null;
|
||||
}
|
||||
|
||||
private function reconcilePhpSrcVersion(): void
|
||||
{
|
||||
$src_dir = SOURCE_PATH . '/php-src';
|
||||
$cache = ApplicationContext::get(ArtifactCache::class);
|
||||
$requested = $this->options['dl-with-php'] ?? null;
|
||||
if ($requested !== null && $requested !== '' && $requested !== 'git') {
|
||||
$info = $cache->getSourceInfo('php-src') ?? [];
|
||||
$cv = $info['version'] ?? null;
|
||||
if (($info['cache_type'] ?? null) === 'git' || $cv === null
|
||||
|| ($cv !== $requested && !str_starts_with($cv, $requested . '.'))) {
|
||||
$resolved = null;
|
||||
$candidates = glob(DOWNLOAD_PATH . '/php-' . $requested . '.*.tar.xz') ?: [];
|
||||
if ($candidates !== []) {
|
||||
usort($candidates, 'strnatcmp');
|
||||
if (preg_match('/^php-([0-9.]+)\.tar\.xz$/', basename(end($candidates)), $vm)) {
|
||||
$resolved = $vm[1];
|
||||
}
|
||||
} elseif ($this->download) {
|
||||
$j = @file_get_contents('https://www.php.net/releases/index.php?json&version=' . urlencode($requested));
|
||||
$rel = is_string($j) ? json_decode($j, true) : null;
|
||||
$resolved = is_array($rel) ? ($rel['version'] ?? null) : null;
|
||||
} else {
|
||||
throw new WrongUsageException("Requested PHP '{$requested}' but no php-{$requested}.*.tar.xz in downloads/; drop --no-download or run 'bin/spc download php-src --with-php={$requested}' first.");
|
||||
}
|
||||
if ($resolved !== null) {
|
||||
$cf = DOWNLOAD_PATH . '/.cache.json';
|
||||
$j = json_decode(@file_get_contents($cf) ?: '{}', true) ?: [];
|
||||
$tarball = DOWNLOAD_PATH . "/php-{$resolved}.tar.xz";
|
||||
$j['php-src']['source'] = [
|
||||
'lock_type' => 'source', 'cache_type' => 'archive',
|
||||
'filename' => "php-{$resolved}.tar.xz",
|
||||
'extract' => $info['extract'] ?? null,
|
||||
'hash' => is_file($tarball) ? sha1_file($tarball) : null,
|
||||
'time' => time(), 'version' => $resolved,
|
||||
'config' => $info['config'] ?? ['type' => 'php-release', 'domain' => 'https://www.php.net'],
|
||||
'downloader' => $info['downloader'] ?? \StaticPHP\Artifact\Downloader\Type\PhpRelease::class,
|
||||
];
|
||||
$j['php-src']['binary'] ??= [];
|
||||
file_put_contents($cf, json_encode($j, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
ApplicationContext::set(ArtifactCache::class, $cache = new ArtifactCache());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!is_dir($src_dir) || ($info = $cache->getSourceInfo('php-src')) === null) {
|
||||
return;
|
||||
}
|
||||
if (($info['cache_type'] ?? null) === 'git') {
|
||||
if (!is_dir($src_dir . '/.git')) {
|
||||
FileSystem::removeDir($src_dir);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$vh = $src_dir . '/main/php_version.h';
|
||||
if (is_file($vh)
|
||||
&& preg_match('/#define\s+PHP_VERSION\s+"([^"]+)"/', file_get_contents($vh), $m)
|
||||
&& $m[1] !== ($info['version'] ?? null)
|
||||
) {
|
||||
FileSystem::removeDir($src_dir);
|
||||
}
|
||||
}
|
||||
|
||||
private function kindLabel(Package $package): string
|
||||
{
|
||||
return match (true) {
|
||||
$package instanceof PhpExtensionPackage => 'extension',
|
||||
$package instanceof TargetPackage => 'target',
|
||||
$package instanceof LibraryPackage => 'library',
|
||||
default => 'package',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Package[] $packages
|
||||
*/
|
||||
@@ -771,7 +688,7 @@ class PackageInstaller
|
||||
if ($package->getBuildOption('build-all') || $package->getBuildOption('build-frankenphp')) {
|
||||
$frankenphp = PackageLoader::getPackage('frankenphp');
|
||||
$this->install_packages[$frankenphp->getName()] = $frankenphp;
|
||||
$this->build_packages[$frankenphp->getName()] = $frankenphp;
|
||||
$this->build_packages[$package->getName()] = $package;
|
||||
$added = true;
|
||||
}
|
||||
$this->build_packages[$package->getName()] = $package;
|
||||
|
||||
@@ -12,7 +12,6 @@ use StaticPHP\Exception\WrongUsageException;
|
||||
use StaticPHP\Runtime\SystemTarget;
|
||||
use StaticPHP\Toolchain\ToolchainManager;
|
||||
use StaticPHP\Toolchain\ZigToolchain;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
use StaticPHP\Util\GlobalEnvManager;
|
||||
use StaticPHP\Util\SPCConfigUtil;
|
||||
|
||||
@@ -279,15 +278,10 @@ class PhpExtensionPackage extends Package
|
||||
[$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']);
|
||||
$preStatic = PHP_OS_FAMILY === 'Darwin' ? '' : '-Wl,--start-group ';
|
||||
$postStatic = PHP_OS_FAMILY === 'Darwin' ? '' : ' -Wl,--end-group ';
|
||||
// -Wl,-Bsymbolic: bind zend_* refs to the .so's own copies, not via global lookup
|
||||
$ldflags = (string) $config['ldflags'];
|
||||
if (PHP_OS_FAMILY !== 'Darwin' && !str_contains($ldflags, '-Wl,-Bsymbolic')) {
|
||||
$ldflags = clean_spaces($ldflags . ' -Wl,-Bsymbolic');
|
||||
}
|
||||
return [
|
||||
'CFLAGS' => $config['cflags'],
|
||||
'CXXFLAGS' => $config['cxxflags'],
|
||||
'LDFLAGS' => $ldflags,
|
||||
'CXXFLAGS' => $config['cflags'],
|
||||
'LDFLAGS' => $config['ldflags'],
|
||||
'LIBS' => clean_spaces("{$preStatic} {$staticLibs} {$postStatic} {$sharedLibs}"),
|
||||
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,
|
||||
];
|
||||
@@ -309,7 +303,6 @@ class PhpExtensionPackage extends Package
|
||||
public function configureForUnix(array $env, PhpExtensionPackage $package): void
|
||||
{
|
||||
$phpvars = getenv('SPC_EXTRA_PHP_VARS') ?: '';
|
||||
// CustomPhpConfigureArg keys are OS names ('Linux'/'Darwin'), not platform strings
|
||||
shell()->cd($package->getSourceDir())
|
||||
->setEnv($env)
|
||||
->exec(
|
||||
@@ -325,53 +318,11 @@ class PhpExtensionPackage extends Package
|
||||
#[Stage]
|
||||
public function makeForUnix(array $env, PhpExtensionPackage $package, PackageBuilder $builder): void
|
||||
{
|
||||
// phpize Makefile's _SHARED_LIBADD line misses our static archives — splice them in
|
||||
$package->patchSharedLibAdd();
|
||||
$extra_ldflags = (string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS');
|
||||
$makeArgs = $extra_ldflags !== '' ? 'EXTRA_LDFLAGS=' . escapeshellarg($extra_ldflags) : '';
|
||||
shell()->cd($package->getSourceDir())
|
||||
->setEnv($env)
|
||||
->exec('make clean')
|
||||
->exec("make -j{$builder->concurrency} {$makeArgs}")
|
||||
->exec("make install {$makeArgs}");
|
||||
|
||||
// install-modules deref'd libtool's `$ext.so → $ext-X.so` symlink into two regular files; restore the symlink.
|
||||
if (preg_match('/-release\s+(\S+)/', $extra_ldflags, $m)) {
|
||||
$name = $package->getExtensionName();
|
||||
$unversioned = BUILD_MODULES_PATH . "/{$name}.so";
|
||||
$versioned = BUILD_MODULES_PATH . "/{$name}-{$m[1]}.so";
|
||||
if (file_exists($versioned) && file_exists($unversioned) && !is_link($unversioned)) {
|
||||
unlink($unversioned);
|
||||
symlink(basename($versioned), $unversioned);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function patchSharedLibAdd(): void
|
||||
{
|
||||
$config = new SPCConfigUtil()->getExtensionConfig($this);
|
||||
[$staticLibs, $sharedLibs] = $this->splitLibsIntoStaticAndShared($config['libs']);
|
||||
$lstdcpp = str_contains($sharedLibs, '-l:libstdc++.a')
|
||||
? '-l:libstdc++.a'
|
||||
: (str_contains($sharedLibs, '-lstdc++') ? '-lstdc++' : '');
|
||||
|
||||
$makefile = $this->getSourceDir() . '/Makefile';
|
||||
if (!is_file($makefile)) {
|
||||
return;
|
||||
}
|
||||
$content = (string) file_get_contents($makefile);
|
||||
if (!preg_match('/^(.*_SHARED_LIBADD\s*=\s*)(.*)$/m', $content, $m)) {
|
||||
return;
|
||||
}
|
||||
$prefix = $m[1];
|
||||
$current = trim($m[2]);
|
||||
$merged = clean_spaces("{$current} {$staticLibs} {$lstdcpp}");
|
||||
$merged = deduplicate_flags($merged);
|
||||
FileSystem::replaceFileRegex(
|
||||
$makefile,
|
||||
'/^(.*_SHARED_LIBADD\s*=.*)$/m',
|
||||
$prefix . $merged
|
||||
);
|
||||
->exec("make -j{$builder->concurrency}")
|
||||
->exec('make install');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,31 +333,14 @@ class PhpExtensionPackage extends Package
|
||||
*/
|
||||
public function buildSharedForUnix(PackageBuilder $builder): void
|
||||
{
|
||||
// skip virtual addons (arg-type=none + display-name → owning ext); the parent ext built it
|
||||
$argType = $this->extension_config['arg-type'] ?? null;
|
||||
$displayName = $this->extension_config['display-name'] ?? null;
|
||||
if ($argType === 'none' && $displayName !== null && $displayName !== $this->getExtensionName()) {
|
||||
logger()->info("Skipping virtual extension [{$this->getName()}] — it's part of [{$displayName}].");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_dir($this->getSourceDir())) {
|
||||
throw new ValidationException(
|
||||
"Extension source directory not found: {$this->getSourceDir()}",
|
||||
validation_module: "Extension {$this->getName()} source"
|
||||
);
|
||||
}
|
||||
|
||||
$env = $this->getSharedExtensionEnv();
|
||||
|
||||
$this->runStage([$this, 'phpizeForUnix'], ['env' => $env]);
|
||||
$this->runStage([$this, 'configureForUnix'], ['env' => $env]);
|
||||
$this->runStage([$this, 'makeForUnix'], ['env' => $env]);
|
||||
|
||||
// libtool's -release X gives $name-X.so as the real file
|
||||
$soFile = BUILD_MODULES_PATH . '/' . $this->getExtensionName()
|
||||
. (preg_match('/-release\s+(\S+)/', (string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), $m) ? "-{$m[1]}" : '')
|
||||
. '.so';
|
||||
// process *.so file
|
||||
$soFile = BUILD_MODULES_PATH . '/' . $this->getExtensionName() . '.so';
|
||||
if (!file_exists($soFile)) {
|
||||
throw new ValidationException("Extension {$this->getExtensionName()} build failed: {$soFile} not found", validation_module: "Extension {$this->getExtensionName()} build");
|
||||
}
|
||||
|
||||
@@ -36,13 +36,6 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -367,18 +367,15 @@ class PackageLoader
|
||||
|
||||
public static function getBeforeStageCallbacks(string $package_name, string $stage): iterable
|
||||
{
|
||||
// match condition; '*' is a wildcard that fires for every package's stage
|
||||
// match condition
|
||||
$installer = ApplicationContext::get(PackageInstaller::class);
|
||||
$stages = array_merge(
|
||||
self::$before_stages[$package_name][$stage] ?? [],
|
||||
$package_name === '*' ? [] : (self::$before_stages['*'][$stage] ?? []),
|
||||
);
|
||||
$stages = self::$before_stages[$package_name][$stage] ?? [];
|
||||
foreach ($stages as [$callback, $only_when_package_resolved, $conditionals]) {
|
||||
if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($conditionals as $class) {
|
||||
if (ApplicationContext::tryGet($class) === null) {
|
||||
if (!ApplicationContext::has($class)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
@@ -388,19 +385,16 @@ class PackageLoader
|
||||
|
||||
public static function getAfterStageCallbacks(string $package_name, string $stage): array
|
||||
{
|
||||
// match condition; '*' is a wildcard that fires for every package's stage
|
||||
// match condition
|
||||
$installer = ApplicationContext::get(PackageInstaller::class);
|
||||
$stages = array_merge(
|
||||
self::$after_stages[$package_name][$stage] ?? [],
|
||||
$package_name === '*' ? [] : (self::$after_stages['*'][$stage] ?? []),
|
||||
);
|
||||
$stages = self::$after_stages[$package_name][$stage] ?? [];
|
||||
$result = [];
|
||||
foreach ($stages as [$callback, $only_when_package_resolved, $conditionals]) {
|
||||
if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($conditionals as $class) {
|
||||
if (ApplicationContext::tryGet($class) === null) {
|
||||
if (!ApplicationContext::has($class)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
@@ -431,20 +425,6 @@ class PackageLoader
|
||||
{
|
||||
foreach (['BeforeStage' => self::$before_stages, 'AfterStage' => self::$after_stages] as $event_name => $ev_all) {
|
||||
foreach ($ev_all as $package_name => $stages) {
|
||||
// wildcard hooks fire for every package's stage; nothing to validate against
|
||||
if ($package_name === '*') {
|
||||
foreach ($stages as $stage_name => $before_events) {
|
||||
foreach ($before_events as [$event_callable, $only_when_package_resolved, $conditionals]) {
|
||||
if ($only_when_package_resolved !== null && !self::hasPackage($only_when_package_resolved)) {
|
||||
throw new RegistryException("{$event_name} event for wildcard [*] stage [{$stage_name}] has unknown only_when_package_resolved package [{$only_when_package_resolved}].");
|
||||
}
|
||||
if (!is_callable($event_callable)) {
|
||||
throw new RegistryException("{$event_name} event for wildcard [*] stage [{$stage_name}] has invalid callable.");
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// check package exists
|
||||
if (!self::hasPackage($package_name)) {
|
||||
throw new RegistryException(
|
||||
|
||||
@@ -89,19 +89,14 @@ class Registry
|
||||
self::$current_registry_name = $registry_name;
|
||||
|
||||
try {
|
||||
// resolve autoload manually — path-repo installs have no vendor/, FileSystem::fullpath would throw
|
||||
// Load composer autoload if specified (for external registries with their own dependencies)
|
||||
if (isset($data['autoload']) && is_string($data['autoload'])) {
|
||||
$base = dirname($registry_file);
|
||||
$autoload_path = FileSystem::isRelativePath($data['autoload'])
|
||||
? rtrim($base, '/') . DIRECTORY_SEPARATOR . $data['autoload']
|
||||
: $data['autoload'];
|
||||
$autoload_path = FileSystem::fullpath($data['autoload'], dirname($registry_file));
|
||||
if (file_exists($autoload_path)) {
|
||||
logger()->debug("Loading external autoload from: {$autoload_path}");
|
||||
require_once $autoload_path;
|
||||
} elseif (str_contains(rtrim(FileSystem::convertPath($base), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR)) {
|
||||
logger()->debug("Registry autoload not present, relying on consumer autoloader: {$autoload_path}");
|
||||
} else {
|
||||
throw new RegistryException("Path does not exist: {$autoload_path}");
|
||||
logger()->warning("Autoload file not found: {$autoload_path}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ 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;
|
||||
|
||||
@@ -150,17 +149,13 @@ class UnixAutoconfExecutor extends Executor
|
||||
*/
|
||||
private function getDefaultConfigureArgs(): array
|
||||
{
|
||||
$args = [
|
||||
return [
|
||||
'--disable-shared',
|
||||
'--enable-static',
|
||||
"--prefix={$this->package->getBuildRootPath()}",
|
||||
'--with-pic',
|
||||
'--enable-pic',
|
||||
];
|
||||
if ($host_triple = SystemTarget::getAutoconfHostTriple()) {
|
||||
$args[] = "--host={$host_triple}";
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -302,12 +302,9 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES "{$include}")
|
||||
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES "{$include}")
|
||||
CMAKE;
|
||||
// pin AR/RANLIB so cmake uses zig-ar/zig-ranlib instead of system /usr/bin/ranlib (zig archives need it)
|
||||
// Whoops, linux may need CMAKE_AR sometimes
|
||||
if (PHP_OS_FAMILY === 'Linux') {
|
||||
$ar = getenv('SPC_DEFAULT_AR') ?: getenv('AR') ?: 'ar';
|
||||
$ranlib = getenv('SPC_DEFAULT_RANLIB') ?: (getenv('RANLIB') ?: 'ranlib');
|
||||
$toolchain .= "\nSET(CMAKE_AR \"{$ar}\")";
|
||||
$toolchain .= "\nSET(CMAKE_RANLIB \"{$ranlib}\")";
|
||||
$toolchain .= "\nSET(CMAKE_AR \"ar\")";
|
||||
}
|
||||
FileSystem::writeFile(SOURCE_PATH . '/toolchain.cmake', $toolchain);
|
||||
return $created = realpath(SOURCE_PATH . '/toolchain.cmake');
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Runtime;
|
||||
|
||||
use StaticPHP\Toolchain\ZigToolchain;
|
||||
use StaticPHP\Util\System\LinuxUtil;
|
||||
|
||||
/**
|
||||
@@ -17,7 +16,7 @@ class SystemTarget
|
||||
*/
|
||||
public static function getLibc(): ?string
|
||||
{
|
||||
if ($target = self::target()) {
|
||||
if ($target = getenv('SPC_TARGET')) {
|
||||
if (str_contains($target, '-gnu')) {
|
||||
return 'glibc';
|
||||
}
|
||||
@@ -58,7 +57,7 @@ class SystemTarget
|
||||
public static function getLibcVersion(): ?string
|
||||
{
|
||||
if (PHP_OS_FAMILY === 'Linux') {
|
||||
$target = self::target();
|
||||
$target = (string) getenv('SPC_TARGET');
|
||||
if (str_contains($target, '-gnu.2.')) {
|
||||
return preg_match('/-gnu\.(2\.\d+)/', $target, $matches) ? $matches[1] : null;
|
||||
}
|
||||
@@ -76,7 +75,7 @@ class SystemTarget
|
||||
*/
|
||||
public static function getTargetOS(): string
|
||||
{
|
||||
$target = self::target();
|
||||
$target = (string) getenv('SPC_TARGET');
|
||||
return match (true) {
|
||||
str_contains($target, '-linux') => 'Linux',
|
||||
str_contains($target, '-macos') => 'Darwin',
|
||||
@@ -92,7 +91,7 @@ class SystemTarget
|
||||
*/
|
||||
public static function getTargetArch(): string
|
||||
{
|
||||
$target = self::target();
|
||||
$target = (string) getenv('SPC_TARGET');
|
||||
return match (true) {
|
||||
str_contains($target, 'x86_64') || str_contains($target, 'amd64') => 'x86_64',
|
||||
str_contains($target, 'aarch64') || str_contains($target, 'arm64') => 'aarch64',
|
||||
@@ -128,70 +127,4 @@ 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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ class ClangBrewToolchain extends ClangNativeToolchain
|
||||
GlobalEnvManager::putenv("SPC_DEFAULT_CC={$homebrew_prefix}/opt/llvm/bin/clang");
|
||||
GlobalEnvManager::putenv("SPC_DEFAULT_CXX={$homebrew_prefix}/opt/llvm/bin/clang++");
|
||||
GlobalEnvManager::putenv("SPC_DEFAULT_AR={$homebrew_prefix}/opt/llvm/bin/llvm-ar");
|
||||
GlobalEnvManager::putenv("SPC_DEFAULT_RANLIB={$homebrew_prefix}/opt/llvm/bin/llvm-ranlib");
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
||||
GlobalEnvManager::addPathIfNotExists("{$homebrew_prefix}/opt/llvm/bin");
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ class ClangNativeToolchain implements UnixToolchainInterface
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_CC=clang');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_CXX=clang++');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=ranlib');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ class GccNativeToolchain implements UnixToolchainInterface
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_CC=gcc');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_CXX=g++');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=ranlib');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld');
|
||||
}
|
||||
|
||||
|
||||
@@ -4,49 +4,46 @@ 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
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_CC=zig-cc');
|
||||
GlobalEnvManager::putenv('SPC_DEFAULT_CXX=zig-c++');
|
||||
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());
|
||||
|
||||
// Generate additional objects needed for zig toolchain
|
||||
$paths = ['/usr/lib/gcc', '/usr/local/lib/gcc'];
|
||||
$objects = ['crtbeginS.o', 'crtendS.o'];
|
||||
$found = [];
|
||||
|
||||
foreach ($objects as $obj) {
|
||||
$located = null;
|
||||
foreach ($paths as $base) {
|
||||
$output = shell_exec("find {$base} -name {$obj} 2>/dev/null | grep -v '/32/' | head -n 1");
|
||||
$line = trim((string) $output);
|
||||
if ($line !== '') {
|
||||
$located = $line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($located) {
|
||||
$found[] = $located;
|
||||
}
|
||||
}
|
||||
GlobalEnvManager::putenv('SPC_EXTRA_RUNTIME_OBJECTS=' . implode(' ', $found));
|
||||
}
|
||||
|
||||
public function afterInit(): void
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
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
|
||||
$cflags = getenv('SPC_DEFAULT_CFLAGS') ?: '';
|
||||
$cxxflags = getenv('SPC_DEFAULT_CXXFLAGS') ?: '';
|
||||
$extraCflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') ?: '';
|
||||
@@ -57,8 +54,7 @@ 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('SPC_COMPILER_RT_DIR=' . zig::path() . '/lib/' . SystemTarget::getCanonicalTriple());
|
||||
GlobalEnvManager::putenv('OBJCOPY=' . PKG_ROOT_PATH . '/llvm-tools/bin/llvm-objcopy');
|
||||
GlobalEnvManager::putenv('OBJCOPY=zig-objcopy');
|
||||
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
|
||||
if (!str_contains($extra_libs, '-lunwind')) {
|
||||
// Add unwind library if not already present
|
||||
@@ -74,8 +70,6 @@ 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
|
||||
@@ -111,53 +105,8 @@ 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 zig::path();
|
||||
return PKG_ROOT_PATH . '/zig';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,24 +123,6 @@ 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.
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace StaticPHP\Util;
|
||||
|
||||
use StaticPHP\DI\ApplicationContext;
|
||||
use Symfony\Component\Console\Helper\Helper;
|
||||
use Symfony\Component\Console\Helper\ProgressIndicator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
@@ -28,19 +27,14 @@ class InteractiveTerm
|
||||
}
|
||||
}
|
||||
|
||||
public static function success(string $message, bool $indent = false, ?float $start_time = null): void
|
||||
public static function success(string $message, bool $indent = false): void
|
||||
{
|
||||
$no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false;
|
||||
$output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput();
|
||||
if ($start_time !== null) {
|
||||
$message .= ' (' . Helper::formatTime(microtime(true) - $start_time) . ')';
|
||||
}
|
||||
if ($output->isVerbose()) {
|
||||
logger()->info(strip_ansi_colors($message));
|
||||
} else {
|
||||
// wipe the current indicator line so our persistent ✔ line doesn't get appended to a spinner
|
||||
$clear = (self::$indicator !== null && $output->isDecorated() && !$no_ansi) ? "\x0D\x1B[2K" : '';
|
||||
$output->writeln($clear . ($no_ansi ? 'strip_ansi_colors' : 'strval')(ConsoleColor::green(($indent ? ' ' : '') . '✔ ') . $message));
|
||||
$output->writeln(($no_ansi ? 'strip_ansi_colors' : 'strval')(ConsoleColor::green(($indent ? ' ' : '') . '✔ ') . $message));
|
||||
logger()->debug(strip_ansi_colors($message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,265 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StaticPHP\Util\Pgo;
|
||||
|
||||
use StaticPHP\Command\BaseCommand;
|
||||
use StaticPHP\DI\ApplicationContext;
|
||||
use StaticPHP\Exception\WrongUsageException;
|
||||
use StaticPHP\Util\FileSystem;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
final class PgoContext
|
||||
{
|
||||
public const string MODE_INSTRUMENT = 'instrument';
|
||||
|
||||
public const string MODE_CS_INSTRUMENT = 'cs-instrument';
|
||||
|
||||
public const string MODE_USE = 'use';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public const array TRAINABLE = [
|
||||
'cli' => 'build-cli',
|
||||
'micro' => 'build-micro',
|
||||
'cgi' => 'build-cgi',
|
||||
'fpm' => 'build-fpm',
|
||||
'embed' => 'build-embed',
|
||||
'frankenphp' => 'build-frankenphp',
|
||||
];
|
||||
|
||||
public const array SHUTDOWN_PATCHES = [
|
||||
'php-src' => 'spc_pgo_flush_php_main.patch',
|
||||
'frankenphp' => 'spc_pgo_flush_frankenphp.patch',
|
||||
];
|
||||
|
||||
/** @var list<string> */
|
||||
private array $trainableSapis = [];
|
||||
|
||||
public function __construct(
|
||||
public readonly string $mode,
|
||||
public readonly string $profileRoot,
|
||||
) {
|
||||
if (!in_array($mode, [self::MODE_INSTRUMENT, self::MODE_CS_INSTRUMENT, self::MODE_USE], true)) {
|
||||
throw new WrongUsageException("PgoContext: unknown mode '{$mode}'");
|
||||
}
|
||||
}
|
||||
|
||||
public static function registerOptions(BaseCommand $cmd): void
|
||||
{
|
||||
$cmd->addOption('pgi', null, null, 'PGO instrument pass: build with -fprofile-generate so the resulting binary writes .profraw on shutdown.');
|
||||
$cmd->addOption('cs-pgi', null, null, 'PGO context-sensitive instrument pass: -fprofile-use=<existing.profdata> + -fcs-profile-generate. Requires a prior --pgi/--pgo cycle.');
|
||||
$cmd->addOption('pgo', null, null, 'PGO use pass: merge the collected .profraw into .profdata, then rebuild with -fprofile-use.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $sapis
|
||||
* @param array<string, mixed> $build_options
|
||||
*/
|
||||
public static function tryFromInput(InputInterface $input, array $sapis, array &$build_options): ?self
|
||||
{
|
||||
$modes = array_filter(['pgi', 'cs-pgi', 'pgo'], fn ($m) => (bool) $input->getOption($m));
|
||||
if (count($modes) > 1) {
|
||||
throw new WrongUsageException('--pgi, --cs-pgi, and --pgo are mutually exclusive');
|
||||
}
|
||||
$picked = array_values($modes)[0] ?? null;
|
||||
if ($picked === null) {
|
||||
return null;
|
||||
}
|
||||
$mode = match ($picked) {
|
||||
'pgi' => self::MODE_INSTRUMENT,
|
||||
'cs-pgi' => self::MODE_CS_INSTRUMENT,
|
||||
'pgo' => self::MODE_USE,
|
||||
};
|
||||
$ctx = new self($mode, BUILD_ROOT_PATH . '/pgo-data');
|
||||
$ctx->setTrainableSapis($sapis);
|
||||
|
||||
match ($mode) {
|
||||
self::MODE_INSTRUMENT => $ctx->setupInstrument(),
|
||||
self::MODE_CS_INSTRUMENT => $ctx->setupCsInstrument(),
|
||||
self::MODE_USE => $ctx->mergeProfiles(),
|
||||
};
|
||||
|
||||
if ($ctx->isInstrument() || $ctx->isCsInstrument()) {
|
||||
$build_options['no-strip'] = true;
|
||||
}
|
||||
ApplicationContext::set(self::class, $ctx);
|
||||
return $ctx;
|
||||
}
|
||||
|
||||
public function isInstrument(): bool
|
||||
{
|
||||
return $this->mode === self::MODE_INSTRUMENT;
|
||||
}
|
||||
|
||||
public function isCsInstrument(): bool
|
||||
{
|
||||
return $this->mode === self::MODE_CS_INSTRUMENT;
|
||||
}
|
||||
|
||||
public function isUse(): bool
|
||||
{
|
||||
return $this->mode === self::MODE_USE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $sapis
|
||||
*/
|
||||
public function setTrainableSapis(array $sapis): void
|
||||
{
|
||||
$resolved = [];
|
||||
foreach ($sapis as $sapi) {
|
||||
$r = $this->resolveSapi($sapi);
|
||||
if (!in_array($r, $resolved, true)) {
|
||||
$resolved[] = $r;
|
||||
}
|
||||
}
|
||||
if ($resolved === []) {
|
||||
throw new WrongUsageException(
|
||||
'PGO: no trainable SAPI selected; supply one of ' . implode(', ', array_keys(self::TRAINABLE))
|
||||
);
|
||||
}
|
||||
$this->trainableSapis = $resolved;
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
public function trainableSapis(): array
|
||||
{
|
||||
return $this->trainableSapis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static-embed mode links libphp.a into frankenphp, sharing a single binary
|
||||
* and profdata. Shared-embed keeps them separate.
|
||||
*/
|
||||
public function resolveSapi(string $sapi): string
|
||||
{
|
||||
if ($sapi === 'embed' && getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') {
|
||||
return 'frankenphp';
|
||||
}
|
||||
return $sapi;
|
||||
}
|
||||
|
||||
public function rawDir(string $sapi): string
|
||||
{
|
||||
return $this->profileRoot . '/' . $sapi;
|
||||
}
|
||||
|
||||
public function csRawDir(string $sapi): string
|
||||
{
|
||||
return $this->profileRoot . '/cs-' . $sapi;
|
||||
}
|
||||
|
||||
public function profDataFile(string $sapi): string
|
||||
{
|
||||
return $this->profileRoot . '/' . $sapi . '.profdata';
|
||||
}
|
||||
|
||||
public function cflagsFor(string $sapi): string
|
||||
{
|
||||
$sapi = $this->resolveSapi($sapi);
|
||||
if ($this->mode === self::MODE_USE && !is_file($this->profDataFile($sapi))) {
|
||||
return '';
|
||||
}
|
||||
return match ($this->mode) {
|
||||
self::MODE_INSTRUMENT => '-fprofile-generate=' . $this->rawDir($sapi)
|
||||
. ' -fprofile-update=atomic',
|
||||
self::MODE_CS_INSTRUMENT => '-fprofile-use=' . $this->profDataFile($sapi)
|
||||
. ' -fcs-profile-generate=' . $this->csRawDir($sapi)
|
||||
. ' -fprofile-update=atomic'
|
||||
. ' -Wno-error=profile-instr-unprofiled'
|
||||
. ' -Wno-error=profile-instr-out-of-date'
|
||||
. ' -Wno-backend-plugin',
|
||||
self::MODE_USE => '-fprofile-use=' . $this->profDataFile($sapi)
|
||||
. ' -Wno-error=profile-instr-unprofiled'
|
||||
. ' -Wno-error=profile-instr-out-of-date'
|
||||
. ' -Wno-backend-plugin',
|
||||
default => throw new WrongUsageException("PgoContext: unreachable mode '{$this->mode}'"),
|
||||
};
|
||||
}
|
||||
|
||||
public function ldflagsFor(string $sapi): string
|
||||
{
|
||||
$resolved = $this->resolveSapi($sapi);
|
||||
$flags = $this->cflagsFor($sapi);
|
||||
$patterns = ['/\s*-Wno-error=\S+/', '/\s*-Wno-backend-plugin/'];
|
||||
if ($resolved === 'frankenphp') {
|
||||
$patterns[] = '/\s*-fprofile-use=\S+/';
|
||||
$patterns[] = '/\s*-fcs-profile-generate=\S+/';
|
||||
}
|
||||
return trim((string) preg_replace($patterns, '', $flags));
|
||||
}
|
||||
|
||||
public function applyEnvFor(string $sapi): void
|
||||
{
|
||||
self::overwritePgoFlags('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS', $this->cflagsFor($sapi));
|
||||
self::overwritePgoFlags('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM', $this->ldflagsFor($sapi));
|
||||
}
|
||||
|
||||
public function setupInstrument(): void
|
||||
{
|
||||
FileSystem::removeDir($this->profileRoot);
|
||||
FileSystem::createDir($this->profileRoot);
|
||||
foreach ($this->trainableSapis as $sapi) {
|
||||
FileSystem::createDir($this->rawDir($sapi));
|
||||
}
|
||||
}
|
||||
|
||||
public function setupCsInstrument(): void
|
||||
{
|
||||
foreach ($this->trainableSapis as $sapi) {
|
||||
if (!is_file($this->profDataFile($sapi))) {
|
||||
throw new WrongUsageException(
|
||||
"PGO --phase=cs-instrument: missing {$sapi}.profdata; run --phase=instrument and --phase=use first"
|
||||
);
|
||||
}
|
||||
FileSystem::createDir($this->csRawDir($sapi));
|
||||
}
|
||||
}
|
||||
|
||||
public function mergeProfiles(): void
|
||||
{
|
||||
foreach ($this->trainableSapis as $sapi) {
|
||||
$this->mergeSapi($sapi);
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeSapi(string $sapi): void
|
||||
{
|
||||
$raws = glob($this->rawDir($sapi) . '/*.profraw') ?: [];
|
||||
$csRaws = glob($this->csRawDir($sapi) . '/*.profraw') ?: [];
|
||||
if ($raws === [] && $csRaws === []) {
|
||||
if ($sapi === 'frankenphp') {
|
||||
logger()->warning(
|
||||
'PGO --phase=use: no .profraw for frankenphp (cgo-glue PGO will be skipped); ' .
|
||||
'run --phase=instrument, exercise frankenphp longer, then re-run --phase=use'
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new WrongUsageException(
|
||||
"PGO --phase=use: no .profraw for {$sapi}; run --phase=instrument, exercise the binary, then re-run --phase=use"
|
||||
);
|
||||
}
|
||||
$out = $this->profDataFile($sapi);
|
||||
$inputs = array_merge($raws, $csRaws);
|
||||
$argv = implode(' ', array_map('escapeshellarg', $inputs));
|
||||
$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}");
|
||||
}
|
||||
logger()->info("PGO merged {$sapi}: " . filesize($out) . ' bytes');
|
||||
}
|
||||
|
||||
private static function overwritePgoFlags(string $var, string $append): void
|
||||
{
|
||||
$cur = (string) getenv($var);
|
||||
$cur = preg_replace('/\s*-f(cs-)?profile-(generate|use)=\S+/', '', $cur) ?? $cur;
|
||||
$cur = preg_replace('/\s*-fprofile-update=atomic/', '', $cur) ?? $cur;
|
||||
$cur = preg_replace('/\s*-Wno-error=profile-instr-\S+/', '', $cur) ?? $cur;
|
||||
$cur = preg_replace('/\s*-Wno-backend-plugin/', '', $cur) ?? $cur;
|
||||
f_putenv($var . '=' . trim(trim($cur) . ' ' . $append));
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ class PkgConfigUtil
|
||||
public static function getCflags(string $pkg_config_str): string
|
||||
{
|
||||
// get other things
|
||||
$result = self::execWithResult("pkg-config --static --cflags {$pkg_config_str}");
|
||||
$result = self::execWithResult("pkg-config --static --cflags-only-other {$pkg_config_str}");
|
||||
return trim($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,9 @@ declare(strict_types=1);
|
||||
namespace StaticPHP\Util;
|
||||
|
||||
use StaticPHP\Config\PackageConfig;
|
||||
use StaticPHP\DI\ApplicationContext;
|
||||
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
|
||||
@@ -35,82 +32,123 @@ class SPCConfigUtil
|
||||
$this->absolute_libs = $options['absolute_libs'] ?? false;
|
||||
}
|
||||
|
||||
public function config(array $packages = []): array
|
||||
public function config(array $packages = [], bool $include_suggests = false): array
|
||||
{
|
||||
// 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 have php, make php as all extension's dependency
|
||||
if (!$this->no_php) {
|
||||
foreach ($installer->getResolvedPackages(PhpExtensionPackage::class) as $ext) {
|
||||
if ($ext->isBuildStatic()) {
|
||||
$php_extras[] = $ext->getName();
|
||||
}
|
||||
$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(TargetPackage::class) as $target) {
|
||||
if ($target->isVirtualTarget()) {
|
||||
$php_extras[] = $target->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";
|
||||
}
|
||||
}
|
||||
|
||||
$sorted = [];
|
||||
$visited = [];
|
||||
foreach ($packages as $pkg) {
|
||||
self::visitConfigDeps(
|
||||
is_string($pkg) ? $pkg : $pkg->getName(),
|
||||
$resolved_set,
|
||||
$php_extras,
|
||||
$visited,
|
||||
$sorted,
|
||||
);
|
||||
$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);
|
||||
}
|
||||
return $this->configWithResolvedPackages($sorted);
|
||||
|
||||
return [
|
||||
'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags),
|
||||
'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags),
|
||||
'libs' => clean_spaces($allLibs),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* [Helper function]
|
||||
* Get configuration for a specific extension(s) dependencies.
|
||||
*
|
||||
* Uses the installer's resolved package set as the source of truth — only libraries that
|
||||
* are actually enabled in this build appear in the result. The resolved set already
|
||||
* reflects the user's `--with-suggests` choice.
|
||||
*
|
||||
* @param array|PhpExtensionPackage $extension_packages Extension instance or list
|
||||
* @return array{
|
||||
* cflags: string,
|
||||
* cxxflags: string,
|
||||
* ldflags: string,
|
||||
* libs: string
|
||||
* }
|
||||
*/
|
||||
public function getExtensionConfig(array|PhpExtensionPackage $extension_packages): array
|
||||
public function getExtensionConfig(array|PhpExtensionPackage $extension_packages, bool $include_suggests = false): array
|
||||
{
|
||||
if (!is_array($extension_packages)) {
|
||||
$extension_packages = [$extension_packages];
|
||||
}
|
||||
$names = array_map(fn ($y) => $y->getName(), $extension_packages);
|
||||
return $this->configWithResolvedPackages($this->collectEnabledLinkPackages($names));
|
||||
return $this->config(
|
||||
packages: array_map(fn ($y) => $y->getName(), $extension_packages),
|
||||
include_suggests: $include_suggests,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Helper function]
|
||||
* Get configuration for a specific library(s) dependencies.
|
||||
*
|
||||
* Like {@see getExtensionConfig()}, draws from the resolved package set so we never
|
||||
* link against a library that wasn't built.
|
||||
*
|
||||
* @param array|LibraryPackage $lib Library instance or list
|
||||
* @param array|LibraryPackage $lib Library instance or list
|
||||
* @param bool $include_suggests Whether to include suggested libraries
|
||||
* @return array{
|
||||
* cflags: string,
|
||||
* cxxflags: string,
|
||||
* ldflags: string,
|
||||
* libs: string
|
||||
* }
|
||||
*/
|
||||
public function getLibraryConfig(array|LibraryPackage $lib): array
|
||||
public function getLibraryConfig(array|LibraryPackage $lib, bool $include_suggests = false): array
|
||||
{
|
||||
if (!is_array($lib)) {
|
||||
$lib = [$lib];
|
||||
@@ -119,37 +157,39 @@ class SPCConfigUtil
|
||||
$this->no_php = true;
|
||||
$save_libs_only_deps = $this->libs_only_deps;
|
||||
$this->libs_only_deps = true;
|
||||
$names = array_map(fn ($y) => $y->getName(), $lib);
|
||||
$ret = $this->configWithResolvedPackages($this->collectEnabledLinkPackages($names));
|
||||
$ret = $this->config(
|
||||
packages: array_map(fn ($y) => $y->getName(), $lib),
|
||||
include_suggests: $include_suggests,
|
||||
);
|
||||
$this->no_php = $save_no_php;
|
||||
$this->libs_only_deps = $save_libs_only_deps;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get build configuration for a package's sub-dependencies within a resolved set.
|
||||
* Get build configuration for a package and its sub-dependencies within a resolved set.
|
||||
*
|
||||
* Walks both depends and suggests edges — the resolved set is the filter, so anything
|
||||
* reachable but unbuilt is naturally excluded. No `include_suggests` knob is needed.
|
||||
* This is useful when you need to statically link something against a specific
|
||||
* library and all its transitive dependencies. It properly handles optional
|
||||
* dependencies by only including those that were actually resolved.
|
||||
*
|
||||
* @param string $package_name The package to get config for
|
||||
* @param string[] $resolved_packages The full resolved package list
|
||||
* @param bool $include_suggests Whether to include resolved suggests
|
||||
* @return array{
|
||||
* cflags: string,
|
||||
* cxxflags: string,
|
||||
* ldflags: string,
|
||||
* libs: string
|
||||
* }
|
||||
*/
|
||||
public function getPackageDepsConfig(string $package_name, array $resolved_packages): array
|
||||
public function getPackageDepsConfig(string $package_name, array $resolved_packages, bool $include_suggests = false): array
|
||||
{
|
||||
// Get sub-dependencies within the resolved set
|
||||
$sub_deps = DependencyResolver::getSubDependencies($package_name, $resolved_packages, include_suggests: true);
|
||||
$sub_deps = DependencyResolver::getSubDependencies($package_name, $resolved_packages, $include_suggests);
|
||||
|
||||
if (empty($sub_deps)) {
|
||||
return [
|
||||
'cflags' => '',
|
||||
'cxxflags' => '',
|
||||
'ldflags' => '',
|
||||
'libs' => '',
|
||||
];
|
||||
@@ -170,12 +210,11 @@ class SPCConfigUtil
|
||||
}
|
||||
|
||||
/**
|
||||
* Build cflags/cxxflags/ldflags/libs for an already-resolved package set (skip dependency resolution).
|
||||
* Get configuration using already-resolved packages (skip dependency resolution).
|
||||
*
|
||||
* @param string[] $resolved_packages Resolved package names in build order
|
||||
* @param string[] $resolved_packages Already resolved package names in build order
|
||||
* @return array{
|
||||
* cflags: string,
|
||||
* cxxflags: string,
|
||||
* ldflags: string,
|
||||
* libs: string
|
||||
* }
|
||||
@@ -183,14 +222,9 @@ class SPCConfigUtil
|
||||
public function configWithResolvedPackages(array $resolved_packages): array
|
||||
{
|
||||
$ldflags = $this->getLdflagsString();
|
||||
$includes = $this->getIncludesString($resolved_packages);
|
||||
$cflags = $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}";
|
||||
@@ -226,25 +260,15 @@ class SPCConfigUtil
|
||||
$libs = BUILD_LIB_PATH . '/libmimalloc.a ' . str_replace([BUILD_LIB_PATH . '/libmimalloc.a', '-lmimalloc'], ['', ''], $libs);
|
||||
}
|
||||
return [
|
||||
'cflags' => $cflags,
|
||||
'cxxflags' => $cxxflags,
|
||||
'ldflags' => $ldflags,
|
||||
'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";
|
||||
}
|
||||
$libs = "-lphp {$libs} -lc";
|
||||
}
|
||||
|
||||
$allLibs = getenv('LIBS') . ' ' . $libs;
|
||||
@@ -255,9 +279,8 @@ class SPCConfigUtil
|
||||
}
|
||||
|
||||
return [
|
||||
'cflags' => $cflags,
|
||||
'cxxflags' => $cxxflags,
|
||||
'ldflags' => $ldflags,
|
||||
'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags),
|
||||
'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags),
|
||||
'libs' => clean_spaces($allLibs),
|
||||
];
|
||||
}
|
||||
@@ -276,50 +299,6 @@ 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.
|
||||
*
|
||||
* @param string[] $package_names Input package names
|
||||
* @return string[] Resolved packages to link against
|
||||
*/
|
||||
private function collectEnabledLinkPackages(array $package_names): array
|
||||
{
|
||||
$resolved = array_keys(ApplicationContext::get(PackageInstaller::class)->getResolvedPackages());
|
||||
$out = [];
|
||||
foreach ($package_names as $name) {
|
||||
$sub = DependencyResolver::getSubDependencies($name, $resolved, include_suggests: true);
|
||||
$out = [...$out, ...$sub, $name];
|
||||
}
|
||||
return array_values(array_unique($out));
|
||||
}
|
||||
|
||||
private function hasCpp(array $packages): bool
|
||||
{
|
||||
foreach ($packages as $package) {
|
||||
|
||||
@@ -168,9 +168,6 @@ 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',
|
||||
@@ -196,12 +193,6 @@ 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');
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ 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);
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
--- a/frankenphp.c
|
||||
+++ b/frankenphp.c
|
||||
@@ -1254,6 +1254,12 @@
|
||||
|
||||
go_frankenphp_shutdown_main_thread();
|
||||
|
||||
+ /* spc-pgo: explicit profile flush so the cgo-instrumented frankenphp
|
||||
+ * still writes .profraw on Go-runtime exit (which bypasses libc atexit).
|
||||
+ * Weak symbol → no-op in non-PGO builds. */
|
||||
+ { extern int __llvm_profile_write_file(void) __attribute__((weak));
|
||||
+ if (__llvm_profile_write_file) __llvm_profile_write_file(); }
|
||||
+
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
--- a/main/main.c
|
||||
+++ b/main/main.c
|
||||
@@ -2563,6 +2563,9 @@
|
||||
#endif
|
||||
|
||||
zend_observer_shutdown();
|
||||
+
|
||||
+ { extern int __llvm_profile_write_file(void) __attribute__((weak));
|
||||
+ if (__llvm_profile_write_file) __llvm_profile_write_file(); }
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
||||
BUILDROOT_INC="${BUILD_INCLUDE_PATH:-$SCRIPT_DIR/../../../buildroot/include}"
|
||||
BUILDROOT_ABS="$(realpath "$BUILDROOT_INC" 2>/dev/null || true)"
|
||||
BUILDROOT_ABS="${BUILD_ROOT_PATH:-$(realpath "$SCRIPT_DIR/../../../buildroot/include" 2>/dev/null || true)}"
|
||||
PARSED_ARGS=()
|
||||
|
||||
is_buildroot_inc() {
|
||||
[[ -n "$BUILDROOT_ABS" && "$1" == "$BUILDROOT_ABS" ]]
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-isystem)
|
||||
@@ -16,37 +11,27 @@ while [[ $# -gt 0 ]]; do
|
||||
ARG="$1"
|
||||
shift
|
||||
ARG_ABS="$(realpath "$ARG" 2>/dev/null || true)"
|
||||
is_buildroot_inc "$ARG_ABS" && PARSED_ARGS+=("-I$ARG") || PARSED_ARGS+=("-isystem" "$ARG")
|
||||
[[ "$ARG_ABS" == "$BUILDROOT_ABS" ]] && PARSED_ARGS+=("-I$ARG") || PARSED_ARGS+=("-isystem" "$ARG")
|
||||
;;
|
||||
-isystem*)
|
||||
ARG="${1#-isystem}"
|
||||
shift
|
||||
ARG_ABS="$(realpath "$ARG" 2>/dev/null || true)"
|
||||
is_buildroot_inc "$ARG_ABS" && PARSED_ARGS+=("-I$ARG") || PARSED_ARGS+=("-isystem$ARG")
|
||||
[[ "$ARG_ABS" == "$BUILDROOT_ABS" ]] && PARSED_ARGS+=("-I$ARG") || PARSED_ARGS+=("-isystem$ARG")
|
||||
;;
|
||||
-march=*|-mcpu=*)
|
||||
OPT_NAME="${1%%=*}"
|
||||
OPT_VALUE="${1#*=}"
|
||||
# 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]}"
|
||||
# Skip armv8- flags entirely as Zig doesn't support them
|
||||
if [[ "$OPT_VALUE" == armv8-* ]]; then
|
||||
shift
|
||||
continue
|
||||
fi
|
||||
# zig uses snake_case in CPU/feature names (x86-64 -> x86_64).
|
||||
# replace -march=x86-64 with -march=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
|
||||
;;
|
||||
*)
|
||||
PARSED_ARGS+=("$1")
|
||||
shift
|
||||
@@ -54,29 +39,6 @@ while [[ $# -gt 0 ]]; do
|
||||
esac
|
||||
done
|
||||
|
||||
IS_LINK=1
|
||||
NEED_PROFILE_RT=0 # https://codeberg.org/ziglang/zig/issues/32066
|
||||
NEED_CRT=0 # https://codeberg.org/ziglang/zig/issues/32064
|
||||
for _a in "${PARSED_ARGS[@]}"; do
|
||||
case "$_a" in
|
||||
-c|-S|-E|-M|-MM) IS_LINK=0 ;;
|
||||
-fprofile-generate*|-fprofile-instr-generate*|-fcs-profile-generate*) NEED_PROFILE_RT=1 ;;
|
||||
-shared) NEED_CRT=1 ;;
|
||||
esac
|
||||
done
|
||||
[[ "$SPC_COMPILER_EXTRA" == *-fprofile-generate* || "$SPC_COMPILER_EXTRA" == *-fcs-profile-generate* ]] && NEED_PROFILE_RT=1
|
||||
|
||||
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 && -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=""
|
||||
|
||||
if [[ "$SPC_TARGET" =~ \.[0-9]+\.[0-9]+ ]]; then
|
||||
|
||||
Reference in New Issue
Block a user