Skip to content

Commit b0007ed

Browse files
authored
Fix doctor on fresh Linux, auto-install llvm-tools for cross builds (#1256)
2 parents 219b40c + 3629644 commit b0007ed

24 files changed

Lines changed: 325 additions & 89 deletions

‎config/pkg/tool/llvm-tools.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ llvm-tools:
33
artifact:
44
source:
55
type: custom
6-
binary: custom
6+
binary:
7+
linux-x86_64: { type: custom, base-url: 'https://dl.static-php.dev/v3/deps/llvm-tools/' }
8+
linux-aarch64: { type: custom, base-url: 'https://dl.static-php.dev/v3/deps/llvm-tools/' }
79
metadata:
810
license-files: [llvm/LICENSE.TXT]
911
license: Apache-2.0-with-LLVM-exception

‎docs/en/develop/artifact-model.md‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,10 +454,26 @@ artifact:
454454

455455
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.
456456

457-
- **Optional**: `func` — name of the specific method to invoke in the implementation class
457+
- **Optional**:
458+
- `func` — name of the specific method to invoke in the implementation class
459+
- `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
458460

459461
```yaml
460462
artifact:
461463
source:
462464
type: custom
465+
binary:
466+
linux-x86_64:
467+
type: custom
468+
base-url: 'https://dl.static-php.dev/v3/deps/llvm-tools/'
469+
```
470+
471+
A custom downloader receives the `Artifact` and reads the option through `getBinaryDownloadConfig()`:
472+
473+
```php
474+
public function downBinary(ArtifactDownloader $downloader, Artifact $artifact): DownloadResult
475+
{
476+
$base_url = $artifact->getBinaryDownloadConfig()['base-url'];
477+
// ... append the filename computed at runtime and download
478+
}
463479
```

‎docs/en/develop/system-build-tools.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Doctor additionally checks:
6666

6767
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.
6868

69-
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.
69+
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.
7070

7171
::: tip
7272
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.

‎docs/en/guide/cli-reference.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ These flags apply only to the combined `build:php` target. To build a specific S
9696
|--------------------------------------|-------|--------------------------------------------------------------------------------------------------------|
9797
| `--no-strip` | | Keep debug symbols; do not strip the binary |
9898
| `--with-upx-pack` | | Compress the output binary with UPX (install first with `spc install-pkg upx`; Linux and Windows only) |
99-
| `--with-llvm-tools` | | Use LLVM binutils (`llvm-objcopy`/`llvm-strip`/`llvm-profdata`) instead of the host ones (Zig toolchain, Linux only) |
10099
| `--disable-opcache-jit` | | Disable OPcache JIT |
101100
| `--with-config-file-path=<path>` | | Directory where PHP looks for `php.ini` (default: `/usr/local/etc/php`) |
102101
| `--with-config-file-scan-dir=<path>` | | Directory PHP scans for additional `.ini` files (default: `/usr/local/etc/php/conf.d`) |

‎docs/en/guide/first-build.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ spc build:php "bcmath,phar,zlib,openssl,curl" --build-cli --build-micro
119119
| `--no-strip` | Keep debug symbols; do not strip the binary |
120120
| `-I key=value` | Hard-compile an INI option into PHP |
121121
| `--with-upx-pack` | Compress output with UPX (run `spc install-pkg upx` first) |
122-
| `--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) |
123122

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

‎docs/zh/develop/artifact-model.md‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,11 +450,27 @@ artifact:
450450

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

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

455457
```yaml
456458
artifact:
457459
source:
458460
type: custom
461+
binary:
462+
linux-x86_64:
463+
type: custom
464+
base-url: 'https://dl.static-php.dev/v3/deps/llvm-tools/'
465+
```
466+
467+
自定义下载器会收到 `Artifact`,并通过 `getBinaryDownloadConfig()` 读取该选填项:
468+
469+
```php
470+
public function downBinary(ArtifactDownloader $downloader, Artifact $artifact): DownloadResult
471+
{
472+
$base_url = $artifact->getBinaryDownloadConfig()['base-url'];
473+
// ... 拼接运行时计算出的文件名并下载
474+
}
459475
```
460476

‎docs/zh/develop/system-build-tools.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Doctor 还会检查:
6666

6767
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 构建不需要这些跨宿主机组件。
6868

69-
可选的 `--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` 已安装时生效,其余情况构建过程会打印警告说明被忽略的原因。
69+
在 `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`。
7070

7171
::: tip
7272
目标字符串和宿主发行版是两个不同概念。glibc 发行版可以通过 Zig 生成 musl 目标,而 `*-gnu.*` 目标仍会动态链接到指定的 glibc ABI。

