Separate package config

This commit is contained in:
crazywhalecc
2026-02-02 13:32:35 +08:00
parent f232588dbe
commit 455d42d162
21 changed files with 294 additions and 157 deletions

View File

@@ -77,4 +77,19 @@ class ArtifactConfig
{
return self::$artifact_configs[$artifact_name] ?? null;
}
/**
* Register an inline artifact configuration.
* Used when artifact is defined inline within a package configuration.
*
* @param string $artifact_name Artifact name (usually same as package name)
* @param array $config Artifact configuration
* @param string $registry_name Registry name
* @param string $source_info Source info for debugging
*/
public static function registerInlineArtifact(string $artifact_name, array $config, string $registry_name, string $source_info = 'inline'): void
{
self::$artifact_configs[$artifact_name] = $config;
Registry::_bindArtifactConfigFile($artifact_name, $registry_name, $source_info);
}
}

View File

@@ -18,7 +18,7 @@ class ConfigValidator
'type' => ConfigType::STRING,
'depends' => ConfigType::LIST_ARRAY, // @
'suggests' => ConfigType::LIST_ARRAY, // @
'artifact' => ConfigType::STRING,
'artifact' => [self::class, 'validateArtifactField'], // STRING or OBJECT
'license' => [ConfigType::class, 'validateLicenseField'],
'lang' => ConfigType::STRING,
'frameworks' => ConfigType::LIST_ARRAY, // @
@@ -102,7 +102,14 @@ class ConfigValidator
if (!is_array($data)) {
throw new ValidationException("{$config_file_name} is broken");
}
// Define allowed artifact fields
$allowed_artifact_fields = ['source', 'source-mirror', 'binary', 'binary-mirror', 'metadata'];
foreach ($data as $name => $artifact) {
// First pass: validate unknown fields
self::validateNoInvalidFields('artifact', $name, $artifact, $allowed_artifact_fields);
foreach ($artifact as $k => $v) {
// check source field
if ($k === 'source' || $k === 'source-mirror') {
@@ -202,6 +209,11 @@ class ConfigValidator
throw new ValidationException("Package [{$name}] in {$config_file_name} of type '{$pkg['type']}' must have an 'artifact' field");
}
// validate and lint inline artifact object if present
if (isset($pkg['artifact']) && is_array($pkg['artifact'])) {
self::validateAndLintInlineArtifact($name, $data[$name]['artifact']);
}
// check if "php-extension" package has php-extension specific fields and validate inside
if ($pkg['type'] === 'php-extension') {
self::validatePhpExtensionFields($name, $pkg);
@@ -234,6 +246,19 @@ class ConfigValidator
}
}
/**
* Validate artifact field - can be string (reference) or object (inline).
*
* @param mixed $value Field value
*/
public static function validateArtifactField(mixed $value): bool
{
if (!is_string($value) && !is_assoc_array($value)) {
return false;
}
return true;
}
/**
* Validate an artifact download object field.
*
@@ -373,4 +398,19 @@ class ConfigValidator
}
}
}
/**
* Validate and lint inline artifact object structure.
*
* @param string $pkg_name Package name
* @param array $artifact Inline artifact configuration (passed by reference to apply linting)
*/
private static function validateAndLintInlineArtifact(string $pkg_name, array &$artifact): void
{
// Validate and lint as if it's a standalone artifact
$temp_data = [$pkg_name => $artifact];
self::validateAndLintArtifacts("inline artifact in package '{$pkg_name}'", $temp_data);
// Write back the linted artifact configuration
$artifact = $temp_data[$pkg_name];
}
}

View File

