Fix show/depends commands to display and abort when a circular dep was reached, fixes #4983
parent
b93b73e836
commit
27e1c4358e
|
@ -20,6 +20,7 @@ use Composer\Repository\CompositeRepository;
|
||||||
use Composer\Repository\PlatformRepository;
|
use Composer\Repository\PlatformRepository;
|
||||||
use Composer\Plugin\CommandEvent;
|
use Composer\Plugin\CommandEvent;
|
||||||
use Composer\Plugin\PluginEvents;
|
use Composer\Plugin\PluginEvents;
|
||||||
|
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||||
use Composer\Package\Version\VersionParser;
|
use Composer\Package\Version\VersionParser;
|
||||||
use Symfony\Component\Console\Helper\Table;
|
use Symfony\Component\Console\Helper\Table;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
@ -39,6 +40,8 @@ class BaseDependencyCommand extends BaseCommand
|
||||||
const OPTION_RECURSIVE = 'recursive';
|
const OPTION_RECURSIVE = 'recursive';
|
||||||
const OPTION_TREE = 'tree';
|
const OPTION_TREE = 'tree';
|
||||||
|
|
||||||
|
protected $colors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set common options and arguments.
|
* Set common options and arguments.
|
||||||
*/
|
*/
|
||||||
|
@ -119,6 +122,7 @@ class BaseDependencyCommand extends BaseCommand
|
||||||
$this->getIO()->writeError(sprintf('<info>There is no installed package depending on "%s"%s</info>',
|
$this->getIO()->writeError(sprintf('<info>There is no installed package depending on "%s"%s</info>',
|
||||||
$needle, $extra));
|
$needle, $extra));
|
||||||
} elseif ($renderTree) {
|
} elseif ($renderTree) {
|
||||||
|
$this->initStyles($output);
|
||||||
$root = $packages[0];
|
$root = $packages[0];
|
||||||
$this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription()));
|
$this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription()));
|
||||||
$this->printTree($results);
|
$this->printTree($results);
|
||||||
|
@ -169,13 +173,34 @@ class BaseDependencyCommand extends BaseCommand
|
||||||
$renderer->setRows($table)->render();
|
$renderer->setRows($table)->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init styles for tree
|
||||||
|
*
|
||||||
|
* @param OutputInterface $output
|
||||||
|
*/
|
||||||
|
protected function initStyles(OutputInterface $output)
|
||||||
|
{
|
||||||
|
$this->colors = array(
|
||||||
|
'green',
|
||||||
|
'yellow',
|
||||||
|
'cyan',
|
||||||
|
'magenta',
|
||||||
|
'blue',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($this->colors as $color) {
|
||||||
|
$style = new OutputFormatterStyle($color);
|
||||||
|
$output->getFormatter()->setStyle($color, $style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively prints a tree of the selected results.
|
* Recursively prints a tree of the selected results.
|
||||||
*
|
*
|
||||||
* @param array $results
|
* @param array $results
|
||||||
* @param string $prefix
|
* @param string $prefix
|
||||||
*/
|
*/
|
||||||
protected function printTree($results, $prefix = '')
|
protected function printTree($results, $prefix = '', $level = 1)
|
||||||
{
|
{
|
||||||
$count = count($results);
|
$count = count($results);
|
||||||
$idx = 0;
|
$idx = 0;
|
||||||
|
@ -185,12 +210,18 @@ class BaseDependencyCommand extends BaseCommand
|
||||||
* @var Link $link
|
* @var Link $link
|
||||||
*/
|
*/
|
||||||
list($package, $link, $children) = $result;
|
list($package, $link, $children) = $result;
|
||||||
|
|
||||||
|
$color = $this->colors[$level % count($this->colors)];
|
||||||
|
$prevColor = $this->colors[($level - 1) % count($this->colors)];
|
||||||
$isLast = (++$idx == $count);
|
$isLast = (++$idx == $count);
|
||||||
$versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion();
|
$versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion();
|
||||||
$packageText = rtrim(sprintf('%s %s', $package->getPrettyName(), $versionText));
|
$packageText = rtrim(sprintf('<%s>%s</%1$s> %s', $color, $package->getPrettyName(), $versionText));
|
||||||
$linkText = implode(' ', array($link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()));
|
$linkText = sprintf('%s <%s>%s</%2$s> %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint());
|
||||||
$this->writeTreeLine(sprintf("%s%s%s (%s)", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText));
|
$circularWarn = $children === false ? '(circular dependency aborted here)' : '';
|
||||||
$this->printTree($children, $prefix . ($isLast ? ' ' : '│ '));
|
$this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn)));
|
||||||
|
if ($children) {
|
||||||
|
$this->printTree($children, $prefix . ($isLast ? ' ' : '│ '), $level + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -491,9 +491,6 @@ EOT
|
||||||
*/
|
*/
|
||||||
protected function displayPackageTree(PackageInterface $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos)
|
protected function displayPackageTree(PackageInterface $package, RepositoryInterface $installedRepo, RepositoryInterface $distantRepos)
|
||||||
{
|
{
|
||||||
$packagesInTree = array();
|
|
||||||
$packagesInTree[] = $package;
|
|
||||||
|
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
$io->write(sprintf('<info>%s</info>', $package->getPrettyName()), false);
|
$io->write(sprintf('<info>%s</info>', $package->getPrettyName()), false);
|
||||||
$io->write(' ' . $package->getPrettyVersion(), false);
|
$io->write(' ' . $package->getPrettyVersion(), false);
|
||||||
|
@ -518,8 +515,7 @@ EOT
|
||||||
$this->writeTreeLine($info);
|
$this->writeTreeLine($info);
|
||||||
|
|
||||||
$treeBar = str_replace('└', ' ', $treeBar);
|
$treeBar = str_replace('└', ' ', $treeBar);
|
||||||
|
$packagesInTree = array($package->getName(), $requireName);
|
||||||
$packagesInTree[] = $requireName;
|
|
||||||
|
|
||||||
$this->displayTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree, $treeBar, $level + 1);
|
$this->displayTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree, $treeBar, $level + 1);
|
||||||
}
|
}
|
||||||
|
@ -547,19 +543,22 @@ EOT
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$total = count($requires);
|
$total = count($requires);
|
||||||
foreach ($requires as $requireName => $require) {
|
foreach ($requires as $requireName => $require) {
|
||||||
|
$currentTree = $packagesInTree;
|
||||||
$i++;
|
$i++;
|
||||||
if ($i == $total) {
|
if ($i == $total) {
|
||||||
$treeBar = $previousTreeBar . ' └';
|
$treeBar = $previousTreeBar . ' └';
|
||||||
}
|
}
|
||||||
$colorIdent = $level % count($this->colors);
|
$colorIdent = $level % count($this->colors);
|
||||||
$color = $this->colors[$colorIdent];
|
$color = $this->colors[$colorIdent];
|
||||||
$info = sprintf('%s──<%s>%s</%s> %s', $treeBar, $color, $requireName, $color, $require->getPrettyConstraint());
|
|
||||||
|
$circularWarn = in_array($requireName, $currentTree) ? '(circular dependency aborted here)' : '';
|
||||||
|
$info = rtrim(sprintf('%s──<%s>%s</%s> %s %s', $treeBar, $color, $requireName, $color, $require->getPrettyConstraint(), $circularWarn));
|
||||||
$this->writeTreeLine($info);
|
$this->writeTreeLine($info);
|
||||||
|
|
||||||
$treeBar = str_replace('└', ' ', $treeBar);
|
$treeBar = str_replace('└', ' ', $treeBar);
|
||||||
if (!in_array($requireName, $packagesInTree)) {
|
if (!in_array($requireName, $currentTree)) {
|
||||||
$packagesInTree[] = $requireName;
|
$currentTree[] = $requireName;
|
||||||
$this->displayTree($requireName, $require, $installedRepo, $distantRepos, $packagesInTree, $treeBar, $level + 1);
|
$this->displayTree($requireName, $require, $installedRepo, $distantRepos, $currentTree, $treeBar, $level + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,23 +26,33 @@ abstract class BaseRepository implements RepositoryInterface
|
||||||
* Returns a list of links causing the requested needle packages to be installed, as an associative array with the
|
* Returns a list of links causing the requested needle packages to be installed, as an associative array with the
|
||||||
* dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship
|
* dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship
|
||||||
* as values. If recursive lookup was requested a third value is returned containing an identically formed array up
|
* as values. If recursive lookup was requested a third value is returned containing an identically formed array up
|
||||||
* to the root package.
|
* to the root package. That third value will be false in case a circular recursion was detected.
|
||||||
*
|
*
|
||||||
* @param string|string[] $needle The package name(s) to inspect.
|
* @param string|string[] $needle The package name(s) to inspect.
|
||||||
* @param ConstraintInterface|null $constraint Optional constraint to filter by.
|
* @param ConstraintInterface|null $constraint Optional constraint to filter by.
|
||||||
* @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed.
|
* @param bool $invert Whether to invert matches to discover reasons for the package *NOT* to be installed.
|
||||||
* @param bool $recurse Whether to recursively expand the requirement tree up to the root package.
|
* @param bool $recurse Whether to recursively expand the requirement tree up to the root package.
|
||||||
|
* @param string[] $packagesFound Used internally when recurring
|
||||||
* @return array An associative array of arrays as described above.
|
* @return array An associative array of arrays as described above.
|
||||||
*/
|
*/
|
||||||
public function getDependents($needle, $constraint = null, $invert = false, $recurse = true)
|
public function getDependents($needle, $constraint = null, $invert = false, $recurse = true, $packagesFound = null)
|
||||||
{
|
{
|
||||||
$needles = (array) $needle;
|
$needles = (array) $needle;
|
||||||
$results = array();
|
$results = array();
|
||||||
|
|
||||||
|
// initialize the array with the needles before any recursion occurs
|
||||||
|
if (null === $packagesFound) {
|
||||||
|
$packagesFound = $needles;
|
||||||
|
}
|
||||||
|
|
||||||
// Loop over all currently installed packages.
|
// Loop over all currently installed packages.
|
||||||
foreach ($this->getPackages() as $package) {
|
foreach ($this->getPackages() as $package) {
|
||||||
$links = $package->getRequires();
|
$links = $package->getRequires();
|
||||||
|
|
||||||
|
// each loop needs its own "tree" as we want to show the complete dependent set of every needle
|
||||||
|
// without warning all the time about finding circular deps
|
||||||
|
$packagesInTree = $packagesFound;
|
||||||
|
|
||||||
// Replacements are considered valid reasons for a package to be installed during forward resolution
|
// Replacements are considered valid reasons for a package to be installed during forward resolution
|
||||||
if (!$invert) {
|
if (!$invert) {
|
||||||
$links += $package->getReplaces();
|
$links += $package->getReplaces();
|
||||||
|
@ -58,7 +68,13 @@ abstract class BaseRepository implements RepositoryInterface
|
||||||
foreach ($needles as $needle) {
|
foreach ($needles as $needle) {
|
||||||
if ($link->getTarget() === $needle) {
|
if ($link->getTarget() === $needle) {
|
||||||
if (is_null($constraint) || (($link->getConstraint()->matches($constraint) === !$invert))) {
|
if (is_null($constraint) || (($link->getConstraint()->matches($constraint) === !$invert))) {
|
||||||
$dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true) : array();
|
// already displayed this node's dependencies, cutting short
|
||||||
|
if (in_array($link->getSource(), $packagesInTree)) {
|
||||||
|
$results[$link->getSource()] = array($package, $link, false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$packagesInTree[] = $link->getSource();
|
||||||
|
$dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array();
|
||||||
$results[$link->getSource()] = array($package, $link, $dependents);
|
$results[$link->getSource()] = array($package, $link, $dependents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue