Compare commits

...

24 Commits

Author SHA1 Message Date
Jerry Ma
24e19deb58 Merge pull request #768 from crazywhalecc/ci/commit-tests
Add commit tests
2025-06-18 14:16:34 +08:00
crazywhalecc
f7a3f80689 Add test extensions as trigger 2025-06-18 14:06:59 +08:00
crazywhalecc
b4168d09b5 Add test extensions as trigger 2025-06-18 14:06:36 +08:00
Marc
04cefda66c Merge pull request #774 from crazywhalecc/fix/arm64-musl
ah, the infamous arm64 bug with -fpic vs -fPIC
2025-06-18 12:46:57 +07:00
DubbleClick
8c6a708764 ah, the infamous arm64 bug with -fpic vs -fPIC
it's a bit slower, but oh well
2025-06-18 12:46:05 +07:00
crazywhalecc
71783088c0 Use new trigger test message 2025-06-18 11:55:13 +08:00
crazywhalecc
7057a135cf Trigger extension test 2025-06-18 11:54:10 +08:00
crazywhalecc
5cb107b844 Test test 2025-06-18 11:53:17 +08:00
crazywhalecc
2bfc8e92ef Test test 2025-06-18 11:49:42 +08:00
crazywhalecc
68548cf248 Wrap it to test test test 2025-06-18 11:42:32 +08:00
crazywhalecc
3a64feefd0 Change test strategy for commit tests 2025-06-18 11:34:51 +08:00
crazywhalecc
0d3a80e582 Define env in phpunit.xml 2025-06-16 12:48:37 +08:00
crazywhalecc
5a401a5f92 Add source hash comparator & refactor download lock 2025-06-16 12:48:37 +08:00
crazywhalecc
0e88cdb258 Add shared extension parser
{craft}
extensions: bcmath
shared-extensions: xdebug,swoole
sapi: cli
{/craft}
[spc_prefix:bin/spc-gnu-docker]
2025-06-14 13:49:29 +08:00
crazywhalecc
e5cd3adf97 Next pr, I won't run 2025-06-14 02:36:47 +08:00
crazywhalecc
6253b7a912 Next pr, I won't run 2025-06-14 02:35:37 +08:00
crazywhalecc
fe455bf901 Fix shared-extensions as optional
{craft}
extensions: curl,ast
sapi: cli,micro
{/craft}
2025-06-14 02:30:00 +08:00
crazywhalecc
3a0d21eb44 Support multi-line
{craft}
extensions: curl,ast
sapi: cli,micro
{/craft}
2025-06-14 02:27:48 +08:00
crazywhalecc
45ec0cef24 Add checkout
{craft}
extensions: curl,ast
sapi: cli,micro
{/craft}
2025-06-14 02:20:52 +08:00
crazywhalecc
1468bb99f0 Add commit tests
{craft}
extensions: curl,ast
sapi: cli,micro
{/craft}
2025-06-14 02:06:12 +08:00
Marc
8fbe6ee8ff Merge pull request #766 from crazywhalecc/docs
rebuild shared extensions even if they already exist
2025-06-12 19:20:05 +07:00
DubbleClick
5a3a8db772 rebuild shared extensions even if they already exist 2025-06-12 19:17:44 +07:00
Marc
56cd6711ce Merge pull request #764 from crazywhalecc/docs
remove incorrect space after --enable-yac
2025-06-12 14:21:02 +07:00
DubbleClick
29339b962c remove incorrect space after --enable-yac 2025-06-12 14:20:04 +07:00
18 changed files with 170 additions and 57 deletions

View File

@@ -7,7 +7,11 @@
> If your PR involves the changes mentioned below and completed the action, please tick the corresponding option.
> If a modification is not involved, please skip it directly.
- [ ] If you modified `*.php`, run `composer cs-fix` at local machine.
- [ ] If it's an extension or dependency update, make sure adding related extensions in `src/global/test-extensions.php`.
- [ ] If you changed the behavior of static-php-cli, update docs in `./docs/`.
- [ ] If you updated `config/xxx.json` content, run `bin/spc dev:sort-config xxx`.
- If you modified `*.php` or `*.json`, run them locally to ensure your changes are valid:
- [ ] `PHP_CS_FIXER_IGNORE_ENV=1 composer cs-fix`
- [ ] `composer analyse`
- [ ] `composer test`
- [ ] `bin/spc dev:sort-config`
- If it's an extension or dependency update, please ensure the following:
- [ ] Add your test combination to `src/globals/test-extensions.php`.
- [ ] If adding new or fixing bugs, add commit message containing `extension test` or `test extensions` to trigger full test suite.

