Compare commits

..

51 Commits

Author SHA1 Message Date
henderkes
a65441bbeb dont pass invalid empty gh token 2026-07-09 23:08:49 +07:00
henderkes
6b6a099a47 fix gmssl and event properly.. 2026-07-09 19:27:41 +07:00
henderkes
8f557aae8a forward-port feat/pgo fixes
Forward-port the genuinely-new fixes from feat/pgo (2026-06-09 .. 07-09)
into v3's reorganized layout:

- zig-cc.sh: prefix runtime-lib appends with `-x none` so the profile/crt/
  cpu_model archives don't poison the following files as `-x c` sources
- ZigToolchain: set ax_cv_have_func_attribute_ifunc=no under -flto
  (an ifunc in LTO bitcode crashes zig 0.16's lld during thin-link)
- php configure: add --with-sysconfdir option; strip build-time env vars
  from phpinfo's "Configure Command"
- ext-brotli / ext-zstd: switch to stable release/tag tarballs; brotli
  configures with --with-libbrotli
- ext-event: patch libevent http_connection.c to use a const peer address
  (both static and shared builds)
- ghrel downloader: prefer GitHub's /releases/latest (the semantically
  latest stable release) over publish-order iteration, so a newer-tagged
  prerelease (e.g. libevent 2.2.x-alpha) can't win over 2.1.x-stable

