From bdfd3eb269764490733e82b69ec2051b428aee3e Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 24 May 2026 20:41:18 +0700 Subject: [PATCH 01/14] also revert #1122 --- src/Package/Extension/xlswriter.php | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/Package/Extension/xlswriter.php b/src/Package/Extension/xlswriter.php index f4d15530..f4202376 100644 --- a/src/Package/Extension/xlswriter.php +++ b/src/Package/Extension/xlswriter.php @@ -11,7 +11,6 @@ use StaticPHP\Attribute\Package\Extension; use StaticPHP\Attribute\PatchDescription; use StaticPHP\Package\PackageInstaller; use StaticPHP\Package\PhpExtensionPackage; -use StaticPHP\Util\GlobalEnvManager; use StaticPHP\Util\SourcePatcher; #[Extension('xlswriter')] @@ -21,20 +20,14 @@ class xlswriter extends PhpExtensionPackage #[CustomPhpConfigureArg('Linux')] public function getUnixConfigureArg(bool $shared, PackageInstaller $installer): string { - $arg = '--with-xlswriter --enable-reader'; + $shared = $shared ? '=shared' : ''; + $arg = "--with-xlswriter{$shared} --enable-reader"; if ($installer->getLibraryPackage('openssl')) { $arg .= ' --with-openssl=' . $installer->getLibraryPackage('openssl')->getBuildRootPath(); } return $arg; } - #[BeforeStage('php', [php::class, 'makeForUnix'], 'ext-xlswriter')] - #[PatchDescription('Fix Unix build: add -std=gnu17 to CFLAGS to fix build errors on older GCC versions')] - public function patchBeforeUnixMake(): void - { - GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . ' -std=gnu17'); - } - #[BeforeStage('php', [php::class, 'makeForWindows'], 'ext-xlswriter')] #[PatchDescription('Fix Windows build: apply win32 patch and add UTF-8 BOM to theme.c')] public function patchBeforeMakeForWindows(): void @@ -47,11 +40,4 @@ class xlswriter extends PhpExtensionPackage file_put_contents($this->getSourceDir() . '/library/libxlsxwriter/src/theme.c', $bom . $content); } } - - public function getSharedExtensionEnv(): array - { - $parent = parent::getSharedExtensionEnv(); - $parent['CFLAGS'] .= ' -std=gnu17'; - return $parent; - } } From 1a779be0280d986ae3d514dab87228fe5f53bb2d Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 24 May 2026 20:54:21 +0700 Subject: [PATCH 02/14] imagemagick: --without-gcc-arch ax_gcc_archflag has no Zen cpuid pattern and falls back to -mtune=amdfam10, which under LLVM+LTO emits SSE4a extrq and SIGILLs on Intel hosts. Disable the implicit --with-gcc-arch so host CPU features do not bleed into the built binaries. --- src/Package/Library/imagemagick.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Package/Library/imagemagick.php b/src/Package/Library/imagemagick.php index fd5de9ed..d423163a 100644 --- a/src/Package/Library/imagemagick.php +++ b/src/Package/Library/imagemagick.php @@ -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` From 0807e9e253f585e72432f77ed5b2b7aba7a86eaf Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 24 May 2026 20:54:44 +0700 Subject: [PATCH 03/14] watcher: drop ldflags from compile-only invocation The shell invocation runs `$CXX -c` to compile watcher-c.cpp to a .o, which never links. Passing linker flags to a compile-only step is either ignored or, with some flags, an error. Drop them. --- src/Package/Library/watcher.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Package/Library/watcher.php b/src/Package/Library/watcher.php index 56f93d93..34f8eb9a 100644 --- a/src/Package/Library/watcher.php +++ b/src/Package/Library/watcher.php @@ -20,9 +20,8 @@ class watcher extends LibraryPackage if (stripos($cflags, '-fpic') === false) { $cflags .= ' -fPIC'; } - $ldflags = $this->getLibExtraLdFlags() ? ' ' . $this->getLibExtraLdFlags() : ''; shell()->cd("{$this->getSourceDir()}/watcher-c") - ->exec(getenv('CXX') . " -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra {$cflags}{$ldflags}") + ->exec(getenv('CXX') . " -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra {$cflags}") ->exec(getenv('AR') . ' rcs libwatcher-c.a libwatcher-c.o'); copy("{$this->getSourceDir()}/watcher-c/libwatcher-c.a", "{$this->getLibDir()}/libwatcher-c.a"); From 51725802941757fdad9998e4182450ee0de9e5a2 Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 24 May 2026 20:55:35 +0700 Subject: [PATCH 04/14] toolchain: add SPC_DEFAULT_RANLIB and pin cmake AR/RANLIB ClangBrew, ClangNative and GccNative now export SPC_DEFAULT_RANLIB alongside SPC_DEFAULT_AR. UnixCMakeExecutor honours both when generating the Linux toolchain file, so cmake uses the toolchain's ar/ranlib (e.g. zig-ar/zig-ranlib for archives that the system ranlib does not understand) instead of /usr/bin/ranlib. --- src/StaticPHP/Runtime/Executor/UnixCMakeExecutor.php | 7 +++++-- src/StaticPHP/Toolchain/ClangBrewToolchain.php | 1 + src/StaticPHP/Toolchain/ClangNativeToolchain.php | 1 + src/StaticPHP/Toolchain/GccNativeToolchain.php | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/StaticPHP/Runtime/Executor/UnixCMakeExecutor.php b/src/StaticPHP/Runtime/Executor/UnixCMakeExecutor.php index 82fa468a..9fc00840 100644 --- a/src/StaticPHP/Runtime/Executor/UnixCMakeExecutor.php +++ b/src/StaticPHP/Runtime/Executor/UnixCMakeExecutor.php @@ -302,9 +302,12 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES "{$include}") set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES "{$include}") CMAKE; - // Whoops, linux may need CMAKE_AR sometimes + // pin AR/RANLIB so cmake uses zig-ar/zig-ranlib instead of system /usr/bin/ranlib (zig archives need it) if (PHP_OS_FAMILY === 'Linux') { - $toolchain .= "\nSET(CMAKE_AR \"ar\")"; + $ar = getenv('SPC_DEFAULT_AR') ?: getenv('AR') ?: 'ar'; + $ranlib = getenv('SPC_DEFAULT_RANLIB') ?: (getenv('RANLIB') ?: 'ranlib'); + $toolchain .= "\nSET(CMAKE_AR \"{$ar}\")"; + $toolchain .= "\nSET(CMAKE_RANLIB \"{$ranlib}\")"; } FileSystem::writeFile(SOURCE_PATH . '/toolchain.cmake', $toolchain); return $created = realpath(SOURCE_PATH . '/toolchain.cmake'); diff --git a/src/StaticPHP/Toolchain/ClangBrewToolchain.php b/src/StaticPHP/Toolchain/ClangBrewToolchain.php index 5d8963ef..23260954 100644 --- a/src/StaticPHP/Toolchain/ClangBrewToolchain.php +++ b/src/StaticPHP/Toolchain/ClangBrewToolchain.php @@ -15,6 +15,7 @@ class ClangBrewToolchain extends ClangNativeToolchain GlobalEnvManager::putenv("SPC_DEFAULT_CC={$homebrew_prefix}/opt/llvm/bin/clang"); GlobalEnvManager::putenv("SPC_DEFAULT_CXX={$homebrew_prefix}/opt/llvm/bin/clang++"); GlobalEnvManager::putenv("SPC_DEFAULT_AR={$homebrew_prefix}/opt/llvm/bin/llvm-ar"); + GlobalEnvManager::putenv("SPC_DEFAULT_RANLIB={$homebrew_prefix}/opt/llvm/bin/llvm-ranlib"); GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld'); GlobalEnvManager::addPathIfNotExists("{$homebrew_prefix}/opt/llvm/bin"); } diff --git a/src/StaticPHP/Toolchain/ClangNativeToolchain.php b/src/StaticPHP/Toolchain/ClangNativeToolchain.php index 27ae2b65..7602c3ed 100644 --- a/src/StaticPHP/Toolchain/ClangNativeToolchain.php +++ b/src/StaticPHP/Toolchain/ClangNativeToolchain.php @@ -21,6 +21,7 @@ class ClangNativeToolchain implements UnixToolchainInterface GlobalEnvManager::putenv('SPC_DEFAULT_CC=clang'); GlobalEnvManager::putenv('SPC_DEFAULT_CXX=clang++'); GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar'); + GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=ranlib'); GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld'); } diff --git a/src/StaticPHP/Toolchain/GccNativeToolchain.php b/src/StaticPHP/Toolchain/GccNativeToolchain.php index 7c339e69..9aecb7e0 100644 --- a/src/StaticPHP/Toolchain/GccNativeToolchain.php +++ b/src/StaticPHP/Toolchain/GccNativeToolchain.php @@ -18,6 +18,7 @@ class GccNativeToolchain implements UnixToolchainInterface GlobalEnvManager::putenv('SPC_DEFAULT_CC=gcc'); GlobalEnvManager::putenv('SPC_DEFAULT_CXX=g++'); GlobalEnvManager::putenv('SPC_DEFAULT_AR=ar'); + GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=ranlib'); GlobalEnvManager::putenv('SPC_DEFAULT_LD=ld'); } From 39beb6802497460d7490f2bc905ca0dd7d7d05cf Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 24 May 2026 20:56:08 +0700 Subject: [PATCH 05/14] artifact: use {pkg_root_path} template in rust and go_win extract Switch the rust and go_win downloaders from baking PKG_ROOT_PATH into the extract path at download time to the {pkg_root_path} template, which ArtifactExtractor resolves at extract time. This keeps the path stable across runs where pkg_root_path differs between download and extract (e.g. containerised vs host builds). --- src/Package/Artifact/go_win.php | 4 +--- src/Package/Artifact/rust.php | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Package/Artifact/go_win.php b/src/Package/Artifact/go_win.php index e06e615a..7a87d2db 100644 --- a/src/Package/Artifact/go_win.php +++ b/src/Package/Artifact/go_win.php @@ -20,8 +20,6 @@ class go_win ])] public function downBinary(ArtifactDownloader $downloader): DownloadResult { - $pkgroot = PKG_ROOT_PATH; - // get version [$version] = explode("\n", default_shell()->executeCurl('https://go.dev/VERSION?m=text', retries: $downloader->getRetry()) ?: ''); if ($version === '') { @@ -52,7 +50,7 @@ class go_win throw new DownloaderException("Hash mismatch for downloaded go-win binary. Expected {$hash}, got {$file_hash}"); } - return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $version], extract: "{$pkgroot}/go-win", verified: true, version: $version); + return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $version], extract: '{pkg_root_path}/go-win', verified: true, version: $version); } #[CustomBinaryCheckUpdate('go-win', ['windows-x86_64'])] diff --git a/src/Package/Artifact/rust.php b/src/Package/Artifact/rust.php index e5c9f525..cc9bb175 100644 --- a/src/Package/Artifact/rust.php +++ b/src/Package/Artifact/rust.php @@ -46,7 +46,7 @@ class rust $download_url = "https://static.rust-lang.org/dist/rust-{$latest_version}-{$arch}-unknown-linux-{$distro}.tar.xz"; $path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . basename($download_url); default_shell()->executeCurlDownload($download_url, $path, retries: $downloader->getRetry()); - return DownloadResult::archive(basename($path), ['url' => $download_url, 'version' => $latest_version], extract: PKG_ROOT_PATH . '/rust-install', verified: false, version: $latest_version); + return DownloadResult::archive(basename($path), ['url' => $download_url, 'version' => $latest_version], extract: '{pkg_root_path}/rust-install', verified: false, version: $latest_version); } #[CustomBinaryCheckUpdate('rust', [ From 37d8b87c3b7a8404377949fd3a1583976e83283b Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 24 May 2026 21:11:49 +0700 Subject: [PATCH 06/14] oops --- src/StaticPHP/Toolchain/ZigToolchain.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/StaticPHP/Toolchain/ZigToolchain.php b/src/StaticPHP/Toolchain/ZigToolchain.php index 36e42d04..f9048ad3 100644 --- a/src/StaticPHP/Toolchain/ZigToolchain.php +++ b/src/StaticPHP/Toolchain/ZigToolchain.php @@ -16,6 +16,7 @@ class ZigToolchain implements UnixToolchainInterface GlobalEnvManager::putenv('SPC_DEFAULT_CC=zig-cc'); GlobalEnvManager::putenv('SPC_DEFAULT_CXX=zig-c++'); GlobalEnvManager::putenv('SPC_DEFAULT_AR=zig-ar'); + GlobalEnvManager::putenv('SPC_DEFAULT_RANLIB=zig-ranlib'); GlobalEnvManager::putenv('SPC_DEFAULT_LD=zig-ld.lld'); // Generate additional objects needed for zig toolchain From b8dd50814820a9b5667d118cb9441eabe950ecbd Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 24 May 2026 21:38:00 +0700 Subject: [PATCH 07/14] patch: strip trailing U+200E from spc_fix_avx512_cache_before_80400.patch The filename had a Left-To-Right Mark (U+200E) appended invisibly, so the file is unreachable by code that constructs the path from a plain ASCII string literal. Rename to the visible name. --- ...before_80400.patch‎ => spc_fix_avx512_cache_before_80400.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/globals/patch/{spc_fix_avx512_cache_before_80400.patch‎ => spc_fix_avx512_cache_before_80400.patch} (100%) diff --git a/src/globals/patch/spc_fix_avx512_cache_before_80400.patch‎ b/src/globals/patch/spc_fix_avx512_cache_before_80400.patch similarity index 100% rename from src/globals/patch/spc_fix_avx512_cache_before_80400.patch‎ rename to src/globals/patch/spc_fix_avx512_cache_before_80400.patch From c666cd6cd0d05e4ce809f38ebdef498019460d56 Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 24 May 2026 21:38:22 +0700 Subject: [PATCH 08/14] deduplicate_flags: keep paired flag+value tokens together deduplicate_flags() split flags on whitespace then ran a per-token unique. For paired flags like `-Xclang -mllvm` or `-framework Cocoa`, where the value is a separate token, the value token could collide with an unrelated flag or value and get dropped, corrupting the command line. Group known paired flags (-Xclang, -Xpreprocessor, -Xlinker, -Xassembler, -framework, -arch, -target, -include, -imacros, -isystem, -isysroot, -iquote, -idirafter, -MT, -MF, -MQ) with their following token into a single atom before the unique pass. --- src/globals/functions.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/globals/functions.php b/src/globals/functions.php index 549ef498..c824bd3d 100644 --- a/src/globals/functions.php +++ b/src/globals/functions.php @@ -256,10 +256,30 @@ function clean_spaces(string $string): string */ function deduplicate_flags(string $flags): string { - $tokens = preg_split('/\s+/', trim($flags)); + // Flags that take their value as a separate token. + static $paired = [ + '-Xclang', '-Xpreprocessor', '-Xlinker', '-Xassembler', + '-framework', '-arch', '-target', + '-include', '-imacros', '-isystem', '-isysroot', '-iquote', '-idirafter', + '-MT', '-MF', '-MQ', + ]; + + $tokens = preg_split('/\s+/', trim($flags)) ?: []; + + // Group paired flag+value into a single atom before dedup. + $atoms = []; + $n = count($tokens); + for ($i = 0; $i < $n; ++$i) { + if (in_array($tokens[$i], $paired, true) && $i + 1 < $n) { + $atoms[] = $tokens[$i] . ' ' . $tokens[$i + 1]; + ++$i; + } else { + $atoms[] = $tokens[$i]; + } + } // Reverse, unique, reverse back - keeps last occurrence of duplicates - $deduplicated = array_reverse(array_unique(array_reverse($tokens))); + $deduplicated = array_reverse(array_unique(array_reverse($atoms))); return implode(' ', $deduplicated); } From e72f9aa623a23331dfa0e6a7c4846c4ff5d9c7dc Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 24 May 2026 21:40:26 +0700 Subject: [PATCH 09/14] LinuxMuslCheck: pass tool env via setEnv instead of command prefixes Build the CC/CXX/AR/LD/RANLIB map once and hand it to shell()->setEnv() so the configure/make invocations don't have to repeat the same prefix on every line. For the sudo make-install path the env still needs to be on the command line (sudo strips the parent env), so the same map is rendered into $envFlags and prepended there. Also adds RANLIB, which the upstream Makefile honours. --- src/StaticPHP/Doctor/Item/LinuxMuslCheck.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php b/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php index df3b5241..1fe7ed00 100644 --- a/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php +++ b/src/StaticPHP/Doctor/Item/LinuxMuslCheck.php @@ -73,13 +73,20 @@ class LinuxMuslCheck $prefix = 'sudo '; logger()->warning('Current user is not root, using sudo for running command'); } + $sysEnv = ['CC' => 'gcc', 'CXX' => 'g++', 'AR' => 'ar', 'LD' => 'ld', 'RANLIB' => 'ranlib']; + $envFlags = ''; + foreach ($sysEnv as $k => $v) { + $envFlags .= "{$k}={$v} "; + } + $envFlags = rtrim($envFlags); $shell = shell()->cd(SOURCE_PATH . '/musl-wrapper') - ->exec('CC=gcc CXX=g++ AR=ar LD=ld ./configure --disable-gcc-wrapper') - ->exec('CC=gcc CXX=g++ AR=ar LD=ld make -j'); + ->setEnv($sysEnv) + ->exec('./configure --disable-gcc-wrapper') + ->exec('make -j'); if ($prefix !== '') { - f_passthru('cd ' . SOURCE_PATH . "/musl-wrapper && CC=gcc CXX=g++ AR=ar LD=ld {$prefix}make install"); + f_passthru('cd ' . SOURCE_PATH . "/musl-wrapper && {$envFlags} {$prefix}make install"); } else { - $shell->exec("CC=gcc CXX=g++ AR=ar LD=ld {$prefix}make install"); + $shell->exec("{$prefix}make install"); } return true; } From 96ab2de4b1dd53395867bf2c339e26f6acd87378 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Thu, 28 May 2026 22:38:56 -0700 Subject: [PATCH 10/14] Add preliminary MacPorts support --- src/Package/Library/unixodbc.php | 4 +- src/StaticPHP/Doctor/Item/MacOSToolCheck.php | 44 ++++++++++++++++++- .../Toolchain/ClangPortsToolchain.php | 20 +++++++++ src/StaticPHP/Util/GlobalEnvManager.php | 4 +- 4 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 src/StaticPHP/Toolchain/ClangPortsToolchain.php diff --git a/src/Package/Library/unixodbc.php b/src/Package/Library/unixodbc.php index e482e68b..78debadf 100644 --- a/src/Package/Library/unixodbc.php +++ b/src/Package/Library/unixodbc.php @@ -20,8 +20,8 @@ class unixodbc extends LibraryPackage { $sysconf_selector = match ($os = SystemTarget::getTargetOS()) { 'Darwin' => match (SystemTarget::getTargetArch()) { - 'x86_64' => '/usr/local/etc', - 'aarch64' => '/opt/homebrew/etc', + 'x86_64' => is_dir('/usr/local/etc') ? '/usr/local/etc' : '/opt/local/etc', + 'aarch64' => is_dir('/opt/homebrew/etc') ? '/opt/homebrew/etc' : '/opt/local/etc', default => throw new WrongUsageException('Unsupported architecture: ' . GNU_ARCH), }, 'Linux' => '/etc', diff --git a/src/StaticPHP/Doctor/Item/MacOSToolCheck.php b/src/StaticPHP/Doctor/Item/MacOSToolCheck.php index 9a256c08..d1e81a01 100644 --- a/src/StaticPHP/Doctor/Item/MacOSToolCheck.php +++ b/src/StaticPHP/Doctor/Item/MacOSToolCheck.php @@ -45,6 +45,18 @@ class MacOSToolCheck return CheckResult::ok(); } + #[CheckItem('if macports has installed', limit_os: 'Darwin', level: 998)] + public function checkPorts(): ?CheckResult + { + if (($path = MacOSUtil::findCommand('port')) === null) { + return CheckResult::fail('MacPorts is not installed', 'port'); + } + if ($path !== '/opt/local/bin/port' && getenv('GNU_ARCH') === 'aarch64') { + return CheckResult::fail('Current macports (/opt/local/bin/port) is not installed for M1 Mac, please re-install macports!'); + } + return CheckResult::ok(); + } + #[CheckItem('if necessary tools are installed', limit_os: 'Darwin')] public function checkCliTools(): ?CheckResult { @@ -74,6 +86,20 @@ class MacOSToolCheck return null; } + #[CheckItem('if macports llvm are installed', limit_os: 'Darwin')] + public function checkPortsLLVM(): ?CheckResult + { + if (getenv('SPC_USE_LLVM') === 'port') { + $macports_prefix = getenv('MACPORTS_PREFIX') ?: '/opt/local'; + + if (($path = MacOSUtil::findCommand('clang', ["{$macports_prefix}/bin"])) === null) { + return CheckResult::fail('MacPorts llvm is not installed', 'build-tools', ['missing' => ['llvm']]); + } + return CheckResult::ok($path); + } + return null; + } + #[CheckItem('if bison version is 3.0 or later', limit_os: 'Darwin')] public function checkBisonVersion(array $command_path = []): ?CheckResult { @@ -91,7 +117,7 @@ class MacOSToolCheck if ($command_path !== []) { return CheckResult::fail("Current {$bison} version is too old: " . $matches[0]); } - return $this->checkBisonVersion(['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin']); + return $this->checkBisonVersion(['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin', '/opt/local/bin']); } return CheckResult::ok($matches[0]); } @@ -108,6 +134,9 @@ class MacOSToolCheck #[FixItem('build-tools')] public function fixBuildTools(array $missing): bool { + $hasBrew = $this->checkBrew()?->isOK(); + $hasMacports = $this->checkPorts()?->isOK(); + $replacement = [ 'glibtoolize' => 'libtool', ]; @@ -115,7 +144,18 @@ class MacOSToolCheck if (isset($replacement[$cmd])) { $cmd = $replacement[$cmd]; } - shell()->exec('brew install --formula ' . escapeshellarg($cmd)); + + if ($hasBrew) { + shell()->exec('brew install --formula ' . escapeshellarg($cmd)); + continue; + } + + if ($hasMacports) { + shell()->exec('port install ' . escapeshellarg($cmd)); + continue; + } + + return false; } return true; } diff --git a/src/StaticPHP/Toolchain/ClangPortsToolchain.php b/src/StaticPHP/Toolchain/ClangPortsToolchain.php new file mode 100644 index 00000000..088a538a --- /dev/null +++ b/src/StaticPHP/Toolchain/ClangPortsToolchain.php @@ -0,0 +1,20 @@ + Date: Thu, 28 May 2026 22:40:33 -0700 Subject: [PATCH 11/14] Add a check for macports alongside Brew in toolchainmanager --- src/StaticPHP/Toolchain/ToolchainManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/StaticPHP/Toolchain/ToolchainManager.php b/src/StaticPHP/Toolchain/ToolchainManager.php index 478b08a6..f5f64a79 100644 --- a/src/StaticPHP/Toolchain/ToolchainManager.php +++ b/src/StaticPHP/Toolchain/ToolchainManager.php @@ -41,6 +41,7 @@ class ToolchainManager 'Windows' => MSVCToolchain::class, 'Darwin' => match (getenv('SPC_USE_LLVM') ?: 'system') { 'brew' => ClangBrewToolchain::class, + 'port' => ClangPortsToolchain::class, default => ClangNativeToolchain::class, }, default => throw new WrongUsageException('Unsupported OS family: ' . PHP_OS_FAMILY), From 605739464108b265be7958d1ba8451b1a6dbc997 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Sun, 31 May 2026 22:04:44 -0700 Subject: [PATCH 12/14] Simplify macports checks --- src/StaticPHP/Doctor/Item/MacOSToolCheck.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/StaticPHP/Doctor/Item/MacOSToolCheck.php b/src/StaticPHP/Doctor/Item/MacOSToolCheck.php index d1e81a01..95b0715f 100644 --- a/src/StaticPHP/Doctor/Item/MacOSToolCheck.php +++ b/src/StaticPHP/Doctor/Item/MacOSToolCheck.php @@ -48,12 +48,9 @@ class MacOSToolCheck #[CheckItem('if macports has installed', limit_os: 'Darwin', level: 998)] public function checkPorts(): ?CheckResult { - if (($path = MacOSUtil::findCommand('port')) === null) { + if (MacOSUtil::findCommand('port') === null) { return CheckResult::fail('MacPorts is not installed', 'port'); } - if ($path !== '/opt/local/bin/port' && getenv('GNU_ARCH') === 'aarch64') { - return CheckResult::fail('Current macports (/opt/local/bin/port) is not installed for M1 Mac, please re-install macports!'); - } return CheckResult::ok(); } @@ -127,6 +124,11 @@ class MacOSToolCheck #[FixItem('brew')] public function fixBrew(): bool { + $hasMacports = $this->checkPorts(); + if ($hasMacports->isOK()) { + return true; // Nothing to fix - will use macports instead + } + shell(true)->exec('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'); return true; } From 3ff9426e50e70c474faa346701faeb3186938570 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 4 Jun 2026 09:51:06 +0800 Subject: [PATCH 13/14] feat: add permissions for id-token and attestations in release build --- .github/workflows/release-build.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 5740a631..54748575 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -38,6 +38,9 @@ jobs: - name: "windows-x64" os: "ubuntu-latest" filename: "spc-windows-x64.exe" + permissions: + id-token: write + attestations: write steps: - name: "Checkout" uses: "actions/checkout@v5" @@ -105,6 +108,12 @@ jobs: fi fi + - name: "Generate build provenance attestation" + if: github.event_name != 'pull_request' + uses: actions/attest-build-provenance@v4 + with: + subject-path: "${{ github.workspace }}/${{ matrix.operating-system.name == 'windows-x64' && 'spc.exe' || 'spc' }}" + - name: "Copy file" run: | if [ "${{ matrix.operating-system.name }}" != "windows-x64" ]; then From 0761267eb3b5598d424514323fa69c16b6c41664 Mon Sep 17 00:00:00 2001 From: Kevin Boyd Date: Thu, 4 Jun 2026 21:40:01 -0700 Subject: [PATCH 14/14] Combine the macos tool checks into a single checkBrewOrPorts method --- src/StaticPHP/Doctor/Item/MacOSToolCheck.php | 49 +++++++------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/src/StaticPHP/Doctor/Item/MacOSToolCheck.php b/src/StaticPHP/Doctor/Item/MacOSToolCheck.php index 95b0715f..2d011703 100644 --- a/src/StaticPHP/Doctor/Item/MacOSToolCheck.php +++ b/src/StaticPHP/Doctor/Item/MacOSToolCheck.php @@ -33,24 +33,20 @@ class MacOSToolCheck 'glibtoolize', ]; - #[CheckItem('if homebrew has installed', limit_os: 'Darwin', level: 998)] - public function checkBrew(): ?CheckResult + #[CheckItem('if homebrew or macports has installed', limit_os: 'Darwin', level: 998)] + public function checkBrewOrPorts(): ?CheckResult { - if (($path = MacOSUtil::findCommand('brew')) === null) { - return CheckResult::fail('Homebrew is not installed', 'brew'); - } - if ($path !== '/opt/homebrew/bin/brew' && getenv('GNU_ARCH') === 'aarch64') { + $brewPath = MacOSUtil::findCommand('brew'); + $portPath = MacOSUtil::findCommand('port'); + + if ($brewPath && $brewPath !== '/opt/homebrew/bin/brew' && getenv('GNU_ARCH') === 'aarch64') { return CheckResult::fail('Current homebrew (/usr/local/bin/homebrew) is not installed for M1 Mac, please re-install homebrew in /opt/homebrew/ !'); } - return CheckResult::ok(); - } - #[CheckItem('if macports has installed', limit_os: 'Darwin', level: 998)] - public function checkPorts(): ?CheckResult - { - if (MacOSUtil::findCommand('port') === null) { - return CheckResult::fail('MacPorts is not installed', 'port'); + if ($brewPath === null && $portPath === null) { + return CheckResult::fail('Homebrew is not installed', 'brew'); } + return CheckResult::ok(); } @@ -69,8 +65,8 @@ class MacOSToolCheck return CheckResult::ok(); } - #[CheckItem('if homebrew llvm are installed', limit_os: 'Darwin')] - public function checkBrewLLVM(): ?CheckResult + #[CheckItem('if homebrew or macports llvm are installed', limit_os: 'Darwin')] + public function checkBrewOrPortsLLVM(): ?CheckResult { if (getenv('SPC_USE_LLVM') === 'brew') { $homebrew_prefix = getenv('HOMEBREW_PREFIX') ?: (SystemTarget::getTargetArch() === 'aarch64' ? '/opt/homebrew' : '/usr/local/homebrew'); @@ -80,20 +76,16 @@ class MacOSToolCheck } return CheckResult::ok($path); } - return null; - } - #[CheckItem('if macports llvm are installed', limit_os: 'Darwin')] - public function checkPortsLLVM(): ?CheckResult - { if (getenv('SPC_USE_LLVM') === 'port') { - $macports_prefix = getenv('MACPORTS_PREFIX') ?: '/opt/local'; + $macportsPrefix = '/opt/local'; - if (($path = MacOSUtil::findCommand('clang', ["{$macports_prefix}/bin"])) === null) { + if (($path = MacOSUtil::findCommand('clang', ["{$macportsPrefix}/bin"])) === null) { return CheckResult::fail('MacPorts llvm is not installed', 'build-tools', ['missing' => ['llvm']]); } return CheckResult::ok($path); } + return null; } @@ -124,11 +116,6 @@ class MacOSToolCheck #[FixItem('brew')] public function fixBrew(): bool { - $hasMacports = $this->checkPorts(); - if ($hasMacports->isOK()) { - return true; // Nothing to fix - will use macports instead - } - shell(true)->exec('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'); return true; } @@ -136,8 +123,8 @@ class MacOSToolCheck #[FixItem('build-tools')] public function fixBuildTools(array $missing): bool { - $hasBrew = $this->checkBrew()?->isOK(); - $hasMacports = $this->checkPorts()?->isOK(); + $brewPath = MacOSUtil::findCommand('brew'); + $portPath = MacOSUtil::findCommand('port'); $replacement = [ 'glibtoolize' => 'libtool', @@ -147,12 +134,12 @@ class MacOSToolCheck $cmd = $replacement[$cmd]; } - if ($hasBrew) { + if ($brewPath !== null) { shell()->exec('brew install --formula ' . escapeshellarg($cmd)); continue; } - if ($hasMacports) { + if ($portPath !== null) { shell()->exec('port install ' . escapeshellarg($cmd)); continue; }