Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion config/pkg/tool/llvm-tools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ llvm-tools:
artifact:
source:
type: custom
binary: custom
binary:
linux-x86_64: { type: custom, base-url: 'https://dl.static-php.dev/v3/deps/llvm-tools/' }
linux-aarch64: { type: custom, base-url: 'https://dl.static-php.dev/v3/deps/llvm-tools/' }
metadata:
license-files: [llvm/LICENSE.TXT]
license: Apache-2.0-with-LLVM-exception
Expand Down
18 changes: 17 additions & 1 deletion docs/en/develop/artifact-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,10 +454,26 @@ artifact:

Delegates download logic entirely to a PHP class under `src/Package/Artifact/`. If `func` is not specified, the class's default download method is called.

- **Optional**: `func` — name of the specific method to invoke in the implementation class
- **Optional**:
- `func` — name of the specific method to invoke in the implementation class
- `base-url` — an extra base URL the implementation can read from its artifact config, for example to assemble a download URL around a filename it computes at runtime

```yaml
artifact:
source:
type: custom
binary:
linux-x86_64:
type: custom
base-url: 'https://dl.static-php.dev/v3/deps/llvm-tools/'
```

A custom downloader receives the `Artifact` and reads the option through `getBinaryDownloadConfig()`:

```php
public function downBinary(ArtifactDownloader $downloader, Artifact $artifact): DownloadResult
{
$base_url = $artifact->getBinaryDownloadConfig()['base-url'];
// ... append the filename computed at runtime and download
}
```
2 changes: 1 addition & 1 deletion docs/en/develop/system-build-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Doctor additionally checks:

The Linux default is `ZigToolchain`. The toolchain declares the `zig` tool package, so every build installs it even when `spc doctor` was never run (Doctor can also install it when it is missing, and `--auto-fix` checks the rest of the environment). It also declares `llvm-compiler-rt`, which every build installs before anything else: it supplies the profile runtime, the `crtbegin`/`crtend` objects and `libclang_rt.cpu_model.a` that the zig-shipped Clang omits. Its sources come from a trimmed compiler-rt archive published under `dl.static-php.dev/v3/deps/compiler-rt/`, keyed by the clang version zig reports, so a build downloads about 140KB instead of the 150MB+ llvm-project tarball that LLVM has required since release 22. That archive is the only source: when the archive for the zig version in use is missing, the build fails with instructions for publishing it. Because compiler-rt sources are resolved from the clang zig ships, a machine that has no zig yet installs zig on its first build and picks compiler-rt up on the next one. The objects are compiled locally (a few seconds) and are rebuilt when zig is upgraded or when `SPC_TARGET` changes the target triple. When a musl target is built on a non-musl host, Doctor also checks the musl runtime/wrapper required by that target. The legacy `MuslToolchain` additionally expects the musl cross toolchain under `/usr/local/musl`. A native Alpine build does not need these cross-host components.