‎docs/zh/guide/cli-reference.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ spc build:php <extensions> [options]
9696
|--------------------------------------|------|--------------------------------------------------------|
9797
| `--no-strip` | | 保留调试符号,不精简二进制 |
9898
| `--with-upx-pack` | | 用 UPX 压缩产物(需先 `spc install-pkg upx`;仅 Linux 和 Windows) |
99-
| `--with-llvm-tools` | | 使用 LLVM binutils(`llvm-objcopy`/`llvm-strip`/`llvm-profdata`)替代系统工具(仅 Zig 工具链、Linux) |
10099
| `--disable-opcache-jit` | | 禁用 OPcache JIT |
101100
| `--with-config-file-path=<path>` | | PHP 查找 `php.ini` 的目录(默认:`/usr/local/etc/php`) |
102101
| `--with-config-file-scan-dir=<path>` | | PHP 扫描追加 `.ini` 文件的目录(默认:`/usr/local/etc/php/conf.d`) |

‎docs/zh/guide/first-build.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ spc build:php "bcmath,phar,zlib,openssl,curl" --build-cli --build-micro
121121
| `--no-strip` | 保留调试符号,不精简二进制 |
122122
| `-I key=value` | 硬编译 INI 选项到 PHP 中 |
123123
| `--with-upx-pack` | 用 UPX 压缩产物(需先 `spc install-pkg upx`) |
124-
| `--with-llvm-tools` | Zig + Linux 构建时使用 LLVM binutils(LTO/PGO/共享库产物;依赖 `llvm-tools` 包,其预构建二进制尚未发布) |
125124

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

‎src/Package/Artifact/llvm_tools.php‎

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
namespace Package\Artifact;
66

7+
use StaticPHP\Artifact\Artifact;
78
use StaticPHP\Artifact\ArtifactDownloader;
89
use StaticPHP\Artifact\Downloader\DownloadResult;
910
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult;
10-
use StaticPHP\Artifact\Downloader\Type\GitHubRelease;
1111
use StaticPHP\Artifact\Downloader\Type\GitHubTokenSetupTrait;
1212
use StaticPHP\Attribute\Artifact\CustomBinary;
1313
use StaticPHP\Attribute\Artifact\CustomSource;
@@ -39,15 +39,32 @@ public function downSource(ArtifactDownloader $downloader): DownloadResult
3939
'linux-x86_64',
4040
'linux-aarch64',
4141
])]
42-
public function downBinary(ArtifactDownloader $downloader): DownloadResult
42+
public function downBinary(ArtifactDownloader $downloader, Artifact $artifact): DownloadResult
4343
{
44-
$asset = 'llvm-tools-' . SystemTarget::getTargetArch() . '-linux-musl-' . $this->clangVersion() . '.txz';
45-
return new GitHubRelease()->download('llvm-tools', [
46-
'type' => 'ghrel',
47-
'repo' => 'static-php/hosted',
48-
'match' => preg_quote($asset, '|'),
49-
'extract' => '{pkg_root_path}/llvm-tools',
50-
], $downloader);
44+
// The tools run on the build host, so the host architecture selects the archive, not
45+
// SPC_TARGET; they are static musl executables and run on any Linux distribution.
46+
$clang = $this->clangVersion();
47+
// Pre-built archives: llvm-tools-{host-arch}-linux-musl-{clang-version}.txz plus a .sha256 sidecar.
48+
$filename = 'llvm-tools-' . GNU_ARCH . "-linux-musl-{$clang}.txz";
49+
$base_url = $artifact->getBinaryDownloadConfig()['base-url'] ?? null;
50+
if (!is_string($base_url) || $base_url === '') {
51+
throw new DownloaderException('llvm-tools: binary config for ' . SystemTarget::getCurrentPlatformString() . ' does not define a base-url');
52+
}
53+
$url = $base_url . $filename;
54+
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
55+
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());
56+
57+
// Published next to the archive by the llvm-tools workflow of static-php/hosted.
58+
$checksum = default_shell()->executeCurl("{$url}.sha256", retries: $downloader->getRetry());
59+
$expected = is_string($checksum) ? strtolower((string) strtok(trim($checksum), " \t")) : '';
60+
if (preg_match('/^[0-9a-f]{64}$/', $expected) !== 1) {
61+
throw new DownloaderException("Cannot fetch the checksum of {$filename} from {$url}.sha256");
62+
}
63+
if (($actual = hash_file('sha256', $path)) !== $expected) {
64+
unlink($path);
65+
throw new DownloaderException("Hash mismatch for downloaded {$filename}. Expected {$expected}, got {$actual}");
66+
}
67+
return DownloadResult::archive($filename, ['url' => $url], extract: '{pkg_root_path}/llvm-tools', verified: true, version: $clang);
5168
}
5269

5370
#[CustomSourceCheckUpdate('llvm-tools')]

0 commit comments

Comments
 (0)