diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index fdba936d..ec733a8e 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -160,7 +160,7 @@ abstract class BuilderBase } 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 @@ abstract class BuilderBase 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 @@ abstract class BuilderBase } } + /** + * 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 ad884b3b..9871eeb4 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -49,6 +49,7 @@ class BuildPHPCommand extends BuildCommand $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 @@ class BuildPHPCommand extends BuildCommand 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 00bcc194..a843aa36 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -30,7 +30,7 @@ class DownloadCommand extends BaseCommand $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 @@ class DownloadCommand extends BaseCommand 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 @@ class DownloadCommand extends BaseCommand $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 @@ class DownloadCommand extends BaseCommand 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 ccf61dd8..7d685d67 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -682,7 +682,11 @@ class Downloader ...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 35728e22..ef545e48 100644 --- a/src/SPC/store/SourceManager.php +++ b/src/SPC/store/SourceManager.php @@ -39,7 +39,14 @@ class SourceManager // 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 @@ class SourceManager $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 @@ class SourceManager 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 27e8bb89..6c4049bc 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'; - if ($major === 'git') { - Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force); + $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 { - Downloader::downloadSource('php-src', $this->getLatestPHPInfo($major), $force); + $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4'; + } + + if ($major === 'git') { + Downloader::downloadSource($source_name, ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force); + } else { + Downloader::downloadSource($source_name, $this->getLatestPHPInfo($major), $force); } }