1
0
Fork 0

Implemented Prohibits and Depends correctly now.

pull/4917/head
Niels Keurentjes 2016-02-20 01:39:24 +01:00
parent 692a3ed300
commit 75bb0d9b10
5 changed files with 291 additions and 161 deletions

View File

@ -0,0 +1,188 @@
<?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\Command;
use Composer\DependencyResolver\Pool;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Repository\ArrayRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Semver\VersionParser;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Base implementation for commands mapping dependency relationships.
*
* @author Niels Keurentjes <niels.keurentjes@omines.com>
*/
class BaseDependencyCommand extends BaseCommand
{
const ARGUMENT_PACKAGE = 'package';
const ARGUMENT_CONSTRAINT = 'constraint';
const OPTION_RECURSIVE = 'recursive';
const OPTION_TREE = 'tree';
/**
* Set common options and arguments.
*/
protected function configure()
{
$this->setDefinition(array(
new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'),
new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::OPTIONAL, 'Optional version constraint', '*'),
new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
));
}
/**
* Execute the command.
*
* @param InputInterface $input
* @param OutputInterface $output
* @param bool @inverted Whether
*/
protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
{
// Emit command event on startup
$composer = $this->getComposer();
$commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
// Prepare repositories and set up a pool
$platformOverrides = $composer->getConfig()->get('platform') ?: array();
$repository = new CompositeRepository(array(
new ArrayRepository(array($composer->getPackage())),
$composer->getRepositoryManager()->getLocalRepository(),
new PlatformRepository(array(), $platformOverrides),
));
$pool = new Pool();
$pool->addRepository($repository);
// Parse package name and constraint
list($needle, $textConstraint) = array_pad(explode(':', $input->getArgument(self::ARGUMENT_PACKAGE)),
2, $input->getArgument(self::ARGUMENT_CONSTRAINT));
// Find packages that are or provide the requested package first
$packages = $pool->whatProvides($needle);
if (empty($packages)) {
throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
}
// Include replaced packages for inverted lookups as they are then the actual starting point to consider
$needles = array($needle);
if ($inverted) {
foreach ($packages as $package) {
$needles = array_merge($needles, array_map(function (Link $link) {
return $link->getTarget();
}, $package->getReplaces()));
}
}
// Parse constraint if one was supplied
if ('*' !== $textConstraint) {
$versionParser = new VersionParser();
$constraint = $versionParser->parseConstraints($textConstraint);
} else {
$constraint = null;
}
// Parse rendering options
$renderTree = $input->getOption(self::OPTION_TREE);
$recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE);
// Resolve dependencies
$results = $repository->getDependents($needles, $constraint, $inverted, $recursive);
if (empty($results)) {
$extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : '';
$this->getIO()->writeError(sprintf('<info>There is no installed package depending on "%s"%s</info>',
$needle, $extra));
} elseif ($renderTree) {
$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);
}
}
/**
* Assembles and prints a bottom-up table of the dependencies.
*
* @param OutputInterface $output
* @param array $results
*/
protected 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;
$version = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion();
$rows[] = array($package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint()));
$queue = array_merge($queue, $children);
}
$results = $queue;
$table = array_merge($rows, $table);
} while(!empty($results));
// Render table
$renderer = new Table($output);
$renderer->setStyle('compact')->setRows($table)->render();
}
/**
* Recursively prints a tree of the selected results.
*
* @param OutputInterface $output
* @param array $results
* @param string $prefix
*/
protected 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);
$versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion();
$packageText = rtrim(sprintf('%s %s', $package->getPrettyName(), $versionText));
$linkText = implode(' ', array($link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()));
$output->write(sprintf("%s%s %s (%s)\n", $prefix, $isLast ? '`-' : '|-', $packageText, $linkText));
$this->printTree($output, $children, $prefix . ($isLast ? ' ' : '| '));
}
}
}

View File

