Compare commits

...

4 Commits

Author SHA1 Message Date
henderkes
433043c0c8 fix macOS 2026-05-19 23:22:52 +07:00
henderkes
b38c7b274f install runtime rt after zig init 2026-05-19 22:55:01 +07:00
henderkes
efa7946c14 build in SOURCE_PATH 2026-05-19 20:44:47 +07:00
henderkes
7bb4a09a3c turn llvm-tools into a doctor check. llvm-runtime is a target now 2026-05-19 20:02:13 +07:00
16 changed files with 516 additions and 228 deletions

View File

@@ -0,0 +1,6 @@
llvm-compiler-rt:
type: target
artifact:
binary: custom
depends:
- zig

View File

@@ -1,4 +1,6 @@
clang-runtime-bits:
llvm-tools:
type: target
artifact:
binary: custom
depends:
- zig

View File

@@ -1,163 +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\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
use StaticPHP\Exception\DownloaderException;
class clang_runtime_bits
{
#[CustomBinary('clang-runtime-bits', [
'linux-x86_64',
'linux-aarch64',
])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
{
$llvmVersion = $this->detectZigLlvmVersion()
?? throw new DownloaderException('Could not detect bundled clang version from zig cc --version; ensure zig is installed');
$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, retries: $downloader->getRetry());
return DownloadResult::archive($tarball, ['url' => $url, 'version' => $llvmVersion], extract: '{pkg_root_path}/clang-runtime-bits', verified: false, version: $llvmVersion);
}
#[CustomBinaryCheckUpdate('clang-runtime-bits', [
'linux-x86_64',
'linux-aarch64',
])]
public function checkUpdateBinary(?string $old_version, ArtifactDownloader $downloader): CheckUpdateResult
{
$llvmVersion = $this->detectZigLlvmVersion()
?? throw new DownloaderException('Could not detect bundled clang version from zig cc --version; ensure zig is installed');
return new CheckUpdateResult(
old: $old_version,
new: $llvmVersion,
needUpdate: $old_version === null || $llvmVersion !== $old_version,
);
}
#[AfterBinaryExtract('clang-runtime-bits', [
'linux-x86_64',
'linux-aarch64',
])]
public function postExtract(string $target_path): void
{
$zig = PKG_ROOT_PATH . '/zig/zig';
$libDir = PKG_ROOT_PATH . '/zig/lib';
$profileLib = "{$libDir}/libclang_rt.profile.a";
$crtBegin = "{$libDir}/clang_rt.crtbegin.o";
$crtEnd = "{$libDir}/clang_rt.crtend.o";
if (file_exists($profileLib) && file_exists($crtBegin) && file_exists($crtEnd)) {
return;
}
$llvmVersion = $this->detectZigLlvmVersion();
if ($llvmVersion === null) {
logger()->warning('[clang-runtime-bits] could not detect bundled clang version; skipping runtime bit build (PGO + shared libs without __dso_handle will fail to link)');
return;
}
logger()->info("Building clang runtime bits for LLVM {$llvmVersion} (zig's bundled clang)");
f_mkdir($libDir, recursive: true);
if (!file_exists($profileLib)) {
$this->buildProfileRuntime($zig, $target_path, $profileLib);
}
if (!file_exists($crtBegin) || !file_exists($crtEnd)) {
$this->buildCrtObjects($zig, $target_path, $crtBegin, $crtEnd);
}
}
private function buildProfileRuntime(string $zig, string $srcRoot, string $libPath): void
{
$profileSrc = "{$srcRoot}/lib/profile";
$profileInc = "{$srcRoot}/include";
if (!is_dir($profileSrc)) {
logger()->warning("[clang-runtime-bits] profile src dir missing at {$profileSrc} — PGO will not work");
return;
}
$sources = array_merge(
glob("{$profileSrc}/*.c") ?: [],
glob("{$profileSrc}/*.cpp") ?: []
);
// Keep Linux-only compilation units; the others bring in OS-specific headers
// we can't satisfy without their respective SDKs.
$skip = ['/PlatformAIX', '/PlatformDarwin', '/PlatformFuchsia', '/PlatformOther', '/PlatformWindows', '/WindowsMMap'];
$sources = array_filter($sources, function ($f) use ($skip) {
foreach ($skip as $s) {
if (str_contains($f, $s)) {
return false;
}
}
return true;
});
$objDir = "{$srcRoot}/obj-profile";
f_mkdir($objDir, recursive: true);
$cflags = '-c -O2 -fPIC -fvisibility=hidden ' .
'-I' . escapeshellarg($profileInc) . ' ' .
'-DCOMPILER_RT_HAS_ATOMICS=1 -DCOMPILER_RT_HAS_FCNTL_LCK=1 -DCOMPILER_RT_HAS_UNAME=1';
$objs = [];
foreach ($sources as $src) {
$obj = $objDir . '/' . pathinfo($src, PATHINFO_FILENAME) . '.o';
$cmd = escapeshellarg($zig) . ' cc ' . $cflags . ' -o ' . escapeshellarg($obj) . ' ' . escapeshellarg($src) . ' 2>&1';
if (!$this->runZigCmd($cmd, $obj, "failed to compile {$src}")) {
return;
}
$objs[] = $obj;
}
$arCmd = escapeshellarg($zig) . ' ar rcs ' . escapeshellarg($libPath) . ' ' . implode(' ', array_map('escapeshellarg', $objs)) . ' 2>&1';
if (!$this->runZigCmd($arCmd, $libPath, 'zig ar failed')) {
return;
}
logger()->info('[clang-runtime-bits] libclang_rt.profile.a installed (' . filesize($libPath) . ' bytes)');
}
private function buildCrtObjects(string $zig, string $srcRoot, string $crtBegin, string $crtEnd): void
{
$beginSrc = "{$srcRoot}/lib/builtins/crtbegin.c";
$endSrc = "{$srcRoot}/lib/builtins/crtend.c";
if (!is_file($beginSrc) || !is_file($endSrc)) {
logger()->error("[clang-runtime-bits] crtbegin/crtend source missing under {$srcRoot}/lib/builtins — shared libs will lack __dso_handle");
return;
}
$cflags = '-c -O2 -fPIC -fvisibility=hidden -DCRT_HAS_INITFINI_ARRAY';
foreach ([[$beginSrc, $crtBegin], [$endSrc, $crtEnd]] as [$src, $dst]) {
$cmd = escapeshellarg($zig) . ' cc ' . $cflags . ' -o ' . escapeshellarg($dst) . ' ' . escapeshellarg($src) . ' 2>&1';
if (!$this->runZigCmd($cmd, $dst, "failed to compile {$src}")) {
return;
}
}
logger()->info('[clang-runtime-bits] clang_rt.crtbegin.o + clang_rt.crtend.o installed (' . filesize($crtBegin) . ' + ' . filesize($crtEnd) . ' bytes)');
}
private function runZigCmd(string $cmd, string $dst, string $errPrefix): bool
{
exec($cmd, $out, $rc);
if ($rc !== 0 || !is_file($dst)) {
logger()->warning("[clang-runtime-bits] {$errPrefix}: " . implode("\n", $out));
return false;
}
return true;
}
private function detectZigLlvmVersion(): ?string
{
$zig = PKG_ROOT_PATH . '/zig/zig';
if (!is_file($zig)) {
return null;
}
$verLine = trim((string) shell_exec(escapeshellarg($zig) . ' cc --version 2>/dev/null'));
if (!preg_match('/clang version (\d+\.\d+\.\d+)/', $verLine, $m)) {
return null;
}
return $m[1];
}
}

