diff --git a/config/lib.json b/config/lib.json index 1565a1a6..cfe41927 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 1da2336a..e0500cbe 100644 --- a/src/SPC/builder/LibraryBase.php +++ b/src/SPC/builder/LibraryBase.php @@ -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; diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index f7ab1918..26217cbb 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -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!'); diff --git a/src/SPC/builder/windows/WindowsBuilder.php b/src/SPC/builder/windows/WindowsBuilder.php index 76fac3b7..1c43c375 100644 --- a/src/SPC/builder/windows/WindowsBuilder.php +++ b/src/SPC/builder/windows/WindowsBuilder.php @@ -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!'); diff --git a/src/SPC/command/BaseCommand.php b/src/SPC/command/BaseCommand.php index 6a6c2313..3688c249 100644 --- a/src/SPC/command/BaseCommand.php +++ b/src/SPC/command/BaseCommand.php @@ -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 " _ _ _ _ diff --git a/src/SPC/command/BuildCliCommand.php b/src/SPC/command/BuildCliCommand.php index 38184a79..2750b5e4 100644 --- a/src/SPC/command/BuildCliCommand.php +++ b/src/SPC/command/BuildCliCommand.php @@ -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', ]; diff --git a/src/SPC/command/BuildLibsCommand.php b/src/SPC/command/BuildLibsCommand.php index 4f20edf3..8e87d1b9 100644 --- a/src/SPC/command/BuildLibsCommand.php +++ b/src/SPC/command/BuildLibsCommand.php @@ -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(); diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index 80ec6eab..9c682ea1 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -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)); } diff --git a/src/SPC/command/dev/SortConfigCommand.php b/src/SPC/command/dev/SortConfigCommand.php index bc29ed13..dcac4c34 100644 --- a/src/SPC/command/dev/SortConfigCommand.php +++ b/src/SPC/command/dev/SortConfigCommand.php @@ -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('Write file lib.json failed!'); return static::FAILURE; diff --git a/src/SPC/store/Config.php b/src/SPC/store/Config.php index 42895894..07ed2887 100644 --- a/src/SPC/store/Config.php +++ b/src/SPC/store/Config.php @@ -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', ''], diff --git a/src/SPC/util/ConfigValidator.php b/src/SPC/util/ConfigValidator.php index 924c1b0c..c992d90f 100644 --- a/src/SPC/util/ConfigValidator.php +++ b/src/SPC/util/ConfigValidator.php @@ -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"); + } } } diff --git a/src/SPC/util/DependencyUtil.php b/src/SPC/util/DependencyUtil.php index a4d76ddb..8985e4bc 100644 --- a/src/SPC/util/DependencyUtil.php +++ b/src/SPC/util/DependencyUtil.php @@ -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); } diff --git a/src/SPC/util/LicenseDumper.php b/src/SPC/util/LicenseDumper.php index 69f21199..bc1c6293 100644 --- a/src/SPC/util/LicenseDumper.php +++ b/src/SPC/util/LicenseDumper.php @@ -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); diff --git a/src/globals/functions.php b/src/globals/functions.php index b7eceeae..8432f947 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 5e8ae92d..499a800f 100644 --- a/tests/SPC/builder/ExtensionTest.php +++ b/tests/SPC/builder/ExtensionTest.php @@ -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); diff --git a/tests/SPC/builder/unix/UnixSystemUtilTest.php b/tests/SPC/builder/unix/UnixSystemUtilTest.php index fec21e36..8da6c278 100644 --- a/tests/SPC/builder/unix/UnixSystemUtilTest.php +++ b/tests/SPC/builder/unix/UnixSystemUtilTest.php @@ -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(); } diff --git a/tests/SPC/util/DependencyUtilTest.php b/tests/SPC/util/DependencyUtilTest.php index b77d9d79..a4f7839f 100644 --- a/tests/SPC/util/DependencyUtilTest.php +++ b/tests/SPC/util/DependencyUtilTest.php @@ -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'], diff --git a/tests/SPC/util/LicenseDumperTest.php b/tests/SPC/util/LicenseDumperTest.php index 5462df58..c175ddbf 100644 --- a/tests/SPC/util/LicenseDumperTest.php +++ b/tests/SPC/util/LicenseDumperTest.php @@ -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', ],