PageRenderTime 27ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/composer-master/src/Composer/DependencyResolver/RuleSetGenerator.php

https://bitbucket.org/columbkh/project
PHP | 337 lines | 208 code | 52 blank | 77 comment | 29 complexity | 4e54070c0ee09efcca1b035c87e63367 MD5 | raw file
  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\PackageInterface;
  13. use Composer\Package\AliasPackage;
  14. use Composer\Repository\PlatformRepository;
  15. /**
  16. * @author Nils Adermann <naderman@naderman.de>
  17. */
  18. class RuleSetGenerator
  19. {
  20. protected $policy;
  21. protected $pool;
  22. protected $rules;
  23. protected $jobs;
  24. protected $installedMap;
  25. protected $whitelistedMap;
  26. protected $addedMap;
  27. public function __construct(PolicyInterface $policy, Pool $pool)
  28. {
  29. $this->policy = $policy;
  30. $this->pool = $pool;
  31. }
  32. /**
  33. * Creates a new rule for the requirements of a package
  34. *
  35. * This rule is of the form (-A|B|C), where B and C are the providers of
  36. * one requirement of the package A.
  37. *
  38. * @param PackageInterface $package The package with a requirement
  39. * @param array $providers The providers of the requirement
  40. * @param int $reason A RULE_* constant describing the
  41. * reason for generating this rule
  42. * @param mixed $reasonData Any data, e.g. the requirement name,
  43. * that goes with the reason
  44. * @return Rule The generated rule or null if tautological
  45. */
  46. protected function createRequireRule(PackageInterface $package, array $providers, $reason, $reasonData = null)
  47. {
  48. $literals = array(-$package->id);
  49. foreach ($providers as $provider) {
  50. // self fulfilling rule?
  51. if ($provider === $package) {
  52. return null;
  53. }
  54. $literals[] = $provider->id;
  55. }
  56. return new GenericRule($literals, $reason, $reasonData);
  57. }
  58. /**
  59. * Creates a rule to install at least one of a set of packages
  60. *
  61. * The rule is (A|B|C) with A, B and C different packages. If the given
  62. * set of packages is empty an impossible rule is generated.
  63. *
  64. * @param array $packages The set of packages to choose from
  65. * @param int $reason A RULE_* constant describing the reason for
  66. * generating this rule
  67. * @param array $job The job this rule was created from
  68. * @return Rule The generated rule
  69. */
  70. protected function createInstallOneOfRule(array $packages, $reason, $job)
  71. {
  72. $literals = array();
  73. foreach ($packages as $package) {
  74. $literals[] = $package->id;
  75. }
  76. return new GenericRule($literals, $reason, $job['packageName'], $job);
  77. }
  78. /**
  79. * Creates a rule to remove a package
  80. *
  81. * The rule for a package A is (-A).
  82. *
  83. * @param PackageInterface $package The package to be removed
  84. * @param int $reason A RULE_* constant describing the
  85. * reason for generating this rule
  86. * @param array $job The job this rule was created from
  87. * @return Rule The generated rule
  88. */
  89. protected function createRemoveRule(PackageInterface $package, $reason, $job)
  90. {
  91. return new GenericRule(array(-$package->id), $reason, $job['packageName'], $job);
  92. }
  93. /**
  94. * Creates a rule for two conflicting packages
  95. *
  96. * The rule for conflicting packages A and B is (-A|-B). A is called the issuer
  97. * and B the provider.
  98. *
  99. * @param PackageInterface $issuer The package declaring the conflict
  100. * @param PackageInterface $provider The package causing the conflict
  101. * @param int $reason A RULE_* constant describing the
  102. * reason for generating this rule
  103. * @param mixed $reasonData Any data, e.g. the package name, that
  104. * goes with the reason
  105. * @return Rule The generated rule
  106. */
  107. protected function createRule2Literals(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
  108. {
  109. // ignore self conflict
  110. if ($issuer === $provider) {
  111. return null;
  112. }
  113. return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData);
  114. }
  115. /**
  116. * Adds a rule unless it duplicates an existing one of any type
  117. *
  118. * To be able to directly pass in the result of one of the rule creation
  119. * methods null is allowed which will not insert a rule.
  120. *
  121. * @param int $type A TYPE_* constant defining the rule type
  122. * @param Rule $newRule The rule about to be added
  123. */
  124. private function addRule($type, Rule $newRule = null)
  125. {
  126. if (!$newRule) {
  127. return;
  128. }
  129. $this->rules->add($newRule, $type);
  130. }
  131. protected function whitelistFromPackage(PackageInterface $package)
  132. {
  133. $workQueue = new \SplQueue;
  134. $workQueue->enqueue($package);
  135. while (!$workQueue->isEmpty()) {
  136. $package = $workQueue->dequeue();
  137. if (isset($this->whitelistedMap[$package->id])) {
  138. continue;
  139. }
  140. $this->whitelistedMap[$package->id] = true;
  141. foreach ($package->getRequires() as $link) {
  142. $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint(), true);
  143. foreach ($possibleRequires as $require) {
  144. $workQueue->enqueue($require);
  145. }
  146. }
  147. $obsoleteProviders = $this->pool->whatProvides($package->getName(), null, true);
  148. foreach ($obsoleteProviders as $provider) {
  149. if ($provider === $package) {
  150. continue;
  151. }
  152. if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
  153. $workQueue->enqueue($provider);
  154. }
  155. }
  156. }
  157. }
  158. protected function addRulesForPackage(PackageInterface $package, $ignorePlatformReqs)
  159. {
  160. $workQueue = new \SplQueue;
  161. $workQueue->enqueue($package);
  162. while (!$workQueue->isEmpty()) {
  163. $package = $workQueue->dequeue();
  164. if (isset($this->addedMap[$package->id])) {
  165. continue;
  166. }
  167. $this->addedMap[$package->id] = true;
  168. foreach ($package->getRequires() as $link) {
  169. if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
  170. continue;
  171. }
  172. $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  173. $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link));
  174. foreach ($possibleRequires as $require) {
  175. $workQueue->enqueue($require);
  176. }
  177. }
  178. foreach ($package->getConflicts() as $link) {
  179. $possibleConflicts = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  180. foreach ($possibleConflicts as $conflict) {
  181. $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link));
  182. }
  183. }
  184. // check obsoletes and implicit obsoletes of a package
  185. $isInstalled = (isset($this->installedMap[$package->id]));
  186. foreach ($package->getReplaces() as $link) {
  187. $obsoleteProviders = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());
  188. foreach ($obsoleteProviders as $provider) {
  189. if ($provider === $package) {
  190. continue;
  191. }
  192. if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
  193. $reason = ($isInstalled) ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
  194. $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
  195. }
  196. }
  197. }
  198. $obsoleteProviders = $this->pool->whatProvides($package->getName(), null);
  199. foreach ($obsoleteProviders as $provider) {
  200. if ($provider === $package) {
  201. continue;
  202. }
  203. if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
  204. $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
  205. } elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
  206. $reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
  207. $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRule2Literals($package, $provider, $reason, $package));
  208. }
  209. }
  210. }
  211. }
  212. protected function obsoleteImpossibleForAlias($package, $provider)
  213. {
  214. $packageIsAlias = $package instanceof AliasPackage;
  215. $providerIsAlias = $provider instanceof AliasPackage;
  216. $impossible = (
  217. ($packageIsAlias && $package->getAliasOf() === $provider) ||
  218. ($providerIsAlias && $provider->getAliasOf() === $package) ||
  219. ($packageIsAlias && $providerIsAlias && $provider->getAliasOf() === $package->getAliasOf())
  220. );
  221. return $impossible;
  222. }
  223. protected function whitelistFromJobs()
  224. {
  225. foreach ($this->jobs as $job) {
  226. switch ($job['cmd']) {
  227. case 'install':
  228. $packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true);
  229. foreach ($packages as $package) {
  230. $this->whitelistFromPackage($package);
  231. }
  232. break;
  233. }
  234. }
  235. }
  236. protected function addRulesForJobs($ignorePlatformReqs)
  237. {
  238. foreach ($this->jobs as $job) {
  239. switch ($job['cmd']) {
  240. case 'install':
  241. if (!$job['fixed'] && $ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
  242. break;
  243. }
  244. $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
  245. if ($packages) {
  246. foreach ($packages as $package) {
  247. if (!isset($this->installedMap[$package->id])) {
  248. $this->addRulesForPackage($package, $ignorePlatformReqs);
  249. }
  250. }
  251. $rule = $this->createInstallOneOfRule($packages, Rule::RULE_JOB_INSTALL, $job);
  252. $this->addRule(RuleSet::TYPE_JOB, $rule);
  253. }
  254. break;
  255. case 'remove':
  256. // remove all packages with this name including uninstalled
  257. // ones to make sure none of them are picked as replacements
  258. $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
  259. foreach ($packages as $package) {
  260. $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE, $job);
  261. $this->addRule(RuleSet::TYPE_JOB, $rule);
  262. }
  263. break;
  264. }
  265. }
  266. }
  267. public function getRulesFor($jobs, $installedMap, $ignorePlatformReqs = false)
  268. {
  269. $this->jobs = $jobs;
  270. $this->rules = new RuleSet;
  271. $this->installedMap = $installedMap;
  272. $this->whitelistedMap = array();
  273. foreach ($this->installedMap as $package) {
  274. $this->whitelistFromPackage($package);
  275. }
  276. $this->whitelistFromJobs();
  277. $this->pool->setWhitelist($this->whitelistedMap);
  278. $this->addedMap = array();
  279. foreach ($this->installedMap as $package) {
  280. $this->addRulesForPackage($package, $ignorePlatformReqs);
  281. }
  282. $this->addRulesForJobs($ignorePlatformReqs);
  283. return $this->rules;
  284. }
  285. }