Rule.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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\DependencyResolver;
  12. use Composer\Package\CompletePackage;
  13. use Composer\Package\Link;
  14. use Composer\Package\PackageInterface;
  15. use Composer\Repository\RepositorySet;
  16. /**
  17. * @author Nils Adermann <naderman@naderman.de>
  18. * @author Ruben Gonzalez <rubenrua@gmail.com>
  19. */
  20. abstract class Rule
  21. {
  22. // reason constants
  23. const RULE_INTERNAL_ALLOW_UPDATE = 1;
  24. const RULE_ROOT_REQUIRE = 2;
  25. const RULE_FIXED = 3;
  26. const RULE_PACKAGE_CONFLICT = 6;
  27. const RULE_PACKAGE_REQUIRES = 7;
  28. const RULE_PACKAGE_SAME_NAME = 10;
  29. const RULE_LEARNED = 12;
  30. const RULE_PACKAGE_ALIAS = 13;
  31. // bitfield defs
  32. const BITFIELD_TYPE = 0;
  33. const BITFIELD_REASON = 8;
  34. const BITFIELD_DISABLED = 16;
  35. protected $bitfield;
  36. protected $request;
  37. protected $reasonData;
  38. /**
  39. * @param int $reason A RULE_* constant describing the reason for generating this rule
  40. * @param Link|PackageInterface $reasonData
  41. */
  42. public function __construct($reason, $reasonData)
  43. {
  44. $this->reasonData = $reasonData;
  45. $this->bitfield = (0 << self::BITFIELD_DISABLED) |
  46. ($reason << self::BITFIELD_REASON) |
  47. (255 << self::BITFIELD_TYPE);
  48. }
  49. abstract public function getLiterals();
  50. abstract public function getHash();
  51. abstract public function equals(Rule $rule);
  52. public function getReason()
  53. {
  54. return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON;
  55. }
  56. public function getReasonData()
  57. {
  58. return $this->reasonData;
  59. }
  60. public function getRequiredPackage()
  61. {
  62. $reason = $this->getReason();
  63. if ($reason === self::RULE_ROOT_REQUIRE) {
  64. return $this->reasonData['packageName'];
  65. }
  66. if ($reason === self::RULE_FIXED) {
  67. return $this->reasonData['package']->getName();
  68. }
  69. if ($reason === self::RULE_PACKAGE_REQUIRES) {
  70. return $this->reasonData->getTarget();
  71. }
  72. }
  73. public function setType($type)
  74. {
  75. $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE);
  76. }
  77. public function getType()
  78. {
  79. return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE;
  80. }
  81. public function disable()
  82. {
  83. $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED);
  84. }
  85. public function enable()
  86. {
  87. $this->bitfield &= ~(255 << self::BITFIELD_DISABLED);
  88. }
  89. public function isDisabled()
  90. {
  91. return (bool) (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
  92. }
  93. public function isEnabled()
  94. {
  95. return !(($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
  96. }
  97. abstract public function isAssertion();
  98. public function isCausedByLock()
  99. {
  100. return $this->getReason() === self::RULE_FIXED && $this->reasonData['lockable'];
  101. }
  102. public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, array $installedMap = array(), array $learnedPool = array())
  103. {
  104. $literals = $this->getLiterals();
  105. $ruleText = '';
  106. foreach ($literals as $i => $literal) {
  107. if ($i != 0) {
  108. $ruleText .= '|';
  109. }
  110. $ruleText .= $pool->literalToPrettyString($literal, $installedMap);
  111. }
  112. switch ($this->getReason()) {
  113. case self::RULE_INTERNAL_ALLOW_UPDATE:
  114. return $ruleText;
  115. case self::RULE_ROOT_REQUIRE:
  116. $packageName = $this->reasonData['packageName'];
  117. $constraint = $this->reasonData['constraint'];
  118. $packages = $pool->whatProvides($packageName, $constraint);
  119. if (!$packages) {
  120. return 'No package found to satisfy root composer.json require '.$packageName.($constraint ? ' '.$constraint->getPrettyString() : '');
  121. }
  122. return 'Root composer.json requires '.$packageName.($constraint ? ' '.$constraint->getPrettyString() : '').' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages).'.';
  123. case self::RULE_FIXED:
  124. $package = $this->reasonData['package'];
  125. if ($this->reasonData['lockable']) {
  126. return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion().' and an update of this package was not requested.';
  127. }
  128. return $package->getPrettyName().' is present at version '.$package->getPrettyVersion() . ' and cannot be modified by Composer';
  129. case self::RULE_PACKAGE_CONFLICT:
  130. $package1 = $pool->literalToPackage($literals[0]);
  131. $package2 = $pool->literalToPackage($literals[1]);
  132. return $package2->getPrettyString().' conflicts with '.$package1->getPrettyString().'.';
  133. case self::RULE_PACKAGE_REQUIRES:
  134. $sourceLiteral = array_shift($literals);
  135. $sourcePackage = $pool->literalToPackage($sourceLiteral);
  136. $requires = array();
  137. foreach ($literals as $literal) {
  138. $requires[] = $pool->literalToPackage($literal);
  139. }
  140. $text = $this->reasonData->getPrettyString($sourcePackage);
  141. if ($requires) {
  142. $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires) . '.';
  143. } else {
  144. $targetName = $this->reasonData->getTarget();
  145. $reason = Problem::getMissingPackageReason($repositorySet, $request, $pool, $targetName, $this->reasonData->getConstraint());
  146. return $text . ' -> ' . $reason[1];
  147. }
  148. return $text;
  149. case self::RULE_PACKAGE_SAME_NAME:
  150. $packageNames = array();
  151. foreach ($literals as $literal) {
  152. $package = $pool->literalToPackage($literal);
  153. $packageNames[$package->getName()] = true;
  154. }
  155. $replacedName = $this->reasonData;
  156. if (count($packageNames) > 1) {
  157. $reason = null;
  158. if (!isset($packageNames[$replacedName])) {
  159. $reason = 'They '.(count($literals) == 2 ? 'both' : 'all').' replace '.$replacedName.' and thus cannot coexist.';
  160. } else {
  161. $replacerNames = $packageNames;
  162. unset($replacerNames[$replacedName]);
  163. $replacerNames = array_keys($replacerNames);
  164. if (count($replacerNames) == 1) {
  165. $reason = $replacerNames[0] . ' replaces ';
  166. } else {
  167. $reason = '['.implode(', ', $replacerNames).'] replace ';
  168. }
  169. $reason .= $replacedName.' and thus cannot coexist with it.';
  170. }
  171. $installedPackages = array();
  172. $removablePackages = array();
  173. foreach ($literals as $literal) {
  174. if (isset($installedMap[abs($literal)])) {
  175. $installedPackages[] = $pool->literalToPackage($literal);
  176. } else {
  177. $removablePackages[] = $pool->literalToPackage($literal);
  178. }
  179. }
  180. if ($installedPackages && $removablePackages) {
  181. return $this->formatPackagesUnique($pool, $removablePackages).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages).'. '.$reason;
  182. }
  183. return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals).'. '.$reason;
  184. }
  185. return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals) . '.';
  186. case self::RULE_LEARNED:
  187. if (isset($learnedPool[$this->reasonData])) {
  188. $learnedString = ', learned rules:'."\n - ";
  189. $reasons = array();
  190. foreach ($learnedPool[$this->reasonData] as $learnedRule) {
  191. $reasons[] = $learnedRule->getPrettyString($repositorySet, $request, $pool, $installedMap, $learnedPool);
  192. }
  193. $learnedString .= implode("\n - ", array_unique($reasons));
  194. } else {
  195. $learnedString = ' (reasoning unavailable)';
  196. }
  197. return 'Conclusion: '.$ruleText.$learnedString;
  198. case self::RULE_PACKAGE_ALIAS:
  199. return $ruleText;
  200. default:
  201. return '('.$ruleText.')';
  202. }
  203. }
  204. /**
  205. * @param Pool $pool
  206. * @param array $packages
  207. *
  208. * @return string
  209. */
  210. protected function formatPackagesUnique($pool, array $packages)
  211. {
  212. $prepared = array();
  213. foreach ($packages as $index => $package) {
  214. if (!is_object($package)) {
  215. $packages[$index] = $pool->literalToPackage($package);
  216. }
  217. }
  218. return Problem::getPackageList($packages);
  219. }
  220. private function getReplacedNames(PackageInterface $package)
  221. {
  222. $names = array();
  223. foreach ($package->getReplaces() as $link) {
  224. $names[] = $link->getTarget();
  225. }
  226. return $names;
  227. }
  228. }