From 3c0cc5bd86058f19def509a5c39d645841c4c8f1 Mon Sep 17 00:00:00 2001 From: henderkes Date: Thu, 1 Jan 2026 13:56:01 +0100 Subject: [PATCH 1/7] allow downloading multiple php versions --- src/SPC/builder/BuilderBase.php | 12 +++++-- src/SPC/command/BuildPHPCommand.php | 31 ++++++++++++++++++ src/SPC/command/DownloadCommand.php | 51 +++++++++++++++++++++++------ src/SPC/store/Downloader.php | 6 +++- src/SPC/store/SourceManager.php | 15 ++++++--- src/SPC/store/source/PhpSource.php | 14 ++++++-- 6 files changed, 109 insertions(+), 20 deletions(-) diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index fdba936d7..ec733a8e2 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -160,7 +160,7 @@ public function proveExts(array $static_extensions, array $shared_extensions = [ } if (!$skip_extract) { $this->emitPatchPoint('before-php-extract'); - SourceManager::initSource(sources: ['php-src'], source_only: true); + SourceManager::initSource(sources: [$this->getPhpSrcName()], source_only: true); $this->emitPatchPoint('after-php-extract'); if ($this->getPHPVersionID() >= 80000) { $this->emitPatchPoint('before-micro-extract'); @@ -319,7 +319,7 @@ public function getPHPVersion(bool $exception_on_failure = true): string public function getPHPVersionFromArchive(?string $file = null): false|string { if ($file === null) { - $lock = LockFile::get('php-src'); + $lock = LockFile::get($this->getPhpSrcName()); if ($lock === null) { return false; } @@ -498,6 +498,14 @@ public function checkBeforeBuildPHP(int $rule): void } } + /** + * Get the php-src name to use for lock file lookups (supports version-specific names like php-src-8.2) + */ + protected function getPhpSrcName(): string + { + return getenv('SPC_PHP_SRC_NAME') ?: 'php-src'; + } + /** * Generate micro extension test php code. */ diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index ad884b3ba..9871eeb45 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -49,6 +49,7 @@ public function configure(): void $this->addOption('with-micro-logo', null, InputOption::VALUE_REQUIRED, 'Use custom .ico for micro.sfx (windows only)'); $this->addOption('enable-micro-win32', null, null, 'Enable win32 mode for phpmicro (Windows only)'); $this->addOption('with-frankenphp-app', null, InputOption::VALUE_REQUIRED, 'Path to a folder to be embedded in FrankenPHP'); + $this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'PHP version to build (e.g., 8.2, 8.3, 8.4). Uses php-src-X.Y if available, otherwise php-src'); } public function handle(): int @@ -120,6 +121,36 @@ public function handle(): int logger()->warning('Some cases micro.sfx cannot be packed via UPX due to dynamic size bug, be aware!'); } } + + // Determine which php-src to use based on --with-php option + $php_version = $this->getOption('with-php'); + if ($php_version !== null) { + // Check if version-specific php-src exists in lock file + $version_specific_name = "php-src-{$php_version}"; + $lock_file_path = DOWNLOAD_PATH . '/.lock.json'; + if (file_exists($lock_file_path)) { + $lock_content = json_decode(file_get_contents($lock_file_path), true); + if (isset($lock_content[$version_specific_name])) { + // Use version-specific php-src + f_putenv("SPC_PHP_SRC_NAME={$version_specific_name}"); + logger()->info("Building with PHP {$php_version} (using {$version_specific_name})"); + } elseif (isset($lock_content['php-src'])) { + // Fall back to regular php-src + f_putenv('SPC_PHP_SRC_NAME=php-src'); + logger()->warning("php-src-{$php_version} not found, using default php-src"); + } else { + logger()->error('No php-src found in downloads. Please run download command first.'); + return static::FAILURE; + } + } else { + logger()->error('Lock file not found. Please download sources first.'); + return static::FAILURE; + } + } else { + // No version specified, use default php-src + f_putenv('SPC_PHP_SRC_NAME=php-src'); + } + // create builder $builder = BuilderProvider::makeBuilderByInput($this->input); $include_suggest_ext = $this->getOption('with-suggested-exts'); diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index 00bcc1948..a843aa36f 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -30,7 +30,7 @@ public function configure(): void $this->addArgument('sources', InputArgument::REQUIRED, 'The sources will be compiled, comma separated'); $this->addOption('shallow-clone', null, null, 'Clone shallow'); $this->addOption('with-openssl11', null, null, 'Use openssl 1.1'); - $this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'version in major.minor format (default 8.4)', '8.4'); + $this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'version in major.minor format, comma-separated for multiple versions (default 8.4)', '8.4'); $this->addOption('clean', null, null, 'Clean old download cache and source before fetch'); $this->addOption('all', 'A', null, 'Fetch all sources that static-php-cli needed'); $this->addOption('custom-url', 'U', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Specify custom source download url, e.g "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"'); @@ -94,17 +94,25 @@ public function handle(): int return $this->downloadFromZip($path); } - // Define PHP major version - $ver = $this->php_major_ver = $this->getOption('with-php'); - define('SPC_BUILD_PHP_VERSION', $ver); - if ($ver !== 'git' && !preg_match('/^\d+\.\d+$/', $ver)) { - // If not git, we need to check the version format - if (!preg_match('/^\d+\.\d+(\.\d+)?$/', $ver)) { - logger()->error("bad version arg: {$ver}, x.y or x.y.z required!"); - return static::FAILURE; + // Define PHP major version(s) + $php_versions_str = $this->getOption('with-php'); + $php_versions = array_map('trim', explode(',', $php_versions_str)); + + // Validate all versions + foreach ($php_versions as $ver) { + if ($ver !== 'git' && !preg_match('/^\d+\.\d+$/', $ver)) { + // If not git, we need to check the version format + if (!preg_match('/^\d+\.\d+(\.\d+)?$/', $ver)) { + logger()->error("bad version arg: {$ver}, x.y or x.y.z required!"); + return static::FAILURE; + } } } + // Set the first version as the default for backward compatibility + $this->php_major_ver = $php_versions[0]; + define('SPC_BUILD_PHP_VERSION', $this->php_major_ver); + // retry $retry = (int) $this->getOption('retry'); f_putenv('SPC_DOWNLOAD_RETRIES=' . $retry); @@ -125,6 +133,20 @@ public function handle(): int $chosen_sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources')))); + // Handle multiple PHP versions + // If php-src is in the sources, replace it with version-specific sources + if (in_array('php-src', $chosen_sources)) { + // Remove php-src from the list + $chosen_sources = array_diff($chosen_sources, ['php-src']); + // Add version-specific php-src for each version + foreach ($php_versions as $ver) { + $version_specific_name = "php-src-{$ver}"; + $chosen_sources[] = $version_specific_name; + // Store the version for this specific php-src + f_putenv("SPC_PHP_VERSION_{$version_specific_name}={$ver}"); + } + } + $sss = $this->getOption('ignore-cache-sources'); if ($sss === false) { // false is no-any-ignores, that is, default. @@ -201,7 +223,16 @@ public function handle(): int logger()->info("[{$ni}/{$cnt}] Downloading source {$source} from custom git: {$new_config['url']}"); Downloader::downloadSource($source, $new_config, true); } else { - $config = Config::getSource($source); + // Handle version-specific php-src (php-src-8.2, php-src-8.3, etc.) + if (preg_match('/^php-src-[\d.]+$/', $source)) { + $config = Config::getSource('php-src'); + if ($config === null) { + logger()->error('php-src configuration not found in source.json'); + return static::FAILURE; + } + } else { + $config = Config::getSource($source); + } // Prefer pre-built, we need to search pre-built library if ($this->getOption('prefer-pre-built') && ($config['provide-pre-built'] ?? false) === true) { // We need to replace pattern diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index ccf61dd8d..7d685d671 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -682,7 +682,11 @@ private static function downloadByType(string $type, string $name, array $conf, ...FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/pkg', 'SPC\store\pkg'), ]; foreach ($classes as $class) { - if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) { + // Support php-src and php-src-X.Y patterns + $matches = ($class::NAME === $name) || + ($class::NAME === 'php-src' && preg_match('/^php-src(-[\d.]+)?$/', $name)); + if (is_a($class, CustomSourceBase::class, true) && $matches) { + $conf['source_name'] = $name; // Pass the actual source name (new $class())->fetch($force, $conf, $download_as); break; } diff --git a/src/SPC/store/SourceManager.php b/src/SPC/store/SourceManager.php index 35728e22c..ef545e48b 100644 --- a/src/SPC/store/SourceManager.php +++ b/src/SPC/store/SourceManager.php @@ -39,7 +39,14 @@ public static function initSource(?array $sources = null, ?array $libs = null, ? // start check foreach ($sources_extracted as $source => $item) { - if (Config::getSource($source) === null) { + $extract_dir_name = $source; + // Handle version-specific php-src (php-src-8.2, php-src-8.3, etc.) + $source_config = Config::getSource($source); + if ($source_config === null && preg_match('/^php-src-[\d.]+$/', $source)) { + $source_config = Config::getSource('php-src'); + $extract_dir_name = 'php-src'; + } + if ($source_config === null) { throw new WrongUsageException("Source [{$source}] does not exist, please check the name and correct it !"); } // check source downloaded @@ -56,12 +63,12 @@ public static function initSource(?array $sources = null, ?array $libs = null, ? $lock_content = LockFile::get($lock_name); // check source dir exist - $check = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source); + $check = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $extract_dir_name); // $check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']); if (!is_dir($check)) { logger()->debug("Extracting source [{$source}] to {$check} ..."); $filename = LockFile::getLockFullPath($lock_content); - FileSystem::extractSource($source, $lock_content['source_type'], $filename, $check); + FileSystem::extractSource($extract_dir_name, $lock_content['source_type'], $filename, $check); LockFile::putLockSourceHash($lock_content, $check); continue; } @@ -89,7 +96,7 @@ public static function initSource(?array $sources = null, ?array $libs = null, ? logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ..."); FileSystem::removeDir($check); $filename = LockFile::getLockFullPath($lock_content); - $move_path = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source); + $move_path = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $extract_dir_name); FileSystem::extractSource($source, $lock_content['source_type'], $filename, $move_path); LockFile::putLockSourceHash($lock_content, $check); } diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php index 27e8bb891..6c4049bc1 100644 --- a/src/SPC/store/source/PhpSource.php +++ b/src/SPC/store/source/PhpSource.php @@ -14,11 +14,19 @@ class PhpSource extends CustomSourceBase public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void { - $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4'; + $source_name = $config['source_name'] ?? 'php-src'; + + // Try to extract version from source name (e.g., "php-src-8.2" -> "8.2") + if (preg_match('/^php-src-([\d.]+)$/', $source_name, $matches)) { + $major = $matches[1]; + } else { + $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4'; + } + if ($major === 'git') { - Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force); + Downloader::downloadSource($source_name, ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force); } else { - Downloader::downloadSource('php-src', $this->getLatestPHPInfo($major), $force); + Downloader::downloadSource($source_name, $this->getLatestPHPInfo($major), $force); } } From 134efa17ab363222eab69f838d71ffdf8fc046cc Mon Sep 17 00:00:00 2001 From: henderkes Date: Thu, 1 Jan 2026 14:31:35 +0100 Subject: [PATCH 2/7] add --update flag to DownloadCommand to check all downloaded sources if they need an update --- src/SPC/command/DownloadCommand.php | 311 +++++++++++++++++++++- src/SPC/store/Downloader.php | 33 +++ src/SPC/store/source/CustomSourceBase.php | 9 + src/SPC/store/source/PhpSource.php | 27 ++ src/SPC/store/source/PostgreSQLSource.php | 10 + 5 files changed, 389 insertions(+), 1 deletion(-) diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index a843aa36f..3abd75ffc 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -9,7 +9,9 @@ use SPC\exception\SPCException; use SPC\store\Config; use SPC\store\Downloader; +use SPC\store\FileSystem; use SPC\store\LockFile; +use SPC\store\source\CustomSourceBase; use SPC\util\DependencyUtil; use SPC\util\SPCTarget; use Symfony\Component\Console\Attribute\AsCommand; @@ -27,7 +29,7 @@ class DownloadCommand extends BaseCommand public function configure(): void { - $this->addArgument('sources', InputArgument::REQUIRED, 'The sources will be compiled, comma separated'); + $this->addArgument('sources', InputArgument::OPTIONAL, 'The sources will be compiled, comma separated'); $this->addOption('shallow-clone', null, null, 'Clone shallow'); $this->addOption('with-openssl11', null, null, 'Use openssl 1.1'); $this->addOption('with-php', null, InputOption::VALUE_REQUIRED, 'version in major.minor format, comma-separated for multiple versions (default 8.4)', '8.4'); @@ -43,10 +45,31 @@ public function configure(): void $this->addOption('retry', 'R', InputOption::VALUE_REQUIRED, 'Set retry time when downloading failed (default: 0)', '0'); $this->addOption('prefer-pre-built', 'P', null, 'Download pre-built libraries when available'); $this->addOption('no-alt', null, null, 'Do not download alternative sources'); + $this->addOption('update', null, null, 'Check and update downloaded sources'); } public function initialize(InputInterface $input, OutputInterface $output): void { + // mode: --update + if ($input->getOption('update') && empty($input->getArgument('sources')) && empty($input->getOption('for-extensions')) && empty($input->getOption('for-libs'))) { + if (!file_exists(LockFile::LOCK_FILE)) { + parent::initialize($input, $output); + return; + } + $lock_content = json_decode(file_get_contents(LockFile::LOCK_FILE), true); + if (is_array($lock_content)) { + // Filter out pre-built sources + $sources_to_check = array_filter($lock_content, function ($name) { + return + !str_contains($name, '-Linux-') && + !str_contains($name, '-Windows-') && + !str_contains($name, '-Darwin-'); + }); + $input->setArgument('sources', implode(',', array_keys($sources_to_check))); + } + parent::initialize($input, $output); + return; + } // mode: --all if ($input->getOption('all')) { $input->setArgument('sources', implode(',', array_keys(Config::getSources()))); @@ -94,6 +117,10 @@ public function handle(): int return $this->downloadFromZip($path); } + if ($this->getOption('update')) { + return $this->handleUpdate(); + } + // Define PHP major version(s) $php_versions_str = $this->getOption('with-php'); $php_versions = array_map('trim', explode(',', $php_versions_str)); @@ -393,4 +420,286 @@ private function _clean(): int } return static::FAILURE; } + + private function handleUpdate(): int + { + logger()->info('Checking sources for updates...'); + + // Get lock file content + $lock_file_path = LockFile::LOCK_FILE; + if (!file_exists($lock_file_path)) { + logger()->warning('No lock file found. Please download sources first using "bin/spc download"'); + return static::FAILURE; + } + + $lock_content = json_decode(file_get_contents($lock_file_path), true); + if ($lock_content === null || !is_array($lock_content)) { + logger()->error('Failed to parse lock file'); + return static::FAILURE; + } + + // Filter sources to check + $sources_arg = $this->getArgument('sources'); + if (!empty($sources_arg)) { + $requested_sources = array_map('trim', array_filter(explode(',', $sources_arg))); + $sources_to_check = []; + foreach ($requested_sources as $source) { + if (isset($lock_content[$source])) { + $sources_to_check[$source] = $lock_content[$source]; + } else { + logger()->warning("Source '{$source}' not found in lock file, skipping"); + } + } + } else { + $sources_to_check = $lock_content; + } + + // Filter out pre-built sources (they are derivatives) + $sources_to_check = array_filter($sources_to_check, function ($lock_item, $name) { + // Skip pre-built sources (they contain OS/arch in the name) + if (str_contains($name, '-Linux-') || str_contains($name, '-Windows-') || str_contains($name, '-Darwin-')) { + logger()->debug("Skipping pre-built source: {$name}"); + return false; + } + return true; + }, ARRAY_FILTER_USE_BOTH); + + if (empty($sources_to_check)) { + logger()->warning('No sources to check'); + return static::FAILURE; + } + + $total = count($sources_to_check); + $current = 0; + $updated_sources = []; + + foreach ($sources_to_check as $name => $lock_item) { + ++$current; + try { + // Handle version-specific php-src (php-src-8.2, php-src-8.3, etc.) + if (preg_match('/^php-src-[\d.]+$/', $name)) { + $config = Config::getSource('php-src'); + } else { + $config = Config::getSource($name); + } + + if ($config === null) { + logger()->warning("[{$current}/{$total}] Source '{$name}' not found in source config, skipping"); + continue; + } + + // Check and update based on source type + $source_type = $lock_item['source_type'] ?? 'unknown'; + + if ($source_type === SPC_SOURCE_ARCHIVE) { + if ($this->checkArchiveSourceUpdate($name, $lock_item, $config, $current, $total)) { + $updated_sources[] = $name; + } + } elseif ($source_type === SPC_SOURCE_GIT) { + if ($this->checkGitSourceUpdate($name, $lock_item, $config, $current, $total)) { + $updated_sources[] = $name; + } + } elseif ($source_type === SPC_SOURCE_LOCAL) { + logger()->debug("[{$current}/{$total}] Source '{$name}' is local, skipping"); + } else { + logger()->warning("[{$current}/{$total}] Unknown source type '{$source_type}' for '{$name}', skipping"); + } + } catch (\Throwable $e) { + logger()->error("[{$current}/{$total}] Error checking '{$name}': {$e->getMessage()}"); + continue; + } + } + + // Output summary + if (empty($updated_sources)) { + logger()->info('All sources are up to date.'); + } else { + logger()->info('Updated sources: ' . implode(', ', $updated_sources)); + + // Write updated sources to file + $date = date('Y-m-d'); + $update_file = DOWNLOAD_PATH . '/.update-' . $date . '.txt'; + $content = implode(',', $updated_sources); + file_put_contents($update_file, $content); + logger()->debug("Updated sources written to: {$update_file}"); + } + + return static::SUCCESS; + } + + private function checkCustomSourceUpdate(string $name, array $lock, array $config, int $current, int $total): bool + { + $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source'); + foreach ($classes as $class) { + // Support php-src and php-src-X.Y patterns + $matches = ($class::NAME === $name) || + ($class::NAME === 'php-src' && preg_match('/^php-src(-[\d.]+)?$/', $name)); + if (is_a($class, CustomSourceBase::class, true) && $matches) { + try { + $config['source_name'] = $name; + $updated = (new $class())->update($lock, $config); + if ($updated) { + logger()->info("[{$current}/{$total}] Source '{$name}' updated"); + } else { + logger()->info("[{$current}/{$total}] Source '{$name}' is up to date"); + } + return $updated; + } catch (\Throwable $e) { + logger()->warning("[{$current}/{$total}] Failed to check '{$name}': {$e->getMessage()}"); + return false; + } + } + } + logger()->warning("[{$current}/{$total}] Custom source handler for '{$name}' not found"); + return false; + } + + /** + * Check and update an archive source + * + * @param string $name Source name + * @param array $lock Lock file entry + * @param array $config Source configuration + * @param int $current Current progress number + * @param int $total Total sources to check + * @return bool True if updated, false otherwise + */ + private function checkArchiveSourceUpdate(string $name, array $lock, array $config, int $current, int $total): bool + { + $type = $config['type'] ?? 'unknown'; + $locked_filename = $lock['filename'] ?? ''; + + // Skip local types that don't support version detection + if (in_array($type, ['url', 'local', 'unknown'])) { + logger()->debug("[{$current}/{$total}] Source '{$name}' (type: {$type}) doesn't support version detection, skipping"); + return false; + } + + try { + // Get latest version info + $latest_info = match ($type) { + 'ghtar' => Downloader::getLatestGithubTarball($name, $config), + 'ghtagtar' => Downloader::getLatestGithubTarball($name, $config, 'tags'), + 'ghrel' => Downloader::getLatestGithubRelease($name, $config), + 'pie' => Downloader::getPIEInfo($name, $config), + 'bitbuckettag' => Downloader::getLatestBitbucketTag($name, $config), + 'filelist' => Downloader::getFromFileList($name, $config), + 'url' => Downloader::getLatestUrlInfo($name, $config), + 'custom' => $this->checkCustomSourceUpdate($name, $lock, $config, $current, $total), + default => null, + }; + + if ($latest_info === null) { + logger()->warning("[{$current}/{$total}] Could not get version info for '{$name}' (type: {$type})"); + return false; + } + + $latest_filename = $latest_info[1] ?? ''; + + // Compare filenames + if ($locked_filename !== $latest_filename) { + logger()->info("[{$current}/{$total}] Update available for '{$name}': {$locked_filename} → {$latest_filename}"); + $this->downloadSourceForUpdate($name, $config, $current, $total); + return true; + } + + logger()->info("[{$current}/{$total}] Source '{$name}' is up to date"); + return false; + } catch (DownloaderException $e) { + logger()->warning("[{$current}/{$total}] Failed to check '{$name}': {$e->getMessage()}"); + return false; + } + } + + /** + * Check and update a git source + * + * @param string $name Source name + * @param array $lock Lock file entry + * @param array $config Source configuration + * @param int $current Current progress number + * @param int $total Total sources to check + * @return bool True if updated, false otherwise + */ + private function checkGitSourceUpdate(string $name, array $lock, array $config, int $current, int $total): bool + { + $locked_hash = $lock['hash'] ?? ''; + $url = $config['url'] ?? ''; + $branch = $config['rev'] ?? 'main'; + + if (empty($url)) { + logger()->warning("[{$current}/{$total}] No URL found for git source '{$name}'"); + return false; + } + + try { + $remote_hash = $this->getRemoteGitCommit($url, $branch); + + if ($remote_hash === null) { + logger()->warning("[{$current}/{$total}] Could not fetch remote commit for '{$name}'"); + return false; + } + + // Compare hashes (use first 7 chars for display) + $locked_short = substr($locked_hash, 0, 7); + $remote_short = substr($remote_hash, 0, 7); + + if ($locked_hash !== $remote_hash) { + logger()->info("[{$current}/{$total}] Update available for '{$name}': {$locked_short} → {$remote_short}"); + $this->downloadSourceForUpdate($name, $config, $current, $total); + return true; + } + + logger()->info("[{$current}/{$total}] Source '{$name}' is up to date"); + return false; + } catch (\Throwable $e) { + logger()->warning("[{$current}/{$total}] Failed to check '{$name}': {$e->getMessage()}"); + return false; + } + } + + /** + * Download a source after removing old lock entry + * + * @param string $name Source name + * @param array $config Source configuration + * @param int $current Current progress number + * @param int $total Total sources to check + */ + private function downloadSourceForUpdate(string $name, array $config, int $current, int $total): void + { + logger()->info("[{$current}/{$total}] Downloading '{$name}'..."); + + // Remove old lock entry (this triggers cleanup of old files) + LockFile::put($name, null); + + // Download new version + Downloader::downloadSource($name, $config, true); + } + + /** + * Get remote git commit hash without cloning + * + * @param string $url Git repository URL + * @param string $branch Branch or tag to check + * @return null|string Remote commit hash or null on failure + */ + private function getRemoteGitCommit(string $url, string $branch): ?string + { + try { + $cmd = SPC_GIT_EXEC . ' ls-remote ' . escapeshellarg($url) . ' ' . escapeshellarg($branch); + f_exec($cmd, $output, $ret); + + if ($ret !== 0 || empty($output)) { + return null; + } + + // Output format: "commit_hash\trefs/heads/branch" or "commit_hash\tHEAD" + $parts = preg_split('/\s+/', $output[0]); + return $parts[0] ?? null; + } catch (\Throwable $e) { + logger()->debug("Failed to fetch remote git commit: {$e->getMessage()}"); + return null; + } + } } diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index 7d685d671..a85f639f2 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -220,6 +220,39 @@ public static function getFromFileList(string $name, array $source): array return [$source['url'] . end($versions), end($versions), key($versions)]; } + /** + * Get latest version from direct URL (detect redirect and filename) + * + * @param string $name Source name + * @param array $source Source meta info: [url] + * @return array [url, filename] + */ + public static function getLatestUrlInfo(string $name, array $source): array + { + logger()->debug("finding {$name} source from direct url"); + $url = $source['url']; + $headers = self::curlExec( + url: $url, + method: 'HEAD', + retries: self::getRetryAttempts() + ); + + // Find redirect location if any + if (preg_match('/^location:\s+(?.+)$/im', $headers, $matches)) { + $url = trim($matches['url']); + // If it's a relative URL, we need to handle it, but usually it's absolute for downloads + } + + // Find filename from content-disposition + if (preg_match('/^content-disposition:\s+attachment;\s*filename=("?)(?.+)\1/im', $headers, $matches)) { + $filename = trim($matches['filename']); + } else { + $filename = $source['filename'] ?? basename($url); + } + + return [$url, $filename]; + } + /** * Download file from URL * diff --git a/src/SPC/store/source/CustomSourceBase.php b/src/SPC/store/source/CustomSourceBase.php index 2f02fa7d3..84705dfb4 100644 --- a/src/SPC/store/source/CustomSourceBase.php +++ b/src/SPC/store/source/CustomSourceBase.php @@ -25,4 +25,13 @@ abstract class CustomSourceBase * @param int $lock_as Lock type constant */ abstract public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWNLOAD_SOURCE): void; + + /** + * Update the source from its repository + * + * @param array $lock Lock file entry + * @param array $config Optional configuration array + * @return bool True if updated, false otherwise + */ + abstract public function update(array $lock, ?array $config = null): bool; } diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php index 6c4049bc1..bd3297d9f 100644 --- a/src/SPC/store/source/PhpSource.php +++ b/src/SPC/store/source/PhpSource.php @@ -30,6 +30,33 @@ public function fetch(bool $force = false, ?array $config = null, int $lock_as = } } + public function update(array $lock, ?array $config = null): bool + { + $source_name = $config['source_name'] ?? 'php-src'; + + // Try to extract version from source name (e.g., "php-src-8.2" -> "8.2") + if (preg_match('/^php-src-([\d.]+)$/', $source_name, $matches)) { + $major = $matches[1]; + } else { + $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4'; + } + + if ($major === 'git') { + return false; + } + + $latest_php = $this->getLatestPHPInfo($major); + $latest_url = $latest_php['url']; + $locked_url = $lock['url'] ?? ''; + + if ($locked_url !== $latest_url) { + Downloader::downloadSource($source_name, $latest_php, true); + return true; + } + + return false; + } + /** * 获取 PHP x.y 的具体版本号,例如通过 8.1 来获取 8.1.10 */ diff --git a/src/SPC/store/source/PostgreSQLSource.php b/src/SPC/store/source/PostgreSQLSource.php index ec1d3357e..e627a00fc 100644 --- a/src/SPC/store/source/PostgreSQLSource.php +++ b/src/SPC/store/source/PostgreSQLSource.php @@ -15,6 +15,16 @@ public function fetch(bool $force = false, ?array $config = null, int $lock_as = Downloader::downloadSource('postgresql', self::getLatestInfo(), $force); } + public function update(array $lock, ?array $config = null): bool + { + $latest = $this->getLatestInfo(); + if (($lock['url'] ?? '') !== $latest['url']) { + Downloader::downloadSource('postgresql', $latest, true); + return true; + } + return false; + } + public function getLatestInfo(): array { [, $filename, $version] = Downloader::getFromFileList('postgresql', [ From ec3be16aaf5b2e3cd6063382433376afdbbc120a Mon Sep 17 00:00:00 2001 From: henderkes Date: Thu, 1 Jan 2026 14:48:21 +0100 Subject: [PATCH 3/7] update custom sources too --- src/SPC/command/DownloadCommand.php | 21 +++++++-------------- src/SPC/store/Downloader.php | 5 +++-- src/SPC/store/LockFile.php | 1 + src/SPC/store/source/CustomSourceBase.php | 8 ++++---- src/SPC/store/source/PhpSource.php | 13 ++++--------- src/SPC/store/source/PostgreSQLSource.php | 9 +++------ 6 files changed, 22 insertions(+), 35 deletions(-) diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index 3abd75ffc..17cb7e420 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -527,7 +527,7 @@ private function handleUpdate(): int return static::SUCCESS; } - private function checkCustomSourceUpdate(string $name, array $lock, array $config, int $current, int $total): bool + private function checkCustomSourceUpdate(string $name, array $lock, array $config, int $current, int $total): ?array { $classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source'); foreach ($classes as $class) { @@ -537,21 +537,15 @@ private function checkCustomSourceUpdate(string $name, array $lock, array $confi if (is_a($class, CustomSourceBase::class, true) && $matches) { try { $config['source_name'] = $name; - $updated = (new $class())->update($lock, $config); - if ($updated) { - logger()->info("[{$current}/{$total}] Source '{$name}' updated"); - } else { - logger()->info("[{$current}/{$total}] Source '{$name}' is up to date"); - } - return $updated; + return (new $class())->update($lock, $config); } catch (\Throwable $e) { logger()->warning("[{$current}/{$total}] Failed to check '{$name}': {$e->getMessage()}"); - return false; + return null; } } } - logger()->warning("[{$current}/{$total}] Custom source handler for '{$name}' not found"); - return false; + logger()->debug("[{$current}/{$total}] Custom source handler for '{$name}' not found"); + return null; } /** @@ -570,13 +564,12 @@ private function checkArchiveSourceUpdate(string $name, array $lock, array $conf $locked_filename = $lock['filename'] ?? ''; // Skip local types that don't support version detection - if (in_array($type, ['url', 'local', 'unknown'])) { + if (in_array($type, ['local', 'unknown'])) { logger()->debug("[{$current}/{$total}] Source '{$name}' (type: {$type}) doesn't support version detection, skipping"); return false; } try { - // Get latest version info $latest_info = match ($type) { 'ghtar' => Downloader::getLatestGithubTarball($name, $config), 'ghtagtar' => Downloader::getLatestGithubTarball($name, $config, 'tags'), @@ -670,7 +663,7 @@ private function downloadSourceForUpdate(string $name, array $config, int $curre { logger()->info("[{$current}/{$total}] Downloading '{$name}'..."); - // Remove old lock entry (this triggers cleanup of old files) + // Remove old lock entry LockFile::put($name, null); // Download new version diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index a85f639f2..73e75018e 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -280,7 +280,7 @@ public static function downloadFile(string $name, string $url, string $filename, if ($download_as === SPC_DOWNLOAD_PRE_BUILT) { $name = self::getPreBuiltLockName($name); } - LockFile::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]); + LockFile::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'url' => $url, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]); } /** @@ -339,7 +339,7 @@ public static function downloadGit(string $name, string $url, string $branch, ?a } // Lock logger()->debug("Locking git source {$name}"); - LockFile::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]); + LockFile::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'url' => $url, 'rev' => $branch, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]); /* // 复制目录过去 @@ -700,6 +700,7 @@ private static function downloadByType(string $type, string $name, array $conf, LockFile::lockSource($name, [ 'source_type' => SPC_SOURCE_LOCAL, 'dirname' => $conf['dirname'], + 'path' => $conf['path'] ?? null, 'move_path' => $conf['path'] ?? $conf['extract'] ?? null, 'lock_as' => $download_as, ]); diff --git a/src/SPC/store/LockFile.php b/src/SPC/store/LockFile.php index 88ecd6cb0..eeb853f93 100644 --- a/src/SPC/store/LockFile.php +++ b/src/SPC/store/LockFile.php @@ -155,6 +155,7 @@ public static function putLockSourceHash(array $lock_options, string $destinatio * @param string $name Source name * @param array{ * source_type: string, + * url: ?string, * dirname?: ?string, * filename?: ?string, * move_path: ?string, diff --git a/src/SPC/store/source/CustomSourceBase.php b/src/SPC/store/source/CustomSourceBase.php index 84705dfb4..8158db559 100644 --- a/src/SPC/store/source/CustomSourceBase.php +++ b/src/SPC/store/source/CustomSourceBase.php @@ -29,9 +29,9 @@ abstract public function fetch(bool $force = false, ?array $config = null, int $ /** * Update the source from its repository * - * @param array $lock Lock file entry - * @param array $config Optional configuration array - * @return bool True if updated, false otherwise + * @param array $lock Lock file entry + * @param array $config Optional configuration array + * @return null|array Latest version info [url, filename], or null if no update needed */ - abstract public function update(array $lock, ?array $config = null): bool; + abstract public function update(array $lock, ?array $config = null): ?array; } diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php index bd3297d9f..079d52878 100644 --- a/src/SPC/store/source/PhpSource.php +++ b/src/SPC/store/source/PhpSource.php @@ -30,7 +30,7 @@ public function fetch(bool $force = false, ?array $config = null, int $lock_as = } } - public function update(array $lock, ?array $config = null): bool + public function update(array $lock, ?array $config = null): ?array { $source_name = $config['source_name'] ?? 'php-src'; @@ -42,19 +42,14 @@ public function update(array $lock, ?array $config = null): bool } if ($major === 'git') { - return false; + return null; } $latest_php = $this->getLatestPHPInfo($major); $latest_url = $latest_php['url']; - $locked_url = $lock['url'] ?? ''; + $filename = basename($latest_url); - if ($locked_url !== $latest_url) { - Downloader::downloadSource($source_name, $latest_php, true); - return true; - } - - return false; + return [$latest_url, $filename]; } /** diff --git a/src/SPC/store/source/PostgreSQLSource.php b/src/SPC/store/source/PostgreSQLSource.php index e627a00fc..32024530a 100644 --- a/src/SPC/store/source/PostgreSQLSource.php +++ b/src/SPC/store/source/PostgreSQLSource.php @@ -15,14 +15,11 @@ public function fetch(bool $force = false, ?array $config = null, int $lock_as = Downloader::downloadSource('postgresql', self::getLatestInfo(), $force); } - public function update(array $lock, ?array $config = null): bool + public function update(array $lock, ?array $config = null): ?array { $latest = $this->getLatestInfo(); - if (($lock['url'] ?? '') !== $latest['url']) { - Downloader::downloadSource('postgresql', $latest, true); - return true; - } - return false; + $filename = basename($latest['url']); + return [$latest['url'], $filename]; } public function getLatestInfo(): array From 4dbbc8e8ac0a56213e35de0aa32d40dbdaf71eee Mon Sep 17 00:00:00 2001 From: henderkes Date: Thu, 1 Jan 2026 14:51:02 +0100 Subject: [PATCH 4/7] update the updated sources file instead of overwriting it --- src/SPC/command/DownloadCommand.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index 17cb7e420..ed1b965cb 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -519,6 +519,11 @@ private function handleUpdate(): int // Write updated sources to file $date = date('Y-m-d'); $update_file = DOWNLOAD_PATH . '/.update-' . $date . '.txt'; + if (file_exists($update_file)) { + $existing_content = file_get_contents($update_file); + $existing_sources = array_map('trim', explode(',', $existing_content)); + $updated_sources = array_unique(array_merge($existing_sources, $updated_sources)); + } $content = implode(',', $updated_sources); file_put_contents($update_file, $content); logger()->debug("Updated sources written to: {$update_file}"); From 20b670edc15f1bac6081f1c8c7750994db622b94 Mon Sep 17 00:00:00 2001 From: henderkes Date: Thu, 1 Jan 2026 15:04:25 +0100 Subject: [PATCH 5/7] use key, woops --- src/SPC/command/DownloadCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index ed1b965cb..fb1179e32 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -64,7 +64,7 @@ public function initialize(InputInterface $input, OutputInterface $output): void !str_contains($name, '-Linux-') && !str_contains($name, '-Windows-') && !str_contains($name, '-Darwin-'); - }); + }, ARRAY_FILTER_USE_KEY); $input->setArgument('sources', implode(',', array_keys($sources_to_check))); } parent::initialize($input, $output); From 51ef5e61be32f8709d981355e0280c090e48536b Mon Sep 17 00:00:00 2001 From: henderkes Date: Thu, 1 Jan 2026 15:13:44 +0100 Subject: [PATCH 6/7] proper updates for "url" type downloads --- config/source.json | 31 ------------------------------- src/SPC/store/Downloader.php | 4 ++-- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/config/source.json b/config/source.json index 1bae46ce7..a71152508 100644 --- a/config/source.json +++ b/config/source.json @@ -11,7 +11,6 @@ "type": "url", "url": "https://pecl.php.net/get/amqp", "path": "php-src/ext/amqp", - "filename": "amqp.tgz", "license": { "type": "file", "path": "LICENSE" @@ -21,7 +20,6 @@ "type": "url", "url": "https://pecl.php.net/get/APCu", "path": "php-src/ext/apcu", - "filename": "apcu.tgz", "license": { "type": "file", "path": "LICENSE" @@ -31,7 +29,6 @@ "type": "url", "url": "https://pecl.php.net/get/ast", "path": "php-src/ext/ast", - "filename": "ast.tgz", "license": { "type": "file", "path": "LICENSE" @@ -88,7 +85,6 @@ "type": "url", "url": "https://pecl.php.net/get/dio", "path": "php-src/ext/dio", - "filename": "dio.tgz", "license": { "type": "file", "path": "LICENSE" @@ -98,7 +94,6 @@ "type": "url", "url": "https://pecl.php.net/get/ev", "path": "php-src/ext/ev", - "filename": "ev.tgz", "license": { "type": "file", "path": "LICENSE" @@ -118,7 +113,6 @@ "type": "url", "url": "https://pecl.php.net/get/ds", "path": "php-src/ext/ds", - "filename": "ds.tgz", "license": { "type": "file", "path": "LICENSE" @@ -155,7 +149,6 @@ "type": "url", "url": "https://pecl.php.net/get/grpc", "path": "php-src/ext/grpc", - "filename": "grpc.tgz", "license": { "type": "file", "path": [ @@ -167,7 +160,6 @@ "type": "url", "url": "https://pecl.php.net/get/imagick", "path": "php-src/ext/imagick", - "filename": "imagick.tgz", "license": { "type": "file", "path": "LICENSE" @@ -177,7 +169,6 @@ "type": "url", "url": "https://pecl.php.net/get/imap", "path": "php-src/ext/imap", - "filename": "imap.tgz", "license": { "type": "file", "path": [ @@ -199,7 +190,6 @@ "ext-maxminddb": { "type": "url", "url": "https://pecl.php.net/get/maxminddb", - "filename": "ext-maxminddb.tgz", "license": { "type": "file", "path": "LICENSE" @@ -209,7 +199,6 @@ "type": "url", "url": "https://pecl.php.net/get/memcache", "path": "php-src/ext/memcache", - "filename": "memcache.tgz", "license": { "type": "file", "path": "LICENSE" @@ -228,7 +217,6 @@ "type": "url", "url": "https://pecl.php.net/get/simdjson", "path": "php-src/ext/simdjson", - "filename": "simdjson.tgz", "license": { "type": "file", "path": "LICENSE" @@ -248,7 +236,6 @@ "type": "url", "url": "https://pecl.php.net/get/ssh2", "path": "php-src/ext/ssh2", - "filename": "ssh2.tgz", "license": { "type": "file", "path": "LICENSE" @@ -258,7 +245,6 @@ "type": "url", "url": "https://pecl.php.net/get/trader", "path": "php-src/ext/trader", - "filename": "trader.tgz", "license": { "type": "file", "path": "LICENSE" @@ -268,7 +254,6 @@ "type": "url", "url": "https://pecl.php.net/get/uuid", "path": "php-src/ext/uuid", - "filename": "uuid.tgz", "license": { "type": "file", "path": "LICENSE" @@ -278,7 +263,6 @@ "type": "url", "url": "https://pecl.php.net/get/uv", "path": "php-src/ext/uv", - "filename": "uv.tgz", "license": { "type": "file", "path": "LICENSE" @@ -297,7 +281,6 @@ "ext-zip": { "type": "url", "url": "https://pecl.php.net/get/zip", - "filename": "ext-zip.tgz", "license": { "type": "file", "path": "LICENSE" @@ -405,7 +388,6 @@ "type": "url", "url": "https://pecl.php.net/get/igbinary", "path": "php-src/ext/igbinary", - "filename": "igbinary.tgz", "license": { "type": "file", "path": "COPYING" @@ -432,7 +414,6 @@ "type": "url", "url": "https://pecl.php.net/get/inotify", "path": "php-src/ext/inotify", - "filename": "inotify.tgz", "license": { "type": "file", "path": "LICENSE" @@ -837,7 +818,6 @@ "type": "url", "url": "https://pecl.php.net/get/memcached", "path": "php-src/ext/memcached", - "filename": "memcached.tgz", "license": { "type": "file", "path": "LICENSE" @@ -878,7 +858,6 @@ "type": "url", "url": "https://pecl.php.net/get/msgpack", "path": "php-src/ext/msgpack", - "filename": "msgpack.tgz", "license": { "type": "file", "path": "LICENSE" @@ -981,7 +960,6 @@ "type": "url", "url": "https://pecl.php.net/get/opentelemetry", "path": "php-src/ext/opentelemetry", - "filename": "opentelemetry.tgz", "license": { "type": "file", "path": "LICENSE" @@ -991,7 +969,6 @@ "type": "url", "url": "https://pecl.php.net/get/parallel", "path": "php-src/ext/parallel", - "filename": "parallel.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1000,7 +977,6 @@ "pcov": { "type": "url", "url": "https://pecl.php.net/get/pcov", - "filename": "pcov.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1010,7 +986,6 @@ "type": "url", "url": "https://pecl.php.net/get/pdo_sqlsrv", "path": "php-src/ext/pdo_sqlsrv", - "filename": "pdo_sqlsrv.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1046,7 +1021,6 @@ "type": "url", "url": "https://pecl.php.net/get/protobuf", "path": "php-src/ext/protobuf", - "filename": "protobuf.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1108,7 +1082,6 @@ "type": "url", "url": "https://pecl.php.net/get/redis", "path": "php-src/ext/redis", - "filename": "redis.tgz", "license": { "type": "file", "path": [ @@ -1148,7 +1121,6 @@ "type": "url", "url": "https://pecl.php.net/get/sqlsrv", "path": "php-src/ext/sqlsrv", - "filename": "sqlsrv.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1214,7 +1186,6 @@ "type": "url", "url": "https://pecl.php.net/get/xhprof", "path": "php-src/ext/xhprof-src", - "filename": "xhprof.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1224,7 +1195,6 @@ "type": "url", "url": "https://pecl.php.net/get/xlswriter", "path": "php-src/ext/xlswriter", - "filename": "xlswriter.tgz", "license": { "type": "file", "path": "LICENSE" @@ -1245,7 +1215,6 @@ "type": "url", "url": "https://pecl.php.net/get/yac", "path": "php-src/ext/yac", - "filename": "yac.tgz", "license": { "type": "file", "path": "LICENSE" diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index 73e75018e..9422f9144 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -689,8 +689,7 @@ private static function downloadByType(string $type, string $name, array $conf, self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as); break; case 'url': // Direct download URL - $url = $conf['url']; - $filename = $conf['filename'] ?? basename($conf['url']); + [$url, $filename] = self::getLatestUrlInfo($name, $conf); self::downloadFile($name, $url, $filename, $conf['path'] ?? $conf['extract'] ?? null, $download_as); break; case 'git': // Git repo @@ -700,6 +699,7 @@ private static function downloadByType(string $type, string $name, array $conf, LockFile::lockSource($name, [ 'source_type' => SPC_SOURCE_LOCAL, 'dirname' => $conf['dirname'], + 'url' => null, 'path' => $conf['path'] ?? null, 'move_path' => $conf['path'] ?? $conf['extract'] ?? null, 'lock_as' => $download_as, From ce7829fd13d477d57da19167b32400cbff3c4439 Mon Sep 17 00:00:00 2001 From: henderkes Date: Thu, 1 Jan 2026 15:38:35 +0100 Subject: [PATCH 7/7] support for php-src switching by downloading different php version (like it was before, essentially) --- src/SPC/command/BuildPHPCommand.php | 5 ----- src/SPC/command/DownloadCommand.php | 5 +++-- src/SPC/store/source/PhpSource.php | 5 +++++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 9871eeb45..0294f350e 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -134,10 +134,6 @@ public function handle(): int // Use version-specific php-src f_putenv("SPC_PHP_SRC_NAME={$version_specific_name}"); logger()->info("Building with PHP {$php_version} (using {$version_specific_name})"); - } elseif (isset($lock_content['php-src'])) { - // Fall back to regular php-src - f_putenv('SPC_PHP_SRC_NAME=php-src'); - logger()->warning("php-src-{$php_version} not found, using default php-src"); } else { logger()->error('No php-src found in downloads. Please run download command first.'); return static::FAILURE; @@ -147,7 +143,6 @@ public function handle(): int return static::FAILURE; } } else { - // No version specified, use default php-src f_putenv('SPC_PHP_SRC_NAME=php-src'); } diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index fb1179e32..84fa57531 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -280,8 +280,9 @@ public function handle(): int logger()->warning("Pre-built content not found for {$source}, fallback to source download"); } logger()->info("[{$ni}/{$cnt}] Downloading source {$source}"); + $force_download = $force_all || in_array($source, $force_list) || str_starts_with($source, 'php-src-') && in_array('php-src', $force_list); try { - Downloader::downloadSource($source, $config, $force_all || in_array($source, $force_list)); + Downloader::downloadSource($source, $config, $force_download); } catch (SPCException $e) { // if `--no-alt` option is set, we will not download alternative sources if ($this->getOption('no-alt')) { @@ -299,7 +300,7 @@ public function handle(): int logger()->notice("Trying to download alternative sources for {$source}"); $alt_config = array_merge($config, $alt_sources); } - Downloader::downloadSource($source, $alt_config, $force_all || in_array($source, $force_list)); + Downloader::downloadSource($source, $alt_config, $force_download); } } } diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php index 079d52878..f5c9063f0 100644 --- a/src/SPC/store/source/PhpSource.php +++ b/src/SPC/store/source/PhpSource.php @@ -7,6 +7,7 @@ use JetBrains\PhpStorm\ArrayShape; use SPC\exception\DownloaderException; use SPC\store\Downloader; +use SPC\store\LockFile; class PhpSource extends CustomSourceBase { @@ -28,6 +29,10 @@ public function fetch(bool $force = false, ?array $config = null, int $lock_as = } else { Downloader::downloadSource($source_name, $this->getLatestPHPInfo($major), $force); } + + if ($source_name !== 'php-src') { + LockFile::put('php-src', LockFile::get($source_name)); + } } public function update(array $lock, ?array $config = null): ?array