View File

@@ -0,0 +1,141 @@
<?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\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}.
*/
class llvm_compiler_rt
{
#[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, 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 = PKG_ROOT_PATH . '/zig/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);
}
}
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");
}
private function detectZigLlvmVersion(): ?string
{
[$rc, $out] = shell()->execWithResult('zig 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));
}
}
}

View File

@@ -0,0 +1,147 @@
<?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\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
{
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, 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=' . PKG_ROOT_PATH . '/zig/zig-cc',
'-DCMAKE_CXX_COMPILER=' . PKG_ROOT_PATH . '/zig/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
{
$zig = PKG_ROOT_PATH . '/zig/zig';
if (!is_file($zig)) {
return null;
}
[$rc, $out] = shell()->execWithResult(escapeshellarg($zig) . ' cc --version', false);
if ($rc !== 0) {
return null;
}
return preg_match('/clang version (\d+\.\d+\.\d+)/', implode("\n", $out), $m) ? $m[1] : null;
}
}

View File

@@ -110,26 +110,24 @@ class zig
break;
}
}
if ($all_exist) {
return;
if (!$all_exist) {
$script_path = ROOT_DIR . '/src/globals/scripts/zig-cc.sh';
$script_content = file_get_contents($script_path);
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);
}
$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);
}
}

View File

