Separate package config

This commit is contained in:
crazywhalecc 2026-02-02 13:32:35 +08:00
parent f232588dbe
commit 455d42d162
No known key found for this signature in database
GPG Key ID: 1F4BDD59391F2680
21 changed files with 294 additions and 157 deletions

View File

@ -1,3 +0,0 @@
# static-php-cli
English README has been moved to [README.md](README.md).

View File

@ -1,67 +0,0 @@
attr:
source:
type: url
url: 'https://download.savannah.nongnu.org/releases/attr/attr-2.5.2.tar.gz'
source-mirror:
type: url
url: 'https://mirror.souseiseki.middlendian.com/nongnu/attr/attr-2.5.2.tar.gz'
metadata:
license-files: ['doc/COPYING.LGPL']
license: LGPL-2.1-or-later
brotli:
source:
type: ghtagtar
repo: google/brotli
match: 'v1\.\d.*'
binary: hosted # 等价于v2的provide-pre-built: true
metadata:
license-files: ['LICENSE']
license: MIT
bzip2:
source:
type: url
url: 'https://dl.static-php.dev/static-php-cli/deps/bzip2/bzip2-1.0.8.tar.gz'
source-mirror:
type: filelist
url: 'https://sourceware.org/pub/bzip2/'
regex: '/href="(?<file>bzip2-(?<version>[^"]+)\.tar\.gz)"/'
binary: hosted
metadata:
license-files: ['{registry_root}/src/globals/licenses/bzip2.txt']
license: bzip2-1.0.6
fastlz:
source:
type: git
url: 'https://github.com/ariya/FastLZ.git'
rev: master
metadata:
license-files: ['LICENSE.MIT']
license: MIT
openssl:
source:
type: ghrel
repo: openssl/openssl
match: 'openssl.+\.tar\.gz'
prefer-stable: true
source-mirror:
type: filelist
url: 'https://www.openssl.org/source/'
regex: '/href="(?<file>openssl-(?<version>[^"]+)\.tar\.gz)"/'
binary: hosted
metadata:
license-files: ['LICENSE.txt']
license: OpenSSL
zlib:
source:
type: ghrel
repo: madler/zlib
match: 'zlib.+\.tar\.gz'
binary: hosted
metadata:
license-files: ['{registry_root}/src/globals/licenses/zlib.txt']
license: Zlib-Custom

View File

@ -1,48 +0,0 @@
attr:
type: library
static-libs@unix:
- libattr.a
artifact: attr
brotli:
type: library
pkg-configs:
- libbrotlicommon
- libbrotlidec
- libbrotlienc
headers:
- brotli
artifact: brotli
bzip2:
type: library
static-libs@unix:
- libbz2.a
headers:
- bzlib.h
artifact: bzip2
fastlz:
type: library
static-libs@unix:
- libfastlz.a
headers:
- fastlz.h
artifact: fastlz
openssl:
type: library
static-libs@unix:
- libssl.a
- libcrypto.a
headers: ['openssl']
depends:
- zlib
artifact: openssl
zlib:
type: library
static-libs@unix:
- libz.a
headers:
- zlib.h
- zconf.h
artifact: zlib

14
config/pkg/lib/attr.yml Normal file
View File

@ -0,0 +1,14 @@
attr:
type: library
static-libs@unix:
- libattr.a
artifact:
source:
type: url
url: 'https://download.savannah.nongnu.org/releases/attr/attr-2.5.2.tar.gz'
source-mirror:
type: url
url: 'https://mirror.souseiseki.middlendian.com/nongnu/attr/attr-2.5.2.tar.gz'
metadata:
license-files: ['doc/COPYING.LGPL']
license: LGPL-2.1-or-later

17
config/pkg/lib/brotli.yml Normal file
View File

@ -0,0 +1,17 @@
brotli:
type: library
pkg-configs:
- libbrotlicommon
- libbrotlidec
- libbrotlienc
headers:
- brotli
artifact:
source:
type: ghtagtar
repo: google/brotli
match: 'v1\.\d.*'
binary: hosted
metadata:
license-files: ['LICENSE']
license: MIT

18
config/pkg/lib/bzip2.yml Normal file
View File

