This commit is contained in:
DubbleClick
2025-07-01 11:00:24 +07:00
54 changed files with 538 additions and 180 deletions

View File

@@ -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(),
]
);
}

View File

@@ -14,6 +14,7 @@ use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\LockFile;
use SPC\store\SourceManager;
use SPC\store\SourcePatcher;
use SPC\util\CustomExt;
abstract class BuilderBase
@@ -203,6 +204,8 @@ abstract class BuilderBase
$this->emitPatchPoint('before-exts-extract');
SourceManager::initSource(exts: [...$static_extensions, ...$shared_extensions]);
$this->emitPatchPoint('after-exts-extract');
// patch micro
SourcePatcher::patchMicro();
}
foreach ([...$static_extensions, ...$shared_extensions] as $extension) {

View File

@@ -11,6 +11,7 @@ use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\util\SPCConfigUtil;
use SPC\util\SPCTarget;
class Extension
{
@@ -545,6 +546,11 @@ class Extension
$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)];
}

View File

@@ -12,17 +12,7 @@ class imagick extends Extension
{
public function getUnixConfigureArg(bool $shared = false): string
{
return '--with-imagick=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . ' ac_cv_func_omp_pause_resource_all=no';
}
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 (str_contains(getenv('CC'), 'devtoolset-10')) {
$static .= ' -lstdc++';
$shared = str_replace('-lstdc++', '', $shared);
}
return [$static, $shared];
$disable_omp = ' ac_cv_func_omp_pause_resource_all=no';
return '--with-imagick=' . ($shared ? 'shared,' : '') . BUILD_ROOT_PATH . $disable_omp;
}
}

View File

