1
0
Fork 0

Merge branch 'master' into filter-packages

pull/8850/head
Jordi Boggiano 2020-08-25 16:51:28 +02:00
commit 9a04ecefbf
No known key found for this signature in database
GPG Key ID: 7BBD42C429EC80BC
73 changed files with 2857 additions and 342 deletions

View File

@ -11,6 +11,7 @@ on:
env:
COMPOSER_FLAGS: "--ansi --no-interaction --no-progress --prefer-dist"
COMPOSER_UPDATE_FLAGS: ""
COMPOSER_TESTS_ARE_RUNNING: "1"
SYMFONY_PHPUNIT_VERSION: "8.3"
SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT: "1"
@ -108,7 +109,7 @@ jobs:
run: "echo \"::set-output name=directory::$(composer config cache-dir)\""
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v1"
uses: "actions/cache@v2"
with:
path: "${{ steps.determine-composer-cache-directory.outputs.directory }}"
key: "php-${{ matrix.php-version }}-symfony-php-unit-version-${{ env.SYMFONY_PHPUNIT_VERSION }}-${{ hashFiles('**/composer.lock') }}"

View File

@ -34,14 +34,13 @@ jobs:
extensions: "intl, zip"
ini-values: "memory_limit=-1"
php-version: "${{ matrix.php-version }}"
tools: "cs2pr"
- name: "Determine composer cache directory"
id: "determine-composer-cache-directory"
run: "echo \"::set-output name=directory::$(composer config cache-dir)\""
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v1"
uses: "actions/cache@v2"
with:
path: "${{ steps.determine-composer-cache-directory.outputs.directory }}"
key: "php-${{ matrix.php-version }}-symfony-php-unit-version-${{ env.SYMFONY_PHPUNIT_VERSION }}-${{ hashFiles('**/composer.lock') }}"
@ -52,5 +51,5 @@ jobs:
- name: Run PHPStan
run: |
bin/composer require --dev phpstan/phpstan:^0.12.26 phpunit/phpunit:^7.5 --with-all-dependencies
vendor/bin/phpstan analyse --configuration=phpstan/config.neon || vendor/bin/phpstan analyse --configuration=phpstan/config.neon --error-format=checkstyle | cs2pr
bin/composer require --dev phpstan/phpstan:^0.12.37 phpunit/phpunit:^7.5.20 --with-all-dependencies
vendor/bin/phpstan analyse --configuration=phpstan/config.neon

View File

@ -1,3 +1,14 @@
### [2.0.0-alpha3] 2020-08-03
* Breaking: Zip archives loaded by artifact repositories must now have a composer.json on top level, or a max of one folder on top level of the archive
* Added --no-dev support to `show` and `outdated` commands to skip dev requirements
* Added support for multiple --repository flags being passed into the `create-project` command, only useful in combination with `--add-repository` to persist them to composer.json
* Added a new optional `list` API endpoint for v2-format composer repositories, see [UPGRADE](UPGRADE-2.0.md) for details
* Fixed `show -a` command not listing anything
* Fixed solver bug where it ended in a "Reached invalid decision id 0"
* Fixed updates of git-installed packages on windows
* Lots of minor bug fixes
### [2.0.0-alpha2] 2020-06-24
* Added parallel installation of packages (requires OSX/Linux/WSL, and that `unzip` is present in PATH)
@ -46,6 +57,12 @@
* Fixed suggest output being very spammy, it now is only one line long and shows more rarely
* Fixed conflict rules like e.g. >=5 from matching dev-master, as it is not normalized to 9999999-dev internally anymore
### [1.10.10] 2020-08-03
* Fixed `create-project` not triggering events while installing the root package
* Fixed PHP 8 compatibility issue
* Fixed `self-update` to avoid automatically upgrading to the next major version once it becomes stable
### [1.10.9] 2020-07-16
* Fixed Bitbucket redirect loop when credentials are outdated
@ -921,8 +938,10 @@
* Initial release
[2.0.0-alpha3]: https://github.com/composer/composer/compare/2.0.0-alpha2...2.0.0-alpha3
[2.0.0-alpha2]: https://github.com/composer/composer/compare/2.0.0-alpha1...2.0.0-alpha2
[2.0.0-alpha1]: https://github.com/composer/composer/compare/1.10.7...2.0.0-alpha1
[1.10.10]: https://github.com/composer/composer/compare/1.10.9...1.10.10
[1.10.9]: https://github.com/composer/composer/compare/1.10.8...1.10.9
[1.10.8]: https://github.com/composer/composer/compare/1.10.7...1.10.8
[1.10.7]: https://github.com/composer/composer/compare/1.10.6...1.10.7

View File

@ -80,4 +80,6 @@ The providers-api is optional, but if you implement it it should return packages
This is also optional, it should accept an optional `?filter=xx` query param, which can contain `*` as wildcards matching any substring.
It must return an array of package names as `{"packageNames": ["a/b", "c/d"]}`. See https://packagist.org/packages/list.json?filter=composer/* for example.
It must return an array of package names as `{"packageNames": ["a/b", "c/d"]}`. See <https://packagist.org/packages/list.json?filter=composer/*> for example.
It should return the names of package which names match the filter (or all names if no filter is present). Replace/provide rules should not be considered here.

44
composer.lock generated
View File