@@ -317,7 +317,10 @@ class ArtifactDownloader
if (!is_dir(DOWNLOAD_PATH)) {
FileSystem::createDir(DOWNLOAD_PATH);
}
logger()->info('Downloading' . implode(', ', array_map(fn ($x) => " '{$x->getName()}'", $this->artifacts)) . " with concurrency {$this->parallel} ...");
$pending = array_values(array_filter($this->artifacts, fn ($a) => $this->generateQueue($a) !== []));
if ($pending !== []) {
logger()->info('Downloading' . implode(', ', array_map(fn ($x) => " '{$x->getName()}'", $pending)) . " with concurrency {$this->parallel} ...");
}
// Download artifacts parallelly
if ($this->parallel > 1) {
$this->downloadWithConcurrency();

View File

@@ -10,6 +10,7 @@ use StaticPHP\DI\ApplicationContext;
use StaticPHP\Registry\ArtifactLoader;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Util\DependencyResolver;
use StaticPHP\Util\GlobalEnvManager;
use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
@@ -34,6 +35,7 @@ class ExtractCommand extends BaseCommand
public function handle(): int
{
GlobalEnvManager::afterInit();
$cache = ApplicationContext::get(ArtifactCache::class);
$extractor = new ArtifactExtractor($cache);
$force_source = (bool) $this->getOption('source-only');

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Doctor\Item;
use Package\Artifact\llvm_compiler_rt;
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 = PKG_ROOT_PATH . '/zig/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 = PKG_ROOT_PATH . '/zig/lib/' . SystemTarget::getCanonicalTriple();
return new llvm_compiler_rt()->isBuilt($libDir);
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Doctor\Item;
use Package\Artifact\llvm_tools;
use StaticPHP\Attribute\Doctor\CheckItem;
use StaticPHP\Attribute\Doctor\FixItem;
use StaticPHP\Attribute\Doctor\OptionalCheck;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Doctor\CheckResult;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Toolchain\Interface\ToolchainInterface;
use StaticPHP\Toolchain\ZigToolchain;
#[OptionalCheck([self::class, 'optionalCheck'])]
class LlvmToolsCheck
{
public static function optionalCheck(): bool
{
return ApplicationContext::get(ToolchainInterface::class) instanceof ZigToolchain;
}
/** @noinspection PhpUnused */
#[CheckItem('if llvm-tools (objcopy/strip/profdata) are built', level: 798)]
public function checkLlvmTools(): CheckResult
{
$binDir = PKG_ROOT_PATH . '/llvm-tools/bin';
if (new llvm_tools()->allBuilt($binDir)) {
return CheckResult::ok($binDir);
}
return CheckResult::fail('llvm-tools are not built', 'build-llvm-tools');
}
#[FixItem('build-llvm-tools')]
public function fixLlvmTools(): bool
{
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('llvm-tools');
$installer->run(true);
new llvm_tools()->buildForHost();
return new llvm_tools()->allBuilt(PKG_ROOT_PATH . '/llvm-tools/bin');
}
}

View File

@@ -39,34 +39,4 @@ class ZigCheck
$installer->run(true);
return $installer->isPackageInstalled('zig');
}
/** @noinspection PhpUnused */
#[CheckItem('if clang runtime bits are built', limit_os: 'Linux', level: 799)]
public function checkClangRuntimeBits(): ?CheckResult
{
// Skip if zig is not installed yet (zig check runs at level 800)
if (!new PackageInstaller()->addInstallPackage('zig')->isPackageInstalled('zig')) {
return null;
}
$libDir = PKG_ROOT_PATH . '/zig/lib';
if (file_exists("{$libDir}/libclang_rt.profile.a")
&& file_exists("{$libDir}/clang_rt.crtbegin.o")
&& file_exists("{$libDir}/clang_rt.crtend.o")
) {
return CheckResult::ok("{$libDir}/libclang_rt.profile.a");
}
return CheckResult::fail('clang runtime bits are not built', 'build-clang-runtime-bits');
}
#[FixItem('build-clang-runtime-bits')]
public function fixClangRuntimeBits(): bool
{
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('clang-runtime-bits');
$installer->run(true);
$libDir = PKG_ROOT_PATH . '/zig/lib';
return file_exists("{$libDir}/libclang_rt.profile.a")
&& file_exists("{$libDir}/clang_rt.crtbegin.o")
&& file_exists("{$libDir}/clang_rt.crtend.o");
}
}

View File

