2023-03-18 17:32:21 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace SPC\builder;
|
|
|
|
|
|
|
|
|
|
use SPC\exception\FileSystemException;
|
|
|
|
|
use SPC\exception\RuntimeException;
|
2023-08-20 19:51:45 +08:00
|
|
|
use SPC\exception\WrongUsageException;
|
2023-03-18 17:32:21 +08:00
|
|
|
use SPC\store\Config;
|
2024-07-07 20:45:18 +08:00
|
|
|
use SPC\store\FileSystem;
|
2024-07-01 10:29:31 +08:00
|
|
|
use SPC\store\SourceManager;
|
2023-03-18 17:32:21 +08:00
|
|
|
|
|
|
|
|
abstract class LibraryBase
|
|
|
|
|
{
|
2023-08-20 19:51:45 +08:00
|
|
|
/** @var string */
|
2023-03-18 17:32:21 +08:00
|
|
|
public const NAME = 'unknown';
|
|
|
|
|
|
|
|
|
|
protected string $source_dir;
|
|
|
|
|
|
|
|
|
|
protected array $dependencies = [];
|
|
|
|
|
|
2023-10-14 14:05:33 +08:00
|
|
|
protected bool $patched = false;
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
/**
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
*/
|
|
|
|
|
public function __construct(?string $source_dir = null)
|
|
|
|
|
{
|
|
|
|
|
if (static::NAME === 'unknown') {
|
|
|
|
|
throw new RuntimeException('no unknown!!!!!');
|
|
|
|
|
}
|
|
|
|
|
$this->source_dir = $source_dir ?? (SOURCE_PATH . '/' . static::NAME);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-07 20:45:18 +08:00
|
|
|
/**
|
|
|
|
|
* Try to install or build this library.
|
|
|
|
|
* @param bool $force If true, force install or build
|
|
|
|
|
* @throws FileSystemException
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
* @throws WrongUsageException
|
|
|
|
|
*/
|
|
|
|
|
public function setup(bool $force = false): int
|
|
|
|
|
{
|
|
|
|
|
$lock = json_decode(FileSystem::readFile(DOWNLOAD_PATH . '/.lock.json'), true) ?? [];
|
|
|
|
|
$source = Config::getLib(static::NAME, 'source');
|
|
|
|
|
// if source is locked as pre-built, we just tryInstall it
|
2024-07-07 21:18:30 +08:00
|
|
|
if (isset($lock[$source]) && ($lock[$source]['lock_as'] ?? SPC_LOCK_SOURCE) === SPC_LOCK_PRE_BUILT) {
|
2024-07-07 20:45:18 +08:00
|
|
|
return $this->tryInstall($lock[$source]['filename'], $force);
|
|
|
|
|
}
|
|
|
|
|
return $this->tryBuild($force);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get library name.
|
|
|
|
|
*/
|
|
|
|
|
public function getName(): string
|
|
|
|
|
{
|
|
|
|
|
return static::NAME;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Get current lib source root dir.
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function getSourceDir(): string
|
|
|
|
|
{
|
|
|
|
|
return $this->source_dir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Get current lib dependencies.
|
2023-03-18 17:32:21 +08:00
|
|
|
*
|
2023-08-20 19:51:45 +08:00
|
|
|
* @return array<string, LibraryBase>
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function getDependencies(bool $recursive = false): array
|
|
|
|
|
{
|
|
|
|
|
// 非递归情况下直接返回通过 addLibraryDependency 方法添加的依赖
|
|
|
|
|
if (!$recursive) {
|
|
|
|
|
return $this->dependencies;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$deps = [];
|
|
|
|
|
|
|
|
|
|
$added = 1;
|
|
|
|
|
while ($added !== 0) {
|
|
|
|
|
$added = 0;
|
|
|
|
|
foreach ($this->dependencies as $depName => $dep) {
|
|
|
|
|
foreach ($dep->getDependencies(true) as $depdepName => $depdep) {
|
|
|
|
|
if (!in_array($depdepName, array_keys($deps), true)) {
|
|
|
|
|
$deps[$depdepName] = $depdep;
|
|
|
|
|
++$added;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!in_array($depName, array_keys($deps), true)) {
|
|
|
|
|
$deps[$depName] = $dep;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $deps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Calculate dependencies for current library.
|
2023-03-18 17:32:21 +08:00
|
|
|
*
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
* @throws FileSystemException
|
2023-08-20 19:51:45 +08:00
|
|
|
* @throws WrongUsageException
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function calcDependency(): void
|
|
|
|
|
{
|
2023-08-20 19:51:45 +08:00
|
|
|
// Add dependencies from the configuration file. Here, choose different metadata based on the operating system.
|
2023-03-18 17:32:21 +08:00
|
|
|
/*
|
2023-08-20 19:51:45 +08:00
|
|
|
Rules:
|
|
|
|
|
If it is a Windows system, try the following dependencies in order: lib-depends-windows, lib-depends-win, lib-depends.
|
|
|
|
|
If it is a macOS system, try the following dependencies in order: lib-depends-darwin, lib-depends-unix, lib-depends.
|
|
|
|
|
If it is a Linux system, try the following dependencies in order: lib-depends-linux, lib-depends-unix, lib-depends.
|
|
|
|
|
*/
|
2023-03-18 17:32:21 +08:00
|
|
|
foreach (Config::getLib(static::NAME, 'lib-depends', []) as $dep_name) {
|
|
|
|
|
$this->addLibraryDependency($dep_name);
|
|
|
|
|
}
|
|
|
|
|
foreach (Config::getLib(static::NAME, 'lib-suggests', []) as $dep_name) {
|
|
|
|
|
$this->addLibraryDependency($dep_name, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Get config static libs.
|
2023-03-18 17:32:21 +08:00
|
|
|
*
|
|
|
|
|
* @throws FileSystemException
|
2023-08-20 19:51:45 +08:00
|
|
|
* @throws WrongUsageException
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function getStaticLibs(): array
|
|
|
|
|
{
|
|
|
|
|
return Config::getLib(static::NAME, 'static-libs', []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Get config headers.
|
2023-03-18 17:32:21 +08:00
|
|
|
*
|
|
|
|
|
* @throws FileSystemException
|
2023-08-20 19:51:45 +08:00
|
|
|
* @throws WrongUsageException
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function getHeaders(): array
|
|
|
|
|
{
|
|
|
|
|
return Config::getLib(static::NAME, 'headers', []);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-08 14:29:44 +08:00
|
|
|
/**
|
|
|
|
|
* Get binary files.
|
|
|
|
|
*
|
|
|
|
|
* @throws FileSystemException
|
|
|
|
|
* @throws WrongUsageException
|
|
|
|
|
*/
|
|
|
|
|
public function getBinaryFiles(): array
|
|
|
|
|
{
|
|
|
|
|
return Config::getLib(static::NAME, 'bin', []);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-07 20:45:18 +08:00
|
|
|
/**
|
|
|
|
|
* @throws WrongUsageException
|
|
|
|
|
* @throws FileSystemException
|
|
|
|
|
*/
|
|
|
|
|
public function tryInstall(string $install_file, bool $force_install = false): int
|
|
|
|
|
{
|
|
|
|
|
if ($force_install) {
|
|
|
|
|
logger()->info('Installing required library [' . static::NAME . '] from pre-built binaries');
|
|
|
|
|
|
|
|
|
|
// Extract files
|
|
|
|
|
try {
|
|
|
|
|
FileSystem::extractPackage($install_file, DOWNLOAD_PATH . '/' . $install_file, BUILD_ROOT_PATH);
|
|
|
|
|
$this->install();
|
|
|
|
|
return LIB_STATUS_OK;
|
|
|
|
|
} catch (FileSystemException|RuntimeException $e) {
|
|
|
|
|
logger()->error('Failed to extract pre-built library [' . static::NAME . ']: ' . $e->getMessage());
|
|
|
|
|
return LIB_STATUS_INSTALL_FAILED;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
foreach ($this->getStaticLibs() as $name) {
|
|
|
|
|
if (!file_exists(BUILD_LIB_PATH . "/{$name}")) {
|
|
|
|
|
$this->tryInstall($install_file, true);
|
|
|
|
|
return LIB_STATUS_OK;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
foreach ($this->getHeaders() as $name) {
|
|
|
|
|
if (!file_exists(BUILD_INCLUDE_PATH . "/{$name}")) {
|
|
|
|
|
$this->tryInstall($install_file, true);
|
|
|
|
|
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->tryInstall($install_file, true);
|
|
|
|
|
return LIB_STATUS_OK;
|
|
|
|
|
}
|
|
|
|
|
return LIB_STATUS_ALREADY;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Try to build this library, before build, we check first.
|
|
|
|
|
*
|
|
|
|
|
* BUILD_STATUS_OK if build success
|
|
|
|
|
* BUILD_STATUS_ALREADY if already built
|
|
|
|
|
* BUILD_STATUS_FAILED if build failed
|
2023-03-18 17:32:21 +08:00
|
|
|
*
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
* @throws FileSystemException
|
2023-08-20 19:51:45 +08:00
|
|
|
* @throws WrongUsageException
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
public function tryBuild(bool $force_build = false): int
|
|
|
|
|
{
|
2023-10-14 14:05:33 +08:00
|
|
|
if (file_exists($this->source_dir . '/.spc.patched')) {
|
|
|
|
|
$this->patched = true;
|
|
|
|
|
}
|
2023-08-20 19:51:45 +08:00
|
|
|
// force means just build
|
2023-03-18 17:32:21 +08:00
|
|
|
if ($force_build) {
|
2025-03-08 14:29:44 +08:00
|
|
|
$type = Config::getLib(static::NAME, 'type', 'lib');
|
|
|
|
|
logger()->info('Building required ' . $type . ' [' . static::NAME . ']');
|
2024-07-01 10:29:31 +08:00
|
|
|
|
|
|
|
|
// extract first if not exists
|
|
|
|
|
if (!is_dir($this->source_dir)) {
|
|
|
|
|
$this->getBuilder()->emitPatchPoint('before-library[ ' . static::NAME . ']-extract');
|
|
|
|
|
SourceManager::initSource(libs: [static::NAME]);
|
|
|
|
|
$this->getBuilder()->emitPatchPoint('after-library[ ' . static::NAME . ']-extract');
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-14 14:05:33 +08:00
|
|
|
if (!$this->patched && $this->patchBeforeBuild()) {
|
|
|
|
|
file_put_contents($this->source_dir . '/.spc.patched', 'PATCHED!!!');
|
|
|
|
|
}
|
2024-01-03 15:57:05 +08:00
|
|
|
$this->getBuilder()->emitPatchPoint('before-library[ ' . static::NAME . ']-build');
|
2023-03-18 17:32:21 +08:00
|
|
|
$this->build();
|
2024-07-08 12:55:41 +08:00
|
|
|
$this->installLicense();
|
2024-01-03 15:57:05 +08:00
|
|
|
$this->getBuilder()->emitPatchPoint('after-library[ ' . static::NAME . ']-build');
|
2024-07-07 20:45:18 +08:00
|
|
|
return LIB_STATUS_OK;
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
|
|
|
|
|
2023-08-20 19:51:45 +08:00
|
|
|
// check if these libraries exist, if not, invoke compilation and return the result status
|
2023-03-18 17:32:21 +08:00
|
|
|
foreach ($this->getStaticLibs() as $name) {
|
|
|
|
|
if (!file_exists(BUILD_LIB_PATH . "/{$name}")) {
|
|
|
|
|
$this->tryBuild(true);
|
2024-07-07 20:45:18 +08:00
|
|
|
return LIB_STATUS_OK;
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-20 19:51:45 +08:00
|
|
|
// header files the same
|
2023-03-18 17:32:21 +08:00
|
|
|
foreach ($this->getHeaders() as $name) {
|
|
|
|
|
if (!file_exists(BUILD_INCLUDE_PATH . "/{$name}")) {
|
|
|
|
|
$this->tryBuild(true);
|
2024-07-07 20:45:18 +08:00
|
|
|
return LIB_STATUS_OK;
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-08 14:29:44 +08:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-10 02:04:08 +08:00
|
|
|
}
|
2023-08-20 19:51:45 +08:00
|
|
|
// if all the files exist at this point, skip the compilation process
|
2024-07-07 20:45:18 +08:00
|
|
|
return LIB_STATUS_ALREADY;
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Patch before build, overwrite this and return true to patch libs.
|
2023-07-28 23:45:39 +08:00
|
|
|
*/
|
|
|
|
|
public function patchBeforeBuild(): bool
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-16 13:01:11 +08:00
|
|
|
public function validate(): void
|
|
|
|
|
{
|
|
|
|
|
// do nothing, just throw wrong usage exception if not valid
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-30 22:37:01 +08:00
|
|
|
/**
|
|
|
|
|
* Get current lib version
|
|
|
|
|
*
|
|
|
|
|
* @return null|string Version string or null
|
|
|
|
|
*/
|
|
|
|
|
public function getLibVersion(): ?string
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-28 23:45:39 +08:00
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Get current builder object.
|
2023-03-18 17:32:21 +08:00
|
|
|
*/
|
|
|
|
|
abstract public function getBuilder(): BuilderBase;
|
|
|
|
|
|
2024-07-07 20:45:18 +08:00
|
|
|
public function beforePack(): void
|
|
|
|
|
{
|
|
|
|
|
// do something before pack, default do nothing. overwrite this method to do something (e.g. modify pkg-config file)
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Build this library.
|
2023-03-18 17:32:21 +08:00
|
|
|
*
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
*/
|
|
|
|
|
abstract protected function build();
|
|
|
|
|
|
2024-07-07 20:45:18 +08:00
|
|
|
protected function install(): void
|
|
|
|
|
{
|
|
|
|
|
// do something after extracting pre-built files, default do nothing. overwrite this method to do something
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-18 17:32:21 +08:00
|
|
|
/**
|
2023-08-20 19:51:45 +08:00
|
|
|
* Add lib dependency
|
2023-03-18 17:32:21 +08:00
|
|
|
*
|
|
|
|
|
* @throws RuntimeException
|
|
|
|
|
*/
|
|
|
|
|
protected function addLibraryDependency(string $name, bool $optional = false): void
|
|
|
|
|
{
|
|
|
|
|
$dep_lib = $this->getBuilder()->getLib($name);
|
2023-08-20 19:51:45 +08:00
|
|
|
if ($dep_lib) {
|
2023-03-18 17:32:21 +08:00
|
|
|
$this->dependencies[$name] = $dep_lib;
|
2023-08-20 19:51:45 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!$optional) {
|
|
|
|
|
throw new RuntimeException(static::NAME . " requires library {$name}");
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
2023-08-20 19:51:45 +08:00
|
|
|
logger()->debug('enabling ' . static::NAME . " without {$name}");
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|
2024-04-07 15:52:24 +08:00
|
|
|
|
|
|
|
|
protected function getSnakeCaseName(): string
|
|
|
|
|
{
|
|
|
|
|
return str_replace('-', '_', static::NAME);
|
|
|
|
|
}
|
2024-07-08 12:55:41 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Install license files in buildroot directory
|
|
|
|
|
*/
|
|
|
|
|
protected function installLicense(): void
|
|
|
|
|
{
|
|
|
|
|
FileSystem::createDir(BUILD_ROOT_PATH . '/source-licenses/' . $this->getName());
|
|
|
|
|
$source = Config::getLib($this->getName(), 'source');
|
|
|
|
|
$license_files = Config::getSource($source)['license'] ?? [];
|
|
|
|
|
if (is_assoc_array($license_files)) {
|
|
|
|
|
$license_files = [$license_files];
|
|
|
|
|
}
|
|
|
|
|
foreach ($license_files as $index => $license) {
|
|
|
|
|
if ($license['type'] === 'text') {
|
|
|
|
|
FileSystem::writeFile(BUILD_ROOT_PATH . '/source-licenses/' . $this->getName() . "/{$index}.txt", $license['text']);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ($license['type'] === 'file') {
|
|
|
|
|
copy($this->source_dir . '/' . $license['path'], BUILD_ROOT_PATH . '/source-licenses/' . $this->getName() . "/{$index}.txt");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-18 17:32:21 +08:00
|
|
|
}
|