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 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', [ 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; - } } 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` 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/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"); 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; } diff --git a/src/StaticPHP/Doctor/Item/MacOSToolCheck.php b/src/StaticPHP/Doctor/Item/MacOSToolCheck.php index 9a256c08..2d011703 100644 --- a/src/StaticPHP/Doctor/Item/MacOSToolCheck.php +++ b/src/StaticPHP/Doctor/Item/MacOSToolCheck.php @@ -33,15 +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/ !'); } + + if ($brewPath === null && $portPath === null) { + return CheckResult::fail('Homebrew is not installed', 'brew'); + } + return CheckResult::ok(); } @@ -60,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'); @@ -71,6 +76,16 @@ class MacOSToolCheck } return CheckResult::ok($path); } + + if (getenv('SPC_USE_LLVM') === 'port') { + $macportsPrefix = '/opt/local'; + + 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; } @@ -91,7 +106,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 +123,9 @@ class MacOSToolCheck #[FixItem('build-tools')] public function fixBuildTools(array $missing): bool { + $brewPath = MacOSUtil::findCommand('brew'); + $portPath = MacOSUtil::findCommand('port'); + $replacement = [ 'glibtoolize' => 'libtool', ]; @@ -115,7 +133,18 @@ class MacOSToolCheck if (isset($replacement[$cmd])) { $cmd = $replacement[$cmd]; } - shell()->exec('brew install --formula ' . escapeshellarg($cmd)); + + if ($brewPath !== null) { + shell()->exec('brew install --formula ' . escapeshellarg($cmd)); + continue; + } + + if ($portPath !== null) { + shell()->exec('port install ' . escapeshellarg($cmd)); + continue; + } + + return false; } return true; } 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/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 @@ + 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), 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 diff --git a/src/StaticPHP/Util/GlobalEnvManager.php b/src/StaticPHP/Util/GlobalEnvManager.php index 5b4b16b2..2e1f11df 100644 --- a/src/StaticPHP/Util/GlobalEnvManager.php +++ b/src/StaticPHP/Util/GlobalEnvManager.php @@ -134,10 +134,10 @@ class GlobalEnvManager } // test bison if (PHP_OS_FAMILY === 'Darwin') { - if ($bison = MacOSUtil::findCommand('bison', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin'])) { + if ($bison = MacOSUtil::findCommand('bison', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin', '/opt/local/bin/bison'])) { self::putenv("BISON={$bison}"); } - if ($yacc = MacOSUtil::findCommand('yacc', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin'])) { + if ($yacc = MacOSUtil::findCommand('yacc', ['/opt/homebrew/opt/bison/bin', '/usr/local/opt/bison/bin', '/opt/local/bin/yacc'])) { self::putenv("YACC={$yacc}"); } } 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); } 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