From 12aadf18cc2a7b30a2f993896c9e2d5555e29bef Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sat, 28 Jun 2025 16:36:05 +0800 Subject: [PATCH] refactor: replace SPC_LIBC with SPC_TARGET and update related logic --- .github/workflows/tests.yml | 2 +- bin/build-static-frankenphp | 4 +- bin/spc-gnu-docker | 4 +- config/env.ini | 18 +++- docs/en/guide/extension-notes.md | 2 +- docs/zh/guide/extension-notes.md | 2 +- src/SPC/ConsoleApplication.php | 2 + src/SPC/builder/extension/imagick.php | 7 +- src/SPC/builder/linux/LinuxBuilder.php | 16 +--- src/SPC/builder/linux/SystemUtil.php | 5 +- src/SPC/builder/linux/library/icu.php | 3 +- src/SPC/builder/unix/UnixBuilderBase.php | 5 +- src/SPC/builder/unix/library/imagemagick.php | 11 ++- src/SPC/builder/unix/library/ldap.php | 3 +- src/SPC/builder/unix/library/libxslt.php | 8 +- src/SPC/builder/unix/library/mimalloc.php | 3 +- src/SPC/builder/unix/library/pkgconfig.php | 4 +- src/SPC/builder/unix/library/postgresql.php | 3 +- src/SPC/command/BuildPHPCommand.php | 5 +- src/SPC/command/DownloadCommand.php | 7 +- src/SPC/command/dev/EnvCommand.php | 37 +++++++++ src/SPC/command/dev/PackLibCommand.php | 9 +- src/SPC/doctor/CheckListHandler.php | 8 ++ src/SPC/doctor/OptionalCheck.php | 11 +++ src/SPC/doctor/item/LinuxMuslCheck.php | 22 ++--- src/SPC/store/Downloader.php | 12 ++- src/SPC/util/GlobalEnvManager.php | 48 ++++++----- src/SPC/util/SPCConfigUtil.php | 4 +- src/SPC/util/SPCTarget.php | 83 +++++++++++++++++++ .../util/toolchain/ClangNativeToolchain.php | 33 ++++++++ src/SPC/util/toolchain/GccNativeToolchain.php | 32 +++++++ src/SPC/util/toolchain/MSVCToolchain.php | 12 +++ src/SPC/util/toolchain/MuslToolchain.php | 44 ++++++++++ src/SPC/util/toolchain/ToolchainInterface.php | 18 ++++ src/SPC/util/toolchain/ZigToolchain.php | 18 ++++ 35 files changed, 420 insertions(+), 85 deletions(-) create mode 100644 src/SPC/command/dev/EnvCommand.php create mode 100644 src/SPC/doctor/OptionalCheck.php create mode 100644 src/SPC/util/SPCTarget.php create mode 100644 src/SPC/util/toolchain/ClangNativeToolchain.php create mode 100644 src/SPC/util/toolchain/GccNativeToolchain.php create mode 100644 src/SPC/util/toolchain/MSVCToolchain.php create mode 100644 src/SPC/util/toolchain/MuslToolchain.php create mode 100644 src/SPC/util/toolchain/ToolchainInterface.php create mode 100644 src/SPC/util/toolchain/ZigToolchain.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5ed6d59d..ee4a17fb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -105,7 +105,7 @@ jobs: run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: "Run PHPUnit Tests" - run: SPC_LIBC=glibc vendor/bin/phpunit tests/ --no-coverage + run: SPC_TARGET=glibc vendor/bin/phpunit tests/ --no-coverage define-matrix: name: "Define Matrix" diff --git a/bin/build-static-frankenphp b/bin/build-static-frankenphp index 10a693a7..bc5cdb33 100755 --- a/bin/build-static-frankenphp +++ b/bin/build-static-frankenphp @@ -92,7 +92,7 @@ ADD ./bin/spc /app/bin/spc RUN /app/bin/setup-runtime RUN /app/bin/php /app/bin/composer install --no-dev --classmap-authoritative ENV PATH="/app/bin:/cmake/bin:/usr/local/go/bin:$PATH" -ENV SPC_LIBC=glibc +ENV SPC_TARGET=glibc ADD ./config/env.ini /app/config/env.ini RUN bin/spc doctor --auto-fix --debug @@ -146,7 +146,7 @@ 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_LIBC=glibc' >> /tmp/spc-gnu-docker.env +echo 'SPC_TARGET=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/bin/spc-gnu-docker b/bin/spc-gnu-docker index 6cc36bfa..523905c5 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -108,7 +108,7 @@ ADD ./bin/spc /app/bin/spc RUN /app/bin/setup-runtime RUN /app/bin/php /app/bin/composer install --no-dev ENV PATH="/app/bin:/cmake/bin:$PATH" -ENV SPC_LIBC=glibc +ENV SPC_TARGET=glibc ADD ./config/env.ini /app/config/env.ini RUN CC=gcc bin/spc doctor --auto-fix --debug @@ -159,7 +159,7 @@ 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=-fPIC' >> /tmp/spc-gnu-docker.env -echo 'SPC_LIBC=glibc' >> /tmp/spc-gnu-docker.env +echo 'SPC_TARGET=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/config/env.ini b/config/env.ini index d80bc3e7..a8e7a8a6 100644 --- a/config/env.ini +++ b/config/env.ini @@ -55,6 +55,8 @@ SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/frankenphp/cadd ; EXTENSION_DIR= [windows] +; build target: win7-static +SPC_TARGET=msvc-static ; php-sdk-binary-tools path PHP_SDK_PATH="${WORKING_DIR}\php-sdk-binary-tools" ; upx executable path @@ -63,8 +65,17 @@ UPX_EXEC="${PKG_ROOT_PATH}\bin\upx.exe" SPC_MICRO_PATCHES=static_extensions_win32,cli_checks,disable_huge_page,vcruntime140,win32,zend_stream,cli_static [linux] -; include PATH for musl libc. -SPC_LIBC=musl +; Linux can use different build toolchain, but the toolchain can not be changed in this file: +; - musl (default): used for general linux distros, can build `musl-static` target only. +; - zig (WIP): used for general linux distros, can build `musl` and `glibc` targets. +; - musl-native: used for alpine linux, can build `musl-static` and `musl`(WIP) target. +; - gnu-native (assume): used for general linux distros, can build `glibc` target only and have portability issues. + +; build target: +; - musl-static (default): pure static linking, using musl-libc, can run on any linux distro. +; - musl: static linking with dynamic linking to musl-libc, can run on musl-based linux distro. +; - glibc: static linking with dynamic linking to glibc, can run on glibc-based linux distro. +SPC_TARGET=musl-static ; compiler environments CC=${SPC_LINUX_DEFAULT_CC} CXX=${SPC_LINUX_DEFAULT_CXX} @@ -109,6 +120,9 @@ SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS="" SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM="-all-static -Wl,-O1 -pie" [macos] +; build target: macho or macho (possibly we could support macho-universal in the future) +; Currently we do not support universal and cross-compilation for macOS. +SPC_TARGET=macho ; compiler environments CC=clang CXX=clang++ diff --git a/docs/en/guide/extension-notes.md b/docs/en/guide/extension-notes.md index aa11c377..5a022b59 100644 --- a/docs/en/guide/extension-notes.md +++ b/docs/en/guide/extension-notes.md @@ -82,7 +82,7 @@ and this extension cannot be compiled into php by static linking, so it cannot b ## xdebug -1. Xdebug is only buildable as a shared extension. On Linux, you need to use static-php-cli with SPC_LIBC=glibc. +1. Xdebug is only buildable as a shared extension. You need to use a build target other than `musl-static` for SPC_TARGET. 2. When using Linux/glibc or macOS, you can compile Xdebug as a shared extension using --build-shared="xdebug". The compiled `./php` binary can be configured and run by specifying the INI, eg `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`. diff --git a/docs/zh/guide/extension-notes.md b/docs/zh/guide/extension-notes.md index 10e62eb0..ad787dd9 100644 --- a/docs/zh/guide/extension-notes.md +++ b/docs/zh/guide/extension-notes.md @@ -76,7 +76,7 @@ bin/spc build gd --with-libs=freetype,libjpeg,libavif,libwebp --build-cli ## xdebug -1. Xdebug 只能作为共享扩展进行构建。在 Linux 上,您需要使用 static-php-cli 并设置 SPC_LIBC=glibc。 +1. Xdebug 只能作为共享扩展进行构建。您需要使用除了 `musl-static` 外的其他 `SPC_TARGET` 构建目标。 2. 使用 Linux/glibc 或 macOS 时,您可以使用 `--build-shared=xdebug` 将 Xdebug 编译为共享扩展。 编译后的 `./php` 二进制文件可以通过指定 INI 文件进行配置和运行,例如 `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`。 diff --git a/src/SPC/ConsoleApplication.php b/src/SPC/ConsoleApplication.php index b69dcf08..a24eada9 100644 --- a/src/SPC/ConsoleApplication.php +++ b/src/SPC/ConsoleApplication.php @@ -9,6 +9,7 @@ use SPC\command\BuildPHPCommand; use SPC\command\CraftCommand; use SPC\command\DeleteDownloadCommand; use SPC\command\dev\AllExtCommand; +use SPC\command\dev\EnvCommand; use SPC\command\dev\ExtVerCommand; use SPC\command\dev\GenerateExtDepDocsCommand; use SPC\command\dev\GenerateExtDocCommand; @@ -70,6 +71,7 @@ final class ConsoleApplication extends Application new GenerateExtDepDocsCommand(), new GenerateLibDepDocsCommand(), new PackLibCommand(), + new EnvCommand(), ] ); } diff --git a/src/SPC/builder/extension/imagick.php b/src/SPC/builder/extension/imagick.php index 7951ea69..bcdbffab 100644 --- a/src/SPC/builder/extension/imagick.php +++ b/src/SPC/builder/extension/imagick.php @@ -6,6 +6,7 @@ namespace SPC\builder\extension; use SPC\builder\Extension; use SPC\util\CustomExt; +use SPC\util\SPCTarget; #[CustomExt('imagick')] class imagick extends Extension @@ -15,7 +16,7 @@ class imagick extends Extension if (PHP_OS_FAMILY !== 'Linux') { return false; } - if (getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) { + if (SPCTarget::isTarget(SPCTarget::GLIBC)) { return false; } // imagick with calls omp_pause_all, which requires openmp, on non-musl we build imagick without openmp @@ -26,7 +27,7 @@ class imagick extends Extension public function getUnixConfigureArg(bool $shared = false): string { - $disable_omp = !(getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) ? '' : ' ac_cv_func_omp_pause_resource_all=no'; + $disable_omp = SPCTarget::isTarget(SPCTarget::GLIBC) ? ' ac_cv_func_omp_pause_resource_all=no' : ''; return '--with-imagick=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $disable_omp; } @@ -34,7 +35,7 @@ class imagick extends Extension { // 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')) { + if (SPCTarget::isTarget(SPCTarget::GLIBC)) { $static .= ' -lstdc++'; $shared = str_replace('-lstdc++', '', $shared); } diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 68548615..94d8a41e 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -25,22 +25,8 @@ class LinuxBuilder extends UnixBuilderBase { $this->options = $options; - // check musl-cross make installed if we use musl-cross-make - $arch = arch2gnu(php_uname('m')); - GlobalEnvManager::init(); - - 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\""); - $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}"); - - if (!file_exists("/usr/local/musl/{$arch}-linux-musl/lib/libc.a")) { - throw new WrongUsageException('You are building with musl-libc target in glibc distro, but musl-toolchain is not installed, please install musl-toolchain first. (You can use `doctor` command to install it)'); - } - } + GlobalEnvManager::afterInit(); // concurrency $this->concurrency = intval(getenv('SPC_CONCURRENCY')); diff --git a/src/SPC/builder/linux/SystemUtil.php b/src/SPC/builder/linux/SystemUtil.php index a85f3dec..1e405768 100644 --- a/src/SPC/builder/linux/SystemUtil.php +++ b/src/SPC/builder/linux/SystemUtil.php @@ -6,6 +6,7 @@ namespace SPC\builder\linux; use SPC\builder\traits\UnixSystemUtilTrait; use SPC\exception\RuntimeException; +use SPC\util\SPCTarget; class SystemUtil { @@ -193,7 +194,7 @@ class SystemUtil if (self::$libc_version !== null) { return self::$libc_version; } - if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'glibc') { + if (SPCTarget::isTarget(SPCTarget::GLIBC)) { $result = shell()->execWithResult('ldd --version', false); if ($result[0] !== 0) { return null; @@ -208,7 +209,7 @@ class SystemUtil } return null; } - if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { + if (SPCTarget::isTarget(SPCTarget::MUSL_STATIC)) { if (self::isMuslDist()) { $result = shell()->execWithResult('ldd 2>&1', false); } else { diff --git a/src/SPC/builder/linux/library/icu.php b/src/SPC/builder/linux/library/icu.php index aa2825b7..6fb7e1fc 100644 --- a/src/SPC/builder/linux/library/icu.php +++ b/src/SPC/builder/linux/library/icu.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace SPC\builder\linux\library; use SPC\store\FileSystem; +use SPC\util\SPCTarget; class icu extends LinuxLibraryBase { @@ -16,7 +17,7 @@ class icu extends LinuxLibraryBase { $cppflags = 'CPPFLAGS="-DU_CHARSET_IS_UTF8=1 -DU_USING_ICU_NAMESPACE=1 -DU_STATIC_IMPLEMENTATION=1 -DPIC -fPIC"'; $cxxflags = 'CXXFLAGS="-std=c++17 -DPIC -fPIC -fno-ident"'; - $ldflags = getenv('SPC_LIBC') !== 'glibc' ? 'LDFLAGS="-static"' : ''; + $ldflags = SPCTarget::isTarget(SPCTarget::MUSL_STATIC) ? 'LDFLAGS="-static"' : ''; shell()->cd($this->source_dir . '/source')->initializeEnv($this) ->exec( "{$cppflags} {$cxxflags} {$ldflags} " . diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 0d56b0eb..886eec68 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -17,6 +17,7 @@ use SPC\store\Downloader; use SPC\store\FileSystem; use SPC\util\DependencyUtil; use SPC\util\SPCConfigUtil; +use SPC\util\SPCTarget; abstract class UnixBuilderBase extends BuilderBase { @@ -200,7 +201,7 @@ abstract class UnixBuilderBase extends BuilderBase $util = new SPCConfigUtil($this); $config = $util->config($this->ext_list, $this->lib_list, $this->getOption('with-suggested-exts'), $this->getOption('with-suggested-libs')); $lens = "{$config['cflags']} {$config['ldflags']} {$config['libs']}"; - if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { + if (SPCTarget::isTarget(SPCTarget::MUSL_STATIC)) { $lens .= ' -static'; } [$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens); @@ -334,7 +335,7 @@ abstract class UnixBuilderBase extends BuilderBase $debugFlags = $this->getOption('no-strip') ? "'-w -s' " : ''; $extLdFlags = "-extldflags '-pie'"; $muslTags = ''; - if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { + if (SPCTarget::isTarget(SPCTarget::MUSL_STATIC)) { $extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'"; $muslTags = 'static_build,'; } diff --git a/src/SPC/builder/unix/library/imagemagick.php b/src/SPC/builder/unix/library/imagemagick.php index ead786a2..88ae9f7d 100644 --- a/src/SPC/builder/unix/library/imagemagick.php +++ b/src/SPC/builder/unix/library/imagemagick.php @@ -4,12 +4,11 @@ declare(strict_types=1); namespace SPC\builder\unix\library; -use SPC\builder\linux\library\LinuxLibraryBase; -use SPC\builder\macos\library\MacOSLibraryBase; use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; use SPC\store\FileSystem; use SPC\util\executor\UnixAutoconfExecutor; +use SPC\util\SPCTarget; trait imagemagick { @@ -33,16 +32,16 @@ trait imagemagick ->optionalLib('bzip2', ...ac_with_args('bzlib')) ->addConfigureArgs( // TODO: glibc rh 10 toolset's libgomp.a was built without -fPIC so we can't use openmp without depending on libgomp.so - getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10') ? '--disable-openmp' : '--enable-openmp', + SPCTarget::isTarget(SPCTarget::GLIBC) ? '--disable-openmp' : '--enable-openmp', '--without-jxl', '--without-x', ); - // special: linux musl needs `-static` - $ldflags = ($this instanceof LinuxLibraryBase) && getenv('SPC_LIBC') !== 'glibc' ? ('-static -ldl') : '-ldl'; + // special: linux musl-static needs `-static` + $ldflags = !SPCTarget::isTarget(SPCTarget::MUSL_STATIC) ? ('-static -ldl') : '-ldl'; // special: macOS needs -iconv - $libs = $this instanceof MacOSLibraryBase ? '-liconv' : ''; + $libs = SPCTarget::isTarget(SPCTarget::MACHO) ? '-liconv' : ''; $ac->appendEnv([ 'LDFLAGS' => $ldflags, diff --git a/src/SPC/builder/unix/library/ldap.php b/src/SPC/builder/unix/library/ldap.php index 62f7bcd7..e87310cb 100644 --- a/src/SPC/builder/unix/library/ldap.php +++ b/src/SPC/builder/unix/library/ldap.php @@ -6,12 +6,13 @@ namespace SPC\builder\unix\library; use SPC\store\FileSystem; use SPC\util\executor\UnixAutoconfExecutor; +use SPC\util\SPCTarget; trait ldap { public function patchBeforeBuild(): bool { - $extra = getenv('SPC_LIBC') === 'glibc' ? '-ldl -lpthread -lm -lresolv -lutil' : ''; + $extra = SPCTarget::isTarget(SPCTarget::GLIBC) ? '-ldl -lpthread -lm -lresolv -lutil' : ''; FileSystem::replaceFileStr($this->source_dir . '/configure', '"-lssl -lcrypto', '"-lssl -lcrypto -lz ' . $extra); return true; } diff --git a/src/SPC/builder/unix/library/libxslt.php b/src/SPC/builder/unix/library/libxslt.php index c820f8f6..aabc277d 100644 --- a/src/SPC/builder/unix/library/libxslt.php +++ b/src/SPC/builder/unix/library/libxslt.php @@ -33,7 +33,13 @@ trait libxslt '--without-debugger', "--with-libxml-prefix={$this->getBuildRootPath()}", ); - $ac->exec("{$this->builder->getOption('library_path')} {$this->builder->getOption('ld_library_path')} ./configure {$ac->getConfigureArgsString()}")->make(); + if (getenv('SPC_LINUX_DEFAULT_LD_LIBRARY_PATH') && getenv('SPC_LINUX_DEFAULT_LIBRARY_PATH')) { + $ac->appendEnv([ + 'LD_LIBRARY_PATH' => getenv('SPC_LINUX_DEFAULT_LD_LIBRARY_PATH'), + 'LIBRARY_PATH' => getenv('SPC_LINUX_DEFAULT_LIBRARY_PATH'), + ]); + } + $ac->configure()->make(); $this->patchPkgconfPrefix(['libexslt.pc']); $this->patchLaDependencyPrefix(); diff --git a/src/SPC/builder/unix/library/mimalloc.php b/src/SPC/builder/unix/library/mimalloc.php index 78868856..0808516c 100644 --- a/src/SPC/builder/unix/library/mimalloc.php +++ b/src/SPC/builder/unix/library/mimalloc.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace SPC\builder\unix\library; use SPC\util\executor\UnixCMakeExecutor; +use SPC\util\SPCTarget; trait mimalloc { @@ -15,7 +16,7 @@ trait mimalloc '-DMI_BUILD_SHARED=OFF', '-DMI_INSTALL_TOPLEVEL=ON' ); - if (getenv('SPC_LIBC') === 'musl') { + if (SPCTarget::isTarget(SPCTarget::MUSL) || SPCTarget::isTarget(SPCTarget::MUSL_STATIC)) { $cmake->addConfigureArgs('-DMI_LIBC_MUSL=ON'); } $cmake->build(); diff --git a/src/SPC/builder/unix/library/pkgconfig.php b/src/SPC/builder/unix/library/pkgconfig.php index 05727f96..3522c52a 100644 --- a/src/SPC/builder/unix/library/pkgconfig.php +++ b/src/SPC/builder/unix/library/pkgconfig.php @@ -4,8 +4,8 @@ declare(strict_types=1); namespace SPC\builder\unix\library; -use SPC\builder\linux\library\LinuxLibraryBase; use SPC\util\executor\UnixAutoconfExecutor; +use SPC\util\SPCTarget; trait pkgconfig { @@ -14,7 +14,7 @@ trait pkgconfig UnixAutoconfExecutor::create($this) ->appendEnv([ 'CFLAGS' => PHP_OS_FAMILY !== 'Linux' ? '-Wimplicit-function-declaration -Wno-int-conversion' : '', - 'LDFLAGS' => !($this instanceof LinuxLibraryBase) || getenv('SPC_LIBC') === 'glibc' ? '' : '--static', + 'LDFLAGS' => SPCTarget::isStaticTarget() ? '--static' : '', ]) ->configure( '--with-internal-glib', diff --git a/src/SPC/builder/unix/library/postgresql.php b/src/SPC/builder/unix/library/postgresql.php index 1de3e4c2..efcfc089 100644 --- a/src/SPC/builder/unix/library/postgresql.php +++ b/src/SPC/builder/unix/library/postgresql.php @@ -8,6 +8,7 @@ use SPC\builder\linux\library\LinuxLibraryBase; use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; use SPC\store\FileSystem; +use SPC\util\SPCTarget; trait postgresql { @@ -50,7 +51,7 @@ trait postgresql $error_exec_cnt += $output[0] === 0 ? 0 : 1; if (!empty($output[1][0])) { $ldflags = $output[1][0]; - $envs .= !($this instanceof LinuxLibraryBase) || getenv('SPC_LIBC') === 'glibc' ? " LDFLAGS=\"{$ldflags}\" " : " LDFLAGS=\"{$ldflags} -static\" "; + $envs .= SPCTarget::isTarget(SPCTarget::MUSL_STATIC) ? " LDFLAGS=\"{$ldflags} -static\" " : " LDFLAGS=\"{$ldflags}\" "; } $output = shell()->execWithResult("pkg-config --libs-only-l --static {$packages}"); $error_exec_cnt += $output[0] === 0 ? 0 : 1; diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 21a848a5..f9938957 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -13,6 +13,7 @@ use SPC\store\SourcePatcher; use SPC\util\DependencyUtil; use SPC\util\GlobalEnvManager; use SPC\util\LicenseDumper; +use SPC\util\SPCTarget; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -63,7 +64,7 @@ class BuildPHPCommand extends BuildCommand // check dynamic extension build env // linux must build with glibc - if (!empty($shared_extensions) && PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') !== 'glibc') { + if (!empty($shared_extensions) && SPCTarget::isTarget(SPCTarget::MUSL_STATIC)) { $this->output->writeln('Linux does not support dynamic extension loading with musl-libc full-static build, please build with glibc!'); return static::FAILURE; } @@ -133,6 +134,8 @@ class BuildPHPCommand extends BuildCommand // print info $indent_texts = [ 'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')', + 'Build Target' => getenv('SPC_TARGET'), + 'Build Toolchain' => getenv('SPC_TOOLCHAIN'), 'Build SAPI' => $builder->getBuildTypeName($rule), 'Static Extensions (' . count($static_extensions) . ')' => implode(',', $static_extensions), 'Shared Extensions (' . count($shared_extensions) . ')' => implode(',', $shared_extensions), diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index d5054aa9..35529f5e 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -14,6 +14,7 @@ use SPC\store\Config; use SPC\store\Downloader; use SPC\store\LockFile; use SPC\util\DependencyUtil; +use SPC\util\SPCTarget; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -223,7 +224,11 @@ class DownloadCommand extends BaseCommand '{name}' => $source, '{arch}' => arch2gnu(php_uname('m')), '{os}' => strtolower(PHP_OS_FAMILY), - '{libc}' => getenv('SPC_LIBC') ?: 'default', + '{libc}' => match (getenv('SPC_TARGET')) { + SPCTarget::MUSL_STATIC, SPCTarget::MUSL => 'musl', + SPCTarget::GLIBC => 'glibc', + default => 'default', + }, '{libcver}' => PHP_OS_FAMILY === 'Linux' ? (SystemUtil::getLibcVersionIfExists() ?? 'default') : 'default', ]; $find = str_replace(array_keys($replace), array_values($replace), Config::getPreBuilt('match-pattern')); diff --git a/src/SPC/command/dev/EnvCommand.php b/src/SPC/command/dev/EnvCommand.php new file mode 100644 index 00000000..d5a0cec3 --- /dev/null +++ b/src/SPC/command/dev/EnvCommand.php @@ -0,0 +1,37 @@ +addArgument('env', InputArgument::REQUIRED, 'The environment variable to show, if not set, all will be shown'); + } + + public function initialize(InputInterface $input, OutputInterface $output): void + { + $this->no_motd = true; + parent::initialize($input, $output); + } + + public function handle(): int + { + $env = $this->getArgument('env'); + if (($val = getenv($env)) === false) { + $this->output->writeln("Environment variable '{$env}' is not set."); + return static::FAILURE; + } + $this->output->writeln("{$val}"); + return static::SUCCESS; + } +} diff --git a/src/SPC/command/dev/PackLibCommand.php b/src/SPC/command/dev/PackLibCommand.php index d0d9797e..29a22353 100644 --- a/src/SPC/command/dev/PackLibCommand.php +++ b/src/SPC/command/dev/PackLibCommand.php @@ -16,6 +16,7 @@ use SPC\store\Config; use SPC\store\FileSystem; use SPC\store\LockFile; use SPC\util\DependencyUtil; +use SPC\util\SPCTarget; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; @@ -76,8 +77,12 @@ class PackLibCommand extends BuildCommand '{name}' => $lib->getName(), '{arch}' => arch2gnu(php_uname('m')), '{os}' => strtolower(PHP_OS_FAMILY), - '{libc}' => getenv('SPC_LIBC') ?: 'default', - '{libcver}' => PHP_OS_FAMILY === 'Linux' ? (SystemUtil::getLibcVersionIfExists() ?? 'default') : 'default', + '{libc}' => match (getenv('SPC_TARGET')) { + SPCTarget::MUSL_STATIC, SPCTarget::MUSL => 'musl', + SPCTarget::GLIBC => 'glibc', + default => 'default', + }, + '{libcver}' => SystemUtil::getLibcVersionIfExists() ?? 'default', ]; // detect suffix, for proper tar option $tar_option = $this->getTarOptionFromSuffix(Config::getPreBuilt('match-pattern')); diff --git a/src/SPC/doctor/CheckListHandler.php b/src/SPC/doctor/CheckListHandler.php index 23005dfd..cac17ce3 100644 --- a/src/SPC/doctor/CheckListHandler.php +++ b/src/SPC/doctor/CheckListHandler.php @@ -72,6 +72,14 @@ final class CheckListHandler { foreach (FileSystem::getClassesPsr4(__DIR__ . '/item', 'SPC\doctor\item') as $class) { $ref = new \ReflectionClass($class); + $optional = $ref->getAttributes(OptionalCheck::class)[0] ?? null; + if ($optional !== null) { + /** @var OptionalCheck $instance */ + $instance = $optional->newInstance(); + if (is_callable($instance->check) && !call_user_func($instance->check)) { + continue; // skip this class if optional check is false + } + } foreach ($ref->getMethods() as $method) { foreach ($method->getAttributes() as $a) { if (is_a($a->getName(), AsCheckItem::class, true)) { diff --git a/src/SPC/doctor/OptionalCheck.php b/src/SPC/doctor/OptionalCheck.php new file mode 100644 index 00000000..4dae938b --- /dev/null +++ b/src/SPC/doctor/OptionalCheck.php @@ -0,0 +1,11 @@ + 'musl', + SPCTarget::GLIBC => 'glibc', + default => 'default', + }; + $libc_version = SystemUtil::getLibcVersionIfExists() ?? 'default'; + + return "{$source}-{$os_family}-{$gnu_arch}-{$libc}-{$libc_version}"; } /** diff --git a/src/SPC/util/GlobalEnvManager.php b/src/SPC/util/GlobalEnvManager.php index fa5c9cf5..a526dd5a 100644 --- a/src/SPC/util/GlobalEnvManager.php +++ b/src/SPC/util/GlobalEnvManager.php @@ -40,24 +40,6 @@ class GlobalEnvManager self::putenv('PKG_CONFIG_PATH=' . BUILD_ROOT_PATH . '/lib/pkgconfig'); } - // Define env vars for linux - if (PHP_OS_FAMILY === 'Linux') { - $arch = getenv('GNU_ARCH'); - 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'); - 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"); - self::addPathIfNotExists('/usr/local/musl/bin'); - self::addPathIfNotExists("/usr/local/musl/{$arch}-linux-musl/bin"); - } - } - $ini = self::readIniFile(); $default_put_list = []; @@ -80,6 +62,29 @@ class GlobalEnvManager self::putenv("{$k}={$v}"); } } + + // deprecated: convert SPC_LIBC to SPC_TARGET + if (getenv('SPC_LIBC') !== false) { + logger()->warning('SPC_LIBC is deprecated, please use SPC_TARGET instead.'); + $target = match (getenv('SPC_LIBC')) { + 'musl' => SPCTarget::MUSL_STATIC, + default => SPCTarget::GLIBC, + }; + self::putenv("SPC_TARGET={$target}"); + self::putenv('SPC_LIBC'); + } + + // auto-select toolchain based on target and OS temporarily + // TODO: use 'zig' instead of 'gcc-native' when ZigToolchain is implemented + $toolchain = match (getenv('SPC_TARGET')) { + SPCTarget::MUSL_STATIC, SPCTarget::MUSL => SystemUtil::isMuslDist() ? 'gcc-native' : 'musl', + SPCTarget::MACHO => 'clang-native', + SPCTarget::MSVC_STATIC => 'msvc', + default => 'gcc-native', + }; + + SPCTarget::initTargetForToolchain($toolchain); + // apply second time $ini2 = self::readIniFile(); @@ -108,13 +113,18 @@ class GlobalEnvManager self::$env_cache[] = $val; } - private static function addPathIfNotExists(string $path): void + public static function addPathIfNotExists(string $path): void { if (is_unix() && !str_contains(getenv('PATH'), $path)) { self::putenv("PATH={$path}:" . getenv('PATH')); } } + public static function afterInit(): void + { + SPCTarget::afterInitTargetForToolchain(); + } + /** * @throws WrongUsageException */ diff --git a/src/SPC/util/SPCConfigUtil.php b/src/SPC/util/SPCConfigUtil.php index 8e69e89c..bf441efe 100644 --- a/src/SPC/util/SPCConfigUtil.php +++ b/src/SPC/util/SPCConfigUtil.php @@ -55,7 +55,7 @@ class SPCConfigUtil ob_get_clean(); $ldflags = $this->getLdflagsString(); $libs = $this->getLibsString($libraries, $with_dependencies); - if (PHP_OS_FAMILY === 'Darwin') { + if (SPCTarget::isTarget(SPCTarget::MACHO)) { $libs .= " {$this->getFrameworksString($extensions)}"; } $cflags = $this->getIncludesString(); @@ -146,7 +146,7 @@ class SPCConfigUtil } } // patch: imagick (imagemagick wrapper) for linux needs libgomp - if (in_array('imagemagick', $libraries) && PHP_OS_FAMILY === 'Linux' && !(getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10'))) { + if (in_array('imagemagick', $libraries) && !SPCTarget::isTarget(SPCTarget::GLIBC)) { $short_name[] = '-lgomp'; } return implode(' ', $short_name); diff --git a/src/SPC/util/SPCTarget.php b/src/SPC/util/SPCTarget.php new file mode 100644 index 00000000..2d81fc4e --- /dev/null +++ b/src/SPC/util/SPCTarget.php @@ -0,0 +1,83 @@ + MuslToolchain::class, + 'gcc-native' => GccNativeToolchain::class, + 'clang-native' => ClangNativeToolchain::class, + 'msvc' => MSVCToolchain::class, + 'zig' => ZigToolchain::class, + ]; + + public static function isTarget(string $target): bool + { + $env = getenv('SPC_TARGET'); + if ($env === false) { + return false; + } + $env = strtolower($env); + return $env === $target; + } + + public static function isStaticTarget(): bool + { + $env = getenv('SPC_TARGET'); + if ($env === false) { + return false; + } + $env = strtolower($env); + return str_ends_with($env, '-static') || $env === self::MUSL_STATIC; + } + + public static function initTargetForToolchain(string $toolchain): void + { + $target = getenv('SPC_TARGET'); + $toolchain = strtolower($toolchain); + if (isset(self::TOOLCHAIN_LIST[$toolchain])) { + $toolchainClass = self::TOOLCHAIN_LIST[$toolchain]; + /* @var ToolchainInterface $toolchainClass */ + (new $toolchainClass())->initEnv($target); + } + GlobalEnvManager::putenv("SPC_TOOLCHAIN={$toolchain}"); + } + + public static function afterInitTargetForToolchain() + { + if (!getenv('SPC_TOOLCHAIN')) { + throw new WrongUsageException('SPC_TOOLCHAIN not set'); + } + $toolchain = getenv('SPC_TOOLCHAIN'); + if (!isset(self::TOOLCHAIN_LIST[$toolchain])) { + throw new WrongUsageException("Unknown toolchain: {$toolchain}"); + } + $toolchainClass = self::TOOLCHAIN_LIST[$toolchain]; + (new $toolchainClass())->afterInit(getenv('SPC_TARGET')); + } +} diff --git a/src/SPC/util/toolchain/ClangNativeToolchain.php b/src/SPC/util/toolchain/ClangNativeToolchain.php new file mode 100644 index 00000000..863636ec --- /dev/null +++ b/src/SPC/util/toolchain/ClangNativeToolchain.php @@ -0,0 +1,33 @@ + LinuxSystemUtil::findCommand('clang++') ?? throw new WrongUsageException('Clang++ not found, please install it or manually set CC/CXX to a valid path.'), + 'Darwin' => MacOSSystemUtil::findCommand('clang++') ?? throw new WrongUsageException('Clang++ not found, please install it or set CC/CXX to a valid path.'), + 'BSD' => FreeBSDSystemUtil::findCommand('clang++') ?? throw new WrongUsageException('Clang++ not found, please install it or set CC/CXX to a valid path.'), + default => throw new WrongUsageException('Clang is not supported on ' . PHP_OS_FAMILY . '.'), + }; + } +} diff --git a/src/SPC/util/toolchain/GccNativeToolchain.php b/src/SPC/util/toolchain/GccNativeToolchain.php new file mode 100644 index 00000000..c845d11d --- /dev/null +++ b/src/SPC/util/toolchain/GccNativeToolchain.php @@ -0,0 +1,32 @@ + LinuxSystemUtil::findCommand('g++') ?? throw new \RuntimeException('g++ not found, please install it or set CC/CXX to a valid path.'), + 'Darwin' => MacOSSystemUtil::findCommand('g++') ?? throw new \RuntimeException('g++ not found, please install it or set CC/CXX to a valid path.'), + 'BSD' => FreeBSDSystemUtil::findCommand('g++') ?? throw new \RuntimeException('g++ not found, please install it or set CC/CXX to a valid path.'), + default => throw new \RuntimeException('GCC is not supported on ' . PHP_OS_FAMILY . '.'), + }; + } +} diff --git a/src/SPC/util/toolchain/MSVCToolchain.php b/src/SPC/util/toolchain/MSVCToolchain.php new file mode 100644 index 00000000..2ec8111c --- /dev/null +++ b/src/SPC/util/toolchain/MSVCToolchain.php @@ -0,0 +1,12 @@ +