RequireCommand.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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 Symfony\Component\Console\Input\InputInterface;
  13. use Symfony\Component\Console\Input\InputArgument;
  14. use Symfony\Component\Console\Input\InputOption;
  15. use Symfony\Component\Console\Output\OutputInterface;
  16. use Composer\Factory;
  17. use Composer\Installer;
  18. use Composer\Json\JsonFile;
  19. use Composer\Json\JsonManipulator;
  20. use Composer\Package\Version\VersionParser;
  21. use Composer\Plugin\CommandEvent;
  22. use Composer\Plugin\PluginEvents;
  23. use Composer\Repository\CompositeRepository;
  24. use Composer\Repository\PlatformRepository;
  25. /**
  26. * @author Jérémy Romey <jeremy@free-agent.fr>
  27. * @author Jordi Boggiano <j.boggiano@seld.be>
  28. */
  29. class RequireCommand extends InitCommand
  30. {
  31. protected function configure()
  32. {
  33. $this
  34. ->setName('require')
  35. ->setDescription('Adds required packages to your composer.json and installs them')
  36. ->setDefinition(array(
  37. new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
  38. new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
  39. new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
  40. new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
  41. new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
  42. new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
  43. new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
  44. new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'),
  45. new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
  46. new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
  47. ))
  48. ->setHelp(<<<EOT
  49. The require command adds required packages to your composer.json and installs them
  50. If you do not want to install the new dependencies immediately you can call it with --no-update
  51. EOT
  52. )
  53. ;
  54. }
  55. protected function execute(InputInterface $input, OutputInterface $output)
  56. {
  57. $file = Factory::getComposerFile();
  58. $newlyCreated = !file_exists($file);
  59. if (!file_exists($file) && !file_put_contents($file, "{\n}\n")) {
  60. $output->writeln('<error>'.$file.' could not be created.</error>');
  61. return 1;
  62. }
  63. if (!is_readable($file)) {
  64. $output->writeln('<error>'.$file.' is not readable.</error>');
  65. return 1;
  66. }
  67. if (!is_writable($file)) {
  68. $output->writeln('<error>'.$file.' is not writable.</error>');
  69. return 1;
  70. }
  71. $json = new JsonFile($file);
  72. $composerDefinition = $json->read();
  73. $composerBackup = file_get_contents($json->getPath());
  74. $composer = $this->getComposer();
  75. $repos = $composer->getRepositoryManager()->getRepositories();
  76. $this->repos = new CompositeRepository(array_merge(
  77. array(new PlatformRepository),
  78. $repos
  79. ));
  80. $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
  81. $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
  82. $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
  83. $baseRequirements = array_key_exists($requireKey, $composerDefinition) ? $composerDefinition[$requireKey] : array();
  84. $requirements = $this->formatRequirements($requirements);
  85. // validate requirements format
  86. $versionParser = new VersionParser();
  87. foreach ($requirements as $constraint) {
  88. $versionParser->parseConstraints($constraint);
  89. }
  90. $sortPackages = $input->getOption('sort-packages');
  91. if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey, $removeKey, $sortPackages)) {
  92. foreach ($requirements as $package => $version) {
  93. $baseRequirements[$package] = $version;
  94. if (isset($composerDefinition[$removeKey][$package])) {
  95. unset($composerDefinition[$removeKey][$package]);
  96. }
  97. }
  98. $composerDefinition[$requireKey] = $baseRequirements;
  99. $json->write($composerDefinition);
  100. }
  101. $output->writeln('<info>'.$file.' has been '.($newlyCreated ? 'created' : 'updated').'</info>');
  102. if ($input->getOption('no-update')) {
  103. return 0;
  104. }
  105. $updateDevMode = !$input->getOption('update-no-dev');
  106. // Update packages
  107. $this->resetComposer();
  108. $composer = $this->getComposer();
  109. $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
  110. $io = $this->getIO();
  111. $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
  112. $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
  113. $install = Installer::create($io, $composer);
  114. $install
  115. ->setVerbose($input->getOption('verbose'))
  116. ->setPreferSource($input->getOption('prefer-source'))
  117. ->setPreferDist($input->getOption('prefer-dist'))
  118. ->setDevMode($updateDevMode)
  119. ->setUpdate(true)
  120. ->setUpdateWhitelist(array_keys($requirements))
  121. ->setWhitelistDependencies($input->getOption('update-with-dependencies'))
  122. ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
  123. ;
  124. $status = $install->run();
  125. if ($status !== 0) {
  126. if ($newlyCreated) {
  127. $output->writeln("\n".'<error>Installation failed, deleting '.$file.'.</error>');
  128. unlink($json->getPath());
  129. } else {
  130. $output->writeln("\n".'<error>Installation failed, reverting '.$file.' to its original content.</error>');
  131. file_put_contents($json->getPath(), $composerBackup);
  132. }
  133. }
  134. return $status;
  135. }
  136. private function updateFileCleanly($json, array $base, array $new, $requireKey, $removeKey, $sortPackages)
  137. {
  138. $contents = file_get_contents($json->getPath());
  139. $manipulator = new JsonManipulator($contents);
  140. foreach ($new as $package => $constraint) {
  141. if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) {
  142. return false;
  143. }
  144. if (!$manipulator->removeSubNode($removeKey, $package)) {
  145. return false;
  146. }
  147. }
  148. file_put_contents($json->getPath(), $manipulator->getContents());
  149. return true;
  150. }
  151. protected function interact(InputInterface $input, OutputInterface $output)
  152. {
  153. return;
  154. }
  155. }