Commits already present in v3 were skipped: protobuf-latest + libaom
ENABLE_*=OFF (9398a667), the clang runtime -target flag (2adedc09), the
runtime-bits fail-hard half of 3823631a, and github prefer-stable's
prerelease skip (incl. the ghtar /releases/latest path, already in v3).
2026-07-09 14:47:51 +07:00
henderkes
3b8abda8c9 add libclang.cpu.a 2026-06-09 12:27:58 +07:00
henderkes
5747a5661e build shared extensions before frankenphp 2026-06-02 04:00:57 +00:00
henderkes
d52ba59edc fix SourcePatcher::patchHardcodedINI method signature 2026-05-30 09:08:49 +07:00
henderkes
2d5abd31c1 micro patch for cross-arch 32 bit builds 2026-05-29 23:15:53 +07:00
henderkes
69d0f9b8cc remove SPC_ARCH 2026-05-29 22:38:58 +07:00
henderkes
d846db2ef2 forward v2 fix 2026-05-26 00:51:16 +00:00
henderkes
f06891155c revert 2026-05-24 21:42:41 +07:00
henderkes
15deecd34f Merge remote-tracking branch 'origin/v3' into feat/pgo-v3 2026-05-24 20:26:50 +07:00
Marc
7ae5d742c2 V3 check/4 (#1159) 2026-05-24 20:24:11 +07:00
Marc
95be291a84 Merge branch 'v3' into v3-check/4 2026-05-24 20:24:03 +07:00
Marc
d89add106a V3 check/3 (#1158) 2026-05-24 20:23:44 +07:00
Marc
a36129c6cd V3 check/2 (#1157) 2026-05-24 20:23:32 +07:00
Marc
3f3edd62a1 Update src/Package/Extension/password_argon2.php 2026-05-24 17:57:02 +07:00
crazywhalecc
82b77af317 Chore: extension fixes
clickhouse, mongodb, opcache, password-argon2, pgsql, spx
2026-05-24 18:16:47 +08:00
crazywhalecc
df26b93e58 Remove redundant suffix for getClassesPsr4 function 2026-05-24 18:12:04 +08:00
crazywhalecc
d1b4c05381 Fix curl exe build on windows 2026-05-24 18:11:41 +08:00
crazywhalecc
e1658bc0e6 Chore: gettext-win version, password-argon2 deps 2026-05-24 18:09:51 +08:00
crazywhalecc
5053620b61 feat: auto-load local working directory registry in vendor mode 2026-05-24 18:07:06 +08:00
crazywhalecc
9366cbacd9 Update discord invite link 2026-05-24 18:06:30 +08:00
henderkes
fe4803cfaf run shared ext builds after frankenphp build 2026-05-24 12:15:40 +07:00
henderkes
80d81079db backport v2 passing correct libraries, not all 2026-05-24 12:15:04 +07:00
henderkes
6ab52a5181 cxxflags in spcconfigutil 2026-05-23 20:46:21 +07:00
henderkes
5bdcd3f562 dont pass all static exts to frankenphp build either 2026-05-23 20:02:24 +07:00
henderkes
203fed65d9 dont pass shared extension packages to SPCConfigUtil for building php (bleeds libpq polyfills into php configure, poisoning e.g. have_strlcat results) 2026-05-23 16:17:12 +07:00
henderkes
1ae989df59 simplify common zig code paths 2026-05-23 16:15:12 +07:00
henderkes
e7fb1e203f backport fixes from v2 2026-05-23 16:15:12 +07:00
Marc
713f8255af mongodb: export PHP_VERSION_ID for in-tree builds (#1156) 2026-05-23 07:35:24 +07:00
Luther Monson
3c24c92d61 mongodb: export PHP_VERSION_ID for in-tree builds
mongo-php-driver 2.3.3 added a config.m4 check that falls back to
php-config when PHP_VERSION_ID is unset in the shell env. In-tree
PHP source builds have no php-config, so configure fails with:

    checking PHP version... configure: error: php-config not found

Set PHP_VERSION_ID from main/php_version.h before configureForUnix
so the lookup short-circuits.
2026-05-22 10:03:43 -07:00
henderkes
d93fdd9707 use github tokens for package downloads 2026-05-22 23:06:25 +07:00
henderkes
c40d069b0c address #1155 2026-05-22 23:05:51 +07:00
henderkes
9d508f1d39 forward port v2 pgo changes 2026-05-22 15:42:51 +07:00
crazywhalecc
a23ad55fe2 Add preInstall stage 2026-05-22 14:03:26 +08:00
crazywhalecc
735f12648e Update discord invite link 2026-05-22 11:23:53 +08:00
Jerry Ma
6fe55a4d6b Merge branch 'v3' into feat/pgo-v3 2026-05-22 10:27:39 +08:00
Jerry Ma
b8ce9f7787 [v3] fix(windows): static builds vswhere.exe -product * (#1149) 2026-05-22 10:20:18 +08:00
Jerry Ma
6772403d81 [v3] fix: use ftpmirror.gnu.org instead of ftp.gnu.org (#1151) 2026-05-21 15:26:58 +08:00
Marc
60a91a82e1 [v3] fix(macos): static build remove -fno-plt from macOS CFLAGS (#1150) 2026-05-21 12:06:18 +07:00
Luther Monson
4a274b69ac fix: use ftpmirror.gnu.org instead of ftp.gnu.org
ftp.gnu.org is unreliable and frequently times out during CI builds.
ftpmirror.gnu.org is GNU's own CDN that auto-redirects to the nearest
mirror. This is the recommended download method per
https://www.gnu.org/prep/ftp.en.html

Also normalizes /pub/gnu/ paths to /gnu/ since ftpmirror only serves
the latter.

Affects: libiconv, gettext, gmp, idn2, libunistring, ncurses, readline
2026-05-20 21:09:34 -07:00
Luther Monson
e4edd0a00f Merge branch 'v3' into fix/macos-fno-plt 2026-05-20 21:05:54 -07:00
Luther Monson
ae26ef3bdb fix(macos): remove -fno-plt from macOS CFLAGS
-fno-plt is an ELF-only flag that has no effect on macOS Mach-O
targets — clang emits "argument unused" when it encounters it.
Libraries like xz that run -Werror sanity checks during configure
promote that warning to a fatal error, breaking the build.
2026-05-20 20:57:12 -07:00
Luther Monson
049ecfe0a5 Merge branch 'v3' into fix/vswhere-products 2026-05-20 20:54:26 -07:00
Jerry Ma
01a4d872ad fix windows test failures: path separators and arch normalization (#1148) 2026-05-21 10:50:33 +08:00
Luther Monson
a9e54bb725 fix(windows): add -products * to vswhere so Build Tools are found
vswhere.exe defaults to searching Community, Professional, and
Enterprise editions only. CI environments typically install the
lightweight Build Tools product which is a separate product type
(Microsoft.VisualStudio.Product.BuildTools). Without -products *
the tool returns no results and the build fails with "Visual Studio
with C++ tools not found".

See: https://github.com/microsoft/vswhere/wiki/Find-MSBuild
2026-05-20 18:36:58 -07:00
Marc
f1525c0ca7 Merge branch 'v3' into feat/pgo-v3 2026-05-20 16:55:02 +07:00
Luther Monson
ad9b9ec69f Merge branch 'v3' into v3-fix-windows-test-paths 2026-05-19 23:19:39 -07:00
Marc
8a37303925 Add a Log Filter to Strip Github Tokens (#1147) 2026-05-20 13:19:03 +07:00
Luther Monson
52d234f1f4 fix windows test failures: path separators and arch normalization 2026-05-19 21:59:18 -07:00
Luther Monson
a3c39576df filter secrets at logger callback; register basic-auth encoded blob 2026-05-19 21:23:43 -07:00
59 changed files with 629 additions and 195 deletions

View File

@@ -5,7 +5,7 @@
[![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
[![Discord](https://img.shields.io/discord/nrSRbpMJ?label=Discord&logo=discord&style=flat-square)](https://discord.gg/nrSRbpMJ)
[![Discord](https://img.shields.io/discord/nrSRbpMJ?label=Discord&logo=discord&style=flat-square)](https://discord.gg/xf6Rd4pEAk)
**StaticPHP** 是一个强大的工具,用于构建可移植的可执行文件,包括 PHP、扩展等。

View File

@@ -5,7 +5,7 @@
[![Releases](https://img.shields.io/packagist/v/crazywhalecc/static-php-cli?include_prereleases&label=Release&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/releases)
[![CI](https://img.shields.io/github/actions/workflow/status/crazywhalecc/static-php-cli/tests.yml?branch=main&label=Build%20Test&style=flat-square)](https://github.com/crazywhalecc/static-php-cli/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://github.com/crazywhalecc/static-php-cli/blob/main/LICENSE)
[![Discord](https://img.shields.io/discord/nrSRbpMJ?label=Discord&logo=discord&style=flat-square)](https://discord.gg/nrSRbpMJ)
[![Discord](https://img.shields.io/discord/nrSRbpMJ?label=Discord&logo=discord&style=flat-square)](https://discord.gg/xf6Rd4pEAk)
**StaticPHP** is a powerful tool designed for building portable executables including PHP, extensions, and more.

View File

@@ -4,5 +4,5 @@ ncurses:
- COPYING
source:
type: filelist
url: 'https://ftp.gnu.org/pub/gnu/ncurses/'
url: 'https://ftpmirror.gnu.org/gnu/ncurses/'
regex: '/href="(?<file>ncurses-(?<version>[^"]+)\.tar\.gz)"/'

View File

@@ -146,7 +146,7 @@ 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-plt -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections"
SPC_DEFAULT_CFLAGS="--target=${MAC_ARCH}-apple-darwin -O3 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffunction-sections -fdata-sections"
SPC_DEFAULT_CXXFLAGS="${SPC_DEFAULT_CFLAGS}"
SPC_DEFAULT_LDFLAGS="-Wl,-dead_strip"
; phpmicro patches, for more info, see: https://github.com/easysoft/phpmicro/tree/master/patches

View File

@@ -2,12 +2,13 @@ ext-brotli:
type: php-extension
artifact:
source:
type: git
type: ghtagtar
repo: kjdev/php-ext-brotli
extract: php-src/ext/brotli
rev: master
url: 'https://github.com/kjdev/php-ext-brotli'
metadata:
license-files: [LICENSE]
license: MIT
depends:
- brotli
php-extension:
arg-type: '--enable-brotli@shared_suffix@ --with-libbrotli'

View File

@@ -0,0 +1,20 @@
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

View File

@@ -0,0 +1,14 @@
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

View File

@@ -10,3 +10,5 @@ ext-gmssl:
license: PHP-3.01
depends:
- gmssl
php-extension:
arg-type: with-path

View File

@@ -2,9 +2,8 @@ ext-zstd:
type: php-extension
artifact:
source:
type: git
url: 'https://github.com/kjdev/php-ext-zstd'
rev: master
type: ghtar
repo: kjdev/php-ext-zstd
extract: php-src/ext/zstd
metadata:
license-files: [LICENSE]

View File

@@ -3,7 +3,7 @@ gettext:
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/pub/gnu/gettext/'
url: 'https://ftpmirror.gnu.org/gnu/gettext/'
regex: '/href="(?<file>gettext-(?<version>[^"]+)\.tar\.xz)"/'
metadata:
license-files: [gettext-runtime/intl/COPYING.LIB]

View File

@@ -3,7 +3,7 @@ gmp:
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/gmp/'
url: 'https://ftpmirror.gnu.org/gnu/gmp/'
regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/'
source-mirror:
type: url

View File

@@ -2,8 +2,9 @@ gmssl:
type: library
artifact:
source:
type: ghtar
repo: guanzhi/GmSSL
type: git
url: 'https://github.com/guanzhi/GmSSL.git'
rev: master
metadata:
license-files: [LICENSE]
license: Apache-2.0

View File

@@ -3,7 +3,7 @@ idn2:
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/libidn/'
url: 'https://ftpmirror.gnu.org/gnu/libidn/'
regex: '/href="(?<file>libidn2-(?<version>[^"]+)\.tar\.gz)"/'
metadata:
license-files: [COPYING.LESSERv3]

View File

@@ -3,7 +3,7 @@ libiconv:
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/libiconv/'
url: 'https://ftpmirror.gnu.org/gnu/libiconv/'
regex: '/href="(?<file>libiconv-(?<version>[^"]+)\.tar\.gz)"/'
metadata:
license-files: [COPYING.LIB]

View File

@@ -3,7 +3,7 @@ libunistring:
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/gnu/libunistring/'
url: 'https://ftpmirror.gnu.org/gnu/libunistring/'
regex: '/href="(?<file>libunistring-(?<version>[^"]+)\.tar\.gz)"/'
metadata:
license-files: [COPYING.LIB]

View File

@@ -3,7 +3,7 @@ readline:
artifact:
source:
type: filelist
url: 'https://ftp.gnu.org/pub/gnu/readline/'
url: 'https://ftpmirror.gnu.org/gnu/readline/'
regex: '/href="(?<file>readline-(?<version>[^"]+)\.tar\.gz)"/'
metadata:
license-files: [COPYING]

View File

@@ -47,7 +47,7 @@ Build your CLI with [symfony/console](https://symfony.com/doc/current/components
## Community
Join our [Discord server](https://discord.gg/nrSRbpMJ) to ask questions, share your builds, and connect with other StaticPHP users.
Join our [Discord server](https://discord.gg/xf6Rd4pEAk) to ask questions, share your builds, and connect with other StaticPHP users.
## Next steps

View File

@@ -55,7 +55,7 @@ StaticPHP 支持将 FrankenPHP 连同所需扩展一起静态编译,
## 社区
加入我们的 [Discord 服务器](https://discord.gg/nrSRbpMJ),提问、分享构建成果,与其他 StaticPHP 用户交流。
加入我们的 [Discord 服务器](https://discord.gg/xf6Rd4pEAk),提问、分享构建成果,与其他 StaticPHP 用户交流。
## 接下来

View File

@@ -17,4 +17,11 @@ class gmssl
FileSystem::replaceFileStr($target_path . '/src/hex.c', 'unsigned char *OPENSSL_hexstr2buf(const char *str, size_t *len)', 'unsigned char *GMSSL_hexstr2buf(const char *str, size_t *len)');
FileSystem::replaceFileStr($target_path . '/src/hex.c', 'OPENSSL_hexchar2int', 'GMSSL_hexchar2int');
}
#[AfterSourceExtract('ext-gmssl')]
#[PatchDescription('Rename pbkdf2_hmac_sm3_genkey() to sm3_pbkdf2() (renamed upstream in GmSSL 3.2+; identical signature)')]
public function patchExt(string $target_path): void
{
FileSystem::replaceFileStr($target_path . '/gmssl.c', 'pbkdf2_hmac_sm3_genkey(', 'sm3_pbkdf2(');
}
}

View File

@@ -7,6 +7,7 @@ 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;
@@ -18,9 +19,13 @@ 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',
@@ -34,7 +39,7 @@ class llvm_compiler_rt
$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());
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);
}
@@ -70,7 +75,7 @@ class llvm_compiler_rt
{
$sourceDir ??= SOURCE_PATH . '/llvm-compiler-rt';
$triple ??= SystemTarget::getCanonicalTriple();
$libDir = PKG_ROOT_PATH . '/zig/lib/' . $triple;
$libDir = zig::path() . '/lib/' . $triple;
if ($this->isBuilt($libDir)) {
return;
}
@@ -87,18 +92,26 @@ class llvm_compiler_rt
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");
&& file_exists("{$libDir}/clang_rt.crtend.o")
&& (self::cpuModelArch(basename($libDir)) === null || file_exists("{$libDir}/libclang_rt.cpu_model.a"));
}
private function detectZigLlvmVersion(): ?string
{
[$rc, $out] = shell()->execWithResult('zig cc --version', false);
if (!zig::isInstalled()) {
return null;
}
[$rc, $out] = shell()->execWithResult(escapeshellarg(zig::binary()) . ' cc --version', false);
if ($rc !== 0) {
return null;
}
@@ -138,4 +151,43 @@ class llvm_compiler_rt
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,
};
}
}

View File

@@ -7,6 +7,7 @@ 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;
@@ -17,6 +18,8 @@ use StaticPHP\Package\PackageBuilder;
class llvm_tools
{
use GitHubTokenSetupTrait;
public const array TOOLS = ['llvm-objcopy', 'llvm-strip', 'llvm-profdata'];
#[CustomBinary('llvm-tools', [
@@ -32,7 +35,7 @@ class llvm_tools
$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());
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);
}
@@ -99,8 +102,8 @@ class llvm_tools
'-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_C_COMPILER=' . zig::binary('zig-cc'),
'-DCMAKE_CXX_COMPILER=' . zig::binary('zig-c++'),
'-DCMAKE_INSTALL_PREFIX=' . $installDir,
]));
$jobs = ApplicationContext::get(PackageBuilder::class)->concurrency;
@@ -134,11 +137,10 @@ class llvm_tools
private function detectLlvmVersion(): ?string
{
$zig = PKG_ROOT_PATH . '/zig/zig';
if (!is_file($zig)) {
if (!zig::isInstalled()) {
return null;
}
[$rc, $out] = shell()->execWithResult(escapeshellarg($zig) . ' cc --version', false);
[$rc, $out] = shell()->execWithResult(escapeshellarg(zig::binary()) . ' cc --version', false);
if ($rc !== 0) {
return null;
}

View File

@@ -15,6 +15,23 @@ use StaticPHP\Runtime\SystemTarget;
class zig
{
/** Directory zig extracts into. */
public static function path(): string
{
return PKG_ROOT_PATH . '/zig';
}
/** Path to a binary inside the zig install dir (zig, zig-cc, zig-c++, zig-ar, …). */
public static function binary(string $name = 'zig'): string
{
return self::path() . '/' . $name;
}
public static function isInstalled(): bool
{
return is_file(self::binary());
}
#[CustomBinary('zig', [
'linux-x86_64',
'linux-aarch64',

View File

@@ -4,29 +4,14 @@ declare(strict_types=1);
namespace Package\Extension;
use Package\Target\php;
use StaticPHP\Attribute\Package\BeforeStage;
use StaticPHP\Attribute\Package\CustomPhpConfigureArg;
use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Util\FileSystem;
#[Extension('clickhouse')]
class clickhouse extends PhpExtensionPackage
{
#[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-clickhouse')]
#[PatchDescription('Replace THIS_DIR=`dirname $0` with PHP_EXT_SRCDIR() in config.m4 so include paths resolve to the ext source dir during PHP main configure (dirname $0 returns "." when run from php-src root).')]
public function patchBeforeBuildconfUnix(): void
{
FileSystem::replaceFileRegex(
"{$this->getSourceDir()}/config.m4",
'/^(\s*)THIS_DIR=.*/m',
'$1THIS_DIR=PHP_EXT_SRCDIR()',
);
}
#[CustomPhpConfigureArg('Darwin')]
#[CustomPhpConfigureArg('Linux')]
public function getUnixConfigureArg(bool $shared, PackageInstaller $installer): string

View File

@@ -35,12 +35,37 @@ class event extends PhpExtensionPackage
#[BeforeStage('php', [php::class, 'makeForUnix'], 'ext-event')]
#[PatchDescription('Prevent event extension compile error on macOS')]
#[PatchDescription('Patch libevent http_connection.c to use a const peer address')]
public function patchBeforeMake(PackageInstaller $installer): void
{
$php_src = $installer->getTargetPackage('php')->getSourceDir();
// Prevent event extension compile error on macOS
if (SystemTarget::getTargetOS() === 'Darwin') {
$php_src = $installer->getTargetPackage('php')->getSourceDir();
FileSystem::replaceFileRegex("{$php_src}/main/php_config.h", '/^#define HAVE_OPENPTY 1$/m', '');
}
$this->patchLibeventConstPeer("{$php_src}/ext/event");
}
#[BeforeStage('ext-event', [PhpExtensionPackage::class, 'makeForUnix'])]
#[PatchDescription('Patch libevent http_connection.c to use a const peer address')]
public function patchBeforeSharedMake(PhpExtensionPackage $pkg): void
{
$this->patchLibeventConstPeer($pkg->getSourceDir());
}
private function patchLibeventConstPeer(string $event_source_dir): bool
{
$file = "{$event_source_dir}/php8/classes/http_connection.c";
if (!is_file($file)) {
return false;
}
// libevent >= 2.2 changed evhttp_connection_get_peer()'s
// address arg to `const char **`
$header = $this->getBuilder()->getBuildRootPath() . '/include/event2/http.h';
$isConst = is_file($header)
&& preg_match('/evhttp_connection_get_peer\s*\([^;]*\bconst\s+char\s*\*\*\s*address/s', file_get_contents($header)) === 1;
return (bool) ($isConst
? FileSystem::replaceFileRegex($file, '/^\tchar \*address;$/m', "\tconst char *address;")
: FileSystem::replaceFileRegex($file, '/^\tconst char \*address;$/m', "\tchar *address;"));
}
}

View File

@@ -16,6 +16,14 @@ use StaticPHP\Util\FileSystem;
#[Extension('mongodb')]
class mongodb extends PhpExtensionPackage
{
#[BeforeStage('php', [php::class, 'configureForUnix'], 'ext-mongodb')]
#[PatchDescription('Export PHP_VERSION_ID so mongo-php-driver >= 2.3.3 skips its php-config lookup (in-tree builds have no php-config).')]
public function exportPhpVersionIdForUnix(): void
{
$id = php::getPHPVersionID();
f_putenv("PHP_VERSION_ID={$id}");
}
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-mongodb')]
#[PatchDescription('Add /utf-8 flag to CFLAGS_MONGODB for Windows build to fix compilation error on non-English Windows.')]
public function patchBeforeBuild(): void

View File

@@ -27,7 +27,7 @@ class password_argon2 extends PhpExtensionPackage
#[CustomPhpConfigureArg('Darwin')]
public function getConfigureArg(PackageInstaller $installer, PackageBuilder $builder): string
{
if ($installer->getPhpExtensionPackage('openssl')?->isBuildStatic() || $this->isBuildShared()) {
if ($installer->getPhpExtensionPackage('openssl')?->isBuildStatic()) {
if (php::getPHPVersionID() >= 80500 || (php::getPHPVersionID() >= 80400 && !$builder->getOption('enable-zts'))) {
return '--without-password-argon2'; // use --with-openssl-argon2 in openssl extension instead
}

View File

@@ -42,6 +42,9 @@ class imagemagick
->addConfigureArgs(
'--disable-openmp',
'--without-x',
// implicit --with-gcc-arch
// bleeds host cpu features into built binaries
'--without-gcc-arch',
);
// special: linux-static target needs `-static`

View File

@@ -9,8 +9,10 @@ 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
@@ -39,9 +41,23 @@ 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=generic')
->addConfigureArgs(
"-DAOM_TARGET_CPU={$targetCpu}",
'-DCONFIG_RUNTIME_CPU_DETECT=1',
'-DENABLE_EXAMPLES=OFF',
'-DENABLE_TESTS=OFF',
'-DENABLE_TOOLS=OFF',
'-DENABLE_DOCS=OFF',
)
->build();
f_putenv("SPC_COMPILER_EXTRA={$extra}");
$this->patchPkgconfPrefix(['aom.pc']);

View File

@@ -8,6 +8,7 @@ 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
@@ -28,7 +29,7 @@ class libffi extends LibraryPackage
#[BuildFor('Darwin')]
public function buildDarwin(): void
{
$arch = getenv('SPC_ARCH');
$arch = SystemTarget::getTargetArch();
UnixAutoconfExecutor::create($this)
->configure(
"--host={$arch}-apple-darwin",

View File

@@ -24,6 +24,17 @@ 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')]

View File

@@ -9,6 +9,7 @@ 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')]
@@ -24,7 +25,7 @@ class libpng
];
// Enable architecture-specific optimizations
match (getenv('SPC_ARCH')) {
match (SystemTarget::getTargetArch()) {
'x86_64' => $args[] = '--enable-intel-sse',
'aarch64' => $args[] = '--enable-arm-neon',
default => null,

View File

@@ -65,6 +65,7 @@ class ncurses
'--without-tests',
'--without-dlsym',
'--without-debug',
'--disable-stripping',
'--enable-symlinks',
"--with-terminfo-dirs={$terminfo_dirs}",
"--bindir={$package->getBinDir()}",

View File

@@ -76,7 +76,7 @@ class openssl
public function buildForDarwin(LibraryPackage $pkg): void
{
$zlib_libs = $pkg->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles();
$arch = getenv('SPC_ARCH');
$arch = SystemTarget::getTargetArch();
shell()->cd($pkg->getSourceDir())->initializeEnv($pkg)
->exec(
@@ -95,12 +95,7 @@ class openssl
#[BuildFor('Linux')]
public function build(LibraryPackage $lib): void
{
$arch = getenv('SPC_ARCH');
$env = "CC='" . getenv('CC') . ' -idirafter ' . BUILD_INCLUDE_PATH .
' -idirafter /usr/include/ ' .
' -idirafter /usr/include/' . getenv('SPC_ARCH') . '-linux-gnu/ ' .
"' ";
$arch = SystemTarget::getTargetArch();
$ex_lib = trim($lib->getInstaller()->getLibraryPackage('zlib')->getStaticLibFiles()) . ' -ldl -pthread';
$zlib_extra =
@@ -119,7 +114,7 @@ class openssl
shell()->cd($lib->getSourceDir())->initializeEnv($lib)
->exec(
"{$env} ./Configure no-shared zlib " .
'./Configure no-shared zlib ' .
"--prefix={$lib->getBuildRootPath()} " .
"--libdir={$lib->getLibDir()} " .
"--openssldir={$openssl_dir} " .

View File

@@ -93,6 +93,9 @@ class curl
if (str_contains(FileSystem::readFile($pc_path), '-lgssapi_krb5')) {
FileSystem::replaceFileRegex($pc_path, '/-lcom_err$/m', '-lcom_err -lkrb5support');
}
// FindThreads can put '-lpthread' into INTERFACE_LINK_LIBRARIES; curl's pc generator
// prepends '-l' to each entry, producing '-l-lpthread'. Strip the extra '-l'.
FileSystem::replaceFileRegex($pc_path, '/-l(-l\S+)/', '$1');
shell()->cd("{$lib->getLibDir()}/cmake/CURL/")
->exec("sed -ie 's|\"/lib/libcurl.a\"|\"{$lib->getLibDir()}/libcurl.a\"|g' CURLTargets-release.cmake");

View File

@@ -143,6 +143,7 @@ class php extends TargetPackage
$package->addBuildOption('disable-opcache-jit', null, null, 'Disable opcache jit');
$package->addBuildOption('with-config-file-path', null, InputOption::VALUE_REQUIRED, 'Set the path in which to look for php.ini', PHP_OS_FAMILY === 'Windows' ? null : '/usr/local/etc/php');
$package->addBuildOption('with-config-file-scan-dir', null, InputOption::VALUE_REQUIRED, 'Set the directory to scan for .ini files after reading php.ini', PHP_OS_FAMILY === 'Windows' ? null : '/usr/local/etc/php/conf.d');
$package->addBuildOption('with-sysconfdir', null, InputOption::VALUE_REQUIRED, 'Set the sysconfdir (e.g. /etc/php-zts) used by configure (not available on Windows)');
$package->addBuildOption('with-hardcoded-ini', 'I', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Patch PHP source code, inject hardcoded INI');
$package->addBuildOption('enable-zts', null, null, 'Enable thread safe support');
$package->addBuildOption('no-smoke-test', null, InputOption::VALUE_OPTIONAL, 'Disable smoke test for specific SAPIs, or all if no value provided', false);
@@ -336,7 +337,7 @@ class php extends TargetPackage
logger()->info("Adding hardcoded INI [{$source_name} = {$ini_value}]");
}
if (!empty($custom_ini)) {
ApplicationContext::invoke([SourcePatcher::class, 'patchHardcodedINI'], [$package->getSourceDir(), $custom_ini]);
ApplicationContext::invoke([SourcePatcher::class, 'patchHardcodedINI'], ['php_source_dir' => $package->getSourceDir(), 'ini' => $custom_ini]);
}
// Patch StaticPHP version

View File

@@ -73,10 +73,7 @@ trait frankenphp
$staticFlags = '';
}
$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);
$config = new SPCConfigUtil()->config(['frankenphp']);
$cflags = "{$package->getLibExtraCFlags()} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . " -DFRANKENPHP_VERSION={$frankenphp_version}";
$libs = $config['libs'];

View File

@@ -42,6 +42,15 @@ 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');
@@ -66,6 +75,7 @@ trait unix
#[BeforeStage('php', [self::class, 'configureForUnix'], 'php')]
#[PatchDescription('Patch configure to use -std=gnu17 instead of -std=gnu23 for PHP <= 8.2')]
#[PatchDescription('Strip build-time env vars from phpinfo\'s "Configure Command"')]
public function patchBeforeConfigure(TargetPackage $package): void
{
if (SystemTarget::isUnix() && self::getPHPVersionID() < 80300) {
@@ -75,6 +85,22 @@ trait unix
"for ac_arg in '' -std=gnu17",
);
}
// strip our build-time env vars from phpinfo's "Configure Command" ('|' delimiter: PHP_BUILD_PROVIDER may contain '#')
if (SystemTarget::isUnix()) {
FileSystem::replaceFileStr(
"{$package->getSourceDir()}/configure",
'for var in CFLAGS CXXFLAGS CPPFLAGS LDFLAGS EXTRA_LDFLAGS_PROGRAM LIBS CC CXX; do',
'for var in CFLAGS CXXFLAGS CPPFLAGS LDFLAGS EXTRA_LDFLAGS_PROGRAM LIBS CC CXX '
. 'PKG_CONFIG PKG_CONFIG_PATH EXTENSION_DIR OPENSSL_LIBS '
. 'PHP_BUILD_SYSTEM PHP_BUILD_PROVIDER PHP_BUILD_COMPILER PHP_BUILD_ARCH; do',
);
FileSystem::replaceFileStr(
"{$package->getSourceDir()}/configure",
'clean_configure_args=$(echo $clean_configure_args | $SED -e "s#\'$var=$val\'##")',
'clean_configure_args=$(echo $clean_configure_args | $SED -e "s|\'$var=$val\'||")',
);
}
}
#[Stage]
@@ -116,6 +142,9 @@ trait unix
if ($option = $package->getBuildOption('with-config-file-scan-dir', false)) {
$args[] = "--with-config-file-scan-dir={$option}";
}
if ($option = $package->getBuildOption('with-sysconfdir', false)) {
$args[] = "--sysconfdir={$option}";
}
// perform enable cli options
$args[] = $installer->isPackageResolved('php-cli') ? '--enable-cli' : '--disable-cli';
$args[] = $installer->isPackageResolved('php-fpm')
@@ -544,6 +573,8 @@ 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']);
}
@@ -656,7 +687,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($installer->getAvailableResolvedPackageNames());
$config = new SPCConfigUtil()->config(['php']);
$lens = "{$config['cflags']} {$config['ldflags']} {$config['libs']}";
if ($toolchain->isStatic()) {
$lens .= ' -static';
@@ -743,7 +774,7 @@ trait unix
*/
private function makeVars(PackageInstaller $installer): array
{
$config = new SPCConfigUtil(['libs_only_deps' => true])->config($installer->getAvailableResolvedPackageNames());
$config = new SPCConfigUtil(['libs_only_deps' => true])->config(['php']);
$static = ApplicationContext::get(ToolchainInterface::class)->isStatic() ? '-all-static' : '';
$pie = SystemTarget::getTargetOS() === 'Linux' ? '-pie' : '';
$lib = BUILD_LIB_PATH;

View File

@@ -27,12 +27,18 @@ 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 = [];
@@ -331,7 +337,7 @@ class Artifact
public function getSourceRoot(): string
{
if (isset($this->config['metadata']['source-root'])) {
return $this->getSourceDir() . '/' . ltrim($this->config['metadata']['source-root'], '/');
return FileSystem::convertPath($this->getSourceDir() . '/' . ltrim($this->config['metadata']['source-root'], '/'));
}
return $this->getSourceDir();
}
@@ -411,10 +417,13 @@ 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): void
public function setCustomSourceCallback(callable $callback, string $origin = 'package downloader'): void
{
$this->custom_source_callback = $callback;
$this->custom_source_callback_origin = $origin;
}
public function getCustomSourceCallback(): ?callable
@@ -422,6 +431,11 @@ class Artifact
return $this->custom_source_callback ?? null;
}
public function getCustomSourceCallbackOrigin(): ?string
{
return $this->custom_source_callback_origin;
}
/**
* Set custom source check-update callback.
*/
@@ -456,11 +470,19 @@ 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): void
public function setCustomBinaryCallback(string $target_os, callable $callback, string $origin = 'package downloader'): 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;
}
/**

View File

@@ -223,8 +223,8 @@ class ArtifactCache
public function getCacheFullPath(array $cache_info): string
{
return match ($cache_info['cache_type']) {
'archive', 'file' => DOWNLOAD_PATH . '/' . $cache_info['filename'],
'git' => DOWNLOAD_PATH . '/' . $cache_info['dirname'],
'archive', 'file' => FileSystem::convertPath(DOWNLOAD_PATH . '/' . $cache_info['filename']),
'git' => FileSystem::convertPath(DOWNLOAD_PATH . '/' . $cache_info['dirname']),
'local' => $cache_info['dirname'], // local dirname is absolute path
default => throw new SPCInternalException("Unknown cache type: {$cache_info['cache_type']}"),
};

View File

@@ -554,8 +554,8 @@ class ArtifactDownloader
$instance = null;
$call = $this->downloaders[$item['config']['type']] ?? null;
$type_display_name = match (true) {
$item['lock'] === 'source' && ($callback = $artifact->getCustomSourceCallback()) !== null => 'user defined source downloader',
$item['lock'] === 'binary' && ($callback = $artifact->getCustomBinaryCallback()) !== null => 'user defined binary downloader',
$item['lock'] === 'source' && $artifact->getCustomSourceCallback() !== null => $artifact->getCustomSourceCallbackOrigin() ?? 'source package downloader',
$item['lock'] === 'binary' && $artifact->getCustomBinaryCallback() !== null => $artifact->getCustomBinaryCallbackOrigin() ?? 'binary package downloader',
default => SPC_DOWNLOAD_TYPE_DISPLAY_NAME[$item['config']['type']] ?? $item['config']['type'],
};
$try_h = $try ? 'Try downloading' : 'Downloading';
@@ -838,21 +838,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');
}
}
}

View File

@@ -57,6 +57,16 @@ class GitHubRelease implements DownloadTypeInterface, ValidatorInterface, CheckU
if (!is_array($data)) {
throw new DownloaderException("Failed to get GitHub release API info for {$repo} from {$url}");
}
// GitHub's /releases list is ordered by publish date, so a newer-tagged prerelease
// (e.g. 2.2.x-alpha) can precede the latest stable (2.1.x-stable). /releases/latest
// returns the semantically latest stable release regardless of order; check it first.
if ($prefer_stable) {
$latest_url = str_replace('{repo}', $repo, self::API_URL) . '/latest';
$latest = json_decode(default_shell()->executeCurl($latest_url, headers: $headers, retries: $retries) ?: '', true);
if (is_array($latest) && isset($latest['assets'])) {
array_unshift($data, $latest);
}
}
foreach ($data as $release) {
if ($prefer_stable && $release['prerelease'] === true) {
continue;

View File

@@ -14,16 +14,18 @@ trait GitHubTokenSetupTrait
public static function getGitHubTokenHeadersStatic(): array
{
// GITHUB_TOKEN support
if (($token = getenv('GITHUB_TOKEN')) !== false && ($user = getenv('GITHUB_USER')) !== false) {
$token = getenv('GITHUB_TOKEN');
if ($token === false || $token === '') {
return [];
}
if (($user = getenv('GITHUB_USER')) !== false && $user !== '') {
logger()->debug("Using 'GITHUB_TOKEN' with user {$user} for authentication");
spc_add_log_filter([$user, $token]);
return ['Authorization: Basic ' . base64_encode("{$user}:{$token}")];
$encoded = base64_encode("{$user}:{$token}");
spc_add_log_filter([$user, $token, $encoded]);
return ["Authorization: Basic {$encoded}"];
}
if (($token = getenv('GITHUB_TOKEN')) !== false) {
logger()->debug("Using 'GITHUB_TOKEN' for authentication");
spc_add_log_filter($token);
return ["Authorization: Bearer {$token}"];
}
return [];
logger()->debug("Using 'GITHUB_TOKEN' for authentication");
spc_add_log_filter($token);
return ["Authorization: Bearer {$token}"];
}
}

View File

@@ -10,8 +10,10 @@ 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;
@@ -44,6 +46,10 @@ 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);

View File

@@ -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, $include_suggests);
$config = $util->config($packages);
$this->output->writeln(match (true) {
$this->getOption('includes') => $config['cflags'],

View File

@@ -5,6 +5,7 @@ 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;
@@ -27,7 +28,7 @@ class LlvmCompilerRtCheck
#[CheckItem('if llvm-compiler-rt is built for current target', level: 799)]
public function checkLlvmCompilerRt(): CheckResult
{
$libDir = PKG_ROOT_PATH . '/zig/lib/' . SystemTarget::getCanonicalTriple();
$libDir = zig::path() . '/lib/' . SystemTarget::getCanonicalTriple();
if (new llvm_compiler_rt()->isBuilt($libDir)) {
return CheckResult::ok($libDir);
}
@@ -41,7 +42,7 @@ class LlvmCompilerRtCheck
$installer->addInstallPackage('llvm-compiler-rt');
$installer->run(true);
new llvm_compiler_rt()->buildForTriple();
$libDir = PKG_ROOT_PATH . '/zig/lib/' . SystemTarget::getCanonicalTriple();
$libDir = zig::path() . '/lib/' . SystemTarget::getCanonicalTriple();
return new llvm_compiler_rt()->isBuilt($libDir);
}
}

View File

@@ -4,6 +4,7 @@ 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;
@@ -26,7 +27,7 @@ class ZigCheck
public function checkZig(): CheckResult
{
if (new PackageInstaller()->addInstallPackage('zig')->isPackageInstalled('zig')) {
return CheckResult::ok(PKG_ROOT_PATH . '/zig/zig');
return CheckResult::ok(zig::binary());
}
return CheckResult::fail('zig is not installed', 'install-zig');
}

View File

@@ -155,6 +155,7 @@ class PackageInstaller
}
$this->reconcilePhpSrcVersion();
$this->emitPreInstallEvents();
if ($this->interactive && !$disable_delay_msg) {
// show install or build options in terminal with beautiful output
@@ -361,6 +362,15 @@ 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) {

View File

@@ -286,7 +286,7 @@ class PhpExtensionPackage extends Package
}
return [
'CFLAGS' => $config['cflags'],
'CXXFLAGS' => $config['cflags'],
'CXXFLAGS' => $config['cxxflags'],
'LDFLAGS' => $ldflags,
'LIBS' => clean_spaces("{$preStatic} {$staticLibs} {$postStatic} {$sharedLibs}"),
'LD_LIBRARY_PATH' => BUILD_LIB_PATH,

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace StaticPHP\Runtime;
use StaticPHP\Toolchain\ZigToolchain;
use StaticPHP\Util\System\LinuxUtil;
/**
@@ -16,7 +17,7 @@ class SystemTarget
*/
public static function getLibc(): ?string
{
if ($target = getenv('SPC_TARGET')) {
if ($target = self::target()) {
if (str_contains($target, '-gnu')) {
return 'glibc';
}
@@ -57,7 +58,7 @@ class SystemTarget
public static function getLibcVersion(): ?string
{
if (PHP_OS_FAMILY === 'Linux') {
$target = (string) getenv('SPC_TARGET');
$target = self::target();
if (str_contains($target, '-gnu.2.')) {
return preg_match('/-gnu\.(2\.\d+)/', $target, $matches) ? $matches[1] : null;
}
@@ -75,7 +76,7 @@ class SystemTarget
*/
public static function getTargetOS(): string
{
$target = (string) getenv('SPC_TARGET');
$target = self::target();
return match (true) {
str_contains($target, '-linux') => 'Linux',
str_contains($target, '-macos') => 'Darwin',
@@ -91,7 +92,7 @@ class SystemTarget
*/
public static function getTargetArch(): string
{
$target = (string) getenv('SPC_TARGET');
$target = self::target();
return match (true) {
str_contains($target, 'x86_64') || str_contains($target, 'amd64') => 'x86_64',
str_contains($target, 'aarch64') || str_contains($target, 'arm64') => 'aarch64',
@@ -136,7 +137,7 @@ class SystemTarget
*/
public static function getCanonicalTriple(): string
{
$target = (string) getenv('SPC_TARGET');
$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] ?? '';
@@ -161,7 +162,7 @@ class SystemTarget
*/
public static function getAutoconfHostTriple(): ?string
{
$target = (string) getenv('SPC_TARGET');
$target = self::target();
if ($target === '' || str_contains($target, 'native')) {
return null;
}
@@ -183,4 +184,14 @@ class SystemTarget
}
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');
}
}

View File

@@ -5,6 +5,7 @@ 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;
@@ -36,7 +37,16 @@ class ZigToolchain implements UnixToolchainInterface
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
// 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);
}
}
$cflags = getenv('SPC_DEFAULT_CFLAGS') ?: '';
$cxxflags = getenv('SPC_DEFAULT_CXXFLAGS') ?: '';
$extraCflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') ?: '';
@@ -47,7 +57,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=' . PKG_ROOT_PATH . '/zig/lib/' . SystemTarget::getCanonicalTriple());
GlobalEnvManager::putenv('SPC_COMPILER_RT_DIR=' . zig::path() . '/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')) {
@@ -65,6 +75,16 @@ class ZigToolchain implements UnixToolchainInterface
$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}");
// An __attribute__((ifunc)) in LTO bitcode crashes zig 0.16's lld (LLVM 21)
// during thin-link symbol resolution
$all_flags = $cflags . ' ' . (getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') ?: '') . ' ' . (getenv('SPC_DEFAULT_LD_FLAGS') ?: '');
if (str_contains($all_flags, '-flto')) {
$extra_vars = getenv('SPC_EXTRA_PHP_VARS') ?: '';
if (!str_contains($extra_vars, 'ax_cv_have_func_attribute_ifunc')) {
GlobalEnvManager::putenv("SPC_EXTRA_PHP_VARS={$extra_vars} ax_cv_have_func_attribute_ifunc=no");
}
}
$this->ensureCompilerRt();
}
@@ -103,9 +123,12 @@ class ZigToolchain implements UnixToolchainInterface
private function ensureCompilerRt(): void
{
if (!zig::isInstalled()) {
return;
}
$rt = new llvm_compiler_rt();
$triple = SystemTarget::getCanonicalTriple();
$libDir = PKG_ROOT_PATH . '/zig/lib/' . $triple;
$libDir = zig::path() . '/lib/' . $triple;
if ($rt->isBuilt($libDir)) {
return;
}
@@ -145,6 +168,6 @@ class ZigToolchain implements UnixToolchainInterface
private function getPath(): string
{
return PKG_ROOT_PATH . '/zig';
return zig::path();
}
}

View File

@@ -123,6 +123,24 @@ 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.

View File

@@ -10,6 +10,7 @@ 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
@@ -34,86 +35,39 @@ class SPCConfigUtil
$this->absolute_libs = $options['absolute_libs'] ?? false;
}
public function config(array $packages = [], bool $include_suggests = false): array
public function config(array $packages = []): array
{
// if have php, make php as all extension's dependency
// 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 (!$this->no_php) {
$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(PhpExtensionPackage::class) as $ext) {
if ($ext->isBuildStatic()) {
$php_extras[] = $ext->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";
foreach ($installer->getResolvedPackages(TargetPackage::class) as $target) {
if ($target->isVirtualTarget()) {
$php_extras[] = $target->getName();
}
}
}
$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);
$sorted = [];
$visited = [];
foreach ($packages as $pkg) {
self::visitConfigDeps(
is_string($pkg) ? $pkg : $pkg->getName(),
$resolved_set,
$php_extras,
$visited,
$sorted,
);
}
return [
'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags),
'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags),
'libs' => clean_spaces($allLibs),
];
return $this->configWithResolvedPackages($sorted);
}
/**
@@ -127,6 +81,7 @@ class SPCConfigUtil
* @param array|PhpExtensionPackage $extension_packages Extension instance or list
* @return array{
* cflags: string,
* cxxflags: string,
* ldflags: string,
* libs: string
* }
@@ -150,6 +105,7 @@ class SPCConfigUtil
* @param array|LibraryPackage $lib Library instance or list
* @return array{
* cflags: string,
* cxxflags: string,
* ldflags: string,
* libs: string
* }
@@ -180,6 +136,7 @@ class SPCConfigUtil
* @param string[] $resolved_packages The full resolved package list
* @return array{
* cflags: string,
* cxxflags: string,
* ldflags: string,
* libs: string
* }
@@ -192,6 +149,7 @@ class SPCConfigUtil
if (empty($sub_deps)) {
return [
'cflags' => '',
'cxxflags' => '',
'ldflags' => '',
'libs' => '',
];
@@ -212,11 +170,12 @@ class SPCConfigUtil
}
/**
* Get configuration using already-resolved packages (skip dependency resolution).
* Build cflags/cxxflags/ldflags/libs for an already-resolved package set (skip dependency resolution).
*
* @param string[] $resolved_packages Already resolved package names in build order
* @param string[] $resolved_packages Resolved package names in build order
* @return array{
* cflags: string,
* cxxflags: string,
* ldflags: string,
* libs: string
* }
@@ -224,9 +183,14 @@ class SPCConfigUtil
public function configWithResolvedPackages(array $resolved_packages): array
{
$ldflags = $this->getLdflagsString();
$cflags = $this->getIncludesString($resolved_packages);
$includes = $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}";
@@ -262,15 +226,25 @@ class SPCConfigUtil
$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),
'cflags' => $cflags,
'cxxflags' => $cxxflags,
'ldflags' => $ldflags,
'libs' => clean_spaces(getenv('LIBS') . ' ' . $libs),
];
}
// embed
if (!$this->no_php) {
$libs = "-lphp {$libs} -lc";
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";
}
}
$allLibs = getenv('LIBS') . ' ' . $libs;
@@ -281,8 +255,9 @@ class SPCConfigUtil
}
return [
'cflags' => clean_spaces(getenv('CFLAGS') . ' ' . $cflags),
'ldflags' => clean_spaces(getenv('LDFLAGS') . ' ' . $ldflags),
'cflags' => $cflags,
'cxxflags' => $cxxflags,
'ldflags' => $ldflags,
'libs' => clean_spaces($allLibs),
];
}
@@ -301,6 +276,32 @@ 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.

View File

@@ -168,6 +168,9 @@ 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',
@@ -193,6 +196,12 @@ 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');
}

View File

@@ -54,6 +54,7 @@ class WindowsUtil
}
$args = [
'-latest',
'-products', '*',
'-format', 'json',
'-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
];

View File

@@ -33,6 +33,23 @@ ConsoleLogger::$date_format = 'H:i:s';
ConsoleLogger::$format = '[%date% %level_long%] %body%';
$ob_logger = new ConsoleLogger(LogLevel::WARNING);
$ob_logger->addLogCallback(function ($level, &$output, &$message, &$context, bool $shouldLog) {
global $spc_log_filters;
if (!is_array($spc_log_filters)) {
$spc_log_filters = [];
}
// filter message and context
$output = str_replace($spc_log_filters, '***', $output);
$message = str_replace($spc_log_filters, '***', $message);
$context = array_map(function ($item) use ($spc_log_filters) {
if (is_string($item)) {
return str_replace($spc_log_filters, '***', $item);
}
return $item;
}, $context);
return true;
});
// setup log file
if (filter_var(getenv('SPC_ENABLE_LOG_FILE'), FILTER_VALIDATE_BOOLEAN)) {
// init spc log files

View File

@@ -72,7 +72,6 @@ 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);

View File

@@ -27,12 +27,14 @@ while [[ $# -gt 0 ]]; do
-march=*|-mcpu=*)
OPT_NAME="${1%%=*}"
OPT_VALUE="${1#*=}"
# Skip armv8- flags entirely as Zig doesn't support them
if [[ "$OPT_VALUE" == armv8-* ]]; then
shift
continue
# 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]}"
fi
# replace -march=x86-64 with -march=x86_64
# zig uses snake_case in CPU/feature names (x86-64 -> x86_64).
OPT_VALUE="${OPT_VALUE//-/_}"
PARSED_ARGS+=("${OPT_NAME}=${OPT_VALUE}")
shift
@@ -66,10 +68,13 @@ done
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")
PARSED_ARGS+=("-x" "none" "$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")
PARSED_ARGS+=("-x" "none" "$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+=("-x" "none" "$RT_DIR/libclang_rt.cpu_model.a")
fi
[[ -n "$SPC_TARGET" ]] && TARGET="-target $SPC_TARGET" || TARGET=""

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace Tests;
use PHPUnit\Framework\TestCase;
use Psr\Log\LogLevel;
use ZM\Logger\ConsoleLogger;
/**
* @internal
*/
class GlobalsFunctionsTest extends TestCase
{
protected function setUp(): void
{
$GLOBALS['spc_log_filters'] = null;
}
protected function tearDown(): void
{
$GLOBALS['spc_log_filters'] = null;
}
public function testAddLogFilterDeduplicates(): void
{
spc_add_log_filter('secret-value');
spc_add_log_filter('secret-value');
spc_add_log_filter(['secret-value', 'other']);
$this->assertSame(['secret-value', 'other'], $GLOBALS['spc_log_filters']);
}
public function testWriteLogMasksRegisteredValues(): void
{
spc_add_log_filter(['octocat', 'ghp_abcdef1234567890']);
$stream = fopen('php://memory', 'r+');
spc_write_log($stream, 'user=octocat token=ghp_abcdef1234567890');
rewind($stream);
$written = stream_get_contents($stream);
fclose($stream);
$this->assertSame('user=*** token=***', $written);
}
public function testLoggerCallbackMasksOutput(): void
{
$token = 'ghp_abcdef1234567890';
spc_add_log_filter($token);
$stream = fopen('php://memory', 'r+');
$logger = new ConsoleLogger(LogLevel::DEBUG, $stream, false);
$logger->addLogCallback(function ($level, &$output, &$message, &$context, bool $shouldLog) {
global $spc_log_filters;
if (!is_array($spc_log_filters)) {
$spc_log_filters = [];
}
$output = str_replace($spc_log_filters, '***', $output);
$message = str_replace($spc_log_filters, '***', $message);
$context = array_map(function ($item) use ($spc_log_filters) {
if (is_string($item)) {
return str_replace($spc_log_filters, '***', $item);
}
return $item;
}, $context);
return true;
});
$logger->debug("[PASSTHRU] curl -H\"Authorization: Bearer {$token}\" https://api.github.com/x");
rewind($stream);
$written = stream_get_contents($stream);
fclose($stream);
$this->assertStringNotContainsString($token, $written);
$this->assertStringContainsString('***', $written);
}
public function testGitHubTokenTraitRegistersEncodedBasicAuthBlob(): void
{
$user = 'octocat';
$token = 'ghp_abcdef1234567890';
$original_token = getenv('GITHUB_TOKEN');
$original_user = getenv('GITHUB_USER');
putenv("GITHUB_TOKEN={$token}");
putenv("GITHUB_USER={$user}");
try {
$headers = \StaticPHP\Artifact\Downloader\Type\GitHubRelease::getGitHubTokenHeadersStatic();
$encoded = base64_encode("{$user}:{$token}");
$this->assertSame(["Authorization: Basic {$encoded}"], $headers);
$this->assertContains($user, $GLOBALS['spc_log_filters']);
$this->assertContains($token, $GLOBALS['spc_log_filters']);
$this->assertContains($encoded, $GLOBALS['spc_log_filters']);
} finally {
$original_token === false ? putenv('GITHUB_TOKEN') : putenv("GITHUB_TOKEN={$original_token}");
$original_user === false ? putenv('GITHUB_USER') : putenv("GITHUB_USER={$original_user}");
}
}
}

View File

@@ -254,11 +254,16 @@ class ArtifactTest extends TestCase
ApplicationContext::initialize();
ApplicationContext::set(ArtifactCache::class, $cache);
// Use a platform-appropriate absolute path: a Unix-style /tmp/... isn't
// absolute on Windows (no drive letter), so the test would otherwise
// fall through into the SOURCE_PATH-prefixed branch on Windows.
$extract = DIRECTORY_SEPARATOR === '\\' ? 'C:\tmp\my-pkg-extract' : '/tmp/my-pkg-extract';
$artifact = new Artifact('my-pkg', [
'source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz', 'extract' => '/tmp/my-pkg-extract'],
'source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz', 'extract' => $extract],
]);
$expected = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, '/tmp/my-pkg-extract');
$expected = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $extract);
$this->assertSame($expected, $artifact->getSourceDir());
}
@@ -703,11 +708,7 @@ class ArtifactTest extends TestCase
'Windows' => 'windows',
default => 'linux',
};
$arch = php_uname('m');
if ($arch === 'arm64') {
$arch = 'aarch64';
}
return "{$os}-{$arch}";
return "{$os}-" . arch2gnu(php_uname('m'));
}
private function injectArtifactConfig(string $name, array $config): void