/src/PEAR2/Pyrus/Package/Dependency/Set.php

https://github.com/boekkooi/PEAR2_Pyrus · PHP · 285 lines · 201 code · 34 blank · 50 comment · 37 complexity · ad2778c21c5bdc0fed7dc380567a78cc MD5 · raw file

  1. <?php
  2. /**
  3. * \PEAR2\Pyrus\Dependency\Set
  4. *
  5. * PHP version 5
  6. *
  7. * @category PEAR2
  8. * @package PEAR2_Pyrus
  9. * @author Greg Beaver <cellog@php.net>
  10. * @copyright 2010 The PEAR Group
  11. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  12. * @version SVN: $Id$
  13. * @link http://svn.php.net/viewvc/pear2/Pyrus/
  14. */
  15. /**
  16. * Implements a set of dependency trees, and manipulates the trees to combine
  17. * them into a unique set of package releases to download
  18. *
  19. * This structure allows us to properly determine the best version, if any,
  20. * of a package that satisfies all dependencies of both packages to download and
  21. * installed packages.
  22. *
  23. * @category PEAR2
  24. * @package PEAR2_Pyrus
  25. * @author Greg Beaver <cellog@php.net>
  26. * @copyright 2010 The PEAR Group
  27. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  28. * @link http://svn.php.net/viewvc/pear2/Pyrus/
  29. */
  30. namespace PEAR2\Pyrus\Package\Dependency;
  31. class Set
  32. {
  33. protected $packageTrees = array();
  34. protected $duplicates = array();
  35. protected $optionalDeps = array();
  36. function __construct(array $packages)
  37. {
  38. Set\PackageTree::setLocalPackages($packages);
  39. foreach ($packages as $package) {
  40. $tree = new Set\PackageTree($this, $package);
  41. $this->packageTrees[] = $tree;
  42. while (!$tree->populate()) {
  43. $this->refine();
  44. }
  45. //echo $tree; // uncomment to get a map of each separate dep tree
  46. }
  47. }
  48. function retrieveAllPackages()
  49. {
  50. $ret = array();
  51. $this->optionalDeps = Set\PackageTree::getUnusedOptionalDeps();
  52. foreach ($this->packageTrees as $tree) {
  53. foreach ($tree->getPackageSet() as $package) {
  54. $name = $package->name();
  55. $ret[$name] = $package->node;
  56. if (isset($this->optionalDeps[$name])) {
  57. unset($this->optionalDeps[$name]);
  58. }
  59. }
  60. }
  61. return $ret;
  62. }
  63. function synchronizeDeps()
  64. {
  65. foreach ($this->packageTrees as $tree) {
  66. $tree->synchronize();
  67. //echo $tree; // uncomment to get a map of each separate dep tree
  68. }
  69. }
  70. function refine()
  71. {
  72. $dirty = Set\PackageTree::dirtyNodes();
  73. while (count($dirty)) {
  74. foreach ($dirty as $i => $node) {
  75. foreach ($this->packageTrees as $package) {
  76. if (!$package->has($node)) {
  77. continue;
  78. }
  79. $package->rebuildIfNecessary($node);
  80. }
  81. $dirty[$i] = null;
  82. }
  83. $dirty = array_filter($dirty);
  84. }
  85. }
  86. function getIgnoredOptionalDeps()
  87. {
  88. return $this->optionalDeps;
  89. }
  90. function getDependencies(\PEAR2\Pyrus\Package $info)
  91. {
  92. $deps = array();
  93. foreach ($this->packageTrees as $tree) {
  94. $deps = $tree->getDependencies($deps, $info->channel . '/' . $info->name);
  95. }
  96. return array_merge($deps, $this->getDependenciesOn($info));
  97. }
  98. function getDependenciesOn($info)
  99. {
  100. $name = $info->name;
  101. $channel = $info->channel;
  102. $packages = \PEAR2\Pyrus\Config::current()->registry
  103. ->getDependentPackages($info->getPackageFileObject());
  104. $ret = array();
  105. foreach ($packages as $package) {
  106. $deps = $package->dependencies;
  107. foreach (array('package', 'subpackage') as $type) {
  108. foreach (array('required', 'optional') as $required) {
  109. foreach ($deps[$required]->$type as $dep) {
  110. if ($dep->channel != $channel || $dep->name != $name) {
  111. continue;
  112. }
  113. $ret[] = $dep;
  114. }
  115. }
  116. }
  117. }
  118. return $ret;
  119. }
  120. /**
  121. * Return a composite dependency on the package, as defined by combining
  122. * all dependencies on this package into one.
  123. *
  124. * As an example, for these dependencies:
  125. *
  126. * <pre>
  127. * P1 version >= 1.2.0
  128. * P1 version <= 3.0.0, != 2.3.2
  129. * P1 version >= 1.1.0, != 1.2.0
  130. * </pre>
  131. *
  132. * The composite dependency is
  133. *
  134. * <pre>
  135. * P1 version >= 1.2.0, <= 3.0.0, != 2.3.2, 1.2.0
  136. * </pre>
  137. * @param PEAR2\Pyrus\Package $info
  138. * @param bool $conflicting if true, return a composite <conflicts> dependency, if any
  139. */
  140. function getCompositeDependency(\PEAR2\Pyrus\Package $info, $conflicting = false)
  141. {
  142. $deps = $this->getDependencies($info);
  143. if (!count($deps)) {
  144. $dep = new \PEAR2\Pyrus\PackageFile\v2\Dependencies\Package(
  145. 'required', 'package', null, array('name' => $info->name, 'channel' => $info->channel, 'uri' => null,
  146. 'min' => null, 'max' => null,
  147. 'recommended' => null, 'exclude' => null,
  148. 'providesextension' => null, 'conflicts' => null), 0);
  149. $dep->setCompositeSources(array());
  150. return $dep;
  151. }
  152. $compdep = array('name' => $info->name, 'channel' => $info->channel, 'uri' => null,
  153. 'min' => null, 'max' => null,
  154. 'recommended' => null, 'exclude' => null,
  155. 'providesextension' => null, 'conflicts' => null);
  156. $initial = true;
  157. $max = $min = $recommended = null;
  158. $useddeps = array();
  159. foreach ($deps as $actualdep) {
  160. if ($conflicting) {
  161. if (!$actualdep->conflicts) {
  162. continue;
  163. }
  164. $compdep['conflicts'] = '';
  165. } elseif ($actualdep->conflicts) {
  166. continue;
  167. }
  168. $useddeps[] = $actualdep;
  169. $deppackage = $actualdep->getPackageFile()->channel . '/' .
  170. $actualdep->getPackageFile()->name;
  171. if ($initial) {
  172. if ($actualdep->min) {
  173. $compdep['min'] = $actualdep->min;
  174. $min = $deppackage;
  175. }
  176. if ($actualdep->max) {
  177. $compdep['max'] = $actualdep->max;
  178. $max = $deppackage;
  179. }
  180. if ($actualdep->recommended) {
  181. $compdep['recommended'] = $actualdep->recommended;
  182. $recommended = $deppackage;
  183. }
  184. $compdep['exclude'] = $actualdep->exclude;
  185. $initial = false;
  186. continue;
  187. }
  188. if (isset($compdep['recommended']) && isset($actualdep->recommended)
  189. && $actualdep->recommended != $compdep['recommended']) {
  190. throw new \PEAR2\Pyrus\Package\Exception('Cannot install ' . $info->channel . '/' .
  191. $info->name . ', two dependencies conflict (different recommended values for ' .
  192. $deppackage . ' and ' . $recommended . ')');
  193. }
  194. if ($compdep['max'] && $actualdep->min && version_compare($actualdep->min, $compdep['max'], '>')) {
  195. throw new \PEAR2\Pyrus\Package\Exception('Cannot install ' . $info->channel . '/' .
  196. $info->name . ', two dependencies conflict (' .
  197. $deppackage . ' min is > ' . $max . ' max)');
  198. }
  199. if ($compdep['min'] && $actualdep->max && version_compare($actualdep->max, $compdep['min'], '<')) {
  200. throw new \PEAR2\Pyrus\Package\Exception('Cannot install ' . $info->channel . '/' .
  201. $info->name . ', two dependencies conflict (' .
  202. $deppackage . ' max is < ' . $min . ' min)');
  203. }
  204. if ($actualdep->min) {
  205. if ($compdep['min']) {
  206. if (version_compare($actualdep->min, $compdep['min'], '>')) {
  207. $compdep['min'] = $actualdep->min;
  208. $min = $deppackage;
  209. }
  210. } else {
  211. $compdep['min'] = $actualdep->min;
  212. $min = $deppackage;
  213. }
  214. }
  215. if ($actualdep->max) {
  216. if ($compdep['max']) {
  217. if (version_compare($actualdep->max, $compdep['max'], '<')) {
  218. $compdep['max'] = $actualdep->max;
  219. $max = $deppackage;
  220. }
  221. } else {
  222. $compdep['max'] = $actualdep->max;
  223. $max = $deppackage;
  224. }
  225. }
  226. if ($actualdep->recommended) {
  227. $compdep['recommended'] = $actualdep->recommended;
  228. $recommended = $deppackage;
  229. }
  230. if ($actualdep->exclude) {
  231. if (!$compdep['exclude']) {
  232. $compdep['exclude'] = array();
  233. foreach ($actualdep->exclude as $exclude) {
  234. $compdep['exclude'][] = $exclude;
  235. }
  236. continue;
  237. }
  238. foreach ($actualdep->exclude as $exclude) {
  239. if (in_array($exclude, $compdep['exclude'])) {
  240. continue;
  241. }
  242. $compdep['exclude'][] = $exclude;
  243. }
  244. }
  245. }
  246. $dep = new \PEAR2\Pyrus\PackageFile\v2\Dependencies\Package(
  247. 'required', 'package', null, $compdep, 0);
  248. $dep->setCompositeSources($useddeps);
  249. return $dep;
  250. }
  251. }