@ -12,39 +12,25 @@
namespace Composer\Command;
use Composer\DependencyResolver\Pool;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Repository\ArrayRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
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;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Niels Keurentjes <niels.keurentjes@omines.com>
*/
class DependsCommand extends BaseCommand
class DependsCommand extends BaseDependencyCommand
{
/**
* Configure command metadata.
*/
protected function configure()
{
parent::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('recursive', 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
new InputOption('tree', 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
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'),
))
->setDescription('Shows which packages cause the given package to be installed')
->setHelp(<<<EOT
Displays detailed information about where a package is referenced.
@ -55,120 +41,15 @@ EOT
;
}
/**
* Execute the function.
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int|null
*/
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();
$repository = new CompositeRepository(array(
new ArrayRepository(array($composer->getPackage())),
$composer->getRepositoryManager()->getLocalRepository(),
new PlatformRepository(array(), $platformOverrides),
));
$pool = new Pool();
$pool->addRepository($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(sprintf('Could not find package "%s" in your project', $needle));
}
// 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');
// Parse rendering options
$renderTree = $input->getOption('tree');
$recursive = $renderTree || $input->getOption('recursive');
// Resolve dependencies
$results = $this->getDependents($needle, $repository->getPackages(), $constraint, $matchInvert, $recursive);
if (empty($results)) {
$extra = (null !== $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 ($renderTree) {
$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);
}
parent::doExecute($input, $output, false);
}
/**
* Assembles and prints a bottom-up table of the dependencies.
*
* @param OutputInterface $output
* @param array $results
*/
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;
$version = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion();
$rows[] = array($package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint()));
$queue = array_merge($queue, $children);
}
$results = $queue;
$table = array_merge($rows, $table);
} while(!empty($results));
// Render table
$renderer = new Table($output);
$renderer->setStyle('compact')->setRows($table)->render();
}
/**
* Recursively prints a tree of the selected results.
*
* @param OutputInterface $output
* @param array $results
* @param string $prefix
*/
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);
$versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion();
$packageText = rtrim(sprintf('%s %s', $package->getPrettyName(), $versionText));
$linkText = implode(' ', array($link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()));
$output->write(sprintf("%s%s %s (%s)\n", $prefix, $isLast ? '`-' : '|-', $packageText, $linkText));
$this->printTree($output, $children, $prefix . ($isLast ? ' ' : '| '));
}
}
}

View File

@ -0,0 +1,55 @@
<?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\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Niels Keurentjes <niels.keurentjes@omines.com>
*/
class ProhibitsCommand extends BaseDependencyCommand
{
/**
* Configure command metadata.
*/
protected function configure()
{
parent::configure();
$this
->setName('prohibits')
->setAliases(array('why-not'))
->setDescription('Shows which packages prevent the given package from being installed')
->setHelp(<<<EOT
Displays detailed information about why a package cannot be installed.
<info>php composer.phar prohibits composer/composer</info>
EOT
)
;
}
/**
* Execute the function.
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int|null
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
parent::doExecute($input, $output, true);
}
}

View File

@ -279,33 +279,35 @@ class Application extends BaseApplication
}
/**
* Initializes all the composer commands
* Initializes all the composer commands.
*/
protected function getDefaultCommands()
{
$commands = parent::getDefaultCommands();
$commands[] = new Command\AboutCommand();
$commands[] = new Command\ConfigCommand();
$commands[] = new Command\DependsCommand();
$commands[] = new Command\InitCommand();
$commands[] = new Command\InstallCommand();
$commands[] = new Command\CreateProjectCommand();
$commands[] = new Command\UpdateCommand();
$commands[] = new Command\SearchCommand();
$commands[] = new Command\ValidateCommand();
$commands[] = new Command\ShowCommand();
$commands[] = new Command\SuggestsCommand();
$commands[] = new Command\RequireCommand();
$commands[] = new Command\DumpAutoloadCommand();
$commands[] = new Command\StatusCommand();
$commands[] = new Command\ArchiveCommand();
$commands[] = new Command\DiagnoseCommand();
$commands[] = new Command\RunScriptCommand();
$commands[] = new Command\LicensesCommand();
$commands[] = new Command\GlobalCommand();
$commands[] = new Command\ClearCacheCommand();
$commands[] = new Command\RemoveCommand();
$commands[] = new Command\HomeCommand();
$commands = array_merge(parent::getDefaultCommands(), array(
new Command\AboutCommand(),
new Command\ConfigCommand(),
new Command\DependsCommand(),
new Command\ProhibitsCommand(),
new Command\InitCommand(),
new Command\InstallCommand(),
new Command\CreateProjectCommand(),
new Command\UpdateCommand(),
new Command\SearchCommand(),
new Command\ValidateCommand(),
new Command\ShowCommand(),
new Command\SuggestsCommand(),
new Command\RequireCommand(),
new Command\DumpAutoloadCommand(),
new Command\StatusCommand(),
new Command\ArchiveCommand(),
new Command\DiagnoseCommand(),
new Command\RunScriptCommand(),
new Command\LicensesCommand(),
new Command\GlobalCommand(),
new Command\ClearCacheCommand(),
new Command\RemoveCommand(),
new Command\HomeCommand(),
));
if ('phar:' === substr(__FILE__, 0, 5)) {
$commands[] = new Command\SelfUpdateCommand();

View File

@ -34,15 +34,19 @@ abstract class BaseRepository implements RepositoryInterface
* @param bool $recurse Whether to recursively expand the requirement tree up to the root package.
* @return array An associative array of arrays as described above.
*/
private function getDependents($needle, $constraint = null, $invert = false, $recurse = true)
public function getDependents($needle, $constraint = null, $invert = false, $recurse = true)
{
$needles = is_array($needle) ? $needle : array($needle);
$results = array();
// Loop over all currently installed packages.
foreach ($this->getPackages() as $package) {
// Requirements and replaces are both considered valid reasons for a package to be installed
$links = $package->getRequires() + $package->getReplaces();
$links = $package->getRequires();
// Replacements are considered valid reasons for a package to be installed during forward resolution
if (!$invert) {
$links += $package->getReplaces();
}
// Require-dev is only relevant for the root package
if ($package instanceof RootPackageInterface) {