The optional `--with-llvm-tools` build option installs the `llvm-tools` package (`llvm-objcopy`, `llvm-strip`, `llvm-profdata`), which is needed for LTO, PGO and shared-library builds because the host binutils cannot process the archives/bitcode Zig produces. It is never required: without it the build prefers the host binutils (zig's own `objcopy` only implements a subset of the options) and reports a failing strip as a warning instead of aborting the build. No prebuilt `llvm-tools` binaries are published yet, so enabling the option currently falls back to building LLVM from source; point `OBJCOPY` and `STRIP` at a system LLVM installation instead if you need LLVM binutils today. The option is only honoured for Linux targets built with `ZigToolchain` and after `zig` is installed; otherwise the build prints a warning explaining why it was ignored.
Under `ZigToolchain`, `llvm-objcopy`, `llvm-strip` and `llvm-profdata` come from the `llvm-tools` package. Cross builds, where `SPC_TARGET` names a different architecture or OS than the host, require it, because the host binutils only understand the host architecture: the toolchain then declares `llvm-tools` alongside `llvm-compiler-rt`, and the build installs it automatically. Native builds keep using the host binutils (zig's own `objcopy` only implements a subset of the options), and a failing strip or debug-info split only produces a warning instead of aborting the build; run `bin/spc install-pkg llvm-tools` to use the LLVM tools there as well. The package downloads prebuilt static binaries from `dl.static-php.dev/v3/deps/llvm-tools/`, selected by the host architecture (not `SPC_TARGET`) and keyed by the clang version zig reports, and verifies them against the published SHA-256 checksum. When no archive is published for the zig version in use yet (or the checksum does not match), it falls back to building LLVM from source, which takes much longer. Like compiler-rt, it needs zig on disk to know that version, so a cross build on a machine without zig installs zig first and picks up `llvm-tools` on the next run.

::: tip
The target string and the host distribution are different concepts. A glibc distribution can produce a musl target through Zig, while a `*-gnu.*` target remains dynamically linked to the requested glibc ABI.
Expand Down
1 change: 0 additions & 1 deletion docs/en/guide/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ These flags apply only to the combined `build:php` target. To build a specific S
|--------------------------------------|-------|--------------------------------------------------------------------------------------------------------|
| `--no-strip` | | Keep debug symbols; do not strip the binary |
| `--with-upx-pack` | | Compress the output binary with UPX (install first with `spc install-pkg upx`; Linux and Windows only) |
| `--with-llvm-tools` | | Use LLVM binutils (`llvm-objcopy`/`llvm-strip`/`llvm-profdata`) instead of the host ones (Zig toolchain, Linux only) |
| `--disable-opcache-jit` | | Disable OPcache JIT |
| `--with-config-file-path=<path>` | | Directory where PHP looks for `php.ini` (default: `/usr/local/etc/php`) |
| `--with-config-file-scan-dir=<path>` | | Directory PHP scans for additional `.ini` files (default: `/usr/local/etc/php/conf.d`) |
Expand Down
1 change: 0 additions & 1 deletion docs/en/guide/first-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ spc build:php "bcmath,phar,zlib,openssl,curl" --build-cli --build-micro
| `--no-strip` | Keep debug symbols; do not strip the binary |
| `-I key=value` | Hard-compile an INI option into PHP |
| `--with-upx-pack` | Compress output with UPX (run `spc install-pkg upx` first) |
| `--with-llvm-tools` | Use LLVM binutils for Zig builds on Linux (LTO/PGO/shared output; needs the `llvm-tools` package, whose prebuilt binaries are not published yet) |

Example — baking in a larger memory limit and disabling the `system` function:

Expand Down
18 changes: 17 additions & 1 deletion docs/zh/develop/artifact-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,11 +450,27 @@ artifact:

完全自定义的下载逻辑,由 `src/Package/Artifact/` 目录下对应的 PHP 类实现。如果不指定 `func`,则调用类的默认下载方法。

- **选填**:`func` — 调用实现类中的指定方法名
- **选填**:
- `func` — 调用实现类中的指定方法名
- `base-url` — 实现类可从其 artifact 配置中读取的额外基础 URL,例如配合运行时计算出的文件名拼接下载地址

```yaml
artifact:
source:
type: custom
binary:
linux-x86_64:
type: custom
base-url: 'https://dl.static-php.dev/v3/deps/llvm-tools/'
```

自定义下载器会收到 `Artifact`,并通过 `getBinaryDownloadConfig()` 读取该选填项:

```php
public function downBinary(ArtifactDownloader $downloader, Artifact $artifact): DownloadResult
{
$base_url = $artifact->getBinaryDownloadConfig()['base-url'];
// ... 拼接运行时计算出的文件名并下载
}
```

2 changes: 1 addition & 1 deletion docs/zh/develop/system-build-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Doctor 还会检查:

Linux 默认使用 `ZigToolchain`。工具链会把 `zig` 工具 Package 声明为自己的依赖,因此即使从未运行过 `spc doctor`,每次构建也会自动安装它(Doctor 同样会在缺失时安装,`--auto-fix` 负责检查其余环境)。它还会声明 `llvm-compiler-rt`,每次构建都会先安装该 target:它提供 Zig 自带 Clang 所缺失的 profile 运行时、`crtbegin`/`crtend` 目标文件和 `libclang_rt.cpu_model.a`。其源码来自发布在 `dl.static-php.dev/v3/deps/compiler-rt/` 下的裁剪版 compiler-rt 归档,以 zig 报告的 clang 版本作为键,因此一次构建只需下载约 140KB,而不是 LLVM 自 22 版起才提供的 150MB+ llvm-project 单体包。该归档是唯一来源:若当前 zig 版本对应的归档尚未发布,构建会直接失败并提示如何发布它。由于 compiler-rt 的源码要按 zig 自带的 clang 版本来解析,尚无 zig 的机器会在第一次构建时装上 zig,并在下一次构建时补上 compiler-rt。这些对象在本地编译(只需几秒),并在 zig 升级或 `SPC_TARGET` 改变目标 triple 时重新构建。在非 musl 宿主机上构建 musl 目标时,Doctor 还会检查该目标所需的 musl 运行时/wrapper。旧式的 `MuslToolchain` 还要求 `/usr/local/musl` 下存在 musl 交叉工具链。原生 Alpine 构建不需要这些跨宿主机组件。

可选的 `--with-llvm-tools` 构建选项会安装 `llvm-tools` 包(`llvm-objcopy`、`llvm-strip`、`llvm-profdata`);LTO、PGO 和共享库构建需要它,因为系统 binutils 无法处理 Zig 产生的归档/bitcode。它并非必需:未安装时会优先使用系统 binutils(Zig 自带的 `objcopy` 只实现了部分选项),且 strip 失败只会输出警告而不会中断构建。目前尚未发布 `llvm-tools` 的预构建二进制,因此启用该选项会回退到从源码构建 LLVM;如果现在就想要 LLVM binutils,可改为把 `OBJCOPY`/`STRIP` 指向系统安装的 LLVM 工具。该选项仅在 Linux 目标 + `ZigToolchain` 且 `zig` 已安装时生效,其余情况构建过程会打印警告说明被忽略的原因。
在 `ZigToolchain` 下,`llvm-objcopy`、`llvm-strip` 和 `llvm-profdata` 由 `llvm-tools` 包提供。交叉构建(`SPC_TARGET` 指定的架构或操作系统与宿主机不同)必须使用它,因为系统 binutils 只认识宿主机架构:此时工具链会在 `llvm-compiler-rt` 之外再声明 `llvm-tools`,构建过程会自动安装它。本机构建继续使用系统 binutils(Zig 自带的 `objcopy` 只实现了部分选项),strip 或拆分调试信息失败只会输出警告而不会中断构建;如需在本机构建中也使用 LLVM 工具,可运行 `bin/spc install-pkg llvm-tools`。该包会从 `dl.static-php.dev/v3/deps/llvm-tools/` 下载预构建的静态二进制:按宿主机架构(而非 `SPC_TARGET`)选择,以 zig 报告的 clang 版本作为键,并用发布的 SHA-256 校验值进行校验。若当前 zig 版本对应的归档尚未发布(或校验失败),则回退到从源码构建 LLVM,耗时会长得多。与 compiler-rt 一样,它需要磁盘上已有 zig 才能确定该版本,因此在尚无 zig 的机器上进行交叉构建时,会先安装 zig,并在下一次构建时补上 `llvm-tools`。

::: tip
目标字符串和宿主发行版是两个不同概念。glibc 发行版可以通过 Zig 生成 musl 目标,而 `*-gnu.*` 目标仍会动态链接到指定的 glibc ABI。
Expand Down
1 change: 0 additions & 1 deletion docs/zh/guide/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ spc build:php <extensions> [options]
|--------------------------------------|------|--------------------------------------------------------|
| `--no-strip` | | 保留调试符号,不精简二进制 |
| `--with-upx-pack` | | 用 UPX 压缩产物(需先 `spc install-pkg upx`;仅 Linux 和 Windows) |
| `--with-llvm-tools` | | 使用 LLVM binutils(`llvm-objcopy`/`llvm-strip`/`llvm-profdata`)替代系统工具(仅 Zig 工具链、Linux) |
| `--disable-opcache-jit` | | 禁用 OPcache JIT |
| `--with-config-file-path=<path>` | | PHP 查找 `php.ini` 的目录(默认:`/usr/local/etc/php`) |
| `--with-config-file-scan-dir=<path>` | | PHP 扫描追加 `.ini` 文件的目录(默认:`/usr/local/etc/php/conf.d`) |
Expand Down
1 change: 0 additions & 1 deletion docs/zh/guide/first-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ spc build:php "bcmath,phar,zlib,openssl,curl" --build-cli --build-micro
| `--no-strip` | 保留调试符号,不精简二进制 |
| `-I key=value` | 硬编译 INI 选项到 PHP 中 |
| `--with-upx-pack` | 用 UPX 压缩产物(需先 `spc install-pkg upx`) |
| `--with-llvm-tools` | Zig + Linux 构建时使用 LLVM binutils(LTO/PGO/共享库产物;依赖 `llvm-tools` 包,其预构建二进制尚未发布) |

硬编译 INI 的例子——预设更大的内存限制,并禁用 `system` 函数:

Expand Down
35 changes: 26 additions & 9 deletions src/Package/Artifact/llvm_tools.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

namespace Package\Artifact;

use StaticPHP\Artifact\Artifact;
use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
use StaticPHP\Artifact\Downloader\Type\GitHubRelease;
use StaticPHP\Artifact\Downloader\Type\GitHubTokenSetupTrait;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomSource;
Expand Down Expand Up @@ -39,15 +39,32 @@ public function downSource(ArtifactDownloader $downloader): DownloadResult
'linux-x86_64',
'linux-aarch64',
])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
public function downBinary(ArtifactDownloader $downloader, Artifact $artifact): DownloadResult
{
$asset = 'llvm-tools-' . SystemTarget::getTargetArch() . '-linux-musl-' . $this->clangVersion() . '.txz';
return new GitHubRelease()->download('llvm-tools', [
'type' => 'ghrel',
'repo' => 'static-php/hosted',
'match' => preg_quote($asset, '|'),
'extract' => '{pkg_root_path}/llvm-tools',
], $downloader);
// The tools run on the build host, so the host architecture selects the archive, not
// SPC_TARGET; they are static musl executables and run on any Linux distribution.
$clang = $this->clangVersion();
// Pre-built archives: llvm-tools-{host-arch}-linux-musl-{clang-version}.txz plus a .sha256 sidecar.
$filename = 'llvm-tools-' . GNU_ARCH . "-linux-musl-{$clang}.txz";
$base_url = $artifact->getBinaryDownloadConfig()['base-url'] ?? null;
if (!is_string($base_url) || $base_url === '') {
throw new DownloaderException('llvm-tools: binary config for ' . SystemTarget::getCurrentPlatformString() . ' does not define a base-url');
}
$url = $base_url . $filename;
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());

