Allow locking different arch pre-built content

This commit is contained in:
crazywhalecc 2025-03-30 16:53:14 +08:00
parent 5648681ecc
commit 87c0535624
No known key found for this signature in database
GPG Key ID: 1F4BDD59391F2680
12 changed files with 150 additions and 76 deletions

View File

@ -1,6 +1,7 @@
{
"repo": "static-php/static-php-cli-hosted",
"prefer-stable": true,
"match-pattern-linux": "{name}-{arch}-{os}-{libc}.txz",
"match-pattern": "{name}-{arch}-{os}.txz",
"suffix": "txz"
}

View File

@ -8,6 +8,7 @@ use SPC\exception\FileSystemException;
use SPC\exception\RuntimeException;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use SPC\store\SourceManager;
@ -45,8 +46,9 @@ abstract class LibraryBase
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$source = Config::getLib(static::NAME, 'source');
// if source is locked as pre-built, we just tryInstall it
if (isset($lock[$source]) && ($lock[$source]['lock_as'] ?? SPC_LOCK_SOURCE) === SPC_LOCK_PRE_BUILT) {
return $this->tryInstall($lock[$source]['filename'], $force);
$pre_built_name = Downloader::getPreBuiltName($source);
if (isset($lock[$pre_built_name]) && ($lock[$pre_built_name]['lock_as'] ?? SPC_DOWN_SOURCE) === SPC_DOWN_PRE_BUILT) {
return $this->tryInstall($lock[$pre_built_name]['filename'], $force);
}
return $this->tryBuild($force);
}

View File

@ -7,6 +7,7 @@ namespace SPC\command;
use SPC\exception\DownloaderException;
use SPC\exception\FileSystemException;
use SPC\exception\WrongUsageException;
use SPC\store\Downloader;
use SPC\store\FileSystem;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
@ -47,30 +48,35 @@ class DeleteDownloadCommand extends BaseCommand
$chosen_sources = $sources;
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$deleted_sources = [];
foreach ($chosen_sources as $source) {
$source = trim($source);
if (!isset($lock[$source])) {
logger()->warning("Source/Package [{$source}] not locked or not downloaded, skipped.");
continue;
foreach ([$source, Downloader::getPreBuiltName($source)] as $name) {
if (isset($lock[$name])) {
$deleted_sources[] = $name;
}
}
}
foreach ($deleted_sources as $lock_name) {
// remove download file/dir if exists
if ($lock[$source]['source_type'] === 'archive') {
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$source]['filename']))) {
if ($lock[$lock_name]['source_type'] === 'archive') {
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']))) {
logger()->info('Deleting file ' . $path);
unlink($path);
} else {
logger()->warning("Source/Package [{$source}] file not found, skip deleting file.");
logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file.");
}
} else {
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$source]['dirname']))) {
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname']))) {
logger()->info('Deleting dir ' . $path);
FileSystem::removeDir($path);
} else {
logger()->warning("Source/Package [{$source}] directory not found, skip deleting dir.");
logger()->warning("Source/Package [{$lock_name}] directory not found, skip deleting dir.");
}
}
// remove locked sources
unset($lock[$source]);
unset($lock[$lock_name]);
}
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
logger()->info('Delete success!');

View File

@ -231,11 +231,17 @@ class DownloadCommand extends BaseCommand
// 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
$find = str_replace(['{name}', '{arch}', '{os}'], [$source, arch2gnu(php_uname('m')), strtolower(PHP_OS_FAMILY)], Config::getPreBuilt('match-pattern'));
$replace = [
'{name}' => $source,
'{arch}' => arch2gnu(php_uname('m')),
'{os}' => strtolower(PHP_OS_FAMILY),
'{libc}' => getenv('SPC_LIBC') ?: 'default',
];
$find = str_replace(array_keys($replace), array_values($replace), Config::getPreBuilt('match-pattern'));
// find filename in asset list
if (($url = $this->findPreBuilt($pre_built_libs, $find)) !== null) {
logger()->info("[{$ni}/{$cnt}] Downloading pre-built content {$source}");
Downloader::downloadSource($source, ['type' => 'url', 'url' => $url], $force_all || in_array($source, $force_list), SPC_LOCK_PRE_BUILT);
Downloader::downloadSource($source, ['type' => 'url', 'url' => $url], $force_all || in_array($source, $force_list), SPC_DOWN_PRE_BUILT);
continue;
}
logger()->warning("Pre-built content not found for {$source}, fallback to source download");

View File