View File

@@ -1,16 +1,14 @@
name: "Extension matrix tests"
name: "Extension Matrix Tests"
on:
workflow_dispatch:
pull_request:
branches: [ "main" ]
paths:
- '.github/workflows/ext-matrix-tests.yml'
push:
jobs:
test:
name: "${{ matrix.extension }} (PHP ${{ matrix.php-version }} on ${{ matrix.operating-system }})"
runs-on: ${{ matrix.operating-system }}
if: contains(github.event.head_commit.message, 'extension test') || contains(github.event.head_commit.message, 'test extensions')
strategy:
fail-fast: false
matrix:

View File

@@ -68,8 +68,8 @@ CXX=${SPC_LINUX_DEFAULT_CXX}
AR=${SPC_LINUX_DEFAULT_AR}
LD=ld.gold
; default compiler flags, used in CMake toolchain file, openssl and pkg-config build
SPC_DEFAULT_C_FLAGS="-fpic -Os"
SPC_DEFAULT_CXX_FLAGS="-fpic -Os"
SPC_DEFAULT_C_FLAGS="-fPIC -Os"
SPC_DEFAULT_CXX_FLAGS="-fPIC -Os"
; extra libs for building php executable, used in `make` command for building php (this value may changed by extension build process, space separated)
SPC_EXTRA_LIBS=
; upx executable path
@@ -89,7 +89,7 @@ SPC_CMD_VAR_PHP_EMBED_TYPE="static"
; *** default build vars for building php ***
; CFLAGS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS="${SPC_DEFAULT_C_FLAGS} -fpie"
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS="${SPC_DEFAULT_C_FLAGS} -fPIE"
; CPPFLAGS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS="-I${BUILD_INCLUDE_PATH}"
; LDFLAGS for configuring php
@@ -97,7 +97,7 @@ SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS="-L${BUILD_LIB_PATH}"
; LIBS for configuring php
SPC_CMD_VAR_PHP_CONFIGURE_LIBS="-ldl -lpthread -lm"
; EXTRA_CFLAGS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fno-ident -fpie ${SPC_DEFAULT_C_FLAGS}"
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fno-ident -fPIE ${SPC_DEFAULT_C_FLAGS}"
; EXTRA_LIBS for `make` php
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS="-ldl -lpthread -lm"
; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so

View File

@@ -42,6 +42,9 @@ build-options:
# Set micro SAPI as win32 mode, without this, micro SAPI will be compiled as a console application (only for Windows, default: false)
enable-micro-win32: false
# Build options for shared extensions (list or comma-separated are both accepted)
shared-extensions: [ ]
# Download options
download-options:
# Use custom url for specified sources, format: "{source-name}:{url}" (e.g. "php-src:https://example.com/php-8.4.0.tar.gz")

View File

@@ -2,4 +2,7 @@
<phpunit
bootstrap="tests/bootstrap.php"
>
</phpunit>
<php>
<env name="SPC_IGNORE_BAD_HASH" value="yes" force="true" />
</php>
</phpunit>

View File