@@ -23,7 +23,7 @@ class PackageConfig
throw new WrongUsageException("Directory {$dir} does not exist, cannot load pkg.json config.");
}
$loaded = [];
$files = glob("{$dir}/pkg.*.json");
$files = glob("{$dir}/*");
if (is_array($files)) {
foreach ($files as $file) {
self::loadFromFile($file, $registry_name);
@@ -58,10 +58,39 @@ class PackageConfig
foreach ($data as $pkg_name => $config) {
self::$package_configs[$pkg_name] = $config;
Registry::_bindPackageConfigFile($pkg_name, $registry_name, $file);
// Register inline artifact if present
if (isset($config['artifact']) && is_array($config['artifact'])) {
ArtifactConfig::registerInlineArtifact(
$pkg_name,
$config['artifact'],
$registry_name,
"inline in {$file}"
);
}
}
return $file;
}
public static function loadFromArray(array $data, string $registry_name): void
{
ConfigValidator::validateAndLintPackages('array_input', $data);
foreach ($data as $pkg_name => $config) {
self::$package_configs[$pkg_name] = $config;
Registry::_bindPackageConfigFile($pkg_name, $registry_name, 'array_input');
// Register inline artifact if present
if (isset($config['artifact']) && is_array($config['artifact'])) {
ArtifactConfig::registerInlineArtifact(
$pkg_name,
$config['artifact'],
$registry_name,
'inline in array_input'
);
}
}
}
/**
* Check if a package configuration exists.
*/

View File

@@ -19,7 +19,6 @@ class LinuxToolCheck
'bzip2', 'cmake', 'gcc',
'g++', 'patch', 'binutils-gold',
'libtoolize', 'which',
'patchelf',
];
public const TOOLS_DEBIAN = [
@@ -28,7 +27,6 @@ class LinuxToolCheck
'tar', 'unzip', 'gzip', 'gcc', 'g++',
'bzip2', 'cmake', 'patch',
'xz', 'libtoolize', 'which',
'patchelf',
];
public const TOOLS_RHEL = [
@@ -36,8 +34,7 @@ class LinuxToolCheck
'git', 'autoconf', 'automake',
'tar', 'unzip', 'gzip', 'gcc', 'g++',
'bzip2', 'cmake', 'patch', 'which',
'xz', 'libtool', 'gettext-devel',
'patchelf', 'file',
'xz', 'libtool', 'gettext-devel', 'file',
];
public const TOOLS_ARCH = [

View File

@@ -175,8 +175,21 @@ abstract class Package
public function getArtifact(): ?Artifact
{
// find config
$artifact_name = PackageConfig::get($this->name, 'artifact');
return $artifact_name !== null ? ArtifactLoader::getArtifactInstance($artifact_name) : null;
$artifact_field = PackageConfig::get($this->name, 'artifact');
if ($artifact_field === null) {
return null;
}
if (is_string($artifact_field)) {
return ArtifactLoader::getArtifactInstance($artifact_field);
}
if (is_array($artifact_field)) {
return ArtifactLoader::getArtifactInstance($this->name);
}
return null;
}
/**

View File

@@ -13,6 +13,8 @@ use Symfony\Component\Yaml\Yaml;
class Registry
{
private static ?string $current_registry_name = null;
/** @var string[] List of loaded registries */
private static array $loaded_registries = [];
@@ -35,7 +37,7 @@ class Registry
public static function getRegistryConfig(?string $registry_name = null): array
{
if ($registry_name === null && spc_mode(SPC_MODE_SOURCE)) {
return self::$registry_configs['internal'];
return self::$registry_configs['core'];
}
if ($registry_name !== null && isset(self::$registry_configs[$registry_name])) {
return self::$registry_configs[$registry_name];
@@ -83,6 +85,8 @@ class Registry
logger()->debug("Loading registry '{$registry_name}' from file: {$registry_file}");
self::$current_registry_name = $registry_name;
// Load composer autoload if specified (for external registries with their own dependencies)
if (isset($data['autoload']) && is_string($data['autoload'])) {
$autoload_path = FileSystem::fullpath($data['autoload'], dirname($registry_file));
@@ -94,24 +98,6 @@ class Registry
}
}
// load doctor items from PSR-4 directories
if (isset($data['doctor']['psr-4']) && is_assoc_array($data['doctor']['psr-4'])) {
foreach ($data['doctor']['psr-4'] as $namespace => $path) {
$path = FileSystem::fullpath($path, dirname($registry_file));
DoctorLoader::loadFromPsr4Dir($path, $namespace, $auto_require);
}
}
// load doctor items from specific classes
// Supports both array format ["ClassName"] and map format {"ClassName": "path/to/file.php"}
if (isset($data['doctor']['classes']) && is_array($data['doctor']['classes'])) {
foreach ($data['doctor']['classes'] as $key => $value) {
[$class, $file] = self::parseClassEntry($key, $value);
self::requireClassFile($class, $file, dirname($registry_file), $auto_require);
DoctorLoader::loadFromClass($class);
}
}
// load package configs
if (isset($data['package']['config']) && is_array($data['package']['config'])) {
foreach ($data['package']['config'] as $path) {
@@ -136,6 +122,24 @@ class Registry
}
}
// load doctor items from PSR-4 directories
if (isset($data['doctor']['psr-4']) && is_assoc_array($data['doctor']['psr-4'])) {
foreach ($data['doctor']['psr-4'] as $namespace => $path) {
$path = FileSystem::fullpath($path, dirname($registry_file));
DoctorLoader::loadFromPsr4Dir($path, $namespace, $auto_require);
}
}
// load doctor items from specific classes
// Supports both array format ["ClassName"] and map format {"ClassName": "path/to/file.php"}
if (isset($data['doctor']['classes']) && is_array($data['doctor']['classes'])) {
foreach ($data['doctor']['classes'] as $key => $value) {
[$class, $file] = self::parseClassEntry($key, $value);
self::requireClassFile($class, $file, dirname($registry_file), $auto_require);
DoctorLoader::loadFromClass($class);
}
}
// load packages from PSR-4 directories
if (isset($data['package']['psr-4']) && is_assoc_array($data['package']['psr-4'])) {
foreach ($data['package']['psr-4'] as $namespace => $path) {
@@ -193,6 +197,7 @@ class Registry
}
ConsoleApplication::_addAdditionalCommands($instances);
}
self::$current_registry_name = null;
}
/**
@@ -300,6 +305,11 @@ class Registry
return self::$loaded_artifact_configs;
}
public static function getCurrentRegistryName(): ?string
{
return self::$current_registry_name;
}
/**
* Parse a class entry from the classes array.
* Supports two formats:

View File

@@ -163,18 +163,26 @@ class LicenseDumper
{
$artifact_name = $artifact->getName();
// Try source directory first (if extracted)
if ($artifact->isSourceExtracted()) {
$source_dir = $artifact->getSourceDir();
$full_path = "{$source_dir}/{$license_file_path}";
// replace
if (str_starts_with($license_file_path, '@/')) {
$license_file_path = str_replace('@/', ROOT_DIR . '/src/globals/licenses/', $license_file_path);
}
$source_dir = $artifact->getSourceDir();
if (FileSystem::isRelativePath($license_file_path)) {
$full_path = "{$source_dir}/{$license_file_path}";
} else {
$full_path = $license_file_path;
}
// Try source directory first (if extracted)
if ($artifact->isSourceExtracted() || file_exists($full_path)) {
logger()->debug("Checking license file: {$full_path}");
if (file_exists($full_path)) {
logger()->info("Reading license from source: {$full_path}");
return file_get_contents($full_path);
}
} else {
logger()->debug("Artifact source not extracted: {$artifact_name}");
logger()->warning("Artifact source not extracted: {$artifact_name}");
}
// Fallback: try SOURCE_PATH directly