@ -47,7 +47,7 @@ class PackLibCommand extends BuildCommand
// Get lock info
$lock = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
$source = Config::getLib($lib->getName(), 'source');
if (!isset($lock[$source]) || ($lock[$source]['lock_as'] ?? SPC_LOCK_SOURCE) === SPC_LOCK_PRE_BUILT) {
if (!isset($lock[$source]) || ($lock[$source]['lock_as'] ?? SPC_DOWN_SOURCE) === SPC_DOWN_PRE_BUILT) {
logger()->critical("The library {$lib->getName()} is downloaded as pre-built, we need to build it instead of installing pre-built.");
return static::FAILURE;
}
@ -69,7 +69,15 @@ class PackLibCommand extends BuildCommand
// write list to packlib_files.txt
FileSystem::writeFile(WORKING_DIR . '/packlib_files.txt', implode("\n", $increase_files));
// pack
$filename = WORKING_DIR . '/dist/' . $lib->getName() . '-' . arch2gnu(php_uname('m')) . '-' . strtolower(PHP_OS_FAMILY) . '.' . Config::getPreBuilt('suffix');
$filename = Config::getPreBuilt('match-pattern');
$replace = [
'{name}' => $lib->getName(),
'{arch}' => arch2gnu(php_uname('m')),
'{os}' => strtolower(PHP_OS_FAMILY),
'{libc}' => getenv('SPC_LIBC') ?: 'default',
];
$filename = str_replace(array_keys($replace), array_values($replace), $filename);
$filename = WORKING_DIR . '/dist/' . $filename . '.' . Config::getPreBuilt('suffix');
f_passthru('tar -czf ' . $filename . ' -T ' . WORKING_DIR . '/packlib_files.txt');
logger()->info('Pack library ' . $lib->getName() . ' to ' . $filename . ' complete.');
}

View File

@ -22,11 +22,30 @@ class Config
public static ?array $pre_built = null;
/**
* @throws WrongUsageException
* @throws FileSystemException
*/
public static function getPreBuilt(string $name): mixed
{
if (self::$pre_built === null) {
self::$pre_built = FileSystem::loadConfigArray('pre-built');
}
$supported_sys_based = ['match-pattern'];
if (in_array($name, $supported_sys_based)) {
$m_key = match (PHP_OS_FAMILY) {
'Windows' => ['-windows', '-win', ''],
'Darwin' => ['-macos', '-unix', ''],
'Linux' => ['-linux', '-unix', ''],
'BSD' => ['-freebsd', '-bsd', '-unix', ''],
default => throw new WrongUsageException('OS ' . PHP_OS_FAMILY . ' is not supported'),
};
foreach ($m_key as $v) {
if (isset(self::$pre_built["{$name}{$v}"])) {
return self::$pre_built["{$name}{$v}"];
}
}
}
return self::$pre_built[$name] ?? null;
}

View File

