@ -15,12 +15,15 @@ namespace Composer\Command;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Pool;
use Composer\Package\Link;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackage;
use Composer\Repository\ArrayRepository;
use Composer\Repository\ArrayRepository;
use Composer\Repository\CompositeRepository;
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 Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\VersionParser;
use Composer\Semver\VersionParser;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputOption;
@ -32,22 +35,19 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
*/
class DependsCommand extends Command
class DependsCommand extends Command
{
{
protected $linkTypes = array(
/** @var CompositeRepository */
'require' => array('requires', 'requires'),
private $repository;
'require-dev' => array('devRequires', 'requires (dev)'),
);
protected function configure()
protected function configure()
{
{
$this
$this
->setName('depends')
->setName('depends')
->setAliases(array('why'))
->setDescription('Shows which packages depend on the given package')
->setDescription('Shows which packages depend on the given package')
->setDefinition(array(
->setDefinition(array(
new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'),
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('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('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
->setHelp(< < < EOT
Displays detailed information about where a package is referenced.
Displays detailed information about where a package is referenced.
@ -61,80 +61,137 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output)
{
{
// Emit command event on startup
$composer = $this->getComposer();
$composer = $this->getComposer();
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'depends', $input, $output);
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'depends', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
// Prepare repositories and set up a pool
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
$repo = new CompositeRepository(array(
$this-> repository = new CompositeRepository(array(
new ArrayRepository(array($composer->getPackage())),
new ArrayRepository(array($composer->getPackage())),
$composer->getRepositoryManager()->getLocalRepository(),
$composer->getRepositoryManager()->getLocalRepository(),
new PlatformRepository(array(), $platformOverrides),
new PlatformRepository(array(), $platformOverrides),
));
));
$needle = $input->getArgument('package');
$pool = new Pool();
$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);
$packages = $pool->whatProvides($needle);
if (empty($packages)) {
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) {
private function printTable(OutputInterface $output, $results)
$type = rtrim($type, 's');
{
if (!isset($linkTypes[$type])) {
$table = array();
throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', array_keys($linkTypes)));
$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;
// Render table
}, $input->getOption('link-type'));
$renderer = new Table($output);
$renderer->setStyle('compact')->setRows($table)->render();
}
$versionParser = new VersionParser();
public function printTree(OutputInterface $output, $results, $prefix = '')
$constraint = $versionParser->parseConstraints($input->getOption('match-constraint'));
{
$matchInvert = $input->getOption('invert-match-constraint');
$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')) {
* @param string $needle The package to inspect.
foreach ($packages as $package) {
* @param ConstraintInterface|null $constraint Optional constraint to filter by.
$needles = array_merge($needles, array_map(function (Link $link) {
* @param bool $invert Whether to invert matches on the previous constraint.
return $link->getTarget();
* @param bool $recurse Whether to recursively expand the requirement tree.
}, $package->getReplaces()));
* @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();
// Cross-reference all discovered links to the needles
$outputPackages = array();
foreach ($links as $link) {
$io = $this->getIO();
foreach ($needles as $needle) {
/** @var PackageInterface $package */
if ($link->getTarget() === $needle) {
foreach ($repo->getPackages() as $package) {
if (is_null($constraint) || (($link->getConstraint()->matches($constraint) === !$invert))) {
foreach ($types as $type) {
$results[$link->getSource()] = array($package, $link, $recurse ? $this->getDependers($link->getSource(), null, false, true) : array());
/** @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;
}
}
}
}
}
}
}
}
}
}
}
ksort($results);
if ($messages) {
return $results;
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 > ');
}
}
}
}
}