diff --git a/config/lib.json b/config/lib.json index 1565a1a65..cfe419271 100644 --- a/config/lib.json +++ b/config/lib.json @@ -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": [ diff --git a/src/SPC/builder/LibraryBase.php b/src/SPC/builder/LibraryBase.php index 1da2336a7..e0500cbe9 100644 --- a/src/SPC/builder/LibraryBase.php +++ b/src/SPC/builder/LibraryBase.php @@ -146,6 +146,17 @@ public function getHeaders(): array 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 @@ public function tryBuild(bool $force_build = false): int } // 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 @@ public function tryBuild(bool $force_build = false): int 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; diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index f7ab19183..26217cbb8 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -110,13 +110,11 @@ public function proveLibs(array $sorted_libraries): void $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!'); diff --git a/src/SPC/builder/windows/WindowsBuilder.php b/src/SPC/builder/windows/WindowsBuilder.php index 76fac3b73..1c43c3758 100644 --- a/src/SPC/builder/windows/WindowsBuilder.php +++ b/src/SPC/builder/windows/WindowsBuilder.php @@ -236,6 +236,9 @@ public function proveLibs(array $sorted_libraries): void // 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!'); diff --git a/src/SPC/command/BaseCommand.php b/src/SPC/command/BaseCommand.php index 6a6c23137..3688c2494 100644 --- a/src/SPC/command/BaseCommand.php +++ b/src/SPC/command/BaseCommand.php @@ -46,7 +46,6 @@ public function initialize(InputInterface $input, OutputInterface $output): void 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 @@ public function initialize(InputInterface $input, OutputInterface $output): void logger()->{$level_tip[1]}($error); // 如果 return false 则错误会继续递交给 PHP 标准错误处理 return true; - }, E_ALL | E_STRICT); + }); $version = ConsoleApplication::VERSION; if (!$this->no_motd) { echo " _ _ _ _ diff --git a/src/SPC/command/BuildCliCommand.php b/src/SPC/command/BuildCliCommand.php index 38184a79c..2750b5e42 100644 --- a/src/SPC/command/BuildCliCommand.php +++ b/src/SPC/command/BuildCliCommand.php @@ -7,6 +7,7 @@ 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 @@ public function handle(): int $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', ]; diff --git a/src/SPC/command/BuildLibsCommand.php b/src/SPC/command/BuildLibsCommand.php index 4f20edf3c..8e87d1b9d 100644 --- a/src/SPC/command/BuildLibsCommand.php +++ b/src/SPC/command/BuildLibsCommand.php @@ -7,6 +7,7 @@ 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 @@ public function handle(): int $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(); diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index 80ec6eabf..9c682ea13 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -72,10 +72,6 @@ public function initialize(InputInterface $input, OutputInterface $output): void 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 @@ private function calculateSourcesByExt(array $extensions, bool $include_suggests } } 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)); } diff --git a/src/SPC/command/dev/SortConfigCommand.php b/src/SPC/command/dev/SortConfigCommand.php index bc29ed130..dcac4c344 100644 --- a/src/SPC/command/dev/SortConfigCommand.php +++ b/src/SPC/command/dev/SortConfigCommand.php @@ -33,7 +33,17 @@ public function handle(): int 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('Write file lib.json failed!'); return static::FAILURE; diff --git a/src/SPC/store/Config.php b/src/SPC/store/Config.php index 42895894e..07ed2887c 100644 --- a/src/SPC/store/Config.php +++ b/src/SPC/store/Config.php @@ -73,7 +73,7 @@ public static function getLib(string $name, ?string $key = null, mixed $default 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', ''], diff --git a/src/SPC/util/ConfigValidator.php b/src/SPC/util/ConfigValidator.php index 924c1b0cb..c992d90ff 100644 --- a/src/SPC/util/ConfigValidator.php +++ b/src/SPC/util/ConfigValidator.php @@ -53,13 +53,45 @@ public static function validateSource(array $data): void */ 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"); + } } } diff --git a/src/SPC/util/DependencyUtil.php b/src/SPC/util/DependencyUtil.php index a4d76ddbc..8985e4bcf 100644 --- a/src/SPC/util/DependencyUtil.php +++ b/src/SPC/util/DependencyUtil.php @@ -33,7 +33,7 @@ public static function platExtToLibs(): array $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 @@ public static function platExtToLibs(): array } 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 @@ private static function visitPlatDeps(string $lib_name, array $dep_list, array & } $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); } diff --git a/src/SPC/util/LicenseDumper.php b/src/SPC/util/LicenseDumper.php index 69f211992..bc1c6293b 100644 --- a/src/SPC/util/LicenseDumper.php +++ b/src/SPC/util/LicenseDumper.php @@ -70,6 +70,9 @@ public function dump(string $target_dir): bool } 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); diff --git a/src/globals/functions.php b/src/globals/functions.php index b7eceeae9..8432f9472 100644 --- a/src/globals/functions.php +++ b/src/globals/functions.php @@ -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 */ diff --git a/tests/SPC/builder/ExtensionTest.php b/tests/SPC/builder/ExtensionTest.php index 5e8ae92d6..499a800fa 100644 --- a/tests/SPC/builder/ExtensionTest.php +++ b/tests/SPC/builder/ExtensionTest.php @@ -71,7 +71,7 @@ public function testGetDistName() 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); diff --git a/tests/SPC/builder/unix/UnixSystemUtilTest.php b/tests/SPC/builder/unix/UnixSystemUtilTest.php index fec21e360..8da6c278a 100644 --- a/tests/SPC/builder/unix/UnixSystemUtilTest.php +++ b/tests/SPC/builder/unix/UnixSystemUtilTest.php @@ -26,7 +26,7 @@ public function setUp(): void 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(); } diff --git a/tests/SPC/util/DependencyUtilTest.php b/tests/SPC/util/DependencyUtilTest.php index b77d9d79c..a4f7839f1 100644 --- a/tests/SPC/util/DependencyUtilTest.php +++ b/tests/SPC/util/DependencyUtilTest.php @@ -29,6 +29,8 @@ public function testGetExtLibsByDeps(): void ], ]; Config::$lib = [ + 'lib-base' => ['type' => 'root'], + 'php' => ['type' => 'root'], 'libaaa' => [ 'source' => 'test1', 'static-libs' => ['libaaa.a'], diff --git a/tests/SPC/util/LicenseDumperTest.php b/tests/SPC/util/LicenseDumperTest.php index 5462df58a..c175ddbf5 100644 --- a/tests/SPC/util/LicenseDumperTest.php +++ b/tests/SPC/util/LicenseDumperTest.php @@ -34,6 +34,8 @@ protected function tearDown(): void public function testDumpWithSingleLicense(): void { Config::$lib = [ + 'lib-base' => ['type' => 'root'], + 'php' => ['type' => 'root'], 'fake_lib' => [ 'source' => 'fake_lib', ], @@ -57,6 +59,8 @@ public function testDumpWithSingleLicense(): void public function testDumpWithMultipleLicenses(): void { Config::$lib = [ + 'lib-base' => ['type' => 'root'], + 'php' => ['type' => 'root'], 'fake_lib' => [ 'source' => 'fake_lib', ],