@ -189,7 +189,7 @@ class Downloader
* @throws RuntimeException
* @throws WrongUsageException
*/
public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null, int $lock_as = SPC_LOCK_SOURCE): void
public static function downloadFile(string $name, string $url, string $filename, ?string $move_path = null, int $download_as = SPC_DOWN_SOURCE): void
{
logger()->debug("Downloading {$url}");
$cancel_func = function () use ($filename) {
@ -202,12 +202,23 @@ class Downloader
self::curlDown(url: $url, path: FileSystem::convertPath(DOWNLOAD_PATH . "/{$filename}"), retry: self::getRetryTime());
self::unregisterCancelEvent();
logger()->debug("Locking {$filename}");
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $lock_as]);
if ($download_as === SPC_DOWN_PRE_BUILT) {
$name = self::getPreBuiltName($name);
}
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
}
/**
* Try to lock source.
*
* @param string $name Source name
* @param array{
* source_type: string,
* dirname: ?string,
* filename: ?string,
* move_path: ?string,
* lock_as: int
* } $data Source data
* @throws FileSystemException
*/
public static function lockSource(string $name, array $data): void
@ -228,7 +239,7 @@ class Downloader
* @throws RuntimeException
* @throws WrongUsageException
*/
public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null, int $retry = 0, int $lock_as = SPC_LOCK_SOURCE): void
public static function downloadGit(string $name, string $url, string $branch, ?string $move_path = null, int $retry = 0, int $lock_as = SPC_DOWN_SOURCE): void
{
$download_path = FileSystem::convertPath(DOWNLOAD_PATH . "/{$name}");
if (file_exists($download_path)) {
@ -316,50 +327,36 @@ class Downloader
FileSystem::createDir(DOWNLOAD_PATH);
}
// load lock file
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading
if (isset($lock[$name]) && !$force) {
if ($lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename'])) {
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['filename']);
return;
}
if ($lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])) {
logger()->notice("Package [{$name}] already downloaded: " . $lock[$name]['dirname']);
return;
}
if (self::isAlreadyDownloaded($name, $force, SPC_DOWN_PACKAGE)) {
return;
}
try {
switch ($pkg['type']) {
case 'bitbuckettag': // BitBucket Tag
[$url, $filename] = self::getLatestBitbucketTag($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWN_PACKAGE);
break;
case 'ghtar': // GitHub Release (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWN_PACKAGE);
break;
case 'ghtagtar': // GitHub Tag (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $pkg, 'tags');
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWN_PACKAGE);
break;
case 'ghrel': // GitHub Release (uploaded)
[$url, $filename] = self::getLatestGithubRelease($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWN_PACKAGE);
break;
case 'filelist': // Basic File List (regex based crawler)
[$url, $filename] = self::getFromFileList($name, $pkg);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWN_PACKAGE);
break;
case 'url': // Direct download URL
$url = $pkg['url'];
$filename = $pkg['filename'] ?? basename($pkg['url']);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_LOCK_PRE_BUILT);
self::downloadFile($name, $url, $filename, $pkg['extract'] ?? null, SPC_DOWN_PACKAGE);
break;
case 'git': // Git repo
self::downloadGit(
@ -368,7 +365,7 @@ class Downloader
$pkg['rev'],
$pkg['extract'] ?? null,
self::getRetryTime(),
SPC_LOCK_PRE_BUILT
SPC_DOWN_PRE_BUILT
);
break;
case 'custom': // Custom download method, like API-based download or other
@ -414,13 +411,13 @@ class Downloader
* text: ?string
* }
* } $source source meta info: [type, path, rev, url, filename, regex, license]
* @param bool $force Whether to force download (default: false)
* @param int $lock_as Lock source type (default: SPC_LOCK_SOURCE)
* @param bool $force Whether to force download (default: false)
* @param int $download_as Lock source type (default: SPC_LOCK_SOURCE)
* @throws DownloaderException
* @throws FileSystemException
* @throws WrongUsageException
*/
public static function downloadSource(string $name, ?array $source = null, bool $force = false, int $lock_as = SPC_LOCK_SOURCE): void
public static function downloadSource(string $name, ?array $source = null, bool $force = false, int $download_as = SPC_DOWN_SOURCE): void
{
if ($source === null) {
$source = Config::getSource($name);
@ -436,49 +433,36 @@ class Downloader
}
// load lock file
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading
if (isset($lock[$name]) && !$force && ($lock[$name]['lock_as'] ?? SPC_LOCK_SOURCE) === $lock_as) {
if ($lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename'])) {
logger()->notice("Source [{$name}] already downloaded: " . $lock[$name]['filename']);
return;
}
if ($lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])) {
logger()->notice("Source [{$name}] already downloaded: " . $lock[$name]['dirname']);
return;
}
if (self::isAlreadyDownloaded($name, $force, $download_as)) {
return;
}
try {
switch ($source['type']) {
case 'bitbuckettag': // BitBucket Tag
[$url, $filename] = self::getLatestBitbucketTag($name, $source);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break;
case 'ghtar': // GitHub Release (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $source);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break;
case 'ghtagtar': // GitHub Tag (tar)
[$url, $filename] = self::getLatestGithubTarball($name, $source, 'tags');
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break;
case 'ghrel': // GitHub Release (uploaded)
[$url, $filename] = self::getLatestGithubRelease($name, $source);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break;
case 'filelist': // Basic File List (regex based crawler)
[$url, $filename] = self::getFromFileList($name, $source);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break;
case 'url': // Direct download URL
$url = $source['url'];
$filename = $source['filename'] ?? basename($source['url']);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $lock_as);
self::downloadFile($name, $url, $filename, $source['path'] ?? null, $download_as);
break;
case 'git': // Git repo
self::downloadGit(
@ -487,14 +471,14 @@ class Downloader
$source['rev'],
$source['path'] ?? null,
self::getRetryTime(),
$lock_as
$download_as
);
break;
case 'custom': // Custom download method, like API-based download or other
$classes = FileSystem::getClassesPsr4(ROOT_DIR . '/src/SPC/store/source', 'SPC\store\source');
foreach ($classes as $class) {
if (is_a($class, CustomSourceBase::class, true) && $class::NAME === $name) {
(new $class())->fetch($force, $source, $lock_as);
(new $class())->fetch($force, $source, $download_as);
break;
}
}
@ -609,6 +593,11 @@ class Downloader
}
}
public static function getPreBuiltName(string $source): string
{
return "{$source}-" . PHP_OS_FAMILY . '-' . getenv('GNU_ARCH') . '-' . (getenv('SPC_LIBC') ?: 'default');
}
/**
* Register CTRL+C event for different OS.
*
@ -641,4 +630,39 @@ class Downloader
{
return intval(getenv('SPC_RETRY_TIME') ? getenv('SPC_RETRY_TIME') : 0);
}
/**
* @throws FileSystemException
*/
private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWN_SOURCE): bool
{
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
$lock = [];
} else {
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
}
// If lock file exists, skip downloading for source mode
if (!$force && $download_as === SPC_DOWN_SOURCE && isset($lock[$name])) {
if (
$lock[$name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename']) ||
$lock[$name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])
) {
logger()->notice("Source [{$name}] already downloaded: " . ($lock[$name]['filename'] ?? $lock[$name]['dirname']));
return true;
}
}
// If lock file exists for current arch and glibc target, skip downloading
$lock_name = self::getPreBuiltName($name);
if (!$force && $download_as === SPC_DOWN_PRE_BUILT && isset($lock[$lock_name])) {
// lock name with env
if (
$lock[$lock_name]['source_type'] === 'archive' && file_exists(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']) ||
$lock[$lock_name]['source_type'] === 'dir' && is_dir(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname'])
) {
logger()->notice("Pre-built content [{$name}] already downloaded: " . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname']));
return true;
}
}
return false;
}
}

View File

@ -54,15 +54,22 @@ class SourceManager
if (Config::getSource($source) === null) {
throw new WrongUsageException("Source [{$source}] does not exist, please check the name and correct it !");
}
if (!isset($lock[$source])) {
throw new WrongUsageException('Source [' . $source . '] not downloaded or not locked, you should download it first !');
// check source downloaded
$pre_built_name = Downloader::getPreBuiltName($source);
if (!isset($lock[$pre_built_name])) {
if (!isset($lock[$source])) {
throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !");
}
$lock_name = $source;
} else {
$lock_name = $pre_built_name;
}
// check source dir exist
$check = $lock[$source]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$source]['move_path']);
$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 . ' ...');
FileSystem::extractSource($source, DOWNLOAD_PATH . '/' . ($lock[$source]['filename'] ?? $lock[$source]['dirname']), $lock[$source]['move_path']);
FileSystem::extractSource($source, DOWNLOAD_PATH . '/' . ($lock[$lock_name]['filename'] ?? $lock[$lock_name]['dirname']), $lock[$lock_name]['move_path']);
} else {
logger()->debug('Source [' . $source . '] already extracted in ' . $check . ', skip !');
}