@@ -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() && !str_contains((string) getenv('CC'), 'zig')) {
$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'));
@@ -104,10 +90,6 @@ class LinuxBuilder extends UnixBuilderBase
$zts = '';
}
$disable_jit = $this->getOption('disable-opcache-jit', false) ? '--disable-opcache-jit ' : '';
$cc = trim(getenv('CC'));
if (!$disable_jit && $this->getExt('opcache') && str_contains($cc, 'zig')) {
f_putenv("CC={$cc} -fno-sanitize=undefined");
}
$config_file_path = $this->getOption('with-config-file-path', false) ?
('--with-config-file-path=' . $this->getOption('with-config-file-path') . ' ') : '';
@@ -139,9 +121,6 @@ class LinuxBuilder extends UnixBuilderBase
}
$embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static';
if ($embed_type !== 'static' && getenv('SPC_LIBC') === 'musl' && getenv('SPC_LIBC_LINKAGE') === '-static') {
throw new WrongUsageException('Musl libc does not support dynamic linking of PHP embed!');
}
shell()->cd(SOURCE_PATH . '/php-src')
->exec(
getenv('SPC_CMD_PREFIX_PHP_CONFIGURE') . ' ' .
@@ -183,9 +162,6 @@ class LinuxBuilder extends UnixBuilderBase
}
$this->buildEmbed();
}
if (!$disable_jit && $this->getExt('opcache') && str_contains($cc, 'zig')) {
f_putenv("CC={$cc}");
}
if ($enableFrankenphp) {
logger()->info('building frankenphp');
$this->buildFrankenphp();

View File

@@ -192,12 +192,12 @@ class SystemUtil
/**
* 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;
@@ -212,7 +212,7 @@ class SystemUtil
}
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 {

View File

@@ -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::isStatic() ? 'LDFLAGS="-static"' : '';
shell()->cd($this->source_dir . '/source')->initializeEnv($this)
->exec(
"{$cppflags} {$cxxflags} {$ldflags} " .

View File

@@ -29,6 +29,7 @@ class MacOSBuilder extends UnixBuilderBase
// apply global environment variables
GlobalEnvManager::init();
GlobalEnvManager::afterInit();
// ---------- set necessary compile vars ----------
// concurrency

View File

@@ -19,6 +19,7 @@ use SPC\store\pkg\GoXcaddy;
use SPC\util\DependencyUtil;
use SPC\util\GlobalEnvManager;
use SPC\util\SPCConfigUtil;
use SPC\util\SPCTarget;
abstract class UnixBuilderBase extends BuilderBase
{
@@ -202,16 +203,8 @@ 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']}";
$lens .= ' ' . getenv('SPC_LIBC_LINKAGE');
// if someone changed to EMBED_TYPE=shared, we need to add LD_LIBRARY_PATH
if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') {
$ext_path = 'LD_LIBRARY_PATH=' . BUILD_LIB_PATH . ':$LD_LIBRARY_PATH ';
FileSystem::removeFileIfExists(BUILD_LIB_PATH . '/libphp.a');
} else {
$ext_path = '';
foreach (glob(BUILD_LIB_PATH . '/libphp*.so') as $file) {
unlink($file);
}
if (SPCTarget::isStatic()) {
$lens .= ' -static';
}
[$ret, $out] = shell()->cd($sample_file_path)->execWithResult(getenv('CC') . ' -o embed embed.c ' . $lens);
if ($ret !== 0) {
@@ -327,8 +320,7 @@ abstract class UnixBuilderBase extends BuilderBase
$debugFlags = $this->getOption('no-strip') ? "'-w -s' " : '';
$extLdFlags = "-extldflags '-pie'";
$muslTags = '';
$staticFlags = '';
if (PHP_OS_FAMILY === 'Linux' && getenv('SPC_LIBC') === 'musl' && getenv('SPC_LIBC_LINKAGE') === 'static') {
if (SPCTarget::isStatic()) {
$extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000'";
$muslTags = 'static_build,';
$staticFlags = '-static -static-pie';

View File

@@ -4,18 +4,19 @@ 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\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
{
@@ -37,11 +38,11 @@ trait imagemagick
'--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,

View File

@@ -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::getLibc() === 'glibc' ? '-ldl -lpthread -lm -lresolv -lutil' : '';
FileSystem::replaceFileStr($this->source_dir . '/configure', '"-lssl -lcrypto', '"-lssl -lcrypto -lz ' . $extra);
return true;
}

View File

@@ -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();

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace SPC\builder\unix\library;
use SPC\util\executor\UnixCMakeExecutor;
use SPC\util\SPCTarget;
trait mimalloc
{
@@ -13,9 +14,9 @@ trait mimalloc
$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();

View File

@@ -4,17 +4,17 @@ 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
{
protected function build(): void
{
UnixAutoconfExecutor::create($this)
->appendEnv([
->appendEnv([[
'CFLAGS' => '-Wimplicit-function-declaration -Wno-int-conversion',
'LDFLAGS' => !($this instanceof LinuxLibraryBase) || getenv('SPC_LIBC') === 'glibc' ? '' : '--static',
'LDFLAGS' => SPCTarget::isStatic() ? '--static' : '',
])
->configure(
'--with-internal-glib',

View File

@@ -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::isStatic() ? " LDFLAGS=\"{$ldflags} -static\" " : " LDFLAGS=\"{$ldflags}\" ";
}
$output = shell()->execWithResult("pkg-config --libs-only-l --static {$packages}");
$error_exec_cnt += $output[0] === 0 ? 0 : 1;

View File

@@ -34,6 +34,7 @@ class WindowsBuilder extends BuilderBase
$this->options = $options;
GlobalEnvManager::init();
GlobalEnvManager::afterInit();
// ---------- set necessary options ----------
// set sdk (require visual studio 16 or 17)

View File

@@ -99,6 +99,7 @@ abstract class BaseCommand extends Command
// init GlobalEnv
if (!$this instanceof BuildCommand) {
GlobalEnvManager::init();
f_putenv('SPC_SKIP_TOOLCHAIN_CHECK=yes');
}
if ($this->shouldExecute()) {
try {

View File

@@ -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,8 +64,8 @@ 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' && getenv('SPC_LIBC_LINKAGE') === '-static') {
$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 fully static builds, please build with a shared C runtime target!');
return static::FAILURE;
}
$static_and_shared = array_intersect($static_extensions, $shared_extensions);
@@ -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),

View File

@@ -10,7 +10,7 @@ use Symfony\Component\Console\Attribute\AsCommand;
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
{

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
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\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,8 +223,8 @@ class DownloadCommand extends BaseCommand
'{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

View File

@@ -11,7 +11,7 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
#[AsCommand('spc-config', 'Build dependencies')]
class SPCConfigCommand extends BuildCommand
class SPCConfigCommand extends BaseCommand
{
protected bool $no_motd = true;

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace SPC\command\dev;
use SPC\command\BaseCommand;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand('dev:env', 'Returns the internally defined environment variables')]
class EnvCommand extends BaseCommand
{
public function configure()
{
$this->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("<error>Environment variable '{$env}' is not set.</error>");
return static::FAILURE;
}
$this->output->writeln("<info>{$val}</info>");
return static::SUCCESS;
}
}

View File

@@ -6,7 +6,6 @@ namespace SPC\command\dev;
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\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 +76,8 @@ 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}' => SPCTarget::getLibc() ?? 'default',
'{libcver}' => SPCTarget::getLibcVersion() ?? 'default',
];
// detect suffix, for proper tar option
$tar_option = $this->getTarOptionFromSuffix(Config::getPreBuilt('match-pattern'));

View File

@@ -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)) {

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace SPC\doctor;
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
class OptionalCheck
{
public function __construct(public array $check) {}
}

View File

@@ -4,10 +4,10 @@ declare(strict_types=1);
namespace SPC\doctor\item;
use SPC\builder\linux\SystemUtil;
use SPC\doctor\AsCheckItem;
use SPC\doctor\AsFixItem;
use SPC\doctor\CheckResult;
use SPC\doctor\OptionalCheck;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
@@ -16,23 +16,20 @@ use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\PackageManager;
use SPC\store\SourcePatcher;
use SPC\toolchain\MuslToolchain;
#[OptionalCheck([self::class, 'optionalCheck'])]
class LinuxMuslCheck
{
public static function optionalCheck(): bool
{
return getenv('SPC_TOOLCHAIN') === MuslToolchain::class;
}
/** @noinspection PhpUnused */
#[AsCheckItem('if musl-wrapper is installed', limit_os: 'Linux', level: 800)]
public function checkMusl(): CheckResult
{
if (SystemUtil::isMuslDist()) {
return CheckResult::ok('musl-based distro, skipped');
}
if (getenv('SPC_LIBC') === 'glibc') {
return CheckResult::ok('Building with glibc, skipped');
}
if (str_contains(getenv('CC'), 'zig')) {
return CheckResult::ok('Building with zig, skipped');
}
$musl_wrapper_lib = sprintf('/lib/ld-musl-%s.so.1', php_uname('m'));
if (file_exists($musl_wrapper_lib) && file_exists('/usr/local/musl/lib/libc.a')) {
return CheckResult::ok();
@@ -43,16 +40,6 @@ class LinuxMuslCheck
#[AsCheckItem('if musl-cross-make is installed', limit_os: 'Linux', level: 799)]
public function checkMuslCrossMake(): CheckResult
{
if (SystemUtil::isMuslDist()) {
return CheckResult::ok('musl-based distro, skipped');
}
if (getenv('SPC_LIBC') === 'glibc') {
return CheckResult::ok('Building with glibc, skipped');
}
if (str_contains(getenv('CC'), 'zig')) {
return CheckResult::ok('Building with zig, skipped');
}
$arch = arch2gnu(php_uname('m'));
$cross_compile_lib = "/usr/local/musl/{$arch}-linux-musl/lib/libc.a";
$cross_compile_gcc = "/usr/local/musl/bin/{$arch}-linux-musl-gcc";

View File

@@ -51,6 +51,7 @@ class LinuxToolCheckList
'binutils-gold' => 'ld.gold',
'base-devel' => 'automake',
'gettext-devel' => 'gettextize',
'gettext-dev' => 'gettextize',
];
/** @noinspection PhpUnused */

View File

@@ -4,13 +4,13 @@ declare(strict_types=1);
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 @@ class Downloader
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}";
}
/**

View File

@@ -11,13 +11,13 @@ use SPC\builder\unix\UnixBuilderBase;
use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\util\SPCTarget;
class SourcePatcher
{
public static function init(): void
{
// FileSystem::addSourceExtractHook('swow', [__CLASS__, 'patchSwow']);
FileSystem::addSourceExtractHook('micro', [__CLASS__, 'patchMicro']);
FileSystem::addSourceExtractHook('openssl', [__CLASS__, 'patchOpenssl11Darwin']);
FileSystem::addSourceExtractHook('swoole', [__CLASS__, 'patchSwoole']);
FileSystem::addSourceExtractHook('php-src', [__CLASS__, 'patchPhpLibxml212']);
@@ -105,7 +105,7 @@ class SourcePatcher
* @throws RuntimeException
* @throws FileSystemException
*/
public static function patchMicro(string $name = '', string $target = '', ?array $items = null): bool
public static function patchMicro(?array $items = null): bool
{
if (!file_exists(SOURCE_PATH . '/php-src/sapi/micro/php_micro.c')) {
return false;
@@ -152,11 +152,7 @@ class SourcePatcher
foreach ($patches as $patch) {
logger()->info("Patching micro with {$patch}");
$patchesStr = str_replace('/', DIRECTORY_SEPARATOR, $patch);
f_passthru(
'cd ' . SOURCE_PATH . '/php-src && ' .
(PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . $patchesStr . ' | patch -p1 '
);
self::patchFile(SOURCE_PATH . "/php-src/{$patch}", SOURCE_PATH . '/php-src');
}
return true;
@@ -165,24 +161,32 @@ class SourcePatcher
/**
* Use existing patch file for patching
*
* @param string $patch_name Patch file name in src/globals/patch/
* @param string $patch_name Patch file name in src/globals/patch/ or absolute path
* @param string $cwd Working directory for patch command
* @param bool $reverse Reverse patches (default: False)
* @throws RuntimeException
*/
public static function patchFile(string $patch_name, string $cwd, bool $reverse = false): bool
{
if (!file_exists(ROOT_DIR . "/src/globals/patch/{$patch_name}")) {
if (FileSystem::isRelativePath($patch_name)) {
$patch_file = ROOT_DIR . "/src/globals/patch/{$patch_name}";
} else {
$patch_file = $patch_name;
}
if (!file_exists($patch_file)) {
return false;
}
$patch_file = ROOT_DIR . "/src/globals/patch/{$patch_name}";
$patch_str = str_replace('/', DIRECTORY_SEPARATOR, $patch_file);
$patch_str = FileSystem::convertPath($patch_file);
if (!file_exists($patch_str)) {
throw new RuntimeException("Patch file [{$patch_str}] does not exist");
}
// Copy patch from phar
if (\Phar::running() !== '') {
file_put_contents(SOURCE_PATH . '/' . $patch_name, file_get_contents($patch_file));
$patch_str = str_replace('/', DIRECTORY_SEPARATOR, SOURCE_PATH . '/' . $patch_name);
if (str_starts_with($patch_str, 'phar://')) {
$filename = pathinfo($patch_file, PATHINFO_BASENAME);
file_put_contents(SOURCE_PATH . "/{$filename}", file_get_contents($patch_file));
$patch_str = FileSystem::convertPath(SOURCE_PATH . "/{$filename}");
}
// detect
@@ -458,7 +462,7 @@ class SourcePatcher
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')) {

View File

@@ -7,7 +7,6 @@ namespace SPC\store\pkg;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\LockFile;
use SPC\util\GlobalEnvManager;
class GoXcaddy extends CustomPackage
{
@@ -65,7 +64,6 @@ class GoXcaddy extends CustomPackage
FileSystem::extractPackage($name, $source_type, $filename, $extract);
GlobalEnvManager::init();
// install xcaddy
shell()
->appendEnv([

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
use SPC\builder\freebsd\SystemUtil as FreeBSDSystemUtil;
use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
use SPC\builder\macos\SystemUtil as MacOSSystemUtil;
use SPC\exception\WrongUsageException;
use SPC\util\GlobalEnvManager;
class ClangNativeToolchain implements ToolchainInterface
{
public function initEnv(): void
{
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=clang');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=clang++');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=ar');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld');
}
public function afterInit(): void
{
// check clang exists
match (PHP_OS_FAMILY) {
'Linux' => 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 . '.'),
};
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
use SPC\builder\freebsd\SystemUtil as FreeBSDSystemUtil;
use SPC\builder\linux\SystemUtil as LinuxSystemUtil;
use SPC\builder\macos\SystemUtil as MacOSSystemUtil;
use SPC\exception\WrongUsageException;
use SPC\util\GlobalEnvManager;
class GccNativeToolchain implements ToolchainInterface
{
public function initEnv(): void
{
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CC=gcc');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_CXX=g++');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_AR=ar');
GlobalEnvManager::putenv('SPC_LINUX_DEFAULT_LD=ld.gold');
}
public function afterInit(): void
{
// check gcc exists
match (PHP_OS_FAMILY) {
'Linux' => 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 . '.'),
};
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
class MSVCToolchain implements ToolchainInterface
{
public function initEnv(): void {}
public function afterInit(): void {}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
use SPC\exception\WrongUsageException;
use SPC\util\GlobalEnvManager;
class MuslToolchain implements ToolchainInterface
{
public function initEnv(): void
{
$arch = getenv('GNU_ARCH');
// Set environment variables for musl toolchain
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_CC={$arch}-linux-musl-gcc");
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_CXX={$arch}-linux-musl-g++");
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_AR={$arch}-linux-musl-ar");
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_LD={$arch}-linux-musl-ld");
GlobalEnvManager::addPathIfNotExists('/usr/local/musl/bin');
GlobalEnvManager::addPathIfNotExists("/usr/local/musl/{$arch}-linux-musl/bin");
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_LD_LIBRARY_PATH=/usr/local/musl/lib:/usr/local/musl/{$arch}-linux-musl/lib");
GlobalEnvManager::putenv("SPC_LINUX_DEFAULT_LIBRARY_PATH=/usr/local/musl/lib:/usr/local/musl/{$arch}-linux-musl/lib");
}
public function afterInit(): void
{
$arch = getenv('GNU_ARCH');
// append LD_LIBRARY_PATH to $configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE');
$configure = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE');
$ld_library_path = getenv('SPC_LINUX_DEFAULT_LD_LIBRARY_PATH');
GlobalEnvManager::putenv("SPC_CMD_PREFIX_PHP_CONFIGURE=LD_LIBRARY_PATH=\"{$ld_library_path}\" {$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)');
}
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
interface ToolchainInterface
{
/**
* Initialize the environment for the given target.
*/
public function initEnv(): void;
/**
* Perform actions after the environment has been initialized for the given target.
*/
public function afterInit(): void;
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
use SPC\builder\linux\SystemUtil;
use SPC\exception\WrongUsageException;
use SPC\util\GlobalEnvManager;
class ToolchainManager
{
public const array OS_DEFAULT_TOOLCHAIN = [
'Linux' => 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();
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace SPC\toolchain;
class ZigToolchain implements ToolchainInterface
{
public function initEnv(): void {}
public function afterInit(): void {}
}

View File

@@ -4,10 +4,10 @@ declare(strict_types=1);
namespace SPC\util;
use SPC\builder\linux\SystemUtil;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\pkg\Zig;
use SPC\toolchain\ToolchainManager;
/**
* Environment variable manager
@@ -41,30 +41,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 (str_contains((string) getenv('CC'), 'zig')) {
self::putenv('SPC_LINUX_DEFAULT_CC=zig-cc');
self::putenv('SPC_LINUX_DEFAULT_CXX=zig-c++');
self::putenv('SPC_LINUX_DEFAULT_AR=ar');
self::putenv('SPC_LINUX_DEFAULT_LD=ld.lld');
} elseif (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 = [];
@@ -87,6 +63,9 @@ class GlobalEnvManager
self::putenv("{$k}={$v}");
}
}
ToolchainManager::initToolchain();
// apply second time
$ini2 = self::readIniFile();
@@ -107,17 +86,6 @@ class GlobalEnvManager
self::putenv("{$k}={$v}");
}
}
if (getenv('SPC_LIBC_LINKAGE') === '-static' && getenv('SPC_LIBC') === 'glibc') {
self::putenv('SPC_LIBC_LINKAGE=');
}
if (str_contains((string) getenv('CC'), 'zig') || str_contains((string) getenv('CXX'), 'zig')) {
$zigEnv = Zig::getEnvironment();
foreach ($zigEnv as $key => $value) {
if ($key === 'PATH') {
self::addPathIfNotExists($value);
}
}
}
}
public static function putenv(string $val): void
@@ -133,6 +101,19 @@ class GlobalEnvManager
}
}
/**
* Initialize the toolchain after the environment variables are set.
* The toolchain or environment availability check is done here.
*
* @throws WrongUsageException
*/
public static function afterInit(): void
{
if (!filter_var(getenv('SPC_SKIP_TOOLCHAIN_CHECK'), FILTER_VALIDATE_BOOL)) {
ToolchainManager::afterInitToolchain();
}
}
/**
* @throws WrongUsageException
*/

View File

@@ -56,7 +56,7 @@ class SPCConfigUtil
ob_get_clean();
$ldflags = $this->getLdflagsString();
$libs = $this->getLibsString($libraries, $with_dependencies);
if (PHP_OS_FAMILY === 'Darwin') {
if (SPCTarget::getTargetOS() === 'Darwin') {
$libs .= " {$this->getFrameworksString($extensions)}";
}
$cflags = $this->getIncludesString();

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace SPC\util;
use SPC\builder\linux\SystemUtil;
use SPC\exception\WrongUsageException;
/**
* SPC build target constants and toolchain initialization.
* format: {target_name}[-{libc_subtype}]
*/
class SPCTarget
{
public const array LIBC_LIST = [
'musl',
'glibc',
];
/**
* Returns whether the target is a full-static target.
*/
public static function isStatic(): bool
{
$env = getenv('SPC_TARGET');
$libc = getenv('SPC_LIBC');
// if SPC_LIBC is set, it means the target is static, remove it when 3.0 is released
if ($libc === 'musl') {
return true;
}
// TODO: add zig target parser here
return false;
}
/**
* Returns the libc type if set, for other OS, it will always return null.
*/
public static function getLibc(): ?string
{
$env = getenv('SPC_TARGET');
$libc = getenv('SPC_LIBC');
if ($libc !== false) {
return $libc;
}
// TODO: zig target parser
return null;
}
/**
* Returns the libc version if set, for other OS, it will always return null.
*/
public static function getLibcVersion(): ?string
{
$env = getenv('SPC_TARGET');
$libc = getenv('SPC_LIBC');
if ($libc !== false) {
// legacy method: get a version from system
return SystemUtil::getLibcVersionIfExists($libc);
}
// TODO: zig target parser
return null;
}
/**
* Returns the target OS family, e.g. Linux, Darwin, Windows, BSD.
* Currently, we only support native building.
*
* @return 'BSD'|'Darwin'|'Linux'|'Windows'
* @throws WrongUsageException
*/
public static function getTargetOS(): string
{
$target = getenv('SPC_TARGET');
if ($target === false) {
return PHP_OS_FAMILY;
}
// TODO: zig target parser like below?
return match (true) {
str_contains($target, 'linux') => 'Linux',
str_contains($target, 'macos') => 'Darwin',
str_contains($target, 'windows') => 'Windows',
default => throw new WrongUsageException('Cannot parse target.'),
};
}
}

View File

@@ -21,15 +21,15 @@ $test_php_version = [
// 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-22.04-arm',
'ubuntu-24.04',
'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
];
$zig = true;
@@ -84,7 +84,7 @@ $with_libs = match (PHP_OS_FAMILY) {
// 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',
};