@ -8,16 +8,16 @@
"packages": [
{
"name": "composer/ca-bundle",
"version": "1.2.7",
"version": "1.2.8",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
"reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd"
"reference": "8a7ecad675253e4654ea05505233285377405215"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd",
"reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/8a7ecad675253e4654ea05505233285377405215",
"reference": "8a7ecad675253e4654ea05505233285377405215",
"shasum": ""
},
"require": {
@ -63,19 +63,23 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues",
"source": "https://github.com/composer/ca-bundle/tree/1.2.7"
"source": "https://github.com/composer/ca-bundle/tree/1.2.8"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2020-04-08T08:27:21+00:00"
"time": "2020-08-23T12:54:47+00:00"
},
{
"name": "composer/semver",
@ -239,16 +243,16 @@
},
{
"name": "composer/xdebug-handler",
"version": "1.4.2",
"version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
"reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51"
"reference": "ebd27a9866ae8254e873866f795491f02418c5a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51",
"reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ebd27a9866ae8254e873866f795491f02418c5a5",
"reference": "ebd27a9866ae8254e873866f795491f02418c5a5",
"shasum": ""
},
"require": {
@ -282,7 +286,7 @@
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/xdebug-handler/issues",
"source": "https://github.com/composer/xdebug-handler/tree/1.4.2"
"source": "https://github.com/composer/xdebug-handler/tree/1.4.3"
},
"funding": [
{
@ -298,7 +302,7 @@
"type": "tidelift"
}
],
"time": "2020-06-04T11:16:35+00:00"
"time": "2020-08-19T10:27:58+00:00"
},
{
"name": "justinrainbow/json-schema",
@ -470,16 +474,16 @@
},
{
"name": "seld/jsonlint",
"version": "1.8.0",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/jsonlint.git",
"reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1"
"reference": "3d5eb71705adfa34bd34b993400622932b2f62fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1",
"reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1",
"url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/3d5eb71705adfa34bd34b993400622932b2f62fd",
"reference": "3d5eb71705adfa34bd34b993400622932b2f62fd",
"shasum": ""
},
"require": {
@ -529,7 +533,7 @@
"type": "tidelift"
}
],
"time": "2020-04-30T19:05:18+00:00"
"time": "2020-08-13T09:07:59+00:00"
},
{
"name": "seld/phar-utils",
@ -810,7 +814,7 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.18.0",
"version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@ -889,7 +893,7 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.18.0",
"version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
@ -949,7 +953,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/master"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.18.1"
},
"funding": [
{

View File

@ -142,6 +142,9 @@ reinstalling the project you can feel confident the dependencies installed are
still working even if your dependencies released many new versions since then.
(See note below about using the `update` command.)
> **Note:** For libraries it is not necessary to commit the lock
> file, see also: [Libraries - Lock file](02-libraries.md#lock-file).
## Updating dependencies to their latest versions
As mentioned above, the `composer.lock` file prevents you from automatically getting
@ -165,9 +168,6 @@ If you only want to install, upgrade or remove one dependency, you can explicitl
php composer.phar update monolog/monolog [...]
```
> **Note:** For libraries it is not necessary to commit the lock
> file, see also: [Libraries - Lock file](02-libraries.md#lock-file).
## Packagist
[Packagist](https://packagist.org/) is the main Composer repository. A Composer

View File

@ -516,7 +516,7 @@ There are some cases, when there is no ability to have one of the previously
mentioned repository types online, even the VCS one. Typical example could be
cross-organisation library exchange through built artifacts. Of course, most
of the times they are private. To simplify maintenance, one can simply use a
repository of type `artifact` with a folder containing ZIP archives of those
repository of type `artifact` with a folder containing ZIP or TAR archives of those
private packages:
```json

View File

@ -85,11 +85,11 @@ gitlab.com the domain names must be also specified with the
## gitlab-token
A list of domain names and private tokens. Private token can be either simple
string, or array with username and token. For example using `{"gitlab.com":
A list of domain names and private tokens. Private token can be either simple
string, or array with username and token. For example using `{"gitlab.com":
"privatetoken"}` as the value of this option will use `privatetoken` to access
private repositories on gitlab. Using `{"gitlab.com": {"username": "gitlabuser",
"token": "privatetoken"}}` will use both username and token for gitlab deploy
"token": "privatetoken"}}` will use both username and token for gitlab deploy
token functionality (https://docs.gitlab.com/ee/user/project/deploy_tokens/)
Please note: If the package is not hosted at
gitlab.com the domain names must be also specified with the
@ -204,6 +204,10 @@ downloads. When the garbage collection is periodically ran, this is the maximum
size the cache will be able to use. Older (less used) files will be removed
first until the cache fits.
## cache-read-only
Defaults to `false`. Whether to use the Composer cache in read-only mode.
## bin-compat
Defaults to `auto`. Determines the compatibility of the binaries to be installed.

View File

@ -237,6 +237,10 @@
"type": ["string", "integer"],
"description": "The cache max size for the files cache, defaults to \"300MiB\"."
},
"cache-read-only": {
"type": ["boolean"],
"description": "Whether to use the Composer cache in read-only mode."
},
"bin-compat": {
"enum": ["auto", "full"],
"description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed) and can be \"full\" (compatible with both Windows and Unix-based systems)."

View File

@ -30,19 +30,22 @@ class Cache
private $enabled = true;
private $allowlist;
private $filesystem;
private $readOnly;
/**
* @param IOInterface $io
* @param string $cacheDir location of the cache
* @param string $allowlist List of characters that are allowed in path names (used in a regex character class)
* @param Filesystem $filesystem optional filesystem instance
* @param bool $readOnly whether the cache is in readOnly mode
*/
public function __construct(IOInterface $io, $cacheDir, $allowlist = 'a-z0-9.', Filesystem $filesystem = null)
public function __construct(IOInterface $io, $cacheDir, $allowlist = 'a-z0-9.', Filesystem $filesystem = null, $readOnly = false)
{
$this->io = $io;
$this->root = rtrim($cacheDir, '/\\') . '/';
$this->allowlist = $allowlist;
$this->filesystem = $filesystem ?: new Filesystem();
$this->readOnly = (bool) $readOnly;
if (!self::isUsable($cacheDir)) {
$this->enabled = false;
@ -59,6 +62,22 @@ class Cache
}
}
/**
* @param bool $readOnly
*/
public function setReadOnly($readOnly)
{
$this->readOnly = (bool) $readOnly;
}
/**
* @return bool
*/
public function isReadOnly()
{
return $this->readOnly;
}
public static function isUsable($path)
{
return !preg_match('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path);
@ -90,7 +109,7 @@ class Cache
public function write($file, $contents)
{
if ($this->enabled) {
if ($this->enabled && !$this->readOnly) {
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
$this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);
@ -128,7 +147,7 @@ class Cache
*/
public function copyFrom($file, $source)
{
if ($this->enabled) {
if ($this->enabled && !$this->readOnly) {
$file = preg_replace('{[^'.$this->allowlist.']}i', '-', $file);
$this->filesystem->ensureDirectoryExists(dirname($this->root . $file));

View File

@ -59,6 +59,7 @@ EOT
continue;
}
$cache = new Cache($io, $cachePath);
$cache->setReadOnly($config->get('cache-read-only'));
if (!$cache->isEnabled()) {
$io->writeError("<info>Cache is not enabled ($key): $cachePath</info>");

View File

@ -135,8 +135,36 @@ EOT
$latest = $versionsUtil->getLatest();
$latestStable = $versionsUtil->getLatest('stable');
try {
$latestPreview = $versionsUtil->getLatest('preview');
} catch (\UnexpectedValueException $e) {
$latestPreview = $latestStable;
}
$latestVersion = $latest['version'];
$updateVersion = $input->getArgument('version') ?: $latestVersion;
$currentMajorVersion = preg_replace('{^(\d+).*}', '$1', Composer::getVersion());
$updateMajorVersion = preg_replace('{^(\d+).*}', '$1', $updateVersion);
$previewMajorVersion = preg_replace('{^(\d+).*}', '$1', $latestPreview['version']);
if ($versionsUtil->getChannel() === 'stable' && !$input->getArgument('version')) {
// if requesting stable channel and no specific version, avoid automatically upgrading to the next major
// simply output a warning that the next major stable is available and let users upgrade to it manually
if ($currentMajorVersion < $updateMajorVersion) {
$skippedVersion = $updateVersion;
$versionsUtil->setChannel($currentMajorVersion);
$latest = $versionsUtil->getLatest();
$latestStable = $versionsUtil->getLatest('stable');
$latestVersion = $latest['version'];
$updateVersion = $latestVersion;
$io->writeError('<warning>A new stable major version of Composer is available ('.$skippedVersion.'), run "composer self-update --'.$updateMajorVersion.'" to update to it. See also https://github.com/composer/composer/releases for changelogs.</warning>');
} elseif ($currentMajorVersion < $previewMajorVersion) {
// promote next major version if available in preview
$io->writeError('<warning>A preview release of the next major version of Composer is available ('.$latestPreview['version'].'), run "composer self-update --preview" to give it a try. See also https://github.com/composer/composer/releases for changelogs.</warning>');
}
}
if ($requestedChannel && is_numeric($requestedChannel) && substr($latestStable['version'], 0, 1) !== $requestedChannel) {
$io->writeError('<warning>Warning: You forced the install of '.$latestVersion.' via --'.$requestedChannel.', but '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>');
@ -243,7 +271,12 @@ TAGSPUBKEY
$signature = json_decode($signature, true);
$signature = base64_decode($signature['sha384']);
$verified = 1 === openssl_verify(file_get_contents($tempFilename), $signature, $pubkeyid, $algo);
openssl_free_key($pubkeyid);
// PHP 8 automatically frees the key instance and deprecates the function
if (PHP_VERSION_ID < 80000) {
openssl_free_key($pubkeyid);
}
if (!$verified) {
throw new \RuntimeException('The phar signature did not match the file you downloaded, this means your public keys are outdated or that the phar file is corrupt/has been modified');
}

View File

@ -578,8 +578,8 @@ EOT
$matchedPackage = null;
$versions = array();
$matches = $repositorySet->findPackages($name, $constraint);
$pool = $repositorySet->createPoolForPackage($name);
$matches = $pool->whatProvides($name, $constraint);
foreach ($matches as $index => $package) {
// select an exact match if it is in the installed repo and no specific version was required
if (null === $version && $installedRepo->hasPackage($package)) {

View File

@ -41,6 +41,7 @@ class Config
'cache-ttl' => 15552000, // 6 months
'cache-files-ttl' => null, // fallback to cache-ttl
'cache-files-maxsize' => '300MiB',
'cache-read-only' => false,
'bin-compat' => 'auto',
'discard-changes' => false,
'autoloader-suffix' => null,
@ -211,6 +212,7 @@ class Config
public function get($key, $flags = 0)
{
switch ($key) {
// strings/paths with env var and {$refs} support
case 'vendor-dir':
case 'bin-dir':
case 'process-timeout':
@ -234,20 +236,34 @@ class Config
return (($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS) ? $val : $this->realpath($val);
// booleans with env var support
case 'cache-read-only':
case 'htaccess-protect':
$value = $this->getComposerEnv('COMPOSER_HTACCESS_PROTECT');
if (false === $value) {
$value = $this->config[$key];
}
return $value !== 'false' && (bool) $value;
// convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
$env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
$val = $this->getComposerEnv($env);
if (false === $val) {
$val = $this->config[$key];
}
return $val !== 'false' && (bool) $val;
// booleans without env var support
case 'disable-tls':
case 'secure-http':
case 'use-github-api':
case 'lock':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
// ints without env var support
case 'cache-ttl':
return (int) $this->config[$key];
// numbers with kb/mb/gb support, without env var support
case 'cache-files-maxsize':
if (!preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $this->config[$key], $matches)) {
throw new \RuntimeException(
"Could not parse the value of 'cache-files-maxsize': {$this->config[$key]}"
"Could not parse the value of '$key': {$this->config[$key]}"
);
}
$size = $matches[1];
@ -269,6 +285,7 @@ class Config
return $size;
// special cases below
case 'cache-files-ttl':
if (isset($this->config[$key])) {
return (int) $this->config[$key];
@ -326,14 +343,6 @@ class Config
return $protos;
case 'disable-tls':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'secure-http':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'use-github-api':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'lock':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
default:
if (!isset($this->config[$key])) {
return null;

View File

@ -22,6 +22,7 @@ use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Seld\JsonLint\ParsingException;
use Composer\Command;
use Composer\Composer;
use Composer\Factory;
@ -191,6 +192,20 @@ class Application extends BaseApplication
}
} catch (NoSslException $e) {
// suppress these as they are not relevant at this point
} catch (ParsingException $e) {
$details = $e->getDetails();
$file = realpath(Factory::getComposerFile());
$line = null;
if ($details && isset($details['line'])) {
$line = $details['line'];
}
$ghe = new GithubActionError($this->io);
$ghe->emit($e->getMessage(), $file, $line);
throw $e;
}
$this->hasPluginCommands = true;
@ -237,7 +252,7 @@ class Application extends BaseApplication
if (function_exists('posix_getuid') && posix_getuid() === 0) {
if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
$io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');
if ($io->isInteractive()) {
if (!$io->askConfirmation('<info>Continue as root/super user</info> [<comment>yes</comment>]? ', true)) {
return 1;
@ -307,8 +322,13 @@ class Application extends BaseApplication
} catch (ScriptExecutionException $e) {
return (int) $e->getCode();
} catch (\Exception $e) {
$ghe = new GithubActionError($this->io);
$ghe->emit($e->getMessage());
$this->hintCommonErrors($e);
restore_error_handler();
throw $e;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Composer\Console;
use Composer\IO\IOInterface;
final class GithubActionError
{
/**
* @var IOInterface
*/
protected $io;
public function __construct(IOInterface $io)
{
$this->io = $io;
}
/**
* @param string $message
* @param null|string $file
* @param null|int $line
*/
public function emit($message, $file = null, $line = null)
{
if (getenv('GITHUB_ACTIONS') && !getenv('COMPOSER_TESTS_ARE_RUNNING')) {
// newlines need to be encoded
// see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448
$message = str_replace("\n", '%0A', $message);
if ($file && $line) {
$this->io->write("::error file=". $file .",line=". $line ."::". $message);
} elseif ($file) {
$this->io->write("::error file=". $file ."::". $message);
} else {
$this->io->write("::error ::". $message);
}
}
}
}

View File

@ -60,7 +60,7 @@ class DefaultPolicy implements PolicyInterface
$sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals);
}
$selected = \call_user_func_array('array_merge', $packages);
$selected = \call_user_func_array('array_merge', array_values($packages));
// now sort the result across all packages to respect replaces across packages
usort($selected, function ($a, $b) use ($policy, $pool, $requiredPackage) {

View File

@ -276,27 +276,19 @@ class Problem
// check if the package is found when bypassing stability checks
if ($packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) {
// we must first verify if a valid package would be found in a lower priority repository
if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) {
return self::computeCheckForLowerPrioRepo($isVerbose, $packageName, $constraint, $packages, $allReposPackages, 'minimum-stability');
}
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.');
}
// check if the package is found when bypassing the constraint check
if ($packages = $repositorySet->findPackages($packageName, null)) {
// check if the package is found when bypassing the constraint and stability checks
if ($packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) {
// we must first verify if a valid package would be found in a lower priority repository
if ($allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES)) {
$higherRepoPackages = $repositorySet->findPackages($packageName, null);
$nextRepoPackages = array();
$nextRepo = null;
foreach ($allReposPackages as $package) {
if ($nextRepo === null || $nextRepo === $package->getRepository()) {
$nextRepoPackages[] = $package;
$nextRepo = $package->getRepository();
} else {
break;
}
}
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your constraint and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.');
return self::computeCheckForLowerPrioRepo($isVerbose, $packageName, $constraint, $packages, $allReposPackages, 'constraint');
}
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.');
@ -396,6 +388,24 @@ class Problem
return false;
}
private static function computeCheckForLowerPrioRepo($isVerbose, $packageName, $constraint, array $higherRepoPackages, array $allReposPackages, $reason)
{
$nextRepoPackages = array();
$nextRepo = null;
foreach ($allReposPackages as $package) {
if ($nextRepo === null || $nextRepo === $package->getRepository()) {
$nextRepoPackages[] = $package;
$nextRepo = $package->getRepository();
} else {
break;
}
}
return array("- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages with higher priority do not match your '.$reason.' and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.');
}
/**
* Turns a constraint into text usable in a sentence describing a request
*

View File

@ -46,10 +46,17 @@ abstract class ArchiveDownloader extends FileDownloader
$this->io->writeError('Extracting archive', false);
}
$this->filesystem->emptyDirectory($path);
$vendorDir = $this->config->get('vendor-dir');
// clean up the target directory, unless it contains the vendor dir, as the vendor dir contains
// the archive to be extracted. This is the case when installing with create-project in the current directory
// but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here.
if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path).DIRECTORY_SEPARATOR)) {
$this->filesystem->emptyDirectory($path);
}
do {
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
$temporaryDir = $vendorDir.'/composer/'.substr(md5(uniqid('', true)), 0, 8);
} while (is_dir($temporaryDir));
$this->addCleanupPath($package, $temporaryDir);

View File

@ -187,7 +187,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
$url = reset($urls);
$cacheKey = $url['cacheKey'];
if ($cache) {
if ($cache && !$cache->isReadOnly()) {
$self->lastCacheWrites[$package->getName()] = $cacheKey;
$cache->copyFrom($cacheKey, $fileName);
}

View File

@ -146,10 +146,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
if (!empty($this->cachedPackages[$target->getId()][$ref])) {
$msg = "Checking out ".$this->getShortHash($ref).' from cache';
$command = 'git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%';
$command = '(git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer)) && git remote set-url composer %sanitizedUrl%';
} else {
$msg = "Checking out ".$this->getShortHash($ref);
$command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%';
$command = '(git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)) && git remote set-url composer %sanitizedUrl%';
if (getenv('COMPOSER_DISABLE_NETWORK')) {
throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
}

View File

@ -0,0 +1,7 @@
<?php
namespace Composer\Downloader;
class MaxFileSizeExceededException extends TransportException
{
}

View File

@ -180,7 +180,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
{
$realUrl = realpath($package->getDistUrl());
if ($path === $realUrl) {
if (realpath($path) === $realUrl) {
if ($output) {
$this->io->writeError(" - " . UninstallOperation::format($package).", source is still present in $path");
}

View File

@ -477,6 +477,7 @@ class Factory
$cache = null;
if ($config->get('cache-files-ttl') > 0) {
$cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./');
$cache->setReadOnly($config->get('cache-read-only'));
}
$fs = new Filesystem($process);

View File

@ -46,7 +46,7 @@ class InstalledVersions
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param ?string $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
*
* @return bool
*/

View File

@ -13,6 +13,7 @@
namespace Composer;
use Composer\Autoload\AutoloadGenerator;
use Composer\Console\GithubActionError;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\LocalRepoTransaction;
use Composer\DependencyResolver\LockTransaction;
@ -387,9 +388,13 @@ class Installer
$this->io->writeError('<info>Updating dependencies</info>');
// if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata
if ($this->updateMirrors) {
if ($this->updateMirrors && $lockedRepository) {
foreach ($lockedRepository->getPackages() as $lockedPackage) {
$request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion()));
// exclude alias packages here as for root aliases, both alias and aliased are
// present in the lock repo and we only want to require the aliased version
if (!$lockedPackage instanceof AliasPackage) {
$request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion()));
}
}
} else {
$links = array_merge($this->package->getRequires(), $this->package->getDevRequires());
@ -412,12 +417,18 @@ class Installer
$ruleSetSize = $solver->getRuleSetSize();
$solver = null;
} catch (SolverProblemsException $e) {
$this->io->writeError('<error>Your requirements could not be resolved to an installable set of packages.</error>', true, IOInterface::QUIET);
$this->io->writeError($e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()));
$err = 'Your requirements could not be resolved to an installable set of packages.';
$prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose());
$this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
$this->io->writeError($prettyProblem);
if (!$this->devMode) {
$this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET);
}
$ghe = new GithubActionError($this->io);
$ghe->emit($err."\n".$prettyProblem);
return max(1, $e->getCode());
}
@ -571,10 +582,16 @@ class Installer
$nonDevLockTransaction = $solver->solve($request, $this->ignorePlatformReqs);
$solver = null;
} catch (SolverProblemsException $e) {
$this->io->writeError('<error>Unable to find a compatible set of packages based on your non-dev requirements alone.</error>', true, IOInterface::QUIET);
$err = 'Unable to find a compatible set of packages based on your non-dev requirements alone.';
$prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), true);
$this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
$this->io->writeError('Your requirements can be resolved successfully when require-dev packages are present.');
$this->io->writeError('You may need to move packages from require-dev or some of their dependencies to require.');
$this->io->writeError($e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), true));
$this->io->writeError($prettyProblem);
$ghe = new GithubActionError($this->io);
$ghe->emit($err."\n".$prettyProblem);
return max(1, $e->getCode());
}
@ -637,8 +654,14 @@ class Installer
return 1;
}
} catch (SolverProblemsException $e) {
$this->io->writeError('<error>Your lock file does not contain a compatible set of packages. Please run composer update.</error>', true, IOInterface::QUIET);
$this->io->writeError($e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose()));
$err = 'Your lock file does not contain a compatible set of packages. Please run composer update.';
$prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose());
$this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
$this->io->writeError($prettyProblem);
$ghe = new GithubActionError($this->io);
$ghe->emit($err."\n".$prettyProblem);
return max(1, $e->getCode());
}

View File

@ -111,25 +111,27 @@ class ValidatingArrayLoader implements LoaderInterface
if (is_array($this->config['license']) || is_string($this->config['license'])) {
$licenses = (array) $this->config['license'];
// strip proprietary since it's not a valid SPDX identifier, but is accepted by composer
foreach ($licenses as $key => $license) {
if ('proprietary' === $license) {
unset($licenses[$key]);
}
}
$licenseValidator = new SpdxLicenses();
if (count($licenses) === 1 && !$licenseValidator->validate($licenses) && $licenseValidator->validate(trim($licenses[0]))) {
$this->warnings[] = sprintf(
'License %s must not contain extra spaces, make sure to trim it.',
json_encode($this->config['license'])
);
} elseif (array() !== $licenses && !$licenseValidator->validate($licenses)) {
$this->warnings[] = sprintf(
'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL .
'If the software is closed-source, you may use "proprietary" as license.',
json_encode($this->config['license'])
);
foreach ($licenses as $license) {
// replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer
if ('proprietary' === $license) {
continue;
}
$licenseToValidate = str_replace('proprietary', 'MIT', $license);
if (!$licenseValidator->validate($licenseToValidate)) {
if ($licenseValidator->validate(trim($licenseToValidate))) {
$this->warnings[] = sprintf(
'License %s must not contain extra spaces, make sure to trim it.',
json_encode($license)
);
} else {
$this->warnings[] = sprintf(
'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL .
'If the software is closed-source, you may use "proprietary" as license.',
json_encode($license)
);
}
}
}
}
}

View File

@ -0,0 +1,61 @@
<?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;
}
}

View File

@ -0,0 +1,108 @@
<?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 string $fn
* @return bool
*/
public function hasFunction($fn)
{
return function_exists($fn);
}
/**
* @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();
}
}

View File

@ -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\Platform;
/**
* @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
);
}
}

View File

@ -16,6 +16,7 @@ use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\LoaderInterface;
use Composer\Util\Tar;
use Composer\Util\Zip;
/**
@ -66,7 +67,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito
$directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
$iterator = new \RecursiveIteratorIterator($directory);
$regex = new \RegexIterator($iterator, '/^.+\.(zip|phar)$/i');
$regex = new \RegexIterator($iterator, '/^.+\.(zip|phar|tar|gz|tgz)$/i');
foreach ($regex as $file) {
/* @var $file \SplFileInfo */
if (!$file->isFile()) {
@ -88,7 +89,26 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito
private function getComposerInformation(\SplFileInfo $file)
{
$json = Zip::getComposerJson($file->getPathname());
$json = null;
$fileType = null;
$fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION);
if (in_array($fileExtension, array('gz', 'tar', 'tgz'), true)) {
$fileType = 'tar';
} else if ($fileExtension === 'zip') {
$fileType = 'zip';
} else {
throw new \RuntimeException('Files with "'.$fileExtension.'" extensions aren\'t supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.');
}
try {
if ($fileType === 'tar') {
$json = Tar::getComposerJson($file->getPathname());
} else {
$json = Zip::getComposerJson($file->getPathname());
}
} catch (\Exception $exception) {
$this->io->write('Failed loading package '.$file->getPathname().': '.$exception->getMessage(), false, IOInterface::VERBOSE);
}
if (null === $json) {
return false;
@ -96,7 +116,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito
$package = JsonFile::parseJson($json, $file->getPathname().'#composer.json');
$package['dist'] = array(
'type' => 'zip',
'type' => $fileType,
'url' => strtr($file->getPathname(), '\\', '/'),
'shasum' => sha1_file($file->getRealPath()),
);

View File

@ -130,6 +130,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$this->baseUrl = rtrim(preg_replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/');
$this->io = $io;
$this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$~');
$this->cache->setReadOnly($config->get('cache-read-only'));
$this->versionParser = new VersionParser();
$this->loader = new ArrayLoader($this->versionParser);
$this->httpDownloader = $httpDownloader;
@ -1082,7 +1083,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$data = $response->decodeJson();
HttpDownloader::outputWarnings($this->io, $this->url, $data);
if ($cacheKey) {
if ($cacheKey && !$this->cache->isReadOnly()) {
if ($storeLastModifiedTime) {
$lastModifiedDate = $response->getHeader('last-modified');
if ($lastModifiedDate) {
@ -1150,7 +1151,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
if (isset($options['http']['header'])) {
$options['http']['header'] = (array) $options['http']['header'];
}
$options['http']['header'][] = array('If-Modified-Since: '.$lastModifiedTime);
$options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime;
$response = $this->httpDownloader->get($filename, $options);
$json = $response->getBody();
if ($json === '' && $response->getStatusCode() === 304) {
@ -1166,7 +1167,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$data['last-modified'] = $lastModifiedDate;
$json = json_encode($data);
}
$this->cache->write($cacheKey, $json);
if (!$this->cache->isReadOnly()) {
$this->cache->write($cacheKey, $json);
}
return $data;
} catch (\Exception $e) {
@ -1213,7 +1216,13 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$filename = $preFileDownloadEvent->getProcessedUrl();
}
$options = $lastModifiedTime ? array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))) : array();
$options = $this->options;
if ($lastModifiedTime) {
if (isset($options['http']['header'])) {
$options['http']['header'] = (array) $options['http']['header'];
}
$options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime;
}
$io = $this->io;
$url = $this->url;
@ -1243,7 +1252,9 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$data['last-modified'] = $lastModifiedDate;
$json = JsonFile::encode($data, JsonFile::JSON_UNESCAPED_SLASHES | JsonFile::JSON_UNESCAPED_UNICODE);
}
$cache->write($cacheKey, $json);
if (!$cache->isReadOnly()) {
$cache->write($cacheKey, $json);
}
$repo->freshMetadataUrls[$filename] = true;
return $data;

View File

@ -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\Platform\Version;
use Composer\Plugin\PluginInterface;
use Composer\Util\ProcessExecutor;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\Silencer;
use Composer\Util\Platform;
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'), 'inet_pton', array('::')) !== 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>.+)$/im', $info, $librabbitmqMatches)) {
$this->addLibrary($name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version');
}
// AMQP protocol version => 0-9-1
if (preg_match('/^AMQP protocol version => (?<version>.+)$/im', $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>.*),/im', $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>.+)$}im', $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>.+?)(?:/.*)?$}im', $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>.+)$}im', $info, $zlibMatches)) {
$this->addLibrary($name.'-zlib', $zlibMatches['version'], 'curl zlib version');
}
break;
case 'iconv':
$prettyVersion = ICONV_VERSION;
case 'date':
$info = $this->runtime->getExtensionInfo($name);
// timelib version => 2018.03
if (preg_match('/^timelib version => (?<version>.+)$/im', $info, $timelibMatches)) {
$this->addLibrary($name.'-timelib', $timelibMatches['version'], 'date timelib version');
}
// Timezone Database => internal
if (preg_match('/^Timezone Database => (?<source>internal|external)$/im', $info, $zoneinfoSourceMatches)) {
$external = $zoneinfoSourceMatches['source'] === 'external';
if (preg_match('/^"Olson" Timezone Database Version => (?<version>.+?)(\.system)?$/im', $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 'intl':
$name = 'ICU';
if (defined('INTL_ICU_VERSION')) {
$prettyVersion = INTL_ICU_VERSION;
} else {
$reflector = new \ReflectionExtension('intl');
case 'fileinfo':
$info = $this->runtime->getExtensionInfo($name);
ob_start();
$reflector->info();
$output = ob_get_clean();
// libmagic => 537
if (preg_match('/^libmagic => (?<version>.+)$/im', $info, $magicMatches)) {
$this->addLibrary($name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version');
}
break;
preg_match('/^ICU version => (.*)$/m', $output, $matches);
$prettyVersion = $matches[1];
case 'gd':
$this->addLibrary($name, $this->runtime->getConstant('GD_VERSION'));
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^libJPEG Version => (?<version>.+?)(?: compatible)?$/im', $info, $libjpegMatches)) {
$this->addLibrary($name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd');
}
if (preg_match('/^libPNG Version => (?<version>.+)$/im', $info, $libpngMatches)) {
$this->addLibrary($name.'-libpng', $libpngMatches['version'], 'libpng version for gd');
}
if (preg_match('/^FreeType Version => (?<version>.+)$/im', $info, $freetypeMatches)) {
$this->addLibrary($name.'-freetype', $freetypeMatches['version'], 'freetype version for gd');
}
if (preg_match('/^libXpm Version => (?<versionId>\d+)$/im', $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':
$this->addLibrary($name, $this->runtime->getConstant('ICONV_VERSION'));
break;
case '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>.+)$/im', $info, $matches)) {
$this->addLibrary('icu', $matches['version'], $description);
}
// ICU TZData version => 2019c
if (preg_match('/^ICU TZData version => (?<version>.*)$/im', $info, $zoneinfoMatches)) {
$this->addLibrary('icu-zoneinfo', Version::parseZoneinfoVersion($zoneinfoMatches['version']), 'zoneinfo ("Olson") database for icu');
}
// 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+)$/im', $info, $matches) && preg_match('/^Vendor Name => (?<vendor>.+)$/im', $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 'mbstring':
$info = $this->runtime->getExtensionInfo($name);
// libmbfl version => 1.3.2
if (preg_match('/^libmbfl version => (?<version>.+)$/im', $info, $libmbflMatches)) {
$this->addLibrary($name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version');
}
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>.+)$/im', $info, $onigurumaMatches)) {
$this->addLibrary($name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version');
}
break;
case 'memcached':
$info = $this->runtime->getExtensionInfo($name);
// libmemcached version => 1.0.18
if (preg_match('/^libmemcached version => (?<version>.+)$/im', $info, $matches)) {
$this->addLibrary($name.'-libmemcached', $matches['version'], 'libmemcached version');
}
break;
case 'openssl':
$prettyVersion = preg_replace_callback('{^(?:OpenSSL|LibreSSL)?\s*([0-9.]+)([a-z]*).*}i', function ($match) {
if (empty($match[2])) {
return $match[1];
}
// OpenSSL versions add another letter when they reach Z.
// e.g. OpenSSL 0.9.8zh 3 Dec 2015
if (!preg_match('{^z*[a-z]$}', $match[2])) {
// 0.9.8abc is garbage
return 0;
}
$len = strlen($match[2]);
$patchVersion = ($len - 1) * 26; // All Z
$patchVersion += ord($match[2][$len - 1]) - 96;
return $match[1].'.'.$patchVersion;
}, OPENSSL_VERSION_TEXT);
$description = OPENSSL_VERSION_TEXT;
// 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>.+)$/im', $info, $pcreUnicodeMatches)) {
$this->addLibrary($name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support');
}
break;
case 'uuid':
$prettyVersion = phpversion('uuid');
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 'mongodb':
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^libmongoc bundled version => (?<version>.+)$/im', $info, $libmongocMatches)) {
$this->addLibrary($name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb');
}
if (preg_match('/^libbson bundled version => (?<version>.+)$/im', $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>.*)$/im', $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>.+)$/im', $info, $matches)) {
$this->addLibrary($name.'-sqlite', $matches['version']);
}
break;
case 'ssh2':
$info = $this->runtime->getExtensionInfo($name);
if (preg_match('/^libssh2 version => (?<version>.+)$/im', $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>.+)$/im', $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>.+)$/im', $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;
case 'zlib':
if ($this->runtime->hasConstant('ZLIB_VERSION')) {
$this->addLibrary($name, $this->runtime->getConstant('ZLIB_VERSION'));
// Linked Version => 1.2.8
} elseif (preg_match('/^Linked Version => (?<version>.+)$/im', $this->runtime->getExtensionInfo($name), $matches)) {
$this->addLibrary($name, $matches['version']);
}
break;
default:
// None handled extensions have no special cases, skip
continue 2;
break;
}
try {
$version = $this->versionParser->normalize($prettyVersion);
} catch (\UnexpectedValueException $e) {
continue;
}
$lib = new CompletePackage('lib-'.$name, $version, $prettyVersion);
$lib->setDescription($description);
$this->addPackage($lib);
}
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);
}
@ -346,31 +560,37 @@ class PlatformRepository extends ArrayRepository
*/
private function buildPackageName($name)
{
return 'ext-' . str_replace(' ', '-', $name);
return 'ext-' . str_replace(' ', '-', strtolower($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);
}
}

View File

@ -193,7 +193,7 @@ class RepositorySet
}
}
return $candidates;
return $result;
}
public function getProviders($packageName)

