mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-03-18 04:44:53 +08:00
Refactor lock component to a single class (#773)
This commit is contained in:
parent
1c439a01a1
commit
ba0ea5b40a
@ -12,6 +12,7 @@ use SPC\exception\RuntimeException;
|
|||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use SPC\store\SourceManager;
|
use SPC\store\SourceManager;
|
||||||
use SPC\util\CustomExt;
|
use SPC\util\CustomExt;
|
||||||
|
|
||||||
@ -350,15 +351,11 @@ abstract class BuilderBase
|
|||||||
public function getPHPVersionFromArchive(?string $file = null): false|string
|
public function getPHPVersionFromArchive(?string $file = null): false|string
|
||||||
{
|
{
|
||||||
if ($file === null) {
|
if ($file === null) {
|
||||||
$lock = file_exists(DOWNLOAD_PATH . '/.lock.json') ? file_get_contents(DOWNLOAD_PATH . '/.lock.json') : false;
|
$lock = LockFile::get('php-src');
|
||||||
if ($lock === false) {
|
if ($lock === null) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$lock = json_decode($lock, true);
|
|
||||||
$file = $lock['php-src']['filename'] ?? null;
|
|
||||||
if ($file === null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$file = LockFile::getLockFullPath($lock);
|
||||||
}
|
}
|
||||||
if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) {
|
if (preg_match('/php-(\d+\.\d+\.\d+(?:RC\d+)?)\.tar\.(?:gz|bz2|xz)/', $file, $match)) {
|
||||||
return $match[1];
|
return $match[1];
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use SPC\exception\WrongUsageException;
|
|||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\Downloader;
|
use SPC\store\Downloader;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use SPC\store\SourceManager;
|
use SPC\store\SourceManager;
|
||||||
use SPC\util\GlobalValueTrait;
|
use SPC\util\GlobalValueTrait;
|
||||||
|
|
||||||
@ -46,12 +47,11 @@ abstract class LibraryBase
|
|||||||
*/
|
*/
|
||||||
public function setup(bool $force = false): int
|
public function setup(bool $force = false): int
|
||||||
{
|
{
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
|
||||||
$source = Config::getLib(static::NAME, 'source');
|
$source = Config::getLib(static::NAME, 'source');
|
||||||
// if source is locked as pre-built, we just tryInstall it
|
// if source is locked as pre-built, we just tryInstall it
|
||||||
$pre_built_name = Downloader::getPreBuiltLockName($source);
|
$pre_built_name = Downloader::getPreBuiltLockName($source);
|
||||||
if (isset($lock[$pre_built_name]) && ($lock[$pre_built_name]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) {
|
if (($lock = LockFile::get($pre_built_name)) && $lock['lock_as'] === SPC_DOWNLOAD_PRE_BUILT) {
|
||||||
return $this->tryInstall($lock[$pre_built_name], $force);
|
return $this->tryInstall($lock, $force);
|
||||||
}
|
}
|
||||||
return $this->tryBuild($force);
|
return $this->tryBuild($force);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ use SPC\exception\FileSystemException;
|
|||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Downloader;
|
use SPC\store\Downloader;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
@ -46,29 +47,29 @@ class DeleteDownloadCommand extends BaseCommand
|
|||||||
return static::SUCCESS;
|
return static::SUCCESS;
|
||||||
}
|
}
|
||||||
$chosen_sources = $sources;
|
$chosen_sources = $sources;
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
|
||||||
|
|
||||||
$deleted_sources = [];
|
$deleted_sources = [];
|
||||||
foreach ($chosen_sources as $source) {
|
foreach ($chosen_sources as $source) {
|
||||||
$source = trim($source);
|
$source = trim($source);
|
||||||
foreach ([$source, Downloader::getPreBuiltLockName($source)] as $name) {
|
foreach ([$source, Downloader::getPreBuiltLockName($source)] as $name) {
|
||||||
if (isset($lock[$name])) {
|
if (LockFile::get($name)) {
|
||||||
$deleted_sources[] = $name;
|
$deleted_sources[] = $name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($deleted_sources as $lock_name) {
|
foreach ($deleted_sources as $lock_name) {
|
||||||
|
$lock = LockFile::get($lock_name);
|
||||||
// remove download file/dir if exists
|
// remove download file/dir if exists
|
||||||
if ($lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE) {
|
if ($lock['source_type'] === SPC_SOURCE_ARCHIVE) {
|
||||||
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']))) {
|
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['filename']))) {
|
||||||
logger()->info('Deleting file ' . $path);
|
logger()->info('Deleting file ' . $path);
|
||||||
unlink($path);
|
unlink($path);
|
||||||
} else {
|
} else {
|
||||||
logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file.");
|
logger()->warning("Source/Package [{$lock_name}] file not found, skip deleting file.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['dirname']))) {
|
if (is_dir($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock['dirname']))) {
|
||||||
logger()->info('Deleting dir ' . $path);
|
logger()->info('Deleting dir ' . $path);
|
||||||
FileSystem::removeDir($path);
|
FileSystem::removeDir($path);
|
||||||
} else {
|
} else {
|
||||||
@ -76,9 +77,8 @@ class DeleteDownloadCommand extends BaseCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remove locked sources
|
// remove locked sources
|
||||||
unset($lock[$lock_name]);
|
LockFile::put($lock_name, null);
|
||||||
}
|
}
|
||||||
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
||||||
logger()->info('Delete success!');
|
logger()->info('Delete success!');
|
||||||
return static::SUCCESS;
|
return static::SUCCESS;
|
||||||
} catch (DownloaderException $e) {
|
} catch (DownloaderException $e) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ namespace SPC\command;
|
|||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\Downloader;
|
use SPC\store\Downloader;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
@ -40,16 +41,9 @@ class SwitchPhpVersionCommand extends BaseCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// detect if downloads/.lock.json exists
|
if (LockFile::isLockFileExists('php-src')) {
|
||||||
$lock_file = DOWNLOAD_PATH . '/.lock.json';
|
|
||||||
// parse php-src part of lock file
|
|
||||||
$lock_data = json_decode(file_get_contents($lock_file), true);
|
|
||||||
// get php-src downloaded file name
|
|
||||||
$php_src = $lock_data['php-src'];
|
|
||||||
$file = DOWNLOAD_PATH . '/' . ($php_src['filename'] ?? '.donot.delete.me');
|
|
||||||
if (file_exists($file)) {
|
|
||||||
$this->output->writeln('<info>Removing old PHP source...</info>');
|
$this->output->writeln('<info>Removing old PHP source...</info>');
|
||||||
unlink($file);
|
LockFile::put('php-src', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download new PHP source
|
// Download new PHP source
|
||||||
|
|||||||
@ -14,6 +14,7 @@ use SPC\exception\RuntimeException;
|
|||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Config;
|
use SPC\store\Config;
|
||||||
use SPC\store\FileSystem;
|
use SPC\store\FileSystem;
|
||||||
|
use SPC\store\LockFile;
|
||||||
use SPC\util\DependencyUtil;
|
use SPC\util\DependencyUtil;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
@ -47,9 +48,8 @@ class PackLibCommand extends BuildCommand
|
|||||||
$lib->setup();
|
$lib->setup();
|
||||||
} else {
|
} else {
|
||||||
// Get lock info
|
// Get lock info
|
||||||
$lock = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
|
||||||
$source = Config::getLib($lib->getName(), 'source');
|
$source = Config::getLib($lib->getName(), 'source');
|
||||||
if (!isset($lock[$source]) || ($lock[$source]['lock_as'] ?? SPC_DOWNLOAD_SOURCE) === SPC_DOWNLOAD_PRE_BUILT) {
|
if (($lock = LockFile::get($source)) === null || ($lock['lock_as'] === SPC_DOWNLOAD_PRE_BUILT)) {
|
||||||
logger()->critical("The library {$lib->getName()} is downloaded as pre-built, we need to build it instead of installing 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;
|
return static::FAILURE;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -208,34 +208,7 @@ class Downloader
|
|||||||
if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
|
if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
|
||||||
$name = self::getPreBuiltLockName($name);
|
$name = self::getPreBuiltLockName($name);
|
||||||
}
|
}
|
||||||
self::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, '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
|
|
||||||
{
|
|
||||||
if (!file_exists(FileSystem::convertPath(DOWNLOAD_PATH . '/.lock.json'))) {
|
|
||||||
$lock = [];
|
|
||||||
} else {
|
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
|
||||||
}
|
|
||||||
// calculate hash
|
|
||||||
$hash = self::getLockSourceHash($data);
|
|
||||||
$data['hash'] = $hash;
|
|
||||||
$lock[$name] = $data;
|
|
||||||
FileSystem::writeFile(DOWNLOAD_PATH . '/.lock.json', json_encode($lock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -281,7 +254,7 @@ class Downloader
|
|||||||
}
|
}
|
||||||
// Lock
|
// Lock
|
||||||
logger()->debug("Locking git source {$name}");
|
logger()->debug("Locking git source {$name}");
|
||||||
self::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, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// 复制目录过去
|
// 复制目录过去
|
||||||
@ -377,7 +350,7 @@ class Downloader
|
|||||||
case 'local':
|
case 'local':
|
||||||
// Local directory, do nothing, just lock it
|
// Local directory, do nothing, just lock it
|
||||||
logger()->debug("Locking local source {$name}");
|
logger()->debug("Locking local source {$name}");
|
||||||
self::lockSource($name, [
|
LockFile::lockSource($name, [
|
||||||
'source_type' => SPC_SOURCE_LOCAL,
|
'source_type' => SPC_SOURCE_LOCAL,
|
||||||
'dirname' => $pkg['dirname'],
|
'dirname' => $pkg['dirname'],
|
||||||
'move_path' => $pkg['extract'] ?? null,
|
'move_path' => $pkg['extract'] ?? null,
|
||||||
@ -493,7 +466,7 @@ class Downloader
|
|||||||
case 'local':
|
case 'local':
|
||||||
// Local directory, do nothing, just lock it
|
// Local directory, do nothing, just lock it
|
||||||
logger()->debug("Locking local source {$name}");
|
logger()->debug("Locking local source {$name}");
|
||||||
self::lockSource($name, [
|
LockFile::lockSource($name, [
|
||||||
'source_type' => SPC_SOURCE_LOCAL,
|
'source_type' => SPC_SOURCE_LOCAL,
|
||||||
'dirname' => $source['dirname'],
|
'dirname' => $source['dirname'],
|
||||||
'move_path' => $source['extract'] ?? null,
|
'move_path' => $source['extract'] ?? null,
|
||||||
@ -617,43 +590,6 @@ class Downloader
|
|||||||
return "{$source}-" . PHP_OS_FAMILY . '-' . getenv('GNU_ARCH') . '-' . (getenv('SPC_LIBC') ?: 'default') . '-' . (SystemUtil::getLibcVersionIfExists() ?? 'default');
|
return "{$source}-" . PHP_OS_FAMILY . '-' . getenv('GNU_ARCH') . '-' . (getenv('SPC_LIBC') ?: 'default') . '-' . (SystemUtil::getLibcVersionIfExists() ?? 'default');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the hash of the lock source based on the lock options.
|
|
||||||
*
|
|
||||||
* @param array $lock_options Lock options
|
|
||||||
* @return string Hash of the lock source
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public static function getLockSourceHash(array $lock_options): string
|
|
||||||
{
|
|
||||||
$result = match ($lock_options['source_type']) {
|
|
||||||
SPC_SOURCE_ARCHIVE => sha1_file(DOWNLOAD_PATH . '/' . $lock_options['filename']),
|
|
||||||
SPC_SOURCE_GIT => exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $lock_options['dirname']) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD'),
|
|
||||||
SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT',
|
|
||||||
default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new RuntimeException("Unknown source type: {$lock_options['source_type']}"),
|
|
||||||
};
|
|
||||||
if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) {
|
|
||||||
throw new RuntimeException("Failed to get hash for source: {$lock_options['source_type']}");
|
|
||||||
}
|
|
||||||
return $result ?: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $lock_options Lock options
|
|
||||||
* @param string $destination Target directory
|
|
||||||
* @throws FileSystemException
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public static function putLockSourceHash(array $lock_options, string $destination): void
|
|
||||||
{
|
|
||||||
$hash = self::getLockSourceHash($lock_options);
|
|
||||||
if ($lock_options['source_type'] === SPC_SOURCE_LOCAL) {
|
|
||||||
logger()->debug("Source [{$lock_options['dirname']}] is local, no hash will be written.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FileSystem::writeFile("{$destination}/.spc-hash", $hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register CTRL+C event for different OS.
|
* Register CTRL+C event for different OS.
|
||||||
*
|
*
|
||||||
@ -689,33 +625,24 @@ class Downloader
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws FileSystemException
|
* @throws FileSystemException
|
||||||
|
* @throws WrongUsageException
|
||||||
*/
|
*/
|
||||||
private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool
|
private static function isAlreadyDownloaded(string $name, bool $force, int $download_as = SPC_DOWNLOAD_SOURCE): bool
|
||||||
{
|
{
|
||||||
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
|
// If the lock file exists, skip downloading for source mode
|
||||||
$lock = [];
|
$lock_item = LockFile::get($name);
|
||||||
} else {
|
if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && $lock_item !== null) {
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
|
||||||
}
|
logger()->notice("Source [{$name}] already downloaded: {$path}");
|
||||||
// If lock file exists, skip downloading for source mode
|
|
||||||
if (!$force && $download_as === SPC_DOWNLOAD_SOURCE && isset($lock[$name])) {
|
|
||||||
if (
|
|
||||||
$lock[$name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$name]['filename']) ||
|
|
||||||
$lock[$name]['source_type'] === SPC_SOURCE_GIT && is_dir(DOWNLOAD_PATH . '/' . $lock[$name]['dirname'])
|
|
||||||
) {
|
|
||||||
logger()->notice("Source [{$name}] already downloaded: " . ($lock[$name]['filename'] ?? $lock[$name]['dirname']));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If lock file exists for current arch and glibc target, skip downloading
|
$lock_name = self::getPreBuiltLockName($name);
|
||||||
|
$lock_item = LockFile::get($lock_name);
|
||||||
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && isset($lock[$lock_name = self::getPreBuiltLockName($name)])) {
|
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && $lock_item !== null) {
|
||||||
// lock name with env
|
// lock name with env
|
||||||
if (
|
if (file_exists($path = LockFile::getLockFullPath($lock_item))) {
|
||||||
$lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']) ||
|
logger()->notice("Pre-built content [{$name}] already downloaded: {$path}");
|
||||||
$lock[$lock_name]['source_type'] === SPC_SOURCE_GIT && 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 true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
227
src/SPC/store/LockFile.php
Normal file
227
src/SPC/store/LockFile.php
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace SPC\store;
|
||||||
|
|
||||||
|
use SPC\exception\FileSystemException;
|
||||||
|
use SPC\exception\RuntimeException;
|
||||||
|
use SPC\exception\WrongUsageException;
|
||||||
|
|
||||||
|
class LockFile
|
||||||
|
{
|
||||||
|
private const string LOCK_FILE = DOWNLOAD_PATH . '/.lock.json';
|
||||||
|
|
||||||
|
private static ?array $lock_file_content = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a lock entry by its name.
|
||||||
|
*
|
||||||
|
* @param string $lock_name Lock name to retrieve
|
||||||
|
* @return null|array{
|
||||||
|
* source_type: string,
|
||||||
|
* filename: ?string,
|
||||||
|
* dirname: ?string,
|
||||||
|
* move_path: ?string,
|
||||||
|
* lock_as: int,
|
||||||
|
* hash: string
|
||||||
|
* } Returns the lock entry as an associative array if it exists, or null if it does not
|
||||||
|
*/
|
||||||
|
public static function get(string $lock_name): ?array
|
||||||
|
{
|
||||||
|
self::init();
|
||||||
|
|
||||||
|
// Return the specific lock entry if it exists, otherwise return an empty array
|
||||||
|
$result = self::$lock_file_content[$lock_name] ?? null;
|
||||||
|
|
||||||
|
// Add old `dir` compatibility
|
||||||
|
if (($result['source_type'] ?? null) === 'dir') {
|
||||||
|
logger()->warning("Lock entry for '{$lock_name}' has 'source_type' set to 'dir', which is deprecated. Please re-download your dependencies.");
|
||||||
|
$result['source_type'] = SPC_SOURCE_GIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a lock file exists for a given lock name.
|
||||||
|
*
|
||||||
|
* @param string $lock_name Lock name to check
|
||||||
|
*/
|
||||||
|
public static function isLockFileExists(string $lock_name): bool
|
||||||
|
{
|
||||||
|
return match (self::get($lock_name)['source_type'] ?? null) {
|
||||||
|
SPC_SOURCE_ARCHIVE => file_exists(DOWNLOAD_PATH . '/' . (self::get($lock_name)['filename'] ?? '.never-exist-file')),
|
||||||
|
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => is_dir(DOWNLOAD_PATH . '/' . (self::get($lock_name)['dirname'] ?? '.never-exist-dir')),
|
||||||
|
default => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a lock entry into the lock file.
|
||||||
|
*
|
||||||
|
* @param string $lock_name Lock name to set or remove
|
||||||
|
* @param null|array $lock_content lock content to set, or null to remove the lock entry
|
||||||
|
* @throws FileSystemException
|
||||||
|
* @throws WrongUsageException
|
||||||
|
*/
|
||||||
|
public static function put(string $lock_name, ?array $lock_content): void
|
||||||
|
{
|
||||||
|
self::init();
|
||||||
|
|
||||||
|
$data = self::$lock_file_content;
|
||||||
|
if ($lock_content === null && isset($data[$lock_name])) {
|
||||||
|
self::removeLockFileIfExists($data[$lock_name]);
|
||||||
|
unset($data[$lock_name]);
|
||||||
|
} else {
|
||||||
|
$data[$lock_name] = $lock_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the updated lock data back to the file
|
||||||
|
file_put_contents(self::LOCK_FILE, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full path of a lock file or directory based on the lock options.
|
||||||
|
*
|
||||||
|
* @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname'
|
||||||
|
* @return string the absolute path to the lock file or directory
|
||||||
|
* @throws WrongUsageException
|
||||||
|
*/
|
||||||
|
public static function getLockFullPath(array $lock_options): string
|
||||||
|
{
|
||||||
|
return match ($lock_options['source_type']) {
|
||||||
|
SPC_SOURCE_ARCHIVE => FileSystem::isRelativePath($lock_options['filename']) ? (DOWNLOAD_PATH . '/' . $lock_options['filename']) : $lock_options['filename'],
|
||||||
|
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => FileSystem::isRelativePath($lock_options['dirname']) ? (DOWNLOAD_PATH . '/' . $lock_options['dirname']) : $lock_options['dirname'],
|
||||||
|
default => throw new WrongUsageException("Unknown source type: {$lock_options['source_type']}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getExtractPath(string $lock_name, string $default_path): ?string
|
||||||
|
{
|
||||||
|
$lock = self::get($lock_name);
|
||||||
|
if ($lock === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If move_path is set, use it; otherwise, use the default extract directory
|
||||||
|
if (isset($lock['move_path'])) {
|
||||||
|
if (FileSystem::isRelativePath($lock['move_path'])) {
|
||||||
|
// If move_path is relative, prepend the default extract directory
|
||||||
|
return match ($lock['lock_as']) {
|
||||||
|
SPC_DOWNLOAD_SOURCE, SPC_DOWNLOAD_PRE_BUILT => FileSystem::convertPath(SOURCE_PATH . '/' . $lock['move_path']),
|
||||||
|
SPC_DOWNLOAD_PACKAGE => FileSystem::convertPath(PKG_ROOT_PATH . '/' . $lock['move_path']),
|
||||||
|
default => throw new WrongUsageException("Unknown lock type: {$lock['lock_as']}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return FileSystem::convertPath($lock['move_path']);
|
||||||
|
}
|
||||||
|
return FileSystem::convertPath($default_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the hash of the lock source based on the lock options.
|
||||||
|
*
|
||||||
|
* @param array $lock_options Lock options
|
||||||
|
* @return string Hash of the lock source
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public static function getLockSourceHash(array $lock_options): string
|
||||||
|
{
|
||||||
|
$result = match ($lock_options['source_type']) {
|
||||||
|
SPC_SOURCE_ARCHIVE => sha1_file(DOWNLOAD_PATH . '/' . $lock_options['filename']),
|
||||||
|
SPC_SOURCE_GIT => exec('cd ' . escapeshellarg(DOWNLOAD_PATH . '/' . $lock_options['dirname']) . ' && ' . SPC_GIT_EXEC . ' rev-parse HEAD'),
|
||||||
|
SPC_SOURCE_LOCAL => 'LOCAL HASH IS ALWAYS DIFFERENT',
|
||||||
|
default => filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN) ? '' : throw new RuntimeException("Unknown source type: {$lock_options['source_type']}"),
|
||||||
|
};
|
||||||
|
if ($result === false && !filter_var(getenv('SPC_IGNORE_BAD_HASH'), FILTER_VALIDATE_BOOLEAN)) {
|
||||||
|
throw new RuntimeException("Failed to get hash for source: {$lock_options['source_type']}");
|
||||||
|
}
|
||||||
|
return $result ?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $lock_options Lock options
|
||||||
|
* @param string $destination Target directory
|
||||||
|
* @throws FileSystemException
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public static function putLockSourceHash(array $lock_options, string $destination): void
|
||||||
|
{
|
||||||
|
$hash = LockFile::getLockSourceHash($lock_options);
|
||||||
|
if ($lock_options['source_type'] === SPC_SOURCE_LOCAL) {
|
||||||
|
logger()->debug("Source [{$lock_options['dirname']}] is local, no hash will be written.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FileSystem::writeFile("{$destination}/.spc-hash", $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to lock source with hash.
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
* @throws RuntimeException
|
||||||
|
* @throws WrongUsageException
|
||||||
|
*/
|
||||||
|
public static function lockSource(string $name, array $data): void
|
||||||
|
{
|
||||||
|
// calculate hash
|
||||||
|
$hash = LockFile::getLockSourceHash($data);
|
||||||
|
$data['hash'] = $hash;
|
||||||
|
self::put($name, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function init(): void
|
||||||
|
{
|
||||||
|
if (self::$lock_file_content === null) {
|
||||||
|
// Initialize the lock file content if it hasn't been loaded yet
|
||||||
|
if (!file_exists(self::LOCK_FILE)) {
|
||||||
|
logger()->debug('Lock file does not exist: ' . self::LOCK_FILE . ', initializing empty lock file.');
|
||||||
|
self::$lock_file_content = [];
|
||||||
|
file_put_contents(self::LOCK_FILE, json_encode(self::$lock_file_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||||
|
} else {
|
||||||
|
$file_content = file_get_contents(self::LOCK_FILE);
|
||||||
|
self::$lock_file_content = json_decode($file_content, true);
|
||||||
|
if (self::$lock_file_content === null) {
|
||||||
|
throw new \RuntimeException('Failed to decode lock file: ' . self::LOCK_FILE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the lock file or directory if it exists.
|
||||||
|
*
|
||||||
|
* @param array $lock_options lock item options, must contain 'source_type', 'filename' or 'dirname'
|
||||||
|
* @throws WrongUsageException
|
||||||
|
* @throws FileSystemException
|
||||||
|
*/
|
||||||
|
private static function removeLockFileIfExists(array $lock_options): void
|
||||||
|
{
|
||||||
|
if ($lock_options['source_type'] === SPC_SOURCE_ARCHIVE) {
|
||||||
|
$path = self::getLockFullPath($lock_options);
|
||||||
|
if (file_exists($path)) {
|
||||||
|
logger()->info('Removing file ' . $path);
|
||||||
|
unlink($path);
|
||||||
|
} else {
|
||||||
|
logger()->debug("Lock file [{$lock_options['filename']}] not found, skip removing file.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$path = self::getLockFullPath($lock_options);
|
||||||
|
if (is_dir($path)) {
|
||||||
|
logger()->info('Removing directory ' . $path);
|
||||||
|
FileSystem::removeDir($path);
|
||||||
|
} else {
|
||||||
|
logger()->debug("Lock directory [{$lock_options['dirname']}] not found, skip removing directory.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,10 +33,11 @@ class PackageManager
|
|||||||
// Download package
|
// Download package
|
||||||
Downloader::downloadPackage($pkg_name, $config, $force);
|
Downloader::downloadPackage($pkg_name, $config, $force);
|
||||||
// After download, read lock file name
|
// After download, read lock file name
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
|
$lock = LockFile::get($pkg_name);
|
||||||
$source_type = $lock[$pkg_name]['source_type'];
|
$source_type = $lock['source_type'];
|
||||||
$filename = DOWNLOAD_PATH . '/' . ($lock[$pkg_name]['filename'] ?? $lock[$pkg_name]['dirname']);
|
$filename = LockFile::getLockFullPath($lock);
|
||||||
$extract = $lock[$pkg_name]['move_path'] === null ? (PKG_ROOT_PATH . '/' . $pkg_name) : $lock[$pkg_name]['move_path'];
|
$extract = LockFile::getExtractPath($pkg_name, PKG_ROOT_PATH . '/' . $pkg_name);
|
||||||
|
|
||||||
FileSystem::extractPackage($pkg_name, $source_type, $filename, $extract);
|
FileSystem::extractPackage($pkg_name, $source_type, $filename, $extract);
|
||||||
|
|
||||||
// if contains extract-files, we just move this file to destination, and remove extract dir
|
// if contains extract-files, we just move this file to destination, and remove extract dir
|
||||||
|
|||||||
@ -17,11 +17,6 @@ class SourceManager
|
|||||||
*/
|
*/
|
||||||
public static function initSource(?array $sources = null, ?array $libs = null, ?array $exts = null, bool $source_only = false): void
|
public static function initSource(?array $sources = null, ?array $libs = null, ?array $exts = null, bool $source_only = false): void
|
||||||
{
|
{
|
||||||
if (!file_exists(DOWNLOAD_PATH . '/.lock.json')) {
|
|
||||||
throw new WrongUsageException('Download lock file "downloads/.lock.json" not found, maybe you need to download sources first ?');
|
|
||||||
}
|
|
||||||
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
|
|
||||||
|
|
||||||
$sources_extracted = [];
|
$sources_extracted = [];
|
||||||
// source check exist
|
// source check exist
|
||||||
if (is_array($sources)) {
|
if (is_array($sources)) {
|
||||||
@ -56,8 +51,8 @@ class SourceManager
|
|||||||
}
|
}
|
||||||
// check source downloaded
|
// check source downloaded
|
||||||
$pre_built_name = Downloader::getPreBuiltLockName($source);
|
$pre_built_name = Downloader::getPreBuiltLockName($source);
|
||||||
if ($source_only || !isset($lock[$pre_built_name])) {
|
if ($source_only || LockFile::get($pre_built_name) === null) {
|
||||||
if (!isset($lock[$source])) {
|
if (LockFile::get($source) === null) {
|
||||||
throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !");
|
throw new WrongUsageException("Source [{$source}] not downloaded or not locked, you should download it first !");
|
||||||
}
|
}
|
||||||
$lock_name = $source;
|
$lock_name = $source;
|
||||||
@ -65,20 +60,23 @@ class SourceManager
|
|||||||
$lock_name = $pre_built_name;
|
$lock_name = $pre_built_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$lock_content = LockFile::get($lock_name);
|
||||||
|
|
||||||
// check source dir exist
|
// check source dir exist
|
||||||
$check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']);
|
$check = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source);
|
||||||
|
// $check = $lock[$lock_name]['move_path'] === null ? (SOURCE_PATH . '/' . $source) : (SOURCE_PATH . '/' . $lock[$lock_name]['move_path']);
|
||||||
if (!is_dir($check)) {
|
if (!is_dir($check)) {
|
||||||
logger()->debug('Extracting source [' . $source . '] to ' . $check . ' ...');
|
logger()->debug('Extracting source [' . $source . '] to ' . $check . ' ...');
|
||||||
$filename = self::getSourceFullPath($lock[$lock_name]);
|
$filename = LockFile::getLockFullPath($lock_content);
|
||||||
FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']);
|
FileSystem::extractSource($source, $lock_content['source_type'], $filename, $check);
|
||||||
Downloader::putLockSourceHash($lock[$lock_name], $check);
|
LockFile::putLockSourceHash($lock_content, $check);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// if a lock file does not have hash, calculate with the current source (backward compatibility)
|
// if a lock file does not have hash, calculate with the current source (backward compatibility)
|
||||||
if (!isset($lock[$lock_name]['hash'])) {
|
if (!isset($lock_content['hash'])) {
|
||||||
$hash = Downloader::getLockSourceHash($lock[$lock_name]);
|
$hash = LockFile::getLockSourceHash($lock_content);
|
||||||
} else {
|
} else {
|
||||||
$hash = $lock[$lock_name]['hash'];
|
$hash = $lock_content['hash'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// when source already extracted, detect if the extracted source hash is the same as the lock file one
|
// when source already extracted, detect if the extracted source hash is the same as the lock file one
|
||||||
@ -90,18 +88,10 @@ class SourceManager
|
|||||||
// if not, remove the source dir and extract again
|
// if not, remove the source dir and extract again
|
||||||
logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ...");
|
logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ...");
|
||||||
FileSystem::removeDir($check);
|
FileSystem::removeDir($check);
|
||||||
$filename = self::getSourceFullPath($lock[$lock_name]);
|
$filename = LockFile::getLockFullPath($lock_content);
|
||||||
FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']);
|
$move_path = LockFile::getExtractPath($lock_name, SOURCE_PATH . '/' . $source);
|
||||||
Downloader::putLockSourceHash($lock[$lock_name], $check);
|
FileSystem::extractSource($source, $lock_content['source_type'], $filename, $move_path);
|
||||||
|
LockFile::putLockSourceHash($lock_content, $check);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function getSourceFullPath(array $lock_options): string
|
|
||||||
{
|
|
||||||
return match ($lock_options['source_type']) {
|
|
||||||
SPC_SOURCE_ARCHIVE => FileSystem::isRelativePath($lock_options['filename']) ? (DOWNLOAD_PATH . '/' . $lock_options['filename']) : $lock_options['filename'],
|
|
||||||
SPC_SOURCE_GIT, SPC_SOURCE_LOCAL => FileSystem::isRelativePath($lock_options['dirname']) ? (DOWNLOAD_PATH . '/' . $lock_options['dirname']) : $lock_options['dirname'],
|
|
||||||
default => throw new WrongUsageException("Unknown source type: {$lock_options['source_type']}"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ namespace SPC\Tests\store;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use SPC\exception\WrongUsageException;
|
use SPC\exception\WrongUsageException;
|
||||||
use SPC\store\Downloader;
|
use SPC\store\Downloader;
|
||||||
|
use SPC\store\LockFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -57,7 +58,7 @@ class DownloaderTest extends TestCase
|
|||||||
|
|
||||||
public function testLockSource()
|
public function testLockSource()
|
||||||
{
|
{
|
||||||
Downloader::lockSource('fake-file', ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']);
|
LockFile::lockSource('fake-file', ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']);
|
||||||
$this->assertFileExists(DOWNLOAD_PATH . '/.lock.json');
|
$this->assertFileExists(DOWNLOAD_PATH . '/.lock.json');
|
||||||
$json = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true);
|
$json = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true);
|
||||||
$this->assertIsArray($json);
|
$this->assertIsArray($json);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user