From 75bb0d9b10be8b6b5ccf5aadc825608cd9a35e3f Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sat, 20 Feb 2016 01:39:24 +0100 Subject: [PATCH] Implemented Prohibits and Depends correctly now. --- .../Command/BaseDependencyCommand.php | 188 ++++++++++++++++++ src/Composer/Command/DependsCommand.php | 143 ++----------- src/Composer/Command/ProhibitsCommand.php | 55 +++++ src/Composer/Console/Application.php | 50 ++--- src/Composer/Repository/BaseRepository.php | 10 +- 5 files changed, 288 insertions(+), 158 deletions(-) create mode 100644 src/Composer/Command/BaseDependencyCommand.php create mode 100644 src/Composer/Command/ProhibitsCommand.php diff --git a/src/Composer/Command/BaseDependencyCommand.php b/src/Composer/Command/BaseDependencyCommand.php new file mode 100644 index 000000000..a84c62bc3 --- /dev/null +++ b/src/Composer/Command/BaseDependencyCommand.php @@ -0,0 +1,188 @@ + + * Jordi Boggiano + * + * 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 + */ +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('There is no installed package depending on "%s"%s', + $needle, $extra)); + } elseif ($renderTree) { + $root = $packages[0]; + $this->getIO()->write(sprintf('%s %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 ? ' ' : '| ')); + } + } +} diff --git a/src/Composer/Command/DependsCommand.php b/src/Composer/Command/DependsCommand.php index f08b00521..3da8d991b 100644 --- a/src/Composer/Command/DependsCommand.php +++ b/src/Composer/Command/DependsCommand.php @@ -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 */ -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(<<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('There is no installed package depending on "%s"%s', - $needle, $extra)); - } elseif ($renderTree) { - $root = $packages[0]; - $this->getIO()->write(sprintf('%s %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. + * Execute the function. * + * @param InputInterface $input * @param OutputInterface $output - * @param array $results + * @return int|null */ - 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 = '') + protected function execute(InputInterface $input, OutputInterface $output) { - $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 ? ' ' : '| ')); - } + parent::doExecute($input, $output, false); } - - } diff --git a/src/Composer/Command/ProhibitsCommand.php b/src/Composer/Command/ProhibitsCommand.php new file mode 100644 index 000000000..3f0ea15a5 --- /dev/null +++ b/src/Composer/Command/ProhibitsCommand.php @@ -0,0 +1,55 @@ + + * Jordi Boggiano + * + * 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 + */ +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(<<php composer.phar prohibits composer/composer + +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); + } +} diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 07d517eae..51db89e89 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -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(); diff --git a/src/Composer/Repository/BaseRepository.php b/src/Composer/Repository/BaseRepository.php index c28ffa791..3b0b81b28 100644 --- a/src/Composer/Repository/BaseRepository.php +++ b/src/Composer/Repository/BaseRepository.php @@ -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) {