View File

@ -60,6 +60,7 @@ abstract class BitbucketDriver extends VcsDriver
$this->repository,
))
);
$this->cache->setReadOnly($this->config->get('cache-read-only'));
}
/**
@ -120,10 +121,14 @@ abstract class BitbucketDriver extends VcsDriver
if (!isset($this->infoCache[$identifier])) {
if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
return $this->infoCache[$identifier] = JsonFile::parseJson($res);
}
$composer = JsonFile::parseJson($res);
} else {
$composer = $this->getBaseComposerInformation($identifier);
$composer = $this->getBaseComposerInformation($identifier);
if ($this->shouldCache($identifier)) {
$this->cache->write($identifier, json_encode($composer));
}
}
if ($composer) {
// specials for bitbucket
@ -174,10 +179,6 @@ abstract class BitbucketDriver extends VcsDriver
}
$this->infoCache[$identifier] = $composer;
if ($this->shouldCache($identifier)) {
$this->cache->write($identifier, json_encode($composer));
}
}
return $this->infoCache[$identifier];

View File

@ -75,6 +75,7 @@ class GitDriver extends VcsDriver
$this->getBranches();
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $cacheUrl));
$this->cache->setReadOnly($this->config->get('cache-read-only'));
}
/**

View File

@ -58,6 +58,7 @@ class GitHubDriver extends VcsDriver
$this->originUrl = 'github.com';
}
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);
$this->cache->setReadOnly($this->config->get('cache-read-only'));
if ( $this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){
$this->setupGitDriver($this->url);
@ -151,10 +152,14 @@ class GitHubDriver extends VcsDriver
if (!isset($this->infoCache[$identifier])) {
if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
return $this->infoCache[$identifier] = JsonFile::parseJson($res);
}
$composer = JsonFile::parseJson($res);
} else {
$composer = $this->getBaseComposerInformation($identifier);
$composer = $this->getBaseComposerInformation($identifier);
if ($this->shouldCache($identifier)) {
$this->cache->write($identifier, json_encode($composer));
}
}
if ($composer) {
// specials for github
@ -173,10 +178,6 @@ class GitHubDriver extends VcsDriver
}
}
if ($this->shouldCache($identifier)) {
$this->cache->write($identifier, json_encode($composer));
}
$this->infoCache[$identifier] = $composer;
}

View File

@ -105,6 +105,7 @@ class GitLabDriver extends VcsDriver
$this->repository = preg_replace('#(\.git)$#', '', $match['repo']);
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository);
$this->cache->setReadOnly($this->config->get('cache-read-only'));
$this->fetchProject();
}
@ -131,10 +132,14 @@ class GitLabDriver extends VcsDriver
if (!isset($this->infoCache[$identifier])) {
if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
return $this->infoCache[$identifier] = JsonFile::parseJson($res);
}
$composer = JsonFile::parseJson($res);
} else {
$composer = $this->getBaseComposerInformation($identifier);
$composer = $this->getBaseComposerInformation($identifier);
if ($this->shouldCache($identifier)) {
$this->cache->write($identifier, json_encode($composer));
}
}
if ($composer) {
// specials for gitlab (this data is only available if authentication is provided)
@ -146,10 +151,6 @@ class GitLabDriver extends VcsDriver
}
}
if ($this->shouldCache($identifier)) {
$this->cache->write($identifier, json_encode($composer));
}
$this->infoCache[$identifier] = $composer;
}
@ -448,7 +449,7 @@ class GitLabDriver extends VcsDriver
if (!$moreThanGuestAccess) {
$this->io->writeError('<warning>GitLab token with Guest only access detected</warning>');
return $this->attemptCloneFallback();
return $this->attemptCloneFallback();
}
}

View File

@ -78,6 +78,7 @@ class SvnDriver extends VcsDriver
}
$this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->baseUrl));
$this->cache->setReadOnly($this->config->get('cache-read-only'));
$this->getBranches();
$this->getTags();

View File

@ -81,7 +81,7 @@ abstract class VcsDriver implements VcsDriverInterface
*/
protected function shouldCache($identifier)
{
return $this->cache && preg_match('{[a-f0-9]{40}}i', $identifier);
return $this->cache && preg_match('{^[a-f0-9]{40}$}iD', $identifier);
}
/**

View File

@ -265,6 +265,9 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
if ($e->getCode() === 404) {
$this->emptyReferences[] = $identifier;
}
if ($e->getCode() === 401 || $e->getCode() === 403) {
throw $e;
}
}
if ($isVeryVerbose) {
$this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).'</warning>');
@ -350,6 +353,9 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
if ($e->getCode() === 404) {
$this->emptyReferences[] = $identifier;
}
if ($e->getCode() === 401 || $e->getCode() === 403) {
throw $e;
}
if ($isVeryVerbose) {
$this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)</warning>');
}

View File

@ -26,6 +26,7 @@ class Versions
private $httpDownloader;
private $config;
private $channel;
private $versionsData;
public function __construct(Config $config, HttpDownloader $httpDownloader)
{
@ -63,12 +64,7 @@ class Versions
public function getLatest($channel = null)
{
if ($this->config->get('disable-tls') === true) {
$protocol = 'http';
} else {
$protocol = 'https';
}
$versions = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson();
$versions = $this->getVersionsData();
foreach ($versions[$channel ?: $this->getChannel()] as $version) {
if ($version['min-php'] <= PHP_VERSION_ID) {
@ -76,6 +72,21 @@ class Versions
}
}
throw new \LogicException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')');
throw new \UnexpectedValueException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')');
}
private function getVersionsData()
{
if (!$this->versionsData) {
if ($this->config->get('disable-tls') === true) {
$protocol = 'http';
} else {
$protocol = 'https';
}
$this->versionsData = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson();
}
return $this->versionsData;
}
}

View File

@ -13,6 +13,7 @@
namespace Composer\Util\Http;
use Composer\Config;
use Composer\Downloader\MaxFileSizeExceededException;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\CaBundle\CaBundle;
@ -56,6 +57,8 @@ class CurlDownloader
'ssl' => array(
'cafile' => CURLOPT_CAINFO,
'capath' => CURLOPT_CAPATH,
'verify_peer' => CURLOPT_SSL_VERIFYPEER,
'verify_peer_name' => CURLOPT_SSL_VERIFYHOST,
),
);
@ -178,7 +181,11 @@ class CurlDownloader
foreach (self::$options as $type => $curlOptions) {
foreach ($curlOptions as $name => $curlOption) {
if (isset($options[$type][$name])) {
curl_setopt($curlHandle, $curlOption, $options[$type][$name]);
if ($type === 'ssl' && $name === 'verify_peer_name') {
curl_setopt($curlHandle, $curlOption, $options[$type][$name] === true ? 2 : $options[$type][$name]);
} else {
curl_setopt($curlHandle, $curlOption, $options[$type][$name]);
}
}
}
}
@ -359,6 +366,18 @@ class CurlDownloader
$previousProgress = $this->jobs[$i]['progress'];
$this->jobs[$i]['progress'] = $progress;
if (isset($this->jobs[$i]['options']['max_file_size'])) {
// Compare max_file_size with the content-length header this value will be -1 until the header is parsed
if ($this->jobs[$i]['options']['max_file_size'] < $progress['download_content_length']) {
throw new MaxFileSizeExceededException('Maximum allowed download size reached. Content-length header indicates ' . $progress['download_content_length'] . ' bytes. Allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes');
}
// Compare max_file_size with the download size in bytes
if ($this->jobs[$i]['options']['max_file_size'] < $progress['size_download']) {
throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . $progress['size_download'] . ' of allowed ' . $this->jobs[$i]['options']['max_file_size'] . ' bytes');
}
}
// TODO
//$this->onProgress($curlHandle, $this->jobs[$i]['callback'], $progress, $previousProgress);
}

View File

@ -13,6 +13,7 @@
namespace Composer\Util;
use Composer\Config;
use Composer\Downloader\MaxFileSizeExceededException;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\CaBundle\CaBundle;
@ -244,6 +245,12 @@ class RemoteFilesystem
$degradedPackagist = true;
}
$maxFileSize = null;
if (isset($options['max_file_size'])) {
$maxFileSize = $options['max_file_size'];
unset($options['max_file_size']);
}
$ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));
$actualContextOptions = stream_context_get_options($ctx);
@ -273,7 +280,7 @@ class RemoteFilesystem
});
$http_response_header = array();
try {
$result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header);
$result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header, $maxFileSize);
if (!empty($http_response_header[0])) {
$statusCode = $this->findStatusCode($http_response_header);
@ -532,23 +539,34 @@ class RemoteFilesystem
/**
* Get contents of remote URL.
*
* @param string $originUrl The origin URL
* @param string $fileUrl The file URL
* @param resource $context The stream context
* @param string $originUrl The origin URL
* @param string $fileUrl The file URL
* @param resource $context The stream context
* @param int $maxFileSize The maximum allowed file size
*
* @return string|false The response contents or false on failure
*/
protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null)
protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null, $maxFileSize = null)
{
$result = false;
try {
$e = null;
$result = file_get_contents($fileUrl, false, $context);
if ($maxFileSize !== null) {
$result = file_get_contents($fileUrl, false, $context, 0, $maxFileSize);
} else {
// passing `null` to file_get_contents will convert `null` to `0` and return 0 bytes
$result = file_get_contents($fileUrl, false, $context);
}
} catch (\Throwable $e) {
} catch (\Exception $e) {
}
if ($maxFileSize !== null && Platform::strlen($result) >= $maxFileSize) {
throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($result) . ' of allowed ' . $maxFileSize . ' bytes');
}
$responseHeaders = isset($http_response_header) ? $http_response_header : array();
if (null !== $e) {

View File

@ -29,6 +29,7 @@ final class StreamContextFactory
* Creates a context supporting HTTP proxies
*
* @param string $url URL the context is to be used for
* @psalm-param array{http: array{follow_location?: int, max_redirects?: int, header?: string|array<string, string|int>}} $defaultOptions
* @param array $defaultOptions Options to merge with the default
* @param array $defaultParams Parameters to specify on the context
* @throws \RuntimeException if https proxy required and OpenSSL uninstalled
@ -56,7 +57,8 @@ final class StreamContextFactory
/**
* @param string $url
* @param array $options
* @return array ['http' => ['header' => [...], 'proxy' => '..', 'request_fulluri' => bool]] formatted as a stream context array
* @psalm-return array{http:{header: string[], proxy?: string, request_fulluri: bool}, ssl: array}
* @return array formatted as a stream context array
*/
public static function initOptions($url, array $options)
{

68
src/Composer/Util/Tar.php Normal file
View File

@ -0,0 +1,68 @@
<?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 Wissem Riahi <wissemr@gmail.com>
*/
class Tar
{
/**
* @param string $pathToArchive
*
* @return string|null
*/
public static function getComposerJson($pathToArchive)
{
$phar = new \PharData($pathToArchive);
if (!$phar->valid()) {
return null;
}
return self::extractComposerJsonFromFolder($phar);
}
/**
* @param \PharData $phar
*
* @throws \RuntimeException
*
* @return string
*/
private static function extractComposerJsonFromFolder(\PharData $phar)
{
if (isset($phar['composer.json'])) {
return $phar['composer.json']->getContent();
}
$topLevelPaths = array();
foreach ($phar as $folderFile) {
$name = $folderFile->getBasename();
if ($folderFile->isDir()) {
$topLevelPaths[$name] = true;
if (\count($topLevelPaths) > 1) {
throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths)));
}
}
}
$composerJsonPath = key($topLevelPaths).'/composer.json';
if ($topLevelPaths && isset($phar[$composerJsonPath])) {
return $phar[$composerJsonPath]->getContent();
}
throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory');
}
}

