Add php and lib-base as special libraries to add dependencies to the root node (#618)

* Remove E_STRICT

* Add lib-base and php as special libs

* Remove debug code

* Fix phpunit with new config structure

* Fix phpunit test and fix license dumper bug for new type of lib

* Add missing lib type filter for windows builder
This commit is contained in:
Jerry Ma 2025-03-08 14:29:44 +08:00 committed by GitHub
parent 15c7e41501
commit d30d1fc447
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 138 additions and 34 deletions

View File

@ -1,4 +1,29 @@
{
"lib-base": {
"type": "root",
"lib-depends-unix": [
"pkg-config"
]
},
"php": {
"type": "root",
"source": "php-src",
"lib-depends": [
"micro",
"lib-base"
]
},
"micro": {
"type": "target",
"source": "micro"
},
"pkg-config": {
"type": "package",
"source": "pkg-config",
"bin-unix": [
"pkg-config"
]
},
"brotli": {
"source": "brotli",
"static-libs-unix": [
@ -599,9 +624,6 @@
"zlib"
]
},
"pkg-config": {
"source": "pkg-config"
},
"postgresql": {
"source": "postgresql",
"static-libs-unix": [

View File

@ -146,6 +146,17 @@ abstract class LibraryBase
return Config::getLib(static::NAME, 'headers', []);
}
/**
* Get binary files.
*
* @throws FileSystemException
* @throws WrongUsageException
*/
public function getBinaryFiles(): array
{
return Config::getLib(static::NAME, 'bin', []);
}
/**
* @throws WrongUsageException
* @throws FileSystemException
@ -203,7 +214,8 @@ abstract class LibraryBase
}
// force means just build
if ($force_build) {
logger()->info('Building required library [' . static::NAME . ']');
$type = Config::getLib(static::NAME, 'type', 'lib');
logger()->info('Building required ' . $type . ' [' . static::NAME . ']');
// extract first if not exists
if (!is_dir($this->source_dir)) {
@ -236,10 +248,14 @@ abstract class LibraryBase
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->tryBuild(true);
return LIB_STATUS_OK;
// current library is package and binary file is not exists
if (Config::getLib(static::NAME, 'type', 'lib') === 'package') {
foreach ($this->getBinaryFiles() as $name) {
if (!file_exists(BUILD_BIN_PATH . "/{$name}")) {
$this->tryBuild(true);
return LIB_STATUS_OK;
}
}
}
// if all the files exist at this point, skip the compilation process
return LIB_STATUS_ALREADY;

View File

@ -110,13 +110,11 @@ abstract class UnixBuilderBase extends BuilderBase
$sorted_libraries = DependencyUtil::getLibs($libraries);
}
// pkg-config must be compiled first, whether it is specified or not
if (!in_array('pkg-config', $sorted_libraries)) {
array_unshift($sorted_libraries, 'pkg-config');
}
// add lib object for builder
foreach ($sorted_libraries as $library) {
if (!in_array(Config::getLib($library, 'type', 'lib'), ['lib', 'package'])) {
continue;
}
// if some libs are not supported (but in config "lib.json", throw exception)
if (!isset($support_lib_list[$library])) {
throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!');

View File

@ -236,6 +236,9 @@ class WindowsBuilder extends BuilderBase
// add lib object for builder
foreach ($sorted_libraries as $library) {
if (!in_array(Config::getLib($library, 'type', 'lib'), ['lib', 'package'])) {
continue;
}
// if some libs are not supported (but in config "lib.json", throw exception)
if (!isset($support_lib_list[$library])) {
throw new WrongUsageException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!');

View File

@ -46,7 +46,6 @@ abstract class BaseCommand extends Command
E_USER_ERROR => ['PHP Error: ', 'error'],
E_USER_WARNING => ['PHP Warning: ', 'warning'],
E_USER_NOTICE => ['PHP Notice: ', 'notice'],
E_STRICT => ['PHP Strict: ', 'notice'],
E_RECOVERABLE_ERROR => ['PHP Recoverable Error: ', 'error'],
E_DEPRECATED => ['PHP Deprecated: ', 'notice'],
E_USER_DEPRECATED => ['PHP User Deprecated: ', 'notice'],
@ -56,7 +55,7 @@ abstract class BaseCommand extends Command
logger()->{$level_tip[1]}($error);
// 如果 return false 则错误会继续递交给 PHP 标准错误处理
return true;
}, E_ALL | E_STRICT);
});
$version = ConsoleApplication::VERSION;
if (!$this->no_motd) {
echo " _ _ _ _

View File

@ -7,6 +7,7 @@ namespace SPC\command;
use SPC\builder\BuilderProvider;
use SPC\exception\ExceptionHandler;
use SPC\exception\WrongUsageException;
use SPC\store\Config;
use SPC\store\FileSystem;
use SPC\store\SourcePatcher;
use SPC\util\DependencyUtil;
@ -107,13 +108,14 @@ class BuildCliCommand extends BuildCommand
$include_suggest_ext = $this->getOption('with-suggested-exts');
$include_suggest_lib = $this->getOption('with-suggested-libs');
[$extensions, $libraries, $not_included] = DependencyUtil::getExtsAndLibs($extensions, $libraries, $include_suggest_ext, $include_suggest_lib);
$display_libs = array_filter($libraries, fn ($lib) => in_array(Config::getLib($lib, 'type', 'lib'), ['lib', 'package']));
// print info
$indent_texts = [
'Build OS' => PHP_OS_FAMILY . ' (' . php_uname('m') . ')',
'Build SAPI' => $builder->getBuildTypeName($rule),
'Extensions (' . count($extensions) . ')' => implode(',', $extensions),
'Libraries (' . count($libraries) . ')' => implode(',', $libraries),
'Libraries (' . count($libraries) . ')' => implode(',', $display_libs),
'Strip Binaries' => $builder->getOption('no-strip') ? 'no' : 'yes',
'Enable ZTS' => $builder->getOption('enable-zts') ? 'yes' : 'no',
];

View File

@ -7,6 +7,7 @@ namespace SPC\command;
use SPC\builder\BuilderProvider;
use SPC\exception\ExceptionHandler;
use SPC\exception\RuntimeException;
use SPC\store\Config;
use SPC\util\DependencyUtil;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
@ -61,7 +62,9 @@ class BuildLibsCommand extends BuildCommand
$builder->setLibsOnly();
// 编译和检查库完整
$libraries = DependencyUtil::getLibs($libraries);
logger()->info('Building libraries: ' . implode(',', $libraries));
$display_libs = array_filter($libraries, fn ($lib) => in_array(Config::getLib($lib, 'type', 'lib'), ['lib', 'package']));
logger()->info('Building libraries: ' . implode(',', $display_libs));
sleep(2);
$builder->proveLibs($libraries);
$builder->validateLibsAndExts();

View File

@ -72,10 +72,6 @@ class DownloadCommand extends BaseCommand
if ($for_ext = $input->getOption('for-extensions')) {
$ext = $this->parseExtensionList($for_ext);
$sources = $this->calculateSourcesByExt($ext, !$input->getOption('without-suggestions'));
if (PHP_OS_FAMILY !== 'Windows') {
array_unshift($sources, 'pkg-config');
}
array_unshift($sources, 'php-src', 'micro');
$final_sources = array_merge($final_sources, array_diff($sources, $final_sources));
}
// mode: --for-libs
@ -323,7 +319,10 @@ class DownloadCommand extends BaseCommand
}
}
foreach ($libraries as $library) {
$sources[] = Config::getLib($library, 'source');
$source = Config::getLib($library, 'source');
if ($source !== null) {
$sources[] = $source;
}
}
return array_values(array_unique($sources));
}

View File

@ -33,7 +33,17 @@ class SortConfigCommand extends BaseCommand
case 'lib':
$file = json_decode(FileSystem::readFile(ROOT_DIR . '/config/lib.json'), true);
ConfigValidator::validateLibs($file);
ksort($file);
uksort($file, function ($a, $b) use ($file) {
$type_a = $file[$a]['type'] ?? 'lib';
$type_b = $file[$b]['type'] ?? 'lib';
$type_order = ['root', 'target', 'package', 'lib'];
// compare type first
if ($type_a !== $type_b) {
return array_search($type_a, $type_order) <=> array_search($type_b, $type_order);
}
// compare name
return $a <=> $b;
});
if (!file_put_contents(ROOT_DIR . '/config/lib.json', json_encode($file, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n")) {
$this->output->writeln('<error>Write file lib.json failed!</error>');
return static::FAILURE;

View File

@ -73,7 +73,7 @@ class Config
if (!isset(self::$lib[$name])) {
throw new WrongUsageException('lib [' . $name . '] is not supported yet');
}
$supported_sys_based = ['static-libs', 'headers', 'lib-depends', 'lib-suggests', 'frameworks'];
$supported_sys_based = ['static-libs', 'headers', 'lib-depends', 'lib-suggests', 'frameworks', 'bin'];
if ($key !== null && in_array($key, $supported_sys_based)) {
$m_key = match (PHP_OS_FAMILY) {
'Windows' => ['-windows', '-win', ''],

View File

@ -53,13 +53,45 @@ class ConfigValidator
*/
public static function validateLibs(mixed $data, array $source_data = []): void
{
is_array($data) || throw new ValidationException('lib.json is broken');
// check if it is an array
if (!is_array($data)) {
throw new ValidationException('lib.json is broken');
}
// check each lib
foreach ($data as $name => $lib) {
isset($lib['source']) || throw new ValidationException("lib {$name} does not assign any source");
is_string($lib['source']) || throw new ValidationException("lib {$name} source must be string");
empty($source_data) || isset($source_data[$lib['source']]) || throw new ValidationException("lib {$name} assigns an invalid source: {$lib['source']}");
!isset($lib['lib-depends']) || !is_assoc_array($lib['lib-depends']) || throw new ValidationException("lib {$name} dependencies must be a list");
!isset($lib['lib-suggests']) || !is_assoc_array($lib['lib-suggests']) || throw new ValidationException("lib {$name} suggested dependencies must be a list");
// check if lib is an assoc array
if (!is_assoc_array($lib)) {
throw new ValidationException("lib {$name} is not an object");
}
// check if lib has valid type
if (!in_array($lib['type'] ?? 'lib', ['lib', 'package', 'target', 'root'])) {
throw new ValidationException("lib {$name} type is invalid");
}
// check if lib and package has source
if (in_array($lib['type'] ?? 'lib', ['lib', 'package']) && !isset($lib['source'])) {
throw new ValidationException("lib {$name} does not assign any source");
}
// check if source is valid
if (isset($lib['source']) && !empty($source_data) && !isset($source_data[$lib['source']])) {
throw new ValidationException("lib {$name} assigns an invalid source: {$lib['source']}");
}
// check if [lib-depends|lib-suggests|static-libs][-windows|-unix|-macos|-linux] are valid list array
$suffixes = ['', '-windows', '-unix', '-macos', '-linux'];
foreach ($suffixes as $suffix) {
if (isset($lib['lib-depends' . $suffix]) && !is_list_array($lib['lib-depends' . $suffix])) {
throw new ValidationException("lib {$name} lib-depends must be a list");
}
if (isset($lib['lib-suggests' . $suffix]) && !is_list_array($lib['lib-suggests' . $suffix])) {
throw new ValidationException("lib {$name} lib-suggests must be a list");
}
if (isset($lib['static-libs' . $suffix]) && !is_list_array($lib['static-libs' . $suffix])) {
throw new ValidationException("lib {$name} static-libs must be a list");
}
}
// check if frameworks is a list array
if (isset($lib['frameworks']) && !is_list_array($lib['frameworks'])) {
throw new ValidationException("lib {$name} frameworks must be a list");
}
}
}

View File

@ -33,7 +33,7 @@ class DependencyUtil
$ext_suggests = array_map(fn ($x) => "ext@{$x}", $ext_suggests);
// merge ext-depends with lib-depends
$lib_depends = Config::getExt($ext_name, 'lib-depends', []);
$depends = array_merge($ext_depends, $lib_depends);
$depends = array_merge($ext_depends, $lib_depends, ['php']);
// merge ext-suggests with lib-suggests
$lib_suggests = Config::getExt($ext_name, 'lib-suggests', []);
$suggests = array_merge($ext_suggests, $lib_suggests);
@ -44,7 +44,7 @@ class DependencyUtil
}
foreach ($libs as $lib_name => $lib) {
$dep_list[$lib_name] = [
'depends' => Config::getLib($lib_name, 'lib-depends', []),
'depends' => array_merge(Config::getLib($lib_name, 'lib-depends', []), ['lib-base']),
'suggests' => Config::getLib($lib_name, 'lib-suggests', []),
];
}
@ -210,6 +210,9 @@ class DependencyUtil
}
$visited[$lib_name] = true;
// 遍历该依赖的所有依赖(此处的 getLib 如果检测到当前库不存在的话,会抛出异常)
if (!isset($dep_list[$lib_name])) {
throw new WrongUsageException("{$lib_name} not exist !");
}
foreach ($dep_list[$lib_name]['depends'] as $dep) {
self::visitPlatDeps($dep, $dep_list, $visited, $sorted);
}

View File

@ -70,6 +70,9 @@ class LicenseDumper
}
foreach ($this->libs as $lib) {
if (Config::getLib($lib, 'type', 'lib') !== 'lib') {
continue;
}
$source_name = Config::getLib($lib, 'source');
foreach ($this->getSourceLicenses($source_name) as $index => $license) {
$result = file_put_contents("{$target_dir}/lib_{$lib}_{$index}.txt", $license);

View File

@ -20,6 +20,14 @@ function is_assoc_array(mixed $array): bool
return is_array($array) && (!empty($array) && array_keys($array) !== range(0, count($array) - 1));
}
/**
* Judge if an array is a list
*/
function is_list_array(mixed $array): bool
{
return is_array($array) && (empty($array) || array_keys($array) === range(0, count($array) - 1));
}
/**
* Return a logger instance
*/

View File

@ -71,7 +71,7 @@ class ExtensionTest extends TestCase
public function testRunCliCheckWindows()
{
if (is_unix()) {
$this->markTestIncomplete('This test is for Windows only');
$this->markTestSkipped('This test is for Windows only');
} else {
$this->extension->runCliCheckWindows();
$this->assertTrue(true);

View File

@ -26,7 +26,7 @@ class UnixSystemUtilTest extends TestCase
default => null,
};
if ($util_class === null) {
self::markTestIncomplete('This test is only for Unix');
self::markTestSkipped('This test is only for Unix');
}
$this->util = new $util_class();
}

View File

@ -29,6 +29,8 @@ final class DependencyUtilTest extends TestCase
],
];
Config::$lib = [
'lib-base' => ['type' => 'root'],
'php' => ['type' => 'root'],
'libaaa' => [
'source' => 'test1',
'static-libs' => ['libaaa.a'],

View File

@ -34,6 +34,8 @@ final class LicenseDumperTest extends TestCase
public function testDumpWithSingleLicense(): void
{
Config::$lib = [
'lib-base' => ['type' => 'root'],
'php' => ['type' => 'root'],
'fake_lib' => [
'source' => 'fake_lib',
],
@ -57,6 +59,8 @@ final class LicenseDumperTest extends TestCase
public function testDumpWithMultipleLicenses(): void
{
Config::$lib = [
'lib-base' => ['type' => 'root'],
'php' => ['type' => 'root'],
'fake_lib' => [
'source' => 'fake_lib',
],