Merge branch 'v3' into fable-v3-windows

This commit is contained in:
Marc
2026-07-08 11:45:02 +07:00
committed by GitHub
136 changed files with 4051 additions and 1453 deletions

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Package\Artifact;
use StaticPHP\Artifact\ArtifactDownloader;
use StaticPHP\Artifact\Downloader\DownloadResult;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract;
use StaticPHP\Attribute\Artifact\BinaryExtract;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\GlobalEnvManager;
class msys2_build_essentials
{
// MSYS subsystem packages required for autotools-based builds.
private const REQUIRED_PACKAGES = ['make', 'autoconf', 'automake', 'libtool', 'pkgconf', 'perl', 'bison', 're2c'];
#[CustomBinary('msys2-build-essentials', ['windows-x86_64'])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
{
// MSYS2 nightly self-extracting archive; running it with `-y -oTARGET` extracts to TARGET\msys64\.
$url = 'https://github.com/msys2/msys2-installer/releases/download/nightly-x86_64/msys2-base-x86_64-latest.sfx.exe';
$filename = 'msys2-base-x86_64-latest.sfx.exe';
$path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename;
default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry());
return DownloadResult::file(
$filename,
['url' => $url, 'version' => 'nightly'],
version: 'nightly',
extract: '{pkg_root_path}/msys2-build-essentials',
);
}
#[BinaryExtract('msys2-build-essentials', ['windows-x86_64'])]
public function extractBinary(string $source_file, string $target_path): void
{
$target_path = FileSystem::convertPath($target_path);
$source_file = FileSystem::convertPath($source_file);
// Guard: skip re-extraction if already initialized (marker written at end of this method).
$marker = "{$target_path}\\.spc-msys2-initialized";
if (file_exists($marker)) {
return;
}
if (!is_dir($target_path)) {
FileSystem::createDir($target_path);
}
cmd()->exec("\"{$source_file}\" -y -o\"{$target_path}\"");
$msys2_bin = "{$target_path}\\msys64\\usr\\bin";
if (!file_exists("{$msys2_bin}\\bash.exe")) {
throw new DownloaderException("MSYS2 extraction failed: bash.exe not found at {$msys2_bin}\\bash.exe");
}
// Add MSYS2 usr\bin to PATH so pacman.exe can load msys-2.0.dll.
GlobalEnvManager::addPathIfNotExists($msys2_bin);
GlobalEnvManager::putenv('CHERE_INVOKING=yes');
GlobalEnvManager::putenv('MSYSTEM=MSYS');
// Disable PGP signature checking: pacman-key --init requires a pseudo-TTY which is unavailable
// from PHP. Patching pacman.conf is the standard approach for CI pipelines.
$pacman_conf = "{$target_path}\\msys64\\etc\\pacman.conf";
FileSystem::replaceFileRegex($pacman_conf, '/^SigLevel\s*=.*$/m', 'SigLevel = Never');
$pacman = "{$target_path}\\msys64\\usr\\bin\\pacman.exe";
// Two-pass update as recommended by MSYS2 CI docs.
cmd()->exec("\"{$pacman}\" --noconfirm -Syuu");
cmd()->exec("\"{$pacman}\" --noconfirm -Syuu");
$pkgs = implode(' ', self::REQUIRED_PACKAGES);
cmd()->exec("\"{$pacman}\" --noconfirm -S --needed {$pkgs}");
FileSystem::writeFile($marker, date('Y-m-d H:i:s'));
}
#[AfterBinaryExtract('msys2-build-essentials', ['windows-x86_64'])]
public function afterExtract(string $target_path): void
{
$target_path = FileSystem::convertPath($target_path);
$msys2_root = "{$target_path}\\msys64";
GlobalEnvManager::putenv("SPC_MSYS2_PATH={$msys2_root}");
GlobalEnvManager::addPathIfNotExists("{$msys2_root}\\usr\\bin");
}
}

View File

@@ -20,7 +20,7 @@ class curl
// which requires secur32.lib for SSL/TLS functions (SslEncryptPackage, etc.).
$extra_libs = getenv('SPC_EXTRA_LIBS') ?: '';
if (!str_contains($extra_libs, 'secur32.lib')) {
putenv('SPC_EXTRA_LIBS=' . trim($extra_libs . ' secur32.lib'));
putenv('SPC_EXTRA_LIBS=' . trim("{$extra_libs} secur32.lib"));
}
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Package\Extension;
use Package\Target\php;
use StaticPHP\Attribute\Package\BeforeStage;
use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Util\FileSystem;
#[Extension('gmssl')]
class gmssl extends PhpExtensionPackage
{
#[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-gmssl')]
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
#[PatchDescription('Fix ext-gmssl v1.1.1 compatibility with GmSSL >= 3.1.0 where SM2_VERIFY_CTX was removed (unified into SM2_SIGN_CTX)')]
public function patchSm2VerifyCtx(): void
{
// See: https://github.com/crazywhalecc/static-php-cli/issues/1182
FileSystem::replaceFileStr(
"{$this->getSourceDir()}/gmssl.c",
'SM2_VERIFY_CTX',
'SM2_SIGN_CTX'
);
}
#[BeforeStage('php', [php::class, 'buildconfForUnix'], 'ext-gmssl')]
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
#[PatchDescription('Fix ext-gmssl v1.1.1: pbkdf2_hmac_sm3_genkey was renamed to sm3_pbkdf2 in GmSSL >= 3.2.0')]
public function patchPbkdf2Rename(): void
{
FileSystem::replaceFileStr(
"{$this->getSourceDir()}/gmssl.c",
'pbkdf2_hmac_sm3_genkey',
'sm3_pbkdf2'
);
}
#[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-gmssl')]
#[PatchDescription('Add CHECK_LIB to config.w32 for static Windows builds')]
public function patchBeforeBuildconfWin(): bool
{
$configW32 = "{$this->getSourceDir()}/config.w32";
if (str_contains(FileSystem::readFile($configW32), 'CHECK_LIB(')) {
return false;
}
FileSystem::replaceFileStr(
$configW32,
'AC_DEFINE(',
'CHECK_LIB("gmssl.lib", "gmssl", PHP_GMSSL);' . PHP_EOL . 'AC_DEFINE('
);
return true;
}
}

View File

@@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
namespace Package\Extension;
use Package\Target\php;
use StaticPHP\Attribute\Package\BeforeStage;
use StaticPHP\Attribute\Package\Extension;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Util\FileSystem;
#[Extension('pdo_sqlite')]
class pdo_sqlite
{
#[BeforeStage('php', [php::class, 'configureForUnix'], 'ext-pdo_sqlite')]
public function patchBeforeConfigure(PackageInstaller $installer): void
{
FileSystem::replaceFileRegex(
"{$installer->getTargetPackage('php')->getSourceDir()}/configure",
'/sqlite3_column_table_name=yes/',
'sqlite3_column_table_name=no'
);
}
}

View File

@@ -50,8 +50,9 @@ class simdjson extends PhpExtensionPackage
if (!str_contains((string) $extra, '-lstdc++')) {
f_putenv('SPC_COMPILER_EXTRA=' . clean_spaces($extra . ' -lstdc++'));
}
$env['CFLAGS'] .= ' -Xclang -target-feature -Xclang +evex512';
$env['CXXFLAGS'] .= ' -Xclang -target-feature -Xclang +evex512';
$env['CFLAGS'] .= ' -fno-sanitize=undefined -Xclang -target-feature -Xclang +evex512';
$env['CXXFLAGS'] .= ' -fno-sanitize=undefined -Xclang -target-feature -Xclang +evex512';
}
return $env;
}
@@ -63,8 +64,8 @@ class simdjson extends PhpExtensionPackage
return;
}
$extra_cflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') ?: '';
GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . trim($extra_cflags . ' -Xclang -target-feature -Xclang +evex512'));
GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS=' . trim($extra_cflags . ' -fno-sanitize=undefined -Xclang -target-feature -Xclang +evex512'));
$extra_cxxflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS') ?: '';
GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS=' . trim($extra_cxxflags . ' -Xclang -target-feature -Xclang +evex512'));
GlobalEnvManager::putenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CXXFLAGS=' . trim($extra_cxxflags . ' -fno-sanitize=undefined -Xclang -target-feature -Xclang +evex512'));
}
}

View File

@@ -30,6 +30,7 @@ class swoole extends PhpExtensionPackage
if ($installer->getPhpExtensionPackage('swoole-hook-odbc') && $installer->getPhpExtensionPackage('pdo_odbc')?->isBuildStatic()) {
throw new WrongUsageException('swoole-hook-odbc provides pdo_odbc, if you enable odbc hook for swoole, you must remove pdo_odbc extension.');
}
// swoole-hook-pgsql conflicts with pdo_pgsql
if ($installer->getPhpExtensionPackage('swoole-hook-pgsql') && $installer->getPhpExtensionPackage('pdo_pgsql')?->isBuildStatic()) {
throw new WrongUsageException('swoole-hook-pgsql provides pdo_pgsql, if you enable pgsql hook for swoole, you must remove pdo_pgsql extension.');

View File

@@ -32,12 +32,15 @@ class xlswriter extends PhpExtensionPackage
#[PatchDescription('Fix Windows build: apply win32 patch and add UTF-8 BOM to theme.c')]
public function patchBeforeMakeForWindows(): void
{
$source_dir = $this->getSourceDir();
$theme_file = "{$source_dir}/library/libxlsx/src/theme.c";
// fix windows build with openssl extension duplicate symbol bug
SourcePatcher::patchFile('spc_fix_xlswriter_win32.patch', $this->getSourceDir());
$content = file_get_contents($this->getSourceDir() . '/library/libxlsxwriter/src/theme.c');
SourcePatcher::patchFile('spc_fix_xlswriter_win32.patch', $source_dir);
$content = file_get_contents($theme_file);
$bom = pack('CCC', 0xEF, 0xBB, 0xBF);
if (!str_starts_with($content, $bom)) {
file_put_contents($this->getSourceDir() . '/library/libxlsxwriter/src/theme.c', $bom . $content);
file_put_contents($theme_file, $bom . $content);
}
}
}

View File

@@ -18,7 +18,9 @@ class gmssl
#[BuildFor('Darwin')]
public function build(LibraryPackage $lib): void
{
UnixCMakeExecutor::create($lib)->build();
UnixCMakeExecutor::create($lib)
->addConfigureArgs('-DENABLE_SM2_PRIVATE_KEY_EXPORT=ON')
->build();
}
#[BuildFor('Windows')]
@@ -33,6 +35,7 @@ class gmssl
'-G "NMake Makefiles"',
'-DWIN32=ON',
'-DBUILD_SHARED_LIBS=OFF',
'-DENABLE_SM2_PRIVATE_KEY_EXPORT=ON',
'-DCMAKE_BUILD_TYPE=Release',
'-DCMAKE_C_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"',
'-DCMAKE_CXX_FLAGS_RELEASE="/MT /O2 /Ob2 /DNDEBUG"',
@@ -42,13 +45,13 @@ class gmssl
->toStep(1)
->build();
// fix cmake_install.cmake install prefix (GmSSL overrides it internally)
$installCmake = "{$buildDir}\\cmake_install.cmake";
FileSystem::writeFile(
$installCmake,
'set(CMAKE_INSTALL_PREFIX "' . str_replace('\\', '/', $lib->getBuildRootPath()) . '")' . PHP_EOL . FileSystem::readFile($installCmake)
);
cmd()->cd($buildDir)->exec('nmake gmssl XCFLAGS=/MT');
cmd()->cd($buildDir)->exec('nmake install XCFLAGS=/MT');
$libPath = "{$lib->getBuildRootPath()}/lib";
$incPath = "{$lib->getBuildRootPath()}/include/gmssl";
FileSystem::createDir($libPath);
FileSystem::createDir($incPath);
FileSystem::copy("{$buildDir}\\bin\\gmssl.lib", "{$libPath}/gmssl.lib");
FileSystem::copyDir("{$lib->getSourceDir()}\\include\\gmssl", $incPath);
}
}

View File

@@ -20,6 +20,7 @@ class libde265 extends LibraryPackage
->addConfigureArgs(
'-DENABLE_SDL=OFF',
'-DENABLE_DECODER=OFF',
'-DENABLE_SIMD=OFF',
'-DHAVE_NEON=OFF',
)
->build();

View File

