BaseDependencyCommand.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Command;
  12. use Composer\Package\Link;
  13. use Composer\Package\PackageInterface;
  14. use Composer\Repository\ArrayRepository;
  15. use Composer\Repository\CompositeRepository;
  16. use Composer\Repository\PlatformRepository;
  17. use Composer\Repository\RepositoryFactory;
  18. use Composer\Plugin\CommandEvent;
  19. use Composer\Plugin\PluginEvents;
  20. use Composer\Repository\RepositorySet;
  21. use Symfony\Component\Console\Formatter\OutputFormatterStyle;
  22. use Composer\Package\Version\VersionParser;
  23. use Symfony\Component\Console\Helper\Table;
  24. use Symfony\Component\Console\Input\InputArgument;
  25. use Symfony\Component\Console\Input\InputInterface;
  26. use Symfony\Component\Console\Input\InputOption;
  27. use Symfony\Component\Console\Output\OutputInterface;
  28. /**
  29. * Base implementation for commands mapping dependency relationships.
  30. *
  31. * @author Niels Keurentjes <niels.keurentjes@omines.com>
  32. */
  33. class BaseDependencyCommand extends BaseCommand
  34. {
  35. const ARGUMENT_PACKAGE = 'package';
  36. const ARGUMENT_CONSTRAINT = 'constraint';
  37. const OPTION_RECURSIVE = 'recursive';
  38. const OPTION_TREE = 'tree';
  39. protected $colors;
  40. /**
  41. * Set common options and arguments.
  42. */
  43. protected function configure()
  44. {
  45. $this->setDefinition(array(
  46. new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'),
  47. new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::OPTIONAL, 'Optional version constraint', '*'),
  48. new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
  49. new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
  50. ));
  51. }
  52. /**
  53. * Execute the command.
  54. *
  55. * @param InputInterface $input
  56. * @param OutputInterface $output
  57. * @param bool $inverted Whether to invert matching process (why-not vs why behaviour)
  58. * @return int|null Exit code of the operation.
  59. */
  60. protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
  61. {
  62. // Emit command event on startup
  63. $composer = $this->getComposer();
  64. $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
  65. $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
  66. // Prepare repositories and set up a repo set
  67. $platformOverrides = $composer->getConfig()->get('platform') ?: array();
  68. $repository = new CompositeRepository(array(
  69. new ArrayRepository(array($composer->getPackage())),
  70. $composer->getRepositoryManager()->getLocalRepository(),
  71. new PlatformRepository(array(), $platformOverrides),
  72. ));
  73. $repositorySet = new RepositorySet();
  74. $repositorySet->addRepository($repository);
  75. // Parse package name and constraint
  76. list($needle, $textConstraint) = array_pad(
  77. explode(':', $input->getArgument(self::ARGUMENT_PACKAGE)),
  78. 2,
  79. $input->getArgument(self::ARGUMENT_CONSTRAINT)
  80. );
  81. // Find packages that are or provide the requested package first
  82. $packages = $repositorySet->findPackages(strtolower($needle), null, false);
  83. if (empty($packages)) {
  84. throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
  85. }
  86. // If the version we ask for is not installed then we need to locate it in remote repos and add it.
  87. // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages.
  88. if (!$repository->findPackage($needle, $textConstraint)) {
  89. $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO()));
  90. if ($match = $defaultRepos->findPackage($needle, $textConstraint)) {
  91. $repository->addRepository(new ArrayRepository(array(clone $match)));
  92. }
  93. }
  94. // Include replaced packages for inverted lookups as they are then the actual starting point to consider
  95. $needles = array($needle);
  96. if ($inverted) {
  97. foreach ($packages as $package) {
  98. $needles = array_merge($needles, array_map(function (Link $link) {
  99. return $link->getTarget();
  100. }, $package->getReplaces()));
  101. }
  102. }
  103. // Parse constraint if one was supplied
  104. if ('*' !== $textConstraint) {
  105. $versionParser = new VersionParser();
  106. $constraint = $versionParser->parseConstraints($textConstraint);
  107. } else {
  108. $constraint = null;
  109. }
  110. // Parse rendering options
  111. $renderTree = $input->getOption(self::OPTION_TREE);
  112. $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE);
  113. // Resolve dependencies
  114. $results = $repository->getDependents($needles, $constraint, $inverted, $recursive);
  115. if (empty($results)) {
  116. $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : '';
  117. $this->getIO()->writeError(sprintf(
  118. '<info>There is no installed package depending on "%s"%s</info>',
  119. $needle,
  120. $extra
  121. ));
  122. } elseif ($renderTree) {
  123. $this->initStyles($output);
  124. $root = $packages[0];
  125. $this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription()));
  126. $this->printTree($results);
  127. } else {
  128. $this->printTable($output, $results);
  129. }
  130. return 0;
  131. }
  132. /**
  133. * Assembles and prints a bottom-up table of the dependencies.
  134. *
  135. * @param OutputInterface $output
  136. * @param array $results
  137. */
  138. protected function printTable(OutputInterface $output, $results)
  139. {
  140. $table = array();
  141. $doubles = array();
  142. do {
  143. $queue = array();
  144. $rows = array();
  145. foreach ($results as $result) {
  146. /**
  147. * @var PackageInterface $package
  148. * @var Link $link
  149. */
  150. list($package, $link, $children) = $result;
  151. $unique = (string) $link;
  152. if (isset($doubles[$unique])) {
  153. continue;
  154. }
  155. $doubles[$unique] = true;
  156. $version = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion();
  157. $rows[] = array($package->getPrettyName(), $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint()));
  158. if ($children) {
  159. $queue = array_merge($queue, $children);
  160. }
  161. }
  162. $results = $queue;
  163. $table = array_merge($rows, $table);
  164. } while (!empty($results));
  165. // Render table
  166. $renderer = new Table($output);
  167. $renderer->setStyle('compact');
  168. $rendererStyle = $renderer->getStyle();
  169. $rendererStyle->setVerticalBorderChar('');
  170. $rendererStyle->setCellRowContentFormat('%s ');
  171. $renderer->setRows($table)->render();
  172. }
  173. /**
  174. * Init styles for tree
  175. *
  176. * @param OutputInterface $output
  177. */
  178. protected function initStyles(OutputInterface $output)
  179. {
  180. $this->colors = array(
  181. 'green',
  182. 'yellow',
  183. 'cyan',
  184. 'magenta',
  185. 'blue',
  186. );
  187. foreach ($this->colors as $color) {
  188. $style = new OutputFormatterStyle($color);
  189. $output->getFormatter()->setStyle($color, $style);
  190. }
  191. }
  192. /**
  193. * Recursively prints a tree of the selected results.
  194. *
  195. * @param array $results Results to be printed at this level.
  196. * @param string $prefix Prefix of the current tree level.
  197. * @param int $level Current level of recursion.
  198. */
  199. protected function printTree($results, $prefix = '', $level = 1)
  200. {
  201. $count = count($results);
  202. $idx = 0;
  203. foreach ($results as $result) {
  204. /**
  205. * @var PackageInterface $package
  206. * @var Link $link
  207. * @var array|bool $children
  208. */
  209. list($package, $link, $children) = $result;
  210. $color = $this->colors[$level % count($this->colors)];
  211. $prevColor = $this->colors[($level - 1) % count($this->colors)];
  212. $isLast = (++$idx == $count);
  213. $versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $package->getPrettyVersion();
  214. $packageText = rtrim(sprintf('<%s>%s</%1$s> %s', $color, $package->getPrettyName(), $versionText));
  215. $linkText = sprintf('%s <%s>%s</%2$s> %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint());
  216. $circularWarn = $children === false ? '(circular dependency aborted here)' : '';
  217. $this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn)));
  218. if ($children) {
  219. $this->printTree($children, $prefix . ($isLast ? ' ' : '│ '), $level + 1);
  220. }
  221. }
  222. }
  223. private function writeTreeLine($line)
  224. {
  225. $io = $this->getIO();
  226. if (!$io->isDecorated()) {
  227. $line = str_replace(array('└', '├', '──', '│'), array('`-', '|-', '-', '|'), $line);
  228. }
  229. $io->write($line);
  230. }
  231. }