// Published next to the archive by the llvm-tools workflow of static-php/hosted.
$checksum = default_shell()->executeCurl("{$url}.sha256", retries: $downloader->getRetry());
$expected = is_string($checksum) ? strtolower((string) strtok(trim($checksum), " \t")) : '';
if (preg_match('/^[0-9a-f]{64}$/', $expected) !== 1) {
throw new DownloaderException("Cannot fetch the checksum of {$filename} from {$url}.sha256");
}
if (($actual = hash_file('sha256', $path)) !== $expected) {
unlink($path);
throw new DownloaderException("Hash mismatch for downloaded {$filename}. Expected {$expected}, got {$actual}");
}
return DownloadResult::archive($filename, ['url' => $url], extract: '{pkg_root_path}/llvm-tools', verified: true, version: $clang);
}

#[CustomSourceCheckUpdate('llvm-tools')]
Expand Down
39 changes: 0 additions & 39 deletions src/Package/Target/php.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Registry\ArtifactLoader;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Toolchain\Interface\ToolchainInterface;
use StaticPHP\Toolchain\ToolchainManager;
use StaticPHP\Toolchain\ZigToolchain;
use StaticPHP\Util\DependencyResolver;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\InteractiveTerm;
Expand Down Expand Up @@ -139,7 +137,6 @@ public function init(TargetPackage $package): void
$package->addBuildArgument('extensions', InputArgument::REQUIRED, 'Comma-separated list of static extensions to build');
$package->addBuildOption('no-strip', null, null, 'build without strip, keep symbols to debug');
$package->addBuildOption('with-upx-pack', null, null, 'Compress / pack binary using UPX tool (linux/windows only)');
$package->addBuildOption('with-llvm-tools', null, null, 'Use LLVM binutils (llvm-objcopy/llvm-strip/llvm-profdata) instead of the host ones (zig toolchain, linux only)');

