libraries */ protected array $libs = []; /** @var array extensions */ protected array $exts = []; /** @var bool compile libs only (just mark it) */ protected bool $libs_only = false; /** @var array compile options */ protected array $options = []; /** * Build libraries * * @param array $libraries Libraries to build * @throws FileSystemException * @throws RuntimeException * @throws WrongUsageException */ public function buildLibs(array $libraries): void { // search all supported libs $support_lib_list = []; $classes = FileSystem::getClassesPsr4( ROOT_DIR . '/src/SPC/builder/' . osfamily2dir() . '/library', 'SPC\\builder\\' . osfamily2dir() . '\\library' ); foreach ($classes as $class) { if (defined($class . '::NAME') && $class::NAME !== 'unknown' && Config::getLib($class::NAME) !== null) { $support_lib_list[$class::NAME] = $class; } } // if no libs specified, compile all supported libs if ($libraries === [] && $this->isLibsOnly()) { $libraries = array_keys($support_lib_list); } // pkg-config must be compiled first, whether it is specified or not if (!in_array('pkg-config', $libraries)) { array_unshift($libraries, 'pkg-config'); } // append dependencies $libraries = DependencyUtil::getLibsByDeps($libraries); // add lib object for builder foreach ($libraries as $library) { // if some libs are not supported (but in config "lib.json", throw exception) if (!isset($support_lib_list[$library])) { throw new RuntimeException('library [' . $library . '] is in the lib.json list but not supported to compile, but in the future I will support it!'); } $lib = new ($support_lib_list[$library])($this); $this->addLib($lib); } // calculate and check dependencies foreach ($this->libs as $lib) { $lib->calcDependency(); } // extract sources SourceExtractor::initSource(libs: $libraries); // build all libs foreach ($this->libs as $lib) { match ($lib->tryBuild($this->getOption('rebuild', false))) { BUILD_STATUS_OK => logger()->info('lib [' . $lib::NAME . '] build success'), BUILD_STATUS_ALREADY => logger()->notice('lib [' . $lib::NAME . '] already built'), BUILD_STATUS_FAILED => logger()->error('lib [' . $lib::NAME . '] build failed'), default => logger()->warning('lib [' . $lib::NAME . '] build status unknown'), }; } } /** * Add library to build. * * @param LibraryBase $library Library object */ public function addLib(LibraryBase $library): void { $this->libs[$library::NAME] = $library; } /** * Get library object by name. */ public function getLib(string $name): ?LibraryBase { return $this->libs[$name] ?? null; } /** * Add extension to build. */ public function addExt(Extension $extension): void { $this->exts[$extension->getName()] = $extension; } /** * Get extension object by name. */ public function getExt(string $name): ?Extension { return $this->exts[$name] ?? null; } /** * Get all extension objects. * * @return Extension[] */ public function getExts(): array { return $this->exts; } /** * Check if there is a cpp extension. * * @throws FileSystemException * @throws WrongUsageException */ public function hasCppExtension(): bool { // judge cpp-extension $exts = array_keys($this->getExts()); $cpp = false; foreach ($exts as $ext) { if (Config::getExt($ext, 'cpp-extension', false) === true) { $cpp = true; break; } } return $cpp; } /** * Set libs only mode. */ public function setLibsOnly(bool $status = true): void { $this->libs_only = $status; } /** * Verify the list of "ext" extensions for validity and declare an Extension object to check the dependencies of the extensions. * * @throws FileSystemException * @throws RuntimeException * @throws \ReflectionException * @throws WrongUsageException */ public function proveExts(array $extensions): void { CustomExt::loadCustomExt(); SourceExtractor::initSource(sources: ['php-src']); if ($this->getPHPVersionID() >= 80000) { SourceExtractor::initSource(sources: ['micro']); } SourceExtractor::initSource(exts: $extensions); foreach ($extensions as $extension) { $class = CustomExt::getExtClass($extension); $ext = new $class($extension, $this); $this->addExt($ext); } foreach ($this->exts as $ext) { $ext->checkDependency(); } } /** * Start to build PHP * * @param int $build_target Build target, see BUILD_TARGET_* */ abstract public function buildPHP(int $build_target = BUILD_TARGET_NONE); /** * Generate extension enable arguments for configure. * e.g. --enable-mbstring * * @throws FileSystemException * @throws WrongUsageException */ public function makeExtensionArgs(): string { $ret = []; foreach ($this->exts as $ext) { $ret[] = trim($ext->getConfigureArg()); } logger()->info('Using configure: ' . implode(' ', $ret)); return implode(' ', $ret); } /** * Get libs only mode. */ public function isLibsOnly(): bool { return $this->libs_only; } /** * Get PHP Version ID from php-src/main/php_version.h */ public function getPHPVersionID(): int { $file = file_get_contents(SOURCE_PATH . '/php-src/main/php_version.h'); preg_match('/PHP_VERSION_ID (\d+)/', $file, $match); return intval($match[1]); } /** * Get build type name string to display. * * @param int $type Build target type */ public function getBuildTypeName(int $type): string { $ls = []; if (($type & BUILD_TARGET_CLI) === BUILD_TARGET_CLI) { $ls[] = 'cli'; } if (($type & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO) { $ls[] = 'micro'; } if (($type & BUILD_TARGET_FPM) === BUILD_TARGET_FPM) { $ls[] = 'fpm'; } return implode(', ', $ls); } /** * Get builder options (maybe changed by user) * * @param string $key Option key * @param mixed $default If not exists, return this value */ public function getOption(string $key, mixed $default = null): mixed { return $this->options[$key] ?? $default; } /** * Get all builder options */ public function getOptions(): array { return $this->options; } /** * Set builder options if not exists. */ public function setOptionIfNotExist(string $key, mixed $value): void { if (!isset($this->options[$key])) { $this->options[$key] = $value; } } /** * Set builder options. */ public function setOption(string $key, mixed $value): void { $this->options[$key] = $value; } /** * Check if all libs are downloaded. * If not, throw exception. * * @throws RuntimeException */ protected function checkLibsSource(): void { $not_downloaded = []; foreach ($this->libs as $lib) { if (!file_exists($lib->getSourceDir())) { $not_downloaded[] = $lib::NAME; } } if ($not_downloaded !== []) { throw new RuntimeException( '"' . implode(', ', $not_downloaded) . '" totally ' . count($not_downloaded) . ' source' . (count($not_downloaded) === 1 ? '' : 's') . ' not downloaded, maybe you need to "fetch" ' . (count($not_downloaded) === 1 ? 'it' : 'them') . ' first?' ); } } }