diff --git a/config/pre-built.json b/config/pre-built.json new file mode 100644 index 00000000..8b5811e2 --- /dev/null +++ b/config/pre-built.json @@ -0,0 +1,5 @@ +{ + "repo": "static-php/static-php-cli-hosted", + "match-pattern": "{name}-{arch}-{os}.tgz", + "pack-config": {} +} \ No newline at end of file diff --git a/config/source.json b/config/source.json index 5dadd944..ba518e28 100644 --- a/config/source.json +++ b/config/source.json @@ -47,6 +47,7 @@ "type": "ghrel", "repo": "curl/curl", "match": "curl.+\\.tar\\.xz", + "prefer-stable": true, "license": { "type": "file", "path": "COPYING" @@ -194,6 +195,7 @@ "type": "ghrel", "repo": "unicode-org/icu", "match": "icu4c.+-src\\.tgz", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" @@ -266,6 +268,7 @@ "type": "ghrel", "repo": "c-ares/c-ares", "match": "c-ares-.+\\.tar\\.gz", + "prefer-stable": true, "alt": { "type": "filelist", "url": "https://c-ares.org/download/", @@ -280,6 +283,7 @@ "type": "ghrel", "repo": "libevent/libevent", "match": "libevent.+\\.tar\\.gz", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" @@ -289,6 +293,7 @@ "type": "ghrel", "repo": "libffi/libffi", "match": "libffi.+\\.tar\\.gz", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" @@ -333,6 +338,7 @@ "type": "ghrel", "repo": "lz4/lz4", "match": "lz4-.+\\.tar\\.gz", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" @@ -369,6 +375,7 @@ "type": "ghrel", "repo": "jedisct1/libsodium", "match": "libsodium-\\d+(\\.\\d+)*\\.tar\\.gz", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" @@ -378,6 +385,7 @@ "type": "ghrel", "repo": "libssh2/libssh2", "match": "libssh2.+\\.tar\\.gz", + "prefer-stable": true, "license": { "type": "file", "path": "COPYING" @@ -444,6 +452,7 @@ "type": "ghrel", "repo": "yaml/libyaml", "match": "yaml-.+\\.tar\\.gz", + "prefer-stable": true, "license": { "type": "file", "path": "License" @@ -453,6 +462,7 @@ "type": "ghrel", "repo": "nih-at/libzip", "match": "libzip.+\\.tar\\.xz", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" @@ -483,6 +493,7 @@ "repo": "mongodb/mongo-php-driver", "path": "php-src/ext/mongodb", "match": "mongodb.+\\.tgz", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" @@ -501,6 +512,7 @@ "type": "ghrel", "repo": "nghttp2/nghttp2", "match": "nghttp2.+\\.tar\\.xz", + "prefer-stable": true, "license": { "type": "file", "path": "COPYING" @@ -510,6 +522,7 @@ "type": "ghrel", "repo": "kkos/oniguruma", "match": "onig-.+\\.tar\\.gz", + "prefer-stable": true, "license": { "type": "file", "path": "COPYING" @@ -649,6 +662,7 @@ "type": "ghtar", "path": "php-src/ext/swoole", "repo": "swoole/swoole-src", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" @@ -734,6 +748,7 @@ "type": "ghrel", "repo": "madler/zlib", "match": "zlib.+\\.tar\\.gz", + "prefer-stable": true, "license": { "type": "text", "text": "(C) 1995-2022 Jean-loup Gailly and Mark Adler\n\nThis software is provided 'as-is', without any express or implied\nwarranty. In no event will the authors be held liable for any damages\narising from the use of this software.\n\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must not\n claim that you wrote the original software. If you use this software\n in a product, an acknowledgment in the product documentation would be\n appreciated but is not required.\n2. Altered source versions must be plainly marked as such, and must not be\n misrepresented as being the original software.\n3. This notice may not be removed or altered from any source distribution.\n\nJean-loup Gailly Mark Adler\njloup@gzip.org madler@alumni.caltech.edu" @@ -743,6 +758,7 @@ "type": "ghrel", "repo": "facebook/zstd", "match": "zstd.+\\.tar\\.gz", + "prefer-stable": true, "license": { "type": "file", "path": "LICENSE" diff --git a/src/SPC/ConsoleApplication.php b/src/SPC/ConsoleApplication.php index 1131ef12..f0a357fc 100644 --- a/src/SPC/ConsoleApplication.php +++ b/src/SPC/ConsoleApplication.php @@ -8,7 +8,9 @@ use SPC\command\BuildLibsCommand; use SPC\command\DeleteDownloadCommand; use SPC\command\dev\AllExtCommand; +use SPC\command\dev\ExtVerCommand; use SPC\command\dev\GenerateExtDocCommand; +use SPC\command\dev\LibVerCommand; use SPC\command\dev\PhpVerCommand; use SPC\command\dev\SortConfigCommand; use SPC\command\DoctorCommand; @@ -48,6 +50,8 @@ public function __construct() // Dev commands new AllExtCommand(), new PhpVerCommand(), + new LibVerCommand(), + new ExtVerCommand(), new SortConfigCommand(), new GenerateExtDocCommand(), ] diff --git a/src/SPC/builder/BuilderBase.php b/src/SPC/builder/BuilderBase.php index 4f4a72f8..de529165 100644 --- a/src/SPC/builder/BuilderBase.php +++ b/src/SPC/builder/BuilderBase.php @@ -161,7 +161,7 @@ public function setLibsOnly(bool $status = true): void * @throws WrongUsageException * @internal */ - public function proveExts(array $extensions): void + public function proveExts(array $extensions, bool $skip_check_deps = false): void { CustomExt::loadCustomExt(); $this->emitPatchPoint('before-php-extract'); @@ -181,6 +181,10 @@ public function proveExts(array $extensions): void $this->addExt($ext); } + if ($skip_check_deps) { + return; + } + foreach ($this->exts as $ext) { $ext->checkDependency(); } diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index 562db372..ff280a15 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -228,6 +228,16 @@ public function validate(): void // do nothing, just throw wrong usage exception if not valid } + /** + * Get current extension version + * + * @return null|string Version string or null + */ + public function getExtVersion(): ?string + { + return null; + } + /** * @throws RuntimeException */ diff --git a/src/SPC/builder/LibraryBase.php b/src/SPC/builder/LibraryBase.php index ef6ea79d..fb0a4591 100644 --- a/src/SPC/builder/LibraryBase.php +++ b/src/SPC/builder/LibraryBase.php @@ -182,6 +182,16 @@ public function validate(): void // do nothing, just throw wrong usage exception if not valid } + /** + * Get current lib version + * + * @return null|string Version string or null + */ + public function getLibVersion(): ?string + { + return null; + } + /** * Get current builder object. */ diff --git a/src/SPC/builder/extension/swoole.php b/src/SPC/builder/extension/swoole.php index 1e892ee5..e95969e3 100644 --- a/src/SPC/builder/extension/swoole.php +++ b/src/SPC/builder/extension/swoole.php @@ -10,6 +10,18 @@ #[CustomExt('swoole')] class swoole extends Extension { + public function getExtVersion(): ?string + { + // Get version from source directory + $file = SOURCE_PATH . '/php-src/ext/swoole/include/swoole_version.h'; + // Match #define SWOOLE_VERSION "5.1.3" + $pattern = '/#define SWOOLE_VERSION "(.+)"/'; + if (preg_match($pattern, file_get_contents($file), $matches)) { + return $matches[1]; + } + return null; + } + public function getUnixConfigureArg(): string { // enable swoole diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index ddcfcb64..a29e9954 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -37,7 +37,7 @@ public function __construct(array $options = []) $this->arch_c_flags = getenv('SPC_DEFAULT_C_FLAGS'); $this->arch_cxx_flags = getenv('SPC_DEFAULT_CXX_FLAGS'); // cmake toolchain - $this->cmake_toolchain_file = SystemUtil::makeCmakeToolchainFile('Darwin', $this->getOption('arch'), $this->arch_c_flags); + $this->cmake_toolchain_file = SystemUtil::makeCmakeToolchainFile('Darwin', $this->getOption('arch', php_uname('m')), $this->arch_c_flags); // create pkgconfig and include dir (some libs cannot create them automatically) f_mkdir(BUILD_LIB_PATH . '/pkgconfig', recursive: true); diff --git a/src/SPC/builder/macos/SystemUtil.php b/src/SPC/builder/macos/SystemUtil.php index 22d7847e..0c2d4560 100644 --- a/src/SPC/builder/macos/SystemUtil.php +++ b/src/SPC/builder/macos/SystemUtil.php @@ -20,7 +20,7 @@ class SystemUtil */ public static function getCpuCount(): int { - [$ret, $output] = shell()->execWithResult('sysctl -n hw.ncpu'); + [$ret, $output] = shell()->execWithResult('sysctl -n hw.ncpu', false); if ($ret !== 0) { throw new RuntimeException('Failed to get cpu count'); } diff --git a/src/SPC/builder/unix/library/libyaml.php b/src/SPC/builder/unix/library/libyaml.php index b1b545cf..53962e9b 100644 --- a/src/SPC/builder/unix/library/libyaml.php +++ b/src/SPC/builder/unix/library/libyaml.php @@ -10,51 +10,27 @@ trait libyaml { + public function getLibVersion(): ?string + { + // Match version from CMakeLists.txt: + // Format: set (YAML_VERSION_MAJOR 0) + // set (YAML_VERSION_MINOR 2) + // set (YAML_VERSION_PATCH 5) + $content = FileSystem::readFile($this->source_dir . '/CMakeLists.txt'); + if (preg_match('/set \(YAML_VERSION_MAJOR (\d+)\)/', $content, $major) + && preg_match('/set \(YAML_VERSION_MINOR (\d+)\)/', $content, $minor) + && preg_match('/set \(YAML_VERSION_PATCH (\d+)\)/', $content, $patch)) { + return "{$major[1]}.{$minor[1]}.{$patch[1]}"; + } + return null; + } + /** * @throws RuntimeException * @throws FileSystemException */ protected function build(): void { - // prepare cmake/config.h.in - if (!is_file(SOURCE_PATH . '/libyaml/cmake/config.h.in')) { - f_mkdir(SOURCE_PATH . '/libyaml/cmake'); - file_put_contents( - SOURCE_PATH . '/libyaml/cmake/config.h.in', - <<<'EOF' -#define YAML_VERSION_MAJOR @YAML_VERSION_MAJOR@ -#define YAML_VERSION_MINOR @YAML_VERSION_MINOR@ -#define YAML_VERSION_PATCH @YAML_VERSION_PATCH@ -#define YAML_VERSION_STRING "@YAML_VERSION_STRING@" -EOF - ); - } - - // prepare yamlConfig.cmake.in - if (!is_file(SOURCE_PATH . '/libyaml/yamlConfig.cmake.in')) { - file_put_contents( - SOURCE_PATH . '/libyaml/yamlConfig.cmake.in', - <<<'EOF' -# Config file for the yaml library. -# -# It defines the following variables: -# yaml_LIBRARIES - libraries to link against - -@PACKAGE_INIT@ - -set_and_check(yaml_TARGETS "@PACKAGE_CONFIG_DIR_CONFIG@/yamlTargets.cmake") - -if(NOT yaml_TARGETS_IMPORTED) - set(yaml_TARGETS_IMPORTED 1) - include(${yaml_TARGETS}) -endif() - -set(yaml_LIBRARIES yaml) - -EOF - ); - } - [$lib, $include, $destdir] = SEPARATED_PATH; FileSystem::resetDir($this->source_dir . '/build'); diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index 5ce1a06a..48fa5e7a 100644 --- a/src/SPC/command/DownloadCommand.php +++ b/src/SPC/command/DownloadCommand.php @@ -38,7 +38,7 @@ public function configure(): void $this->addOption('for-extensions', 'e', InputOption::VALUE_REQUIRED, 'Fetch by extensions, e.g "openssl,mbstring"'); $this->addOption('for-libs', 'l', InputOption::VALUE_REQUIRED, 'Fetch by libraries, e.g "libcares,openssl,onig"'); $this->addOption('without-suggestions', null, null, 'Do not fetch suggested sources when using --for-extensions'); - $this->addOption('ignore-cache-sources', null, InputOption::VALUE_REQUIRED, 'Ignore some source caches, comma separated, e.g "php-src,curl,openssl"', ''); + $this->addOption('ignore-cache-sources', null, InputOption::VALUE_OPTIONAL, 'Ignore some source caches, comma separated, e.g "php-src,curl,openssl"', ''); $this->addOption('retry', 'R', InputOption::VALUE_REQUIRED, 'Set retry time when downloading failed (default: 0)', '0'); } @@ -147,8 +147,12 @@ public function handle(): int } $chosen_sources = array_map('trim', array_filter(explode(',', $this->getArgument('sources')))); - $force_list = array_map('trim', array_filter(explode(',', $this->getOption('ignore-cache-sources')))); - + $force_all = empty($this->getOption('ignore-cache-sources')); + if (!$force_all) { + $force_list = array_map('trim', array_filter(explode(',', $this->getOption('ignore-cache-sources')))); + } else { + $force_list = []; + } if ($this->getOption('all')) { logger()->notice('Downloading with --all option will take more times to download, we recommend you to download with --for-extensions option !'); } @@ -182,7 +186,7 @@ public function handle(): int Downloader::downloadSource($source, $new_config, true); } else { logger()->info("Fetching source {$source} [{$ni}/{$cnt}]"); - Downloader::downloadSource($source, Config::getSource($source), in_array($source, $force_list)); + Downloader::downloadSource($source, Config::getSource($source), $force_all || in_array($source, $force_list)); } } $time = round(microtime(true) - START_TIME, 3); diff --git a/src/SPC/command/dev/ExtVerCommand.php b/src/SPC/command/dev/ExtVerCommand.php new file mode 100644 index 00000000..0f08fa9a --- /dev/null +++ b/src/SPC/command/dev/ExtVerCommand.php @@ -0,0 +1,51 @@ +addArgument('extension', InputArgument::REQUIRED, 'The library name'); + } + + public function initialize(InputInterface $input, OutputInterface $output): void + { + $this->no_motd = true; + parent::initialize($input, $output); + } + + public function handle(): int + { + // Get lib object + $builder = BuilderProvider::makeBuilderByInput($this->input); + + $ext_conf = Config::getExt($this->getArgument('extension')); + $builder->proveExts([$this->getArgument('extension')], true); + + // Check whether lib is extracted + // if (!is_dir(SOURCE_PATH . '/' . $this->getArgument('library'))) { + // $this->output->writeln("Library {$this->getArgument('library')} is not extracted"); + // return static::FAILURE; + // } + + $version = $builder->getExt($this->getArgument('extension'))->getExtVersion(); + if ($version === null) { + $this->output->writeln("Failed to get version of extension {$this->getArgument('extension')}"); + return static::FAILURE; + } + $this->output->writeln("{$version}"); + return static::SUCCESS; + } +} diff --git a/src/SPC/command/dev/LibVerCommand.php b/src/SPC/command/dev/LibVerCommand.php new file mode 100644 index 00000000..717fd155 --- /dev/null +++ b/src/SPC/command/dev/LibVerCommand.php @@ -0,0 +1,60 @@ +addArgument('library', InputArgument::REQUIRED, 'The library name'); + } + + public function initialize(InputInterface $input, OutputInterface $output): void + { + $this->no_motd = true; + parent::initialize($input, $output); + } + + public function handle(): int + { + // Get lib object + $builder = BuilderProvider::makeBuilderByInput($this->input); + $builder->setLibsOnly(); + + // check lib name exist in lib.json + try { + Config::getLib($this->getArgument('library')); + } catch (WrongUsageException $e) { + $this->output->writeln("Library {$this->getArgument('library')} is not supported yet"); + return static::FAILURE; + } + + $builder->proveLibs([$this->getArgument('library')]); + + // Check whether lib is extracted + if (!is_dir(SOURCE_PATH . '/' . $this->getArgument('library'))) { + $this->output->writeln("Library {$this->getArgument('library')} is not extracted"); + return static::FAILURE; + } + + $version = $builder->getLib($this->getArgument('library'))->getLibVersion(); + if ($version === null) { + $this->output->writeln("Failed to get version of library {$this->getArgument('library')}"); + return static::FAILURE; + } + $this->output->writeln("{$version}"); + return static::SUCCESS; + } +} diff --git a/src/SPC/store/Downloader.php b/src/SPC/store/Downloader.php index 00d8881d..312a459c 100644 --- a/src/SPC/store/Downloader.php +++ b/src/SPC/store/Downloader.php @@ -16,7 +16,7 @@ class Downloader { /** - * Get latest version from BitBucket tag + * Get latest version from BitBucket tag (type = bitbuckettag) * * @param string $name source name * @param array $source source meta info: [repo] @@ -51,7 +51,7 @@ public static function getLatestBitbucketTag(string $name, array $source): array } /** - * Get latest version from GitHub tarball + * Get latest version from GitHub tarball (type = ghtar / ghtagtar) * * @param string $name source name * @param array $source source meta info: [repo] @@ -68,7 +68,16 @@ public static function getLatestGithubTarball(string $name, array $source, strin hooks: [[CurlHook::class, 'setupGithubToken']], retry: intval(getenv('SPC_RETRY_TIME') ? getenv('SPC_RETRY_TIME') : 0) ), true); - $url = $data[0]['tarball_url']; + + if (($source['prefer-stable'] ?? false) === true) { + $url = $data[0]['tarball_url']; + } else { + $id = 0; + while ($data[$id]['prerelease'] === true) { + ++$id; + } + $url = $data[$id]['tarball_url'] ?? null; + } if (!$url) { throw new DownloaderException("failed to find {$name} source"); } @@ -106,7 +115,7 @@ public static function getLatestGithubRelease(string $name, array $source): arra ), true); $url = null; foreach ($data as $release) { - if ($release['prerelease'] === true) { + if (($source['prefer-stable'] ?? false) === true && $release['prerelease'] === true) { continue; } foreach ($release['assets'] as $asset) { diff --git a/src/SPC/store/FileSystem.php b/src/SPC/store/FileSystem.php index 6d192bce..0d1aee72 100644 --- a/src/SPC/store/FileSystem.php +++ b/src/SPC/store/FileSystem.php @@ -195,7 +195,7 @@ public static function extractSource(string $name, string $filename, ?string $mo } try { self::extractArchive($filename, $target); - self::emitSourceExtractHook($name); + self::emitSourceExtractHook($name, $target); } catch (RuntimeException $e) { if (PHP_OS_FAMILY === 'Windows') { f_passthru('rmdir /s /q ' . $target); @@ -518,10 +518,10 @@ private static function replaceFile(string $filename, int $replace_type = REPLAC return file_put_contents($filename, $file); } - private static function emitSourceExtractHook(string $name): void + private static function emitSourceExtractHook(string $name, string $target): void { foreach ((self::$_extract_hook[$name] ?? []) as $hook) { - if ($hook($name) === true) { + if ($hook($name, $target) === true) { logger()->info('Patched source [' . $name . '] after extracted'); } } diff --git a/src/SPC/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php index dc114fe2..0eb530c4 100644 --- a/src/SPC/store/SourcePatcher.php +++ b/src/SPC/store/SourcePatcher.php @@ -24,6 +24,7 @@ public static function init(): void FileSystem::addSourceExtractHook('sqlsrv', [SourcePatcher::class, 'patchSQLSRVWin32']); FileSystem::addSourceExtractHook('pdo_sqlsrv', [SourcePatcher::class, 'patchSQLSRVWin32']); FileSystem::addSourceExtractHook('yaml', [SourcePatcher::class, 'patchYamlWin32']); + FileSystem::addSourceExtractHook('libyaml', [SourcePatcher::class, 'patchLibYaml']); } /** @@ -348,6 +349,18 @@ public static function patchYamlWin32(): bool return true; } + public static function patchLibYaml(string $name, string $target): bool + { + if (!file_exists("{$target}\\cmake\\config.h.in")) { + FileSystem::createDir("{$target}\\cmake"); + copy(ROOT_DIR . '\src\globals\extra\libyaml_config.h.in', "{$target}\\cmake\\config.h.in"); + } + if (!file_exists("{$target}\\YamlConfig.cmake.in")) { + copy(ROOT_DIR . '\src\globals\extra\libyaml_YamlConfig.cmake.in', "{$target}\\YamlConfig.cmake.in"); + } + return true; + } + /** * Patch cli SAPI Makefile for Windows. *