View File

@ -66,42 +66,44 @@ class Zip
*
* @param \ZipArchive $zip
* @param string $filename
* @throws \RuntimeException
*
* @return bool|int
* @return int
*/
private static function locateFile(\ZipArchive $zip, $filename)
{
$indexOfShortestMatch = false;
$lengthOfShortestMatch = -1;
// return root composer.json if it is there and is a file
if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) {
return $index;
}
$topLevelPaths = array();
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
if (strcmp(basename($stat['name']), $filename) === 0) {
$directoryName = dirname($stat['name']);
if ($directoryName === '.') {
//if composer.json is in root directory
//it has to be the one to use.
return $i;
}
$name = $zip->getNameIndex($i);
$dirname = dirname($name);
if (strpos($directoryName, '\\') !== false ||
strpos($directoryName, '/') !== false) {
//composer.json files below first directory are rejected
continue;
// handle archives with proper TOC
if ($dirname === '.') {
$topLevelPaths[$name] = true;
if (\count($topLevelPaths) > 1) {
throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths)));
}
continue;
}
$length = strlen($stat['name']);
if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) {
//Check it's not a directory.
$contents = $zip->getFromIndex($i);
if ($contents !== false) {
$indexOfShortestMatch = $i;
$lengthOfShortestMatch = $length;
}
// handle archives which do not have a TOC record for the directory itself
if (false === strpos('\\', $dirname) && false === strpos('/', $dirname)) {
$topLevelPaths[$dirname.'/'] = true;
if (\count($topLevelPaths) > 1) {
throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths)));
}
}
}
return $indexOfShortestMatch;
if ($topLevelPaths && false !== ($index = $zip->locateName(key($topLevelPaths).$filename)) && $zip->getFromIndex($index) !== false) {
return $index;
}
throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory');
}
}