@@ -329,10 +329,6 @@ class Extension
public function buildShared(): void
{
logger()->info('Building extension [' . $this->getName() . '] as shared extension (' . $this->getName() . '.so)');
if (file_exists(BUILD_MODULES_PATH . '/' . $this->getName() . '.so')) {
logger()->info('extension ' . $this->getName() . ' already built, skipping');
return;
}
foreach ($this->dependencies as $dependency) {
if (!$dependency instanceof Extension) {
continue;

View File

@@ -51,7 +51,7 @@ abstract class LibraryBase
// if source is locked as pre-built, we just tryInstall it
$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) {
return $this->tryInstall($lock[$pre_built_name]['filename'], $force);
return $this->tryInstall($lock[$pre_built_name], $force);
}
return $this->tryBuild($force);
}
@@ -166,14 +166,15 @@ abstract class LibraryBase
* @throws WrongUsageException
* @throws FileSystemException
*/
public function tryInstall(string $install_file, bool $force_install = false): int
public function tryInstall(array $lock, bool $force_install = false): int
{
$install_file = $lock['filename'];
if ($force_install) {
logger()->info('Installing required library [' . static::NAME . '] from pre-built binaries');
// Extract files
try {
FileSystem::extractPackage($install_file, DOWNLOAD_PATH . '/' . $install_file, BUILD_ROOT_PATH);
FileSystem::extractPackage($install_file, $lock['source_type'], DOWNLOAD_PATH . '/' . $install_file, BUILD_ROOT_PATH);
$this->install();
return LIB_STATUS_OK;
} catch (FileSystemException|RuntimeException $e) {
@@ -183,19 +184,19 @@ abstract class LibraryBase
}
foreach ($this->getStaticLibs() as $name) {
if (!file_exists(BUILD_LIB_PATH . "/{$name}")) {
$this->tryInstall($install_file, true);
$this->tryInstall($lock, true);
return LIB_STATUS_OK;
}
}
foreach ($this->getHeaders() as $name) {
if (!file_exists(BUILD_INCLUDE_PATH . "/{$name}")) {
$this->tryInstall($install_file, true);
$this->tryInstall($lock, true);
return LIB_STATUS_OK;
}
}
// pkg-config is treated specially. If it is pkg-config, check if the pkg-config binary exists
if (static::NAME === 'pkg-config' && !file_exists(BUILD_ROOT_PATH . '/bin/pkg-config')) {
$this->tryInstall($install_file, true);
$this->tryInstall($lock, true);
return LIB_STATUS_OK;
}
return LIB_STATUS_ALREADY;

View File

@@ -21,6 +21,6 @@ class yac extends Extension
public function getUnixConfigureArg(bool $shared = false): string
{
return '--enable-yac ' . ($shared ? '=shared' : '') . ' --enable-igbinary --enable-json --with-system-fastlz';
return '--enable-yac' . ($shared ? '=shared' : '') . ' --enable-igbinary --enable-json --with-system-fastlz';
}
}

View File

@@ -49,7 +49,7 @@ class CraftCommand extends BaseCommand
}
$static_extensions = implode(',', $craft['extensions']);
$shared_extensions = implode(',', $craft['shared-extensions']);
$shared_extensions = implode(',', $craft['shared-extensions'] ?? []);
$libs = implode(',', $craft['libs']);
// init log

View File

@@ -60,7 +60,7 @@ class DeleteDownloadCommand extends BaseCommand
foreach ($deleted_sources as $lock_name) {
// remove download file/dir if exists
if ($lock[$lock_name]['source_type'] === 'archive') {
if ($lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE) {
if (file_exists($path = FileSystem::convertPath(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']))) {
logger()->info('Deleting file ' . $path);
unlink($path);

View File

@@ -78,7 +78,7 @@ class LinuxMuslCheck
];
logger()->info('Downloading ' . $musl_source['url']);
Downloader::downloadSource($musl_version_name, $musl_source);
FileSystem::extractSource($musl_version_name, DOWNLOAD_PATH . "/{$musl_version_name}.tar.gz");
FileSystem::extractSource($musl_version_name, SPC_SOURCE_ARCHIVE, DOWNLOAD_PATH . "/{$musl_version_name}.tar.gz");
// Apply CVE-2025-26519 patch
SourcePatcher::patchFile('musl-1.2.5_CVE-2025-26519_0001.patch', SOURCE_PATH . "/{$musl_version_name}");

View File

@@ -208,7 +208,7 @@ class Downloader
if ($download_as === SPC_DOWNLOAD_PRE_BUILT) {
$name = self::getPreBuiltLockName($name);
}
self::lockSource($name, ['source_type' => 'archive', 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
self::lockSource($name, ['source_type' => SPC_SOURCE_ARCHIVE, 'filename' => $filename, 'move_path' => $move_path, 'lock_as' => $download_as]);
}
/**
@@ -231,6 +231,9 @@ class Downloader
} 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));
}
@@ -278,7 +281,7 @@ class Downloader
}
// Lock
logger()->debug("Locking git source {$name}");
self::lockSource($name, ['source_type' => 'dir', 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]);
self::lockSource($name, ['source_type' => SPC_SOURCE_GIT, 'dirname' => $name, 'move_path' => $move_path, 'lock_as' => $lock_as]);
/*
// 复制目录过去
@@ -371,6 +374,16 @@ class Downloader
SPC_DOWNLOAD_PRE_BUILT
);
break;
case 'local':
// Local directory, do nothing, just lock it
logger()->debug("Locking local source {$name}");
self::lockSource($name, [
'source_type' => SPC_SOURCE_LOCAL,
'dirname' => $pkg['dirname'],
'move_path' => $pkg['extract'] ?? null,
'lock_as' => SPC_DOWNLOAD_PACKAGE,
]);
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) {
@@ -477,6 +490,16 @@ class Downloader
$download_as
);
break;
case 'local':
// Local directory, do nothing, just lock it
logger()->debug("Locking local source {$name}");
self::lockSource($name, [
'source_type' => SPC_SOURCE_LOCAL,
'dirname' => $source['dirname'],
'move_path' => $source['extract'] ?? null,
'lock_as' => $download_as,
]);
break;
case 'custom': // Custom download method, like API-based download or other
if (isset($source['func']) && is_callable($source['func'])) {
$source['name'] = $name;
@@ -594,6 +617,43 @@ class Downloader
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.
*
@@ -640,8 +700,8 @@ class Downloader
// If lock file exists, skip downloading for source mode
if (!$force && $download_as === SPC_DOWNLOAD_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'])
$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;
@@ -652,8 +712,8 @@ class Downloader
if (!$force && $download_as === SPC_DOWNLOAD_PRE_BUILT && isset($lock[$lock_name = self::getPreBuiltLockName($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'])
$lock[$lock_name]['source_type'] === SPC_SOURCE_ARCHIVE && file_exists(DOWNLOAD_PATH . '/' . $lock[$lock_name]['filename']) ||
$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;

View File

@@ -142,7 +142,7 @@ class FileSystem
* @throws RuntimeException
* @throws FileSystemException
*/
public static function extractPackage(string $name, string $filename, ?string $extract_path = null): void
public static function extractPackage(string $name, string $source_type, string $filename, ?string $extract_path = null): void
{
if ($extract_path !== null) {
// replace
@@ -151,14 +151,15 @@ class FileSystem
} else {
$extract_path = PKG_ROOT_PATH . '/' . $name;
}
logger()->info("extracting {$name} package to {$extract_path} ...");
logger()->info("Extracting {$name} package to {$extract_path} ...");
$target = self::convertPath($extract_path);
if (!is_dir($dir = dirname($target))) {
self::createDir($dir);
}
try {
self::extractArchive($filename, $target);
// extract wrapper command
self::extractWithType($source_type, $filename, $extract_path);
} catch (RuntimeException $e) {
if (PHP_OS_FAMILY === 'Windows') {
f_passthru('rmdir /s /q ' . $target);
@@ -177,24 +178,23 @@ class FileSystem
* @throws FileSystemException
* @throws RuntimeException
*/
public static function extractSource(string $name, string $filename, ?string $move_path = null): void
public static function extractSource(string $name, string $source_type, string $filename, ?string $move_path = null): void
{
// if source hook is empty, load it
if (self::$_extract_hook === []) {
SourcePatcher::init();
}
if ($move_path !== null) {
$move_path = SOURCE_PATH . '/' . $move_path;
} else {
$move_path = SOURCE_PATH . "/{$name}";
}
$move_path = match ($move_path) {
null => SOURCE_PATH . '/' . $name,
default => self::isRelativePath($move_path) ? (SOURCE_PATH . '/' . $move_path) : $move_path,
};
$target = self::convertPath($move_path);
logger()->info("extracting {$name} source to {$target}" . ' ...');
logger()->info("Extracting {$name} source to {$target}" . ' ...');
if (!is_dir($dir = dirname($target))) {
self::createDir($dir);
}
try {
self::extractArchive($filename, $target);
self::extractWithType($source_type, $filename, $move_path);
self::emitSourceExtractHook($name, $target);
} catch (RuntimeException $e) {
if (PHP_OS_FAMILY === 'Windows') {
@@ -484,11 +484,6 @@ class FileSystem
*/
private static function extractArchive(string $filename, string $target): void
{
// Git source, just move
if (is_dir(self::convertPath($filename))) {
self::copyDir(self::convertPath($filename), $target);
return;
}
// Create base dir
if (f_mkdir(directory: $target, recursive: true) !== true) {
throw new FileSystemException('create ' . $target . ' dir failed');
@@ -553,4 +548,16 @@ class FileSystem
}
}
}
private static function extractWithType(string $source_type, string $filename, string $extract_path): void
{
logger()->debug('Extracting source [' . $source_type . ']: ' . $filename);
/* @phpstan-ignore-next-line */
match ($source_type) {
SPC_SOURCE_ARCHIVE => self::extractArchive($filename, $extract_path),
SPC_SOURCE_GIT => self::copyDir(self::convertPath($filename), $extract_path),
// soft link to the local source
SPC_SOURCE_LOCAL => symlink(self::convertPath($filename), $extract_path),
};
}
}

View File

@@ -34,9 +34,10 @@ class PackageManager
Downloader::downloadPackage($pkg_name, $config, $force);
// After download, read lock file name
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true);
$source_type = $lock[$pkg_name]['source_type'];
$filename = DOWNLOAD_PATH . '/' . ($lock[$pkg_name]['filename'] ?? $lock[$pkg_name]['dirname']);
$extract = $lock[$pkg_name]['move_path'] === null ? (PKG_ROOT_PATH . '/' . $pkg_name) : $lock[$pkg_name]['move_path'];
FileSystem::extractPackage($pkg_name, $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 (is_array($config['extract-files'] ?? null) && is_assoc_array($config['extract-files'])) {

View File

@@ -69,10 +69,39 @@ class SourceManager
$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[$lock_name]['filename'] ?? $lock[$lock_name]['dirname']), $lock[$lock_name]['move_path']);
} else {
logger()->debug('Source [' . $source . '] already extracted in ' . $check . ', skip !');
$filename = self::getSourceFullPath($lock[$lock_name]);
FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']);
Downloader::putLockSourceHash($lock[$lock_name], $check);
continue;
}
// if a lock file does not have hash, calculate with the current source (backward compatibility)
if (!isset($lock[$lock_name]['hash'])) {
$hash = Downloader::getLockSourceHash($lock[$lock_name]);
} else {
$hash = $lock[$lock_name]['hash'];
}
// when source already extracted, detect if the extracted source hash is the same as the lock file one
if (file_exists("{$check}/.spc-hash") && FileSystem::readFile("{$check}/.spc-hash") === $hash) {
logger()->debug('Source [' . $source . '] already extracted in ' . $check . ', skip !');
continue;
}
// if not, remove the source dir and extract again
logger()->notice("Source [{$source}] hash mismatch, removing old source dir and extracting again ...");
FileSystem::removeDir($check);
$filename = self::getSourceFullPath($lock[$lock_name]);
FileSystem::extractSource($source, $lock[$lock_name]['source_type'], $filename, $lock[$lock_name]['move_path']);
Downloader::putLockSourceHash($lock[$lock_name], $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']}"),
};
}
}

View File

@@ -164,6 +164,12 @@ class ConfigValidator
if (is_string($craft['extensions'])) {
$craft['extensions'] = array_filter(array_map(fn ($x) => trim($x), explode(',', $craft['extensions'])));
}
if (!isset($craft['shared-extensions'])) {
$craft['shared-extensions'] = [];
}
if (is_string($craft['shared-extensions'] ?? [])) {
$craft['shared-extensions'] = array_filter(array_map(fn ($x) => trim($x), explode(',', $craft['shared-extensions'])));
}
// check libs
if (isset($craft['libs']) && is_string($craft['libs'])) {
$craft['libs'] = array_filter(array_map(fn ($x) => trim($x), explode(',', $craft['libs'])));

View File

@@ -40,7 +40,7 @@ const SPC_EXTENSION_ALIAS = [
'zendopcache' => 'opcache',
];
// spc lock type
// spc download lock type
const SPC_DOWNLOAD_SOURCE = 1; // lock source
const SPC_DOWNLOAD_PRE_BUILT = 2; // lock pre-built
const SPC_DOWNLOAD_PACKAGE = 3; // lock as package
@@ -84,5 +84,10 @@ const AUTOCONF_CPPFLAGS = 4;
const AUTOCONF_LDFLAGS = 8;
const AUTOCONF_ALL = 15;
// spc download source type
const SPC_SOURCE_ARCHIVE = 'archive'; // download as archive
const SPC_SOURCE_GIT = 'git'; // download as git repository
const SPC_SOURCE_LOCAL = 'local'; // download as local directory
ConsoleLogger::$date_format = 'H:i:s';
ConsoleLogger::$format = '[%date%] [%level_short%] %body%';

View File

@@ -57,7 +57,7 @@ class DownloaderTest extends TestCase
public function testLockSource()
{
Downloader::lockSource('fake-file', ['source_type' => 'archive', 'filename' => 'fake-file-name', 'move_path' => 'fake-path', 'lock_as' => 'fake-lock-as']);
Downloader::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');
$json = json_decode(file_get_contents(DOWNLOAD_PATH . '/.lock.json'), true);
$this->assertIsArray($json);
@@ -66,7 +66,7 @@ class DownloaderTest extends TestCase
$this->assertArrayHasKey('filename', $json['fake-file']);
$this->assertArrayHasKey('move_path', $json['fake-file']);
$this->assertArrayHasKey('lock_as', $json['fake-file']);
$this->assertEquals('archive', $json['fake-file']['source_type']);
$this->assertEquals(SPC_SOURCE_ARCHIVE, $json['fake-file']['source_type']);
$this->assertEquals('fake-file-name', $json['fake-file']['filename']);
$this->assertEquals('fake-path', $json['fake-file']['move_path']);
$this->assertEquals('fake-lock-as', $json['fake-file']['lock_as']);