View File

@ -8,5 +8,5 @@ abstract class CustomSourceBase
{
public const NAME = 'unknown';
abstract public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_LOCK_SOURCE): void;
abstract public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWN_SOURCE): void;
}

View File

@ -17,7 +17,7 @@ class PhpSource extends CustomSourceBase
* @throws DownloaderException
* @throws FileSystemException
*/
public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_LOCK_SOURCE): void
public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWN_SOURCE): void
{
$major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.3';
Downloader::downloadSource('php-src', self::getLatestPHPInfo($major), $force);

View File

@ -16,7 +16,7 @@ class PostgreSQLSource extends CustomSourceBase
* @throws DownloaderException
* @throws FileSystemException
*/
public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_LOCK_SOURCE): void
public function fetch(bool $force = false, ?array $config = null, int $lock_as = SPC_DOWN_SOURCE): void
{
Downloader::downloadSource('postgresql', self::getLatestInfo(), $force);
}

View File

@ -41,8 +41,9 @@ const SPC_EXTENSION_ALIAS = [
];
// spc lock type
const SPC_LOCK_SOURCE = 1; // lock source
const SPC_LOCK_PRE_BUILT = 2; // lock pre-built
const SPC_DOWN_SOURCE = 1; // lock source
const SPC_DOWN_PRE_BUILT = 2; // lock pre-built
const SPC_DOWN_PACKAGE = 3; // lock as package
// file replace strategy
const REPLACE_FILE_STR = 1;