@ -0,0 +1,18 @@
bzip2:
type: library
static-libs@unix:
- libbz2.a
headers:
- bzlib.h
artifact:
source:
type: url
url: 'https://dl.static-php.dev/static-php-cli/deps/bzip2/bzip2-1.0.8.tar.gz'
source-mirror:
type: filelist
url: 'https://sourceware.org/pub/bzip2/'
regex: '/href="(?<file>bzip2-(?<version>[^"]+)\.tar\.gz)"/'
binary: hosted
metadata:
license-files: ['{registry_root}/src/globals/licenses/bzip2.txt']
license: bzip2-1.0.6

14
config/pkg/lib/fastlz.yml Normal file
View File

@ -0,0 +1,14 @@
fastlz:
type: library
static-libs@unix:
- libfastlz.a
headers:
- fastlz.h
artifact:
source:
type: git
url: 'https://github.com/ariya/FastLZ.git'
rev: master
metadata:
license-files: ['LICENSE.MIT']
license: MIT

19
config/pkg/lib/gmp.yml Normal file
View File

@ -0,0 +1,19 @@
gmp:
type: library
static-libs@unix:
- libgmp.a
headers:
- gmp.h
pkg-configs:
- gmp
artifact:
source:
type: filelist
url: 'https://gmplib.org/download/gmp/'
regex: '/href="(?<file>gmp-(?<version>[^"]+)\.tar\.xz)"/'
source-mirror:
type: url
url: 'https://dl.static-php.dev/static-php-cli/deps/gmp/gmp-6.3.0.tar.xz'
metadata:
license-files: ['@/gmp.txt']
license: Custom

View File

@ -0,0 +1,22 @@
openssl:
type: library
static-libs@unix:
- libssl.a
- libcrypto.a
headers: ['openssl']
depends:
- zlib
artifact:
source:
type: ghrel
repo: openssl/openssl
match: 'openssl.+\.tar\.gz'
prefer-stable: true
source-mirror:
type: filelist
url: 'https://www.openssl.org/source/'
regex: '/href="(?<file>openssl-(?<version>[^"]+)\.tar\.gz)"/'
binary: hosted
metadata:
license-files: ['LICENSE.txt']
license: OpenSSL

16
config/pkg/lib/zlib.yml Normal file
View File

@ -0,0 +1,16 @@
zlib:
type: library
static-libs@unix:
- libz.a
headers:
- zlib.h
- zconf.h
artifact:
source:
type: ghrel
repo: madler/zlib
match: 'zlib.+\.tar\.gz'
binary: hosted
metadata:
license-files: ['{registry_root}/src/globals/licenses/zlib.txt']
license: Zlib-Custom

View File

@ -1,5 +1,5 @@
{
"name": "internal",
"name": "core",
"autoload": "vendor/autoload.php",
"doctor": {
"psr-4": {
@ -12,14 +12,11 @@
},
"config": [
"config/pkg.ext.json",
"config/pkg.lib.yaml",
"config/pkg/lib",
"config/pkg.target.json"
]
},
"artifact": {
"config": [
"config/artifact.yaml"
],
"psr-4": {
"Package\\Artifact": "src/Package/Artifact"
}

View File

@ -27,9 +27,9 @@ class openssl
spc_skip_if(!file_exists("{$target_path}/openssl/test/v3ext.c"), 'v3ext.c not found, skipping patch.');
FileSystem::replaceFileStr(
SOURCE_PATH . '/openssl/test/v3ext.c',
"{$target_path}/openssl/test/v3ext.c",
'#include <stdio.h>',
'#include <stdio.h>' . PHP_EOL . '#include <string.h>'
"#include <stdio.h>\n#include <string.h>"
);
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Package\Library;
use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\Library;
use StaticPHP\Package\LibraryPackage;
use StaticPHP\Runtime\Executor\UnixAutoconfExecutor;
#[Library('gmp')]
class gmp
{
#[BuildFor('Linux')]
#[BuildFor('Darwin')]
public function build(LibraryPackage $lib): void
{
UnixAutoconfExecutor::create($lib)
->appendEnv(['CFLAGS' => '-std=c17'])
->configure('--enable-fat')
->make();
$lib->patchPkgconfPrefix(['gmp.pc']);
}
}

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

View File

@ -0,0 +1 @@
Since version 6, GMP is distributed under the dual licenses, GNU LGPL v3 and GNU GPL v2. These licenses make the library free to use, share, and improve, and allow you to pass on the result. The GNU licenses give freedoms, but also set firm restrictions on the use with non-free programs.