View File

@ -390,7 +390,7 @@ class GitDownloaderTest extends TestCase
public function testUpdate()
{
$expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
$expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'https://github.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
@ -421,7 +421,7 @@ class GitDownloaderTest extends TestCase
public function testUpdateWithNewRepoUrl()
{
$expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
$expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'https://github.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
@ -496,8 +496,8 @@ composer https://github.com/old/url (push)
*/
public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
{
$expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
$expectedGitUpdateCommand2 = $this->winCompat("git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'git@github.com:composer/composer'");
$expectedGitUpdateCommand = $this->winCompat("(git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'https://github.com/composer/composer'");
$expectedGitUpdateCommand2 = $this->winCompat("(git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'git@github.com:composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
@ -540,8 +540,8 @@ composer https://github.com/old/url (push)
public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover()
{
$expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '".(Platform::isWindows() ? 'C:\\\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer '".(Platform::isWindows() ? 'C:\\\\' : '/')."'");
$expectedSecondGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer); git remote set-url composer 'https://github.com/composer/composer'");
$expectedFirstGitUpdateCommand = $this->winCompat("(git remote set-url composer '".(Platform::isWindows() ? 'C:\\\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer '".(Platform::isWindows() ? 'C:\\\\' : '/')."'");
$expectedSecondGitUpdateCommand = $this->winCompat("(git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)) && git remote set-url composer 'https://github.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())

View File

@ -0,0 +1,77 @@
--TEST--
Partial update from lock file with root aliases should work
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "a/dep", "version": "1.0.0", "require": { "c/aliased": "2.0.0" } },
{ "name": "b/dep", "version": "1.0.0", "require": { "c/aliased": "1.0.0" } },
{ "name": "c/aliased", "version": "1.0.0" },
{ "name": "c/aliased", "version": "2.0.0" }
]
}
],
"require": {
"a/dep": "*",
"b/dep": "*",
"c/aliased": "1.0.0 as 2.0.0"
}
}
--LOCK--
{
"packages": [
{ "name": "a/dep", "version": "1.0.0", "require": { "c/aliased": "2.0.0" } },
{ "name": "b/dep", "version": "1.0.0", "require": { "c/aliased": "1.0.0" } },
{ "name": "c/aliased", "version": "1.0.0" }
],
"packages-dev": [],
"aliases": [
{
"package": "c/aliased",
"version": "1.0.0.0",
"alias": "2.0.0",
"alias_normalized": "2.0.0.0"
}
],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--INSTALLED--
[
{ "name": "a/dep", "version": "1.0.0", "require": { "c/aliased": "2.0.0" } },
{ "name": "b/dep", "version": "1.0.0", "require": { "c/aliased": "1.0.0" } },
{ "name": "c/aliased", "version": "1.0.0" }
]
--RUN--
update --lock
--EXPECT-LOCK--
{
"packages": [
{ "name": "a/dep", "version": "1.0.0", "require": { "c/aliased": "2.0.0" }, "type": "library" },
{ "name": "b/dep", "version": "1.0.0", "require": { "c/aliased": "1.0.0" }, "type": "library" },
{ "name": "c/aliased", "version": "1.0.0", "type": "library" }
],
"packages-dev": [],
"aliases": [
{
"package": "c/aliased",
"version": "1.0.0.0",
"alias": "2.0.0",
"alias_normalized": "2.0.0.0"
}
],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--EXPECT--
Marking c/aliased (2.0.0) as installed, alias of c/aliased (1.0.0)

View File

@ -0,0 +1,59 @@
--TEST--
Packages found in a higher priority repository take precedence even if they are not found in the requested version case #2
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{
"name": "ruflin/elastica",
"version": "dev-outdated-branch"
}
]
},
{
"type": "package",
"package": [
{
"name": "friendsofsymfony/elastica-bundle",
"version": "dev-foobar-master",
"require": {
"ruflin/elastica": "2.*"
}
}
]
},
{
"type": "package",
"package": [
{
"name": "ruflin/elastica",
"version": "2.0.0"
},
{
"name": "ruflin/elastica",
"version": "2.0.1"
}
]
}
],
"require": {
"friendsofsymfony/elastica-bundle": "dev-foobar-master"
}
}
--RUN--
update
--EXPECT-OUTPUT--
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires friendsofsymfony/elastica-bundle dev-foobar-master -> satisfiable by friendsofsymfony/elastica-bundle[dev-foobar-master].
- friendsofsymfony/elastica-bundle dev-foobar-master requires ruflin/elastica 2.* -> satisfiable by ruflin/elastica[2.0.0, 2.0.1] from package repo (defining 2 packages) but ruflin/elastica[dev-outdated-branch] from package repo (defining 1 package) has higher repository priority. The packages with higher priority do not match your constraint and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.
--EXPECT--
--EXPECT-EXIT-CODE--
2

View File

@ -0,0 +1,35 @@
--TEST--
Packages found in a higher priority repository take precedence even if they are not found in the requested version case #3
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "foo/a", "version": "2.0.0-dev" }
]
},
{
"type": "package",
"package": [
{ "name": "foo/a", "version": "2.0.0" }
]
}
],
"require": {
"foo/a": "2.*"
}
}
--RUN--
update
--EXPECT-OUTPUT--
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires foo/a 2.*, it is satisfiable by foo/a[2.0.0] from package repo (defining 1 package) but foo/a[2.0.0-dev] from package repo (defining 1 package) has higher repository priority. The packages with higher priority do not match your minimum-stability and are therefore not installable. See https://getcomposer.org/repoprio for details and assistance.
--EXPECT--
--EXPECT-EXIT-CODE--
2

View File

@ -0,0 +1,99 @@
<?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
);
}
}

View File

@ -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\Platform;
use Composer\Platform\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));
}
}

View File

@ -36,6 +36,7 @@ class ArtifactRepositoryTest extends TestCase
'vendor1/package2-4.3.2',
'vendor3/package1-5.4.3',
'test/jsonInRoot-1.0.0',
'test/jsonInRootTarFile-1.0.0',
'test/jsonInFirstLevel-1.0.0',
//The files not-an-artifact.zip and jsonSecondLevel are not valid
//artifacts and do not get detected.
@ -52,6 +53,13 @@ class ArtifactRepositoryTest extends TestCase
sort($foundPackages);
$this->assertSame($expectedPackages, $foundPackages);
$tarPackage = array_filter($repo->getPackages(), function (BasePackage $package) {
return $package->getPrettyName() === 'test/jsonInRootTarFile';
});
$this->assertCount(1, $tarPackage);
$tarPackage = array_pop($tarPackage);
$this->assertSame('tar', $tarPackage->getDistType());
}
public function testAbsoluteRepoUrlCreatesAbsoluteUrlPackages()

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,71 @@
<?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\Tar;
use Composer\Test\TestCase;
/**
* @author Wissem Riahi <wissemr@gmail.com>
*/
class TarTest extends TestCase
{
public function testReturnsNullifTheTarIsNotFound()
{
$result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/invalid.zip');
$this->assertNull($result);
}
public function testReturnsNullIfTheTarIsEmpty()
{
$result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/empty.tar.gz');
$this->assertNull($result);
}
/**
* @expectedException \RuntimeException
*/
public function testThrowsExceptionIfTheTarHasNoComposerJson()
{
Tar::getComposerJson(__DIR__.'/Fixtures/Tar/nojson.tar.gz');
}
/**
* @expectedException \RuntimeException
*/
public function testThrowsExceptionIfTheComposerJsonIsInASubSubfolder()
{
Tar::getComposerJson(__DIR__.'/Fixtures/Tar/subfolders.tar.gz');
}
public function testReturnsComposerJsonInTarRoot()
{
$result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/root.tar.gz');
$this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result);
}
public function testReturnsComposerJsonInFirstFolder()
{
$result = Tar::getComposerJson(__DIR__.'/Fixtures/Tar/folder.tar.gz');
$this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result);
}
/**
* @expectedException \RuntimeException
*/
public function testMultipleTopLevelDirsIsInvalid()
{
Tar::getComposerJson(__DIR__.'/Fixtures/Tar/multiple.tar.gz');
}
}