// php configure and extra patch options
$package->addBuildOption('disable-opcache-jit', null, null, 'Disable opcache jit');
Expand Down Expand Up @@ -266,25 +263,6 @@ public function resolveBuild(TargetPackage $package, PackageInstaller $installer
$installer->addInstallPackage('upx');
}

// Zig's bundled binutils only implement a subset of the options these post-processing
// steps need (zig-objcopy reports --only-keep-debug as unimplemented), and the host
// binutils are used otherwise. The LLVM binutils are the opt-in replacement
// (--with-llvm-tools), added through the normal resolve/download pipeline so the outer
// download options (--no-download, --dl-prefer-binary, ...) are honoured.
// llvm-tools is never required, and llvm-compiler-rt is declared by the toolchain itself.
if ($package->getBuildOption('with-llvm-tools')) {
if (SystemTarget::getTargetOS() !== 'Linux') {
logger()->warning('--with-llvm-tools is only available for Linux targets; ignoring it.');
} elseif (ToolchainManager::getToolchainClass() !== ZigToolchain::class) {
logger()->warning('--with-llvm-tools requires the zig toolchain; ignoring it.');
} elseif (!$this->hasInstalledZigPackage()) {
// llvm-tools is versioned against zig's clang, so its downloader needs zig on disk.
logger()->warning('--with-llvm-tools needs an installed zig; run `spc doctor` first and re-run the build. Ignoring it for now.');
} else {
$installer->addInstallPackage('llvm-tools')->preferBinary('llvm-tools');
}
}

return [...$extensions_pkg, ...$additional_packages];
}

Expand Down Expand Up @@ -569,21 +547,4 @@ private function makeStaticExtensionString(PackageInstaller $installer): string
logger()->debug("Static extension configure args: {$str}");
return $str;
}

/**
* Whether the zig tool package is installed on disk.
*
* Checked through the registry rather than PackageInstaller::isPackageInstalled(): this runs
* inside a #[ResolveBuild] callback, i.e. before the installer has populated its resolved
* package list, so asking the installer would re-enter resolvePackages(). A custom registry
* does not have to ship a `zig` package either, and the caller only needs to warn in that case.
*/
private function hasInstalledZigPackage(): bool
{
if (!PackageLoader::hasPackage('zig')) {
return false;
}
$zig = PackageLoader::getPackage('zig');
return $zig instanceof ToolPackage && $zig->isInstalled();
}
}
Loading
Loading