diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index 6cc36bfa6..20a26389f 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -64,7 +64,7 @@ fi # Detect docker env is setup if ! $DOCKER_EXECUTABLE images | grep -q cwcc-spc-gnu-$SPC_USE_ARCH-$SPC_DOCKER_VERSION; then echo "Docker container does not exist. Building docker image ..." - $DOCKER_EXECUTABLE buildx build $PLATFORM_ARG --no-cache -t cwcc-spc-gnu-$SPC_USE_ARCH-$SPC_DOCKER_VERSION -f- . < /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=-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/config/env.ini b/config/env.ini index 60f4a6d4c..95a3d9466 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=native-windows ; php-sdk-binary-tools path PHP_SDK_PATH="${WORKING_DIR}\php-sdk-binary-tools" ; upx executable path @@ -63,6 +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] +; 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. + ; include PATH for musl libc. SPC_LIBC=musl ; compiler environments @@ -109,6 +122,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=native-macos ; compiler environments CC=clang CXX=clang++ diff --git a/docs/en/guide/extension-notes.md b/docs/en/guide/extension-notes.md index aa11c3772..5a022b590 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 10e62eb06..ad787dd95 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/phpunit.xml.dist b/phpunit.xml.dist index dc58130d9..2f0c2934b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,5 +4,6 @@ > + diff --git a/src/SPC/ConsoleApplication.php b/src/SPC/ConsoleApplication.php index b69dcf084..a24eada9a 100644 --- a/src/SPC/ConsoleApplication.php +++ b/src/SPC/ConsoleApplication.php @@ -9,6 +9,7 @@ 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 @@ public function __construct() new GenerateExtDepDocsCommand(), new GenerateLibDepDocsCommand(), new PackLibCommand(), + new EnvCommand(), ] ); } diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index 3f63c84b8..76fc76d62 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -10,6 +10,7 @@ use SPC\store\Config; use SPC\store\FileSystem; use SPC\util\SPCConfigUtil; +use SPC\util\SPCTarget; class Extension { @@ -532,6 +533,11 @@ protected function getStaticAndSharedLibs(): array $sharedLibString .= '-l' . $lib . ' '; } } + // move static libstdc++ to shared if we are on non-full-static build target + if (!SPCTarget::isStatic() && in_array(SPCTarget::getLibc(), SPCTarget::LIBC_LIST)) { + $staticLibString .= ' -lstdc++'; + $sharedLibString = str_replace('-lstdc++', '', $sharedLibString); + } return [trim($staticLibString), trim($sharedLibString)]; } diff --git a/src/SPC/builder/extension/imagick.php b/src/SPC/builder/extension/imagick.php index 7951ea69d..514e79b32 100644 --- a/src/SPC/builder/extension/imagick.php +++ b/src/SPC/builder/extension/imagick.php @@ -10,34 +10,9 @@ #[CustomExt('imagick')] class imagick extends Extension { - public function patchBeforeMake(): bool - { - if (PHP_OS_FAMILY !== 'Linux') { - return false; - } - if (getenv('SPC_LIBC') === 'glibc' && str_contains(getenv('CC'), 'devtoolset-10')) { - return false; - } - // imagick with calls omp_pause_all, which requires openmp, on non-musl we build imagick without openmp - $extra_libs = trim(getenv('SPC_EXTRA_LIBS') . ' -lgomp'); - f_putenv('SPC_EXTRA_LIBS=' . $extra_libs); - return true; - } - 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 = ' 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]; - } } diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 685486154..94d8a41ee 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -25,22 +25,8 @@ public function __construct(array $options = []) { $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 a85f3dec6..0f6d35781 100644 --- a/src/SPC/builder/linux/SystemUtil.php +++ b/src/SPC/builder/linux/SystemUtil.php @@ -188,12 +188,12 @@ public static function getSupportedDistros(): array /** * Get libc version string from ldd */ - public static function getLibcVersionIfExists(): ?string + public static function getLibcVersionIfExists(string $libc): ?string { if (self::$libc_version !== null) { return self::$libc_version; } - if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'glibc') { + if ($libc === 'glibc') { $result = shell()->execWithResult('ldd --version', false); if ($result[0] !== 0) { return null; @@ -208,7 +208,7 @@ public static function getLibcVersionIfExists(): ?string } return null; } - if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { + if ($libc === 'musl') { 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 aa2825b7d..c911a2fe4 100644 --- a/src/SPC/builder/linux/library/icu.php +++ b/src/SPC/builder/linux/library/icu.php @@ -5,6 +5,7 @@ namespace SPC\builder\linux\library; use SPC\store\FileSystem; +use SPC\util\SPCTarget; class icu extends LinuxLibraryBase { @@ -16,7 +17,7 @@ protected function build(): void { $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::isStatic() ? 'LDFLAGS="-static"' : ''; shell()->cd($this->source_dir . '/source')->initializeEnv($this) ->exec( "{$cppflags} {$cxxflags} {$ldflags} " . diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index 173ec954b..362a68fe7 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -29,6 +29,7 @@ public function __construct(array $options = []) // apply global environment variables GlobalEnvManager::init(); + GlobalEnvManager::afterInit(); // ---------- set necessary compile vars ---------- // concurrency diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 0d56b0eb3..6f0d5045e 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -17,6 +17,7 @@ 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 @@ protected function sanityCheck(int $build_target): void $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::isStatic()) { $lens .= ' -static'; } [$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens); @@ -334,7 +335,7 @@ protected function buildFrankenphp(): void $debugFlags = $this->getOption('no-strip') ? "'-w -s' " : ''; $extLdFlags = "-extldflags '-pie'"; $muslTags = ''; - if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl') { + if (SPCTarget::isStatic()) { $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 ead786a22..d7cfa8876 100644 --- a/src/SPC/builder/unix/library/imagemagick.php +++ b/src/SPC/builder/unix/library/imagemagick.php @@ -4,18 +4,19 @@ 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\exception\WrongUsageException; use SPC\store\FileSystem; use SPC\util\executor\UnixAutoconfExecutor; +use SPC\util\SPCTarget; trait imagemagick { /** * @throws RuntimeException * @throws FileSystemException + * @throws WrongUsageException */ protected function build(): void { @@ -32,17 +33,16 @@ protected function build(): void ->optionalLib('freetype', ...ac_with_args('freetype')) ->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', + '--disable-openmp', '--without-jxl', '--without-x', ); - // special: linux musl needs `-static` - $ldflags = ($this instanceof LinuxLibraryBase) && getenv('SPC_LIBC') !== 'glibc' ? ('-static -ldl') : '-ldl'; + // special: linux-static target needs `-static` + $ldflags = SPCTarget::isStatic() ? ('-static -ldl') : '-ldl'; // special: macOS needs -iconv - $libs = $this instanceof MacOSLibraryBase ? '-liconv' : ''; + $libs = SPCTarget::getTargetOS() === 'Darwin' ? '-liconv' : ''; $ac->appendEnv([ 'LDFLAGS' => $ldflags, diff --git a/src/SPC/builder/unix/library/ldap.php b/src/SPC/builder/unix/library/ldap.php index 62f7bcd75..18393e248 100644 --- a/src/SPC/builder/unix/library/ldap.php +++ b/src/SPC/builder/unix/library/ldap.php @@ -6,12 +6,13 @@ 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::getLibc() === '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 c820f8f69..aabc277d9 100644 --- a/src/SPC/builder/unix/library/libxslt.php +++ b/src/SPC/builder/unix/library/libxslt.php @@ -33,7 +33,13 @@ protected function build(): void '--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 788688565..6d89e6dfc 100644 --- a/src/SPC/builder/unix/library/mimalloc.php +++ b/src/SPC/builder/unix/library/mimalloc.php @@ -5,6 +5,7 @@ namespace SPC\builder\unix\library; use SPC\util\executor\UnixCMakeExecutor; +use SPC\util\SPCTarget; trait mimalloc { @@ -13,9 +14,9 @@ protected function build(): void $cmake = UnixCMakeExecutor::create($this) ->addConfigureArgs( '-DMI_BUILD_SHARED=OFF', - '-DMI_INSTALL_TOPLEVEL=ON' + '-DMI_INSTALL_TOPLEVEL=ON', ); - if (getenv('SPC_LIBC') === 'musl') { + if (SPCTarget::getLibc() === 'musl') { $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 05727f96a..1817ab0c9 100644 --- a/src/SPC/builder/unix/library/pkgconfig.php +++ b/src/SPC/builder/unix/library/pkgconfig.php @@ -4,8 +4,8 @@ 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 @@ protected function build(): void 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::isStatic() ? '--static' : '', ]) ->configure( '--with-internal-glib', diff --git a/src/SPC/builder/unix/library/postgresql.php b/src/SPC/builder/unix/library/postgresql.php index 1de3e4c28..c2cc7165e 100644 --- a/src/SPC/builder/unix/library/postgresql.php +++ b/src/SPC/builder/unix/library/postgresql.php @@ -8,6 +8,7 @@ use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; use SPC\store\FileSystem; +use SPC\util\SPCTarget; trait postgresql { @@ -50,7 +51,7 @@ protected function build(): void $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::isStatic() ? " 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/builder/windows/WindowsBuilder.php b/src/SPC/builder/windows/WindowsBuilder.php index c7450050a..25a503ebf 100644 --- a/src/SPC/builder/windows/WindowsBuilder.php +++ b/src/SPC/builder/windows/WindowsBuilder.php @@ -34,6 +34,7 @@ public function __construct(array $options = []) $this->options = $options; GlobalEnvManager::init(); + GlobalEnvManager::afterInit(); // ---------- set necessary options ---------- // set sdk (require visual studio 16 or 17) diff --git a/src/SPC/command/BaseCommand.php b/src/SPC/command/BaseCommand.php index 832ced971..9c07b074e 100644 --- a/src/SPC/command/BaseCommand.php +++ b/src/SPC/command/BaseCommand.php @@ -99,6 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // init GlobalEnv if (!$this instanceof BuildCommand) { GlobalEnvManager::init(); + f_putenv('SPC_SKIP_TOOLCHAIN_CHECK=yes'); } if ($this->shouldExecute()) { try { diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 21a848a5e..8bfa4aafe 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -13,6 +13,7 @@ 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,8 +64,8 @@ public function handle(): int // check dynamic extension build env // linux must build with glibc - if (!empty($shared_extensions) && PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') !== 'glibc') { - $this->output->writeln('Linux does not support dynamic extension loading with musl-libc full-static build, please build with glibc!'); + if (!empty($shared_extensions) && SPCTarget::isStatic()) { + $this->output->writeln('Linux does not support dynamic extension loading with musl-libc full-static build, please build with shared target!'); return static::FAILURE; } $static_and_shared = array_intersect($static_extensions, $shared_extensions); @@ -133,6 +134,8 @@ public function handle(): int // 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/CraftCommand.php b/src/SPC/command/CraftCommand.php index c8003a3e1..fbf3a5bed 100644 --- a/src/SPC/command/CraftCommand.php +++ b/src/SPC/command/CraftCommand.php @@ -10,7 +10,7 @@ use Symfony\Component\Process\Process; #[AsCommand('craft', 'Build static-php from craft.yml')] -class CraftCommand extends BaseCommand +class CraftCommand extends BuildCommand { public function configure(): void { diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index d5054aa95..1e895bdb6 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -4,7 +4,6 @@ namespace SPC\command; -use SPC\builder\linux\SystemUtil; use SPC\builder\traits\UnixSystemUtilTrait; use SPC\exception\DownloaderException; use SPC\exception\FileSystemException; @@ -14,6 +13,7 @@ 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,8 +223,8 @@ public function handle(): int '{name}' => $source, '{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}' => SPCTarget::getLibc() ?? 'default', + '{libcver}' => SPCTarget::getLibcVersion() ?? 'default', ]; $find = str_replace(array_keys($replace), array_values($replace), Config::getPreBuilt('match-pattern')); // find filename in asset list diff --git a/src/SPC/command/SPCConfigCommand.php b/src/SPC/command/SPCConfigCommand.php index d3c7a0b25..a1886ed1c 100644 --- a/src/SPC/command/SPCConfigCommand.php +++ b/src/SPC/command/SPCConfigCommand.php @@ -11,7 +11,7 @@ use Symfony\Component\Console\Input\InputOption; #[AsCommand('spc-config', 'Build dependencies')] -class SPCConfigCommand extends BuildCommand +class SPCConfigCommand extends BaseCommand { protected bool $no_motd = true; diff --git a/src/SPC/command/dev/EnvCommand.php b/src/SPC/command/dev/EnvCommand.php new file mode 100644 index 000000000..d5a0cec32 --- /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 d0d9797e5..e51cdb174 100644 --- a/src/SPC/command/dev/PackLibCommand.php +++ b/src/SPC/command/dev/PackLibCommand.php @@ -6,7 +6,6 @@ use SPC\builder\BuilderProvider; use SPC\builder\LibraryBase; -use SPC\builder\linux\SystemUtil; use SPC\command\BuildCommand; use SPC\exception\ExceptionHandler; use SPC\exception\FileSystemException; @@ -16,6 +15,7 @@ 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 +76,8 @@ public function handle(): int '{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}' => SPCTarget::getLibc() ?? 'default', + '{libcver}' => SPCTarget::getLibcVersion() ?? '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 23005dfda..cac17ce3b 100644 --- a/src/SPC/doctor/CheckListHandler.php +++ b/src/SPC/doctor/CheckListHandler.php @@ -72,6 +72,14 @@ private function loadCheckList(bool $include_manual = false): array { 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 000000000..4dae938b4 --- /dev/null +++ b/src/SPC/doctor/OptionalCheck.php @@ -0,0 +1,11 @@ + 'ld.gold', 'base-devel' => 'automake', 'gettext-devel' => 'gettextize', + 'gettext-dev' => 'gettextize', ]; /** @noinspection PhpUnused */ diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index ba0cd124d..f031d73a8 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -4,13 +4,13 @@ namespace SPC\store; -use SPC\builder\linux\SystemUtil; 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; +use SPC\util\SPCTarget; /** * Source Downloader. @@ -591,7 +591,12 @@ public static function curlDown(string $url, string $path, string $method = 'GET public static function getPreBuiltLockName(string $source): string { - return "{$source}-" . PHP_OS_FAMILY . '-' . getenv('GNU_ARCH') . '-' . (getenv('SPC_LIBC') ?: 'default') . '-' . (SystemUtil::getLibcVersionIfExists() ?? 'default'); + $os_family = PHP_OS_FAMILY; + $gnu_arch = getenv('GNU_ARCH') ?: 'unknown'; + $libc = SPCTarget::getLibc(); + $libc_version = SPCTarget::getLibcVersion() ?? 'default'; + + return "{$source}-{$os_family}-{$gnu_arch}-{$libc}-{$libc_version}"; } /** diff --git a/src/SPC/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php index 1e8be7927..54f805b60 100644 --- a/src/SPC/store/SourcePatcher.php +++ b/src/SPC/store/SourcePatcher.php @@ -11,6 +11,7 @@ use SPC\exception\FileSystemException; use SPC\exception\RuntimeException; use SPC\exception\WrongUsageException; +use SPC\util\SPCTarget; class SourcePatcher { @@ -459,7 +460,7 @@ public static function patchImagickWith84(): bool public static function patchFfiCentos7FixO3strncmp(): bool { - if (PHP_OS_FAMILY !== 'Linux' || SystemUtil::getLibcVersionIfExists() > '2.17') { + if (!($ver = SPCTarget::getLibcVersion()) || version_compare($ver, '2.17', '>')) { return false; } if (!file_exists(SOURCE_PATH . '/php-src/main/php_version.h')) { diff --git a/src/SPC/store/pkg/GoXcaddy.php b/src/SPC/store/pkg/GoXcaddy.php index e0c7c5b96..8dc1cf2d6 100644 --- a/src/SPC/store/pkg/GoXcaddy.php +++ b/src/SPC/store/pkg/GoXcaddy.php @@ -7,7 +7,6 @@ use SPC\store\Downloader; use SPC\store\FileSystem; use SPC\store\LockFile; -use SPC\util\GlobalEnvManager; class GoXcaddy extends CustomPackage { @@ -62,7 +61,6 @@ public function extract(string $name): void FileSystem::extractPackage($name, $source_type, $filename, $extract); - GlobalEnvManager::init(); // install xcaddy shell() ->appendEnv([ diff --git a/src/SPC/toolchain/ClangNativeToolchain.php b/src/SPC/toolchain/ClangNativeToolchain.php new file mode 100644 index 000000000..c5186521c --- /dev/null +++ b/src/SPC/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/toolchain/GccNativeToolchain.php b/src/SPC/toolchain/GccNativeToolchain.php new file mode 100644 index 000000000..bd8cc8688 --- /dev/null +++ b/src/SPC/toolchain/GccNativeToolchain.php @@ -0,0 +1,33 @@ + LinuxSystemUtil::findCommand('g++') ?? throw new WrongUsageException('g++ not found, please install it or set CC/CXX to a valid path.'), + 'Darwin' => MacOSSystemUtil::findCommand('g++') ?? throw new WrongUsageException('g++ not found, please install it or set CC/CXX to a valid path.'), + 'BSD' => FreeBSDSystemUtil::findCommand('g++') ?? throw new WrongUsageException('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/toolchain/MSVCToolchain.php b/src/SPC/toolchain/MSVCToolchain.php new file mode 100644 index 000000000..567cff38d --- /dev/null +++ b/src/SPC/toolchain/MSVCToolchain.php @@ -0,0 +1,12 @@ + MuslToolchain::class, // use musl toolchain by default, after zig pr merged, change this to ZigToolchain::class + 'Windows' => MSVCToolchain::class, + 'Darwin' => ClangNativeToolchain::class, + 'BSD' => ClangNativeToolchain::class, + ]; + + /** + * @throws WrongUsageException + */ + public static function initToolchain(): void + { + $libc = getenv('SPC_LIBC'); + if ($libc !== false) { + // uncomment this when zig pr is merged + // logger()->warning('SPC_LIBC is deprecated, please use SPC_TARGET instead.'); + $toolchain = match ($libc) { + 'musl' => SystemUtil::isMuslDist() ? GccNativeToolchain::class : MuslToolchain::class, + 'glibc' => !SystemUtil::isMuslDist() ? GccNativeToolchain::class : throw new WrongUsageException('SPC_TARGET must be musl-static or musl for musl dist.'), + default => throw new WrongUsageException('Unsupported SPC_LIBC value: ' . $libc), + }; + } else { + $toolchain = self::OS_DEFAULT_TOOLCHAIN[PHP_OS_FAMILY]; + } + $toolchainClass = $toolchain; + /* @var ToolchainInterface $toolchainClass */ + (new $toolchainClass())->initEnv(); + GlobalEnvManager::putenv("SPC_TOOLCHAIN={$toolchain}"); + } + + public static function afterInitToolchain(): void + { + if (!getenv('SPC_TOOLCHAIN')) { + throw new WrongUsageException('SPC_TOOLCHAIN not set'); + } + $toolchain = getenv('SPC_TOOLCHAIN'); + /* @var ToolchainInterface $toolchain */ + (new $toolchain())->afterInit(); + } +} diff --git a/src/SPC/toolchain/ZigToolchain.php b/src/SPC/toolchain/ZigToolchain.php new file mode 100644 index 000000000..3922fecbb --- /dev/null +++ b/src/SPC/toolchain/ZigToolchain.php @@ -0,0 +1,12 @@ +getLdflagsString(); $libs = $this->getLibsString($libraries, $with_dependencies); - if (PHP_OS_FAMILY === 'Darwin') { + if (SPCTarget::getTargetOS() === 'Darwin') { $libs .= " {$this->getFrameworksString($extensions)}"; } $cflags = $this->getIncludesString(); @@ -145,10 +145,6 @@ private function getLibsString(array $libraries, bool $withDependencies = false) } } } - // 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'))) { - $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 000000000..071f26204 --- /dev/null +++ b/src/SPC/util/SPCTarget.php @@ -0,0 +1,87 @@ + 'Linux', + str_contains($target, 'macos') => 'Darwin', + str_contains($target, 'windows') => 'Windows', + default => throw new WrongUsageException('Cannot parse target.'), + }; + } +} diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index 70758f855..86b53b8dc 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -21,15 +21,15 @@ // test os (macos-13, macos-14, macos-15, ubuntu-latest, windows-latest are available) $test_os = [ - // 'macos-13', - // 'macos-14', - // 'macos-15', - // 'ubuntu-latest', - // 'ubuntu-22.04', - // 'ubuntu-24.04', - // 'ubuntu-22.04-arm', - // 'ubuntu-24.04-arm', - 'windows-latest', + // 'macos-13', // bin/spc for x86_64 + // 'macos-14', // bin/spc for arm64 + 'macos-15', // bin/spc for arm64 + 'ubuntu-latest', // bin/spc-alpine-docker for x86_64 + 'ubuntu-22.04', // bin/spc-gnu-docker for x86_64 + 'ubuntu-24.04', // bin/spc for x86_64 + // 'ubuntu-22.04-arm', // bin/spc-gnu-docker for arm64 + // 'ubuntu-24.04-arm', // bin/spc for arm64 + // 'windows-latest', // .\bin\spc.ps1 ]; // whether enable thread safe @@ -72,7 +72,7 @@ // You can use `common`, `bulk`, `minimal` or `none`. // note: combination is only available for *nix platform. Windows must use `none` combination $base_combination = match (PHP_OS_FAMILY) { - 'Linux', 'Darwin' => 'minimal', + 'Linux', 'Darwin' => 'common', 'Windows' => 'none', };