|
@@ -15,12 +15,15 @@ namespace Composer\Command;
|
|
|
use Composer\DependencyResolver\Pool;
|
|
|
use Composer\Package\Link;
|
|
|
use Composer\Package\PackageInterface;
|
|
|
+use Composer\Package\RootPackage;
|
|
|
use Composer\Repository\ArrayRepository;
|
|
|
use Composer\Repository\CompositeRepository;
|
|
|
use Composer\Repository\PlatformRepository;
|
|
|
use Composer\Plugin\CommandEvent;
|
|
|
use Composer\Plugin\PluginEvents;
|
|
|
+use Composer\Semver\Constraint\ConstraintInterface;
|
|
|
use Composer\Semver\VersionParser;
|
|
|
+use Symfony\Component\Console\Helper\Table;
|
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
|
use Symfony\Component\Console\Input\InputArgument;
|
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
@@ -32,22 +35,19 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|
|
*/
|
|
|
class DependsCommand extends Command
|
|
|
{
|
|
|
- protected $linkTypes = array(
|
|
|
- 'require' => array('requires', 'requires'),
|
|
|
- 'require-dev' => array('devRequires', 'requires (dev)'),
|
|
|
- );
|
|
|
+ /** @var CompositeRepository */
|
|
|
+ private $repository;
|
|
|
|
|
|
protected function configure()
|
|
|
{
|
|
|
$this
|
|
|
->setName('depends')
|
|
|
+ ->setAliases(array('why'))
|
|
|
->setDescription('Shows which packages depend on the given package')
|
|
|
->setDefinition(array(
|
|
|
new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'),
|
|
|
- new InputOption('link-type', '', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Link types to show (require, require-dev)', array_keys($this->linkTypes)),
|
|
|
new InputOption('match-constraint', 'm', InputOption::VALUE_REQUIRED, 'Filters the dependencies shown using this constraint', '*'),
|
|
|
new InputOption('invert-match-constraint', 'i', InputOption::VALUE_NONE, 'Turns --match-constraint around into a blacklist instead of whitelist'),
|
|
|
- new InputOption('with-replaces', '', InputOption::VALUE_NONE, 'Search for replaced packages as well'),
|
|
|
))
|
|
|
->setHelp(<<<EOT
|
|
|
Displays detailed information about where a package is referenced.
|
|
@@ -61,80 +61,137 @@ EOT
|
|
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output)
|
|
|
{
|
|
|
+ // Emit command event on startup
|
|
|
$composer = $this->getComposer();
|
|
|
-
|
|
|
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'depends', $input, $output);
|
|
|
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
|
|
|
|
|
|
+ // Prepare repositories and set up a pool
|
|
|
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
|
|
|
- $repo = new CompositeRepository(array(
|
|
|
+ $this->repository = new CompositeRepository(array(
|
|
|
new ArrayRepository(array($composer->getPackage())),
|
|
|
$composer->getRepositoryManager()->getLocalRepository(),
|
|
|
new PlatformRepository(array(), $platformOverrides),
|
|
|
));
|
|
|
- $needle = $input->getArgument('package');
|
|
|
-
|
|
|
$pool = new Pool();
|
|
|
- $pool->addRepository($repo);
|
|
|
+ $pool->addRepository($this->repository);
|
|
|
|
|
|
+ // Find packages that are or provide the requested package first
|
|
|
+ $needle = $input->getArgument('package');
|
|
|
$packages = $pool->whatProvides($needle);
|
|
|
if (empty($packages)) {
|
|
|
- throw new \InvalidArgumentException('Could not find package "'.$needle.'" in your project.');
|
|
|
+ throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
|
|
|
}
|
|
|
|
|
|
- $linkTypes = $this->linkTypes;
|
|
|
+ // Parse options that are only relevant for the initial needle(s)
|
|
|
+ if ('*' !== ($textConstraint = $input->getOption('match-constraint'))) {
|
|
|
+ $versionParser = new VersionParser();
|
|
|
+ $constraint = $versionParser->parseConstraints($textConstraint);
|
|
|
+ } else {
|
|
|
+ $constraint = null;
|
|
|
+ }
|
|
|
+ $matchInvert = $input->getOption('invert-match-constraint');
|
|
|
+ $recursive = true;
|
|
|
+ $tree = true;
|
|
|
+
|
|
|
+ // Resolve dependencies
|
|
|
+ $results = $this->getDependers($needle, $constraint, $matchInvert, $recursive);
|
|
|
+ if (empty($results)) {
|
|
|
+ $extra = isset($constraint) ? sprintf(' in versions %smatching %s', $matchInvert ? 'not ' : '', $textConstraint) : '';
|
|
|
+ $this->getIO()->writeError(sprintf('<info>There is no installed package depending on "%s"%s</info>',
|
|
|
+ $needle, $extra));
|
|
|
+ } elseif ($tree) {
|
|
|
+ $root = $packages[0];
|
|
|
+ $this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription()));
|
|
|
+ $this->printTree($output, $results);
|
|
|
+ } else {
|
|
|
+ $this->printTable($output, $results);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- $types = array_map(function ($type) use ($linkTypes) {
|
|
|
- $type = rtrim($type, 's');
|
|
|
- if (!isset($linkTypes[$type])) {
|
|
|
- throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', array_keys($linkTypes)));
|
|
|
+ private function printTable(OutputInterface $output, $results)
|
|
|
+ {
|
|
|
+ $table = array();
|
|
|
+ $doubles = array();
|
|
|
+ do {
|
|
|
+ $queue = array();
|
|
|
+ $rows = array();
|
|
|
+ foreach($results as $result) {
|
|
|
+ /**
|
|
|
+ * @var PackageInterface $package
|
|
|
+ * @var Link $link
|
|
|
+ */
|
|
|
+ list($package, $link, $children) = $result;
|
|
|
+ $unique = (string)$link;
|
|
|
+ if (isset($doubles[$unique])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $doubles[$unique] = true;
|
|
|
+ $realVersion = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion();
|
|
|
+ $rows[] = array($package->getPrettyName(), $realVersion, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint()));
|
|
|
+ $queue = array_merge($queue, $children);
|
|
|
}
|
|
|
+ $results = $queue;
|
|
|
+ $table = array_merge($rows, $table);
|
|
|
+ } while(!empty($results));
|
|
|
|
|
|
- return $type;
|
|
|
- }, $input->getOption('link-type'));
|
|
|
+ // Render table
|
|
|
+ $renderer = new Table($output);
|
|
|
+ $renderer->setStyle('compact')->setRows($table)->render();
|
|
|
+ }
|
|
|
|
|
|
- $versionParser = new VersionParser();
|
|
|
- $constraint = $versionParser->parseConstraints($input->getOption('match-constraint'));
|
|
|
- $matchInvert = $input->getOption('invert-match-constraint');
|
|
|
+ public function printTree(OutputInterface $output, $results, $prefix = '')
|
|
|
+ {
|
|
|
+ $count = count($results);
|
|
|
+ $idx = 0;
|
|
|
+ foreach($results as $key => $result) {
|
|
|
+ /**
|
|
|
+ * @var PackageInterface $package
|
|
|
+ * @var Link $link
|
|
|
+ */
|
|
|
+ list($package, $link, $children) = $result;
|
|
|
+ $isLast = (++$idx == $count);
|
|
|
+ $output->write(sprintf("%s%s %s %s %s (%s)\n", $prefix, $isLast ? '`-' : '|-', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()));
|
|
|
+ $this->printTree($output, $children, $prefix . ($isLast ? ' ' : '| '));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- $needles = array($needle);
|
|
|
- if (true === $input->getOption('with-replaces')) {
|
|
|
- foreach ($packages as $package) {
|
|
|
- $needles = array_merge($needles, array_map(function (Link $link) {
|
|
|
- return $link->getTarget();
|
|
|
- }, $package->getReplaces()));
|
|
|
+ /**
|
|
|
+ * @param string $needle The package to inspect.
|
|
|
+ * @param ConstraintInterface|null $constraint Optional constraint to filter by.
|
|
|
+ * @param bool $invert Whether to invert matches on the previous constraint.
|
|
|
+ * @param bool $recurse Whether to recursively expand the requirement tree.
|
|
|
+ * @return array An array with dependers as key, and as values an array containing the source package and the link respectively
|
|
|
+ */
|
|
|
+ private function getDependers($needle, $constraint = null, $invert = false, $recurse = true)
|
|
|
+ {
|
|
|
+ $needles = is_array($needle) ? $needle : array($needle);
|
|
|
+ $results = array();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Loop over all currently installed packages.
|
|
|
+ * @var PackageInterface $package
|
|
|
+ */
|
|
|
+ foreach ($this->repository->getPackages() as $package) {
|
|
|
+ // Retrieve all requirements, but dev only for the root package
|
|
|
+ $links = $package->getRequires();
|
|
|
+ $links += $package->getReplaces();
|
|
|
+ if ($package instanceof RootPackage) {
|
|
|
+ $links += $package->getDevRequires();
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- $messages = array();
|
|
|
- $outputPackages = array();
|
|
|
- $io = $this->getIO();
|
|
|
- /** @var PackageInterface $package */
|
|
|
- foreach ($repo->getPackages() as $package) {
|
|
|
- foreach ($types as $type) {
|
|
|
- /** @var Link $link */
|
|
|
- foreach ($package->{'get'.$linkTypes[$type][0]}() as $link) {
|
|
|
- foreach ($needles as $needle) {
|
|
|
- if ($link->getTarget() === $needle && ($link->getConstraint()->matches($constraint) ? !$matchInvert : $matchInvert)) {
|
|
|
- if (!isset($outputPackages[$package->getName()])) {
|
|
|
- $messages[] = '<info>'.$package->getPrettyName() . '</info> ' . $linkTypes[$type][1] . ' ' . $needle .' (<info>' . $link->getPrettyConstraint() . '</info>)';
|
|
|
- $outputPackages[$package->getName()] = true;
|
|
|
- }
|
|
|
+ // Cross-reference all discovered links to the needles
|
|
|
+ foreach ($links as $link) {
|
|
|
+ foreach ($needles as $needle) {
|
|
|
+ if ($link->getTarget() === $needle) {
|
|
|
+ if (is_null($constraint) || (($link->getConstraint()->matches($constraint) === !$invert))) {
|
|
|
+ $results[$link->getSource()] = array($package, $link, $recurse ? $this->getDependers($link->getSource(), null, false, true) : array());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- if ($messages) {
|
|
|
- sort($messages);
|
|
|
- $io->write($messages);
|
|
|
- } else {
|
|
|
- $matchText = '';
|
|
|
- if ($input->getOption('match-constraint') !== '*') {
|
|
|
- $matchText = ' in versions '.($matchInvert ? 'not ' : '').'matching ' . $input->getOption('match-constraint');
|
|
|
- }
|
|
|
- $io->writeError('<info>There is no installed package depending on "'.$needle.'"'.$matchText.'.</info>');
|
|
|
- }
|
|
|
+ ksort($results);
|
|
|
+ return $results;
|
|
|
}
|
|
|
}
|