View File

@ -20,7 +20,7 @@ use Composer\Test\TestCase;
*/
class ZipTest extends TestCase
{
public function testThrowsExceptionIfZipExcentionIsNotLoaded()
public function testThrowsExceptionIfZipExtensionIsNotLoaded()
{
if (extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is loaded.');
@ -55,28 +55,30 @@ class ZipTest extends TestCase
$this->assertNull($result);
}
public function testReturnsNullIfTheZipHasNoComposerJson()
/**
* @expectedException \RuntimeException
*/
public function testThrowsExceptionIfTheZipHasNoComposerJson()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/nojson.zip');
$this->assertNull($result);
Zip::getComposerJson(__DIR__.'/Fixtures/Zip/nojson.zip');
}
public function testReturnsNullIfTheComposerJsonIsInASubSubfolder()
/**
* @expectedException \RuntimeException
*/
public function testThrowsExceptionIfTheComposerJsonIsInASubSubfolder()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolder.zip');
$this->assertNull($result);
Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolders.zip');
}
public function testReturnsComposerJsonInZipRoot()
@ -99,19 +101,44 @@ class ZipTest extends TestCase
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/folder.zip');
$this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result);
}
public function testReturnsRootComposerJsonAndSkipsSubfolders()
/**
* @expectedException \RuntimeException
*/
public function testMultipleTopLevelDirsIsInvalid()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip');
Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip');
}
public function testReturnsComposerJsonFromFirstSubfolder()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/single-sub.zip');
$this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result);
}
/**
* @expectedException \RuntimeException
*/
public function testThrowsExceptionIfMultipleComposerInSubFoldersWereFound()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple_subfolders.zip');
}
}