From 52f40b7f9f32934718c7408dce3cde1d183d9934 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 12 Jun 2025 20:19:21 +0700 Subject: [PATCH 01/70] the release option also affects shared extensions, which is unwanted, patchelf their soname back and rename them --- src/SPC/builder/linux/LinuxBuilder.php | 18 ++++++++++++++++++ src/SPC/doctor/item/LinuxToolCheckList.php | 3 +++ 2 files changed, 21 insertions(+) diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 00732619..315d7df9 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -293,6 +293,24 @@ class LinuxBuilder extends UnixBuilderBase $cwd = getcwd(); chdir(BUILD_LIB_PATH); symlink($realLibName, 'libphp.so'); + chdir(BUILD_MODULES_PATH); + foreach ($this->getExts() as $ext) { + if (!$ext->isBuildShared()) { + continue; + } + $name = $ext->getName(); + $versioned = "{$name}-{$release}.so"; + $unversioned = "{$name}.so"; + if (is_file(BUILD_MODULES_PATH . "/{$versioned}")) { + rename(BUILD_MODULES_PATH . "/{$versioned}", BUILD_MODULES_PATH . "/{$unversioned}"); + shell()->cd(BUILD_MODULES_PATH) + ->exec(sprintf( + 'patchelf --set-soname %s %s', + escapeshellarg($unversioned), + escapeshellarg($unversioned) + )); + } + } chdir($cwd); } $this->patchPhpScripts(); diff --git a/src/SPC/doctor/item/LinuxToolCheckList.php b/src/SPC/doctor/item/LinuxToolCheckList.php index 2522eb58..56235b0c 100644 --- a/src/SPC/doctor/item/LinuxToolCheckList.php +++ b/src/SPC/doctor/item/LinuxToolCheckList.php @@ -22,6 +22,7 @@ class LinuxToolCheckList 'bzip2', 'cmake', 'gcc', 'g++', 'patch', 'binutils-gold', 'libtoolize', 'which', + 'patchelf', ]; public const TOOLS_DEBIAN = [ @@ -30,6 +31,7 @@ class LinuxToolCheckList 'tar', 'unzip', 'gzip', 'bzip2', 'cmake', 'patch', 'xz', 'libtoolize', 'which', + 'patchelf', ]; public const TOOLS_RHEL = [ @@ -38,6 +40,7 @@ class LinuxToolCheckList 'tar', 'unzip', 'gzip', 'gcc', 'bzip2', 'cmake', 'patch', 'which', 'xz', 'libtool', 'gettext-devel', + 'perl', 'patchelf', ]; public const TOOLS_ARCH = [ From 0a24a6af1f8fdbccd8fb2fcc4cdce7fe972a551c Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 12 Jun 2025 20:51:17 +0700 Subject: [PATCH 02/70] move check when to build to extension.php instead of builder --- src/SPC/builder/BuilderBase.php | 11 ----------- src/SPC/builder/Extension.php | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index 433d0b97..7fc78e44 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -262,17 +262,6 @@ abstract class BuilderBase if (!$ext->isBuildShared()) { continue; } - if (Config::getExt($ext->getName(), 'type') === 'builtin' || Config::getExt($ext->getName(), 'build-with-php') === true) { - if (file_exists(BUILD_MODULES_PATH . '/' . $ext->getName() . '.so')) { - logger()->info('Shared extension [' . $ext->getName() . '] was already built by php-src/configure (' . $ext->getName() . '.so)'); - continue; - } - if (Config::getExt($ext->getName(), 'build-with-php') === true) { - logger()->warning('Shared extension [' . $ext->getName() . '] did not build with php-src/configure (' . $ext->getName() . '.so)'); - logger()->warning('Try deleting your build and source folders and running `spc build`` again.'); - continue; - } - } $ext->buildShared(); } } catch (RuntimeException $e) { diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index 74be112c..5b35aade 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -328,6 +328,17 @@ class Extension */ public function buildShared(): void { + if (Config::getExt($this->getName(), 'type') === 'builtin' || Config::getExt($this->getName(), 'build-with-php') === true) { + if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) { + logger()->info('Shared extension [' . $this->getName() . '] was already built by php-src/configure (' . $this->getName() . '.so)'); + return; + } + if (Config::getExt($this->getName(), 'build-with-php') === true) { + logger()->warning('Shared extension [' . $this->getName() . '] did not build with php-src/configure (' . $this->getName() . '.so)'); + logger()->warning('Try deleting your build and source folders and running `spc build`` again.'); + return; + } + } logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)'); foreach ($this->dependencies as $dependency) { if (!$dependency instanceof Extension) { From d249391816efaea776be714628d1a729375c57ba Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 12 Jun 2025 23:37:18 +0700 Subject: [PATCH 03/70] don't add configure command to phpinfo when -release is set --- src/SPC/builder/LibraryBase.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/SPC/builder/LibraryBase.php b/src/SPC/builder/LibraryBase.php index 6780a695..338aff37 100644 --- a/src/SPC/builder/LibraryBase.php +++ b/src/SPC/builder/LibraryBase.php @@ -214,6 +214,19 @@ abstract class LibraryBase */ public function tryBuild(bool $force_build = false): int { + if (str_contains((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), '-release')) { + FileSystem::replaceFileLineContainsString( + SOURCE_PATH . '/php-src/ext/standard/info.c', + '#ifdef CONFIGURE_COMMAND', + '#ifdef NO_CONFIGURE_COMMAND', + ); + } else { + FileSystem::replaceFileLineContainsString( + SOURCE_PATH . '/php-src/ext/standard/info.c', + '#ifdef NO_CONFIGURE_COMMAND', + '#ifdef CONFIGURE_COMMAND', + ); + } if (file_exists($this->source_dir . '/.spc.patched')) { $this->patched = true; } From 302cf8345de443b0e3a095251ca9e4dd2dfbff64 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 13 Jun 2025 13:01:02 +0700 Subject: [PATCH 04/70] properly handle different php versions, clean up only required files --- src/SPC/command/BuildPHPCommand.php | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 61781d2c..353605b1 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -6,6 +6,7 @@ namespace SPC\command; use SPC\builder\BuilderProvider; use SPC\exception\ExceptionHandler; +use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\FileSystem; @@ -158,14 +159,24 @@ class BuildPHPCommand extends BuildCommand if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) { $indent_texts['UPX Pack'] = 'enabled'; } + try { - $ver = $builder->getPHPVersion(); - $indent_texts['PHP Version'] = $ver; - } catch (\Throwable) { - if (($ver = $builder->getPHPVersionFromArchive()) !== false) { - $indent_texts['PHP Version'] = $ver; + $cleanPhpSrc = $builder->getPHPVersion() !== $builder->getPHPVersionFromArchive(); + } catch (RuntimeException|WrongUsageException) { + $cleanPhpSrc = true; + } + if ($cleanPhpSrc) { + logger()->info('Cleaning previous php build due to mismatching versions...'); + FileSystem::removeDir(SOURCE_PATH . '/php-src'); + FileSystem::removeDir(BUILD_MODULES_PATH); + $binFiles = glob(BUILD_BIN_PATH . '/php*'); + $libFiles = glob(BUILD_LIB_PATH . '/libphp*'); + foreach ([...$binFiles, ...$libFiles] as $file) { + unlink($file); } } + $ver = $builder->getPHPVersionFromArchive() ?: $builder->getPHPVersion(); + $indent_texts['PHP Version'] = $ver; if (!empty($not_included)) { $indent_texts['Extra Exts (' . count($not_included) . ')'] = implode(', ', $not_included); From b265d6dd5625665a0477047f367e7a20673e313a Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 13 Jun 2025 16:25:31 +0700 Subject: [PATCH 05/70] don't set unknown linker flags on macos --- src/SPC/builder/Extension.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index 5b35aade..d0448389 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -368,11 +368,16 @@ class Extension { $config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true); [$staticLibString, $sharedLibString] = $this->getStaticAndSharedLibs(); + + // macOS ld64 doesn't understand these, while Linux and BSD do + // use them to make sure that all symbols are picked up, even if a library has already been visited before + $preStatic = PHP_OS_FAMILY !== 'Darwin' ? '-Wl,-Bstatic -Wl,--start-group ' : ''; + $postStatic = PHP_OS_FAMILY !== 'Darwin' ? ' -Wl,--end-group -Wl,-Bdynamic ' : ' '; $env = [ 'CFLAGS' => $config['cflags'], 'CXXFLAGS' => $config['cflags'], 'LDFLAGS' => $config['ldflags'], - 'LIBS' => '-Wl,-Bstatic -Wl,--start-group ' . $staticLibString . ' -Wl,--end-group -Wl,-Bdynamic ' . $sharedLibString, + 'LIBS' => $preStatic . $staticLibString . $postStatic . $sharedLibString, 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, ]; @@ -393,6 +398,7 @@ class Extension '--enable-shared --disable-static' ); + // some extensions don't define their dependencies well, this patch is only needed for a few FileSystem::replaceFileRegex( $this->source_dir . '/Makefile', '/^(.*_SHARED_LIBADD\s*=.*)$/m', From 962de5b25fe3363a2edfbaf29d3819510e0746bf Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 13 Jun 2025 22:51:35 +0700 Subject: [PATCH 06/70] add edant/watcher, to make spc-packages easier --- config/lib.json | 9 +++++++ config/source.json | 9 +++++++ src/SPC/builder/freebsd/library/watcher.php | 12 +++++++++ src/SPC/builder/linux/library/watcher.php | 12 +++++++++ src/SPC/builder/macos/library/watcher.php | 12 +++++++++ src/SPC/builder/unix/library/watcher.php | 28 +++++++++++++++++++++ 6 files changed, 82 insertions(+) create mode 100644 src/SPC/builder/freebsd/library/watcher.php create mode 100644 src/SPC/builder/linux/library/watcher.php create mode 100644 src/SPC/builder/macos/library/watcher.php create mode 100644 src/SPC/builder/unix/library/watcher.php diff --git a/config/lib.json b/config/lib.json index 160d43f2..dc792c3b 100644 --- a/config/lib.json +++ b/config/lib.json @@ -854,5 +854,14 @@ "zstd.h", "zstd_errors.h" ] + }, + "watcher": { + "source": "watcher", + "static-libs-unix": [ + "libwatcher-c.a" + ], + "headers": [ + "wtr/watcher-c.h" + ] } } diff --git a/config/source.json b/config/source.json index 2ad8ec87..57a10a4e 100644 --- a/config/source.json +++ b/config/source.json @@ -1070,5 +1070,14 @@ "type": "file", "path": "LICENSE" } + }, + "watcher": { + "type": "ghtar", + "repo": "e-dant/watcher", + "prefer-stable": true, + "license": { + "type": "file", + "path": "license" + } } } diff --git a/src/SPC/builder/freebsd/library/watcher.php b/src/SPC/builder/freebsd/library/watcher.php new file mode 100644 index 00000000..1a08dd24 --- /dev/null +++ b/src/SPC/builder/freebsd/library/watcher.php @@ -0,0 +1,12 @@ +cd($this->source_dir . '/watcher-c') + ->initializeEnv($this) + ->exec(getenv('CC') . ' -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra -fPIC') + ->exec(getenv('AR') . ' rcs libwatcher-c.a libwatcher-c.o'); + + copy($this->source_dir . '/watcher-c/libwatcher-c.a', BUILD_LIB_PATH . '/libwatcher-c.a'); + FileSystem::createDir(BUILD_INCLUDE_PATH . '/wtr'); + copy($this->source_dir . '/watcher-c/include/wtr/watcher-c.h', BUILD_INCLUDE_PATH . '/wtr/watcher-c.h'); + } +} From 3efabee1533f4a38729ad4d7a94e2c4542f4b847 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sat, 14 Jun 2025 01:13:33 +0800 Subject: [PATCH 07/70] Remove redundant sanity check call --- src/SPC/builder/macos/MacOSBuilder.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index c71dabce..a0522120 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -180,9 +180,6 @@ class MacOSBuilder extends UnixBuilderBase } $this->buildEmbed(); } - - $this->emitPatchPoint('before-sanity-check'); - $this->sanityCheck($build_target); } public function testPHP(int $build_target = BUILD_TARGET_NONE) From bafa67c8debbb6d2f5c3084d01b8ab173a979164 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Tue, 17 Jun 2025 12:19:59 +0700 Subject: [PATCH 08/70] add patchelf to gnu docker --- bin/spc-gnu-docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index 36e0420e..b300d2cf 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -72,7 +72,7 @@ RUN yum update -y && \ yum install -y devtoolset-10-gcc-* RUN echo "source scl_source enable devtoolset-10" >> /etc/bashrc RUN source /etc/bashrc -RUN yum install -y which +RUN yum install -y which patchelf RUN curl -o cmake.tgz -fsSL https://github.com/Kitware/CMake/releases/download/v3.31.4/cmake-3.31.4-linux-$BASE_ARCH.tar.gz && \ mkdir /cmake && \ From 7b3ea7e12ef43d605c9935de64762955c0da1777 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Tue, 17 Jun 2025 12:30:11 +0700 Subject: [PATCH 09/70] fix installing patchelf --- bin/spc-gnu-docker | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index b300d2cf..bc907e2a 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -72,7 +72,10 @@ RUN yum update -y && \ yum install -y devtoolset-10-gcc-* RUN echo "source scl_source enable devtoolset-10" >> /etc/bashrc RUN source /etc/bashrc -RUN yum install -y which patchelf +RUN yum install -y which + +RUN curl -o patchelf.tgz https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-$BASE_ARCH.tar.gz && \ + tar -xzf patchelf.tgz -C /usr RUN curl -o cmake.tgz -fsSL https://github.com/Kitware/CMake/releases/download/v3.31.4/cmake-3.31.4-linux-$BASE_ARCH.tar.gz && \ mkdir /cmake && \ From 9de5c621361ba2c634253a3858018e37c897a81f Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Tue, 17 Jun 2025 12:34:56 +0700 Subject: [PATCH 10/70] extract elsewhere temporarily --- bin/spc-gnu-docker | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index bc907e2a..b69e0710 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -74,8 +74,10 @@ RUN echo "source scl_source enable devtoolset-10" >> /etc/bashrc RUN source /etc/bashrc RUN yum install -y which -RUN curl -o patchelf.tgz https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-$BASE_ARCH.tar.gz && \ - tar -xzf patchelf.tgz -C /usr +RUN curl -fsSL -o patchelf.tgz https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-x86_64.tar.gz && \ + mkdir -p /patchelf && \ + tar -xzf patchelf.tgz -C /patchelf --strip-components=1 && \ + cp /patchelf/bin/patchelf /usr/bin/ RUN curl -o cmake.tgz -fsSL https://github.com/Kitware/CMake/releases/download/v3.31.4/cmake-3.31.4-linux-$BASE_ARCH.tar.gz && \ mkdir /cmake && \ From aa61a9e77b64937be70eb0a2747c19aa34881e31 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Tue, 17 Jun 2025 14:01:53 +0700 Subject: [PATCH 11/70] extra info on ext load failure --- src/SPC/builder/Extension.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index d0448389..7a68ad09 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -264,9 +264,12 @@ class Extension // If you need to run some check, overwrite this or add your assert in src/globals/ext-tests/{extension_name}.php // If check failed, throw RuntimeException $sharedExtensions = $this->getSharedExtensionLoadString(); - [$ret] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"'); + [$ret, $out] = shell()->execWithResult(BUILD_BIN_PATH . '/php -n' . $sharedExtensions . ' --ri "' . $this->getDistName() . '"'); if ($ret !== 0) { - throw new RuntimeException('extension ' . $this->getName() . ' failed compile check: php-cli returned ' . $ret); + throw new RuntimeException( + 'extension ' . $this->getName() . ' failed runtime check: php-cli returned ' . $ret . "\n" . + join("\n", $out) + ); } if (file_exists(ROOT_DIR . '/src/globals/ext-tests/' . $this->getName() . '.php')) { From 883cc4b6fdde2d39b326adaa708256e97276fe4b Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Tue, 17 Jun 2025 15:53:14 +0700 Subject: [PATCH 12/70] patch on 2.17... --- src/SPC/store/SourcePatcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php index d61020b8..11317314 100644 --- a/src/SPC/store/SourcePatcher.php +++ b/src/SPC/store/SourcePatcher.php @@ -454,7 +454,7 @@ class SourcePatcher public static function patchFfiCentos7FixO3strncmp(): bool { - if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::getLibcVersionIfExists() >= '2.17') { + if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::getLibcVersionIfExists() > '2.17') { return false; } if (!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) { From 3a85d96fa4fc252c4a79d6f72c3db0280ff7181f Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Tue, 17 Jun 2025 18:03:27 +0700 Subject: [PATCH 13/70] yet another damn centos 7 patch --- src/SPC/builder/Extension.php | 2 +- src/SPC/builder/extension/imagick.php | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index 7a68ad09..4ea135a4 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -488,7 +488,7 @@ class Extension * * @return array [staticLibString, sharedLibString] */ - private function getStaticAndSharedLibs(): array + protected function getStaticAndSharedLibs(): array { $config = (new SPCConfigUtil($this->builder))->config([$this->getName()], with_dependencies: true); $sharedLibString = ''; diff --git a/src/SPC/builder/extension/imagick.php b/src/SPC/builder/extension/imagick.php index d78627ef..90af2c49 100644 --- a/src/SPC/builder/extension/imagick.php +++ b/src/SPC/builder/extension/imagick.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; +use SPC\store\FileSystem; use SPC\util\CustomExt; +use SPC\util\SPCConfigUtil; #[CustomExt('imagick')] class imagick extends Extension @@ -29,4 +31,15 @@ class imagick extends Extension $disable_omp = !(getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) ? '' : ' ac_cv_func_omp_pause_resource_all=no'; return '--with-imagick=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $disable_omp; } + + protected function getStaticAndSharedLibs(): array + { + // on centos 7, it will use the symbol _ZTINSt6thread6_StateE, which is not defined in system libstdc++.so.6 + [$static, $shared] = parent::getStaticAndSharedLibs(); + if (getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) { + $static .= ' -lstdc++'; + $shared = str_replace('-lstdc++', '', $shared); + } + return [$static, $shared]; + } } From da75d2d707a82f921b60cac1ac3d28a450c51c3c Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Tue, 17 Jun 2025 18:04:27 +0700 Subject: [PATCH 14/70] cs fix --- src/SPC/builder/extension/imagick.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/SPC/builder/extension/imagick.php b/src/SPC/builder/extension/imagick.php index 90af2c49..7951ea69 100644 --- a/src/SPC/builder/extension/imagick.php +++ b/src/SPC/builder/extension/imagick.php @@ -5,9 +5,7 @@ declare(strict_types=1); namespace SPC\builder\extension; use SPC\builder\Extension; -use SPC\store\FileSystem; use SPC\util\CustomExt; -use SPC\util\SPCConfigUtil; #[CustomExt('imagick')] class imagick extends Extension From cb0a90d1d9b403114c2cda9243b6981faa55ea2d Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sat, 14 Jun 2025 16:09:48 +0800 Subject: [PATCH 15/70] Add source hash comparator & refactor download lock --- tests/bootstrap.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d573204b..c2fb6eee 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,5 +2,7 @@ declare(strict_types=1); +putenv('SPC_IGNORE_BAD_HASH=yes'); + require_once __DIR__ . '/../src/globals/internal-env.php'; require_once __DIR__ . '/mock/SPC_store.php'; From 57b22782d35b35b72ad574c4b045d966462e4287 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 16 Jun 2025 12:34:18 +0800 Subject: [PATCH 16/70] Define env in phpunit.xml --- tests/bootstrap.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index c2fb6eee..d573204b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,7 +2,5 @@ declare(strict_types=1); -putenv('SPC_IGNORE_BAD_HASH=yes'); - require_once __DIR__ . '/../src/globals/internal-env.php'; require_once __DIR__ . '/mock/SPC_store.php'; From c1870af1b1a24be25d8f982cc7d9961aa8cce582 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 10:48:09 +0700 Subject: [PATCH 17/70] add frankenphp sapi --- config/env.ini | 3 + src/SPC/builder/linux/LinuxBuilder.php | 5 ++ src/SPC/builder/traits/UnixGoCheckTrait.php | 83 +++++++++++++++++++++ src/SPC/builder/unix/UnixBuilderBase.php | 30 ++++++++ src/SPC/command/BuildPHPCommand.php | 5 +- src/SPC/doctor/AsCheckItem.php | 3 +- src/SPC/doctor/item/BSDToolCheckList.php | 6 ++ src/SPC/doctor/item/LinuxToolCheckList.php | 8 ++ src/SPC/doctor/item/MacOSToolCheckList.php | 8 ++ src/globals/defines.php | 3 +- 10 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 src/SPC/builder/traits/UnixGoCheckTrait.php diff --git a/config/env.ini b/config/env.ini index ba8652d5..112d0187 100644 --- a/config/env.ini +++ b/config/env.ini @@ -42,6 +42,9 @@ SPC_CONCURRENCY=${CPU_COUNT} SPC_SKIP_PHP_VERSION_CHECK="no" ; Ignore some check item for bin/spc doctor command, comma separated (e.g. SPC_SKIP_DOCTOR_CHECK_ITEMS="if homebrew has installed") SPC_SKIP_DOCTOR_CHECK_ITEMS="" +; extra modules that xcaddy will include in the FrankenPHP build +SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" + ; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them ; only useful for builds targeting not pure-static linking ; default paths diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 315d7df9..fc131214 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -114,6 +114,7 @@ class LinuxBuilder extends UnixBuilderBase $enable_fpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM; $enable_micro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; $enable_embed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; + $enable_frankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP; $mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : ''; // prepare build php envs @@ -175,6 +176,10 @@ class LinuxBuilder extends UnixBuilderBase } $this->buildEmbed(); } + if ($enable_frankenphp) { + logger()->info('building frankenphp'); + $this->buildFrankenphp(); + } } public function testPHP(int $build_target = BUILD_TARGET_NONE) diff --git a/src/SPC/builder/traits/UnixGoCheckTrait.php b/src/SPC/builder/traits/UnixGoCheckTrait.php new file mode 100644 index 00000000..12e3d005 --- /dev/null +++ b/src/SPC/builder/traits/UnixGoCheckTrait.php @@ -0,0 +1,83 @@ +findCommand('go', $paths) === null) { + $this->installGo(); + } + + $gobin = getenv('GOBIN') ?: (getenv('HOME') . '/go/bin'); + putenv("GOBIN={$gobin}"); + + $paths[] = $gobin; + + if ($this->findCommand('xcaddy', $paths) === null) { + shell(true)->exec('go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest'); + } + + return CheckResult::ok(); + } + + private function installGo(): bool + { + $prefix = ''; + if (get_current_user() !== 'root') { + $prefix = 'sudo '; + logger()->warning('Current user is not root, using sudo for running command'); + } + + $arch = php_uname('m'); + $go_arch = match ($arch) { + 'x86_64' => 'amd64', + 'aarch64' => 'arm64', + default => $arch + }; + $os = strtolower(PHP_OS_FAMILY); + + $go_version = '1.24.4'; + $go_filename = "go{$go_version}.{$os}-{$go_arch}.tar.gz"; + $go_url = "https://go.dev/dl/{$go_filename}"; + + logger()->info("Downloading Go {$go_version} for {$go_arch}"); + + try { + // Download Go binary + Downloader::downloadFile('go', $go_url, $go_filename); + + // Extract the tarball + FileSystem::extractSource('go', SPC_SOURCE_ARCHIVE, DOWNLOAD_PATH . "/{$go_filename}"); + + // Move to /usr/local/go + logger()->info('Installing Go to /usr/local/go'); + shell()->exec("{$prefix}rm -rf /usr/local/go"); + shell()->exec("{$prefix}mv " . SOURCE_PATH . '/go /usr/local/'); + + if (!str_contains(getenv('PATH'), '/usr/local/go/bin')) { + logger()->info('Adding Go to PATH'); + shell()->exec("{$prefix}echo 'export PATH=\$PATH:/usr/local/go/bin' >> /etc/profile"); + putenv('PATH=' . getenv('PATH') . ':/usr/local/go/bin'); + } + + logger()->info('Go has been installed successfully'); + return true; + } catch (RuntimeException $e) { + logger()->error('Failed to install Go: ' . $e->getMessage()); + return false; + } + } +} diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 0d367de1..19880bf1 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -277,4 +277,34 @@ abstract class UnixBuilderBase extends BuilderBase FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str); } } + + protected function buildFrankenphp(): void + { + $path = getenv('PATH'); + $xcaddyPath = getenv('GOBIN') ?: (getenv('HOME') . '/go/bin'); + if (!str_contains($path, $xcaddyPath)) { + $path = $path . ':' . $xcaddyPath; + } + $path = BUILD_BIN_PATH . ':' . $path; + f_putenv("PATH={$path}"); + + $brotliLibs = $this->getLib('brotli') !== null ? '-lbrotlienc -lbrotlidec -lbrotlicommon' : ''; + $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; + $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; + + $env = [ + 'CGO_ENABLED' => '1', + 'CGO_CFLAGS' => '$(php-config --includes) -I$(php-config --include-dir)/..', + 'CGO_LDFLAGS' => "$(php-config --ldflags) $(php-config --libs) {$brotliLibs} -lwatcher-c -lphp -Wl,-rpath=" . BUILD_LIB_PATH, + 'XCADDY_GO_BUILD_FLAGS' => "-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" . $nobrotli . $nowatcher, + ]; + shell()->cd(BUILD_BIN_PATH) + ->setEnv($env) + ->exec( + 'xcaddy build ' . + '--output frankenphp ' . + '--with github.com/dunglas/frankenphp/caddy ' . + getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES') + ); + } } diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 353605b1..0ef9dab3 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -33,6 +33,7 @@ class BuildPHPCommand extends BuildCommand $this->addOption('build-cli', null, null, 'Build cli SAPI'); $this->addOption('build-fpm', null, null, 'Build fpm SAPI (not available on Windows)'); $this->addOption('build-embed', null, null, 'Build embed SAPI (not available on Windows)'); + $this->addOption('build-frankenphp', null, null, 'Build FrankenPHP SAPI (not available on Windows)'); $this->addOption('build-all', null, null, 'Build all SAPI'); $this->addOption('no-strip', null, null, 'build without strip, in order to debug and load external extensions'); $this->addOption('disable-opcache-jit', null, null, 'disable opcache jit'); @@ -83,7 +84,8 @@ class BuildPHPCommand extends BuildCommand $this->output->writeln("\t--build-micro\tBuild phpmicro SAPI"); $this->output->writeln("\t--build-fpm\tBuild php-fpm SAPI"); $this->output->writeln("\t--build-embed\tBuild embed SAPI/libphp"); - $this->output->writeln("\t--build-all\tBuild all SAPI: cli, micro, fpm, embed"); + $this->output->writeln("\t--build-frankenphp\tBuild FrankenPHP SAPI/libphp"); + $this->output->writeln("\t--build-all\tBuild all SAPI: cli, micro, fpm, embed, frankenphp"); return static::FAILURE; } if ($rule === BUILD_TARGET_ALL) { @@ -304,6 +306,7 @@ class BuildPHPCommand extends BuildCommand $rule |= ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-embed') || !empty($shared_extensions) ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE); + $rule |= ($this->getOption('build-frankenphp') || !empty($shared_extensions) ? BUILD_TARGET_FRANKENPHP : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE); return $rule; } diff --git a/src/SPC/doctor/AsCheckItem.php b/src/SPC/doctor/AsCheckItem.php index f64d914b..0417bcfa 100644 --- a/src/SPC/doctor/AsCheckItem.php +++ b/src/SPC/doctor/AsCheckItem.php @@ -14,5 +14,6 @@ class AsCheckItem public ?string $limit_os = null, public int $level = 100, public bool $manual = false, - ) {} + ) { + } } diff --git a/src/SPC/doctor/item/BSDToolCheckList.php b/src/SPC/doctor/item/BSDToolCheckList.php index 2505227b..97f0ccf9 100644 --- a/src/SPC/doctor/item/BSDToolCheckList.php +++ b/src/SPC/doctor/item/BSDToolCheckList.php @@ -47,6 +47,12 @@ class BSDToolCheckList return CheckResult::ok(); } + #[AsCheckItem('if xcaddy is installed', limit_os: 'BSD')] + public function checkXcaddy(): ?CheckResult + { + return $this->checkGoAndXcaddy(); + } + #[AsFixItem('build-tools-bsd')] public function fixBuildTools(array $missing): bool { diff --git a/src/SPC/doctor/item/LinuxToolCheckList.php b/src/SPC/doctor/item/LinuxToolCheckList.php index 56235b0c..07f6b5fb 100644 --- a/src/SPC/doctor/item/LinuxToolCheckList.php +++ b/src/SPC/doctor/item/LinuxToolCheckList.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace SPC\doctor\item; use SPC\builder\linux\SystemUtil; +use SPC\builder\traits\UnixGoCheckTrait; use SPC\builder\traits\UnixSystemUtilTrait; use SPC\doctor\AsCheckItem; use SPC\doctor\AsFixItem; @@ -14,6 +15,7 @@ use SPC\exception\RuntimeException; class LinuxToolCheckList { use UnixSystemUtilTrait; + use UnixGoCheckTrait; public const TOOLS_ALPINE = [ 'make', 'bison', 'flex', @@ -87,6 +89,12 @@ class LinuxToolCheckList return CheckResult::ok(); } + #[AsCheckItem('if xcaddy is installed', limit_os: 'Linux')] + public function checkXcaddy(): ?CheckResult + { + return $this->checkGoAndXcaddy(); + } + #[AsCheckItem('if cmake version >= 3.18', limit_os: 'Linux')] public function checkCMakeVersion(): ?CheckResult { diff --git a/src/SPC/doctor/item/MacOSToolCheckList.php b/src/SPC/doctor/item/MacOSToolCheckList.php index b4043a1d..57ba8157 100644 --- a/src/SPC/doctor/item/MacOSToolCheckList.php +++ b/src/SPC/doctor/item/MacOSToolCheckList.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace SPC\doctor\item; +use SPC\builder\traits\UnixGoCheckTrait; use SPC\builder\traits\UnixSystemUtilTrait; use SPC\doctor\AsCheckItem; use SPC\doctor\AsFixItem; @@ -13,6 +14,7 @@ use SPC\exception\RuntimeException; class MacOSToolCheckList { use UnixSystemUtilTrait; + use UnixGoCheckTrait; /** @var string[] MacOS 环境下编译依赖的命令 */ public const REQUIRED_COMMANDS = [ @@ -34,6 +36,12 @@ class MacOSToolCheckList 'glibtoolize', ]; + #[AsCheckItem('if xcaddy is installed', limit_os: 'Darwin')] + public function checkXcaddy(): ?CheckResult + { + return $this->checkGoAndXcaddy(); + } + #[AsCheckItem('if homebrew has installed', limit_os: 'Darwin', level: 998)] public function checkBrew(): ?CheckResult { diff --git a/src/globals/defines.php b/src/globals/defines.php index eab2fcc4..aebd4d5f 100644 --- a/src/globals/defines.php +++ b/src/globals/defines.php @@ -62,7 +62,8 @@ const BUILD_TARGET_CLI = 1; // build cli const BUILD_TARGET_MICRO = 2; // build micro const BUILD_TARGET_FPM = 4; // build fpm const BUILD_TARGET_EMBED = 8; // build embed -const BUILD_TARGET_ALL = 15; // build all +const BUILD_TARGET_FRANKENPHP = BUILD_TARGET_EMBED | 16; // build frankenphp +const BUILD_TARGET_ALL = BUILD_TARGET_CLI | BUILD_TARGET_MICRO | BUILD_TARGET_FPM | BUILD_TARGET_EMBED | BUILD_TARGET_FRANKENPHP; // build all // doctor error fix policy const FIX_POLICY_DIE = 1; // die directly From f64eb0dea5e3ee609de49d7127aa5c5c0297c752 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 11:20:05 +0700 Subject: [PATCH 18/70] build for bsd and macos too --- src/SPC/builder/freebsd/BSDBuilder.php | 5 ++++ src/SPC/builder/linux/LinuxBuilder.php | 32 +++++++++++++------------- src/SPC/builder/macos/MacOSBuilder.php | 5 ++++ src/SPC/command/BuildPHPCommand.php | 2 +- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/SPC/builder/freebsd/BSDBuilder.php b/src/SPC/builder/freebsd/BSDBuilder.php index 04fd43d3..65ebea57 100644 --- a/src/SPC/builder/freebsd/BSDBuilder.php +++ b/src/SPC/builder/freebsd/BSDBuilder.php @@ -96,6 +96,7 @@ class BSDBuilder extends UnixBuilderBase $enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM; $enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; $enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; + $enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP; shell()->cd(SOURCE_PATH . '/php-src') ->exec( @@ -143,6 +144,10 @@ class BSDBuilder extends UnixBuilderBase } $this->buildEmbed(); } + if ($enableFrankenphp) { + logger()->info('building frankenphp'); + $this->buildFrankenphp(); + } } public function testPHP(int $build_target = BUILD_TARGET_NONE) diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index fc131214..addd2f57 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -110,11 +110,11 @@ class LinuxBuilder extends UnixBuilderBase $config_file_scan_dir = $this->getOption('with-config-file-scan-dir', false) ? ('--with-config-file-scan-dir=' . $this->getOption('with-config-file-scan-dir') . ' ') : ''; - $enable_cli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI; - $enable_fpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM; - $enable_micro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; - $enable_embed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; - $enable_frankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP; + $enableCli = ($build_target & BUILD_TARGET_CLI) === BUILD_TARGET_CLI; + $enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM; + $enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; + $enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; + $enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP; $mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : ''; // prepare build php envs @@ -126,7 +126,7 @@ class LinuxBuilder extends UnixBuilderBase ]); // process micro upx patch if micro sapi enabled - if ($enable_micro) { + if ($enableMicro) { if (version_compare($this->getMicroVersion(), '0.2.0') < 0) { // for phpmicro 0.1.x $this->processMicroUPXLegacy(); @@ -138,10 +138,10 @@ class LinuxBuilder extends UnixBuilderBase shell()->cd(SOURCE_PATH . '/php-src') ->exec( getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' . - ($enable_cli ? '--enable-cli ' : '--disable-cli ') . - ($enable_fpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') . - ($enable_embed ? "--enable-embed={$embed_type} " : '--disable-embed ') . - ($enable_micro ? '--enable-micro=all-static ' : '--disable-micro ') . + ($enableCli ? '--enable-cli ' : '--disable-cli ') . + ($enableFpm ? '--enable-fpm ' . ($this->getLib('libacl') !== null ? '--with-fpm-acl ' : '') : '--disable-fpm ') . + ($enableEmbed ? "--enable-embed={$embed_type} " : '--disable-embed ') . + ($enableMicro ? '--enable-micro=all-static ' : '--disable-micro ') . $config_file_path . $config_file_scan_dir . $disable_jit . @@ -157,26 +157,26 @@ class LinuxBuilder extends UnixBuilderBase $this->cleanMake(); - if ($enable_cli) { + if ($enableCli) { logger()->info('building cli'); $this->buildCli(); } - if ($enable_fpm) { + if ($enableFpm) { logger()->info('building fpm'); $this->buildFpm(); } - if ($enable_micro) { + if ($enableMicro) { logger()->info('building micro'); $this->buildMicro(); } - if ($enable_embed) { + if ($enableEmbed) { logger()->info('building embed'); - if ($enable_micro) { + if ($enableMicro) { FileSystem::replaceFileStr(SOURCE_PATH . '/php-src/Makefile', 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la'); } $this->buildEmbed(); } - if ($enable_frankenphp) { + if ($enableFrankenphp) { logger()->info('building frankenphp'); $this->buildFrankenphp(); } diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index a0522120..153da08c 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -122,6 +122,7 @@ class MacOSBuilder extends UnixBuilderBase $enableFpm = ($build_target & BUILD_TARGET_FPM) === BUILD_TARGET_FPM; $enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; $enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; + $enableFrankenphp = ($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP; // prepare build php envs $mimallocLibs = $this->getLib('mimalloc') !== null ? BUILD_LIB_PATH . '/mimalloc.o ' : ''; @@ -180,6 +181,10 @@ class MacOSBuilder extends UnixBuilderBase } $this->buildEmbed(); } + if ($enableFrankenphp) { + logger()->info('building frankenphp'); + $this->buildFrankenphp(); + } } public function testPHP(int $build_target = BUILD_TARGET_NONE) diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 0ef9dab3..96a93653 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -306,7 +306,7 @@ class BuildPHPCommand extends BuildCommand $rule |= ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-embed') || !empty($shared_extensions) ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE); - $rule |= ($this->getOption('build-frankenphp') || !empty($shared_extensions) ? BUILD_TARGET_FRANKENPHP : BUILD_TARGET_NONE); + $rule |= ($this->getOption('build-frankenphp') ? BUILD_TARGET_FRANKENPHP : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE); return $rule; } From c1e68323c70e412c286650fe76d0c7e6eddd85de Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 11:21:50 +0700 Subject: [PATCH 19/70] cs fix --- src/SPC/doctor/AsCheckItem.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/SPC/doctor/AsCheckItem.php b/src/SPC/doctor/AsCheckItem.php index 0417bcfa..f64d914b 100644 --- a/src/SPC/doctor/AsCheckItem.php +++ b/src/SPC/doctor/AsCheckItem.php @@ -14,6 +14,5 @@ class AsCheckItem public ?string $limit_os = null, public int $level = 100, public bool $manual = false, - ) { - } + ) {} } From 92338d478e470d34d1fc63b5039513799f650b2e Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 11:30:04 +0700 Subject: [PATCH 20/70] don't bake the rpath in, otherwise we might run into issues when loading frankenphp after compiling a different version --- src/SPC/builder/unix/UnixBuilderBase.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 19880bf1..666c09c8 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -289,14 +289,16 @@ abstract class UnixBuilderBase extends BuilderBase f_putenv("PATH={$path}"); $brotliLibs = $this->getLib('brotli') !== null ? '-lbrotlienc -lbrotlidec -lbrotlicommon' : ''; + $watcherLibs = $this->getLib('brotli') !== null ? '-lwatcher-c' : ''; $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; $env = [ 'CGO_ENABLED' => '1', 'CGO_CFLAGS' => '$(php-config --includes) -I$(php-config --include-dir)/..', - 'CGO_LDFLAGS' => "$(php-config --ldflags) $(php-config --libs) {$brotliLibs} -lwatcher-c -lphp -Wl,-rpath=" . BUILD_LIB_PATH, + 'CGO_LDFLAGS' => "$(php-config --ldflags) $(php-config --libs) {$brotliLibs} {$watcherLibs} -lphp", 'XCADDY_GO_BUILD_FLAGS' => "-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" . $nobrotli . $nowatcher, + 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, ]; shell()->cd(BUILD_BIN_PATH) ->setEnv($env) From c46f8513dd15b8bda79ae7651417ff46dfcace73 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 11:34:05 +0700 Subject: [PATCH 21/70] watcher... --- src/SPC/builder/unix/UnixBuilderBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 666c09c8..4ef2502b 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -289,7 +289,7 @@ abstract class UnixBuilderBase extends BuilderBase f_putenv("PATH={$path}"); $brotliLibs = $this->getLib('brotli') !== null ? '-lbrotlienc -lbrotlidec -lbrotlicommon' : ''; - $watcherLibs = $this->getLib('brotli') !== null ? '-lwatcher-c' : ''; + $watcherLibs = $this->getLib('watcher') !== null ? '-lwatcher-c' : ''; $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; From abf3bfb98e8acf1ed6b5cd8afc2d4236697ad641 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 11:36:58 +0700 Subject: [PATCH 22/70] suggest watcher --- config/lib.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/lib.json b/config/lib.json index dc792c3b..ba76f4a9 100644 --- a/config/lib.json +++ b/config/lib.json @@ -12,8 +12,12 @@ "lib-base", "micro" ], + "lib-suggests-unix": [ + "watcher" + ], "lib-suggests-linux": [ - "libacl" + "libacl", + "watcher" ] }, "micro": { From dca43d6d8d3d7a24cc85d9f9cdded0a0590ec327 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 11:39:22 +0700 Subject: [PATCH 23/70] nicer escaping --- src/SPC/builder/unix/UnixBuilderBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 4ef2502b..39ea69ec 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -297,7 +297,7 @@ abstract class UnixBuilderBase extends BuilderBase 'CGO_ENABLED' => '1', 'CGO_CFLAGS' => '$(php-config --includes) -I$(php-config --include-dir)/..', 'CGO_LDFLAGS' => "$(php-config --ldflags) $(php-config --libs) {$brotliLibs} {$watcherLibs} -lphp", - 'XCADDY_GO_BUILD_FLAGS' => "-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" . $nobrotli . $nowatcher, + 'XCADDY_GO_BUILD_FLAGS' => "-ldflags='-w -s' -tags=nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, ]; shell()->cd(BUILD_BIN_PATH) From d635b10e248a9c6adc1ec36d70c970333ac4e807 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 11:47:05 +0700 Subject: [PATCH 24/70] specify system gcc to build xcaddy in spc-gnu-docker --- bin/spc-gnu-docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index b69e0710..9583b4dd 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -94,7 +94,7 @@ ENV PATH="/app/bin:/cmake/bin:$PATH" ENV SPC_LIBC=glibc ADD ./config/env.ini /app/config/env.ini -RUN bin/spc doctor --auto-fix --debug +RUN CC=gcc bin/spc doctor --auto-fix --debug RUN curl -o make.tgz -fsSL https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && \ tar -zxvf make.tgz && \ From d094824d76132a6de6c23a82f02416aafa63a492 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 11:54:03 +0700 Subject: [PATCH 25/70] --with github.com/dunglas/caddy-cbrotli requires brotli --- config/env.ini | 2 +- src/SPC/builder/unix/UnixBuilderBase.php | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/config/env.ini b/config/env.ini index 112d0187..03e07b47 100644 --- a/config/env.ini +++ b/config/env.ini @@ -43,7 +43,7 @@ SPC_SKIP_PHP_VERSION_CHECK="no" ; Ignore some check item for bin/spc doctor command, comma separated (e.g. SPC_SKIP_DOCTOR_CHECK_ITEMS="if homebrew has installed") SPC_SKIP_DOCTOR_CHECK_ITEMS="" ; extra modules that xcaddy will include in the FrankenPHP build -SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" +SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" ; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them ; only useful for builds targeting not pure-static linking diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 39ea69ec..ac493b73 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -292,6 +292,10 @@ abstract class UnixBuilderBase extends BuilderBase $watcherLibs = $this->getLib('watcher') !== null ? '-lwatcher-c' : ''; $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; + $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); + if ($this->getLib('brotli') !== null && !str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { + $xcaddyModules .= ' --with github.com/dunglas/caddy-cbrotli'; + } $env = [ 'CGO_ENABLED' => '1', @@ -302,11 +306,6 @@ abstract class UnixBuilderBase extends BuilderBase ]; shell()->cd(BUILD_BIN_PATH) ->setEnv($env) - ->exec( - 'xcaddy build ' . - '--output frankenphp ' . - '--with github.com/dunglas/frankenphp/caddy ' . - getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES') - ); + ->exec('xcaddy build --output frankenphp --with github.com/dunglas/frankenphp/caddy ' . $xcaddyModules); } } From e71f76288b21ace475673ad29313e63fabf7b335 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 12:02:37 +0700 Subject: [PATCH 26/70] support building static frankenphp --- src/SPC/builder/unix/UnixBuilderBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index ac493b73..92ee2518 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -300,7 +300,7 @@ abstract class UnixBuilderBase extends BuilderBase $env = [ 'CGO_ENABLED' => '1', 'CGO_CFLAGS' => '$(php-config --includes) -I$(php-config --include-dir)/..', - 'CGO_LDFLAGS' => "$(php-config --ldflags) $(php-config --libs) {$brotliLibs} {$watcherLibs} -lphp", + 'CGO_LDFLAGS' => '$(php-config --ldflags) -L' . BUILD_LIB_PATH . " $(php-config --libs) {$brotliLibs} {$watcherLibs} -lphp -lrt", 'XCADDY_GO_BUILD_FLAGS' => "-ldflags='-w -s' -tags=nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, ]; From f37c863092804eb5fc57528e23fa0ca9f4948802 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 12:04:01 +0700 Subject: [PATCH 27/70] only needed on linux --- src/SPC/builder/unix/UnixBuilderBase.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 92ee2518..83de392c 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -296,11 +296,12 @@ abstract class UnixBuilderBase extends BuilderBase if ($this->getLib('brotli') !== null && !str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { $xcaddyModules .= ' --with github.com/dunglas/caddy-cbrotli'; } + $lrt = PHP_OS_FAMILY === 'Linux' ? '-lrt' : ''; $env = [ 'CGO_ENABLED' => '1', 'CGO_CFLAGS' => '$(php-config --includes) -I$(php-config --include-dir)/..', - 'CGO_LDFLAGS' => '$(php-config --ldflags) -L' . BUILD_LIB_PATH . " $(php-config --libs) {$brotliLibs} {$watcherLibs} -lphp -lrt", + 'CGO_LDFLAGS' => '$(php-config --ldflags) -L' . BUILD_LIB_PATH . " $(php-config --libs) {$brotliLibs} {$watcherLibs} -lphp {$lrt}", 'XCADDY_GO_BUILD_FLAGS' => "-ldflags='-w -s' -tags=nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, ]; From d58534b07d70e4d388f39961d66ec03e470882fe Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 12:19:33 +0700 Subject: [PATCH 28/70] add support for frankenphp directory from file system, instead of pulling latest xcaddy module --- config/env.ini | 2 +- src/SPC/builder/unix/UnixBuilderBase.php | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/config/env.ini b/config/env.ini index 03e07b47..7acc8730 100644 --- a/config/env.ini +++ b/config/env.ini @@ -43,7 +43,7 @@ SPC_SKIP_PHP_VERSION_CHECK="no" ; Ignore some check item for bin/spc doctor command, comma separated (e.g. SPC_SKIP_DOCTOR_CHECK_ITEMS="if homebrew has installed") SPC_SKIP_DOCTOR_CHECK_ITEMS="" ; extra modules that xcaddy will include in the FrankenPHP build -SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" +SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/frankenphp/caddy --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" ; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them ; only useful for builds targeting not pure-static linking diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 83de392c..758efaa6 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -293,8 +293,13 @@ abstract class UnixBuilderBase extends BuilderBase $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); - if ($this->getLib('brotli') !== null && !str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { - $xcaddyModules .= ' --with github.com/dunglas/caddy-cbrotli'; + // make it possible to build from a different frankenphp directory! + if (!str_contains($xcaddyModules, '--with github.com/dunglas/frankenphp')) { + $xcaddyModules = '--with github.com/dunglas/frankenphp ' . $xcaddyModules; + } + if ($this->getLib('brotli') === null && str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { + logger()->warning('caddy-cbrotli module is enabled, but broli library is not built. Disabling caddy-cbrotli.'); + $xcaddyModules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddyModules); } $lrt = PHP_OS_FAMILY === 'Linux' ? '-lrt' : ''; @@ -307,6 +312,6 @@ abstract class UnixBuilderBase extends BuilderBase ]; shell()->cd(BUILD_BIN_PATH) ->setEnv($env) - ->exec('xcaddy build --output frankenphp --with github.com/dunglas/frankenphp/caddy ' . $xcaddyModules); + ->exec('xcaddy build --output frankenphp ' . $xcaddyModules); } } From 82ee6f0dee53815f09f258c7f4c92feaae26a428 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 12:35:48 +0700 Subject: [PATCH 29/70] allow specifying if we want to build embed shared or static --- src/SPC/command/BuildPHPCommand.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 96a93653..72d6a686 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -32,7 +32,7 @@ class BuildPHPCommand extends BuildCommand $this->addOption('build-micro', null, null, 'Build micro SAPI'); $this->addOption('build-cli', null, null, 'Build cli SAPI'); $this->addOption('build-fpm', null, null, 'Build fpm SAPI (not available on Windows)'); - $this->addOption('build-embed', null, null, 'Build embed SAPI (not available on Windows)'); + $this->addOption('build-embed', null, InputOption::VALUE_OPTIONAL, 'Build embed SAPI (not available on Windows)'); $this->addOption('build-frankenphp', null, null, 'Build FrankenPHP SAPI (not available on Windows)'); $this->addOption('build-all', null, null, 'Build all SAPI'); $this->addOption('no-strip', null, null, 'build without strip, in order to debug and load external extensions'); @@ -305,7 +305,17 @@ class BuildPHPCommand extends BuildCommand $rule |= ($this->getOption('build-cli') ? BUILD_TARGET_CLI : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-micro') ? BUILD_TARGET_MICRO : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-fpm') ? BUILD_TARGET_FPM : BUILD_TARGET_NONE); - $rule |= ($this->getOption('build-embed') || !empty($shared_extensions) ? BUILD_TARGET_EMBED : BUILD_TARGET_NONE); + $embed = $this->getOption('build-embed'); + if (!$embed && !empty($shared_extensions)) { + $embed = true; + } + if ($embed) { + if ($embed === true) { + $embed = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static'; + } + $rule |= BUILD_TARGET_EMBED; + f_putenv('SPC_CMD_VAR_PHP_EMBED_TYPE=' . ($embed === 'static' ? 'static' : 'shared')); + } $rule |= ($this->getOption('build-frankenphp') ? BUILD_TARGET_FRANKENPHP : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE); return $rule; From a1e76d9d02e928cca769743e8e63828ca475f742 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 12:41:27 +0700 Subject: [PATCH 30/70] remove watcher suggestion --- config/lib.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/config/lib.json b/config/lib.json index ba76f4a9..dc792c3b 100644 --- a/config/lib.json +++ b/config/lib.json @@ -12,12 +12,8 @@ "lib-base", "micro" ], - "lib-suggests-unix": [ - "watcher" - ], "lib-suggests-linux": [ - "libacl", - "watcher" + "libacl" ] }, "micro": { From ba0ea5b40af810726a615dde1786c118927bb1bc Mon Sep 17 00:00:00 2001 From: Jerry Ma Date: Wed, 18 Jun 2025 14:05:43 +0800 Subject: [PATCH 31/70] Refactor lock component to a single class (#773) --- src/SPC/builder/BuilderBase.php | 11 +- src/SPC/builder/LibraryBase.php | 6 +- src/SPC/command/DeleteDownloadCommand.php | 14 +- src/SPC/command/SwitchPhpVersionCommand.php | 12 +- src/SPC/command/dev/PackLibCommand.php | 4 +- src/SPC/store/Downloader.php | 103 ++------- src/SPC/store/LockFile.php | 227 ++++++++++++++++++++ src/SPC/store/PackageManager.php | 9 +- src/SPC/store/SourceManager.php | 42 ++-- tests/SPC/store/DownloaderTest.php | 3 +- 10 files changed, 284 insertions(+), 147 deletions(-) create mode 100644 src/SPC/store/LockFile.php diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index 7fc78e44..e9caabfc 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -12,6 +12,7 @@ use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\FileSystem; +use SPC\store\LockFile; use SPC\store\SourceManager; use SPC\util\CustomExt; @@ -350,15 +351,11 @@ abstract class BuilderBase public function getPHPVersionFromArchive(?string $file = null): false|string { if ($file === null) { - $lock = file_exists(DOWNLOAD_PATH . '/.lock.json') ? file_get_contents(DOWNLOAD_PATH . '/.lock.json') : false; - if ($lock === false) { - return false; - } - $lock = json_decode($lock, true); - $file = $lock['php-src']['filename'] ?? null; - if ($file === null) { + $lock = LockFile::get('php-src'); + if ($lock === null) { return false; } + $file = LockFile::getLockFullPath($lock); } if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) { return $match[1]; diff --git a/src/SPC/builder/LibraryBase.php b/src/SPC/builder/LibraryBase.php index ac2d8c5c..a130e8a8 100644 --- a/src/SPC/builder/LibraryBase.php +++ b/src/SPC/builder/LibraryBase.php @@ -10,6 +10,7 @@ use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\Downloader; use SPC\store\FileSystem; +use SPC\store\LockFile; use SPC\store\SourceManager; use SPC\util\GlobalValueTrait; @@ -46,12 +47,11 @@ abstract class LibraryBase */ public function setup(bool $force = false): int { - $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; $source = Config::getLib(static::NAME, 'source'); // if source is locked as pre-built, we just tryInstall it $pre_built_name = Downloader::getPreBuiltLockName($source); - if (isset($lock[$pre_built_name]) && ($lock[$pre_built_name]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) { - return $this->tryInstall($lock[$pre_built_name], $force); + if (($lock = LockFile::get($pre_built_name)) && $lock['lock_as'] === SPC_DOWNLOAD_PRE_BUILT) { + return $this->tryInstall($lock, $force); } return $this->tryBuild($force); } diff --git a/src/SPC/command/DeleteDownloadCommand.php b/src/SPC/command/DeleteDownloadCommand.php index 12b0b420..0306de4c 100644 --- a/src/SPC/command/DeleteDownloadCommand.php +++ b/src/SPC/command/DeleteDownloadCommand.php @@ -9,6 +9,7 @@ use SPC\exception\FileSystemException; use SPC\exception\WrongUsageException; use SPC\store\Downloader; use SPC\store\FileSystem; +use SPC\store\LockFile; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -46,29 +47,29 @@ class DeleteDownloadCommand extends BaseCommand return static::SUCCESS; } $chosen_sources = $sources; - $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; $deleted_sources = []; foreach ($chosen_sources as $source) { $source = trim($source); foreach ([$source, Downloader::getPreBuiltLockName($source)] as $name) { - if (isset($lock[$name])) { + if (LockFile::get($name)) { $deleted_sources[] = $name; } } } foreach ($deleted_sources as $lock_name) { + $lock = LockFile::get($lock_name); // remove download file/dir if exists - if ($lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE) { - if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']))) { + if ($lock['source_type'] === SPC_SOURCE_ARCHIVE) { + if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['filename']))) { logger()->info('Deleting file ' . $path); unlink($path); } else { logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file."); } } else { - if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname']))) { + if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['dirname']))) { logger()->info('Deleting dir ' . $path); FileSystem::removeDir($path); } else { @@ -76,9 +77,8 @@ class DeleteDownloadCommand extends BaseCommand } } // remove locked sources - unset($lock[$lock_name]); + LockFile::put($lock_name, null); } - FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); logger()->info('Delete success!'); return static::SUCCESS; } catch (DownloaderException $e) { diff --git a/src/SPC/command/SwitchPhpVersionCommand.php b/src/SPC/command/SwitchPhpVersionCommand.php index ef463ef4..30ee0c79 100644 --- a/src/SPC/command/SwitchPhpVersionCommand.php +++ b/src/SPC/command/SwitchPhpVersionCommand.php @@ -7,6 +7,7 @@ namespace SPC\command; use SPC\store\Config; use SPC\store\Downloader; use SPC\store\FileSystem; +use SPC\store\LockFile; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -40,16 +41,9 @@ class SwitchPhpVersionCommand extends BaseCommand } } - // detect if downloads/.lock.json exists - $lock_file = DOWNLOAD_PATH . '/.lock.json'; - // parse php-src part of lock file - $lock_data = json_decode(file_get_contents($lock_file), true); - // get php-src downloaded file name - $php_src = $lock_data['php-src']; - $file = DOWNLOAD_PATH . '/' . ($php_src['filename'] ?? '.donot.delete.me'); - if (file_exists($file)) { + if (LockFile::isLockFileExists('php-src')) { $this->output->writeln('Removing old PHP source...'); - unlink($file); + LockFile::put('php-src', null); } // Download new PHP source diff --git a/src/SPC/command/dev/PackLibCommand.php b/src/SPC/command/dev/PackLibCommand.php index 924c3171..d0d9797e 100644 --- a/src/SPC/command/dev/PackLibCommand.php +++ b/src/SPC/command/dev/PackLibCommand.php @@ -14,6 +14,7 @@ use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\FileSystem; +use SPC\store\LockFile; use SPC\util\DependencyUtil; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; @@ -47,9 +48,8 @@ class PackLibCommand extends BuildCommand $lib->setup(); } else { // Get lock info - $lock = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; $source = Config::getLib($lib->getName(), 'source'); - if (!isset($lock[$source]) || ($lock[$source]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) { + if (($lock = LockFile::get($source)) === null || ($lock['lock_as'] === SPC_DOWNLOAD_PRE_BUILT)) { logger()->critical("The library {$lib->getName()} is downloaded as pre-built, we need to build it instead of installing pre-built."); return static::FAILURE; } diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index b0c663d3..919d1f15 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -208,34 +208,7 @@ class Downloader if ($download_as === SPC_DOWNLOAD_PRE_BUILT) { $name = self::getPreBuiltLockName($name); } - self::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]); - } - - /** - * Try to lock source. - * - * @param string $name Source name - * @param array{ - * source_type: string, - * dirname: ?string, - * filename: ?string, - * move_path: ?string, - * lock_as: int - * } $data Source data - * @throws FileSystemException - */ - public static function lockSource(string $name, array $data): void - { - if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.lock.json'))) { - $lock = []; - } else { - $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; - } - // calculate hash - $hash = self::getLockSourceHash($data); - $data['hash'] = $hash; - $lock[$name] = $data; - FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + LockFile::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]); } /** @@ -281,7 +254,7 @@ class Downloader } // Lock logger()->debug("Locking git source {$name}"); - self::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]); + LockFile::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]); /* // 复制目录过去 @@ -377,7 +350,7 @@ class Downloader case 'local': // Local directory, do nothing, just lock it logger()->debug("Locking local source {$name}"); - self::lockSource($name, [ + LockFile::lockSource($name, [ 'source_type' => SPC_SOURCE_LOCAL, 'dirname' => $pkg['dirname'], 'move_path' => $pkg['extract'] ?? null, @@ -493,7 +466,7 @@ class Downloader case 'local': // Local directory, do nothing, just lock it logger()->debug("Locking local source {$name}"); - self::lockSource($name, [ + LockFile::lockSource($name, [ 'source_type' => SPC_SOURCE_LOCAL, 'dirname' => $source['dirname'], 'move_path' => $source['extract'] ?? null, @@ -617,43 +590,6 @@ class Downloader return "{$source}-" . PHP_OS_FAMILY . '-' . getenv('GNU_ARCH') . '-' . (getenv('SPC_LIBC') ?: 'default') . '-' . (SystemUtil::getLibcVersionIfExists() ?? 'default'); } - /** - * Get the hash of the lock source based on the lock options. - * - * @param array $lock_options Lock options - * @return string Hash of the lock source - * @throws RuntimeException - */ - public static function getLockSourceHash(array $lock_options): string - { - $result = match ($lock_options['source_type']) { - SPC_SOURCE_ARCHIVE => sha1_file(DOWNLOAD_PATH . '/' . $lock_options['filename']), - SPC_SOURCE_GIT => exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $lock_options['dirname']) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD'), - SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT', - default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new RuntimeException("Unknown source type: {$lock_options['source_type']}"), - }; - if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) { - throw new RuntimeException("Failed to get hash for source: {$lock_options['source_type']}"); - } - return $result ?: ''; - } - - /** - * @param array $lock_options Lock options - * @param string $destination Target directory - * @throws FileSystemException - * @throws RuntimeException - */ - public static function putLockSourceHash(array $lock_options, string $destination): void - { - $hash = self::getLockSourceHash($lock_options); - if ($lock_options['source_type'] === SPC_SOURCE_LOCAL) { - logger()->debug("Source [{$lock_options['dirname']}] is local, no hash will be written."); - return; - } - FileSystem::writeFile("{$destination}/.spc-hash", $hash); - } - /** * Register CTRL+C event for different OS. * @@ -689,33 +625,24 @@ class Downloader /** * @throws FileSystemException + * @throws WrongUsageException */ private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool { - if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) { - $lock = []; - } else { - $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? []; - } - // If lock file exists, skip downloading for source mode - if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && isset($lock[$name])) { - if ( - $lock[$name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename']) || - $lock[$name]['source_type'] === SPC_SOURCE_GIT && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname']) - ) { - logger()->notice("Source [{$name}] already downloaded: " . ($lock[$name]['filename'] ?? $lock[$name]['dirname'])); + // If the lock file exists, skip downloading for source mode + $lock_item = LockFile::get($name); + if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && $lock_item !== null) { + if (file_exists($path = LockFile::getLockFullPath($lock_item))) { + logger()->notice("Source [{$name}] already downloaded: {$path}"); return true; } } - // If lock file exists for current arch and glibc target, skip downloading - - if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && isset($lock[$lock_name = self::getPreBuiltLockName($name)])) { + $lock_name = self::getPreBuiltLockName($name); + $lock_item = LockFile::get($lock_name); + if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && $lock_item !== null) { // lock name with env - if ( - $lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']) || - $lock[$lock_name]['source_type'] === SPC_SOURCE_GIT && is_dir(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname']) - ) { - logger()->notice("Pre-built content [{$name}] already downloaded: " . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname'])); + if (file_exists($path = LockFile::getLockFullPath($lock_item))) { + logger()->notice("Pre-built content [{$name}] already downloaded: {$path}"); return true; } } diff --git a/src/SPC/store/LockFile.php b/src/SPC/store/LockFile.php new file mode 100644 index 00000000..45e3a94c --- /dev/null +++ b/src/SPC/store/LockFile.php @@ -0,0 +1,227 @@ +warning("Lock entry for '{$lock_name}' has 'source_type' set to 'dir', which is deprecated. Please re-download your dependencies."); + $result['source_type'] = SPC_SOURCE_GIT; + } + + return $result; + } + + /** + * Check if a lock file exists for a given lock name. + * + * @param string $lock_name Lock name to check + */ + public static function isLockFileExists(string $lock_name): bool + { + return match (self::get($lock_name)['source_type'] ?? null) { + SPC_SOURCE_ARCHIVE => file_exists(DOWNLOAD_PATH . '/' . (self::get($lock_name)['filename'] ?? '.never-exist-file')), + SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => is_dir(DOWNLOAD_PATH . '/' . (self::get($lock_name)['dirname'] ?? '.never-exist-dir')), + default => false, + }; + } + + /** + * Put a lock entry into the lock file. + * + * @param string $lock_name Lock name to set or remove + * @param null|array $lock_content lock content to set, or null to remove the lock entry + * @throws FileSystemException + * @throws WrongUsageException + */ + public static function put(string $lock_name, ?array $lock_content): void + { + self::init(); + + $data = self::$lock_file_content; + if ($lock_content === null && isset($data[$lock_name])) { + self::removeLockFileIfExists($data[$lock_name]); + unset($data[$lock_name]); + } else { + $data[$lock_name] = $lock_content; + } + + // Write the updated lock data back to the file + file_put_contents(self::LOCK_FILE, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } + + /** + * Get the full path of a lock file or directory based on the lock options. + * + * @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname' + * @return string the absolute path to the lock file or directory + * @throws WrongUsageException + */ + public static function getLockFullPath(array $lock_options): string + { + return match ($lock_options['source_type']) { + SPC_SOURCE_ARCHIVE => FileSystem::isRelativePath($lock_options['filename']) ? (DOWNLOAD_PATH . '/' . $lock_options['filename']) : $lock_options['filename'], + SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => FileSystem::isRelativePath($lock_options['dirname']) ? (DOWNLOAD_PATH . '/' . $lock_options['dirname']) : $lock_options['dirname'], + default => throw new WrongUsageException("Unknown source type: {$lock_options['source_type']}"), + }; + } + + public static function getExtractPath(string $lock_name, string $default_path): ?string + { + $lock = self::get($lock_name); + if ($lock === null) { + return null; + } + + // If move_path is set, use it; otherwise, use the default extract directory + if (isset($lock['move_path'])) { + if (FileSystem::isRelativePath($lock['move_path'])) { + // If move_path is relative, prepend the default extract directory + return match ($lock['lock_as']) { + SPC_DOWNLOAD_SOURCE, SPC_DOWNLOAD_PRE_BUILT => FileSystem::convertPath(SOURCE_PATH . '/' . $lock['move_path']), + SPC_DOWNLOAD_PACKAGE => FileSystem::convertPath(PKG_ROOT_PATH . '/' . $lock['move_path']), + default => throw new WrongUsageException("Unknown lock type: {$lock['lock_as']}"), + }; + } + return FileSystem::convertPath($lock['move_path']); + } + return FileSystem::convertPath($default_path); + } + + /** + * Get the hash of the lock source based on the lock options. + * + * @param array $lock_options Lock options + * @return string Hash of the lock source + * @throws RuntimeException + */ + public static function getLockSourceHash(array $lock_options): string + { + $result = match ($lock_options['source_type']) { + SPC_SOURCE_ARCHIVE => sha1_file(DOWNLOAD_PATH . '/' . $lock_options['filename']), + SPC_SOURCE_GIT => exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $lock_options['dirname']) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD'), + SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT', + default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new RuntimeException("Unknown source type: {$lock_options['source_type']}"), + }; + if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) { + throw new RuntimeException("Failed to get hash for source: {$lock_options['source_type']}"); + } + return $result ?: ''; + } + + /** + * @param array $lock_options Lock options + * @param string $destination Target directory + * @throws FileSystemException + * @throws RuntimeException + */ + public static function putLockSourceHash(array $lock_options, string $destination): void + { + $hash = LockFile::getLockSourceHash($lock_options); + if ($lock_options['source_type'] === SPC_SOURCE_LOCAL) { + logger()->debug("Source [{$lock_options['dirname']}] is local, no hash will be written."); + return; + } + FileSystem::writeFile("{$destination}/.spc-hash", $hash); + } + + /** + * Try to lock source with hash. + * + * @param string $name Source name + * @param array{ + * source_type: string, + * dirname: ?string, + * filename: ?string, + * move_path: ?string, + * lock_as: int + * } $data Source data + * @throws FileSystemException + * @throws RuntimeException + * @throws WrongUsageException + */ + public static function lockSource(string $name, array $data): void + { + // calculate hash + $hash = LockFile::getLockSourceHash($data); + $data['hash'] = $hash; + self::put($name, $data); + } + + private static function init(): void + { + if (self::$lock_file_content === null) { + // Initialize the lock file content if it hasn't been loaded yet + if (!file_exists(self::LOCK_FILE)) { + logger()->debug('Lock file does not exist: ' . self::LOCK_FILE . ', initializing empty lock file.'); + self::$lock_file_content = []; + file_put_contents(self::LOCK_FILE, json_encode(self::$lock_file_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } else { + $file_content = file_get_contents(self::LOCK_FILE); + self::$lock_file_content = json_decode($file_content, true); + if (self::$lock_file_content === null) { + throw new \RuntimeException('Failed to decode lock file: ' . self::LOCK_FILE); + } + } + } + } + + /** + * Remove the lock file or directory if it exists. + * + * @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname' + * @throws WrongUsageException + * @throws FileSystemException + */ + private static function removeLockFileIfExists(array $lock_options): void + { + if ($lock_options['source_type'] === SPC_SOURCE_ARCHIVE) { + $path = self::getLockFullPath($lock_options); + if (file_exists($path)) { + logger()->info('Removing file ' . $path); + unlink($path); + } else { + logger()->debug("Lock file [{$lock_options['filename']}] not found, skip removing file."); + } + } else { + $path = self::getLockFullPath($lock_options); + if (is_dir($path)) { + logger()->info('Removing directory ' . $path); + FileSystem::removeDir($path); + } else { + logger()->debug("Lock directory [{$lock_options['dirname']}] not found, skip removing directory."); + } + } + } +} diff --git a/src/SPC/store/PackageManager.php b/src/SPC/store/PackageManager.php index ca930228..ae2c9380 100644 --- a/src/SPC/store/PackageManager.php +++ b/src/SPC/store/PackageManager.php @@ -33,10 +33,11 @@ class PackageManager // Download package Downloader::downloadPackage($pkg_name, $config, $force); // After download, read lock file name - $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true); - $source_type = $lock[$pkg_name]['source_type']; - $filename = DOWNLOAD_PATH . '/' . ($lock[$pkg_name]['filename'] ?? $lock[$pkg_name]['dirname']); - $extract = $lock[$pkg_name]['move_path'] === null ? (PKG_ROOT_PATH . '/' . $pkg_name) : $lock[$pkg_name]['move_path']; + $lock = LockFile::get($pkg_name); + $source_type = $lock['source_type']; + $filename = LockFile::getLockFullPath($lock); + $extract = LockFile::getExtractPath($pkg_name, PKG_ROOT_PATH . '/' . $pkg_name); + FileSystem::extractPackage($pkg_name, $source_type, $filename, $extract); // if contains extract-files, we just move this file to destination, and remove extract dir diff --git a/src/SPC/store/SourceManager.php b/src/SPC/store/SourceManager.php index dd419e35..02d07263 100644 --- a/src/SPC/store/SourceManager.php +++ b/src/SPC/store/SourceManager.php @@ -17,11 +17,6 @@ class SourceManager */ public static function initSource(?array $sources = null, ?array $libs = null, ?array $exts = null, bool $source_only = false): void { - if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) { - throw new WrongUsageException('Download lock file "downloads/.lock.json" not found, maybe you need to download sources first ?'); - } - $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true); - $sources_extracted = []; // source check exist if (is_array($sources)) { @@ -56,8 +51,8 @@ class SourceManager } // check source downloaded $pre_built_name = Downloader::getPreBuiltLockName($source); - if ($source_only || !isset($lock[$pre_built_name])) { - if (!isset($lock[$source])) { + if ($source_only || LockFile::get($pre_built_name) === null) { + if (LockFile::get($source) === null) { throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !"); } $lock_name = $source; @@ -65,20 +60,23 @@ class SourceManager $lock_name = $pre_built_name; } + $lock_content = LockFile::get($lock_name); + // check source dir exist - $check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']); + $check = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source); + // $check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']); if (!is_dir($check)) { logger()->debug('Extracting source [' . $source . '] to ' . $check . ' ...'); - $filename = self::getSourceFullPath($lock[$lock_name]); - FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']); - Downloader::putLockSourceHash($lock[$lock_name], $check); + $filename = LockFile::getLockFullPath($lock_content); + FileSystem::extractSource($source, $lock_content['source_type'], $filename, $check); + LockFile::putLockSourceHash($lock_content, $check); continue; } // if a lock file does not have hash, calculate with the current source (backward compatibility) - if (!isset($lock[$lock_name]['hash'])) { - $hash = Downloader::getLockSourceHash($lock[$lock_name]); + if (!isset($lock_content['hash'])) { + $hash = LockFile::getLockSourceHash($lock_content); } else { - $hash = $lock[$lock_name]['hash']; + $hash = $lock_content['hash']; } // when source already extracted, detect if the extracted source hash is the same as the lock file one @@ -90,18 +88,10 @@ class SourceManager // if not, remove the source dir and extract again logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ..."); FileSystem::removeDir($check); - $filename = self::getSourceFullPath($lock[$lock_name]); - FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']); - Downloader::putLockSourceHash($lock[$lock_name], $check); + $filename = LockFile::getLockFullPath($lock_content); + $move_path = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source); + FileSystem::extractSource($source, $lock_content['source_type'], $filename, $move_path); + LockFile::putLockSourceHash($lock_content, $check); } } - - private static function getSourceFullPath(array $lock_options): string - { - return match ($lock_options['source_type']) { - SPC_SOURCE_ARCHIVE => FileSystem::isRelativePath($lock_options['filename']) ? (DOWNLOAD_PATH . '/' . $lock_options['filename']) : $lock_options['filename'], - SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => FileSystem::isRelativePath($lock_options['dirname']) ? (DOWNLOAD_PATH . '/' . $lock_options['dirname']) : $lock_options['dirname'], - default => throw new WrongUsageException("Unknown source type: {$lock_options['source_type']}"), - }; - } } diff --git a/tests/SPC/store/DownloaderTest.php b/tests/SPC/store/DownloaderTest.php index 5c15d42e..43de396a 100644 --- a/tests/SPC/store/DownloaderTest.php +++ b/tests/SPC/store/DownloaderTest.php @@ -7,6 +7,7 @@ namespace SPC\Tests\store; use PHPUnit\Framework\TestCase; use SPC\exception\WrongUsageException; use SPC\store\Downloader; +use SPC\store\LockFile; /** * @internal @@ -57,7 +58,7 @@ class DownloaderTest extends TestCase public function testLockSource() { - Downloader::lockSource('fake-file', ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']); + LockFile::lockSource('fake-file', ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']); $this->assertFileExists(DOWNLOAD_PATH . '/.lock.json'); $json = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true); $this->assertIsArray($json); From f10ba862188ecb56885dca84a379500577616907 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 14:18:01 +0700 Subject: [PATCH 32/70] add extension test for frankenphp --- src/globals/test-extensions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index cfde83f6..44243140 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -177,7 +177,7 @@ if ($argv[1] === 'build_cmd' || $argv[1] === 'build_embed_cmd') { $build_cmd .= $no_strip ? '--no-strip ' : ''; $build_cmd .= $upx ? '--with-upx-pack ' : ''; $build_cmd .= $final_libs === '' ? '' : ('--with-libs=' . quote2($final_libs) . ' '); - $build_cmd .= str_starts_with($argv[2], 'windows-') ? '' : '--build-fpm '; + $build_cmd .= str_starts_with($argv[2], 'windows-') ? '' : '--build-fpm --build-frankenphp'; $build_cmd .= '--debug '; } From 65b828c424b87dbc76c76a1acc24c084456ab4be Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 15:50:55 +0700 Subject: [PATCH 33/70] embed version information --- src/SPC/builder/unix/UnixBuilderBase.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 758efaa6..75e3d77d 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -12,6 +12,7 @@ use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; use SPC\store\Config; +use SPC\store\Downloader; use SPC\store\FileSystem; use SPC\util\DependencyUtil; use SPC\util\SPCConfigUtil; @@ -278,6 +279,10 @@ abstract class UnixBuilderBase extends BuilderBase } } + /** + * @throws WrongUsageException + * @throws RuntimeException + */ protected function buildFrankenphp(): void { $path = getenv('PATH'); @@ -302,12 +307,20 @@ abstract class UnixBuilderBase extends BuilderBase $xcaddyModules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddyModules); } $lrt = PHP_OS_FAMILY === 'Linux' ? '-lrt' : ''; + $releaseInfo = json_decode(Downloader::curlExec('https://api.github.com/repos/php/frankenphp/releases/latest'), true); + $frankenPhpVersion = $releaseInfo['tag_name']; + $libphpVersion = $this->getPHPVersion(); + $debugFlags = $this->getOption('--with-debug') ? "'-w -s' " : ''; $env = [ 'CGO_ENABLED' => '1', 'CGO_CFLAGS' => '$(php-config --includes) -I$(php-config --include-dir)/..', 'CGO_LDFLAGS' => '$(php-config --ldflags) -L' . BUILD_LIB_PATH . " $(php-config --libs) {$brotliLibs} {$watcherLibs} -lphp {$lrt}", - 'XCADDY_GO_BUILD_FLAGS' => "-ldflags='-w -s' -tags=nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", + 'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' . + '-ldflags \\"-linkmode=external -extldflags \'-pie\' '. $debugFlags . + '-X \'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ' . + "{$frankenPhpVersion} PHP {$libphpVersion} Caddy'\\\" " . + "-tags=nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, ]; shell()->cd(BUILD_BIN_PATH) From eee2ff6d614bdaaaccc737203de4b1937ddad626 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Wed, 18 Jun 2025 15:55:14 +0700 Subject: [PATCH 34/70] don't embed minor version when loading libphp.so --- src/SPC/builder/unix/UnixBuilderBase.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 75e3d77d..9801d47b 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -310,6 +310,9 @@ abstract class UnixBuilderBase extends BuilderBase $releaseInfo = json_decode(Downloader::curlExec('https://api.github.com/repos/php/frankenphp/releases/latest'), true); $frankenPhpVersion = $releaseInfo['tag_name']; $libphpVersion = $this->getPHPVersion(); + if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { + $libphpVersion = preg_replace('/\.\d$/', '', $libphpVersion); + } $debugFlags = $this->getOption('--with-debug') ? "'-w -s' " : ''; $env = [ From ae569316ff3234aadaa3331e9eccd8b583045120 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jun 2025 20:54:01 +0800 Subject: [PATCH 35/70] Remove go download from doctor --- src/SPC/builder/traits/UnixGoCheckTrait.php | 83 --------------------- src/SPC/doctor/item/BSDToolCheckList.php | 6 -- src/SPC/doctor/item/LinuxToolCheckList.php | 8 -- src/SPC/doctor/item/MacOSToolCheckList.php | 8 -- 4 files changed, 105 deletions(-) delete mode 100644 src/SPC/builder/traits/UnixGoCheckTrait.php diff --git a/src/SPC/builder/traits/UnixGoCheckTrait.php b/src/SPC/builder/traits/UnixGoCheckTrait.php deleted file mode 100644 index 12e3d005..00000000 --- a/src/SPC/builder/traits/UnixGoCheckTrait.php +++ /dev/null @@ -1,83 +0,0 @@ -findCommand('go', $paths) === null) { - $this->installGo(); - } - - $gobin = getenv('GOBIN') ?: (getenv('HOME') . '/go/bin'); - putenv("GOBIN={$gobin}"); - - $paths[] = $gobin; - - if ($this->findCommand('xcaddy', $paths) === null) { - shell(true)->exec('go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest'); - } - - return CheckResult::ok(); - } - - private function installGo(): bool - { - $prefix = ''; - if (get_current_user() !== 'root') { - $prefix = 'sudo '; - logger()->warning('Current user is not root, using sudo for running command'); - } - - $arch = php_uname('m'); - $go_arch = match ($arch) { - 'x86_64' => 'amd64', - 'aarch64' => 'arm64', - default => $arch - }; - $os = strtolower(PHP_OS_FAMILY); - - $go_version = '1.24.4'; - $go_filename = "go{$go_version}.{$os}-{$go_arch}.tar.gz"; - $go_url = "https://go.dev/dl/{$go_filename}"; - - logger()->info("Downloading Go {$go_version} for {$go_arch}"); - - try { - // Download Go binary - Downloader::downloadFile('go', $go_url, $go_filename); - - // Extract the tarball - FileSystem::extractSource('go', SPC_SOURCE_ARCHIVE, DOWNLOAD_PATH . "/{$go_filename}"); - - // Move to /usr/local/go - logger()->info('Installing Go to /usr/local/go'); - shell()->exec("{$prefix}rm -rf /usr/local/go"); - shell()->exec("{$prefix}mv " . SOURCE_PATH . '/go /usr/local/'); - - if (!str_contains(getenv('PATH'), '/usr/local/go/bin')) { - logger()->info('Adding Go to PATH'); - shell()->exec("{$prefix}echo 'export PATH=\$PATH:/usr/local/go/bin' >> /etc/profile"); - putenv('PATH=' . getenv('PATH') . ':/usr/local/go/bin'); - } - - logger()->info('Go has been installed successfully'); - return true; - } catch (RuntimeException $e) { - logger()->error('Failed to install Go: ' . $e->getMessage()); - return false; - } - } -} diff --git a/src/SPC/doctor/item/BSDToolCheckList.php b/src/SPC/doctor/item/BSDToolCheckList.php index 97f0ccf9..2505227b 100644 --- a/src/SPC/doctor/item/BSDToolCheckList.php +++ b/src/SPC/doctor/item/BSDToolCheckList.php @@ -47,12 +47,6 @@ class BSDToolCheckList return CheckResult::ok(); } - #[AsCheckItem('if xcaddy is installed', limit_os: 'BSD')] - public function checkXcaddy(): ?CheckResult - { - return $this->checkGoAndXcaddy(); - } - #[AsFixItem('build-tools-bsd')] public function fixBuildTools(array $missing): bool { diff --git a/src/SPC/doctor/item/LinuxToolCheckList.php b/src/SPC/doctor/item/LinuxToolCheckList.php index 07f6b5fb..56235b0c 100644 --- a/src/SPC/doctor/item/LinuxToolCheckList.php +++ b/src/SPC/doctor/item/LinuxToolCheckList.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace SPC\doctor\item; use SPC\builder\linux\SystemUtil; -use SPC\builder\traits\UnixGoCheckTrait; use SPC\builder\traits\UnixSystemUtilTrait; use SPC\doctor\AsCheckItem; use SPC\doctor\AsFixItem; @@ -15,7 +14,6 @@ use SPC\exception\RuntimeException; class LinuxToolCheckList { use UnixSystemUtilTrait; - use UnixGoCheckTrait; public const TOOLS_ALPINE = [ 'make', 'bison', 'flex', @@ -89,12 +87,6 @@ class LinuxToolCheckList return CheckResult::ok(); } - #[AsCheckItem('if xcaddy is installed', limit_os: 'Linux')] - public function checkXcaddy(): ?CheckResult - { - return $this->checkGoAndXcaddy(); - } - #[AsCheckItem('if cmake version >= 3.18', limit_os: 'Linux')] public function checkCMakeVersion(): ?CheckResult { diff --git a/src/SPC/doctor/item/MacOSToolCheckList.php b/src/SPC/doctor/item/MacOSToolCheckList.php index 57ba8157..b4043a1d 100644 --- a/src/SPC/doctor/item/MacOSToolCheckList.php +++ b/src/SPC/doctor/item/MacOSToolCheckList.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace SPC\doctor\item; -use SPC\builder\traits\UnixGoCheckTrait; use SPC\builder\traits\UnixSystemUtilTrait; use SPC\doctor\AsCheckItem; use SPC\doctor\AsFixItem; @@ -14,7 +13,6 @@ use SPC\exception\RuntimeException; class MacOSToolCheckList { use UnixSystemUtilTrait; - use UnixGoCheckTrait; /** @var string[] MacOS 环境下编译依赖的命令 */ public const REQUIRED_COMMANDS = [ @@ -36,12 +34,6 @@ class MacOSToolCheckList 'glibtoolize', ]; - #[AsCheckItem('if xcaddy is installed', limit_os: 'Darwin')] - public function checkXcaddy(): ?CheckResult - { - return $this->checkGoAndXcaddy(); - } - #[AsCheckItem('if homebrew has installed', limit_os: 'Darwin', level: 998)] public function checkBrew(): ?CheckResult { From 8e2dffc3b5cfcf1587a77fe8136ad6b5ff2551ab Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jun 2025 20:54:54 +0800 Subject: [PATCH 36/70] Add frankenphp sapi embed build at build command, not constant --- src/SPC/builder/BuilderBase.php | 23 +++++++++++++++++++++++ src/SPC/command/BuildPHPCommand.php | 5 ++++- src/globals/defines.php | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index 7fc78e44..fd4788f8 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -404,6 +404,9 @@ abstract class BuilderBase if (($type & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED) { $ls[] = 'embed'; } + if (($type & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) { + $ls[] = 'frankenphp'; + } return implode(', ', $ls); } @@ -510,6 +513,26 @@ abstract class BuilderBase } } + public function checkBeforeBuildPHP(int $rule): void + { + if (($rule & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) { + // frankenphp only support linux and macOS + if (!in_array(PHP_OS_FAMILY, ['Linux', 'Darwin'])) { + throw new WrongUsageException('FrankenPHP SAPI is only available on Linux and macOS!'); + } + // frankenphp needs package go-mod-frankenphp installed + $pkg_dir = PKG_ROOT_PATH . '/go-mod-frankenphp-' . arch2gnu(php_uname('m')) . '-' . osfamily2shortname(); + if (!file_exists("{$pkg_dir}/bin/go") || !file_exists("{$pkg_dir}/bin/xcaddy")) { + global $argv; + throw new WrongUsageException("FrankenPHP SAPI requires go-mod-frankenphp package, please install it first: {$argv[0]} install-pkg go-mod-frankenphp"); + } + // frankenphp needs libxml2 libs + if (!$this->getLib('libxml2')) { + throw new WrongUsageException('FrankenPHP SAPI requires libxml2 library, please include `xml` extension in your build.'); + } + } + } + /** * Generate micro extension test php code. */ diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 72d6a686..29cedf8f 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -196,6 +196,9 @@ class BuildPHPCommand extends BuildCommand // validate libs and extensions $builder->validateLibsAndExts(); + // check some things before building all the things + $builder->checkBeforeBuildPHP($rule); + // clean builds and sources if ($this->input->getOption('with-clean')) { logger()->info('Cleaning source and previous build dir...'); @@ -316,7 +319,7 @@ class BuildPHPCommand extends BuildCommand $rule |= BUILD_TARGET_EMBED; f_putenv('SPC_CMD_VAR_PHP_EMBED_TYPE=' . ($embed === 'static' ? 'static' : 'shared')); } - $rule |= ($this->getOption('build-frankenphp') ? BUILD_TARGET_FRANKENPHP : BUILD_TARGET_NONE); + $rule |= ($this->getOption('build-frankenphp') ? (BUILD_TARGET_FRANKENPHP | BUILD_TARGET_EMBED) : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE); return $rule; } diff --git a/src/globals/defines.php b/src/globals/defines.php index aebd4d5f..ab37ace9 100644 --- a/src/globals/defines.php +++ b/src/globals/defines.php @@ -62,7 +62,7 @@ const BUILD_TARGET_CLI = 1; // build cli const BUILD_TARGET_MICRO = 2; // build micro const BUILD_TARGET_FPM = 4; // build fpm const BUILD_TARGET_EMBED = 8; // build embed -const BUILD_TARGET_FRANKENPHP = BUILD_TARGET_EMBED | 16; // build frankenphp +const BUILD_TARGET_FRANKENPHP = 16; // build frankenphp const BUILD_TARGET_ALL = BUILD_TARGET_CLI | BUILD_TARGET_MICRO | BUILD_TARGET_FPM | BUILD_TARGET_EMBED | BUILD_TARGET_FRANKENPHP; // build all // doctor error fix policy From f709f3bb18406b50ac102326478b99f4e8ef6dd1 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jun 2025 20:55:24 +0800 Subject: [PATCH 37/70] Add custom package downloader and extractor --- src/SPC/store/Downloader.php | 22 ++++++++++++++++++---- src/SPC/store/PackageManager.php | 15 +++++++++++++++ src/SPC/store/pkg/CustomPackage.php | 17 +++++++++++++++++ src/globals/functions.php | 11 +++++++++++ 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/SPC/store/pkg/CustomPackage.php diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index b0c663d3..e5cc6aae 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -9,6 +9,7 @@ use SPC\exception\DownloaderException; use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; +use SPC\store\pkg\CustomPackage; use SPC\store\source\CustomSourceBase; /** @@ -385,10 +386,13 @@ class Downloader ]); break; case 'custom': // Custom download method, like API-based download or other - $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source'); + $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg'); foreach ($classes as $class) { - if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) { - (new $class())->fetch($force); + if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) { + $cls = new $class(); + if (in_array($name, $cls->getSupportName())) { + (new $class())->fetch($name, $force, $pkg); + } break; } } @@ -708,7 +712,6 @@ class Downloader } } // If lock file exists for current arch and glibc target, skip downloading - if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && isset($lock[$lock_name = self::getPreBuiltLockName($name)])) { // lock name with env if ( @@ -719,6 +722,17 @@ class Downloader return true; } } + + // If lock file exists, skip downloading for source mode + if (!$force && $download_as === SPC_DOWNLOAD_PACKAGE && isset($lock[$name])) { + if ( + $lock[$name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename']) || + $lock[$name]['source_type'] === SPC_SOURCE_GIT && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname']) + ) { + logger()->notice("Package [{$name}] already downloaded: " . ($lock[$name]['filename'] ?? $lock[$name]['dirname'])); + return true; + } + } return false; } } diff --git a/src/SPC/store/PackageManager.php b/src/SPC/store/PackageManager.php index ca930228..7e8ae3fd 100644 --- a/src/SPC/store/PackageManager.php +++ b/src/SPC/store/PackageManager.php @@ -6,6 +6,7 @@ namespace SPC\store; use SPC\exception\FileSystemException; use SPC\exception\WrongUsageException; +use SPC\store\pkg\CustomPackage; class PackageManager { @@ -32,6 +33,20 @@ class PackageManager // Download package Downloader::downloadPackage($pkg_name, $config, $force); + if (Config::getPkg($pkg_name)['type'] === 'custom') { + // Custom extract function + $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg'); + foreach ($classes as $class) { + if (is_a($class, CustomPackage::class, true) && $class !== CustomPackage::class) { + $cls = new $class(); + if (in_array($pkg_name, $cls->getSupportName())) { + (new $class())->extract($pkg_name); + break; + } + } + } + return; + } // After download, read lock file name $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true); $source_type = $lock[$pkg_name]['source_type']; diff --git a/src/SPC/store/pkg/CustomPackage.php b/src/SPC/store/pkg/CustomPackage.php new file mode 100644 index 00000000..89edb17e --- /dev/null +++ b/src/SPC/store/pkg/CustomPackage.php @@ -0,0 +1,17 @@ + 'win', + 'Darwin' => 'macos', + 'Linux' => 'linux', + 'BSD' => 'bsd', + default => throw new WrongUsageException('Not support os: ' . PHP_OS_FAMILY), + }; +} + function shell(?bool $debug = null): UnixShell { /* @noinspection PhpUnhandledExceptionInspection */ From 92284e92c9aaf73a997e2662a02da4f9d7bb6bb6 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jun 2025 20:56:07 +0800 Subject: [PATCH 38/70] Refactor go and frankenphp downloads and builds --- config/pkg.json | 12 +++++ src/SPC/builder/unix/UnixBuilderBase.php | 52 +++++++++++++------ src/SPC/store/pkg/GoModFrankenphp.php | 64 ++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 src/SPC/store/pkg/GoModFrankenphp.php diff --git a/config/pkg.json b/config/pkg.json index 5760c0b1..a2ec8a14 100644 --- a/config/pkg.json +++ b/config/pkg.json @@ -42,5 +42,17 @@ "extract-files": { "upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe" } + }, + "go-mod-frankenphp-x86_64-linux": { + "type": "custom" + }, + "go-mod-frankenphp-aarch64-linux": { + "type": "custom" + }, + "go-mod-frankenphp-x86_64-macos": { + "type": "custom" + }, + "go-mod-frankenphp-aarch64-macos": { + "type": "custom" } } diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 9801d47b..44756955 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -219,6 +219,19 @@ abstract class UnixBuilderBase extends BuilderBase throw new RuntimeException('embed failed sanity check: run failed. Error message: ' . implode("\n", $output)); } } + + // sanity check for frankenphp + if (($build_target & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) { + logger()->info('running frankenphp sanity check'); + $frankenphp = BUILD_BIN_PATH . '/frankenphp'; + if (!file_exists($frankenphp)) { + throw new RuntimeException('FrankenPHP binary not found: ' . $frankenphp); + } + [$ret, $output] = shell()->execWithResult($frankenphp . ' -v'); + if ($ret !== 0 || !str_contains(implode('', $output), 'FrankenPHP')) { + throw new RuntimeException('FrankenPHP failed sanity check: ret[' . $ret . ']. out[' . implode('', $output) . ']'); + } + } } /** @@ -285,16 +298,19 @@ abstract class UnixBuilderBase extends BuilderBase */ protected function buildFrankenphp(): void { - $path = getenv('PATH'); - $xcaddyPath = getenv('GOBIN') ?: (getenv('HOME') . '/go/bin'); - if (!str_contains($path, $xcaddyPath)) { - $path = $path . ':' . $xcaddyPath; - } - $path = BUILD_BIN_PATH . ':' . $path; - f_putenv("PATH={$path}"); + $os = match (PHP_OS_FAMILY) { + 'Linux' => 'linux', + 'Windows' => 'win', + 'Darwin' => 'macos', + 'BSD' => 'freebsd', + default => throw new RuntimeException('Unsupported OS: ' . PHP_OS_FAMILY), + }; + $arch = arch2gnu(php_uname('m')); + + // define executables for go and xcaddy + $go_exec = PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}/bin/go"; + $xcaddy_exec = PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}/bin/xcaddy"; - $brotliLibs = $this->getLib('brotli') !== null ? '-lbrotlienc -lbrotlidec -lbrotlicommon' : ''; - $watcherLibs = $this->getLib('watcher') !== null ? '-lwatcher-c' : ''; $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); @@ -303,7 +319,7 @@ abstract class UnixBuilderBase extends BuilderBase $xcaddyModules = '--with github.com/dunglas/frankenphp ' . $xcaddyModules; } if ($this->getLib('brotli') === null && str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { - logger()->warning('caddy-cbrotli module is enabled, but broli library is not built. Disabling caddy-cbrotli.'); + logger()->warning('caddy-cbrotli module is enabled, but brotli library is not built. Disabling caddy-cbrotli.'); $xcaddyModules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddyModules); } $lrt = PHP_OS_FAMILY === 'Linux' ? '-lrt' : ''; @@ -313,14 +329,20 @@ abstract class UnixBuilderBase extends BuilderBase if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { $libphpVersion = preg_replace('/\.\d$/', '', $libphpVersion); } - $debugFlags = $this->getOption('--with-debug') ? "'-w -s' " : ''; + $debugFlags = $this->getOption('--with-debug') ? "'-w -s' " : ''; + + $config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list, with_dependencies: true); $env = [ + 'PATH' => PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}/bin:" . getenv('PATH'), + 'GOROOT' => PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}", + 'GOBIN' => PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}/bin", + 'GOPATH' => PKG_ROOT_PATH . '/go', 'CGO_ENABLED' => '1', - 'CGO_CFLAGS' => '$(php-config --includes) -I$(php-config --include-dir)/..', - 'CGO_LDFLAGS' => '$(php-config --ldflags) -L' . BUILD_LIB_PATH . " $(php-config --libs) {$brotliLibs} {$watcherLibs} -lphp {$lrt}", + 'CGO_CFLAGS' => $config['cflags'], + 'CGO_LDFLAGS' => "{$config['ldflags']} {$config['libs']} {$lrt}", 'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' . - '-ldflags \\"-linkmode=external -extldflags \'-pie\' '. $debugFlags . + '-ldflags \"-linkmode=external -extldflags \'-pie\' ' . $debugFlags . '-X \'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ' . "{$frankenPhpVersion} PHP {$libphpVersion} Caddy'\\\" " . "-tags=nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", @@ -328,6 +350,6 @@ abstract class UnixBuilderBase extends BuilderBase ]; shell()->cd(BUILD_BIN_PATH) ->setEnv($env) - ->exec('xcaddy build --output frankenphp ' . $xcaddyModules); + ->exec("{$xcaddy_exec} build --output frankenphp {$xcaddyModules}"); } } diff --git a/src/SPC/store/pkg/GoModFrankenphp.php b/src/SPC/store/pkg/GoModFrankenphp.php new file mode 100644 index 00000000..bede5d22 --- /dev/null +++ b/src/SPC/store/pkg/GoModFrankenphp.php @@ -0,0 +1,64 @@ + 'amd64', + 'aarch64' => 'arm64', + default => throw new \InvalidArgumentException('Unsupported architecture: ' . $name), + }; + $os = match (explode('-', $name)[4]) { + 'linux' => 'linux', + 'macos' => 'darwin', + default => throw new \InvalidArgumentException('Unsupported OS: ' . $name), + }; + $go_version = '1.24.4'; + $config = [ + 'type' => 'url', + 'url' => "https://go.dev/dl/go{$go_version}.{$os}-{$arch}.tar.gz", + ]; + Downloader::downloadPackage($name, $config, $force); + } + + public function extract(string $name): void + { + $pkgroot = PKG_ROOT_PATH; + $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true); + $source_type = $lock[$name]['source_type']; + $filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']); + $extract = $lock[$name]['move_path'] === null ? (PKG_ROOT_PATH . "{$pkgroot}/{$name}") : $lock[$name]['move_path']; + + FileSystem::extractPackage($name, $source_type, $filename, $extract); + + // install xcaddy + $go_exec = PKG_ROOT_PATH . "{$pkgroot}/{$name}/bin/go"; + // $xcaddy_exec = PKG_ROOT_PATH . "$pkgroot/$name/bin/xcaddy"; + shell()->appendEnv([ + 'PATH' => "{$pkgroot}/{$name}/bin:" . getenv('PATH'), + 'GOROOT' => "{$pkgroot}/{$name}", + 'GOBIN' => "{$pkgroot}/{$name}/bin", + 'GOPATH' => "{$pkgroot}/go", + ]) + ->exec("{$go_exec} install github.com/caddyserver/xcaddy/cmd/xcaddy@latest"); + // TODO: Here to download dependencies for xcaddy and frankenphp first + } +} From d6858e18df118c9160e97301fa871038593a478a Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jun 2025 20:57:14 +0800 Subject: [PATCH 39/70] phpstan fix --- src/globals/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/globals/functions.php b/src/globals/functions.php index 32f0514e..998b2d1c 100644 --- a/src/globals/functions.php +++ b/src/globals/functions.php @@ -105,7 +105,7 @@ function osfamily2dir(): string function osfamily2shortname(): string { return match (PHP_OS_FAMILY) { - 'Windows', 'WINNT', 'Cygwin' => 'win', + 'Windows' => 'win', 'Darwin' => 'macos', 'Linux' => 'linux', 'BSD' => 'bsd', From 74b1dda884a447258df20c7696fa9a07872367d7 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jun 2025 21:06:08 +0800 Subject: [PATCH 40/70] Fix test-extensions.php --- src/globals/test-extensions.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index 44243140..77e0f71d 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -40,6 +40,9 @@ $no_strip = false; // compress with upx $upx = false; +// whether to test frankenphp build, only available for macos and linux +$frankenphp = false; + // prefer downloading pre-built packages to speed up the build process $prefer_pre_built = false; @@ -177,7 +180,7 @@ if ($argv[1] === 'build_cmd' || $argv[1] === 'build_embed_cmd') { $build_cmd .= $no_strip ? '--no-strip ' : ''; $build_cmd .= $upx ? '--with-upx-pack ' : ''; $build_cmd .= $final_libs === '' ? '' : ('--with-libs=' . quote2($final_libs) . ' '); - $build_cmd .= str_starts_with($argv[2], 'windows-') ? '' : '--build-fpm --build-frankenphp'; + $build_cmd .= str_starts_with($argv[2], 'windows-') ? '' : '--build-fpm '; $build_cmd .= '--debug '; } @@ -208,7 +211,7 @@ switch ($argv[1] ?? null) { passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode); break; case 'build_embed_cmd': - passthru($prefix . $build_cmd . (str_starts_with($argv[2], 'windows-') ? ' --build-cli' : ' --build-embed'), $retcode); + passthru($prefix . $build_cmd . (str_starts_with($argv[2], 'windows-') ? ' --build-cli' : (' --build-embed' . ($frankenphp ? ' --build-frankenphp' : ''))), $retcode); break; case 'doctor_cmd': passthru($prefix . $doctor_cmd, $retcode); From 4ecaffd9085846091e825d300b1b29d64056b763 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jun 2025 21:08:05 +0800 Subject: [PATCH 41/70] Fix test-extensions.php --- src/globals/test-extensions.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index 77e0f71d..79acc984 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -41,7 +41,7 @@ $no_strip = false; $upx = false; // whether to test frankenphp build, only available for macos and linux -$frankenphp = false; +$frankenphp = true; // prefer downloading pre-built packages to speed up the build process $prefer_pre_built = false; @@ -211,6 +211,12 @@ switch ($argv[1] ?? null) { passthru($prefix . $build_cmd . ' --build-cli --build-micro', $retcode); break; case 'build_embed_cmd': + if ($frankenphp) { + passthru("{$prefix}install-pkg go-mod-frankenphp --debug", $retcode); + if ($retcode !== 0) { + break; + } + } passthru($prefix . $build_cmd . (str_starts_with($argv[2], 'windows-') ? ' --build-cli' : (' --build-embed' . ($frankenphp ? ' --build-frankenphp' : ''))), $retcode); break; case 'doctor_cmd': From becee5b42671d2b54316460bad699c8cb18aead1 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jun 2025 21:27:07 +0800 Subject: [PATCH 42/70] Use version instead of -v --- src/SPC/builder/unix/UnixBuilderBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 44756955..d424c594 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -227,7 +227,7 @@ abstract class UnixBuilderBase extends BuilderBase if (!file_exists($frankenphp)) { throw new RuntimeException('FrankenPHP binary not found: ' . $frankenphp); } - [$ret, $output] = shell()->execWithResult($frankenphp . ' -v'); + [$ret, $output] = shell()->execWithResult("{$frankenphp} version"); if ($ret !== 0 || !str_contains(implode('', $output), 'FrankenPHP')) { throw new RuntimeException('FrankenPHP failed sanity check: ret[' . $ret . ']. out[' . implode('', $output) . ']'); } From a76f49f92739d03c683e08ed7cc6e66a9849c83d Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 18 Jun 2025 21:56:36 +0800 Subject: [PATCH 43/70] Remove libxml2 requirement for linux --- src/SPC/builder/BuilderBase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index fd4788f8..daa27765 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -527,8 +527,8 @@ abstract class BuilderBase throw new WrongUsageException("FrankenPHP SAPI requires go-mod-frankenphp package, please install it first: {$argv[0]} install-pkg go-mod-frankenphp"); } // frankenphp needs libxml2 libs - if (!$this->getLib('libxml2')) { - throw new WrongUsageException('FrankenPHP SAPI requires libxml2 library, please include `xml` extension in your build.'); + if (PHP_OS_FAMILY === 'Darwin' && !$this->getLib('libxml2')) { + throw new WrongUsageException('FrankenPHP SAPI for macOS requires libxml2 library, please include `xml` extension in your build.'); } } } From 15979d4636b060f90c624900778d3858f1a034ac Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 08:59:56 +0700 Subject: [PATCH 44/70] fix double path --- src/SPC/store/pkg/GoModFrankenphp.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/store/pkg/GoModFrankenphp.php b/src/SPC/store/pkg/GoModFrankenphp.php index bede5d22..3b93ceac 100644 --- a/src/SPC/store/pkg/GoModFrankenphp.php +++ b/src/SPC/store/pkg/GoModFrankenphp.php @@ -45,7 +45,7 @@ class GoModFrankenphp extends CustomPackage $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true); $source_type = $lock[$name]['source_type']; $filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']); - $extract = $lock[$name]['move_path'] === null ? (PKG_ROOT_PATH . "{$pkgroot}/{$name}") : $lock[$name]['move_path']; + $extract = $lock[$name]['move_path'] === null ? "{$pkgroot}/{$name}" : $lock[$name]['move_path']; FileSystem::extractPackage($name, $source_type, $filename, $extract); From cb010d81ac5f943ce8b2f344060efcb0576ee030 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 09:03:40 +0700 Subject: [PATCH 45/70] there's no documented functionality to download without building - xcaddy is meant to do both in one step --- src/SPC/store/pkg/GoModFrankenphp.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/SPC/store/pkg/GoModFrankenphp.php b/src/SPC/store/pkg/GoModFrankenphp.php index 3b93ceac..59507b6c 100644 --- a/src/SPC/store/pkg/GoModFrankenphp.php +++ b/src/SPC/store/pkg/GoModFrankenphp.php @@ -52,13 +52,13 @@ class GoModFrankenphp extends CustomPackage // install xcaddy $go_exec = PKG_ROOT_PATH . "{$pkgroot}/{$name}/bin/go"; // $xcaddy_exec = PKG_ROOT_PATH . "$pkgroot/$name/bin/xcaddy"; - shell()->appendEnv([ - 'PATH' => "{$pkgroot}/{$name}/bin:" . getenv('PATH'), - 'GOROOT' => "{$pkgroot}/{$name}", - 'GOBIN' => "{$pkgroot}/{$name}/bin", - 'GOPATH' => "{$pkgroot}/go", - ]) + shell() + ->appendEnv([ + 'PATH' => "{$pkgroot}/{$name}/bin:" . getenv('PATH'), + 'GOROOT' => "{$pkgroot}/{$name}", + 'GOBIN' => "{$pkgroot}/{$name}/bin", + 'GOPATH' => "{$pkgroot}/go", + ]) ->exec("{$go_exec} install github.com/caddyserver/xcaddy/cmd/xcaddy@latest"); - // TODO: Here to download dependencies for xcaddy and frankenphp first } } From b42409efd183f68141865589479e00ae85e9674b Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 09:08:42 +0700 Subject: [PATCH 46/70] LD_LIBRARY_PATH for frankenphp sanity check --- src/SPC/builder/unix/UnixBuilderBase.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index d424c594..adde474a 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -227,7 +227,9 @@ abstract class UnixBuilderBase extends BuilderBase if (!file_exists($frankenphp)) { throw new RuntimeException('FrankenPHP binary not found: ' . $frankenphp); } - [$ret, $output] = shell()->execWithResult("{$frankenphp} version"); + [$ret, $output] = shell() + ->setEnv(['LD_LIBRARY_PATH' => BUILD_LIB_PATH]) + ->execWithResult("{$frankenphp} version"); if ($ret !== 0 || !str_contains(implode('', $output), 'FrankenPHP')) { throw new RuntimeException('FrankenPHP failed sanity check: ret[' . $ret . ']. out[' . implode('', $output) . ']'); } @@ -308,7 +310,6 @@ abstract class UnixBuilderBase extends BuilderBase $arch = arch2gnu(php_uname('m')); // define executables for go and xcaddy - $go_exec = PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}/bin/go"; $xcaddy_exec = PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}/bin/xcaddy"; $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; From 804468f7b913ce64563b3c486666fa0d443aa003 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 09:14:39 +0700 Subject: [PATCH 47/70] refactor common exec code out --- src/SPC/util/UnixShell.php | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/SPC/util/UnixShell.php b/src/SPC/util/UnixShell.php index 0f320d50..bb4e8d5e 100644 --- a/src/SPC/util/UnixShell.php +++ b/src/SPC/util/UnixShell.php @@ -44,14 +44,7 @@ class UnixShell { /* @phpstan-ignore-next-line */ logger()->info(ConsoleColor::yellow('[EXEC] ') . ConsoleColor::green($cmd)); - logger()->debug('Executed at: ' . debug_backtrace()[0]['file'] . ':' . debug_backtrace()[0]['line']); - $env_str = $this->getEnvString(); - if (!empty($env_str)) { - $cmd = "{$env_str} {$cmd}"; - } - if ($this->cd !== null) { - $cmd = 'cd ' . escapeshellarg($this->cd) . ' && ' . $cmd; - } + $cmd = $this->getExecString($cmd); if (!$this->debug) { $cmd .= ' 1>/dev/null 2>&1'; } @@ -99,10 +92,7 @@ class UnixShell /* @phpstan-ignore-next-line */ logger()->debug(ConsoleColor::blue('[EXEC] ') . ConsoleColor::gray($cmd)); } - logger()->debug('Executed at: ' . debug_backtrace()[0]['file'] . ':' . debug_backtrace()[0]['line']); - if ($this->cd !== null) { - $cmd = 'cd ' . escapeshellarg($this->cd) . ' && ' . $cmd; - } + $cmd = $this->getExecString($cmd); exec($cmd, $out, $code); return [$code, $out]; } @@ -126,4 +116,21 @@ class UnixShell } return trim($str); } + + /** + * @param string $cmd + * @return string + */ + private function getExecString(string $cmd): string + { + logger()->debug('Executed at: ' . debug_backtrace()[0]['file'] . ':' . debug_backtrace()[0]['line']); + $env_str = $this->getEnvString(); + if (!empty($env_str)) { + $cmd = "{$env_str} {$cmd}"; + } + if ($this->cd !== null) { + $cmd = 'cd ' . escapeshellarg($this->cd) . ' && ' . $cmd; + } + return $cmd; + } } From a9713c3bfa5c4d5db2c0ce5394c1af88e32b8a69 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 09:32:55 +0700 Subject: [PATCH 48/70] polish merge --- src/SPC/store/Downloader.php | 11 ----------- src/SPC/util/UnixShell.php | 4 ---- 2 files changed, 15 deletions(-) diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index 9435202f..4c3bbf61 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -650,17 +650,6 @@ class Downloader return true; } } - - // If lock file exists, skip downloading for source mode - if (!$force && $download_as === SPC_DOWNLOAD_PACKAGE && isset($lock[$name])) { - if ( - $lock[$name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename']) || - $lock[$name]['source_type'] === SPC_SOURCE_GIT && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname']) - ) { - logger()->notice("Package [{$name}] already downloaded: " . ($lock[$name]['filename'] ?? $lock[$name]['dirname'])); - return true; - } - } return false; } } diff --git a/src/SPC/util/UnixShell.php b/src/SPC/util/UnixShell.php index bb4e8d5e..ffe18910 100644 --- a/src/SPC/util/UnixShell.php +++ b/src/SPC/util/UnixShell.php @@ -117,10 +117,6 @@ class UnixShell return trim($str); } - /** - * @param string $cmd - * @return string - */ private function getExecString(string $cmd): string { logger()->debug('Executed at: ' . debug_backtrace()[0]['file'] . ':' . debug_backtrace()[0]['line']); From cc09184183ec8ef7267a0519b49cd9fa5edcb2ec Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 09:34:44 +0700 Subject: [PATCH 49/70] fix test --- tests/SPC/builder/BuilderTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/SPC/builder/BuilderTest.php b/tests/SPC/builder/BuilderTest.php index 17f1e799..8e6cdc41 100644 --- a/tests/SPC/builder/BuilderTest.php +++ b/tests/SPC/builder/BuilderTest.php @@ -161,7 +161,8 @@ class BuilderTest extends TestCase [BUILD_TARGET_FPM, 'fpm'], [BUILD_TARGET_MICRO, 'micro'], [BUILD_TARGET_EMBED, 'embed'], - [BUILD_TARGET_ALL, 'cli, micro, fpm, embed'], + [BUILD_TARGET_FRANKENPHP, 'embed, frankenphp'], + [BUILD_TARGET_ALL, 'cli, micro, fpm, embed, frankenphp'], [BUILD_TARGET_CLI | BUILD_TARGET_EMBED, 'cli, embed'], ]; } From 497728693697c3a8fd9611ce9db6037115651734 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 09:43:13 +0700 Subject: [PATCH 50/70] fix test --- tests/SPC/builder/BuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/SPC/builder/BuilderTest.php b/tests/SPC/builder/BuilderTest.php index 8e6cdc41..2bdf1109 100644 --- a/tests/SPC/builder/BuilderTest.php +++ b/tests/SPC/builder/BuilderTest.php @@ -161,7 +161,7 @@ class BuilderTest extends TestCase [BUILD_TARGET_FPM, 'fpm'], [BUILD_TARGET_MICRO, 'micro'], [BUILD_TARGET_EMBED, 'embed'], - [BUILD_TARGET_FRANKENPHP, 'embed, frankenphp'], + [BUILD_TARGET_FRANKENPHP, 'frankenphp'], [BUILD_TARGET_ALL, 'cli, micro, fpm, embed, frankenphp'], [BUILD_TARGET_CLI | BUILD_TARGET_EMBED, 'cli, embed'], ]; From c2d6b9ad2cd6e82e6460ce5d23d458f42fe295dc Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 09:50:38 +0700 Subject: [PATCH 51/70] refactor lock file constant out --- src/SPC/command/DownloadCommand.php | 3 ++- src/SPC/store/LockFile.php | 2 +- src/SPC/store/pkg/GoModFrankenphp.php | 3 ++- tests/SPC/builder/BuilderTest.php | 3 ++- tests/SPC/store/DownloaderTest.php | 4 ++-- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index 57e83cd0..d5054aa9 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -12,6 +12,7 @@ use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\Downloader; +use SPC\store\LockFile; use SPC\util\DependencyUtil; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; @@ -301,7 +302,7 @@ class DownloadCommand extends BaseCommand throw new WrongUsageException('Windows currently does not support --from-zip !'); } - if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) { + if (!file_exists(LockFile::LOCK_FILE)) { throw new RuntimeException('.lock.json not exist in "downloads/"'); } } catch (RuntimeException $e) { diff --git a/src/SPC/store/LockFile.php b/src/SPC/store/LockFile.php index 45e3a94c..044b2472 100644 --- a/src/SPC/store/LockFile.php +++ b/src/SPC/store/LockFile.php @@ -10,7 +10,7 @@ use SPC\exception\WrongUsageException; class LockFile { - private const string LOCK_FILE = DOWNLOAD_PATH . '/.lock.json'; + public const string LOCK_FILE = DOWNLOAD_PATH . '/.lock.json'; private static ?array $lock_file_content = null; diff --git a/src/SPC/store/pkg/GoModFrankenphp.php b/src/SPC/store/pkg/GoModFrankenphp.php index 59507b6c..31195cd9 100644 --- a/src/SPC/store/pkg/GoModFrankenphp.php +++ b/src/SPC/store/pkg/GoModFrankenphp.php @@ -6,6 +6,7 @@ namespace SPC\store\pkg; use SPC\store\Downloader; use SPC\store\FileSystem; +use SPC\store\LockFile; class GoModFrankenphp extends CustomPackage { @@ -42,7 +43,7 @@ class GoModFrankenphp extends CustomPackage public function extract(string $name): void { $pkgroot = PKG_ROOT_PATH; - $lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true); + $lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true); $source_type = $lock[$name]['source_type']; $filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']); $extract = $lock[$name]['move_path'] === null ? "{$pkgroot}/{$name}" : $lock[$name]['move_path']; diff --git a/tests/SPC/builder/BuilderTest.php b/tests/SPC/builder/BuilderTest.php index 2bdf1109..b80f4a0d 100644 --- a/tests/SPC/builder/BuilderTest.php +++ b/tests/SPC/builder/BuilderTest.php @@ -13,6 +13,7 @@ use SPC\builder\LibraryBase; use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; use SPC\store\FileSystem; +use SPC\store\LockFile; use SPC\util\CustomExt; use SPC\util\DependencyUtil; use Symfony\Component\Console\Input\ArgvInput; @@ -117,7 +118,7 @@ class BuilderTest extends TestCase public function testGetPHPVersionFromArchive() { - $lock = file_exists(DOWNLOAD_PATH . '/.lock.json') ? file_get_contents(DOWNLOAD_PATH . '/.lock.json') : false; + $lock = file_exists(LockFile::LOCK_FILE) ? file_get_contents(LockFile::LOCK_FILE) : false; if ($lock === false) { $this->assertFalse($this->builder->getPHPVersionFromArchive()); } else { diff --git a/tests/SPC/store/DownloaderTest.php b/tests/SPC/store/DownloaderTest.php index 43de396a..91865bfb 100644 --- a/tests/SPC/store/DownloaderTest.php +++ b/tests/SPC/store/DownloaderTest.php @@ -59,8 +59,8 @@ class DownloaderTest extends TestCase public function testLockSource() { LockFile::lockSource('fake-file', ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']); - $this->assertFileExists(DOWNLOAD_PATH . '/.lock.json'); - $json = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true); + $this->assertFileExists(LockFile::LOCK_FILE); + $json = json_decode(file_get_contents(LockFile::LOCK_FILE), true); $this->assertIsArray($json); $this->assertArrayHasKey('fake-file', $json); $this->assertArrayHasKey('source_type', $json['fake-file']); From 597db25178f1022a6c98d6282706a7fd5796c199 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 10:00:56 +0700 Subject: [PATCH 52/70] frankenphp requires ZTS --- src/SPC/command/BuildPHPCommand.php | 7 +++++++ src/SPC/store/LockFile.php | 1 + 2 files changed, 8 insertions(+) diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 29cedf8f..92852e41 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -319,6 +319,13 @@ class BuildPHPCommand extends BuildCommand $rule |= BUILD_TARGET_EMBED; f_putenv('SPC_CMD_VAR_PHP_EMBED_TYPE=' . ($embed === 'static' ? 'static' : 'shared')); } + if ($this->getOption('build-frankenphp')) { + $rule |= BUILD_TARGET_FRANKENPHP; + if (!$this->getOption('enable-zts')) { + logger()->warning('FrankenPHP requires ZTS to work with multiple threads, the --enable-zts option will be enabled automatically!'); + $this->input->setOption('enable-zts', true); + } + } $rule |= ($this->getOption('build-frankenphp') ? (BUILD_TARGET_FRANKENPHP | BUILD_TARGET_EMBED) : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE); return $rule; diff --git a/src/SPC/store/LockFile.php b/src/SPC/store/LockFile.php index 044b2472..132d3764 100644 --- a/src/SPC/store/LockFile.php +++ b/src/SPC/store/LockFile.php @@ -78,6 +78,7 @@ class LockFile } // Write the updated lock data back to the file + FileSystem::createDir(dirname(self::LOCK_FILE)); file_put_contents(self::LOCK_FILE, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } From 32dc5d3cdba1c5f61dd38f254243240ff9ff1ccd Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 10:02:22 +0700 Subject: [PATCH 53/70] leftover double path --- src/SPC/store/pkg/GoModFrankenphp.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/store/pkg/GoModFrankenphp.php b/src/SPC/store/pkg/GoModFrankenphp.php index 31195cd9..b45394a2 100644 --- a/src/SPC/store/pkg/GoModFrankenphp.php +++ b/src/SPC/store/pkg/GoModFrankenphp.php @@ -51,7 +51,7 @@ class GoModFrankenphp extends CustomPackage FileSystem::extractPackage($name, $source_type, $filename, $extract); // install xcaddy - $go_exec = PKG_ROOT_PATH . "{$pkgroot}/{$name}/bin/go"; + $go_exec = "{$pkgroot}/{$name}/bin/go"; // $xcaddy_exec = PKG_ROOT_PATH . "$pkgroot/$name/bin/xcaddy"; shell() ->appendEnv([ From ba6ed137c6f8ff781eed3b89bf03f9a488657db5 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 10:03:35 +0700 Subject: [PATCH 54/70] remove old file deletions --- src/SPC/command/BuildPHPCommand.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 92852e41..f304a3b9 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -162,21 +162,6 @@ class BuildPHPCommand extends BuildCommand $indent_texts['UPX Pack'] = 'enabled'; } - try { - $cleanPhpSrc = $builder->getPHPVersion() !== $builder->getPHPVersionFromArchive(); - } catch (RuntimeException|WrongUsageException) { - $cleanPhpSrc = true; - } - if ($cleanPhpSrc) { - logger()->info('Cleaning previous php build due to mismatching versions...'); - FileSystem::removeDir(SOURCE_PATH . '/php-src'); - FileSystem::removeDir(BUILD_MODULES_PATH); - $binFiles = glob(BUILD_BIN_PATH . '/php*'); - $libFiles = glob(BUILD_LIB_PATH . '/libphp*'); - foreach ([...$binFiles, ...$libFiles] as $file) { - unlink($file); - } - } $ver = $builder->getPHPVersionFromArchive() ?: $builder->getPHPVersion(); $indent_texts['PHP Version'] = $ver; From aec03b2f24b8b37b44748ef67e0236d94e3eaca9 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 10:08:51 +0700 Subject: [PATCH 55/70] move enable zts check --- src/SPC/builder/BuilderBase.php | 9 ++++++--- src/SPC/command/BuildPHPCommand.php | 7 ------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index 6303ffec..956aee6c 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -513,7 +513,10 @@ abstract class BuilderBase public function checkBeforeBuildPHP(int $rule): void { if (($rule & BUILD_TARGET_FRANKENPHP) === BUILD_TARGET_FRANKENPHP) { - // frankenphp only support linux and macOS + if (!$this->getOption('enable-zts')) { + throw new WrongUsageException('FrankenPHP SAPI requires ZTS enabled PHP, build with `--enable-zts`!'); + } + // frankenphp doesn't support windows, BSD is currently not supported by static-php-cli if (!in_array(PHP_OS_FAMILY, ['Linux', 'Darwin'])) { throw new WrongUsageException('FrankenPHP SAPI is only available on Linux and macOS!'); } @@ -523,9 +526,9 @@ abstract class BuilderBase global $argv; throw new WrongUsageException("FrankenPHP SAPI requires go-mod-frankenphp package, please install it first: {$argv[0]} install-pkg go-mod-frankenphp"); } - // frankenphp needs libxml2 libs + // frankenphp needs libxml2 lib on macos, see: https://github.com/php/frankenphp/blob/main/frankenphp.go#L17 if (PHP_OS_FAMILY === 'Darwin' && !$this->getLib('libxml2')) { - throw new WrongUsageException('FrankenPHP SAPI for macOS requires libxml2 library, please include `xml` extension in your build.'); + throw new WrongUsageException('FrankenPHP SAPI for macOS requires libxml2 library, please include the `xml` extension in your build.'); } } } diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index f304a3b9..13c737a7 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -304,13 +304,6 @@ class BuildPHPCommand extends BuildCommand $rule |= BUILD_TARGET_EMBED; f_putenv('SPC_CMD_VAR_PHP_EMBED_TYPE=' . ($embed === 'static' ? 'static' : 'shared')); } - if ($this->getOption('build-frankenphp')) { - $rule |= BUILD_TARGET_FRANKENPHP; - if (!$this->getOption('enable-zts')) { - logger()->warning('FrankenPHP requires ZTS to work with multiple threads, the --enable-zts option will be enabled automatically!'); - $this->input->setOption('enable-zts', true); - } - } $rule |= ($this->getOption('build-frankenphp') ? (BUILD_TARGET_FRANKENPHP | BUILD_TARGET_EMBED) : BUILD_TARGET_NONE); $rule |= ($this->getOption('build-all') ? BUILD_TARGET_ALL : BUILD_TARGET_NONE); return $rule; From 2bc9fef75839bfc314922fafb04e67ade8e4f882 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 10:09:42 +0700 Subject: [PATCH 56/70] cs fix --- src/SPC/command/BuildPHPCommand.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 13c737a7..e40a7d83 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -6,7 +6,6 @@ namespace SPC\command; use SPC\builder\BuilderProvider; use SPC\exception\ExceptionHandler; -use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\FileSystem; From 5f3f999222060d14d25379fcc15aa598a5b6170a Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 10:36:31 +0700 Subject: [PATCH 57/70] run go mod frankenphp automatically when running craft --- config/env.ini | 2 +- src/SPC/builder/linux/LinuxBuilder.php | 1 - src/SPC/builder/unix/UnixBuilderBase.php | 2 +- src/SPC/command/CraftCommand.php | 9 +++++++++ src/SPC/store/pkg/GoModFrankenphp.php | 15 +++++++++++++-- src/SPC/util/GlobalEnvManager.php | 5 ++++- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/config/env.ini b/config/env.ini index 895a08e9..d80bc3e7 100644 --- a/config/env.ini +++ b/config/env.ini @@ -69,7 +69,7 @@ SPC_LIBC=musl CC=${SPC_LINUX_DEFAULT_CC} CXX=${SPC_LINUX_DEFAULT_CXX} AR=${SPC_LINUX_DEFAULT_AR} -LD=ld.gold +LD=${SPC_LINUX_DEFAULT_LD} ; default compiler flags, used in CMake toolchain file, openssl and pkg-config build SPC_DEFAULT_C_FLAGS="-fPIC -Os" SPC_DEFAULT_CXX_FLAGS="-fPIC -Os" diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index addd2f57..722601d1 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -33,7 +33,6 @@ class LinuxBuilder extends UnixBuilderBase if (getenv('SPC_LIBC') === 'musl' && !SystemUtil::isMuslDist()) { $this->setOptionIfNotExist('library_path', "LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\""); $this->setOptionIfNotExist('ld_library_path', "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\""); - GlobalEnvManager::putenv("PATH=/usr/local/musl/bin:/usr/local/musl/{$arch}-linux-musl/bin:" . getenv('PATH')); $configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE'); $configure = "LD_LIBRARY_PATH=\"/usr/local/musl/{$arch}-linux-musl/lib\" " . $configure; GlobalEnvManager::putenv("SPC_CMD_PREFIX_PHP_CONFIGURE={$configure}"); diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index adde474a..4232ef91 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -324,7 +324,7 @@ abstract class UnixBuilderBase extends BuilderBase $xcaddyModules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddyModules); } $lrt = PHP_OS_FAMILY === 'Linux' ? '-lrt' : ''; - $releaseInfo = json_decode(Downloader::curlExec('https://api.github.com/repos/php/frankenphp/releases/latest'), true); + $releaseInfo = json_decode(Downloader::curlExec('https://api.github.com/repos/php/frankenphp/releases/latest', retries: 3), true); $frankenPhpVersion = $releaseInfo['tag_name']; $libphpVersion = $this->getPHPVersion(); if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { diff --git a/src/SPC/command/CraftCommand.php b/src/SPC/command/CraftCommand.php index 9a2ac441..744d1c57 100644 --- a/src/SPC/command/CraftCommand.php +++ b/src/SPC/command/CraftCommand.php @@ -66,6 +66,15 @@ class CraftCommand extends BaseCommand return static::FAILURE; } } + // install go and xcaddy for frankenphp + if (in_array('frankenphp', $craft['sapi'])) { + $retcode = $this->runCommand('install-pkg', 'go-mod-frankenphp'); + if ($retcode !== 0) { + $this->output->writeln('craft go-mod-frankenphp failed'); + $this->log("craft go-mod-frankenphp failed with code: {$retcode}", true); + return static::FAILURE; + } + } // craft download if ($craft['craft-options']['download']) { $sharedAppend = $shared_extensions ? ',' . $shared_extensions : ''; diff --git a/src/SPC/store/pkg/GoModFrankenphp.php b/src/SPC/store/pkg/GoModFrankenphp.php index b45394a2..e631cc8c 100644 --- a/src/SPC/store/pkg/GoModFrankenphp.php +++ b/src/SPC/store/pkg/GoModFrankenphp.php @@ -7,6 +7,7 @@ namespace SPC\store\pkg; use SPC\store\Downloader; use SPC\store\FileSystem; use SPC\store\LockFile; +use SPC\util\GlobalEnvManager; class GoModFrankenphp extends CustomPackage { @@ -22,6 +23,12 @@ class GoModFrankenphp extends CustomPackage public function fetch(string $name, bool $force = false, ?array $config = null): void { + $pkgroot = PKG_ROOT_PATH; + $go_exec = "{$pkgroot}/{$name}/bin/go"; + $xcaddy_exec = "{$pkgroot}/{$name}/bin/xcaddy"; + if (file_exists($go_exec) && file_exists($xcaddy_exec)) { + return; + } $arch = match (explode('-', $name)[3]) { 'x86_64' => 'amd64', 'aarch64' => 'arm64', @@ -43,6 +50,11 @@ class GoModFrankenphp extends CustomPackage public function extract(string $name): void { $pkgroot = PKG_ROOT_PATH; + $go_exec = "{$pkgroot}/{$name}/bin/go"; + $xcaddy_exec = "{$pkgroot}/{$name}/bin/xcaddy"; + if (file_exists($go_exec) && file_exists($xcaddy_exec)) { + return; + } $lock = json_decode(FileSystem::readFile(LockFile::LOCK_FILE), true); $source_type = $lock[$name]['source_type']; $filename = DOWNLOAD_PATH . '/' . ($lock[$name]['filename'] ?? $lock[$name]['dirname']); @@ -50,9 +62,8 @@ class GoModFrankenphp extends CustomPackage FileSystem::extractPackage($name, $source_type, $filename, $extract); + GlobalEnvManager::init(); // install xcaddy - $go_exec = "{$pkgroot}/{$name}/bin/go"; - // $xcaddy_exec = PKG_ROOT_PATH . "$pkgroot/$name/bin/xcaddy"; shell() ->appendEnv([ 'PATH' => "{$pkgroot}/{$name}/bin:" . getenv('PATH'), diff --git a/src/SPC/util/GlobalEnvManager.php b/src/SPC/util/GlobalEnvManager.php index 8ffd5d05..c5a0598a 100644 --- a/src/SPC/util/GlobalEnvManager.php +++ b/src/SPC/util/GlobalEnvManager.php @@ -43,16 +43,19 @@ class GlobalEnvManager } // Define env vars for linux - if (PHP_OS_FAMILY === 'Linux') { + if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { $arch = getenv('GNU_ARCH'); if (SystemUtil::isMuslDist()) { self::putenv('SPC_LINUX_DEFAULT_CC=gcc'); self::putenv('SPC_LINUX_DEFAULT_CXX=g++'); self::putenv('SPC_LINUX_DEFAULT_AR=ar'); + self::putenv('SPC_LINUX_DEFAULT_LD=ld.gold'); } else { self::putenv("SPC_LINUX_DEFAULT_CC={$arch}-linux-musl-gcc"); self::putenv("SPC_LINUX_DEFAULT_CXX={$arch}-linux-musl-g++"); self::putenv("SPC_LINUX_DEFAULT_AR={$arch}-linux-musl-ar"); + self::putenv("SPC_LINUX_DEFAULT_LD={$arch}-linux-musl-ld"); + GlobalEnvManager::putenv("PATH=/usr/local/musl/bin:/usr/local/musl/{$arch}-linux-musl/bin:" . getenv('PATH')); } } From 2f8e225abdd01b0f8bc46c1baae29855be330cef Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 11:00:07 +0700 Subject: [PATCH 58/70] remove copy of property that meant downloader would only lock one source at a time --- src/SPC/store/Downloader.php | 6 ++++++ src/SPC/store/LockFile.php | 11 +++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index 4c3bbf61..ba0cd124 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -650,6 +650,12 @@ class Downloader return true; } } + if (!$force && $download_as === SPC_DOWNLOAD_PACKAGE && $lock_item !== null) { + if (file_exists($path = LockFile::getLockFullPath($lock_item))) { + logger()->notice("Source [{$name}] already downloaded: {$path}"); + return true; + } + } return false; } } diff --git a/src/SPC/store/LockFile.php b/src/SPC/store/LockFile.php index 132d3764..999f91b4 100644 --- a/src/SPC/store/LockFile.php +++ b/src/SPC/store/LockFile.php @@ -69,17 +69,16 @@ class LockFile { self::init(); - $data = self::$lock_file_content; - if ($lock_content === null && isset($data[$lock_name])) { - self::removeLockFileIfExists($data[$lock_name]); - unset($data[$lock_name]); + if ($lock_content === null && isset(self::$lock_file_content[$lock_name])) { + self::removeLockFileIfExists(self::$lock_file_content[$lock_name]); + unset(self::$lock_file_content[$lock_name]); } else { - $data[$lock_name] = $lock_content; + self::$lock_file_content[$lock_name] = $lock_content; } // Write the updated lock data back to the file FileSystem::createDir(dirname(self::LOCK_FILE)); - file_put_contents(self::LOCK_FILE, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + file_put_contents(self::LOCK_FILE, json_encode(self::$lock_file_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } /** From fcf2c967abe5f252c8cc5ca38f8b6e6153369a28 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 11:01:30 +0700 Subject: [PATCH 59/70] fix setting of variables --- src/SPC/util/GlobalEnvManager.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SPC/util/GlobalEnvManager.php b/src/SPC/util/GlobalEnvManager.php index c5a0598a..ae251223 100644 --- a/src/SPC/util/GlobalEnvManager.php +++ b/src/SPC/util/GlobalEnvManager.php @@ -43,9 +43,9 @@ class GlobalEnvManager } // Define env vars for linux - if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { + if (PHP_OS_FAMILY === 'Linux') { $arch = getenv('GNU_ARCH'); - if (SystemUtil::isMuslDist()) { + if (SystemUtil::isMuslDist() || getenv('SPC_LIBC') !== 'musl') { self::putenv('SPC_LINUX_DEFAULT_CC=gcc'); self::putenv('SPC_LINUX_DEFAULT_CXX=g++'); self::putenv('SPC_LINUX_DEFAULT_AR=ar'); From bcea2007bd376f2a8349dfda78ec7c7ed0866d3c Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 11:04:10 +0700 Subject: [PATCH 60/70] base arch instead of hardcoded x86_64 --- bin/spc-gnu-docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index 9583b4dd..9327cd7e 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -74,7 +74,7 @@ RUN echo "source scl_source enable devtoolset-10" >> /etc/bashrc RUN source /etc/bashrc RUN yum install -y which -RUN curl -fsSL -o patchelf.tgz https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-x86_64.tar.gz && \ +RUN curl -fsSL -o patchelf.tgz https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-$BASE_ARCH.tar.gz && \ mkdir -p /patchelf && \ tar -xzf patchelf.tgz -C /patchelf --strip-components=1 && \ cp /patchelf/bin/patchelf /usr/bin/ From 4a70f260f387ec5d60a98fc55c20181e0966ab29 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 11:12:58 +0700 Subject: [PATCH 61/70] test alpine directly too (spc-alpine-docker) --- src/SPC/util/GlobalEnvManager.php | 2 +- src/globals/test-extensions.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SPC/util/GlobalEnvManager.php b/src/SPC/util/GlobalEnvManager.php index ae251223..17785205 100644 --- a/src/SPC/util/GlobalEnvManager.php +++ b/src/SPC/util/GlobalEnvManager.php @@ -45,7 +45,7 @@ class GlobalEnvManager // Define env vars for linux if (PHP_OS_FAMILY === 'Linux') { $arch = getenv('GNU_ARCH'); - if (SystemUtil::isMuslDist() || getenv('SPC_LIBC') !== 'musl') { + if (SystemUtil::isMuslDist() || getenv('SPC_LIBC') === 'glibc') { self::putenv('SPC_LINUX_DEFAULT_CC=gcc'); self::putenv('SPC_LINUX_DEFAULT_CXX=g++'); self::putenv('SPC_LINUX_DEFAULT_AR=ar'); diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index 79acc984..db51db5e 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -24,7 +24,7 @@ $test_os = [ 'macos-13', // 'macos-14', 'macos-15', - // 'ubuntu-latest', + 'ubuntu-latest', 'ubuntu-22.04', 'ubuntu-24.04', 'ubuntu-22.04-arm', From 61a9264802d94bda4b531eb0f565b4ac37b99789 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 11:20:57 +0700 Subject: [PATCH 62/70] libraries must link against -lphp dynamic! --- bin/spc-alpine-docker | 3 ++- bin/spc-gnu-docker | 2 +- src/SPC/builder/Extension.php | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/spc-alpine-docker b/bin/spc-alpine-docker index 0733b232..cb7d7b4c 100755 --- a/bin/spc-alpine-docker +++ b/bin/spc-alpine-docker @@ -84,7 +84,8 @@ RUN apk update; \ wget \ xz \ gettext-dev \ - binutils-gold + binutils-gold \ + patchelf RUN curl -#fSL https://dl.static-php.dev/static-php-cli/bulk/php-8.4.4-cli-linux-\$(uname -m).tar.gz | tar -xz -C /usr/local/bin && \ chmod +x /usr/local/bin/php diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index 9327cd7e..92496e03 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -141,7 +141,7 @@ echo 'CC=/opt/rh/devtoolset-10/root/usr/bin/gcc' > /tmp/spc-gnu-docker.env echo 'CXX=/opt/rh/devtoolset-10/root/usr/bin/g++' >> /tmp/spc-gnu-docker.env echo 'AR=/opt/rh/devtoolset-10/root/usr/bin/ar' >> /tmp/spc-gnu-docker.env echo 'LD=/opt/rh/devtoolset-10/root/usr/bin/ld' >> /tmp/spc-gnu-docker.env -echo 'SPC_DEFAULT_C_FLAGS=-fPIE -fPIC' >> /tmp/spc-gnu-docker.env +echo 'SPC_DEFAULT_C_FLAGS=-fPIC' >> /tmp/spc-gnu-docker.env echo 'SPC_LIBC=glibc' >> /tmp/spc-gnu-docker.env echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-Wl,-O1 -pie"' >> /tmp/spc-gnu-docker.env echo 'SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm -lresolv -lutil -lrt"' >> /tmp/spc-gnu-docker.env diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index 4ea135a4..a457d82e 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -503,7 +503,7 @@ class Extension continue; } $static_lib = 'lib' . $lib . '.a'; - if (file_exists(BUILD_LIB_PATH . '/' . $static_lib)) { + if (file_exists(BUILD_LIB_PATH . '/' . $static_lib) && !str_contains($static_lib, 'libphp')) { if (!str_contains($staticLibString, '-l' . $lib . ' ')) { $staticLibString .= '-l' . $lib . ' '; } From 1357990c4cf110a69c6e5e21ac1730a7559e0908 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 19 Jun 2025 12:51:00 +0800 Subject: [PATCH 63/70] Add github token hook for curlExec --- src/SPC/builder/unix/UnixBuilderBase.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 4232ef91..d88605ae 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -12,6 +12,7 @@ use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; use SPC\store\Config; +use SPC\store\CurlHook; use SPC\store\Downloader; use SPC\store\FileSystem; use SPC\util\DependencyUtil; @@ -324,7 +325,7 @@ abstract class UnixBuilderBase extends BuilderBase $xcaddyModules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddyModules); } $lrt = PHP_OS_FAMILY === 'Linux' ? '-lrt' : ''; - $releaseInfo = json_decode(Downloader::curlExec('https://api.github.com/repos/php/frankenphp/releases/latest', retries: 3), true); + $releaseInfo = json_decode(Downloader::curlExec('https://api.github.com/repos/php/frankenphp/releases/latest', retries: 3, hooks: [[CurlHook::class, 'setupGithubToken']]), true); $frankenPhpVersion = $releaseInfo['tag_name']; $libphpVersion = $this->getPHPVersion(); if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { From 82ec7733bafd8152ec20df7edc0341b93c224c87 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 11:59:41 +0700 Subject: [PATCH 64/70] musl: all-static! --- src/SPC/builder/unix/UnixBuilderBase.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index d88605ae..a13a8e72 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -331,7 +331,13 @@ abstract class UnixBuilderBase extends BuilderBase if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { $libphpVersion = preg_replace('/\.\d$/', '', $libphpVersion); } - $debugFlags = $this->getOption('--with-debug') ? "'-w -s' " : ''; + $debugFlags = $this->getOption('no-strip') ? "'-w -s' " : ''; + $extLdFlags = "-extldflags '-pie'"; + $muslTags = ''; + if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { + $extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'"; + $muslTags = 'static_build,'; + } $config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list, with_dependencies: true); @@ -344,10 +350,10 @@ abstract class UnixBuilderBase extends BuilderBase 'CGO_CFLAGS' => $config['cflags'], 'CGO_LDFLAGS' => "{$config['ldflags']} {$config['libs']} {$lrt}", 'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' . - '-ldflags \"-linkmode=external -extldflags \'-pie\' ' . $debugFlags . + '-ldflags \"-linkmode=external ' . $extLdFlags . ' ' . $debugFlags . '-X \'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ' . "{$frankenPhpVersion} PHP {$libphpVersion} Caddy'\\\" " . - "-tags=nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", + "-tags={$muslTags}nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, ]; shell()->cd(BUILD_BIN_PATH) From 437d6810b764db7afc572dd3a862b9c99cd46883 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 19 Jun 2025 13:01:26 +0800 Subject: [PATCH 65/70] Bump version, update docs, change owner dunglas to php --- README-zh.md | 2 +- README.md | 2 +- config/env.ini | 2 +- docs/en/guide/manual-build.md | 4 ++++ docs/zh/guide/manual-build.md | 4 ++++ src/SPC/ConsoleApplication.php | 2 +- src/SPC/builder/unix/UnixBuilderBase.php | 4 ++-- 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README-zh.md b/README-zh.md index 0cbc4780..03fa7160 100755 --- a/README-zh.md +++ b/README-zh.md @@ -278,7 +278,7 @@ bin/spc micro:combine my-app.phar -I "memory_limit=4G" -I "disable_functions=sys 如果你知道 [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed),你应该知道如何使用它。对于有可能编译用到引入其他库的问题,你可以使用 `buildroot/bin/php-config` 来获取编译时的配置。 -另外,有关如何使用此功能的高级示例,请查看[如何使用它构建 FrankenPHP 的静态版本](https://github.com/dunglas/frankenphp/blob/main/docs/static.md)。 +另外,有关如何使用此功能的高级示例,请查看[如何使用它构建 FrankenPHP 的静态版本](https://github.com/php/frankenphp/blob/main/docs/static.md)。 ## 贡献 diff --git a/README.md b/README.md index 301d76fd..94c10d58 100755 --- a/README.md +++ b/README.md @@ -302,7 +302,7 @@ If you know [embed SAPI](https://github.com/php/php-src/tree/master/sapi/embed), You may require the introduction of other libraries during compilation, you can use `buildroot/bin/php-config` to obtain the compile-time configuration. -For an advanced example of how to use this feature, take a look at [how to use it to build a static version of FrankenPHP](https://github.com/dunglas/frankenphp/blob/main/docs/static.md). +For an advanced example of how to use this feature, take a look at [how to use it to build a static version of FrankenPHP](https://github.com/php/frankenphp/blob/main/docs/static.md). ## Contribution diff --git a/config/env.ini b/config/env.ini index d80bc3e7..aa85751b 100644 --- a/config/env.ini +++ b/config/env.ini @@ -43,7 +43,7 @@ SPC_SKIP_PHP_VERSION_CHECK="no" ; Ignore some check item for bin/spc doctor command, comma separated (e.g. SPC_SKIP_DOCTOR_CHECK_ITEMS="if homebrew has installed") SPC_SKIP_DOCTOR_CHECK_ITEMS="" ; extra modules that xcaddy will include in the FrankenPHP build -SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/frankenphp/caddy --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" +SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/php/frankenphp/caddy --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" ; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them ; only useful for builds targeting not pure-static linking diff --git a/docs/en/guide/manual-build.md b/docs/en/guide/manual-build.md index 3c40d5eb..80c4e95b 100644 --- a/docs/en/guide/manual-build.md +++ b/docs/en/guide/manual-build.md @@ -167,6 +167,7 @@ If the build is successful, you will see the `buildroot/bin` directory in the cu - fpm: The build result is `buildroot/bin/php-fpm`. - micro: The build result is `buildroot/bin/micro.sfx`. If you need to further package it with PHP code, please refer to [Packaging micro binary](./manual-build#command-micro-combine). - embed: See [Using embed](./manual-build#embed-usage). +- frankenphp: The build result is `buildroot/bin/frankenphp`. If the build fails, you can use the `--debug` parameter to view detailed error information, or use the `--with-clean` to clear the old compilation results and recompile. @@ -290,6 +291,7 @@ You need to specify a compilation target, choose from the following parameters: - `--build-fpm`: Build a fpm sapi (php-fpm, used in conjunction with other traditional fpm architecture software such as nginx) - `--build-micro`: Build a micro sapi (used to build a standalone executable binary containing PHP code) - `--build-embed`: Build an embed sapi (used to embed into other C language programs) +- `--build-frankenphp`: Build a [FrankenPHP](https://github.com/php/frankenphp) executable - `--build-all`: build all above sapi ```bash @@ -509,6 +511,8 @@ When `bin/spc doctor` automatically repairs the Windows environment, tools such Here is an example of installing the tool: - Download and install UPX (Linux and Windows only): `bin/spc install-pkg upx` +- Download and install nasm (Windows only): `bin/spc install-pkg nasm` +- Download and install go-mod-frankenphp: `bin/spc install-pkg go-mod-frankenphp` ## Command - del-download diff --git a/docs/zh/guide/manual-build.md b/docs/zh/guide/manual-build.md index ca0395c8..230680ce 100644 --- a/docs/zh/guide/manual-build.md +++ b/docs/zh/guide/manual-build.md @@ -145,6 +145,7 @@ bin/spc craft --debug - fpm: 构建结果为 `buildroot/bin/php-fpm`。 - micro: 构建结果为 `buildroot/bin/micro.sfx`,如需进一步与 PHP 代码打包,请查看 [打包 micro 二进制](./manual-build#命令-micro-combine-打包-micro-二进制)。 - embed: 参见 [embed 使用](./manual-build#embed-使用)。 +- frankenphp: 构建结果为 `buildroot/bin/frankenphp`。 如果中途构建出错,你可以使用 `--debug` 参数查看详细的错误信息,或者使用 `--with-clean` 参数清除旧的编译结果,重新编译。 @@ -250,6 +251,7 @@ bin/spc doctor --auto-fix - `--build-fpm`: 构建一个 fpm sapi(php-fpm,用于和其他传统的 fpm 架构的软件如 nginx 配合使用) - `--build-micro`: 构建一个 micro sapi(用于构建一个包含 PHP 代码的独立可执行二进制) - `--build-embed`: 构建一个 embed sapi(用于嵌入到其他 C 语言程序中) +- `--build-frankenphp`: 构建一个 [frankenphp](https://github.com/php/frankenphp) 二进制 - `--build-all`: 构建以上所有 sapi ```bash @@ -457,6 +459,8 @@ bin/spc dev:sort-config ext 下面是安装工具的示例: - 下载安装 UPX(仅限 Linux 和 Windows): `bin/spc install-pkg upx` +- 下载安装 nasm(仅限 Windows): `bin/spc install-pkg nasm` +- 下载安装 go-mod-frankenphp: `bin/spc install-pkg go-mod-frankenphp` ## 命令 del-download - 删除已下载的资源 diff --git a/src/SPC/ConsoleApplication.php b/src/SPC/ConsoleApplication.php index c74c9bdc..b69dcf08 100644 --- a/src/SPC/ConsoleApplication.php +++ b/src/SPC/ConsoleApplication.php @@ -33,7 +33,7 @@ use Symfony\Component\Console\Application; */ final class ConsoleApplication extends Application { - public const VERSION = '2.6.0'; + public const VERSION = '2.6.1'; public function __construct() { diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index d88605ae..ef4a4f34 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -317,8 +317,8 @@ abstract class UnixBuilderBase extends BuilderBase $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); // make it possible to build from a different frankenphp directory! - if (!str_contains($xcaddyModules, '--with github.com/dunglas/frankenphp')) { - $xcaddyModules = '--with github.com/dunglas/frankenphp ' . $xcaddyModules; + if (!str_contains($xcaddyModules, '--with github.com/php/frankenphp')) { + $xcaddyModules = '--with github.com/php/frankenphp ' . $xcaddyModules; } if ($this->getLib('brotli') === null && str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { logger()->warning('caddy-cbrotli module is enabled, but brotli library is not built. Disabling caddy-cbrotli.'); From fbd6360bda60536567429fb30617da85644f0325 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 12:07:22 +0700 Subject: [PATCH 66/70] rename go-mod-frankenphp to go-xcaddy --- config/pkg.json | 8 ++++---- src/SPC/builder/BuilderBase.php | 6 +++--- src/SPC/builder/unix/UnixBuilderBase.php | 8 ++++---- src/SPC/command/CraftCommand.php | 6 +++--- .../store/pkg/{GoModFrankenphp.php => GoXcaddy.php} | 10 +++++----- src/globals/test-extensions.php | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) rename src/SPC/store/pkg/{GoModFrankenphp.php => GoXcaddy.php} (91%) diff --git a/config/pkg.json b/config/pkg.json index a2ec8a14..e0762cac 100644 --- a/config/pkg.json +++ b/config/pkg.json @@ -43,16 +43,16 @@ "upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe" } }, - "go-mod-frankenphp-x86_64-linux": { + "go-xcaddy-x86_64-linux": { "type": "custom" }, - "go-mod-frankenphp-aarch64-linux": { + "go-xcaddy-aarch64-linux": { "type": "custom" }, - "go-mod-frankenphp-x86_64-macos": { + "go-xcaddy-x86_64-macos": { "type": "custom" }, - "go-mod-frankenphp-aarch64-macos": { + "go-xcaddy-aarch64-macos": { "type": "custom" } } diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index 956aee6c..88e827c5 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -520,11 +520,11 @@ abstract class BuilderBase if (!in_array(PHP_OS_FAMILY, ['Linux', 'Darwin'])) { throw new WrongUsageException('FrankenPHP SAPI is only available on Linux and macOS!'); } - // frankenphp needs package go-mod-frankenphp installed - $pkg_dir = PKG_ROOT_PATH . '/go-mod-frankenphp-' . arch2gnu(php_uname('m')) . '-' . osfamily2shortname(); + // frankenphp needs package go-xcaddy installed + $pkg_dir = PKG_ROOT_PATH . '/go-xcaddy-' . arch2gnu(php_uname('m')) . '-' . osfamily2shortname(); if (!file_exists("{$pkg_dir}/bin/go") || !file_exists("{$pkg_dir}/bin/xcaddy")) { global $argv; - throw new WrongUsageException("FrankenPHP SAPI requires go-mod-frankenphp package, please install it first: {$argv[0]} install-pkg go-mod-frankenphp"); + throw new WrongUsageException("FrankenPHP SAPI requires the go-xcaddy package, please install it first: {$argv[0]} install-pkg go-xcaddy"); } // frankenphp needs libxml2 lib on macos, see: https://github.com/php/frankenphp/blob/main/frankenphp.go#L17 if (PHP_OS_FAMILY === 'Darwin' && !$this->getLib('libxml2')) { diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index df3ac3ba..e76dfa86 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -311,7 +311,7 @@ abstract class UnixBuilderBase extends BuilderBase $arch = arch2gnu(php_uname('m')); // define executables for go and xcaddy - $xcaddy_exec = PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}/bin/xcaddy"; + $xcaddy_exec = PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin/xcaddy"; $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; @@ -342,9 +342,9 @@ abstract class UnixBuilderBase extends BuilderBase $config = (new SPCConfigUtil($this))->config($this->ext_list, $this->lib_list, with_dependencies: true); $env = [ - 'PATH' => PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}/bin:" . getenv('PATH'), - 'GOROOT' => PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}", - 'GOBIN' => PKG_ROOT_PATH . "/go-mod-frankenphp-{$arch}-{$os}/bin", + 'PATH' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin:" . getenv('PATH'), + 'GOROOT' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}", + 'GOBIN' => PKG_ROOT_PATH . "/go-xcaddy-{$arch}-{$os}/bin", 'GOPATH' => PKG_ROOT_PATH . '/go', 'CGO_ENABLED' => '1', 'CGO_CFLAGS' => $config['cflags'], diff --git a/src/SPC/command/CraftCommand.php b/src/SPC/command/CraftCommand.php index 744d1c57..c8003a3e 100644 --- a/src/SPC/command/CraftCommand.php +++ b/src/SPC/command/CraftCommand.php @@ -68,10 +68,10 @@ class CraftCommand extends BaseCommand } // install go and xcaddy for frankenphp if (in_array('frankenphp', $craft['sapi'])) { - $retcode = $this->runCommand('install-pkg', 'go-mod-frankenphp'); + $retcode = $this->runCommand('install-pkg', 'go-xcaddy'); if ($retcode !== 0) { - $this->output->writeln('craft go-mod-frankenphp failed'); - $this->log("craft go-mod-frankenphp failed with code: {$retcode}", true); + $this->output->writeln('craft go-xcaddy failed'); + $this->log("craft go-xcaddy failed with code: {$retcode}", true); return static::FAILURE; } } diff --git a/src/SPC/store/pkg/GoModFrankenphp.php b/src/SPC/store/pkg/GoXcaddy.php similarity index 91% rename from src/SPC/store/pkg/GoModFrankenphp.php rename to src/SPC/store/pkg/GoXcaddy.php index e631cc8c..81f51266 100644 --- a/src/SPC/store/pkg/GoModFrankenphp.php +++ b/src/SPC/store/pkg/GoXcaddy.php @@ -9,15 +9,15 @@ use SPC\store\FileSystem; use SPC\store\LockFile; use SPC\util\GlobalEnvManager; -class GoModFrankenphp extends CustomPackage +class GoXcaddy extends CustomPackage { public function getSupportName(): array { return [ - 'go-mod-frankenphp-x86_64-linux', - 'go-mod-frankenphp-x86_64-macos', - 'go-mod-frankenphp-aarch64-linux', - 'go-mod-frankenphp-aarch64-macos', + 'go-xcaddy-x86_64-linux', + 'go-xcaddy-x86_64-macos', + 'go-xcaddy-aarch64-linux', + 'go-xcaddy-aarch64-macos', ]; } diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index db51db5e..8ec7d9ca 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -212,7 +212,7 @@ switch ($argv[1] ?? null) { break; case 'build_embed_cmd': if ($frankenphp) { - passthru("{$prefix}install-pkg go-mod-frankenphp --debug", $retcode); + passthru("{$prefix}install-pkg go-xcaddy --debug", $retcode); if ($retcode !== 0) { break; } From 40d602c82ee673a59bed8d1c31cf98264b070ac4 Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 12:08:53 +0700 Subject: [PATCH 67/70] xcaddy fails with this, because the module still calls itself dunglas/frankenphp --- config/env.ini | 2 +- src/SPC/builder/unix/UnixBuilderBase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/env.ini b/config/env.ini index aa85751b..d80bc3e7 100644 --- a/config/env.ini +++ b/config/env.ini @@ -43,7 +43,7 @@ SPC_SKIP_PHP_VERSION_CHECK="no" ; Ignore some check item for bin/spc doctor command, comma separated (e.g. SPC_SKIP_DOCTOR_CHECK_ITEMS="if homebrew has installed") SPC_SKIP_DOCTOR_CHECK_ITEMS="" ; extra modules that xcaddy will include in the FrankenPHP build -SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/php/frankenphp/caddy --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" +SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/frankenphp/caddy --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" ; EXTENSION_DIR where the built php will look for extension when a .ini instructs to load them ; only useful for builds targeting not pure-static linking diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index e76dfa86..27800cd8 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -318,7 +318,7 @@ abstract class UnixBuilderBase extends BuilderBase $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); // make it possible to build from a different frankenphp directory! if (!str_contains($xcaddyModules, '--with github.com/php/frankenphp')) { - $xcaddyModules = '--with github.com/php/frankenphp ' . $xcaddyModules; + $xcaddyModules = '--with github.com/dunglas/frankenphp ' . $xcaddyModules; } if ($this->getLib('brotli') === null && str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { logger()->warning('caddy-cbrotli module is enabled, but brotli library is not built. Disabling caddy-cbrotli.'); From dfac385d215ba7c48498ba991c3eff1351a2d0fc Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 12:23:33 +0700 Subject: [PATCH 68/70] suggestions --- docs/en/guide/manual-build.md | 2 +- docs/zh/guide/manual-build.md | 2 +- src/SPC/builder/unix/UnixBuilderBase.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/guide/manual-build.md b/docs/en/guide/manual-build.md index 80c4e95b..13038360 100644 --- a/docs/en/guide/manual-build.md +++ b/docs/en/guide/manual-build.md @@ -512,7 +512,7 @@ Here is an example of installing the tool: - Download and install UPX (Linux and Windows only): `bin/spc install-pkg upx` - Download and install nasm (Windows only): `bin/spc install-pkg nasm` -- Download and install go-mod-frankenphp: `bin/spc install-pkg go-mod-frankenphp` +- Download and install go-xcaddy: `bin/spc install-pkg go-xcaddy` ## Command - del-download diff --git a/docs/zh/guide/manual-build.md b/docs/zh/guide/manual-build.md index 230680ce..0db0bc86 100644 --- a/docs/zh/guide/manual-build.md +++ b/docs/zh/guide/manual-build.md @@ -460,7 +460,7 @@ bin/spc dev:sort-config ext - 下载安装 UPX(仅限 Linux 和 Windows): `bin/spc install-pkg upx` - 下载安装 nasm(仅限 Windows): `bin/spc install-pkg nasm` -- 下载安装 go-mod-frankenphp: `bin/spc install-pkg go-mod-frankenphp` +- 下载安装 go-xcaddy: `bin/spc install-pkg go-xcaddy` ## 命令 del-download - 删除已下载的资源 diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 27800cd8..0d56b0eb 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -317,7 +317,7 @@ abstract class UnixBuilderBase extends BuilderBase $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); // make it possible to build from a different frankenphp directory! - if (!str_contains($xcaddyModules, '--with github.com/php/frankenphp')) { + if (!str_contains($xcaddyModules, '--with github.com/dunglas/frankenphp')) { $xcaddyModules = '--with github.com/dunglas/frankenphp ' . $xcaddyModules; } if ($this->getLib('brotli') === null && str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { From 608a5559ac305c6a03b4b972b8651a1894d736ea Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 12:38:05 +0700 Subject: [PATCH 69/70] fix incorrect array offset --- src/SPC/store/pkg/GoXcaddy.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SPC/store/pkg/GoXcaddy.php b/src/SPC/store/pkg/GoXcaddy.php index 81f51266..e0c7c5b9 100644 --- a/src/SPC/store/pkg/GoXcaddy.php +++ b/src/SPC/store/pkg/GoXcaddy.php @@ -29,12 +29,12 @@ class GoXcaddy extends CustomPackage if (file_exists($go_exec) && file_exists($xcaddy_exec)) { return; } - $arch = match (explode('-', $name)[3]) { + $arch = match (explode('-', $name)[2]) { 'x86_64' => 'amd64', 'aarch64' => 'arm64', default => throw new \InvalidArgumentException('Unsupported architecture: ' . $name), }; - $os = match (explode('-', $name)[4]) { + $os = match (explode('-', $name)[3]) { 'linux' => 'linux', 'macos' => 'darwin', default => throw new \InvalidArgumentException('Unsupported OS: ' . $name), From 16a4245ad7ce39213e09428b4ae82d6681e4aa1a Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Thu, 19 Jun 2025 12:40:42 +0700 Subject: [PATCH 70/70] common static extension tests --- src/globals/test-extensions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index 8ec7d9ca..229da487 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -48,7 +48,7 @@ $prefer_pre_built = false; // If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`). $extensions = match (PHP_OS_FAMILY) { - 'Linux', 'Darwin' => 'dom,mongodb', + 'Linux', 'Darwin' => 'apcu,ast,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,iconv,libxml,mbregex,mbstring,opcache,openssl,pcntl,phar,posix,readline,session,simplexml,sockets,sodium,tokenizer,xml,xmlreader,xmlwriter,zip,zlib', 'Windows' => 'xlswriter,openssl', };