@@ -11,11 +11,12 @@ 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;
use StaticPHP\Util\System\LinuxUtil;
use StaticPHP\Util\System\UnixUtil;
class PackageBuilder
{
@@ -179,7 +180,7 @@ class PackageBuilder
if (SystemTarget::getTargetOS() === 'Darwin') {
shell()->exec("dsymutil -f {$binary_path} -o {$debug_file}");
} elseif (SystemTarget::getTargetOS() === 'Linux') {
$objcopy = LinuxUtil::findCommand('llvm-objcopy') ?: 'objcopy';
$objcopy = getenv('OBJCOPY') ?: 'objcopy';
if ($eu_strip = LinuxUtil::findCommand('eu-strip')) {
shell()
->exec("{$eu_strip} -f {$debug_file} {$binary_path}")
@@ -201,7 +202,9 @@ class PackageBuilder
*/
public function stripBinary(string $binary_path): void
{
$strip = UnixUtil::findCommand('llvm-strip') ?: 'strip';
$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}",

View File

@@ -128,6 +128,31 @@ 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 = (string) getenv('SPC_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).

View File

@@ -4,12 +4,21 @@ declare(strict_types=1);
namespace StaticPHP\Toolchain;
use Package\Artifact\llvm_compiler_rt;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Package\PackageBuilder;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Toolchain\Interface\UnixToolchainInterface;
use StaticPHP\Util\GlobalEnvManager;
use StaticPHP\Util\InteractiveTerm;
use StaticPHP\Util\System\LinuxUtil;
use ZM\Logger\ConsoleColor;
class ZigToolchain implements UnixToolchainInterface
{
private static bool $afterInitDone = false;
public function initEnv(): void
{
// Set environment variables for zig toolchain
@@ -18,11 +27,15 @@ class ZigToolchain implements UnixToolchainInterface
GlobalEnvManager::putenv('SPC_DEFAULT_AR=zig-ar');
GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=zig-ranlib');
GlobalEnvManager::putenv('SPC_DEFAULT_LD=zig-ld.lld');
GlobalEnvManager::addPathIfNotExists($this->getPath());
}
public function afterInit(): void
{
GlobalEnvManager::addPathIfNotExists($this->getPath());
if (self::$afterInitDone) {
return;
}
self::$afterInitDone = true;
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') ?: '';
@@ -34,7 +47,8 @@ class ZigToolchain implements UnixToolchainInterface
GlobalEnvManager::putenv("SPC_DEFAULT_CXXFLAGS={$cxxflags}");
GlobalEnvManager::putenv("SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS={$extraCflags}");
GlobalEnvManager::putenv('RANLIB=zig-ranlib');
GlobalEnvManager::putenv('OBJCOPY=zig-objcopy');
GlobalEnvManager::putenv('SPC_COMPILER_RT_DIR=' . PKG_ROOT_PATH . '/zig/lib/' . SystemTarget::getCanonicalTriple());
GlobalEnvManager::putenv('OBJCOPY=' . PKG_ROOT_PATH . '/llvm-tools/bin/llvm-objcopy');
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
if (!str_contains($extra_libs, '-lunwind')) {
// Add unwind library if not already present
@@ -50,6 +64,8 @@ class ZigToolchain implements UnixToolchainInterface
// zig-cc/clang treats strlcpy/strlcat as compiler builtins, so configure link tests pass (HAVE_STRLCPY=1)
$extra_vars = getenv('SPC_EXTRA_PHP_VARS') ?: '';
GlobalEnvManager::putenv("SPC_EXTRA_PHP_VARS=ac_cv_func_strlcpy=no ac_cv_func_strlcat=no {$extra_vars}");
$this->ensureCompilerRt();
}
public function getCompilerInfo(): ?string
@@ -85,6 +101,48 @@ class ZigToolchain implements UnixToolchainInterface
return false;
}
private function ensureCompilerRt(): void
{
$rt = new llvm_compiler_rt();
$triple = SystemTarget::getCanonicalTriple();
$libDir = PKG_ROOT_PATH . '/zig/lib/' . $triple;
if ($rt->isBuilt($libDir)) {
return;
}
if (!is_dir(SOURCE_PATH . '/llvm-compiler-rt/lib/profile')) {
// Source not yet downloaded; install via nested PackageInstaller. The recursion guard
// on afterInit prevents the nested run from re-entering this method. Save the outer
// installer/builder in the container so executors keep seeing the outer one after.
// The PackageInstaller surfaces its own spinner for the install; AfterBinaryExtract
// builds for the current triple, so we're done after run().
$outerInstaller = ApplicationContext::tryGet(PackageInstaller::class);
$outerBuilder = ApplicationContext::tryGet(PackageBuilder::class);
try {
new PackageInstaller()
->addInstallPackage('llvm-compiler-rt')
->run(true);
} finally {
if ($outerInstaller !== null) {
ApplicationContext::set(PackageInstaller::class, $outerInstaller);
}
if ($outerBuilder !== null) {
ApplicationContext::set(PackageBuilder::class, $outerBuilder);
}
}
return;
}
// Source already extracted from a previous run on a different triple; rebuild here with our
// own progress spinner since we're outside the PackageInstaller flow.
InteractiveTerm::indicateProgress('Building llvm-compiler-rt for ' . ConsoleColor::yellow($triple));
try {
$rt->buildForTriple();
} catch (\Throwable $e) {
InteractiveTerm::finish('Build llvm-compiler-rt for ' . ConsoleColor::red($triple) . ' failed', false);
throw $e;
}
InteractiveTerm::finish('Built llvm-compiler-rt for ' . ConsoleColor::green($triple));
}
private function getPath(): string
{
return PKG_ROOT_PATH . '/zig';

View File

@@ -221,9 +221,6 @@ final class PgoContext
public function mergeProfiles(): void
{
if (trim((string) shell_exec('command -v llvm-profdata 2>/dev/null')) === '') {
throw new WrongUsageException('PGO --phase=use: llvm-profdata not on PATH');
}
foreach ($this->trainableSapis as $sapi) {
$this->mergeSapi($sapi);
}
@@ -248,7 +245,8 @@ final class PgoContext
$out = $this->profDataFile($sapi);
$inputs = array_merge($raws, $csRaws);
$argv = implode(' ', array_map('escapeshellarg', $inputs));
shell()->exec('llvm-profdata merge --failure-mode=warn -output=' . escapeshellarg($out) . ' ' . $argv);
$profdata = PKG_ROOT_PATH . '/llvm-tools/bin/llvm-profdata';
shell()->exec(escapeshellarg($profdata) . ' merge --failure-mode=warn -output=' . escapeshellarg($out) . ' ' . $argv);
if (!is_file($out) || filesize($out) === 0) {
throw new WrongUsageException("PGO --phase=use: empty merge output for {$sapi}");
}

View File

@@ -37,6 +37,10 @@ while [[ $# -gt 0 ]]; do
PARSED_ARGS+=("${OPT_NAME}=${OPT_VALUE}")
shift
;;
-mtune=generic)
PARSED_ARGS+=("-mtune=baseline")
shift
;;
-Wlogical-op|-Wduplicated-cond|-Wduplicated-branches|-Wno-clobbered|-Wjump-misses-init|-Wformat-truncation|-Warray-bounds=*|-Wimplicit-fallthrough=*)
# GCC-only warning flags that clang/zig doesn't recognize; drop to silence -Wunknown-warning-option noise
shift
@@ -59,11 +63,13 @@ for _a in "${PARSED_ARGS[@]}"; do
esac
done
[[ "$SPC_COMPILER_EXTRA" == *-fprofile-generate* || "$SPC_COMPILER_EXTRA" == *-fcs-profile-generate* ]] && NEED_PROFILE_RT=1
if [[ $IS_LINK -eq 1 && $NEED_PROFILE_RT -eq 1 && -f "$SCRIPT_DIR/lib/libclang_rt.profile.a" ]]; then
PARSED_ARGS+=("$SCRIPT_DIR/lib/libclang_rt.profile.a" "-Wl,-u,__llvm_profile_runtime")
RT_DIR="${SPC_COMPILER_RT_DIR:-}"
if [[ $IS_LINK -eq 1 && $NEED_PROFILE_RT -eq 1 && -n "$RT_DIR" && -f "$RT_DIR/libclang_rt.profile.a" ]]; then
PARSED_ARGS+=("$RT_DIR/libclang_rt.profile.a" "-Wl,-u,__llvm_profile_runtime")
fi
if [[ $IS_LINK -eq 1 && $NEED_CRT -eq 1 && -f "$SCRIPT_DIR/lib/clang_rt.crtbegin.o" && -f "$SCRIPT_DIR/lib/clang_rt.crtend.o" ]]; then
PARSED_ARGS+=("$SCRIPT_DIR/lib/clang_rt.crtbegin.o" "$SCRIPT_DIR/lib/clang_rt.crtend.o")
if [[ $IS_LINK -eq 1 && $NEED_CRT -eq 1 && -n "$RT_DIR" && -f "$RT_DIR/clang_rt.crtbegin.o" && -f "$RT_DIR/clang_rt.crtend.o" ]]; then
PARSED_ARGS+=("$RT_DIR/clang_rt.crtbegin.o" "$RT_DIR/clang_rt.crtend.o")
fi
[[ -n "$SPC_TARGET" ]] && TARGET="-target $SPC_TARGET" || TARGET=""