diff --git a/.gitattributes b/.gitattributes index 32378b23e..51b431136 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,3 +10,8 @@ # Exclude non-essential files from dist /tests export-ignore +.github export-ignore +.php_cs export-ignore +.travis.yml export-ignore +appveyor.yml export-ignore +phpunit.xml.dist export-ignore diff --git a/CHANGELOG.md b/CHANGELOG.md index 36ceddfc9..2e6075148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +### [1.8.6] 2019-06-11 + + * Fixed handling of backslash-escapes handling in composer.json when using the require command + * Fixed create-project not following classmap-authoritative and apcu-autoloader config values + * Fixed HHVM version warning showing up in some cases when it was not in use + +### [1.8.5] 2019-04-09 + + * HHVM 4.0 is no longer compatible with Composer. Please use PHP instead going forward. + * Added forward compatibility with upcoming 2.0 changes + * Fixed support for PHP 7.3-style heredoc/nowdoc syntax changes in autoload generation + * Fixed require command usage when combined with --ignore-platform-reqs + * Fixed and cleaned up various Windows junctions handling issues + ### [1.8.4] 2019-02-11 * Fixed long standing solver bug leading to odd solving issues in edge cases, see #7946 @@ -737,6 +751,8 @@ * Initial release +[1.8.6]: https://github.com/composer/composer/compare/1.8.5...1.8.6 +[1.8.5]: https://github.com/composer/composer/compare/1.8.4...1.8.5 [1.8.4]: https://github.com/composer/composer/compare/1.8.3...1.8.4 [1.8.3]: https://github.com/composer/composer/compare/1.8.2...1.8.3 [1.8.2]: https://github.com/composer/composer/compare/1.8.1...1.8.2 diff --git a/composer.lock b/composer.lock index eb15db515..f0c3f0086 100644 --- a/composer.lock +++ b/composer.lock @@ -126,24 +126,23 @@ }, { "name": "composer/spdx-licenses", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2" + "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", + "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" }, "type": "library", "extra": { @@ -183,20 +182,20 @@ "spdx", "validator" ], - "time": "2018-11-01T09:45:54+00:00" + "time": "2019-03-26T10:23:26+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "d17708133b6c276d6e42ef887a877866b909d892" + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892", - "reference": "d17708133b6c276d6e42ef887a877866b909d892", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", "shasum": "" }, "require": { @@ -227,7 +226,7 @@ "Xdebug", "performance" ], - "time": "2019-01-28T20:25:53+00:00" + "time": "2019-05-27T17:52:04+00:00" }, { "name": "justinrainbow/json-schema", @@ -481,7 +480,7 @@ }, { "name": "symfony/console", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/console.git", @@ -542,7 +541,7 @@ }, { "name": "symfony/debug", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -599,7 +598,7 @@ }, { "name": "symfony/filesystem", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -649,7 +648,7 @@ }, { "name": "symfony/finder", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -698,16 +697,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { @@ -719,7 +718,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -752,20 +751,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -777,7 +776,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -811,11 +810,11 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/process", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -1780,7 +1779,7 @@ }, { "name": "symfony/yaml", - "version": "v2.8.49", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", diff --git a/doc/03-cli.md b/doc/03-cli.md index 54cf7642f..c04522733 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -920,6 +920,10 @@ If you use a proxy but it does not support the request_fulluri flag for HTTPS requests, then you should set this env var to `false` or `0` to prevent Composer from setting the request_fulluri option. +### COMPOSER_SELF_UPDATE_TARGET + +If set, makes the self-update command write the new Composer phar file into that path instead of overwriting itself. Useful for updating Composer on read-only filesystem. + ### no_proxy or NO_PROXY If you are behind a proxy and would like to disable it for certain domains, you diff --git a/doc/06-config.md b/doc/06-config.md index 87d73f8a1..f3afc4eb1 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -9,6 +9,20 @@ Defaults to `300`. The duration processes like git clones can run before Composer assumes they died out. You may need to make this higher if you have a slow connection or huge vendors. +To disable the process timeout on a custom command under `scripts`, a static +helper is available: + +```json +{ + "scripts": { + "test": [ + "Composer\\Config::disableProcessTimeout", + "phpunit" + ] + } +} +``` + ## use-include-path Defaults to `false`. If `true`, the Composer autoloader will also look for classes diff --git a/doc/articles/plugins.md b/doc/articles/plugins.md index da20193f6..39974e469 100644 --- a/doc/articles/plugins.md +++ b/doc/articles/plugins.md @@ -261,6 +261,11 @@ Now the `custom-plugin-command` is available alongside Composer commands. > _Composer commands are based on the [Symfony Console Component][10]._ +## Running plugins manually + +Plugins for an event can be run manually by the `run-script` command. This works the same way as +[running scripts manually](scripts.md#running-scripts-manually). + ## Using Plugins Plugin packages are automatically loaded as soon as they are installed and will diff --git a/doc/articles/scripts.md b/doc/articles/scripts.md index 17c83c373..fbc5d0417 100644 --- a/doc/articles/scripts.md +++ b/doc/articles/scripts.md @@ -189,7 +189,7 @@ composer run-script [--dev] [--no-dev] script ``` For example `composer run-script post-install-cmd` will run any -**post-install-cmd** scripts that have been defined. +**post-install-cmd** scripts and [plugins](plugins.md) that have been defined. You can also give additional arguments to the script handler by appending `--` followed by the handler arguments. e.g. @@ -221,6 +221,56 @@ to the `phpunit` script. > are easily accessible. In this example no matter if the `phpunit` binary is > actually in `vendor/bin/phpunit` or `bin/phpunit` it will be found and executed. +Although Composer is not intended to manage long-running processes and other +such aspects of PHP projects, it can sometimes be handy to disable the process +timeout on custom commands. This timeout defaults to 300 seconds and can be +overridden in a variety of ways depending on the desired effect: + +- disable it for all commands using the config key `process-timeout`, +- disable it for the current or future invocations of composer using the + environment variable `COMPOSER_PROCESS_TIMEOUT`, +- for a specific invocation using the `--timeout` flag of the `run-script` command, +- using a static helper for specific scripts. + +To disable the timeout for specific scripts with the static helper directly in +composer.json: + +```json +{ + "scripts": { + "test": [ + "Composer\\Config::disableProcessTimeout", + "phpunit" + ] + } +} +``` + +To disable the timeout for every script on a given project, you can use the +composer.json configuration: + +```json +{ + "config": { + "process-timeout": 0 + } +} +``` + +It's also possible to set the global environment variable to disable the timeout +of all following scripts in the current terminal environment: + +``` +export COMPOSER_PROCESS_TIMEOUT=0 +``` + +To disable the timeout of a single script call, you must use the `run-script` composer +command and specify the `--timeout` parameter: + +``` +composer run-script --timeout=0 test +``` + ## Referencing scripts To enable script re-use and avoid duplicates, you can call a script from another diff --git a/doc/articles/versions.md b/doc/articles/versions.md index c1fb25551..e3da6c8ec 100644 --- a/doc/articles/versions.md +++ b/doc/articles/versions.md @@ -32,7 +32,7 @@ repository:* v1 v2 my-feature -nother-feature +another-feature ~/my-library$ git tag v1.0 diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 7ea1a3444..d970ca5b1 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -21,6 +21,7 @@ use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Util\Filesystem; use Composer\Script\ScriptEvents; +use Composer\Util\PackageSorter; /** * @author Igor Wiedler @@ -545,7 +546,7 @@ EOF; } } - if (preg_match('/\.phar.+$/', $path)) { + if (strpos($path, '.phar') !== false) { $baseDir = "'phar://' . " . $baseDir; } @@ -769,10 +770,14 @@ HEADER; $filesystem = new Filesystem(); $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; + $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/"; $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; + $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/"; $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1); + $absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1); $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1); + $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1); $initializer = ''; $prefix = "\0Composer\Autoload\ClassLoader\0"; @@ -795,9 +800,15 @@ HEADER; // See https://bugs.php.net/68057 $staticPhpVersion = 70000; } - $value = var_export($value, true); - $value = str_replace($absoluteVendorPathCode, $vendorPathCode, $value); - $value = str_replace($absoluteAppBaseDirCode, $appBaseDirCode, $value); + $value = strtr( + var_export($value, true), + array( + $absoluteVendorPathCode => $vendorPathCode, + $absoluteVendorPharPathCode => $vendorPharPathCode, + $absoluteAppBaseDirCode => $appBaseDirCode, + $absoluteAppBaseDirPharCode => $appBaseDirPharCode, + ) + ); $value = ltrim(preg_replace('/^ */m', ' $0$0', $value)); $file .= sprintf(" public static $%s = %s;\n\n", $prop, $value); @@ -963,80 +974,21 @@ INITIALIZER; { $packages = array(); $paths = array(); - $usageList = array(); foreach ($packageMap as $item) { list($package, $path) = $item; $name = $package->getName(); $packages[$name] = $package; $paths[$name] = $path; - - foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) { - $target = $link->getTarget(); - $usageList[$target][] = $name; - } } - $computing = array(); - $computed = array(); - $computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) { - // reusing computed importance - if (isset($computed[$name])) { - return $computed[$name]; - } + $sortedPackages = PackageSorter::sortPackages($packages); - // canceling circular dependency - if (isset($computing[$name])) { - return 0; - } - - $computing[$name] = true; - $weight = 0; - - if (isset($usageList[$name])) { - foreach ($usageList[$name] as $user) { - $weight -= 1 - $computeImportance($user); - } - } - - unset($computing[$name]); - $computed[$name] = $weight; - - return $weight; - }; - - $weightList = array(); - - foreach ($packages as $name => $package) { - $weight = $computeImportance($name); - $weightList[$name] = $weight; - } - - $stable_sort = function (&$array) { - static $transform, $restore; - - $i = 0; - - if (!$transform) { - $transform = function (&$v, $k) use (&$i) { - $v = array($v, ++$i, $k, $v); - }; - - $restore = function (&$v, $k) { - $v = $v[3]; - }; - } - - array_walk($array, $transform); - asort($array); - array_walk($array, $restore); - }; - - $stable_sort($weightList); $sortedPackageMap = array(); - foreach (array_keys($weightList) as $name) { + foreach ($sortedPackages as $package) { + $name = $package->getName(); $sortedPackageMap[] = array($packages[$name], $paths[$name]); } diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index 5d937433b..1ecf96bfe 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -162,7 +162,7 @@ class ClassMapGenerator } // strip heredocs/nowdocs - $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); + $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents); // strip strings $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); // strip leading non-php code if needed diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index be5ad948d..a718bfa0b 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -184,7 +184,9 @@ EOT ->setRunScripts(!$noScripts) ->setIgnorePlatformRequirements($ignorePlatformReqs) ->setSuggestedPackagesReporter($this->suggestedPackagesReporter) - ->setOptimizeAutoloader($config->get('optimize-autoloader')); + ->setOptimizeAutoloader($config->get('optimize-autoloader')) + ->setClassMapAuthoritative($config->get('classmap-authoritative')) + ->setApcuAutoloader($config->get('apcu-autoloader')); if ($disablePlugins) { $installer->disablePlugins(); diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index c34e7f4f9..8a06d46ad 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -25,6 +25,7 @@ use Composer\Plugin\CommandEvent; use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; +use Composer\IO\IOInterface; /** * @author Jérémy Romey @@ -160,15 +161,26 @@ EOT if ($input->getOption('no-update')) { return 0; } + + try { + return $this->doUpdate($input, $output, $io, $requirements); + } catch (\Exception $e) { + $this->revertComposerFile(false); + throw $e; + } + } + + private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements) + { + // Update packages + $this->resetComposer(); + $composer = $this->getComposer(true, $input->getOption('no-plugins')); + $updateDevMode = !$input->getOption('update-no-dev'); $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader'); $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative'); $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader'); - // Update packages - $this->resetComposer(); - $composer = $this->getComposer(true, $input->getOption('no-plugins')); - $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); diff --git a/src/Composer/Config.php b/src/Composer/Config.php index 4d8199ccc..7abca7dfa 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -16,6 +16,7 @@ use Composer\Config\ConfigSourceInterface; use Composer\Downloader\TransportException; use Composer\IO\IOInterface; use Composer\Util\Platform; +use Composer\Util\ProcessExecutor; /** * @author Jordi Boggiano @@ -459,4 +460,20 @@ class Config } } } + + /** + * Used by long-running custom scripts in composer.json + * + * "scripts": { + * "watch": [ + * "Composer\\Config::disableProcessTimeout", + * "vendor/bin/long-running-script --watch" + * ] + * } + */ + public static function disableProcessTimeout() + { + // Override global timeout set earlier by environment or config + ProcessExecutor::setTimeout(0); + } } diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index 7591f2bd5..6b1eba0e6 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -81,8 +81,11 @@ class Problem $job = $reason['job']; - if (isset($job['constraint'])) { - $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); + $packageName = $job['packageName']; + $constraint = $job['constraint']; + + if (isset($constraint)) { + $packages = $this->pool->whatProvides($packageName, $constraint); } else { $packages = array(); } @@ -90,9 +93,9 @@ class Problem if ($job && $job['cmd'] === 'install' && empty($packages)) { // handle php/hhvm - if ($job['packageName'] === 'php' || $job['packageName'] === 'php-64bit' || $job['packageName'] === 'hhvm') { + if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') { $version = phpversion(); - $available = $this->pool->whatProvides($job['packageName']); + $available = $this->pool->whatProvides($packageName); if (count($available)) { $firstAvailable = reset($available); @@ -103,13 +106,13 @@ class Problem } } - $msg = "\n - This package requires ".$job['packageName'].$this->constraintToText($job['constraint']).' but '; + $msg = "\n - This package requires ".$packageName.$this->constraintToText($constraint).' but '; - if (defined('HHVM_VERSION') || count($available)) { + if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) { return $msg . 'your HHVM version does not satisfy that requirement.'; } - if ($job['packageName'] === 'hhvm') { + if ($packageName === 'hhvm') { return $msg . 'you are running this with PHP and not HHVM.'; } @@ -117,43 +120,43 @@ class Problem } // handle php extensions - if (0 === stripos($job['packageName'], 'ext-')) { - if (false !== strpos($job['packageName'], ' ')) { - return "\n - The requested PHP extension ".$job['packageName'].' should be required as '.str_replace(' ', '-', $job['packageName']).'.'; + if (0 === stripos($packageName, 'ext-')) { + if (false !== strpos($packageName, ' ')) { + return "\n - The requested PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.'; } - $ext = substr($job['packageName'], 4); + $ext = substr($packageName, 4); $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system'; - return "\n - The requested PHP extension ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.'; + return "\n - The requested PHP extension ".$packageName.$this->constraintToText($constraint).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.'; } // handle linked libs - if (0 === stripos($job['packageName'], 'lib-')) { - if (strtolower($job['packageName']) === 'lib-icu') { + if (0 === stripos($packageName, 'lib-')) { + if (strtolower($packageName) === 'lib-icu') { $error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.'; - return "\n - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' '.$error; + return "\n - The requested linked library ".$packageName.$this->constraintToText($constraint).' '.$error; } - return "\n - The requested linked library ".$job['packageName'].$this->constraintToText($job['constraint']).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.'; + return "\n - The requested linked library ".$packageName.$this->constraintToText($constraint).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.'; } - if (!preg_match('{^[A-Za-z0-9_./-]+$}', $job['packageName'])) { - $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $job['packageName']); + if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) { + $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName); - return "\n - The requested package ".$job['packageName'].' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.'; + return "\n - The requested package ".$packageName.' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.'; } - if ($providers = $this->pool->whatProvides($job['packageName'], $job['constraint'], true, true)) { - return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.'; + if ($providers = $this->pool->whatProvides($packageName, $constraint, true, true)) { + return "\n - The requested package ".$packageName.$this->constraintToText($constraint).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.'; } - if ($providers = $this->pool->whatProvides($job['packageName'], null, true, true)) { - return "\n - The requested package ".$job['packageName'].$this->constraintToText($job['constraint']).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.'; + if ($providers = $this->pool->whatProvides($packageName, null, true, true)) { + return "\n - The requested package ".$packageName.$this->constraintToText($constraint).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.'; } - return "\n - The requested package ".$job['packageName'].' could not be found in any version, there may be a typo in the package name.'; + return "\n - The requested package ".$packageName.' could not be found in any version, there may be a typo in the package name.'; } } @@ -202,27 +205,29 @@ class Problem */ protected function jobToText($job) { + $packageName = $job['packageName']; + $constraint = $job['constraint']; switch ($job['cmd']) { case 'install': - $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); + $packages = $this->pool->whatProvides($packageName, $constraint); if (!$packages) { - return 'No package found to satisfy install request for '.$job['packageName'].$this->constraintToText($job['constraint']); + return 'No package found to satisfy install request for '.$packageName.$this->constraintToText($constraint); } - return 'Installation request for '.$job['packageName'].$this->constraintToText($job['constraint']).' -> satisfiable by '.$this->getPackageList($packages).'.'; + return 'Installation request for '.$packageName.$this->constraintToText($constraint).' -> satisfiable by '.$this->getPackageList($packages).'.'; case 'update': - return 'Update request for '.$job['packageName'].$this->constraintToText($job['constraint']).'.'; + return 'Update request for '.$packageName.$this->constraintToText($constraint).'.'; case 'remove': - return 'Removal request for '.$job['packageName'].$this->constraintToText($job['constraint']).''; + return 'Removal request for '.$packageName.$this->constraintToText($constraint).''; } - if (isset($job['constraint'])) { - $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']); + if (isset($constraint)) { + $packages = $this->pool->whatProvides($packageName, $constraint); } else { $packages = array(); } - return 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.$this->getPackageList($packages).'])'; + return 'Job(cmd='.$job['cmd'].', target='.$packageName.', packages=['.$this->getPackageList($packages).'])'; } protected function getPackageList($packages) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index fc877f18e..27607978f 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -477,6 +477,8 @@ class Installer $solver = new Solver($policy, $pool, $installedRepo, $this->io); try { $operations = $solver->solve($request, $this->ignorePlatformReqs); + $ruleSetSize = $solver->getRuleSetSize(); + $solver = null; } catch (SolverProblemsException $e) { $this->io->writeError('Your requirements could not be resolved to an installable set of packages.', true, IOInterface::QUIET); $this->io->writeError($e->getMessage()); @@ -493,7 +495,7 @@ class Installer $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $repositorySet, $installedRepo, $request, $operations); $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE); - $this->io->writeError("Analyzed ".$solver->getRuleSetSize()." rules to resolve dependencies", true, IOInterface::VERBOSE); + $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE); // execute operations if (!$operations) { diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index 40c0c09a2..8fe6a9f0a 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -22,7 +22,7 @@ class JsonManipulator private static $DEFINES = '(?(DEFINE) (? -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? ) (? true | false | null ) - (? " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-f]{4} )* " ) + (? " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " ) (? \[ (?: (?&json) \s* (?: , (?&json) \s* )* )? \s* \] ) (? \s* (?&string) \s* : (?&json) \s* ) (? \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} ) diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 09ed4fb9b..89f197856 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -401,4 +401,14 @@ class AliasPackage extends BasePackage implements CompletePackageInterface { return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')'; } + + public function setDistUrl($url) + { + return $this->aliasOf->setDistUrl($url); + } + + public function setDistType($type) + { + return $this->aliasOf->setDistType($type); + } } diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index 405e567f0..43f23236b 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -49,6 +49,10 @@ class ValidatingArrayLoader implements LoaderInterface $this->warnings = array(); $this->config = $config; + if ($err = self::hasPackageNamingError($config['name'])) { + $this->warnings[] = 'Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.'; + } + if ($this->strictName) { $this->validateRegex('name', '[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', true); } else { @@ -195,7 +199,9 @@ class ValidatingArrayLoader implements LoaderInterface foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { if ($this->validateArray($linkType) && isset($this->config[$linkType])) { foreach ($this->config[$linkType] as $package => $constraint) { - if (!preg_match('{^[A-Za-z0-9_./-]+$}', $package)) { + if ($err = self::hasPackageNamingError($package, true)) { + $this->warnings[] = 'Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.'; + } elseif (!preg_match('{^[A-Za-z0-9_./-]+$}', $package)) { $this->warnings[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]'; } if (!is_string($constraint)) { diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 30488e89f..25a2e9bfe 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -358,4 +358,32 @@ interface PackageInterface * @return array */ public function getTransportOptions(); + + /** + * @param string $reference + * + * @return void + */ + public function setSourceReference($reference); + + /** + * @param string $url + * + * @return void + */ + public function setDistUrl($url); + + /** + * @param string $type + * + * @return void + */ + public function setDistType($type); + + /** + * @param string $reference + * + * @return void + */ + public function setDistReference($reference); } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 786d846c5..176207b41 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -15,15 +15,16 @@ namespace Composer\Plugin; use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\IO\IOInterface; +use Composer\Package\CompletePackage; use Composer\Package\Package; use Composer\Package\Version\VersionParser; use Composer\Repository\RepositoryInterface; -use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; use Composer\Package\Link; use Composer\Repository\RepositorySet; use Composer\Semver\Constraint\Constraint; use Composer\Plugin\Capability\Capability; +use Composer\Util\PackageSorter; /** * Plugin manager @@ -253,8 +254,10 @@ class PluginManager */ private function loadRepository(RepositoryInterface $repo) { - foreach ($repo->getPackages() as $package) { /** @var PackageInterface $package */ - if ($package instanceof AliasPackage) { + $packages = $repo->getPackages(); + $sortedPackages = array_reverse(PackageSorter::sortPackages($packages)); + foreach ($sortedPackages as $package) { + if (!($package instanceof CompletePackage)) { continue; } if ('composer-plugin' === $package->getType()) { diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 0cdb74bd6..a53b37c33 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -803,7 +803,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito private function canonicalizeUrl($url) { if ('/' === $url[0]) { - return preg_replace('{(https?://[^/]+).*}i', '$1' . $url, $this->url); + if (preg_match('{^[^:]++://[^/]*+}', $this->url, $matches)) { + return $matches[0] . $url; + } + + return $this->url; } return $url; diff --git a/src/Composer/Repository/PlatformRepository.php b/src/Composer/Repository/PlatformRepository.php index 3c21139e8..3c7993511 100644 --- a/src/Composer/Repository/PlatformRepository.php +++ b/src/Composer/Repository/PlatformRepository.php @@ -166,8 +166,14 @@ class PlatformRepository extends ArrayRepository case 'imagick': $imagick = new \Imagick(); $imageMagickVersion = $imagick->getVersion(); - preg_match('/^ImageMagick ([\d.]+)-(\d+)/', $imageMagickVersion['versionString'], $matches); - $prettyVersion = "{$matches[1]}.{$matches[2]}"; + // 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]; + } break; case 'libxml': diff --git a/src/Composer/Repository/Vcs/BitbucketDriver.php b/src/Composer/Repository/Vcs/BitbucketDriver.php index 6405d3bb3..5b0f26639 100644 --- a/src/Composer/Repository/Vcs/BitbucketDriver.php +++ b/src/Composer/Repository/Vcs/BitbucketDriver.php @@ -219,6 +219,13 @@ abstract class BitbucketDriver extends VcsDriver return $this->fallbackDriver->getChangeDate($identifier); } + if (strpos($identifier, '/') !== false) { + $branches = $this->getBranches(); + if (isset($branches[$identifier])) { + $identifier = $branches[$identifier]; + } + } + $resource = sprintf( 'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date', $this->owner, diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 373b24af1..3812e7a66 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -66,8 +66,9 @@ class HgDriver extends VcsDriver // clean up directory and do a fresh clone into it $fs->removeDirectory($this->repoDir); - $command = function ($url) { - return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($this->repoDir)); + $repoDir = $this->repoDir; + $command = function ($url) use ($repoDir) { + return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir)); }; $hgUtils->runCommand($command, $this->url, $this->repoDir); diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index cebbd2a5b..420f866f0 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -32,7 +32,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt { protected $url; protected $packageName; - protected $verbose; + protected $isVerbose; + protected $isVeryVerbose; protected $io; protected $config; protected $versionParser; @@ -68,7 +69,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->url = $repoConfig['url']; $this->io = $io; $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs'; - $this->verbose = $io->isVeryVerbose(); + $this->isVerbose = $io->isVerbose(); + $this->isVeryVerbose = $io->isVeryVerbose(); $this->config = $config; $this->repoConfig = $repoConfig; $this->versionCache = $versionCache; @@ -133,7 +135,8 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt { parent::initialize(); - $verbose = $this->verbose; + $isVerbose = $this->isVerbose; + $isVeryVerbose = $this->isVeryVerbose; $driver = $this->getDriver(); if (!$driver) { @@ -151,23 +154,23 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $this->packageName = !empty($data['name']) ? $data['name'] : null; } } catch (\Exception $e) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().''); } } foreach ($driver->getTags() as $tag => $identifier) { $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $tag . ')'; - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError($msg); - } else { + } elseif ($isVerbose) { $this->io->overwriteError($msg, false); } // strip the release- prefix from tags if present $tag = str_replace('release-', '', $tag); - $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $verbose); + $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose); if ($cachedPackage) { $this->addPackage($cachedPackage); @@ -179,7 +182,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt } if (!$parsedTag = $this->validateTag($tag)) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', invalid tag name'); } continue; @@ -187,7 +190,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt try { if (!$data = $driver->getComposerInformation($identifier)) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', no composer file'); } $this->emptyReferences[] = $identifier; @@ -209,7 +212,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt // broken package, version doesn't match tag if ($data['version_normalized'] !== $parsedTag) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'); } continue; @@ -217,13 +220,13 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $tagPackageName = isset($data['name']) ? $data['name'] : $this->packageName; if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally'); } continue; } - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')'); } @@ -232,35 +235,35 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($e instanceof TransportException && $e->getCode() === 404) { $this->emptyReferences[] = $identifier; } - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).''); } continue; } } - if (!$verbose) { + if (!$isVeryVerbose) { $this->io->overwriteError('', false); } $branches = $driver->getBranches(); foreach ($branches as $branch => $identifier) { $msg = 'Reading composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $branch . ')'; - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError($msg); - } else { + } elseif ($isVerbose) { $this->io->overwriteError($msg, false); } if ($branch === 'trunk' && isset($branches['master'])) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', can not parse both master and trunk branches as they both resolve to 9999999-dev internally'); } continue; } if (!$parsedBranch = $this->validateBranch($branch)) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', invalid name'); } continue; @@ -274,7 +277,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $version = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch); } - $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $verbose); + $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose); if ($cachedPackage) { $this->addPackage($cachedPackage); @@ -287,7 +290,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt try { if (!$data = $driver->getComposerInformation($identifier)) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file'); } $this->emptyReferences[] = $identifier; @@ -298,7 +301,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $data['version'] = $version; $data['version_normalized'] = $parsedBranch; - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')'); } @@ -312,12 +315,12 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($e->getCode() === 404) { $this->emptyReferences[] = $identifier; } - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped branch '.$branch.', no composer file was found'); } continue; } catch (\Exception $e) { - if (!$verbose) { + if (!$isVeryVerbose) { $this->io->writeError(''); } $this->branchErrorOccurred = true; @@ -328,7 +331,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt } $driver->cleanup(); - if (!$verbose) { + if (!$isVeryVerbose) { $this->io->overwriteError('', false); } @@ -373,7 +376,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt return false; } - private function getCachedPackageVersion($version, $identifier, $verbose) + private function getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose) { if (!$this->versionCache) { return; @@ -381,7 +384,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier); if ($cachedPackage === false) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped '.$version.', no composer file (cached from ref '.$identifier.')'); } @@ -390,14 +393,14 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt if ($cachedPackage) { $msg = 'Found cached composer.json of ' . ($this->packageName ?: $this->url) . ' (' . $version . ')'; - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError($msg); - } else { + } elseif ($isVerbose) { $this->io->overwriteError($msg, false); } if ($existingPackage = $this->findPackage($cachedPackage['name'], $cachedPackage['version_normalized'])) { - if ($verbose) { + if ($isVeryVerbose) { $this->io->writeError('Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally'); } $cachedPackage = null; diff --git a/src/Composer/Util/PackageSorter.php b/src/Composer/Util/PackageSorter.php new file mode 100644 index 000000000..8d8c9a06c --- /dev/null +++ b/src/Composer/Util/PackageSorter.php @@ -0,0 +1,92 @@ +getRequires(), $package->getDevRequires()) as $link) { /** @var Link $link */ + $target = $link->getTarget(); + $usageList[$target][] = $package->getName(); + } + } + $computing = array(); + $computed = array(); + $computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed, $usageList) { + // reusing computed importance + if (isset($computed[$name])) { + return $computed[$name]; + } + + // canceling circular dependency + if (isset($computing[$name])) { + return 0; + } + + $computing[$name] = true; + $weight = 0; + + if (isset($usageList[$name])) { + foreach ($usageList[$name] as $user) { + $weight -= 1 - $computeImportance($user); + } + } + + unset($computing[$name]); + $computed[$name] = $weight; + + return $weight; + }; + + $weightList = array(); + + foreach ($packages as $name => $package) { + $weight = $computeImportance($name); + $weightList[$name] = $weight; + } + + $stable_sort = function (&$array) { + static $transform, $restore; + + $i = 0; + + if (!$transform) { + $transform = function (&$v, $k) use (&$i) { + $v = array($v, ++$i, $k, $v); + }; + + $restore = function (&$v) { + $v = $v[3]; + }; + } + + array_walk($array, $transform); + asort($array); + array_walk($array, $restore); + }; + + $stable_sort($weightList); + + $sortedPackages = array(); + + foreach (array_keys($weightList) as $name) { + $sortedPackages[] = $packages[$name]; + } + return $sortedPackages; + } +} diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index f5e1ef610..d72a02981 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -51,6 +51,7 @@ class ProcessExecutor return '://'.$m['user'].':***@'; }, $command); + $safeCommand = preg_replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand); $this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand); } diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index c1605bf97..84ac16df7 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -486,6 +486,47 @@ class AutoloadGeneratorTest extends TestCase $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } + public function testPharAutoload() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + )); + + $package->setAutoload(array( + 'psr-0' => array( + 'Foo' => 'foo.phar', + 'Bar' => 'dir/bar.phar/src', + ), + 'psr-4' => array( + 'Baz\\' => 'baz.phar', + 'Qux\\' => 'dir/qux.phar/src', + ), + )); + + $vendorPackage = new Package('a/a', '1.0', '1.0'); + $vendorPackage->setAutoload(array( + 'psr-0' => array( + 'Lorem' => 'lorem.phar', + 'Ipsum' => 'dir/ipsum.phar/src', + ), + 'psr-4' => array( + 'Dolor\\' => 'dolor.phar', + 'Sit\\' => 'dir/sit.phar/src', + ), + )); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue(array($vendorPackage))); + + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, 'Phar'); + + $this->assertAutoloadFiles('phar', $this->vendorDir . '/composer'); + $this->assertAutoloadFiles('phar_psr4', $this->vendorDir . '/composer', 'psr4'); + $this->assertAutoloadFiles('phar_static', $this->vendorDir . '/composer', 'static'); + } + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_phar.php b/tests/Composer/Test/Autoload/Fixtures/autoload_phar.php new file mode 100644 index 000000000..7654005f3 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_phar.php @@ -0,0 +1,13 @@ + array('phar://' . $vendorDir . '/a/a/lorem.phar'), + 'Ipsum' => array('phar://' . $vendorDir . '/a/a/dir/ipsum.phar/src'), + 'Foo' => array('phar://' . $baseDir . '/foo.phar'), + 'Bar' => array('phar://' . $baseDir . '/dir/bar.phar/src'), +); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_phar_psr4.php b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_psr4.php new file mode 100644 index 000000000..f6142a001 --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_psr4.php @@ -0,0 +1,13 @@ + array('phar://' . $vendorDir . '/a/a/dir/sit.phar/src'), + 'Qux\\' => array('phar://' . $baseDir . '/dir/qux.phar/src'), + 'Dolor\\' => array('phar://' . $vendorDir . '/a/a/dolor.phar'), + 'Baz\\' => array('phar://' . $baseDir . '/baz.phar'), +); diff --git a/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php new file mode 100644 index 000000000..486a5c0dc --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/autoload_phar_static.php @@ -0,0 +1,87 @@ + + array ( + 'Sit\\' => 4, + ), + 'Q' => + array ( + 'Qux\\' => 4, + ), + 'D' => + array ( + 'Dolor\\' => 6, + ), + 'B' => + array ( + 'Baz\\' => 4, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Sit\\' => + array ( + 0 => 'phar://' . __DIR__ . '/..' . '/a/a/dir/sit.phar/src', + ), + 'Qux\\' => + array ( + 0 => 'phar://' . __DIR__ . '/../..' . '/dir/qux.phar/src', + ), + 'Dolor\\' => + array ( + 0 => 'phar://' . __DIR__ . '/..' . '/a/a/dolor.phar', + ), + 'Baz\\' => + array ( + 0 => 'phar://' . __DIR__ . '/../..' . '/baz.phar', + ), + ); + + public static $prefixesPsr0 = array ( + 'L' => + array ( + 'Lorem' => + array ( + 0 => 'phar://' . __DIR__ . '/..' . '/a/a/lorem.phar', + ), + ), + 'I' => + array ( + 'Ipsum' => + array ( + 0 => 'phar://' . __DIR__ . '/..' . '/a/a/dir/ipsum.phar/src', + ), + ), + 'F' => + array ( + 'Foo' => + array ( + 0 => 'phar://' . __DIR__ . '/../..' . '/foo.phar', + ), + ), + 'B' => + array ( + 'Bar' => + array ( + 0 => 'phar://' . __DIR__ . '/../..' . '/dir/bar.phar/src', + ), + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitPhar::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitPhar::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInitPhar::$prefixesPsr0; + + }, null, ClassLoader::class); + } +} diff --git a/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php b/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php index 4c344089b..8944360ee 100644 --- a/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php +++ b/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php @@ -7,42 +7,81 @@ namespace Foo; */ class StripNoise { - public function test() + public function test_heredoc() { - return <<'; + } + + public function test_simple_string() + { + return 'class FailSimpleString {}'; } } diff --git a/tests/Composer/Test/Json/JsonManipulatorTest.php b/tests/Composer/Test/Json/JsonManipulatorTest.php index 05de454ca..d8bc7c200 100644 --- a/tests/Composer/Test/Json/JsonManipulatorTest.php +++ b/tests/Composer/Test/Json/JsonManipulatorTest.php @@ -2374,6 +2374,26 @@ class JsonManipulatorTest extends TestCase "package/a": "*" } } +', $manipulator->getContents()); + } + + public function testEscapedUnicodeDoesNotCauseBacktrackLimitErrorGithubIssue8131() + { + $manipulator = new JsonManipulator('{ + "description": "Some U\u00F1icode", + "require": { + "foo/bar": "^1.0" + } +}'); + + $this->assertTrue($manipulator->addLink('require', 'foo/baz', '^1.0')); + $this->assertEquals('{ + "description": "Some U\u00F1icode", + "require": { + "foo/bar": "^1.0", + "foo/baz": "^1.0" + } +} ', $manipulator->getContents()); } } diff --git a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php index ebe6871fe..2fc059f3c 100644 --- a/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php @@ -298,6 +298,30 @@ class ValidatingArrayLoaderTest extends TestCase 'homepage : invalid value (foo:bar), must be an http/https URL', ), ), + array( + array( + 'name' => 'foo/bar.json', + ), + array( + 'Deprecation warning: Your package name foo/bar.json is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead. Make sure you fix this as Composer 2.0 will error.', + ), + ), + array( + array( + 'name' => 'com1/foo', + ), + array( + 'Deprecation warning: Your package name com1/foo is reserved, package and vendor names can not match any of: nul, con, prn, aux, com1, com2, com3, com4, com5, com6, com7, com8, com9, lpt1, lpt2, lpt3, lpt4, lpt5, lpt6, lpt7, lpt8, lpt9. Make sure you fix this as Composer 2.0 will error.', + ), + ), + array( + array( + 'name' => 'Foo/Bar', + ), + array( + 'Deprecation warning: Your package name Foo/Bar is invalid, it should not contain uppercase characters. We suggest using foo/bar instead. Make sure you fix this as Composer 2.0 will error.', + ), + ), array( array( 'name' => 'foo/bar', @@ -337,6 +361,18 @@ class ValidatingArrayLoaderTest extends TestCase ), false, ), + array( + array( + 'name' => 'foo/bar', + 'require' => array( + 'Foo/Baz' => '^1.0', + ), + ), + array( + 'Deprecation warning: require.Foo/Baz is invalid, it should not contain uppercase characters. Please use foo/baz instead. Make sure you fix this as Composer 2.0 will error.', + ), + false, + ), array( array( 'name' => 'foo/bar', diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index c8af9418c..47df3a443 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -214,4 +214,71 @@ class ComposerRepositoryTest extends TestCase $repository->search('foo', RepositoryInterface::SEARCH_FULLTEXT, 'library') ); } + + /** + * @dataProvider canonicalizeUrlProvider + * + * @param string $expected + * @param string $url + * @param string $repositoryUrl + */ + public function testCanonicalizeUrl($expected, $url, $repositoryUrl) + { + $repository = new ComposerRepository( + array('url' => $repositoryUrl), + new NullIO(), + FactoryMock::createConfig() + ); + + $object = new \ReflectionObject($repository); + + $method = $object->getMethod('canonicalizeUrl'); + $method->setAccessible(true); + + // ComposerRepository::__construct ensures that the repository URL has a + // protocol, so reset it here in order to test all cases. + $property = $object->getProperty('url'); + $property->setAccessible(true); + $property->setValue($repository, $repositoryUrl); + + $this->assertSame($expected, $method->invoke($repository, $url)); + } + + public function canonicalizeUrlProvider() + { + return array( + array( + 'https://example.org/path/to/file', + '/path/to/file', + 'https://example.org', + ), + array( + 'https://example.org/canonic_url', + 'https://example.org/canonic_url', + 'https://should-not-see-me.test', + ), + array( + 'file:///path/to/repository/file', + '/path/to/repository/file', + 'file:///path/to/repository', + ), + array( + // Assert that the repository URL is returned unchanged if it is + // not a URL. + // (Backward compatibility test) + 'invalid_repo_url', + '/path/to/file', + 'invalid_repo_url', + ), + array( + // Assert that URLs can contain sequences resembling pattern + // references as understood by preg_replace() without messing up + // the result. + // (Regression test) + 'https://example.org/path/to/unusual_$0_filename', + '/path/to/unusual_$0_filename', + 'https://example.org', + ), + ); + } } diff --git a/tests/Composer/Test/Util/ProcessExecutorTest.php b/tests/Composer/Test/Util/ProcessExecutorTest.php index e98898417..db16b8c02 100644 --- a/tests/Composer/Test/Util/ProcessExecutorTest.php +++ b/tests/Composer/Test/Util/ProcessExecutorTest.php @@ -61,11 +61,25 @@ class ProcessExecutorTest extends TestCase ProcessExecutor::setTimeout(60); } - public function testHidePasswords() + /** + * @dataProvider hidePasswordProvider + */ + public function testHidePasswords($command, $expectedCommandOutput) { $process = new ProcessExecutor($buffer = new BufferIO('', StreamOutput::VERBOSITY_DEBUG)); - $process->execute('echo https://foo:bar@example.org/ && echo http://foo@example.org && echo http://abcdef1234567890234578:x-oauth-token@github.com/', $output); - $this->assertEquals('Executing command (CWD): echo https://foo:***@example.org/ && echo http://foo@example.org && echo http://***:***@github.com/', trim($buffer->getOutput())); + $process->execute($command, $output); + $this->assertEquals('Executing command (CWD): ' . $expectedCommandOutput, trim($buffer->getOutput())); + } + + public function hidePasswordProvider() + { + return array( + array('echo https://foo:bar@example.org/', 'echo https://foo:***@example.org/'), + array('echo http://foo@example.org', 'echo http://foo@example.org'), + array('echo http://abcdef1234567890234578:x-oauth-token@github.com/', 'echo http://***:***@github.com/'), + array("svn ls --verbose --non-interactive --username 'foo' --password 'bar' 'https://foo.example.org/svn/'", "svn ls --verbose --non-interactive --username 'foo' --password '***' 'https://foo.example.org/svn/'"), + array("svn ls --verbose --non-interactive --username 'foo' --password 'bar \'bar' 'https://foo.example.org/svn/'", "svn ls --verbose --non-interactive --username 'foo' --password '***' 'https://foo.example.org/svn/'"), + ); } public function testDoesntHidePorts()