Files
static-php-cli/src/Package/Artifact/llvm_tools.php

181 lines
6.8 KiB
PHP
Raw Normal View History

<?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 llvm_tools
{
public const 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: '{pkg_root_path}/llvm-tools-src', 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): bool
{
$sourceRoot ??= PKG_ROOT_PATH . '/llvm-tools-src';
$binDir = PKG_ROOT_PATH . '/llvm-tools/bin';
if ($this->allBuilt($binDir)) {
return true;
}
$llvmDir = "{$sourceRoot}/llvm";
if (!is_dir($llvmDir)) {
logger()->error("[llvm-tools] expected llvm/ subdir at {$llvmDir} (extraction layout changed?)");
return false;
}
$cmake = trim((string) shell_exec('command -v cmake 2>/dev/null'));
if ($cmake === '') {
logger()->error('[llvm-tools] cmake not found on PATH; install cmake first.');
return false;
}
$generator = trim((string) shell_exec('command -v ninja 2>/dev/null')) !== '' ? 'Ninja' : 'Unix Makefiles';
$cc = PKG_ROOT_PATH . '/zig/zig-cc';
$cxx = PKG_ROOT_PATH . '/zig/zig-c++';
$buildDir = "{$sourceRoot}/build";
$installDir = PKG_ROOT_PATH . '/llvm-tools';
f_mkdir($buildDir, recursive: true);
$cmakeArgs = [
'-G', $generator,
'-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=' . $cc,
'-DCMAKE_CXX_COMPILER=' . $cxx,
'-DCMAKE_INSTALL_PREFIX=' . $installDir,
];
$savedTarget = getenv('SPC_TARGET');
f_putenv('SPC_TARGET=' . GNU_ARCH . '-linux-musl');
try {
$cmd = escapeshellarg($cmake) . ' ' . implode(' ', array_map('escapeshellarg', $cmakeArgs)) . ' 2>&1';
logger()->info("Configuring llvm-tools (generator: {$generator}, target: " . getenv('SPC_TARGET') . ')');
exec($cmd, $out, $rc);
if ($rc !== 0) {
logger()->error('[llvm-tools] cmake configure failed: ' . implode("\n", $out));
return false;
}
$jobs = (int) (getenv('SPC_CONCURRENCY') ?: CPU_COUNT);
if ($jobs < 1) {
$jobs = 1;
}
$targetArgs = '';
foreach (self::TOOLS as $t) {
$targetArgs .= ' --target ' . escapeshellarg($t);
}
$buildCmd = 'cmake --build ' . escapeshellarg($buildDir) . $targetArgs
. ($generator === 'Ninja' ? " -j{$jobs}" : " -- -j{$jobs}");
logger()->info('Building llvm-tools (' . implode(', ', self::TOOLS) . ')');
exec($buildCmd . ' 2>&1', $out2, $rc2);
if ($rc2 !== 0) {
logger()->error('[llvm-tools] build failed: ' . implode("\n", array_slice($out2, -40)));
return false;
}
} finally {
f_putenv('SPC_TARGET=' . ($savedTarget === false ? '' : $savedTarget));
}
f_mkdir($binDir, recursive: true);
foreach (self::TOOLS as $t) {
$built = "{$buildDir}/bin/{$t}";
$dst = "{$binDir}/{$t}";
if (!is_file($built)) {
logger()->error("[llvm-tools] expected output {$built} not found");
return false;
}
if (!copy($built, $dst)) {
logger()->error("[llvm-tools] failed to copy {$built}{$dst}");
return false;
}
chmod($dst, 0755);
logger()->info("[llvm-tools] installed {$dst} (" . filesize($dst) . ' bytes)');
}
return true;
}
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';
$verLine = trim((string) shell_exec(escapeshellarg($zig) . ' cc --version 2>/dev/null'));
if (preg_match('/clang version (\d+\.\d+\.\d+)/', $verLine, $m)) {
return $m[1];
}
return null;
}
}