From aa59c6385bf3a81bc1ab457c81dfb3d723d00956 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 30 Jun 2024 22:32:17 +0800 Subject: [PATCH 1/7] Disable getCpuCount log --- src/SPC/builder/macos/SystemUtil.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/builder/macos/SystemUtil.php b/src/SPC/builder/macos/SystemUtil.php index 22d7847e1..0c2d4560c 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'); } From b2d714e81a64434628d3a6d940da338188f7f5fd Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 30 Jun 2024 22:33:03 +0800 Subject: [PATCH 2/7] Use extra files instead of internal patch block for libyaml --- src/SPC/builder/unix/library/libyaml.php | 54 +++++++----------------- src/SPC/store/SourcePatcher.php | 13 ++++++ 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/SPC/builder/unix/library/libyaml.php b/src/SPC/builder/unix/library/libyaml.php index b1b545cf3..53962e9be 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/store/SourcePatcher.php b/src/SPC/store/SourcePatcher.php index dc114fe2b..0eb530c4c 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. * From 8f63147454c9cc18696d9b95114ce836e29b2047 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 30 Jun 2024 22:33:53 +0800 Subject: [PATCH 3/7] Prevent constructing builder without --arch options error --- src/SPC/builder/macos/MacOSBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index ddcfcb64f..a29e99546 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); From 7938304db3390582de1ce3e5360698c44777e011 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 30 Jun 2024 22:34:27 +0800 Subject: [PATCH 4/7] Add target dir to extract hook --- src/SPC/store/FileSystem.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SPC/store/FileSystem.php b/src/SPC/store/FileSystem.php index 6d192bce6..0d1aee721 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'); } } From 44bd83880bfcd00be7619f67f1cd00dbf0b9ff08 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 30 Jun 2024 22:35:22 +0800 Subject: [PATCH 5/7] `--ignore-cache-sources` now support empty values (force all download) --- src/SPC/command/DownloadCommand.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/SPC/command/DownloadCommand.php b/src/SPC/command/DownloadCommand.php index 5ce1a06a6..48fa5e7ae 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); From 2420064278323218c52477cc703371b4b102608d Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 30 Jun 2024 22:36:22 +0800 Subject: [PATCH 6/7] Add prefer-stable option for ghrel, ghtar, ghtagtar --- config/source.json | 16 ++++++++++++++++ src/SPC/store/Downloader.php | 17 +++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/config/source.json b/config/source.json index 5dadd9443..ba518e285 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/store/Downloader.php b/src/SPC/store/Downloader.php index 00d8881d9..312a459c7 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) { From 1f4fd35bf059c89a4b9411dd63e381bff6c8e0f5 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 30 Jun 2024 22:37:01 +0800 Subject: [PATCH 7/7] Add getLibVersion and getExtVersion method for extensions and libs --- config/pre-built.json | 5 +++ src/SPC/ConsoleApplication.php | 4 ++ src/SPC/builder/BuilderBase.php | 6 ++- src/SPC/builder/Extension.php | 10 +++++ src/SPC/builder/LibraryBase.php | 10 +++++ src/SPC/builder/extension/swoole.php | 12 ++++++ src/SPC/command/dev/ExtVerCommand.php | 51 +++++++++++++++++++++++ src/SPC/command/dev/LibVerCommand.php | 60 +++++++++++++++++++++++++++ 8 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 config/pre-built.json create mode 100644 src/SPC/command/dev/ExtVerCommand.php create mode 100644 src/SPC/command/dev/LibVerCommand.php diff --git a/config/pre-built.json b/config/pre-built.json new file mode 100644 index 000000000..8b5811e2a --- /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/src/SPC/ConsoleApplication.php b/src/SPC/ConsoleApplication.php index 1131ef129..f0a357fc5 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 4f4a72f8b..de5291658 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 562db3728..ff280a156 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 ef6ea79d6..fb0a45911 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 1e892ee52..e95969e32 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/command/dev/ExtVerCommand.php b/src/SPC/command/dev/ExtVerCommand.php new file mode 100644 index 000000000..0f08fa9a5 --- /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 000000000..717fd155f --- /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; + } +}