Expand library version checking capabilities (closes #9093)

main
Jordi Boggiano 4 years ago
parent 657ae5519e
commit 7e1ef19a5a
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC

@ -0,0 +1,59 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Platform;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Process\ExecutableFinder;
class HhvmDetector
{
private static $hhvmVersion;
private $executableFinder;
private $processExecutor;
public function __construct(ExecutableFinder $executableFinder = null, ProcessExecutor $processExecutor = null) {
$this->executableFinder = $executableFinder;
$this->processExecutor = $processExecutor;
}
public function reset()
{
self::$hhvmVersion = null;
}
public function getVersion() {
if (null !== self::$hhvmVersion) {
return self::$hhvmVersion ?: null;
}
self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null;
if (self::$hhvmVersion === null && !Platform::isWindows()) {
self::$hhvmVersion = false;
$this->executableFinder = $this->executableFinder ?: new ExecutableFinder();
$hhvmPath = $this->executableFinder->find('hhvm');
if ($hhvmPath !== null) {
$this->processExecutor = $this->processExecutor ?: new ProcessExecutor();
$exitCode = $this->processExecutor->execute(
ProcessExecutor::escape($hhvmPath).
' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null',
self::$hhvmVersion
);
if ($exitCode !== 0) {
self::$hhvmVersion = false;
}
}
}
return self::$hhvmVersion;
}
}

@ -0,0 +1,94 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Platform;
class Runtime
{
/**
* @param string $constant
* @param class-string $class
* @return bool
*/
public function hasConstant($constant, $class = null) {
return defined(ltrim($class.'::'.$constant, ':'));
}
/**
* @param bool $constant
* @param class-string $class
* @return mixed
*/
public function getConstant($constant, $class = null) {
return constant(ltrim($class.'::'.$constant, ':'));
}
/**
* @param callable $callable
* @param array $arguments
* @return mixed
*/
public function invoke($callable, array $arguments = array()) {
return call_user_func_array($callable, $arguments);
}
/**
* @param class-string $class
* @return bool
*/
public function hasClass($class) {
return class_exists($class, false);
}
/**
* @param class-string $class
* @param array $arguments
* @return object
*/
public function construct($class, array $arguments = array()) {
if (empty($arguments)) {
return new $class;
}
$refl = new \ReflectionClass($class);
return $refl->newInstanceArgs($arguments);
}
/** @return string[] */
public function getExtensions()
{
return get_loaded_extensions();
}
/**
* @param string $extension
* @return string
*/
public function getExtensionVersion($extension)
{
return phpversion($extension);
}
/**
* @param string $extension
* @return string
*/
public function getExtensionInfo($extension)
{
$reflector = new \ReflectionExtension($extension);
ob_start();
$reflector->info();
return ob_get_clean();
}
}

@ -14,14 +14,16 @@ namespace Composer\Repository;
use Composer\Composer;
use Composer\Package\CompletePackage;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Platform\HhvmDetector;
use Composer\Platform\Runtime;
use Composer\Plugin\PluginInterface;
use Composer\Util\ProcessExecutor;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\Silencer;
use Composer\Util\Platform;
use Composer\Util\Version;
use Composer\XdebugHandler\XdebugHandler;
use Symfony\Component\Process\ExecutableFinder;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
@ -30,7 +32,9 @@ class PlatformRepository extends ArrayRepository
{
const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer-(?:plugin|runtime)-api)$}iD';
private static $hhvmVersion;
/**
* @var VersionParser
*/
private $versionParser;
/**
@ -42,11 +46,13 @@ class PlatformRepository extends ArrayRepository
*/
private $overrides = array();
private $process;
private $runtime;
private $hhvmDetector;
public function __construct(array $packages = array(), array $overrides = array(), ProcessExecutor $process = null)
public function __construct(array $packages = array(), array $overrides = array(), Runtime $runtime = null, HhvmDetector $hhvmDetector = null)
{
$this->process = $process;
$this->runtime = $runtime ?: new Runtime();
$this->hhvmDetector = $hhvmDetector ?: new HhvmDetector();
foreach ($overrides as $name => $version) {
$this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version);
}
@ -88,10 +94,10 @@ class PlatformRepository extends ArrayRepository
$this->addPackage($composerRuntimeApi);
try {
$prettyVersion = PHP_VERSION;
$prettyVersion = $this->runtime->getConstant('PHP_VERSION');
$version = $this->versionParser->normalize($prettyVersion);
} catch (\UnexpectedValueException $e) {
$prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', PHP_VERSION);
$prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', $this->runtime->getConstant('PHP_VERSION'));
$version = $this->versionParser->normalize($prettyVersion);
}
@ -99,19 +105,19 @@ class PlatformRepository extends ArrayRepository
$php->setDescription('The PHP interpreter');
$this->addPackage($php);
if (PHP_DEBUG) {
if ($this->runtime->getConstant('PHP_DEBUG')) {
$phpdebug = new CompletePackage('php-debug', $version, $prettyVersion);
$phpdebug->setDescription('The PHP interpreter, with debugging symbols');
$this->addPackage($phpdebug);
}
if (defined('PHP_ZTS') && PHP_ZTS) {
if ($this->runtime->hasConstant('PHP_ZTS') && $this->runtime->getConstant('PHP_ZTS')) {
$phpzts = new CompletePackage('php-zts', $version, $prettyVersion);
$phpzts->setDescription('The PHP interpreter, with Zend Thread Safety');
$this->addPackage($phpzts);
}
if (PHP_INT_SIZE === 8) {
if ($this->runtime->getConstant('PHP_INT_SIZE') === 8) {
$php64 = new CompletePackage('php-64bit', $version, $prettyVersion);
$php64->setDescription('The PHP interpreter, 64bit');
$this->addPackage($php64);
@ -119,13 +125,13 @@ class PlatformRepository extends ArrayRepository
// The AF_INET6 constant is only defined if ext-sockets is available but
// IPv6 support might still be available.
if (defined('AF_INET6') || Silencer::call('inet_pton', '::') !== false) {
if ($this->runtime->hasConstant('AF_INET6') || Silencer::call(array($this->runtime, 'invoke'), array('inet_pton', '::')) !== false) {
$phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion);
$phpIpv6->setDescription('The PHP interpreter, with IPv6 support');
$this->addPackage($phpIpv6);
}
$loadedExtensions = get_loaded_extensions();
$loadedExtensions = $this->runtime->getExtensions();
// Extensions scanning
foreach ($loadedExtensions as $name) {
@ -133,9 +139,7 @@ class PlatformRepository extends ArrayRepository
continue;
}
$reflExt = new \ReflectionExtension($name);
$prettyVersion = $reflExt->getVersion();
$this->addExtension($name, $prettyVersion);
$this->addExtension($name, $this->runtime->getExtensionVersion($name));
}
// Check for Xdebug in a restarted process
@ -147,112 +151,317 @@ class PlatformRepository extends ArrayRepository
// Doing it this way to know that functions or constants exist before
// relying on them.
foreach ($loadedExtensions as $name) {
$prettyVersion = null;
$description = 'The '.$name.' PHP library';
switch ($name) {
case 'amqp':
$info = $this->runtime->getExtensionInfo($name);
// librabbitmq version => 0.9.0
if (preg_match('/^librabbitmq version => (?<version>.+)$/m', $info, $librabbitmqMatches)) {
$this->addLibrary($name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version');
}
// AMQP protocol version => 0-9-1
if (preg_match('/^AMQP protocol version => (?<version>.+)$/m', $info, $protocolMatches)) {
$this->addLibrary($name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version');
}
break;
case 'bz2':
$info = $this->runtime->getExtensionInfo($name);
// BZip2 Version => 1.0.6, 6-Sept-2010
if (preg_match('/^BZip2 Version => (?<version>.*),/m', $info, $matches)) {
$this->addLibrary($name, $matches['version']);
}
break;
case 'curl':
$curlVersion = curl_version();
$prettyVersion = $curlVersion['version'];
$curlVersion = $this->runtime->invoke('curl_version');
$this->addLibrary($name, $curlVersion['version']);
$info = $this->runtime->getExtensionInfo($name);
// SSL Version => OpenSSL/1.0.1t
if (preg_match('{^SSL Version => (?<library>[^/]+)/(?<version>.+)$}m', $info, $sslMatches)) {
$library = strtolower($sslMatches['library']);
if ($library === 'openssl') {
$parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips);
$this->addLibrary($name.'-openssl'.($isFips ? '-fips': ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', array(), $isFips ? array('curl-openssl'): array());
} else {
$this->addLibrary($name.'-'.$library, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', array('curl-openssl'));
}
}
// libSSH Version => libssh2/1.4.3
if (preg_match('{^libSSH Version => (?<library>[^/]+)/(?<version>.+?)(?:/.*)?$}m', $info, $sshMatches)) {
$this->addLibrary($name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version');
}
// ZLib Version => 1.2.8
if (preg_match('{^ZLib Version => (?<version>.+)$}m', $info, $zlibMatches)) {
$this->addLibrary($name.'-zlib', $zlibMatches['version'], 'curl zlib version');
}
break;
case 'date':
$info = $this->runtime->getExtensionInfo($name);
// timelib version => 2018.03
if (preg_match('/^timelib version => (?<version>.+)$/m', $info, $timelibMatches)) {
$this->addLibrary($name.'-timelib', $timelibMatches['version'], 'date timelib version');
}
// Timezone Database => internal
if (preg_match('/^Timezone Database => (?<source>internal|external)$/m', $info, $zoneinfoSourceMatches)) {
$external = $zoneinfoSourceMatches['source'] === 'external';
if (preg_match('/^"Olson" Timezone Database Version => (?<version>.+?)(\.system)?$/m', $info, $zoneinfoMatches)) {
// If the timezonedb is provided by ext/timezonedb, register that version as a replacement
if ($external && in_array('timezonedb', $loadedExtensions, true)) {
$this->addLibrary('timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', array($name.'-zoneinfo'));
} else {
$this->addLibrary($name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date');
}
}
}
break;
case 'fileinfo':
$info = $this->runtime->getExtensionInfo($name);
// libmagic => 537
if (preg_match('/^^libmagic => (?<version>.+)$/m', $info, $magicMatches)) {
$this->addLibrary($name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version');
}
break;
case 'gd':
$this->addLibrary($name, $this->runtime->getConstant('GD_VERSION'));
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^libJPEG Version => (?<version>.+?)(?: compatible)?$/m', $info, $libjpegMatches)) {
$this->addLibrary($name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd');
}
if (preg_match('/^libPNG Version => (?<version>.+)$/m', $info, $libpngMatches)) {
$this->addLibrary($name.'-libpng', $libpngMatches['version'], 'libpng version for gd');
}
if (preg_match('/^FreeType Version => (?<version>.+)$/m', $info, $freetypeMatches)) {
$this->addLibrary($name.'-freetype', $freetypeMatches['version'], 'freetype version for gd');
}
if (preg_match('/^libXpm Version => (?<versionId>\d+)$/m', $info, $libxpmMatches)) {
$this->addLibrary($name.'-libxpm', Version::convertLibxpmVersionId($libxpmMatches['versionId']), 'libxpm version for gd');
}
break;
case 'gmp':
$this->addLibrary($name, $this->runtime->getConstant('GMP_VERSION'));
break;
case 'iconv':
$prettyVersion = ICONV_VERSION;
$this->addLibrary($name, $this->runtime->getConstant('ICONV_VERSION'));
break;
case 'intl':
$name = 'ICU';
if (defined('INTL_ICU_VERSION')) {
$prettyVersion = INTL_ICU_VERSION;
} else {
$reflector = new \ReflectionExtension('intl');
$info = $this->runtime->getExtensionInfo($name);
$description = 'The ICU unicode and globalization support library';
// Truthy check is for testing only so we can make the condition fail
if ($this->runtime->hasConstant('INTL_ICU_VERSION')) {
$this->addLibrary('icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description);
} elseif (preg_match('/^ICU version => (?<version>.+)$/m', $info, $matches)) {
$this->addLibrary('icu', $matches['version'], $description);
}
ob_start();
$reflector->info();
$output = ob_get_clean();
// ICU TZData version => 2019c
if (preg_match('/^ICU TZData version => (?<version>.*)$/m', $info, $zoneinfoMatches)) {
$this->addLibrary('icu-zoneinfo', Version::parseZoneinfoVersion($zoneinfoMatches['version']), 'zoneinfo ("Olson") database for icu');
}
preg_match('/^ICU version => (.*)$/m', $output, $matches);
$prettyVersion = $matches[1];
# Add a separate version for the CLDR library version
if ($this->runtime->hasClass('ResourceBundle')) {
$cldrVersion = $this->runtime->invoke(array('ResourceBundle', 'create'), array('root', 'ICUDATA', false))->get('Version');
$this->addLibrary('icu-cldr', $cldrVersion, 'ICU CLDR project version');
}
if ($this->runtime->hasClass('IntlChar')) {
$this->addLibrary('icu-unicode', implode('.', array_slice($this->runtime->invoke(array('IntlChar', 'getUnicodeVersion')), 0, 3)), 'ICU unicode version');
}
break;
case 'imagick':
$imagick = new \Imagick();
$imageMagickVersion = $imagick->getVersion();
$imageMagickVersion = $this->runtime->construct('Imagick')->getVersion();
// 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org
// 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org
preg_match('/^ImageMagick ([\d.]+)(?:-(\d+))?/', $imageMagickVersion['versionString'], $matches);
if (isset($matches[2])) {
$prettyVersion = "{$matches[1]}.{$matches[2]}";
} else {
$prettyVersion = $matches[1];
preg_match('/^ImageMagick (?<version>[\d.]+)(?:-(?<patch>\d+))?/', $imageMagickVersion['versionString'], $matches);
$version = $matches['version'];
if (isset($matches['patch'])) {
$version .= '.'.$matches['patch'];
}
$this->addLibrary($name.'-imagemagick', $version, null, array('imagick'));
break;
case 'ldap':
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^Vendor Version => (?<versionId>\d+)$/m', $info, $matches) && preg_match('/^Vendor Name => (?<vendor>.+)$/m', $info, $vendorMatches)) {
$this->addLibrary($name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId($matches['versionId']), $vendorMatches['vendor'].' version of ldap');
}
break;
case 'libxml':
$prettyVersion = LIBXML_DOTTED_VERSION;
// ext/dom, ext/simplexml, ext/xmlreader and ext/xmlwriter use the same libxml as the ext/libxml
$libxmlProvides = array_map(function($extension) {
return $extension . '-libxml';
}, array_intersect($loadedExtensions, array('dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter')));
$this->addLibrary($name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', array(), $libxmlProvides);
break;
case 'openssl':
$prettyVersion = preg_replace_callback('{^(?:OpenSSL|LibreSSL)?\s*([0-9.]+)([a-z]*).*}i', function ($match) {
if (empty($match[2])) {
return $match[1];
}
case 'mbstring':
$info = $this->runtime->getExtensionInfo($name);
// OpenSSL versions add another letter when they reach Z.
// e.g. OpenSSL 0.9.8zh 3 Dec 2015
// libmbfl version => 1.3.2
if (preg_match('/^libmbfl version => (?<version>.+)$/m', $info, $libmbflMatches)) {
$this->addLibrary($name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version');
}
if (!preg_match('{^z*[a-z]$}', $match[2])) {
// 0.9.8abc is garbage
return 0;
}
if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) {
$this->addLibrary($name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version');
// Multibyte regex (oniguruma) version => 5.9.5
// oniguruma version => 6.9.0
} elseif (preg_match('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?<version>.+)$/m', $info, $onigurumaMatches)) {
$this->addLibrary($name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version');
}
$len = strlen($match[2]);
$patchVersion = ($len - 1) * 26; // All Z
$patchVersion += ord($match[2][$len - 1]) - 96;
break;
return $match[1].'.'.$patchVersion;
}, OPENSSL_VERSION_TEXT);
case 'memcached':
$info = $this->runtime->getExtensionInfo($name);
$description = OPENSSL_VERSION_TEXT;
// libmemcached version => 1.0.18
if (preg_match('/^libmemcached version => (?<version>.+)$/m', $info, $matches)) {
$this->addLibrary($name.'-libmemcached', $matches['version'], 'libmemcached version');
}
break;
case 'openssl':
// OpenSSL 1.1.1g 21 Apr 2020
if (preg_match('{^(?:OpenSSL|LibreSSL)?\s*(?<version>\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) {
$parsedVersion = Version::parseOpenssl($matches['version'], $isFips);
$this->addLibrary($name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), array(), $isFips ? array($name) : array());
}
break;
case 'pcre':
$prettyVersion = preg_replace('{^(\S+).*}', '$1', PCRE_VERSION);
$this->addLibrary($name, preg_replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION')));
$info = $this->runtime->getExtensionInfo($name);
// PCRE Unicode Version => 12.1.0
if (preg_match('/^PCRE Unicode Version => (?<version>.+)$/m', $info, $pcreUnicodeMatches)) {
$this->addLibrary($name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support');
}
break;
case 'mysqlnd':
case 'pdo_mysql':
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^(?:Client API version|Version) => mysqlnd (?<version>.+?) /mi', $info, $matches)) {
$this->addLibrary($name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name);
}
break;
case 'uuid':
$prettyVersion = phpversion('uuid');
case 'mongodb':
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^libmongoc bundled version => (?<version>.+)$/m', $info, $libmongocMatches)) {
$this->addLibrary($name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb');
}
if (preg_match('/^libbson bundled version => (?<version>.+)$/m', $info, $libbsonMatches)) {
$this->addLibrary($name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb');
}
break;
case 'pgsql':
case 'pdo_pgsql':
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^PostgreSQL\(libpq\) Version => (?<version>.*)$/m', $info, $matches)) {
$this->addLibrary($name.'-libpq', $matches['version'], 'libpq for '.$name);
}
break;
case 'libsodium':
case 'sodium':
$this->addLibrary('libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION'));
break;
case 'sqlite3':
case 'pdo_sqlite':
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^SQLite Library => (?<version>.+)$/m', $info, $matches)) {
$this->addLibrary($name.'-sqlite', $matches['version']);
}
break;
case 'ssh2':
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^libssh2 version => (?<version>.+)$/m', $info, $matches)) {
$this->addLibrary($name.'-libssh2', $matches['version']);
}
break;
case 'xsl':
$prettyVersion = LIBXSLT_DOTTED_VERSION;
$this->addLibrary('libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, array('xsl'));
$info = $this->runtime->getExtensionInfo('xsl');
if (preg_match('/^libxslt compiled against libxml Version => (?<version>.+)$/m', $info, $matches)) {
$this->addLibrary('libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against');
}
break;
case 'yaml':
$info = $this->runtime->getExtensionInfo('yaml');
if (preg_match('/^LibYAML Version => (?<version>.+)$/m', $info, $matches)) {
$this->addLibrary($name.'-libyaml', $matches['version'], 'libyaml version of yaml');
}
break;
case 'zip':
if (defined('ZipArchive::LIBZIP_VERSION')) {
$prettyVersion = \ZipArchive::LIBZIP_VERSION;
} else {
continue 2;
if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) {
$this->addLibrary($name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION','ZipArchive'), null, array('zip'));
}
break;
default:
// None handled extensions have no special cases, skip
continue 2;
}
case 'zlib':
if ($this->runtime->hasConstant('ZLIB_VERSION')) {
$this->addLibrary($name, $this->runtime->getConstant('ZLIB_VERSION'));
try {
$version = $this->versionParser->normalize($prettyVersion);
} catch (\UnexpectedValueException $e) {
continue;
}
// Linked Version => 1.2.8
} elseif (preg_match('/^Linked Version => (?<version>.+)$/m', $this->runtime->getExtensionInfo($name), $matches)) {
$this->addLibrary($name, $matches['version']);
}
break;
$lib = new CompletePackage('lib-'.$name, $version, $prettyVersion);
$lib->setDescription($description);
$this->addPackage($lib);
default:
break;
}
}
if ($hhvmVersion = self::getHHVMVersion($this->process)) {
$hhvmVersion = $this->hhvmDetector->getVersion();
if ($hhvmVersion) {
try {
$prettyVersion = $hhvmVersion;
$version = $this->versionParser->normalize($prettyVersion);
@ -337,6 +546,11 @@ class PlatformRepository extends ArrayRepository
$packageName = $this->buildPackageName($name);
$ext = new CompletePackage($packageName, $version, $prettyVersion);
$ext->setDescription('The '.$name.' PHP extension'.$extraDescription);
if ($name === 'uuid') {
$ext->setReplaces(array(new Link('ext-uuid', 'lib-uuid', new Constraint('=', $version))));
}
$this->addPackage($ext);
}
@ -349,28 +563,34 @@ class PlatformRepository extends ArrayRepository
return 'ext-' . str_replace(' ', '-', $name);
}
private static function getHHVMVersion(ProcessExecutor $process = null)
/**
* @param string $name
* @param string $prettyVersion
* @param string|null $description
* @param string[] $replaces
* @param string[] $provides
*/
private function addLibrary($name, $prettyVersion, $description = null, array $replaces = array(), array $provides = array())
{
if (null !== self::$hhvmVersion) {
return self::$hhvmVersion ?: null;
try {
$version = $this->versionParser->normalize($prettyVersion);
} catch (\UnexpectedValueException $e) {
return;
}
self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null;
if (self::$hhvmVersion === null && !Platform::isWindows()) {
self::$hhvmVersion = false;
$finder = new ExecutableFinder();
$hhvmPath = $finder->find('hhvm');
if ($hhvmPath !== null) {
$process = $process ?: new ProcessExecutor();
$exitCode = $process->execute(
ProcessExecutor::escape($hhvmPath).
' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null',
self::$hhvmVersion
);
if ($exitCode !== 0) {
self::$hhvmVersion = false;
}
}
if ($description === null) {
$description = 'The '.$name.' library';
}
$lib = new CompletePackage('lib-'.$name, $version, $prettyVersion);
$lib->setDescription($description);
$links = function ($alias) use ($name, $version) {
return new Link('lib-'.$name, 'lib-'.$alias, new Constraint('=', $version));
};
$lib->setReplaces(array_map($links, $replaces));
$lib->setProvides(array_map($links, $provides));
$this->addPackage($lib);
}
}

@ -0,0 +1,104 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Util;
/**
* @author Lars Strojny <lars@strojny.net>
*/
class Version
{
/**
* @param string $opensslVersion
* @param bool $isFips
* @return string|null
*/
public static function parseOpenssl($opensslVersion, &$isFips)
{
$isFips = false;
if (!preg_match('/^(?<version>[0-9.]+)(?<patch>[a-z]{0,2})?(?<suffix>(?:-?(?:dev|pre|alpha|beta|rc|fips)[\d]*)*)?$/', $opensslVersion, $matches)) {
return null;
}
$isFips = strpos($matches['suffix'], 'fips') !== false;
$suffix = strtr('-'.ltrim($matches['suffix'], '-'), array('-fips' => '', '-pre' => '-alpha'));
$patch = self::convertAlphaVersionToIntVersion($matches['patch']);
return rtrim($matches['version'].'.'.$patch.$suffix, '-');
}
/**
* @param string $libjpegVersion
* @return string|null
*/
public static function parseLibjpeg($libjpegVersion)
{
if (!preg_match('/^(?<major>\d+)(?<minor>[a-z]*)$/', $libjpegVersion, $matches)) {
return null;
}
return $matches['major'].'.'.self::convertAlphaVersionToIntVersion($matches['minor']);
}
/**
* @param string $zoneinfoVersion
* @return string|null
*/
public static function parseZoneinfoVersion($zoneinfoVersion)
{
if (!preg_match('/^(?<year>\d{4})(?<revision>[a-z]*)$/', $zoneinfoVersion, $matches)) {
return null;
}
return $matches['year'].'.'.self::convertAlphaVersionToIntVersion($matches['revision']);
}
/**
* "" => 0, "a" => 1, "zg" => 33
*
* @param string $alpha
* @return int
*/
private static function convertAlphaVersionToIntVersion($alpha)
{
return strlen($alpha) * (-ord('a')+1) + array_sum(array_map('ord', str_split($alpha)));
}
/**
* @param int $versionId
* @return string
*/
public static function convertLibxpmVersionId($versionId)
{
return self::convertVersionId($versionId, 100);
}
/**
* @param int $versionId
* @return string
*/
public static function convertOpenldapVersionId($versionId)
{
return self::convertVersionId($versionId, 100);
}
private static function convertVersionId($versionId, $base)
{
return sprintf(
'%d.%d.%d',
$versionId / ($base * $base),
($versionId / $base) % $base,
$versionId % $base
);
}
}

@ -0,0 +1,97 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Test\Platform;
use Composer\Platform\HhvmDetector;
use Composer\Test\TestCase;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Process\ExecutableFinder;
class HhvmDetectorTest extends TestCase
{
private $hhvmDetector;
protected function setUp()
{
$this->hhvmDetector = new HhvmDetector();
$this->hhvmDetector->reset();
}
public function testHHVMVersionWhenExecutingInHHVM() {
if (!defined('HHVM_VERSION_ID')) {
self::markTestSkipped('Not running with HHVM');
return;
}
$version = $this->hhvmDetector->getVersion();
self::assertSame(self::versionIdToVersion(), $version);
}
public function testHHVMVersionWhenExecutingInPHP() {
if (defined('HHVM_VERSION_ID')) {
self::markTestSkipped('Running with HHVM');
return;
}
if (PHP_VERSION_ID < 50400) {
self::markTestSkipped('Test only works on PHP 5.4+');
return;
}
if (Platform::isWindows()) {
self::markTestSkipped('Test does not run on Windows');
return;
}
$finder = new ExecutableFinder();
$hhvm = $finder->find('hhvm');
if ($hhvm === null) {
self::markTestSkipped('HHVM is not installed');
}
$detectedVersion = $this->hhvmDetector->getVersion();
self::assertNotNull($detectedVersion, 'Failed to detect HHVM version');
$process = new ProcessExecutor();
$exitCode = $process->execute(
ProcessExecutor::escape($hhvm).
' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null',
$version
);
self::assertSame(0, $exitCode);
self::assertSame(self::getVersionParser()->normalize($version), self::getVersionParser()->normalize($detectedVersion));
}
/** @runInSeparateProcess */
public function testHHVMVersionWhenRunningInHHVMWithMockedConstant()
{
if (!defined('HHVM_VERSION_ID')) {
define('HHVM_VERSION', '2.2.1');
define('HHVM_VERSION_ID', 20201);
}
$version = $this->hhvmDetector->getVersion();
self::assertSame(self::getVersionParser()->normalize(self::versionIdToVersion()), self::getVersionParser()->normalize($version));
}
private static function versionIdToVersion()
{
if (!defined('HHVM_VERSION_ID')) {
return null;
}
return sprintf(
'%d.%d.%d',
HHVM_VERSION_ID / 10000,
(HHVM_VERSION_ID / 100) % 100,
HHVM_VERSION_ID % 100
);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,131 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Test\Util;
use Composer\Util\Version;
use Composer\Test\TestCase;
/**
* @author Lars Strojny <lars@strojny.net>
*/
class VersionTest extends TestCase
{
/**
* Create normalized test data set
*
* 1) Clone OpenSSL repository
* 2) git log --pretty=%h --all -- crypto/opensslv.h include/openssl/opensslv.h | while read hash ; do (git show $hash:crypto/opensslv.h; git show $hash:include/openssl/opensslv.h) | grep "define OPENSSL_VERSION_TEXT" ; done > versions.txt
* 3) cat versions.txt | awk -F "OpenSSL " '{print $2}' | awk -F " " '{print $1}' | sed -e "s:\([0-9]*\.[0-9]*\.[0-9]*\):1.2.3:g" -e "s:1\.2\.3[a-z]\(-.*\)\{0,1\}$:1.2.3a\1:g" -e "s:1\.2\.3[a-z]\{2\}\(-.*\)\{0,1\}$:1.2.3zh\1:g" -e "s:beta[0-9]:beta3:g" -e "s:pre[0-9]*:pre2:g" | sort | uniq
*/
public static function getOpenSslVersions()
{
return array(
// Generated
array('1.2.3', '1.2.3.0'),
array('1.2.3-beta3', '1.2.3.0-beta3'),
array('1.2.3-beta3-dev', '1.2.3.0-beta3-dev'),
array('1.2.3-beta3-fips', '1.2.3.0-beta3', true),
array('1.2.3-beta3-fips-dev', '1.2.3.0-beta3-dev', true),
array('1.2.3-dev', '1.2.3.0-dev'),
array('1.2.3-fips', '1.2.3.0', true),
array('1.2.3-fips-beta3', '1.2.3.0-beta3', true),
array('1.2.3-fips-beta3-dev', '1.2.3.0-beta3-dev', true),
array('1.2.3-fips-dev', '1.2.3.0-dev', true),
array('1.2.3-pre2', '1.2.3.0-alpha2'),
array('1.2.3-pre2-dev', '1.2.3.0-alpha2-dev'),
array('1.2.3-pre2-fips', '1.2.3.0-alpha2', true),
array('1.2.3-pre2-fips-dev', '1.2.3.0-alpha2-dev', true),
array('1.2.3a', '1.2.3.1'),
array('1.2.3a-beta3','1.2.3.1-beta3'),
array('1.2.3a-beta3-dev', '1.2.3.1-beta3-dev'),
array('1.2.3a-dev', '1.2.3.1-dev'),
array('1.2.3a-dev-fips', '1.2.3.1-dev', true),
array('1.2.3a-fips', '1.2.3.1', true),
array('1.2.3a-fips-beta3', '1.2.3.1-beta3', true),
array('1.2.3a-fips-dev', '1.2.3.1-dev', true),
array('1.2.3beta3', '1.2.3.0-beta3'),
array('1.2.3beta3-dev', '1.2.3.0-beta3-dev'),
array('1.2.3zh', '1.2.3.34'),
array('1.2.3zh-dev', '1.2.3.34-dev'),
array('1.2.3zh-fips', '1.2.3.34',true),
array('1.2.3zh-fips-dev', '1.2.3.34-dev', true),
// Additional cases
array('1.2.3zh-fips-rc3', '1.2.3.34-rc3', true, '1.2.3.34-RC3'),
array('1.2.3zh-alpha10-fips', '1.2.3.34-alpha10', true),
// Check that alphabetical patch levels overflow correctly
array('1.2.3', '1.2.3.0'),
array('1.2.3a', '1.2.3.1'),
array('1.2.3z', '1.2.3.26'),
array('1.2.3za', '1.2.3.27'),
array('1.2.3zy', '1.2.3.51'),
array('1.2.3zz', '1.2.3.52'),
);
}
/**
* @dataProvider getOpenSslVersions
* @param string $input
* @param string $parsedVersion
* @param bool $fipsExpected
* @param string|null $normalizedVersion
*/
public function testParseOpensslVersions($input, $parsedVersion, $fipsExpected = false, $normalizedVersion = null)
{
self::assertSame($parsedVersion, Version::parseOpenssl($input, $isFips));
self::assertSame($fipsExpected, $isFips);
$normalizedVersion = $normalizedVersion ? $normalizedVersion : $parsedVersion;
self::assertSame($normalizedVersion, $this->getVersionParser()->normalize($parsedVersion));
}
public function getLibJpegVersions()
{
return array(
array('9', '9.0'),
array('9a', '9.1'),
array('9b', '9.2'),
// Never seen in the wild, just for overflow correctness
array('9za', '9.27'),
);
}
/**
* @dataProvider getLibJpegVersions
* @param string $input
* @param string $parsedVersion
*/
public function testParseLibjpegVersion($input, $parsedVersion)
{
self::assertSame($parsedVersion, Version::parseLibjpeg($input));
}
public function getZoneinfoVersions()
{
return array(
array('2019c', '2019.3'),
array('2020a', '2020.1'),
// Never happened so far but fixate overflow behavior
array('2020za', '2020.27'),
);
}
/**
* @dataProvider getZoneinfoVersions
* @param string $input
* @param string $parsedVersion
*/
public function testParseZoneinfoVersion($input, $parsedVersion)
{
self::assertSame($parsedVersion, Version::parseZoneinfoVersion($input));
}
}
Loading…
Cancel
Save