@@ -18,6 +18,7 @@ class libssh2
{
WindowsCMakeExecutor::create($lib)
->addConfigureArgs(
'-DCRYPTO_BACKEND=WinCNG',
'-DENABLE_ZLIB_COMPRESSION=ON',
'-DBUILD_TESTING=OFF'
)

View File

@@ -24,7 +24,7 @@ class openssl
{
if (SystemTarget::getTargetOS() === 'Windows') {
global $argv;
$perl_path_native = PKG_ROOT_PATH . '\strawberry-perl-' . arch2gnu(php_uname('m')) . '-win\perl\bin\perl.exe';
$perl_path_native = PKG_ROOT_PATH . '\strawberry-perl\perl\bin\perl.exe';
$perl = file_exists($perl_path_native) ? ($perl_path_native) : WindowsUtil::findCommand('perl.exe');
if ($perl === null) {
throw new EnvironmentException(

View File

@@ -19,7 +19,12 @@ class sqlite
#[BuildFor('Linux')]
public function buildUnix(LibraryPackage $lib): void
{
UnixAutoconfExecutor::create($lib)->configure()->make();
UnixAutoconfExecutor::create($lib)
->appendEnv([
'CFLAGS' => '-DSQLITE_ENABLE_COLUMN_METADATA=1',
])
->configure()
->make();
$lib->patchPkgconfPrefix(['sqlite3.pc']);
}

View File

@@ -36,13 +36,7 @@ class curl
public function buildWin(LibraryPackage $lib): void
{
$lib_dir = str_replace('\\', '/', $lib->getLibDir());
// Pass zstd's import library by absolute path. A bare name ("zstd_static.lib") lands on the
// link line unresolved and MSVC looks for it relative to the curl build dir (LNK1181).
$zstd_lib = "{$lib_dir}/zstd_static.lib";
// libssh2 uses the OpenSSL crypto backend, but this curl build links Schannel and never
// find_package(OpenSSL), so libcrypto/libssl are absent from the link line and libssh2's
// EVP_*/RAND/PEM/ERR symbols go unresolved. Append them (plus the Win32 libs OpenSSL needs,
// mirroring openssl.php) to every target. MSVC's linker resolves regardless of order.
$extra_libs = "{$lib_dir}/libcrypto.lib {$lib_dir}/libssl.lib ws2_32.lib gdi32.lib advapi32.lib crypt32.lib user32.lib";
WindowsCMakeExecutor::create($lib)
->optionalPackage('zstd', ...cmake_boolean_args('CURL_ZSTD'))

View File

@@ -255,6 +255,13 @@ class php extends TargetPackage
$installer->addBuildPackage('php-embed');
}
// UPX compression: ensure the upx tool package is installed when requested.
// upx is a ToolPackage now, so it's installed directly rather than added as a
// library dependency (tool packages bypass the DependencyResolver graph).
if ($package->getBuildOption('with-upx-pack')) {
$installer->addInstallPackage('upx');
}
return [...$extensions_pkg, ...$additional_packages];
}

View File

@@ -40,6 +40,13 @@ trait windows
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf.bat'));
cmd()->cd($package->getSourceDir())->exec('.\buildconf.bat');
// Bypass the phpsdk_version check in configure.js: we use MSVC + msys2 instead of PHP SDK, so phpsdk_version is not available and the check would always fail.
FileSystem::replaceFileStr(
"{$package->getSourceDir()}\\configure.js",
'check_binary_tools_sdk();',
'/* check_binary_tools_sdk(); skipped: using MSVC + msys2 without PHP SDK */'
);
if ($package->getBuildOption('enable-micro-win32') && $installer->isPackageResolved('php-micro')) {
SourcePatcher::patchMicroWin32();
} else {
@@ -89,6 +96,17 @@ trait windows
cmd()->cd($package->getSourceDir())->exec(".\\configure.bat {$args} {$static_extension_str}");
}
#[BeforeStage('php', [self::class, 'makeCliForWindows'])]
#[PatchDescription('Patch Makefile to ensure buildroot/include comes before extension CFLAGS (fixes zip.h conflict with minizip)')]
public function patchMakefileIncludeOrder(TargetPackage $package): void
{
FileSystem::replaceFileStr(
"{$package->getSourceDir()}\\Makefile",
'$(CFLAGS_PHP_OBJ) $(CFLAGS)',
'$(CFLAGS) $(CFLAGS_PHP_OBJ)'
);
}
#[BeforeStage('php', [self::class, 'makeCliForWindows'])]
#[PatchDescription('Patch Windows Makefile for CLI target')]
public function patchCLITarget(TargetPackage $package): void

View File

@@ -2,17 +2,17 @@
declare(strict_types=1);
namespace Package\Target;
namespace Package\Tool;
use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\InitPackage;
use StaticPHP\Attribute\Package\Target;
use StaticPHP\Attribute\Package\Tool;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
use StaticPHP\Toolchain\Interface\ToolchainInterface;
#[Target('pkg-config')]
#[Tool('pkg-config')]
class pkgconfig
{
#[InitPackage]
@@ -23,7 +23,7 @@ class pkgconfig
#[BuildFor('Linux')]
#[BuildFor('Darwin')]
public function build(TargetPackage $package, ToolchainInterface $toolchain): void
public function build(ToolPackage $package, ToolchainInterface $toolchain): void
{
UnixAutoconfExecutor::create($package)
->appendEnv([

View File

@@ -644,7 +644,7 @@ class Artifact
'{artifact_name}' => $this->name,
'{pkg_root_path}' => PKG_ROOT_PATH,
'{build_root_path}' => BUILD_ROOT_PATH,
'{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: WORKING_DIR . '/php-sdk-binary-tools',
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH'),
'{working_dir}' => WORKING_DIR,
'{download_path}' => DOWNLOAD_PATH,
'{source_path}' => SOURCE_PATH,

View File

@@ -26,6 +26,7 @@ use StaticPHP\Exception\DownloaderException;
use StaticPHP\Exception\ExecutionException;
use StaticPHP\Exception\ValidationException;
use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Package\ToolVersionRegistry;
use StaticPHP\Registry\ArtifactLoader;
use StaticPHP\Runtime\Shell\Shell;
use StaticPHP\Runtime\SystemTarget;
@@ -346,12 +347,32 @@ class ArtifactDownloader
}
}
public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false): CheckUpdateResult
public function checkUpdate(string $artifact_name, bool $prefer_source = false, bool $bare = false, bool $use_installed = false): CheckUpdateResult
{
$artifact = ArtifactLoader::getArtifactInstance($artifact_name);
if ($artifact === null) {
throw new WrongUsageException("Artifact '{$artifact_name}' not found, please check the name.");
}
// --installed: prefer the version actually installed on disk (tool packages only) as the
// baseline, instead of the download cache. Falls back to the normal cache/bare logic below
// if this artifact has no recorded installed version (not a tool package, or not installed
// via the package installer yet).
if ($use_installed) {
$installed_version = ToolVersionRegistry::get($artifact_name);
if ($installed_version !== null) {
[$first, $second] = $prefer_source
? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name, $installed_version), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name, $installed_version)]
: [fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name, $installed_version), fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name, $installed_version)];
$result = $first() ?? $second();
if ($result !== null) {
return $result;
}
return new CheckUpdateResult(old: $installed_version, new: null, needUpdate: false, unsupported: true);
}
logger()->warning("Artifact '{$artifact_name}' has no recorded installed version (not a tool package, or not installed yet); falling back to download-cache based check.");
}
if ($bare) {
[$first, $second] = $prefer_source
? [fn () => $this->probeSourceCheckUpdate($artifact, $artifact_name), fn () => $this->probeBinaryCheckUpdate($artifact, $artifact_name)]
@@ -403,16 +424,17 @@ class ArtifactDownloader
* @param bool $prefer_source Whether to prefer source over binary
* @param bool $bare Check without requiring artifact to be downloaded first
* @param null|callable $onResult Called immediately with (string $name, CheckUpdateResult) as each result arrives
* @param bool $use_installed Prefer the installed (on-disk) version over the download cache (tool packages only)
* @return array<string, CheckUpdateResult> Results keyed by artifact name
*/
public function checkUpdates(array $artifact_names, bool $prefer_source = false, bool $bare = false, ?callable $onResult = null): array
public function checkUpdates(array $artifact_names, bool $prefer_source = false, bool $bare = false, ?callable $onResult = null, bool $use_installed = false): array
{
if ($this->parallel > 1 && count($artifact_names) > 1) {
return $this->checkUpdatesWithConcurrency($artifact_names, $prefer_source, $bare, $onResult);
return $this->checkUpdatesWithConcurrency($artifact_names, $prefer_source, $bare, $onResult, $use_installed);
}
$results = [];
foreach ($artifact_names as $name) {
$result = $this->checkUpdate($name, $prefer_source, $bare);
$result = $this->checkUpdate($name, $prefer_source, $bare, $use_installed);
$results[$name] = $result;
if ($onResult !== null) {
($onResult)($name, $result);
@@ -436,7 +458,7 @@ class ArtifactDownloader
return $this->options[$name] ?? $default;
}
private function checkUpdatesWithConcurrency(array $artifact_names, bool $prefer_source, bool $bare, ?callable $onResult): array
private function checkUpdatesWithConcurrency(array $artifact_names, bool $prefer_source, bool $bare, ?callable $onResult, bool $use_installed = false): array
{
$results = [];
$fiber_pool = [];
@@ -451,8 +473,8 @@ class ArtifactDownloader
// fill pool
while (count($fiber_pool) < $this->parallel && !empty($remaining)) {
$name = array_shift($remaining);
$fiber = new \Fiber(function () use ($name, $prefer_source, $bare) {
return [$name, $this->checkUpdate($name, $prefer_source, $bare)];
$fiber = new \Fiber(function () use ($name, $prefer_source, $bare, $use_installed) {
return [$name, $this->checkUpdate($name, $prefer_source, $bare, $use_installed)];
});
$fiber->start();
$fiber_pool[$name] = $fiber;
@@ -491,12 +513,12 @@ class ArtifactDownloader
return $results;
}
private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
private function probeSourceCheckUpdate(Artifact $artifact, string $artifact_name, ?string $old_version = null): ?CheckUpdateResult
{
if (($callback = $artifact->getCustomSourceCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this,
'old_version' => null,
'old_version' => $old_version,
]);
}
$config = $artifact->getDownloadConfig('source');
@@ -509,16 +531,16 @@ class ArtifactDownloader
}
/** @var CheckUpdateInterface $dl */
$dl = new $cls();
return $dl->checkUpdate($artifact_name, $config, null, $this);
return $dl->checkUpdate($artifact_name, $config, $old_version, $this);
}
private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_name): ?CheckUpdateResult
private function probeBinaryCheckUpdate(Artifact $artifact, string $artifact_name, ?string $old_version = null): ?CheckUpdateResult
{
// custom binary callback takes precedence over config-based binary
if (($callback = $artifact->getCustomBinaryCheckUpdateCallback()) !== null) {
return ApplicationContext::invoke($callback, [
ArtifactDownloader::class => $this,
'old_version' => null,
'old_version' => $old_version,
]);
}
$binary_config = $artifact->getDownloadConfig('binary');
@@ -532,7 +554,7 @@ class ArtifactDownloader
}
/** @var CheckUpdateInterface $dl */
$dl = new $cls();
return $dl->checkUpdate($artifact_name, $platform_config, null, $this);
return $dl->checkUpdate($artifact_name, $platform_config, $old_version, $this);
}
private function downloadWithType(Artifact $artifact, int $current, int $total, bool $parallel = false): int

View File

@@ -614,7 +614,7 @@ class ArtifactExtractor
'{source_path}' => SOURCE_PATH,
'{download_path}' => DOWNLOAD_PATH,
'{working_dir}' => WORKING_DIR,
'{php_sdk_path}' => getenv('PHP_SDK_PATH') ?: '',
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH') ?: '',
];
return str_replace(array_keys($replacement), array_values($replacement), $path);
}

View File

@@ -76,9 +76,10 @@ class DownloadResult
?string $version = null,
array $metadata = [],
?string $downloader = null,
mixed $extract = null,
): DownloadResult {
$cache_type = self::isArchiveFile($filename) ? 'archive' : 'file';
return new self($cache_type, config: $config, filename: $filename, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
return new self($cache_type, config: $config, filename: $filename, extract: $extract, verified: $verified, version: $version, metadata: $metadata, downloader: $downloader);
}
/**

View File

@@ -45,7 +45,11 @@ class FileList implements DownloadTypeInterface, CheckUpdateInterface
throw new DownloaderException("Failed to get {$name} file list from {$config['url']}");
}
$versions = [];
logger()->debug('Matched ' . count($matches['version']) . " versions for {$name}");
$cnt = count($matches['version']);
if ($cnt === 0) {
throw new DownloaderException("Failed to get {$name} file list from {$config['url']}: no version parsed");
}
logger()->debug("Matched {$cnt} versions for {$name}");
foreach ($matches['version'] as $i => $version) {
$lower = strtolower($version);
foreach (['alpha', 'beta', 'rc', 'pre', 'nightly', 'snapshot', 'dev'] as $beta) {

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Attribute\Package;
/**
* Indicates that the annotated class defines a tool package.
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
readonly class Tool
{
public function __construct(public string $name) {}
}

View File

@@ -54,6 +54,10 @@ abstract class BaseCommand extends Command
}
set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) {
// Respect the @ suppression operator (error_reporting() returns 0 when @ is used)
if (error_reporting() === 0) {
return true;
}
$tips = [
E_WARNING => ['PHP Warning: ', 'warning'],
E_NOTICE => ['PHP Notice: ', 'notice'],

View File

@@ -23,6 +23,7 @@ class CheckUpdateCommand extends BaseCommand
$this->addArgument('artifact', InputArgument::OPTIONAL, 'The name of the artifact(s) to check for updates, comma-separated (default: all downloaded artifacts)');
$this->addOption('json', null, null, 'Output result in JSON format');
$this->addOption('bare', null, null, 'Check update without requiring the artifact to be downloaded first (old version will be null)');
$this->addOption('installed', null, null, 'Compare against the version actually installed on disk (tool packages only) instead of the download cache; falls back to normal behavior for artifacts with no recorded installed version');
$this->addOption('parallel', 'p', InputOption::VALUE_REQUIRED, 'Number of parallel update checks (default: 10)', 10);
// --with-php option for checking updates with a specific PHP version context
@@ -45,8 +46,9 @@ class CheckUpdateCommand extends BaseCommand
try {
$downloader = new ArtifactDownloader($this->input->getOptions());
$bare = (bool) $this->getOption('bare');
$use_installed = (bool) $this->getOption('installed');
if ($this->getOption('json')) {
$results = $downloader->checkUpdates($artifacts, bare: $bare);
$results = $downloader->checkUpdates($artifacts, bare: $bare, use_installed: $use_installed);
$outputs = [];
foreach ($results as $artifact => $result) {
$outputs[$artifact] = [
@@ -59,7 +61,7 @@ class CheckUpdateCommand extends BaseCommand
$this->output->writeln(json_encode($outputs, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
return static::OK;
}
$downloader->checkUpdates($artifacts, bare: $bare, onResult: function (string $artifact, CheckUpdateResult $result) {
$downloader->checkUpdates($artifacts, bare: $bare, use_installed: $use_installed, onResult: function (string $artifact, CheckUpdateResult $result) {
if ($result->unsupported) {
$this->output->writeln("Artifact <info>{$artifact}</info> does not support update checking, <comment>skipped</comment>");
} elseif (!$result->needUpdate) {

View File

@@ -36,6 +36,15 @@ class CraftCommand extends BaseCommand
// set verbosity
$this->output->setVerbosity($craft['verbosity']);
// sync logger level and ApplicationContext debug mode to match the new verbosity
$level = match ($this->output->getVerbosity()) {
OutputInterface::VERBOSITY_VERBOSE => 'info',
OutputInterface::VERBOSITY_VERY_VERBOSE, OutputInterface::VERBOSITY_DEBUG => 'debug',
default => 'warning',
};
logger()->setLevel($level);
ApplicationContext::setDebug($this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG);
// apply env
array_walk($craft['extra-env'], fn ($v, $k) => f_putenv("{$k}={$v}"));
@@ -110,7 +119,7 @@ class CraftCommand extends BaseCommand
* shared-extensions: array<string>,
* packages: array<string>,
* sapi: array<string>,
* verbosity: int,
* verbosity: 128|16|256|32|64|8,
* debug: bool,
* clean-build: bool,
* build-options: array<string, mixed>,
@@ -171,11 +180,16 @@ class CraftCommand extends BaseCommand
}
// verbosity
$verbosity_level = $craft['verbosity'] ?? OutputInterface::VERBOSITY_NORMAL;
$debug = $craft['debug'] ?? false;
if ($debug) {
$verbosity_level = OutputInterface::VERBOSITY_DEBUG;
}
$verbosity_level = $debug
? OutputInterface::VERBOSITY_DEBUG
: match ((int) ($craft['verbosity'] ?? 0)) {
OutputInterface::VERBOSITY_QUIET => OutputInterface::VERBOSITY_QUIET,
OutputInterface::VERBOSITY_VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
OutputInterface::VERBOSITY_VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
OutputInterface::VERBOSITY_DEBUG => OutputInterface::VERBOSITY_DEBUG,
default => OutputInterface::VERBOSITY_NORMAL,
};
$craft['verbosity'] = $verbosity_level;
// clean-build (if true, reset before all builds)

View File

@@ -54,8 +54,8 @@ class DumpCapabilitiesCommand extends BaseCommand
{
$result = [];
// library / target / virtual-target
foreach (PackageLoader::getPackages(['library', 'target', 'virtual-target']) as $name => $pkg) {
// library / target / virtual-target / tool
foreach (PackageLoader::getPackages(['library', 'target', 'virtual-target', 'tool']) as $name => $pkg) {
$installable = [];
$artifact = $pkg->getArtifact();
if ($artifact !== null) {

View File

@@ -16,7 +16,7 @@ class GenExtTestMatrixCommand extends BaseCommand
private const array OS_RUNNERS = [
'linux' => ['arch' => 'x86_64', 'runner' => 'ubuntu-latest', 'os_key' => 'Linux'],
'windows' => ['arch' => 'x86_64', 'runner' => 'windows-latest', 'os_key' => 'Windows'],
'windows' => ['arch' => 'x86_64', 'runner' => 'windows-2025', 'os_key' => 'Windows'],
'macos' => ['arch' => 'aarch64', 'runner' => 'macos-15', 'os_key' => 'Darwin'],
];
@@ -60,6 +60,8 @@ class GenExtTestMatrixCommand extends BaseCommand
'glfw',
'imagick',
'intl',
'mongodb',
'gmssl',
];
/**
@@ -105,7 +107,12 @@ class GenExtTestMatrixCommand extends BaseCommand
// Separate into regular and virtual extensions (build-static:false excluded globally)
$all_regular = [];
$all_virtual = [];
$all_libraries = [];
foreach ($all as $pkg_name => $config) {
if (($config['type'] ?? '') === 'library') {
$all_libraries[$pkg_name] = $config;
continue;
}
if (($config['type'] ?? '') !== 'php-extension') {
continue;
}
@@ -152,10 +159,7 @@ class GenExtTestMatrixCommand extends BaseCommand
$raw,
fn ($d) => isset($pool_set[$d]) && $d !== $pkg_name
));
$os_lib_deps[$this->displayName($pkg_name)] = array_values(array_filter(
$raw,
fn ($d) => !str_starts_with($d, 'ext-')
));
$os_lib_deps[$this->displayName($pkg_name)] = $this->collectLibraryDeps($raw, $all_libraries, $os);
}
$all_ext_lib_deps[$os] = $os_lib_deps;
@@ -244,22 +248,23 @@ class GenExtTestMatrixCommand extends BaseCommand
}
}
if (!empty($filter_extensions)) {
$entries = array_values(array_filter($entries, function (array $entry) use ($filter_extensions): bool {
if (!empty($filter_extensions) || !empty($filter_libs)) {
$entries = array_values(array_filter($entries, function (array $entry) use ($filter_extensions, $filter_libs, $all_ext_lib_deps): bool {
$names = explode(',', $entry['extension']);
return count(array_intersect($names, $filter_extensions)) > 0;
}));
}
if (!empty($filter_libs)) {
$entries = array_values(array_filter($entries, function (array $entry) use ($filter_libs, $all_ext_lib_deps): bool {
$names = explode(',', $entry['extension']);
$lib_deps = $all_ext_lib_deps[$entry['os']] ?? [];
foreach ($names as $name) {
if (count(array_intersect($lib_deps[$name] ?? [], $filter_libs)) > 0) {
return true;
if (!empty($filter_extensions) && count(array_intersect($names, $filter_extensions)) > 0) {
return true;
}
if (!empty($filter_libs)) {
$lib_deps = $all_ext_lib_deps[$entry['os']] ?? [];
foreach ($names as $name) {
if (count(array_intersect($lib_deps[$name] ?? [], $filter_libs)) > 0) {
return true;
}
}
}
return false;
}));
}
@@ -298,6 +303,41 @@ class GenExtTestMatrixCommand extends BaseCommand
return str_starts_with($pkg_name, 'ext-') ? substr($pkg_name, 4) : $pkg_name;
}
/**
* Collect direct and transitive library dependencies from a package dependency list.
*
* @param string[] $deps
* @param array<string, mixed[]> $library_configs
* @param array<string, true> $seen
*
* @return string[]
*/
private function collectLibraryDeps(array $deps, array $library_configs, string $platform, array $seen = []): array
{
$collected = [];
foreach ($deps as $dep) {
if (str_starts_with($dep, 'ext-') || isset($seen[$dep])) {
continue;
}
$seen[$dep] = true;
$collected[$dep] = $dep;
if (!isset($library_configs[$dep])) {
continue;
}
$child_deps = array_merge(
$this->resolvePlatformList($library_configs[$dep], 'depends', $platform),
$this->resolvePlatformList($library_configs[$dep], 'suggests', $platform),
);
foreach ($this->collectLibraryDeps($child_deps, $library_configs, $platform, $seen) as $child_dep) {
$collected[$child_dep] = $child_dep;
}
}
return array_values($collected);
}
/**
* Split orphans into batches such that no two conflicting extensions share a batch.
* Uses a greedy graph-coloring approach.

View File

@@ -154,7 +154,7 @@ class TestBotCommand extends BaseCommand
'targets' => array_values($targets),
'gen_matrix_args' => $gen_matrix_args,
'gen_matrix_args_tier2' => $gen_matrix_args_tier2,
'php_versions' => array_values($php_versions),
'php_versions' => $php_versions,
'tier2' => $tier2,
'comment_body' => $comment_body,
];
@@ -253,6 +253,13 @@ class TestBotCommand extends BaseCommand
$fmt($targets),
);
$available_labels = implode(', ', [
'`need-test` (gate)',
'`test/linux` `test/windows` `test/macos` (platform)',
'`test/tier2` (extra arch)',
'`test/php-83` `test/php-84` (PHP version)',
]);
// Case 1: need-test absent → invite the author to add it
if (!$need_test) {
return implode("\n", [
@@ -261,11 +268,9 @@ class TestBotCommand extends BaseCommand
'',
$detected,
'',
'To trigger extension build tests on this PR, add the `need-test` label:',
'To trigger extension build tests on this PR, add the `need-test` label.',
'',
'**Gate**: `need-test`',
'**Platform filter** (optional, default all): `test/linux` `test/windows` `test/macos` · `test/tier2`',
'**PHP version** (optional, default 8.5): `test/php-83` `test/php-84`',
'**Available labels**: ' . $available_labels,
]);
}
@@ -307,6 +312,7 @@ class TestBotCommand extends BaseCommand
'',
$detected,
'**Active labels**: ' . $labels_str,
'**Available labels**: ' . $available_labels,
'**Config**: ' . implode(' + ', $platform_parts) . ' | ' . $php_str,
]);
}

View File

@@ -13,7 +13,7 @@ class DoctorCommand extends BaseCommand
{
public function configure(): void
{
$this->addOption('auto-fix', null, InputOption::VALUE_OPTIONAL, 'Automatically fix failed items (if possible)', false);
$this->addOption('auto-fix', 'y', InputOption::VALUE_OPTIONAL, 'Automatically fix failed items (if possible)', false);
}
public function handle(): int

View File

@@ -23,7 +23,7 @@ class InstallPackageCommand extends BaseCommand
'The package to install (name or path)',
suggestedValues: function (CompletionInput $input) {
$packages = [];
foreach (PackageLoader::getPackages(['target', 'virtual-target']) as $name => $_) {
foreach (PackageLoader::getPackages(['target', 'virtual-target', 'tool']) as $name => $_) {
$packages[] = $name;
}
$val = $input->getCompletionValue();

View File

@@ -4,13 +4,14 @@ declare(strict_types=1);
namespace StaticPHP\Command;
use StaticPHP\Exception\SPCInternalException;
use StaticPHP\Runtime\Shell\Shell;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputOption;
use function Laravel\Prompts\confirm;
use Symfony\Component\Console\Question\ConfirmationQuestion;
#[AsCommand('reset')]
class ResetCommand extends BaseCommand
@@ -46,7 +47,11 @@ class ResetCommand extends BaseCommand
// Confirm with user unless --yes is specified
if (!$this->input->getOption('yes')) {
if (!confirm('Are you sure you want to continue?', false)) {
$helper = $this->getHelper('question');
if (!$helper instanceof QuestionHelper) {
throw new SPCInternalException('Question helper not provided');
}
if (!$helper->ask($this->input, $this->output, new ConfirmationQuestion('Are you sure you want to continue? [y/N] ', false))) {
InteractiveTerm::error(message: 'Reset operation cancelled.');
return static::SUCCESS;
}

View File

@@ -38,7 +38,7 @@ class ArtifactConfig
*/
public static function loadFromFile(string $file, string $registry_name): string
{
$content = file_get_contents($file);
$content = @file_get_contents($file);
if ($content === false) {
throw new WrongUsageException("Failed to read artifact config file: {$file}");
}

View File

@@ -19,6 +19,7 @@ enum ConfigType
'php-extension',
'target',
'virtual-target',
'tool',
];
public static function validateLicenseField(mixed $value): bool

View File

@@ -24,6 +24,10 @@ class ConfigValidator
'lang' => ConfigType::STRING,
'frameworks' => ConfigType::LIST_ARRAY, // @
// build-time tool dependency declaration (resolved independently of the library
// dependency graph, see PackageInstaller::collectRequiredTools())
'tools' => ConfigType::LIST_ARRAY, // @
// php-extension type fields
'php-extension' => ConfigType::ASSOC_ARRAY,
'zend-extension' => ConfigType::BOOL,
@@ -44,6 +48,13 @@ class ConfigValidator
'path' => ConfigType::LIST_ARRAY, // @
'env' => ConfigType::ASSOC_ARRAY, // @
'append-env' => ConfigType::ASSOC_ARRAY, // @
// tool type fields (nested under 'tool' key)
'tool' => ConfigType::ASSOC_ARRAY,
'provides' => ConfigType::LIST_ARRAY,
'binary-subdir' => ConfigType::STRING,
'install-root' => ConfigType::STRING,
'min-version' => ConfigType::STRING,
];
public const array PACKAGE_FIELDS = [
@@ -56,6 +67,9 @@ class ConfigValidator
'lang' => false,
'frameworks' => false, // @
// build-time tool dependency declaration
'tools' => false, // @
// php-extension type fields
'php-extension' => false,
@@ -67,6 +81,9 @@ class ConfigValidator
'path' => false, // @
'env' => false, // @
'append-env' => false, // @
// tool fields (nested object)
'tool' => false,
];
public const array SUFFIX_ALLOWED_FIELDS = [
@@ -78,6 +95,7 @@ class ConfigValidator
'path',
'env',
'append-env',
'tools',
];
public const array PHP_EXTENSION_FIELDS = [
@@ -92,6 +110,13 @@ class ConfigValidator
'os' => false,
];
public const array TOOL_FIELDS = [
'provides' => true,
'binary-subdir' => false,
'install-root' => false,
'min-version' => false,
];
public const array ARTIFACT_TYPE_FIELDS = [ // [required_fields, optional_fields]
'filelist' => [['url', 'regex'], ['extract']],
'git' => [['url'], ['extract', 'submodules', 'rev', 'regex']],
@@ -220,8 +245,8 @@ class ConfigValidator
$fields = self::SUFFIX_ALLOWED_FIELDS;
self::validateSuffixAllowedFields($name, $pkg, $fields, $suffixes);
// check if "library|target" package has artifact field for target and library types
if (in_array($pkg['type'], ['target', 'library']) && !isset($pkg['artifact'])) {
// check if "library|target|tool" package has artifact field
if (in_array($pkg['type'], ['target', 'library', 'tool']) && !isset($pkg['artifact'])) {
throw new ValidationException("Package [{$name}] in {$config_file_name} of type '{$pkg['type']}' must have an 'artifact' field");
}
@@ -235,6 +260,11 @@ class ConfigValidator
self::validatePhpExtensionFields($name, $pkg);
}
// check if "tool" package has tool specific fields and validate inside
if ($pkg['type'] === 'tool') {
self::validateToolFields($name, $pkg);
}
// check for unknown fields
self::validateNoInvalidFields('package', $name, $pkg, array_keys(self::PACKAGE_FIELD_TYPES));
}
@@ -397,6 +427,29 @@ class ConfigValidator
self::validateNoInvalidFields('php-extension', $name, $pkg['php-extension'], array_keys(self::PHP_EXTENSION_FIELDS));
}
/**
* Validate tool specific fields for tool package type.
*/
private static function validateToolFields(int|string $name, mixed $pkg): void
{
if (!isset($pkg['tool'])) {
throw new ValidationException("Package {$name} of type 'tool' must have a 'tool' field");
}
if (!is_assoc_array($pkg['tool'])) {
throw new ValidationException("Package {$name} [tool] must be an object");
}
foreach (self::TOOL_FIELDS as $field => $required) {
if ($required && !isset($pkg['tool'][$field])) {
throw new ValidationException("Package {$name} [tool] must have required field [{$field}]");
}
if (isset($pkg['tool'][$field])) {
self::validatePackageFieldType($field, $pkg['tool'][$field], $name);
}
}
// check for unknown fields in tool
self::validateNoInvalidFields('tool', $name, $pkg['tool'], array_keys(self::TOOL_FIELDS));
}
private static function validateNoInvalidFields(string $config_type, int|string $item_name, mixed $item_content, array $allowed_fields): void
{
foreach ($item_content as $k => $v) {

View File

@@ -16,7 +16,7 @@ class PackageConfig
/**
* Load package configurations from a specified directory.
* It will look for files matching the pattern 'pkg.*.json' and 'pkg.json'.
* Only processes .json, .yml, and .yaml files (skips .gitkeep etc.).
*/
public static function loadFromDir(string $dir, string $registry_name): array
{
@@ -28,6 +28,10 @@ class PackageConfig
$files = FileSystem::scanDirFiles($dir, false);
if (is_array($files)) {
foreach ($files as $file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
if (!in_array($ext, ['json', 'yml', 'yaml'], true)) {
continue;
}
self::loadFromFile($file, $registry_name);
$loaded[] = $file;
}

View File

@@ -32,7 +32,10 @@ use StaticPHP\Command\SPCConfigCommand;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Registry\Registry;
use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ConsoleApplication extends Application
{
@@ -104,4 +107,15 @@ class ConsoleApplication extends Application
{
self::$additional_commands = array_merge(self::$additional_commands, $additional_commands);
}
/**
* Hook Symfony's doRun() to inject the real input/output into InteractiveTerm before
* any command executes. From this point forward, InteractiveTerm uses the configured
* Console (respecting --no-ansi, --verbose, etc.) instead of the lazy STDERR fallback.
*/
public function doRun(InputInterface $input, OutputInterface $output): int
{
InteractiveTerm::init($input, $output);
return parent::doRun($input, $output);
}
}

View File

@@ -79,11 +79,11 @@ class ApplicationContext
/**
* Get a service from the container.
*
* @template T
* @template T of object
*
* @param class-string<T> $id Service identifier
* @param class-string<T>|string $id Service identifier
*
* @return null|T
* @return ($id is class-string<T> ? T : mixed)
*/
public static function get(string $id): mixed
{

View File

@@ -11,11 +11,14 @@ use StaticPHP\Registry\DoctorLoader;
use StaticPHP\Runtime\Shell\Shell;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\InteractiveTerm;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use ZM\Logger\ConsoleColor;
use function Laravel\Prompts\confirm;
readonly class Doctor
{
public function __construct(private ?OutputInterface $output = null, private int $auto_fix = FIX_POLICY_PROMPT, public readonly bool $interactive = true)
@@ -125,9 +128,14 @@ readonly class Doctor
return false;
}
// prompt for fix
if ($this->auto_fix === FIX_POLICY_PROMPT && !confirm('Do you want to try to fix this issue now?')) {
$this->output?->writeln('<comment>You canceled fix.</comment>');
return false;
if ($this->auto_fix === FIX_POLICY_PROMPT) {
$helper = new QuestionHelper();
$input = ApplicationContext::has(InputInterface::class) ? ApplicationContext::get(InputInterface::class) : new ArrayInput([]);
$output = ApplicationContext::has(OutputInterface::class) ? ApplicationContext::get(OutputInterface::class) : $this->output ?? new ConsoleOutput();
if (!$helper->ask($input, $output, new ConfirmationQuestion('Do you want to try to fix this issue now? [Y/n] ', true))) {
$this->output?->writeln('<comment>You canceled fix.</comment>');
return false;
}
}
// perform fix
InteractiveTerm::indicateProgress("Fixing {$result->getFixItem()} ... ");

View File

@@ -54,13 +54,24 @@ class WindowsToolCheck
return CheckResult::ok();
}
#[CheckItem('if php-sdk-binary-tools are downloaded', limit_os: 'Windows', level: 996)]
public function checkSDK(): ?CheckResult
#[CheckItem('if msys2-build-essentials is installed', limit_os: 'Windows', level: 996)]
public function checkMsys2(): ?CheckResult
{
if (!file_exists(getenv('PHP_SDK_PATH') . DIRECTORY_SEPARATOR . 'phpsdk-starter.bat')) {
return CheckResult::fail('php-sdk-binary-tools not downloaded', 'install-php-sdk');
$marker = PKG_ROOT_PATH . '\msys2-build-essentials\.spc-msys2-initialized';
if (!file_exists($marker)) {
return CheckResult::fail('msys2-build-essentials not installed', 'install-msys2-build-essentials');
}
return CheckResult::ok(getenv('PHP_SDK_PATH'));
return CheckResult::ok(PKG_ROOT_PATH . '\msys2-build-essentials\msys64');
}
#[CheckItem('if 7za.exe is installed', limit_os: 'Windows', level: 999)]
public function check7zaWin(): ?CheckResult
{
$path = FileSystem::convertPath(PKG_ROOT_PATH . '\bin\7za.exe');
if (!file_exists($path)) {
return CheckResult::fail('7za.exe not found', 'install-7za-win');
}
return CheckResult::ok($path);
}
#[CheckItem('if nasm installed', level: 995)]
@@ -112,12 +123,20 @@ class WindowsToolCheck
return true;
}
#[FixItem('install-php-sdk')]
public function installSDK(): bool
#[FixItem('install-msys2-build-essentials')]
public function installMsys2(): bool
{
FileSystem::removeDir(getenv('PHP_SDK_PATH'));
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('php-sdk-binary-tools');
$installer->addInstallPackage('msys2-build-essentials');
$installer->run(true);
return true;
}
#[FixItem('install-7za-win')]
public function install7zaWin(): bool
{
$installer = new PackageInstaller(interactive: false);
$installer->addInstallPackage('7za-win');
$installer->run(true);
return true;
}

View File

@@ -113,60 +113,6 @@ class LibraryPackage extends Package
}
}
/**
* Get extra CFLAGS for current package.
* You need to define the environment variable in the format of {LIBRARY_NAME}_CFLAGS
* where {LIBRARY_NAME} is the snake_case name of the library.
* For example, for libjpeg, the environment variable should be libjpeg_CFLAGS.
*/
public function getLibExtraCFlags(): string
{
// get environment variable
$env = getenv($this->getSnakeCaseName() . '_CFLAGS') ?: '';
// get default c flags
$arch_c_flags = getenv('SPC_DEFAULT_CFLAGS') ?: '';
if (!empty(getenv('SPC_DEFAULT_CFLAGS')) && !str_contains($env, $arch_c_flags)) {
$env .= ' ' . $arch_c_flags;
}
return trim($env);
}
/**
* Get extra CXXFLAGS for current package.
* You need to define the environment variable in the format of {LIBRARY_NAME}_CXXFLAGS
* where {LIBRARY_NAME} is the snake_case name of the library.
* For example, for libjpeg, the environment variable should be libjpeg_CXXFLAGS.
*/
public function getLibExtraCxxFlags(): string
{
// get environment variable
$env = getenv($this->getSnakeCaseName() . '_CXXFLAGS') ?: '';
// get default cxx flags
$arch_cxx_flags = getenv('SPC_DEFAULT_CXXFLAGS') ?: '';
if (!empty(getenv('SPC_DEFAULT_CXXFLAGS')) && !str_contains($env, $arch_cxx_flags)) {
$env .= ' ' . $arch_cxx_flags;
}
return trim($env);
}
/**
* Get extra LDFLAGS for current package.
* You need to define the environment variable in the format of {LIBRARY_NAME}_LDFLAGS
* where {LIBRARY_NAME} is the snake_case name of the library.
* For example, for libjpeg, the environment variable should be libjpeg_LDFLAGS.
*/
public function getLibExtraLdFlags(): string
{
// get environment variable
$env = getenv($this->getSnakeCaseName() . '_LDFLAGS') ?: '';
// get default ld flags
$arch_ld_flags = getenv('SPC_DEFAULT_LDFLAGS') ?: '';
if (!empty(getenv('SPC_DEFAULT_LDFLAGS')) && !str_contains($env, $arch_ld_flags)) {
$env .= ' ' . $arch_ld_flags;
}
return trim($env);
}
/**
* Patch pkgconfig file prefix, exec_prefix, libdir, includedir for correct build.
*
@@ -364,17 +310,6 @@ class LibraryPackage extends Package
return $res['libs'];
}
/**
* Get extra LIBS for current package.
* You need to define the environment variable in the format of {LIBRARY_NAME}_LIBS
* where {LIBRARY_NAME} is the snake_case name of the library.
* For example, for libjpeg, the environment variable should be libjpeg_LIBS.
*/
public function getLibExtraLibs(): string
{
return getenv($this->getSnakeCaseName() . '_LIBS') ?: '';
}
/**
* Get tar compress options from suffix
*

View File

@@ -120,6 +120,20 @@ abstract class Package
return false;
}
/**
* Get the target directory where this package's artifacts should be placed.
*
* Libraries install to BUILD_ROOT_PATH (static-libs, headers, pkg-configs).
* Tools install to PKG_ROOT_PATH (executables).
* Extensions install to php-src/ext/ (shared objects).
*
* Override in subclasses to change the default.
*/
public function getInstallTarget(): string
{
return BUILD_ROOT_PATH;
}
/**
* Add a stage to the package.
*/
@@ -270,6 +284,71 @@ abstract class Package
return $this->getArtifact()?->hasPlatformBinary() ?? false;
}
/**
* Get extra CFLAGS for current package.
* You need to define the environment variable in the format of {PACKAGE_NAME}_CFLAGS
* where {PACKAGE_NAME} is the snake_case name of the package.
* For example, for libjpeg, the environment variable should be libjpeg_CFLAGS.
*/
public function getLibExtraCFlags(): string
{
// get environment variable
$env = getenv($this->getSnakeCaseName() . '_CFLAGS') ?: '';
// get default c flags
$arch_c_flags = getenv('SPC_DEFAULT_CFLAGS') ?: '';
if (!empty(getenv('SPC_DEFAULT_CFLAGS')) && !str_contains($env, $arch_c_flags)) {
$env .= ' ' . $arch_c_flags;
}
return trim($env);
}
/**
* Get extra CXXFLAGS for current package.
* You need to define the environment variable in the format of {PACKAGE_NAME}_CXXFLAGS
* where {PACKAGE_NAME} is the snake_case name of the package.
* For example, for libjpeg, the environment variable should be libjpeg_CXXFLAGS.
*/
public function getLibExtraCxxFlags(): string
{
// get environment variable
$env = getenv($this->getSnakeCaseName() . '_CXXFLAGS') ?: '';
// get default cxx flags
$arch_cxx_flags = getenv('SPC_DEFAULT_CXXFLAGS') ?: '';
if (!empty(getenv('SPC_DEFAULT_CXXFLAGS')) && !str_contains($env, $arch_cxx_flags)) {
$env .= ' ' . $arch_cxx_flags;
}
return trim($env);
}
/**
* Get extra LDFLAGS for current package.
* You need to define the environment variable in the format of {PACKAGE_NAME}_LDFLAGS
* where {PACKAGE_NAME} is the snake_case name of the package.
* For example, for libjpeg, the environment variable should be libjpeg_LDFLAGS.
*/
public function getLibExtraLdFlags(): string
{
// get environment variable
$env = getenv($this->getSnakeCaseName() . '_LDFLAGS') ?: '';
// get default ld flags
$arch_ld_flags = getenv('SPC_DEFAULT_LDFLAGS') ?: '';
if (!empty(getenv('SPC_DEFAULT_LDFLAGS')) && !str_contains($env, $arch_ld_flags)) {
$env .= ' ' . $arch_ld_flags;
}
return trim($env);
}
/**
* Get extra LIBS for current package.
* You need to define the environment variable in the format of {PACKAGE_NAME}_LIBS
* where {PACKAGE_NAME} is the snake_case name of the package.
* For example, for libjpeg, the environment variable should be libjpeg_LIBS.
*/
public function getLibExtraLibs(): string
{
return getenv($this->getSnakeCaseName() . '_LIBS') ?: '';
}
/**
* Get the snake_case name of the package.
*/

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace StaticPHP\Package;
use StaticPHP\Artifact\ArtifactCache;
use StaticPHP\Config\PackageConfig;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Exception\SPCException;
@@ -39,8 +40,8 @@ class PackageBuilder
public function buildPackage(Package $package, bool $force = false): int
{
// init build dirs
if (!$package instanceof LibraryPackage) {
throw new SPCInternalException('Please, never try to build non-library packages directly.');
if (!$package instanceof LibraryPackage && !$package instanceof ToolPackage) {
throw new SPCInternalException('Please, never try to build non-library, non-tool packages directly.');
}
FileSystem::createDir($package->getBuildRootPath());
FileSystem::createDir($package->getIncludeDir());
@@ -78,6 +79,14 @@ class PackageBuilder
$this->installLicense($package, $license);
}
}
// Record the installed version for tool packages built from source, so
// `check-update --installed` reflects what's actually on disk (mirrors the
// binary-install recording in PackageInstaller::installBinary()).
if ($package instanceof ToolPackage) {
$source_info = ApplicationContext::get(ArtifactCache::class)->getSourceInfo($package->getName());
ToolVersionRegistry::record($package->getName(), $source_info['version'] ?? null);
}
} catch (SPCException $e) {
// Ensure package information is bound if not already
if ($e->getPackageInfo() === null) {

View File

@@ -11,6 +11,7 @@ use StaticPHP\Artifact\ArtifactExtractor;
use StaticPHP\Artifact\DownloaderOptions;
use StaticPHP\Config\PackageConfig;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Exception\EnvironmentException;
use StaticPHP\Exception\WrongUsageException;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Runtime\SystemTarget;
@@ -75,6 +76,9 @@ class PackageInstaller
}
// special check for php target packages
if (in_array($package->getName(), ['php', 'php-cli', 'php-fpm', 'php-micro', 'php-cgi', 'php-embed', 'frankenphp'], true)) {
if (!$package instanceof TargetPackage) {
throw new WrongUsageException("Package '{$package->getName()}' is expected to be a TargetPackage.");
}
$this->handlePhpTargetPackage($package);
return $this;
}
@@ -171,7 +175,7 @@ class PackageInstaller
// These must always download binary (not source), regardless of global prefer-source setting.
$binary_only_packages = array_filter(
$this->packages,
fn ($p) => $p instanceof LibraryPackage
fn ($p) => ($p instanceof LibraryPackage || $p instanceof ToolPackage)
&& !$this->isBuildPackage($p)
&& !$p->hasStage('build')
&& ($p->getArtifact()?->hasPlatformBinary() ?? false)
@@ -209,8 +213,8 @@ class PackageInstaller
$builder = ApplicationContext::get(PackageBuilder::class);
foreach ($this->packages as $package) {
$is_to_build = $this->isBuildPackage($package);
$has_build_stage = $package instanceof LibraryPackage && $package->hasStage('build');
$should_use_binary = $package instanceof LibraryPackage && ($package->getArtifact()?->shouldUseBinary() ?? false);
$has_build_stage = ($package instanceof LibraryPackage || $package instanceof ToolPackage) && $package->hasStage('build');
$should_use_binary = ($package instanceof LibraryPackage || $package instanceof ToolPackage) && ($package->getArtifact()?->shouldUseBinary() ?? false);
$has_source = $package->hasSource();
if (!$is_to_build && $should_use_binary) {
// install binary
@@ -273,6 +277,11 @@ class PackageInstaller
// perform after-install actions and emit post-install events
$this->emitPostInstallEvents();
// Final verification: declared build-time tools should have been auto-installed by the
// pipeline above (they were merged into $this->packages in resolvePackages()). This only
// throws if a tool genuinely failed to become available (e.g. no artifact for this platform).
$this->ensureRequiredTools();
}
public function isBuildPackage(Package|string $package): bool
@@ -350,12 +359,16 @@ class PackageInstaller
}
// Fallback: if the download cache is missing (e.g. download failed or cache was cleared),
// still check whether the files are physically present in buildroot.
// Note: TargetPackage extends LibraryPackage, but target packages (e.g. zig) have no
// Note: TargetPackage extends LibraryPackage, but target packages have no
// static-libs/headers configured, so isInstalled() would trivially return true for them.
// Only apply this fallback to pure library packages.
if ($package instanceof LibraryPackage && !($package instanceof TargetPackage)) {
return $package->isInstalled();
}
// Tool packages: fall back to checking their provided binaries directly on disk.
if ($package instanceof ToolPackage) {
return $package->isInstalled();
}
return false;
}
@@ -504,6 +517,16 @@ class PackageInstaller
}
$status = $extractor->extract($artifact);
// Record the installed version for tool packages, so `check-update --installed` can
// compare against what's actually on disk instead of only the download cache (which
// only reflects the last download and may be stale or cleared). Recorded regardless of
// $status so the registry self-heals if it was deleted separately from the install dir.
if ($package instanceof ToolPackage) {
$cache_info = ApplicationContext::get(ArtifactCache::class)->getBinaryInfo($artifact->getName(), SystemTarget::getCurrentPlatformString());
ToolVersionRegistry::record($artifact->getName(), $cache_info['version'] ?? null);
}
if ($status === SPC_STATUS_ALREADY_EXTRACTED) {
return SPC_STATUS_ALREADY_INSTALLED;
}
@@ -571,6 +594,66 @@ class PackageInstaller
return null;
}
/**
* Collect all tool packages required by the currently resolved packages.
*
* Reads the 'tools' field from each resolved package's YAML config.
* The field supports platform suffixes (tools@windows, tools@linux, etc.)
* resolved automatically by PackageConfig::get().
*
* Tools are NOT part of the library dependency graph — they are
* build-time prerequisites that must be installed before any library
* build begins.
*
* @return string[] Unique tool package names required for this build
*/
public function collectRequiredTools(): array
{
$tools = [];
foreach ($this->packages as $package) {
$deps = PackageConfig::get($package->getName(), 'tools', []);
foreach ((array) $deps as $tool_name) {
$tools[$tool_name] = true;
}
}
return array_keys($tools);
}
/**
* Check that all required tools are installed.
*
* Iterates through tools collected by collectRequiredTools(),
* resolves each to a ToolPackage instance, and checks isInstalled().
*
* @return array{missing: array<string>, installed: array<string>}
*/
public function checkRequiredTools(): array
{
$missing = [];
$installed = [];
foreach ($this->collectRequiredTools() as $tool_name) {
try {
$tool = PackageLoader::getPackage($tool_name);
} catch (WrongUsageException) {
$missing[] = $tool_name;
logger()->warning("Required tool '{$tool_name}' is not registered as a package.");
continue;
}
if (!$tool instanceof ToolPackage) {
logger()->warning("Package '{$tool_name}' is declared as a tool dependency but is not a ToolPackage (type: {$tool->getType()}).");
continue;
}
if ($tool->isInstalled()) {
$installed[] = $tool_name;
} else {
$missing[] = $tool_name;
}
}
return ['missing' => $missing, 'installed' => $installed];
}
/**
* @param Package[] $packages
*/
@@ -594,8 +677,8 @@ class PackageInstaller
*/
private function validatePackageArtifact(Package $package): void
{
// target and library must have at least source or platform binary
if (in_array($package->getType(), ['library', 'target']) && !$package->getArtifact()?->hasSource() && !$package->getArtifact()?->hasPlatformBinary()) {
// target, library and tool packages must have at least source or platform binary
if (in_array($package->getType(), ['library', 'target', 'tool']) && !$package->getArtifact()?->hasSource() && !$package->getArtifact()?->hasPlatformBinary()) {
throw new WrongUsageException("Validation failed: Target package '{$package->getName()}' has no source or current platform (" . SystemTarget::getCurrentPlatformString() . ') binary defined.');
}
}
@@ -628,11 +711,62 @@ class PackageInstaller
$this->packages[$pkg_name] = PackageLoader::getPackage($pkg_name);
}
// Merge in build-time tool packages declared via 'tools'/'tools@platform' fields on the
// packages resolved above. Tools are intentionally NOT part of the library dependency graph
// (DependencyResolver), but still need to ride the normal download/extract/install pipeline
// so they get auto-installed like any other package.
//
// Tools are prepended so they install BEFORE any package that declares them — otherwise a
// library that invokes e.g. jom.exe during its build stage would fail because the tool
// hasn't been extracted yet (tools were appended after the dependency graph in insertion
// order, which is also the build-loop iteration order).
$tool_packages = [];
foreach ($this->collectRequiredTools() as $tool_name) {
if (isset($this->packages[$tool_name])) {
continue;
}
try {
$tool = PackageLoader::getPackage($tool_name);
} catch (WrongUsageException) {
continue; // will be reported as missing by ensureRequiredTools()
}
if ($tool instanceof ToolPackage) {
$tool_packages[$tool_name] = $tool;
}
}
// Prepend: tools first, then the dependency-resolved packages.
$this->packages = [...$tool_packages, ...$this->packages];
foreach ($this->packages as $package) {
$this->injectPackageEnvs($package);
}
}
/**
* Ensure all required tools are installed, throwing if any are missing.
*
* Called at the end of run(), after the normal download/extract/build/install pipeline
* has had a chance to auto-install any tool package merged in resolvePackages(). This is
* a final safety net: it only throws if a declared tool still isn't available afterward
* (e.g. no artifact defined for the current platform, or the tool name doesn't resolve to
* a registered ToolPackage). "General" tools not declared via any package's 'tools' field
* (e.g. zig, musl-toolchain) are not covered here and remain purely Doctor-driven.
*/
private function ensureRequiredTools(): void
{
$status = $this->checkRequiredTools();
if (empty($status['missing'])) {
if (!empty($status['installed'])) {
logger()->info('Required tools: ' . implode(', ', $status['installed']) . ' — all installed.');
}
return;
}
$msg = 'Missing required build tools: ' . implode(', ', $status['missing']) . "\n";
$msg .= "Run 'bin/spc doctor' to check your environment, or install the missing tools manually.";
throw new EnvironmentException($msg);
}
private function injectPackageEnvs(Package $package): void
{
$name = $package->getName();

View File

@@ -0,0 +1,233 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Package;
use StaticPHP\Config\PackageConfig;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\FileSystem;
/**
* Represents a build-time tool package.
*
* Tool packages are NOT link-time dependencies. They provide executables
* that are needed during the build process (compilers, code generators,
* assemblers, etc.) and are installed into PKG_ROOT_PATH.
*
* Tool packages do NOT produce static-libs, headers, or pkg-config files.
* They are resolved and installed independently from the library dependency graph.
*
* YAML config schema (config/pkg/tool/<name>.yml):
*
* nasm:
* type: tool
* tool:
* provides: [nasm.exe, ndisasm.exe] # executables this tool installs
* binary-subdir: '' # subdirectory under install root (default: '')
* min-version: '2.16' # minimum required version (optional)
*
* Fields nested under 'tool' support the same '@windows'/'@unix'/'@macos'/'@linux' suffix
* overrides as top-level package fields (e.g. 'provides@windows' overrides 'provides' when
* building on Windows), useful when a tool provides differently-named binaries per OS
* (e.g. upx vs upx.exe).
* artifact:
* binary:
* windows-x86_64:
* type: url
* url: 'https://...'
* extract:
* nasm.exe: '{php_sdk_path}/bin/nasm.exe'
*/
class ToolPackage extends Package
{
/**
* Get the build root ('--prefix') for a tool that builds from source.
*
* Unlike LibraryPackage (which installs into BUILD_ROOT_PATH), tool packages that build
* from source (e.g. pkg-config, via UnixAutoconfExecutor/UnixCMakeExecutor) install into
* their own install root (PKG_ROOT_PATH by default), consistent with pre-built tool binaries.
*/
public function getBuildRootPath(): string
{
return $this->getInstallRoot();
}
/**
* Tool packages don't produce headers for other packages to consume. Kept self-contained
* under the tool's own install root so a from-source build never accidentally picks up
* unrelated headers from BUILD_ROOT_PATH.
*/
public function getIncludeDir(): string
{
return $this->getInstallRoot() . DIRECTORY_SEPARATOR . 'include';
}
/**
* Tool packages don't produce libraries for other packages to consume. Kept self-contained
* under the tool's own install root (see getIncludeDir()).
*/
public function getLibDir(): string
{
return $this->getInstallRoot() . DIRECTORY_SEPARATOR . 'lib';
}
/**
* Where this tool's own executables live, i.e. {install-root}/{binary-subdir}.
*/
public function getBinDir(): string
{
return $this->getBinaryDir();
}
/**
* Get the install root directory for this tool.
*
* Defaults to PKG_ROOT_PATH. Override via 'tool.install-root' in YAML
* or via the TOOL_INSTALL_ROOT_{NAME} environment variable.
*/
public function getInstallRoot(): string
{
$env_var = 'TOOL_INSTALL_ROOT_' . strtoupper(str_replace('-', '_', $this->name));
if ($root = getenv($env_var)) {
return $root;
}
$config_root = $this->getToolField('install-root');
if ($config_root !== null) {
return FileSystem::replacePathVariable((string) $config_root);
}
return PKG_ROOT_PATH;
}
/**
* Get the directory where this tool's binaries reside.
*
* This is {install-root}/{binary-subdir}. If binary-subdir is not
* configured, returns the install root directly.
*/
public function getBinaryDir(): string
{
$subdir = $this->getToolField('binary-subdir') ?? '';
if ($subdir === '') {
return $this->getInstallRoot();
}
return $this->getInstallRoot() . DIRECTORY_SEPARATOR . $subdir;
}
/**
* Get the list of executables this tool provides.
*
* Reads from YAML 'tool.provides' field (with '@windows'/'@unix'/'@macos'/'@linux'
* suffix override support). Each entry is a bare filename (e.g. 'nasm.exe'), resolved
* relative to getBinaryDir().
*
* @return string[] Bare executable names (not full paths)
*/
public function getProvides(): array
{
return $this->getToolField('provides') ?? [];
}
/**
* Get the full path to a specific binary provided by this tool.
*
* @param string $name Bare executable name (must be listed in tool.provides).
* If empty, defaults to the first entry in provides.
* @return string Full absolute path to the binary
*/
public function getBinary(string $name = ''): string
{
$provides = $this->getProvides();
if ($name === '') {
$name = $provides[0] ?? throw new \RuntimeException("Tool '{$this->name}' has no 'tool.provides' configured.");
}
if (!in_array($name, $provides, true)) {
throw new \RuntimeException("Binary '{$name}' is not listed in tool.provides for '{$this->name}'. Available: " . implode(', ', $provides));
}
return $this->getBinaryDir() . DIRECTORY_SEPARATOR . $name;
}
/**
* Check whether this tool is installed (all provided binaries exist on disk).
*/
public function isInstalled(): bool
{
return array_all($this->getProvides(), fn ($binary) => file_exists($this->getBinary($binary)));
}
/**
* Get the version currently installed on disk, as recorded by ToolVersionRegistry when this
* tool's binary was last (re-)installed via PackageInstaller::installBinary().
*
* Returns null if the tool was never installed through the package installer (e.g. installed
* manually, or not installed at all), or if its artifact doesn't expose a version string.
* This reflects what's actually on disk, unlike the download cache which only reflects the
* last download and may be stale or cleared.
*/
public function getInstalledVersion(): ?string
{
return ToolVersionRegistry::get($this->name);
}
/**
* Get the minimum required version for this tool, if specified.
*
* Returns null if no version constraint is configured.
*/
public function getMinVersion(): ?string
{
$version = $this->getToolField('min-version');
return $version !== null ? (string) $version : null;
}
/**
* Tools install to PKG_ROOT_PATH (or the configured install-root),
* not BUILD_ROOT_PATH.
*/
public function getInstallTarget(): string
{
return $this->getBinaryDir();
}
/**
* Get the 'tool' sub-config for this package.
*
* Returns the nested array under the 'tool' key in the package YAML,
* or an empty array if not configured.
*
* @return array<string, mixed>
*/
private function getToolConfig(): array
{
$config = PackageConfig::get($this->name);
if (!is_array($config) || !isset($config['tool']) || !is_array($config['tool'])) {
return [];
}
return $config['tool'];
}
/**
* Get a field from the nested 'tool' config block, honoring the same
* '@windows'/'@unix'/'@macos'/'@linux'/'@bsd'/'@freebsd' suffix override priority
* that PackageConfig::get() applies to top-level fields. This lets a tool declare a
* per-OS override, e.g. 'provides' + 'provides@windows', without needing platform-
* specific package config files.
*/
private function getToolField(string $field): mixed
{
$tool = $this->getToolConfig();
$suffixes = match (SystemTarget::getTargetOS()) {
'Windows' => ['@windows', ''],
'Darwin' => ['@macos', '@unix', ''],
'Linux' => ['@linux', '@unix', ''],
'BSD' => ['@freebsd', '@bsd', '@unix', ''],
};
foreach ($suffixes as $suffix) {
$key = "{$field}{$suffix}";
if (isset($tool[$key])) {
return $tool[$key];
}
}
return null;
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Package;
use StaticPHP\Runtime\SystemTarget;
/**
* Tracks the version actually installed on disk for tool packages (PKG_ROOT_PATH), separate from
* the download cache (ArtifactCache). Tools are installed once and reused across many builds, so
* the download cache (which only reflects the last download) can drift from what's really on disk
* (e.g. cache cleared, or installed a long time ago via `doctor`). This registry is the source of
* truth for "what version is currently installed", used by ToolPackage::getInstalledVersion() and
* the `check-update --installed` flag.
*
* Backed by a small JSON file at PKG_ROOT_PATH/.spc-tool-versions.json, mirroring the same
* read-once/write-through pattern used by StaticPHP\Artifact\ArtifactCache.
*/
class ToolVersionRegistry
{
/** @var null|array<string, array{version: null|string, platform: string, installed_at: string}> */
private static ?array $data = null;
/**
* Get the recorded installed version for a tool, or null if never recorded (not a tool
* package, not installed yet, or the artifact doesn't expose a version).
*/
public static function get(string $tool_name): ?string
{
self::load();
return self::$data[$tool_name]['version'] ?? null;
}
/**
* Record the version currently installed for a tool. Called after a tool package's binary
* has been (re-)installed, regardless of whether extraction actually ran (keeps the registry
* self-healing if it was deleted separately from the installed files).
*/
public static function record(string $tool_name, ?string $version): void
{
self::load();
self::$data[$tool_name] = [
'version' => $version,
'platform' => SystemTarget::getCurrentPlatformString(),
'installed_at' => date('c'),
];
self::save();
}
private static function getPath(): string
{
return PKG_ROOT_PATH . '/.spc-tool-versions.json';
}
private static function load(): void
{
if (self::$data !== null) {
return;
}
$path = self::getPath();
if (!file_exists($path)) {
self::$data = [];
return;
}
$content = file_get_contents($path);
self::$data = is_string($content) ? (json_decode($content, true) ?: []) : [];
}
private static function save(): void
{
$path = self::getPath();
$dir = dirname($path);
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
file_put_contents($path, json_encode(self::$data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
}

View File

@@ -17,6 +17,7 @@ use StaticPHP\Attribute\Package\PatchBeforeBuild;
use StaticPHP\Attribute\Package\ResolveBuild;
use StaticPHP\Attribute\Package\Stage;
use StaticPHP\Attribute\Package\Target;
use StaticPHP\Attribute\Package\Tool;
use StaticPHP\Attribute\Package\Validate;
use StaticPHP\Config\PackageConfig;
use StaticPHP\DI\ApplicationContext;
@@ -27,6 +28,7 @@ use StaticPHP\Package\Package;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\PhpExtensionPackage;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Util\FileSystem;
class PackageLoader
@@ -88,6 +90,7 @@ class PackageLoader
'target', 'virtual-target' => new TargetPackage($name, $item['type']),
'library' => new LibraryPackage($name, $item['type']),
'php-extension' => new PhpExtensionPackage($name, $item['type']),
'tool' => new ToolPackage($name, $item['type']),
default => null,
};
if ($pkg !== null) {
@@ -190,7 +193,8 @@ class PackageLoader
$attribute_instance = $attribute->newInstance();
if ($attribute_instance instanceof Target === false &&
$attribute_instance instanceof Library === false &&
$attribute_instance instanceof Extension === false) {
$attribute_instance instanceof Extension === false &&
$attribute_instance instanceof Tool === false) {
// not a package attribute
continue;
}
@@ -216,6 +220,7 @@ class PackageLoader
Target::class => ['target', 'virtual-target'],
Library::class => ['library'],
Extension::class => ['php-extension'],
Tool::class => ['tool'],
default => null,
};
if (!in_array($package_type, $pkg_type_attr, true)) {
@@ -370,7 +375,10 @@ class PackageLoader
// match condition
$installer = ApplicationContext::get(PackageInstaller::class);
$stages = self::$before_stages[$package_name][$stage] ?? [];
foreach ($stages as [$callback, $only_when_package_resolved, $conditionals]) {
foreach ($stages as $entry) {
$callback = $entry[0];
$only_when_package_resolved = $entry[1] ?? null;
$conditionals = $entry[2] ?? [];
if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) {
continue;
}
@@ -389,7 +397,10 @@ class PackageLoader
$installer = ApplicationContext::get(PackageInstaller::class);
$stages = self::$after_stages[$package_name][$stage] ?? [];
$result = [];
foreach ($stages as [$callback, $only_when_package_resolved, $conditionals]) {
foreach ($stages as $entry) {
$callback = $entry[0];
$only_when_package_resolved = $entry[1] ?? null;
$conditionals = $entry[2] ?? [];
if ($only_when_package_resolved !== null && !$installer->isPackageResolved($only_when_package_resolved)) {
continue;
}
@@ -433,7 +444,9 @@ class PackageLoader
}
$pkg = self::getPackage($package_name);
foreach ($stages as $stage_name => $before_events) {
foreach ($before_events as [$event_callable, $only_when_package_resolved, $conditionals]) {
foreach ($before_events as $entry) {
$event_callable = $entry[0];
$only_when_package_resolved = $entry[1] ?? null;
// check only_when_package_resolved package exists
if ($only_when_package_resolved !== null && !self::hasPackage($only_when_package_resolved)) {
throw new RegistryException("{$event_name} event in package [{$package_name}] for stage [{$stage_name}] has unknown only_when_package_resolved package [{$only_when_package_resolved}].");

View File

@@ -5,12 +5,13 @@ declare(strict_types=1);
namespace StaticPHP\Runtime\Executor;
use StaticPHP\Package\LibraryPackage;
use StaticPHP\Package\ToolPackage;
abstract class Executor
{
public function __construct(protected LibraryPackage $package) {}
public function __construct(protected LibraryPackage|ToolPackage $package) {}
public static function create(LibraryPackage $package): static
public static function create(LibraryPackage|ToolPackage $package): static
{
return new static($package);
}

View File

@@ -10,6 +10,7 @@ use StaticPHP\Exception\SPCInternalException;
use StaticPHP\Package\LibraryPackage;
use StaticPHP\Package\PackageBuilder;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Runtime\Shell\UnixShell;
use StaticPHP\Util\InteractiveTerm;
use ZM\Logger\ConsoleColor;
@@ -22,7 +23,7 @@ class UnixAutoconfExecutor extends Executor
protected PackageInstaller $installer;
public function __construct(protected LibraryPackage $package, ?PackageInstaller $installer = null)
public function __construct(protected LibraryPackage|ToolPackage $package, ?PackageInstaller $installer = null)
{
parent::__construct($package);
if ($installer !== null) {
@@ -117,7 +118,7 @@ class UnixAutoconfExecutor extends Executor
/**
* Add configure args.
*/
public function addConfigureArgs(...$args): static
public function addConfigureArgs(string ...$args): static
{
$this->configure_args = [...$this->configure_args, ...$args];
return $this;
@@ -126,7 +127,7 @@ class UnixAutoconfExecutor extends Executor
/**
* Remove some configure args, to bypass the configure option checking for some libs.
*/
public function removeConfigureArgs(...$args): static
public function removeConfigureArgs(string ...$args): static
{
$this->configure_args = array_diff($this->configure_args, $args);
return $this;

View File

@@ -11,6 +11,7 @@ use StaticPHP\Package\LibraryPackage;
use StaticPHP\Package\PackageBuilder;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Runtime\Shell\UnixShell;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\FileSystem;
@@ -40,7 +41,7 @@ class UnixCMakeExecutor extends Executor
protected PackageInstaller $installer;
public function __construct(protected LibraryPackage $package, ?PackageInstaller $installer = null)
public function __construct(protected LibraryPackage|ToolPackage $package, ?PackageInstaller $installer = null)
{
parent::__construct($package);
if ($installer !== null) {
@@ -135,7 +136,7 @@ class UnixCMakeExecutor extends Executor
/**
* Add configure args.
*/
public function addConfigureArgs(...$args): static
public function addConfigureArgs(string ...$args): static
{
$this->configure_args = [...$this->configure_args, ...$args];
return $this;
@@ -144,7 +145,7 @@ class UnixCMakeExecutor extends Executor
/**
* Remove some configure args, to bypass the configure option checking for some libs.
*/
public function removeConfigureArgs(...$args): static
public function removeConfigureArgs(string ...$args): static
{
$this->ignore_args = [...$this->ignore_args, ...$args];
return $this;

View File

@@ -9,6 +9,7 @@ use StaticPHP\Exception\SPCInternalException;
use StaticPHP\Package\LibraryPackage;
use StaticPHP\Package\PackageBuilder;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Runtime\Shell\WindowsCmd;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\InteractiveTerm;
@@ -35,7 +36,7 @@ class WindowsCMakeExecutor extends Executor
protected PackageInstaller $installer;
public function __construct(protected LibraryPackage $package)
public function __construct(protected LibraryPackage|ToolPackage $package)
{
parent::__construct($this->package);
$this->builder = ApplicationContext::get(PackageBuilder::class);
@@ -99,7 +100,7 @@ class WindowsCMakeExecutor extends Executor
/**
* Add configure args.
*/
public function addConfigureArgs(...$args): static
public function addConfigureArgs(string ...$args): static
{
$this->configure_args = [...$this->configure_args, ...$args];
return $this;
@@ -108,7 +109,7 @@ class WindowsCMakeExecutor extends Executor
/**
* Remove some configure args, to bypass the configure option checking for some libs.
*/
public function removeConfigureArgs(...$args): static
public function removeConfigureArgs(string ...$args): static
{
$this->ignore_args = [...$this->ignore_args, ...$args];
return $this;

View File

@@ -185,12 +185,14 @@ class DefaultShell extends Shell
*/
public function execute7zExtract(string $archive_path, string $target_path): bool
{
$sdk_path = getenv('PHP_SDK_PATH');
if ($sdk_path === false) {
throw new SPCInternalException('PHP_SDK_PATH environment variable is not set');
// 7za.exe is installed by the 7za-win target package into PKG_ROOT_PATH\bin,
// which is added to PATH by MSVCToolchain::initEnv().
$_7z_path = FileSystem::convertPath(PKG_ROOT_PATH . '\bin\7za.exe');
if (!file_exists($_7z_path)) {
throw new SPCInternalException('7za.exe not found. Please install the 7za-win target package.');
}
$_7z = escapeshellarg(FileSystem::convertPath($sdk_path . '/bin/7za.exe'));
$_7z = escapeshellarg(FileSystem::convertPath($_7z_path));
$archive_arg = escapeshellarg(FileSystem::convertPath($archive_path));
$target_arg = escapeshellarg(FileSystem::convertPath($target_path));

View File

@@ -6,6 +6,7 @@ namespace StaticPHP\Runtime\Shell;
use StaticPHP\Exception\SPCInternalException;
use StaticPHP\Package\LibraryPackage;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Runtime\SystemTarget;
use ZM\Logger\ConsoleColor;
@@ -40,9 +41,9 @@ class UnixShell extends Shell
/**
* Init the environment variable that common build will be used.
*
* @param LibraryPackage $library Library package
* @param LibraryPackage|ToolPackage $library Library or tool package
*/
public function initializeEnv(LibraryPackage $library): UnixShell
public function initializeEnv(LibraryPackage|ToolPackage $library): UnixShell
{
$this->setEnv([
'CFLAGS' => $library->getLibExtraCFlags(),

View File

@@ -14,10 +14,14 @@ class MSVCToolchain implements ToolchainInterface
public function initEnv(): void
{
GlobalEnvManager::addPathIfNotExists(PKG_ROOT_PATH . '\bin');
$sdk = getenv('PHP_SDK_PATH');
if ($sdk !== false) {
GlobalEnvManager::addPathIfNotExists($sdk . '\bin');
GlobalEnvManager::addPathIfNotExists($sdk . '\msys2\usr\bin');
// msys2-build-essentials: add MSYS2 usr\bin to PATH so that 7za.exe, make, autoconf, etc. are available.
// This must be done here because msys2-build-essentials is not a dependency of any library package,
// so its path@windows entries are not automatically applied by the package installer at runtime.
$msys2_path = getenv('SPC_MSYS2_PATH') ?: (PKG_ROOT_PATH . '\msys2-build-essentials\msys64');
if (is_dir($msys2_path)) {
GlobalEnvManager::putenv("SPC_MSYS2_PATH={$msys2_path}");
GlobalEnvManager::addPathIfNotExists($msys2_path . '\usr\bin');
GlobalEnvManager::addPathIfNotExists("{$msys2_path}\\usr\\lib\\p7zip");
}
// strawberry-perl
if (is_dir(PKG_ROOT_PATH . '\strawberry-perl')) {

View File

@@ -174,7 +174,7 @@ class FileSystem
public static function convertWinPathToMinGW(string $path): string
{
if (preg_match('/^[A-Za-z]:/', $path)) {
$path = '/' . strtolower($path[0]) . '/' . str_replace('\\', '/', substr($path, 2));
$path = '/' . strtolower($path[0]) . '/' . str_replace('\\', '/', ltrim(substr($path, 2), '\/'));
}
return $path;
}
@@ -411,7 +411,7 @@ class FileSystem
$replacement = [
'{build_root_path}' => BUILD_ROOT_PATH,
'{pkg_root_path}' => PKG_ROOT_PATH,
'{php_sdk_path}' => getenv('PHP_SDK_PATH') ? getenv('PHP_SDK_PATH') : WORKING_DIR . '/php-sdk-binary-tools',
'{spc_msys2_path}' => getenv('SPC_MSYS2_PATH') ?: (PKG_ROOT_PATH . DIRECTORY_SEPARATOR . 'msys2-build-essentials' . DIRECTORY_SEPARATOR . 'msys64'),
'{working_dir}' => WORKING_DIR,
'{download_path}' => DOWNLOAD_PATH,
'{source_path}' => SOURCE_PATH,

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace StaticPHP\Util;
use StaticPHP\DI\ApplicationContext;
use Symfony\Component\Console\Helper\ProgressIndicator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
@@ -15,10 +14,31 @@ class InteractiveTerm
{
private static ?ProgressIndicator $indicator = null;
private static ?OutputInterface $output = null;
private static ?bool $noAnsi = null;
/**
* Initialize with a real Symfony Console input/output (called from ConsoleApplication::doRun()).
* After this call, all output goes through the configured Console, and noAnsi reflects
* the user's --no-ansi flag.
*/
public static function init(InputInterface $input, OutputInterface $output): void
{
self::$output = $output;
try {
self::$noAnsi = (bool) $input->getOption('no-ansi');
} catch (\InvalidArgumentException) {
// Symfony hasn't bound the application-level input definition yet
// (e.g. ArgvInput passed directly to doRun() on some versions).
self::$noAnsi = false;
}
}
public static function notice(string $message, bool $indent = false): void
{
$no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false;
$output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput();
$no_ansi = self::noAnsi();
$output = self::output();
if ($output->isVerbose()) {
logger()->notice(strip_ansi_colors($message));
} else {
@@ -29,8 +49,8 @@ class InteractiveTerm
public static function success(string $message, bool $indent = false): void
{
$no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false;
$output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput();
$no_ansi = self::noAnsi();
$output = self::output();
if ($output->isVerbose()) {
logger()->info(strip_ansi_colors($message));
} else {
@@ -41,8 +61,8 @@ class InteractiveTerm
public static function plain(string $message, string $level = 'info'): void
{
$no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false;
$output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput();
$no_ansi = self::noAnsi();
$output = self::output();
if ($output->isVerbose()) {
match ($level) {
'debug' => logger()->debug(strip_ansi_colors($message)),
@@ -59,8 +79,8 @@ class InteractiveTerm
public static function info(string $message): void
{
$no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false;
$output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput();
$no_ansi = self::noAnsi();
$output = self::output();
if (!$output->isVerbose()) {
$output->writeln(($no_ansi ? 'strip_ansi_colors' : 'strval')(ConsoleColor::green('▶ ') . $message));
}
@@ -69,8 +89,8 @@ class InteractiveTerm
public static function error(string $message, bool $indent = true): void
{
$no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false;
$output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput();
$no_ansi = self::noAnsi();
$output = self::output();
if ($output->isVerbose()) {
logger()->error(strip_ansi_colors($message));
} else {
@@ -86,16 +106,16 @@ class InteractiveTerm
public static function setMessage(string $message): void
{
$no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false;
$no_ansi = self::noAnsi();
self::$indicator?->setMessage(($no_ansi ? 'strip_ansi_colors' : 'strval')($message));
logger()->debug(strip_ansi_colors($message));
}
public static function finish(string $message, bool $status = true): void
{
$no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false;
$no_ansi = self::noAnsi();
$message = $no_ansi ? strip_ansi_colors($message) : $message;
$output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput();
$output = self::output();
if ($output->isVerbose()) {
if ($status) {
logger()->info($message);
@@ -116,8 +136,8 @@ class InteractiveTerm
public static function indicateProgress(string $message): void
{
$no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false;
$output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput();
$no_ansi = self::noAnsi();
$output = self::output();
if ($output->isVerbose()) {
logger()->info(strip_ansi_colors($message));
return;
@@ -131,11 +151,41 @@ class InteractiveTerm
logger()->debug(strip_ansi_colors($message));
// if no ansi, use a dot instead of spinner
if ($no_ansi) {
self::$indicator = new ProgressIndicator(ApplicationContext::get(OutputInterface::class), 'verbose', 100, [' •', ' •']);
self::$indicator = new ProgressIndicator(self::output(), 'verbose', 100, [' •', ' •']);
self::$indicator->start(strip_ansi_colors($message));
return;
}
self::$indicator = new ProgressIndicator(ApplicationContext::get(OutputInterface::class), 'verbose', 100, [' ⠏', ' ⠛', ' ⠹', ' ⢸', ' ⣰', ' ⣤', ' ⣆', ' ⡇']);
self::$indicator = new ProgressIndicator(self::output(), 'verbose', 100, [' ⠏', ' ⠛', ' ⠹', ' ⢸', ' ⣰', ' ⣤', ' ⣆', ' ⡇']);
self::$indicator->start($message);
}
/**
* Lazy default initialization used when init() was never called (early-boot errors,
* tests, programmatic usage). Creates a plain STDERR output so error messages are
* visible without depending on the Symfony Console lifecycle.
*/
private static function initDefault(): void
{
if (self::$output !== null) {
return;
}
self::$output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, false);
self::$noAnsi = false;
}
private static function noAnsi(): bool
{
if (self::$output === null) {
self::initDefault();
}
return self::$noAnsi ?? false;
}
private static function output(): OutputInterface
{
if (self::$output === null) {
self::initDefault();
}
return self::$output;
}
}

View File

@@ -45,12 +45,16 @@ class SourcePatcher
file_put_contents(SOURCE_PATH . "/{$filename}", file_get_contents($patch_file));
$patch_str = FileSystem::convertPath(SOURCE_PATH . "/{$filename}");
}
$patch_cwd = FileSystem::convertPath($cwd);
$patch_arg = $patch_str;
// Detect if patch is already applied (reverse detection)
$detect_reverse = !$reverse;
$detect_cmd = 'cd ' . escapeshellarg($cwd) . ' && '
. (PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . escapeshellarg($patch_str)
. ' | patch --dry-run -p1 -s -f ' . ($detect_reverse ? '-R' : '')
$cd_cmd = (PHP_OS_FAMILY === 'Windows' ? 'cd /d ' : 'cd ') . escapeshellarg($patch_cwd);
$detect_cmd = $cd_cmd
. ' && patch --binary --dry-run -p1 -s -f'
. ($detect_reverse ? ' -R' : '')
. ' < ' . escapeshellarg($patch_arg)
. ' > ' . (PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null') . ' 2>&1';
exec($detect_cmd, $output, $detect_status);
@@ -60,9 +64,10 @@ class SourcePatcher
}
// Apply patch
$apply_cmd = 'cd ' . escapeshellarg($cwd) . ' && '
. (PHP_OS_FAMILY === 'Windows' ? 'type' : 'cat') . ' ' . escapeshellarg($patch_str)
. ' | patch -p1 ' . ($reverse ? '-R' : '');
$apply_cmd = $cd_cmd
. ' && patch --binary -p1'
. ($reverse ? ' -R' : '')
. ' < ' . escapeshellarg($patch_arg);
exec($apply_cmd, $apply_output, $apply_status);
if ($apply_status !== 0) {

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE spc_column_metadata_test (id INTEGER)');
$stmt = $pdo->query('SELECT id FROM spc_column_metadata_test');
if ($stmt === false) {
throw new RuntimeException('Failed to query SQLite metadata test table.');
}
$metadata = $stmt->getColumnMeta(0);
if (($metadata['table'] ?? null) !== 'spc_column_metadata_test') {
throw new RuntimeException('PDO SQLite column metadata does not include the origin table.');
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
$sqlite = new SQLite3(':memory:');
$enabled = $sqlite->querySingle("SELECT sqlite_compileoption_used('ENABLE_COLUMN_METADATA')");
if ((int) $enabled !== 1) {
throw new RuntimeException('SQLite was not built with SQLITE_ENABLE_COLUMN_METADATA.');
}

View File

@@ -1,69 +1,69 @@
CC=cl.exe /nologo
AR=lib.exe /nologo
LINK=link.exe /nologo
!IF "" == "$(MACHINE)"
MACHINE=x64
!ENDIF
!IF "" == "$(CRT)"
CRT=vc15
!ENDIF
!IF "" == "$(PREFIX)"
PREFIX="$(CRT)-$(MACHINE)"
!ENDIF
COMMON_CFLAGS=/D SQLITE_THREADSAFE=1 /DSQLITE_ENABLE_FTS3=1 /D SQLITE_ENABLE_FTS4=1 /D SQLITE_ENABLE_FTS5=1 /D SQLITE_ENABLE_JSON1=1 /D SQLITE_ENABLE_COLUMN_METADATA=1 /D SQLITE_CORE=1
!IF "$(DEBUG)"=="1"
SQLITE3_STATIC_BASE=libsqlite3_a_debug
SQLITE3_DLL_BASE=libsqlite3_debug
SQLITE3_EXE_BASE=sqlite3
CFLAGS=$(COMMON_CFLAGS) /Zi /MDd /Od /W3
LDFLAGS=/DEBUG /GUARD:CF /INCREMENTAL:NO
!ELSE
SQLITE3_STATIC_BASE=libsqlite3_a
SQLITE3_DLL_BASE=libsqlite3
SQLITE3_EXE_BASE=sqlite3
CFLAGS=$(COMMON_CFLAGS) /Zi /MT /guard:cf /Zc:inline /Qspectre /Ox /W3 /GF /Gw
LDFLAGS=/GUARD:CF /INCREMENTAL:NO /NXCOMPAT /DYNAMICBASE
!ENDIF
all: $(SQLITE3_STATIC_BASE).lib $(SQLITE3_EXE_BASE).exe $(SQLITE3_DLL_BASE).dll
install: all
if not exist $(PREFIX)\bin mkdir $(PREFIX)\bin
if not exist $(PREFIX)\include mkdir $(PREFIX)\include
if not exist $(PREFIX)\lib mkdir $(PREFIX)\lib
copy /Y sqlite3.h $(PREFIX)\include
copy /Y sqlite3ext.h $(PREFIX)\include
copy /Y $(SQLITE3_STATIC_BASE).lib $(PREFIX)\lib
copy /Y $(SQLITE3_STATIC_BASE).pdb $(PREFIX)\lib
copy /Y $(SQLITE3_DLL_BASE).lib $(PREFIX)\lib
copy /Y $(SQLITE3_DLL_BASE).pdb $(PREFIX)\bin
copy /Y $(SQLITE3_DLL_BASE).dll $(PREFIX)\bin
copy /Y $(SQLITE3_EXE_BASE).exe $(PREFIX)\bin
copy /Y $(SQLITE3_EXE_BASE).pdb $(PREFIX)\bin
install-static: $(SQLITE3_STATIC_BASE).lib
if not exist $(PREFIX)\include mkdir $(PREFIX)\include
if not exist $(PREFIX)\lib mkdir $(PREFIX)\lib
copy /Y sqlite3.h $(PREFIX)\include
copy /Y sqlite3ext.h $(PREFIX)\include
copy /Y $(SQLITE3_STATIC_BASE).lib $(PREFIX)\lib
clean:
del *.obj *.lib *.exe *.pdb *.dll *.exp
$(SQLITE3_STATIC_BASE).lib: sqlite3.c sqlite3.h
$(CC) $(CFLAGS) /Fd$(SQLITE3_STATIC_BASE).pdb /c sqlite3.c
$(AR) sqlite3.obj /OUT:$(SQLITE3_STATIC_BASE).lib
$(SQLITE3_EXE_BASE).exe: shell.c sqlite3.c sqlite3.h
$(CC) $(CFLAGS) shell.c sqlite3.c /Fd$(SQLITE3_EXE_BASE).pdb /Fe$(SQLITE3_EXE_BASE).exe
$(SQLITE3_DLL_BASE).dll: sqlite3.c sqlite3.h
$(CC) $(CFLAGS) /DSQLITE_API=__declspec(dllexport) /Fd$(SQLITE3_DLL_BASE).pdb /c sqlite3.c
CC=cl.exe /nologo
AR=lib.exe /nologo
LINK=link.exe /nologo
!IF "" == "$(MACHINE)"
MACHINE=x64
!ENDIF
!IF "" == "$(CRT)"
CRT=vc15
!ENDIF
!IF "" == "$(PREFIX)"
PREFIX="$(CRT)-$(MACHINE)"
!ENDIF
COMMON_CFLAGS=/D SQLITE_THREADSAFE=1 /DSQLITE_ENABLE_FTS3=1 /D SQLITE_ENABLE_FTS4=1 /D SQLITE_ENABLE_FTS5=1 /D SQLITE_ENABLE_JSON1=1 /D SQLITE_ENABLE_COLUMN_METADATA=1 /D SQLITE_CORE=1
!IF "$(DEBUG)"=="1"
SQLITE3_STATIC_BASE=libsqlite3_a_debug
SQLITE3_DLL_BASE=libsqlite3_debug
SQLITE3_EXE_BASE=sqlite3
CFLAGS=$(COMMON_CFLAGS) /Zi /MDd /Od /W3
LDFLAGS=/DEBUG /GUARD:CF /INCREMENTAL:NO
!ELSE
SQLITE3_STATIC_BASE=libsqlite3_a
SQLITE3_DLL_BASE=libsqlite3
SQLITE3_EXE_BASE=sqlite3
CFLAGS=$(COMMON_CFLAGS) /Zi /MT /guard:cf /Zc:inline /Qspectre /Ox /W3 /GF /Gw
LDFLAGS=/GUARD:CF /INCREMENTAL:NO /NXCOMPAT /DYNAMICBASE
!ENDIF
all: $(SQLITE3_STATIC_BASE).lib $(SQLITE3_EXE_BASE).exe $(SQLITE3_DLL_BASE).dll
install: all
if not exist $(PREFIX)\bin mkdir $(PREFIX)\bin
if not exist $(PREFIX)\include mkdir $(PREFIX)\include
if not exist $(PREFIX)\lib mkdir $(PREFIX)\lib
copy /Y sqlite3.h $(PREFIX)\include
copy /Y sqlite3ext.h $(PREFIX)\include
copy /Y $(SQLITE3_STATIC_BASE).lib $(PREFIX)\lib
copy /Y $(SQLITE3_STATIC_BASE).pdb $(PREFIX)\lib
copy /Y $(SQLITE3_DLL_BASE).lib $(PREFIX)\lib
copy /Y $(SQLITE3_DLL_BASE).pdb $(PREFIX)\bin
copy /Y $(SQLITE3_DLL_BASE).dll $(PREFIX)\bin
copy /Y $(SQLITE3_EXE_BASE).exe $(PREFIX)\bin
copy /Y $(SQLITE3_EXE_BASE).pdb $(PREFIX)\bin
install-static: $(SQLITE3_STATIC_BASE).lib
if not exist $(PREFIX)\include mkdir $(PREFIX)\include
if not exist $(PREFIX)\lib mkdir $(PREFIX)\lib
copy /Y sqlite3.h $(PREFIX)\include
copy /Y sqlite3ext.h $(PREFIX)\include
copy /Y $(SQLITE3_STATIC_BASE).lib $(PREFIX)\lib
clean:
del *.obj *.lib *.exe *.pdb *.dll *.exp
$(SQLITE3_STATIC_BASE).lib: sqlite3.c sqlite3.h
$(CC) $(CFLAGS) /Fd$(SQLITE3_STATIC_BASE).pdb /c sqlite3.c
$(AR) sqlite3.obj /OUT:$(SQLITE3_STATIC_BASE).lib
$(SQLITE3_EXE_BASE).exe: shell.c sqlite3.c sqlite3.h
$(CC) $(CFLAGS) shell.c sqlite3.c /Fd$(SQLITE3_EXE_BASE).pdb /Fe$(SQLITE3_EXE_BASE).exe
$(SQLITE3_DLL_BASE).dll: sqlite3.c sqlite3.h
$(CC) $(CFLAGS) /DSQLITE_API=__declspec(dllexport) /Fd$(SQLITE3_DLL_BASE).pdb /c sqlite3.c
$(LINK) /DLL /OUT:$(SQLITE3_DLL_BASE).dll sqlite3.obj

View File

@@ -1,86 +1,86 @@
// vim:ft=javascript
ARG_WITH("gd", "Bundled GD support", "yes");
if (PHP_GD != "no") {
// check for gd.h (required)
if (!CHECK_HEADER_ADD_INCLUDE("gd.h", "CFLAGS_GD", PHP_GD + ";ext\\gd\\libgd")) {
ERROR("gd not enabled; libraries and headers not found");
}
// zlib ext support (required)
if (!CHECK_LIB("zlib_a.lib;zlib.lib", "gd", PHP_GD)) {
ERROR("gd not enabled; zlib not enabled");
}
// libjpeg lib support
if (CHECK_LIB("libjpeg_a.lib;libjpeg.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("jpeglib.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include")) {
AC_DEFINE("HAVE_LIBJPEG", 1, "JPEG support");
AC_DEFINE("HAVE_GD_JPG", 1, "JPEG support");
}
// libpng16 lib support
if (CHECK_LIB("libpng_a.lib;libpng.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("png.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\libpng16")) {
AC_DEFINE("HAVE_LIBPNG", 1, "PNG support");
AC_DEFINE("HAVE_GD_PNG", 1, "PNG support");
}
// freetype lib support
if (CHECK_LIB("libfreetype_a.lib;libfreetype.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("ft2build.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\freetype2;" + PHP_PHP_BUILD + "\\include\\freetype")) {
AC_DEFINE("HAVE_LIBFREETYPE", 1, "FreeType support");
AC_DEFINE("HAVE_GD_FREETYPE", 1, "FreeType support");
}
// xpm lib support
if (CHECK_LIB("libXpm_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("xpm.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\X11")) {
AC_DEFINE("HAVE_LIBXPM", 1, "XPM support");
AC_DEFINE("HAVE_GD_XPM", 1, "XPM support");
}
// iconv lib support
if ((CHECK_LIB("libiconv_a.lib;libiconv.lib", "gd", PHP_GD) || CHECK_LIB("iconv_a.lib;iconv.lib", "gd", PHP_GD)) &&
CHECK_HEADER_ADD_INCLUDE("iconv.h", "CFLAGS_GD", PHP_GD)) {
AC_DEFINE("HAVE_LIBICONV", 1, "Iconv support");
}
// libwebp lib support
if ((CHECK_LIB("libwebp_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebp.lib", "gd", PHP_GD)) &&
CHECK_LIB("libsharpyuv.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("decode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") &&
CHECK_HEADER_ADD_INCLUDE("encode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp")) {
AC_DEFINE("HAVE_LIBWEBP", 1, "WebP support");
AC_DEFINE("HAVE_GD_WEBP", 1, "WebP support");
}
// libavif lib is not supported on php <= 8.0
CHECK_LIB("User32.lib", "gd", PHP_GD);
CHECK_LIB("Gdi32.lib", "gd", PHP_GD);
EXTENSION("gd", "gd.c", null, "-Iext/gd/libgd");
ADD_SOURCES("ext/gd/libgd", "gd.c \
gdcache.c gdfontg.c gdfontl.c gdfontmb.c gdfonts.c gdfontt.c \
gdft.c gd_gd2.c gd_gd.c gd_gif_in.c gd_gif_out.c gdhelpers.c gd_io.c gd_io_dp.c \
gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_png.c gd_ss.c \
gdtables.c gd_topal.c gd_wbmp.c gdxpm.c wbmp.c gd_xbm.c gd_security.c gd_transform.c \
gd_filter.c gd_pixelate.c gd_rotate.c gd_color_match.c gd_webp.c \
gd_crop.c gd_interpolation.c gd_matrix.c gd_bmp.c gd_tga.c", "gd");
AC_DEFINE('HAVE_LIBGD', 1, 'GD support');
AC_DEFINE('HAVE_GD_BUNDLED', 1, "Bundled GD");
AC_DEFINE('HAVE_GD_BMP', 1, "BMP support");
AC_DEFINE('HAVE_GD_TGA', 1, "TGA support");
ADD_FLAG("CFLAGS_GD", " \
/D PHP_GD_EXPORTS=1 \
/D HAVE_GD_GET_INTERPOLATION \
");
if (ICC_TOOLSET) {
ADD_FLAG("LDFLAGS_GD", "/nodefaultlib:libcmt");
}
PHP_INSTALL_HEADERS("", "ext/gd ext/gd/libgd");
}
// vim:ft=javascript
ARG_WITH("gd", "Bundled GD support", "yes");
if (PHP_GD != "no") {
// check for gd.h (required)
if (!CHECK_HEADER_ADD_INCLUDE("gd.h", "CFLAGS_GD", PHP_GD + ";ext\\gd\\libgd")) {
ERROR("gd not enabled; libraries and headers not found");
}
// zlib ext support (required)
if (!CHECK_LIB("zlib_a.lib;zlib.lib", "gd", PHP_GD)) {
ERROR("gd not enabled; zlib not enabled");
}
// libjpeg lib support
if (CHECK_LIB("libjpeg_a.lib;libjpeg.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("jpeglib.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include")) {
AC_DEFINE("HAVE_LIBJPEG", 1, "JPEG support");
AC_DEFINE("HAVE_GD_JPG", 1, "JPEG support");
}
// libpng16 lib support
if (CHECK_LIB("libpng_a.lib;libpng.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("png.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\libpng16")) {
AC_DEFINE("HAVE_LIBPNG", 1, "PNG support");
AC_DEFINE("HAVE_GD_PNG", 1, "PNG support");
}
// freetype lib support
if (CHECK_LIB("libfreetype_a.lib;libfreetype.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("ft2build.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\freetype2;" + PHP_PHP_BUILD + "\\include\\freetype")) {
AC_DEFINE("HAVE_LIBFREETYPE", 1, "FreeType support");
AC_DEFINE("HAVE_GD_FREETYPE", 1, "FreeType support");
}
// xpm lib support
if (CHECK_LIB("libXpm_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("xpm.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\X11")) {
AC_DEFINE("HAVE_LIBXPM", 1, "XPM support");
AC_DEFINE("HAVE_GD_XPM", 1, "XPM support");
}
// iconv lib support
if ((CHECK_LIB("libiconv_a.lib;libiconv.lib", "gd", PHP_GD) || CHECK_LIB("iconv_a.lib;iconv.lib", "gd", PHP_GD)) &&
CHECK_HEADER_ADD_INCLUDE("iconv.h", "CFLAGS_GD", PHP_GD)) {
AC_DEFINE("HAVE_LIBICONV", 1, "Iconv support");
}
// libwebp lib support
if ((CHECK_LIB("libwebp_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebp.lib", "gd", PHP_GD)) &&
CHECK_LIB("libsharpyuv.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("decode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") &&
CHECK_HEADER_ADD_INCLUDE("encode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp")) {
AC_DEFINE("HAVE_LIBWEBP", 1, "WebP support");
AC_DEFINE("HAVE_GD_WEBP", 1, "WebP support");
}
// libavif lib is not supported on php <= 8.0
CHECK_LIB("User32.lib", "gd", PHP_GD);
CHECK_LIB("Gdi32.lib", "gd", PHP_GD);
EXTENSION("gd", "gd.c", null, "-Iext/gd/libgd");
ADD_SOURCES("ext/gd/libgd", "gd.c \
gdcache.c gdfontg.c gdfontl.c gdfontmb.c gdfonts.c gdfontt.c \
gdft.c gd_gd2.c gd_gd.c gd_gif_in.c gd_gif_out.c gdhelpers.c gd_io.c gd_io_dp.c \
gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_png.c gd_ss.c \
gdtables.c gd_topal.c gd_wbmp.c gdxpm.c wbmp.c gd_xbm.c gd_security.c gd_transform.c \
gd_filter.c gd_pixelate.c gd_rotate.c gd_color_match.c gd_webp.c \
gd_crop.c gd_interpolation.c gd_matrix.c gd_bmp.c gd_tga.c", "gd");
AC_DEFINE('HAVE_LIBGD', 1, 'GD support');
AC_DEFINE('HAVE_GD_BUNDLED', 1, "Bundled GD");
AC_DEFINE('HAVE_GD_BMP', 1, "BMP support");
AC_DEFINE('HAVE_GD_TGA', 1, "TGA support");
ADD_FLAG("CFLAGS_GD", " \
/D PHP_GD_EXPORTS=1 \
/D HAVE_GD_GET_INTERPOLATION \
");
if (ICC_TOOLSET) {
ADD_FLAG("LDFLAGS_GD", "/nodefaultlib:libcmt");
}
PHP_INSTALL_HEADERS("", "ext/gd ext/gd/libgd");
}

View File

@@ -1,94 +1,94 @@
// vim:ft=javascript
ARG_WITH("gd", "Bundled GD support", "yes");
if (PHP_GD != "no") {
// check for gd.h (required)
if (!CHECK_HEADER_ADD_INCLUDE("gd.h", "CFLAGS_GD", PHP_GD + ";ext\\gd\\libgd")) {
ERROR("gd not enabled; libraries and headers not found");
}
// zlib ext support (required)
if (!CHECK_LIB("zlib_a.lib;zlib.lib", "gd", PHP_GD)) {
ERROR("gd not enabled; zlib not enabled");
}
// libjpeg lib support
if (CHECK_LIB("libjpeg_a.lib;libjpeg.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("jpeglib.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include")) {
AC_DEFINE("HAVE_LIBJPEG", 1, "JPEG support");
AC_DEFINE("HAVE_GD_JPG", 1, "JPEG support");
}
// libpng16 lib support
if (CHECK_LIB("libpng_a.lib;libpng.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("png.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\libpng16")) {
AC_DEFINE("HAVE_LIBPNG", 1, "PNG support");
AC_DEFINE("HAVE_GD_PNG", 1, "PNG support");
}
// freetype lib support
if (CHECK_LIB("libfreetype_a.lib;libfreetype.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("ft2build.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\freetype2;" + PHP_PHP_BUILD + "\\include\\freetype")) {
AC_DEFINE("HAVE_LIBFREETYPE", 1, "FreeType support");
AC_DEFINE("HAVE_GD_FREETYPE", 1, "FreeType support");
}
// xpm lib support
if (CHECK_LIB("libXpm_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("xpm.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\X11")) {
AC_DEFINE("HAVE_LIBXPM", 1, "XPM support");
AC_DEFINE("HAVE_GD_XPM", 1, "XPM support");
}
// iconv lib support
if ((CHECK_LIB("libiconv_a.lib;libiconv.lib", "gd", PHP_GD) || CHECK_LIB("iconv_a.lib;iconv.lib", "gd", PHP_GD)) &&
CHECK_HEADER_ADD_INCLUDE("iconv.h", "CFLAGS_GD", PHP_GD)) {
AC_DEFINE("HAVE_LIBICONV", 1, "Iconv support");
}
// libwebp lib support
if ((CHECK_LIB("libwebp_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebp.lib", "gd", PHP_GD)) &&
CHECK_LIB("libsharpyuv.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("decode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") &&
CHECK_HEADER_ADD_INCLUDE("encode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp")) {
AC_DEFINE("HAVE_LIBWEBP", 1, "WebP support");
AC_DEFINE("HAVE_GD_WEBP", 1, "WebP support");
}
// libavif lib support
if (CHECK_LIB("avif_a.lib", "gd", PHP_GD) &&
CHECK_LIB("aom_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("avif.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\avif")) {
ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBAVIF /D HAVE_GD_AVIF");
} else if (CHECK_LIB("avif.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("avif.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\avif")) {
ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBAVIF /D HAVE_GD_AVIF");
}
CHECK_LIB("User32.lib", "gd", PHP_GD);
CHECK_LIB("Gdi32.lib", "gd", PHP_GD);
EXTENSION("gd", "gd.c", null, "-Iext/gd/libgd");
ADD_SOURCES("ext/gd/libgd", "gd.c \
gdcache.c gdfontg.c gdfontl.c gdfontmb.c gdfonts.c gdfontt.c \
gdft.c gd_gd2.c gd_gd.c gd_gif_in.c gd_gif_out.c gdhelpers.c gd_io.c gd_io_dp.c \
gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_png.c gd_ss.c \
gdtables.c gd_topal.c gd_wbmp.c gdxpm.c wbmp.c gd_xbm.c gd_security.c gd_transform.c \
gd_filter.c gd_pixelate.c gd_rotate.c gd_color_match.c gd_webp.c gd_avif.c \
gd_crop.c gd_interpolation.c gd_matrix.c gd_bmp.c gd_tga.c", "gd");
AC_DEFINE('HAVE_LIBGD', 1, 'GD support');
AC_DEFINE('HAVE_GD_BUNDLED', 1, "Bundled GD");
AC_DEFINE('HAVE_GD_BMP', 1, "BMP support");
AC_DEFINE('HAVE_GD_TGA', 1, "TGA support");
ADD_FLAG("CFLAGS_GD", " \
/D PHP_GD_EXPORTS=1 \
/D HAVE_GD_GET_INTERPOLATION \
");
if (ICC_TOOLSET) {
ADD_FLAG("LDFLAGS_GD", "/nodefaultlib:libcmt");
}
PHP_INSTALL_HEADERS("", "ext/gd ext/gd/libgd");
}
// vim:ft=javascript
ARG_WITH("gd", "Bundled GD support", "yes");
if (PHP_GD != "no") {
// check for gd.h (required)
if (!CHECK_HEADER_ADD_INCLUDE("gd.h", "CFLAGS_GD", PHP_GD + ";ext\\gd\\libgd")) {
ERROR("gd not enabled; libraries and headers not found");
}
// zlib ext support (required)
if (!CHECK_LIB("zlib_a.lib;zlib.lib", "gd", PHP_GD)) {
ERROR("gd not enabled; zlib not enabled");
}
// libjpeg lib support
if (CHECK_LIB("libjpeg_a.lib;libjpeg.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("jpeglib.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include")) {
AC_DEFINE("HAVE_LIBJPEG", 1, "JPEG support");
AC_DEFINE("HAVE_GD_JPG", 1, "JPEG support");
}
// libpng16 lib support
if (CHECK_LIB("libpng_a.lib;libpng.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("png.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\libpng16")) {
AC_DEFINE("HAVE_LIBPNG", 1, "PNG support");
AC_DEFINE("HAVE_GD_PNG", 1, "PNG support");
}
// freetype lib support
if (CHECK_LIB("libfreetype_a.lib;libfreetype.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("ft2build.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\freetype2;" + PHP_PHP_BUILD + "\\include\\freetype")) {
AC_DEFINE("HAVE_LIBFREETYPE", 1, "FreeType support");
AC_DEFINE("HAVE_GD_FREETYPE", 1, "FreeType support");
}
// xpm lib support
if (CHECK_LIB("libXpm_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("xpm.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\X11")) {
AC_DEFINE("HAVE_LIBXPM", 1, "XPM support");
AC_DEFINE("HAVE_GD_XPM", 1, "XPM support");
}
// iconv lib support
if ((CHECK_LIB("libiconv_a.lib;libiconv.lib", "gd", PHP_GD) || CHECK_LIB("iconv_a.lib;iconv.lib", "gd", PHP_GD)) &&
CHECK_HEADER_ADD_INCLUDE("iconv.h", "CFLAGS_GD", PHP_GD)) {
AC_DEFINE("HAVE_LIBICONV", 1, "Iconv support");
}
// libwebp lib support
if ((CHECK_LIB("libwebp_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebp.lib", "gd", PHP_GD)) &&
CHECK_LIB("libsharpyuv.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("decode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") &&
CHECK_HEADER_ADD_INCLUDE("encode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp")) {
AC_DEFINE("HAVE_LIBWEBP", 1, "WebP support");
AC_DEFINE("HAVE_GD_WEBP", 1, "WebP support");
}
// libavif lib support
if (CHECK_LIB("avif_a.lib", "gd", PHP_GD) &&
CHECK_LIB("aom_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("avif.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\avif")) {
ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBAVIF /D HAVE_GD_AVIF");
} else if (CHECK_LIB("avif.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("avif.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\avif")) {
ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBAVIF /D HAVE_GD_AVIF");
}
CHECK_LIB("User32.lib", "gd", PHP_GD);
CHECK_LIB("Gdi32.lib", "gd", PHP_GD);
EXTENSION("gd", "gd.c", null, "-Iext/gd/libgd");
ADD_SOURCES("ext/gd/libgd", "gd.c \
gdcache.c gdfontg.c gdfontl.c gdfontmb.c gdfonts.c gdfontt.c \
gdft.c gd_gd2.c gd_gd.c gd_gif_in.c gd_gif_out.c gdhelpers.c gd_io.c gd_io_dp.c \
gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_png.c gd_ss.c \
gdtables.c gd_topal.c gd_wbmp.c gdxpm.c wbmp.c gd_xbm.c gd_security.c gd_transform.c \
gd_filter.c gd_pixelate.c gd_rotate.c gd_color_match.c gd_webp.c gd_avif.c \
gd_crop.c gd_interpolation.c gd_matrix.c gd_bmp.c gd_tga.c", "gd");
AC_DEFINE('HAVE_LIBGD', 1, 'GD support');
AC_DEFINE('HAVE_GD_BUNDLED', 1, "Bundled GD");
AC_DEFINE('HAVE_GD_BMP', 1, "BMP support");
AC_DEFINE('HAVE_GD_TGA', 1, "TGA support");
ADD_FLAG("CFLAGS_GD", " \
/D PHP_GD_EXPORTS=1 \
/D HAVE_GD_GET_INTERPOLATION \
");
if (ICC_TOOLSET) {
ADD_FLAG("LDFLAGS_GD", "/nodefaultlib:libcmt");
}
PHP_INSTALL_HEADERS("", "ext/gd ext/gd/libgd");
}

View File

@@ -1,94 +1,94 @@
// vim:ft=javascript
ARG_WITH("gd", "Bundled GD support", "yes");
if (PHP_GD != "no") {
// check for gd.h (required)
if (!CHECK_HEADER_ADD_INCLUDE("gd.h", "CFLAGS_GD", PHP_GD + ";ext\\gd\\libgd")) {
ERROR("gd not enabled; libraries and headers not found");
}
// zlib ext support (required)
if (!CHECK_LIB("zlib_a.lib;zlib.lib", "gd", PHP_GD)) {
ERROR("gd not enabled; zlib not enabled");
}
// libjpeg lib support
if (CHECK_LIB("libjpeg_a.lib;libjpeg.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("jpeglib.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include")) {
AC_DEFINE("HAVE_LIBJPEG", 1, "JPEG support");
AC_DEFINE("HAVE_GD_JPG", 1, "JPEG support");
}
// libpng16 lib support
if (CHECK_LIB("libpng_a.lib;libpng.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("png.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\libpng16")) {
AC_DEFINE("HAVE_LIBPNG", 1, "PNG support");
AC_DEFINE("HAVE_GD_PNG", 1, "PNG support");
}
// freetype lib support
if (CHECK_LIB("libfreetype_a.lib;libfreetype.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("ft2build.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\freetype2;" + PHP_PHP_BUILD + "\\include\\freetype")) {
AC_DEFINE("HAVE_LIBFREETYPE", 1, "FreeType support");
AC_DEFINE("HAVE_GD_FREETYPE", 1, "FreeType support");
}
// xpm lib support
if (CHECK_LIB("libXpm_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("xpm.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\X11")) {
AC_DEFINE("HAVE_LIBXPM", 1, "XPM support");
AC_DEFINE("HAVE_GD_XPM", 1, "XPM support");
}
// iconv lib support
if ((CHECK_LIB("libiconv_a.lib;libiconv.lib", "gd", PHP_GD) || CHECK_LIB("iconv_a.lib;iconv.lib", "gd", PHP_GD)) &&
CHECK_HEADER_ADD_INCLUDE("iconv.h", "CFLAGS_GD", PHP_GD)) {
AC_DEFINE("HAVE_LIBICONV", 1, "Iconv support");
}
// libwebp lib support
if ((CHECK_LIB("libwebp_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebp.lib", "gd", PHP_GD)) &&
CHECK_LIB("libsharpyuv.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("decode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") &&
CHECK_HEADER_ADD_INCLUDE("encode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp")) {
AC_DEFINE("HAVE_LIBWEBP", 1, "WebP support");
AC_DEFINE("HAVE_GD_WEBP", 1, "WebP support");
}
// libavif lib support
if (CHECK_LIB("avif_a.lib", "gd", PHP_GD) &&
CHECK_LIB("aom_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("avif.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\avif")) {
ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBAVIF /D HAVE_GD_AVIF");
} else if (CHECK_LIB("avif.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("avif.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\avif")) {
ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBAVIF /D HAVE_GD_AVIF");
}
CHECK_LIB("User32.lib", "gd", PHP_GD);
CHECK_LIB("Gdi32.lib", "gd", PHP_GD);
EXTENSION("gd", "gd.c", null, "-Iext/gd/libgd");
ADD_SOURCES("ext/gd/libgd", "gd.c \
gdcache.c gdfontg.c gdfontl.c gdfontmb.c gdfonts.c gdfontt.c \
gdft.c gd_gd2.c gd_gd.c gd_gif_in.c gd_gif_out.c gdhelpers.c gd_io.c gd_io_dp.c \
gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_png.c gd_ss.c \
gdtables.c gd_topal.c gd_wbmp.c gdxpm.c wbmp.c gd_xbm.c gd_security.c gd_transform.c \
gd_filter.c gd_rotate.c gd_color_match.c gd_webp.c gd_avif.c \
gd_crop.c gd_interpolation.c gd_matrix.c gd_bmp.c gd_tga.c", "gd");
AC_DEFINE('HAVE_LIBGD', 1, 'GD support');
AC_DEFINE('HAVE_GD_BUNDLED', 1, "Bundled GD");
AC_DEFINE('HAVE_GD_BMP', 1, "BMP support");
AC_DEFINE('HAVE_GD_TGA', 1, "TGA support");
ADD_FLAG("CFLAGS_GD", " \
/D PHP_GD_EXPORTS=1 \
/D HAVE_GD_GET_INTERPOLATION \
");
if (ICC_TOOLSET) {
ADD_FLAG("LDFLAGS_GD", "/nodefaultlib:libcmt");
}
PHP_INSTALL_HEADERS("", "ext/gd ext/gd/libgd");
}
// vim:ft=javascript
ARG_WITH("gd", "Bundled GD support", "yes");
if (PHP_GD != "no") {
// check for gd.h (required)
if (!CHECK_HEADER_ADD_INCLUDE("gd.h", "CFLAGS_GD", PHP_GD + ";ext\\gd\\libgd")) {
ERROR("gd not enabled; libraries and headers not found");
}
// zlib ext support (required)
if (!CHECK_LIB("zlib_a.lib;zlib.lib", "gd", PHP_GD)) {
ERROR("gd not enabled; zlib not enabled");
}
// libjpeg lib support
if (CHECK_LIB("libjpeg_a.lib;libjpeg.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("jpeglib.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include")) {
AC_DEFINE("HAVE_LIBJPEG", 1, "JPEG support");
AC_DEFINE("HAVE_GD_JPG", 1, "JPEG support");
}
// libpng16 lib support
if (CHECK_LIB("libpng_a.lib;libpng.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("png.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\libpng16")) {
AC_DEFINE("HAVE_LIBPNG", 1, "PNG support");
AC_DEFINE("HAVE_GD_PNG", 1, "PNG support");
}
// freetype lib support
if (CHECK_LIB("libfreetype_a.lib;libfreetype.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("ft2build.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\freetype2;" + PHP_PHP_BUILD + "\\include\\freetype")) {
AC_DEFINE("HAVE_LIBFREETYPE", 1, "FreeType support");
AC_DEFINE("HAVE_GD_FREETYPE", 1, "FreeType support");
}
// xpm lib support
if (CHECK_LIB("libXpm_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("xpm.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\X11")) {
AC_DEFINE("HAVE_LIBXPM", 1, "XPM support");
AC_DEFINE("HAVE_GD_XPM", 1, "XPM support");
}
// iconv lib support
if ((CHECK_LIB("libiconv_a.lib;libiconv.lib", "gd", PHP_GD) || CHECK_LIB("iconv_a.lib;iconv.lib", "gd", PHP_GD)) &&
CHECK_HEADER_ADD_INCLUDE("iconv.h", "CFLAGS_GD", PHP_GD)) {
AC_DEFINE("HAVE_LIBICONV", 1, "Iconv support");
}
// libwebp lib support
if ((CHECK_LIB("libwebp_a.lib", "gd", PHP_GD) || CHECK_LIB("libwebp.lib", "gd", PHP_GD)) &&
CHECK_LIB("libsharpyuv.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("decode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp") &&
CHECK_HEADER_ADD_INCLUDE("encode.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\webp")) {
AC_DEFINE("HAVE_LIBWEBP", 1, "WebP support");
AC_DEFINE("HAVE_GD_WEBP", 1, "WebP support");
}
// libavif lib support
if (CHECK_LIB("avif_a.lib", "gd", PHP_GD) &&
CHECK_LIB("aom_a.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("avif.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\avif")) {
ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBAVIF /D HAVE_GD_AVIF");
} else if (CHECK_LIB("avif.lib", "gd", PHP_GD) &&
CHECK_HEADER_ADD_INCLUDE("avif.h", "CFLAGS_GD", PHP_GD + ";" + PHP_PHP_BUILD + "\\include\\avif")) {
ADD_FLAG("CFLAGS_GD", "/D HAVE_LIBAVIF /D HAVE_GD_AVIF");
}
CHECK_LIB("User32.lib", "gd", PHP_GD);
CHECK_LIB("Gdi32.lib", "gd", PHP_GD);
EXTENSION("gd", "gd.c", null, "-Iext/gd/libgd");
ADD_SOURCES("ext/gd/libgd", "gd.c \
gdcache.c gdfontg.c gdfontl.c gdfontmb.c gdfonts.c gdfontt.c \
gdft.c gd_gd2.c gd_gd.c gd_gif_in.c gd_gif_out.c gdhelpers.c gd_io.c gd_io_dp.c \
gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_png.c gd_ss.c \
gdtables.c gd_topal.c gd_wbmp.c gdxpm.c wbmp.c gd_xbm.c gd_security.c gd_transform.c \
gd_filter.c gd_rotate.c gd_color_match.c gd_webp.c gd_avif.c \
gd_crop.c gd_interpolation.c gd_matrix.c gd_bmp.c gd_tga.c", "gd");
AC_DEFINE('HAVE_LIBGD', 1, 'GD support');
AC_DEFINE('HAVE_GD_BUNDLED', 1, "Bundled GD");
AC_DEFINE('HAVE_GD_BMP', 1, "BMP support");
AC_DEFINE('HAVE_GD_TGA', 1, "TGA support");
ADD_FLAG("CFLAGS_GD", " \
/D PHP_GD_EXPORTS=1 \
/D HAVE_GD_GET_INTERPOLATION \
");
if (ICC_TOOLSET) {
ADD_FLAG("LDFLAGS_GD", "/nodefaultlib:libcmt");
}
PHP_INSTALL_HEADERS("", "ext/gd ext/gd/libgd");
}

View File

@@ -3,22 +3,11 @@
declare(strict_types=1);
// static-php-cli version string
use Laravel\Prompts\ConfirmPrompt;
use Laravel\Prompts\Prompt;
use Laravel\Prompts\TextPrompt;
use StaticPHP\ConsoleApplication;
use StaticPHP\DI\ApplicationContext;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\System\LinuxUtil;
use StaticPHP\Util\System\MacOSUtil;
use StaticPHP\Util\System\WindowsUtil;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
const SPC_VERSION = ConsoleApplication::VERSION;
// output path for everything, other paths are defined relative to this by default
@@ -75,31 +64,3 @@ putenv('CPU_COUNT=' . CPU_COUNT);
putenv('SPC_ARCH=' . php_uname('m'));
putenv('GNU_ARCH=' . GNU_ARCH);
putenv('MAC_ARCH=' . MAC_ARCH);
// initialize windows prompt fallback for laravel-prompts
Prompt::fallbackWhen(PHP_OS_FAMILY === 'Windows');
ConfirmPrompt::fallbackUsing(function (ConfirmPrompt $prompt) {
$helper = new QuestionHelper();
$case = $prompt->default ? ' [Y/n] ' : ' [y/N] ';
$question = new ConfirmationQuestion($prompt->label . $case, $prompt->default);
if (ApplicationContext::has(InputInterface::class) && ApplicationContext::has(OutputInterface::class)) {
$input = ApplicationContext::get(InputInterface::class);
$output = ApplicationContext::get(OutputInterface::class);
} else {
$input = new ArrayInput([]);
$output = new ConsoleOutput();
}
return $helper->ask($input, $output, $question);
});
TextPrompt::fallbackUsing(function (TextPrompt $prompt) {
$helper = new QuestionHelper();
$question = new Question($prompt->label . ' ', $prompt->default);
if (ApplicationContext::has(InputInterface::class) && ApplicationContext::has(OutputInterface::class)) {
$input = ApplicationContext::get(InputInterface::class);
$output = ApplicationContext::get(OutputInterface::class);
} else {
$input = new ArrayInput([]);
$output = new ConsoleOutput();
}
return $helper->ask($input, $output, $question);
});

View File

@@ -1,23 +1,23 @@
PostgreSQL Database Management System
(also known as Postgres, formerly known as Postgres95)
Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
Portions Copyright (c) 1994, The Regents of the University of California
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without a written agreement
is hereby granted, provided that the above copyright notice and this
paragraph and the following two paragraphs appear in all copies.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
PostgreSQL Database Management System
(also known as Postgres, formerly known as Postgres95)
Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
Portions Copyright (c) 1994, The Regents of the University of California
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without a written agreement
is hereby granted, provided that the above copyright notice and this
paragraph and the following two paragraphs appear in all copies.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

View File

@@ -1,7 +1,7 @@
diff --git a/library/libxlsxwriter/third_party/md5/md5.c b/library/libxlsxwriter/third_party/md5/md5.c
diff --git a/library/libxlsx/third_party/md5/md5.c b/library/libxlsx/third_party/md5/md5.c
index b235e17..ce98e18 100644
--- a/library/libxlsxwriter/third_party/md5/md5.c
+++ b/library/libxlsxwriter/third_party/md5/md5.c
--- a/library/libxlsx/third_party/md5/md5.c
+++ b/library/libxlsx/third_party/md5/md5.c
@@ -35,7 +35,11 @@
* compile-time configuration.
*/
@@ -15,10 +15,10 @@ index b235e17..ce98e18 100644
#include <string.h>
diff --git a/library/libxlsxwriter/third_party/md5/md5.h b/library/libxlsxwriter/third_party/md5/md5.h
diff --git a/library/libxlsx/third_party/md5/md5.h b/library/libxlsx/third_party/md5/md5.h
index 2da44bf..3cb0a98 100644
--- a/library/libxlsxwriter/third_party/md5/md5.h
+++ b/library/libxlsxwriter/third_party/md5/md5.h
--- a/library/libxlsx/third_party/md5/md5.h
+++ b/library/libxlsx/third_party/md5/md5.h
@@ -23,7 +23,7 @@
* See md5.c for more information.
*/