Pool.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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\BasePackage;
  13. use Composer\Package\AliasPackage;
  14. use Composer\Package\Version\VersionParser;
  15. use Composer\Package\LinkConstraint\LinkConstraintInterface;
  16. use Composer\Package\LinkConstraint\VersionConstraint;
  17. use Composer\Package\LinkConstraint\EmptyConstraint;
  18. use Composer\Repository\RepositoryInterface;
  19. use Composer\Repository\CompositeRepository;
  20. use Composer\Repository\ComposerRepository;
  21. use Composer\Repository\InstalledRepositoryInterface;
  22. use Composer\Repository\PlatformRepository;
  23. use Composer\Package\PackageInterface;
  24. /**
  25. * A package pool contains repositories that provide packages.
  26. *
  27. * @author Nils Adermann <naderman@naderman.de>
  28. * @author Jordi Boggiano <j.boggiano@seld.be>
  29. */
  30. class Pool
  31. {
  32. const MATCH_NAME = -1;
  33. const MATCH_NONE = 0;
  34. const MATCH = 1;
  35. const MATCH_PROVIDE = 2;
  36. const MATCH_REPLACE = 3;
  37. const MATCH_FILTERED = 4;
  38. protected $repositories = array();
  39. protected $providerRepos = array();
  40. protected $packages = array();
  41. protected $packageByName = array();
  42. protected $packageByExactName = array();
  43. protected $acceptableStabilities;
  44. protected $stabilityFlags;
  45. protected $versionParser;
  46. protected $providerCache = array();
  47. protected $filterRequires;
  48. protected $whitelist = null;
  49. protected $id = 1;
  50. public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array())
  51. {
  52. $stabilities = BasePackage::$stabilities;
  53. $this->versionParser = new VersionParser;
  54. $this->acceptableStabilities = array();
  55. foreach (BasePackage::$stabilities as $stability => $value) {
  56. if ($value <= BasePackage::$stabilities[$minimumStability]) {
  57. $this->acceptableStabilities[$stability] = $value;
  58. }
  59. }
  60. $this->stabilityFlags = $stabilityFlags;
  61. $this->filterRequires = $filterRequires;
  62. foreach ($filterRequires as $name => $constraint) {
  63. if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
  64. unset($this->filterRequires[$name]);
  65. }
  66. }
  67. }
  68. public function setWhitelist($whitelist)
  69. {
  70. $this->whitelist = $whitelist;
  71. $this->providerCache = array();
  72. }
  73. /**
  74. * Adds a repository and its packages to this package pool
  75. *
  76. * @param RepositoryInterface $repo A package repository
  77. * @param array $rootAliases
  78. */
  79. public function addRepository(RepositoryInterface $repo, $rootAliases = array())
  80. {
  81. if ($repo instanceof CompositeRepository) {
  82. $repos = $repo->getRepositories();
  83. } else {
  84. $repos = array($repo);
  85. }
  86. foreach ($repos as $repo) {
  87. $this->repositories[] = $repo;
  88. $exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;
  89. if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
  90. $this->providerRepos[] = $repo;
  91. $repo->setRootAliases($rootAliases);
  92. $repo->resetPackageIds();
  93. } else {
  94. foreach ($repo->getPackages() as $package) {
  95. $names = $package->getNames();
  96. $stability = $package->getStability();
  97. if ($exempt || $this->isPackageAcceptable($names, $stability)) {
  98. $package->setId($this->id++);
  99. $this->packages[] = $package;
  100. $this->packageByExactName[$package->getName()][$package->id] = $package;
  101. foreach ($names as $provided) {
  102. $this->packageByName[$provided][] = $package;
  103. }
  104. // handle root package aliases
  105. $name = $package->getName();
  106. if (isset($rootAliases[$name][$package->getVersion()])) {
  107. $alias = $rootAliases[$name][$package->getVersion()];
  108. if ($package instanceof AliasPackage) {
  109. $package = $package->getAliasOf();
  110. }
  111. $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
  112. $aliasPackage->setRootPackageAlias(true);
  113. $aliasPackage->setId($this->id++);
  114. $package->getRepository()->addPackage($aliasPackage);
  115. $this->packages[] = $aliasPackage;
  116. $this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage;
  117. foreach ($aliasPackage->getNames() as $name) {
  118. $this->packageByName[$name][] = $aliasPackage;
  119. }
  120. }
  121. }
  122. }
  123. }
  124. }
  125. }
  126. public function getPriority(RepositoryInterface $repo)
  127. {
  128. $priority = array_search($repo, $this->repositories, true);
  129. if (false === $priority) {
  130. throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool.");
  131. }
  132. return -$priority;
  133. }
  134. /**
  135. * Retrieves the package object for a given package id.
  136. *
  137. * @param int $id
  138. * @return PackageInterface
  139. */
  140. public function packageById($id)
  141. {
  142. return $this->packages[$id - 1];
  143. }
  144. /**
  145. * Searches all packages providing the given package name and match the constraint
  146. *
  147. * @param string $name The package name to be searched for
  148. * @param LinkConstraintInterface $constraint A constraint that all returned
  149. * packages must match or null to return all
  150. * @param bool $mustMatchName Whether the name of returned packages
  151. * must match the given name
  152. * @return PackageInterface[] A set of packages
  153. */
  154. public function whatProvides($name, LinkConstraintInterface $constraint = null, $mustMatchName = false)
  155. {
  156. $key = ((int) $mustMatchName).$constraint;
  157. if (isset($this->providerCache[$name][$key])) {
  158. return $this->providerCache[$name][$key];
  159. }
  160. return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName);
  161. }
  162. /**
  163. * @see whatProvides
  164. */
  165. private function computeWhatProvides($name, $constraint, $mustMatchName = false)
  166. {
  167. $candidates = array();
  168. foreach ($this->providerRepos as $repo) {
  169. foreach ($repo->whatProvides($this, $name) as $candidate) {
  170. $candidates[] = $candidate;
  171. if ($candidate->id < 1) {
  172. $candidate->setId($this->id++);
  173. $this->packages[$this->id - 2] = $candidate;
  174. }
  175. }
  176. }
  177. if ($mustMatchName) {
  178. $candidates = array_filter($candidates, function ($candidate) use ($name) {
  179. return $candidate->getName() == $name;
  180. });
  181. if (isset($this->packageByExactName[$name])) {
  182. $candidates = array_merge($candidates, $this->packageByExactName[$name]);
  183. }
  184. } elseif (isset($this->packageByName[$name])) {
  185. $candidates = array_merge($candidates, $this->packageByName[$name]);
  186. }
  187. $matches = $provideMatches = array();
  188. $nameMatch = false;
  189. foreach ($candidates as $candidate) {
  190. $aliasOfCandidate = null;
  191. // alias packages are not white listed, make sure that the package
  192. // being aliased is white listed
  193. if ($candidate instanceof AliasPackage) {
  194. $aliasOfCandidate = $candidate->getAliasOf();
  195. }
  196. if ($this->whitelist !== null && (
  197. (!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) ||
  198. ($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id]))
  199. )) {
  200. continue;
  201. }
  202. switch ($this->match($candidate, $name, $constraint)) {
  203. case self::MATCH_NONE:
  204. break;
  205. case self::MATCH_NAME:
  206. $nameMatch = true;
  207. break;
  208. case self::MATCH:
  209. $nameMatch = true;
  210. $matches[] = $candidate;
  211. break;
  212. case self::MATCH_PROVIDE:
  213. $provideMatches[] = $candidate;
  214. break;
  215. case self::MATCH_REPLACE:
  216. $matches[] = $candidate;
  217. break;
  218. case self::MATCH_FILTERED:
  219. break;
  220. default:
  221. throw new \UnexpectedValueException('Unexpected match type');
  222. }
  223. }
  224. // if a package with the required name exists, we ignore providers
  225. if ($nameMatch) {
  226. return $matches;
  227. }
  228. return array_merge($matches, $provideMatches);
  229. }
  230. public function literalToPackage($literal)
  231. {
  232. $packageId = abs($literal);
  233. return $this->packageById($packageId);
  234. }
  235. public function literalToString($literal)
  236. {
  237. return ($literal > 0 ? '+' : '-') . $this->literalToPackage($literal);
  238. }
  239. public function literalToPrettyString($literal, $installedMap)
  240. {
  241. $package = $this->literalToPackage($literal);
  242. if (isset($installedMap[$package->id])) {
  243. $prefix = ($literal > 0 ? 'keep' : 'remove');
  244. } else {
  245. $prefix = ($literal > 0 ? 'install' : 'don\'t install');
  246. }
  247. return $prefix.' '.$package->getPrettyString();
  248. }
  249. public function isPackageAcceptable($name, $stability)
  250. {
  251. foreach ((array) $name as $n) {
  252. // allow if package matches the global stability requirement and has no exception
  253. if (!isset($this->stabilityFlags[$n]) && isset($this->acceptableStabilities[$stability])) {
  254. return true;
  255. }
  256. // allow if package matches the package-specific stability flag
  257. if (isset($this->stabilityFlags[$n]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$n]) {
  258. return true;
  259. }
  260. }
  261. return false;
  262. }
  263. /**
  264. * Checks if the package matches the given constraint directly or through
  265. * provided or replaced packages
  266. *
  267. * @param array|PackageInterface $candidate
  268. * @param string $name Name of the package to be matched
  269. * @param LinkConstraintInterface $constraint The constraint to verify
  270. * @return int One of the MATCH* constants of this class or 0 if there is no match
  271. */
  272. private function match($candidate, $name, LinkConstraintInterface $constraint = null)
  273. {
  274. $candidateName = $candidate->getName();
  275. $candidateVersion = $candidate->getVersion();
  276. $isDev = $candidate->getStability() === 'dev';
  277. $isAlias = $candidate instanceof AliasPackage;
  278. if (!$isDev && !$isAlias && isset($this->filterRequires[$name])) {
  279. $requireFilter = $this->filterRequires[$name];
  280. } else {
  281. $requireFilter = new EmptyConstraint;
  282. }
  283. if ($candidateName === $name) {
  284. $pkgConstraint = new VersionConstraint('==', $candidateVersion);
  285. if ($constraint === null || $constraint->matches($pkgConstraint)) {
  286. return $requireFilter->matches($pkgConstraint) ? self::MATCH : self::MATCH_FILTERED;
  287. }
  288. return self::MATCH_NAME;
  289. }
  290. $provides = $candidate->getProvides();
  291. $replaces = $candidate->getReplaces();
  292. // aliases create multiple replaces/provides for one target so they can not use the shortcut below
  293. if (isset($replaces[0]) || isset($provides[0])) {
  294. foreach ($provides as $link) {
  295. if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
  296. return $requireFilter->matches($link->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED;
  297. }
  298. }
  299. foreach ($replaces as $link) {
  300. if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
  301. return $requireFilter->matches($link->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED;
  302. }
  303. }
  304. return self::MATCH_NONE;
  305. }
  306. if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) {
  307. return $requireFilter->matches($provides[$name]->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED;
  308. }
  309. if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) {
  310. return $requireFilter->matches($replaces[$name]->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED;
  311. }
  312. return self::MATCH_NONE;
  313. }
  314. }