LockTransaction.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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\DependencyResolver\Operation\OperationInterface;
  13. use Composer\Package\AliasPackage;
  14. use Composer\Package\RootAliasPackage;
  15. use Composer\Package\RootPackageInterface;
  16. use Composer\Repository\ArrayRepository;
  17. use Composer\Repository\RepositoryInterface;
  18. use Composer\Test\Repository\ArrayRepositoryTest;
  19. /**
  20. * @author Nils Adermann <naderman@naderman.de>
  21. */
  22. class LockTransaction
  23. {
  24. protected $policy;
  25. /** @var Pool */
  26. protected $pool;
  27. /**
  28. * packages in current lock file, platform repo or otherwise present
  29. * @var array
  30. */
  31. protected $presentMap;
  32. /**
  33. * Packages which cannot be mapped, platform repo, root package, other fixed repos
  34. * @var array
  35. */
  36. protected $unlockableMap;
  37. protected $decisions;
  38. protected $resultPackages;
  39. /**
  40. * @var array
  41. */
  42. protected $operations;
  43. public function __construct($policy, $pool, $presentMap, $unlockableMap, $decisions)
  44. {
  45. $this->policy = $policy;
  46. $this->pool = $pool;
  47. $this->presentMap = $presentMap;
  48. $this->unlockableMap = $unlockableMap;
  49. $this->decisions = $decisions;
  50. $this->operations = $this->calculateOperations();
  51. }
  52. /**
  53. * @return OperationInterface[]
  54. */
  55. public function getOperations()
  56. {
  57. return $this->operations;
  58. }
  59. protected function calculateOperations()
  60. {
  61. $operations = array();
  62. $ignoreRemove = array();
  63. $lockMeansUpdateMap = $this->findPotentialUpdates();
  64. foreach ($this->decisions as $i => $decision) {
  65. $literal = $decision[Decisions::DECISION_LITERAL];
  66. $reason = $decision[Decisions::DECISION_REASON];
  67. $package = $this->pool->literalToPackage($literal);
  68. // wanted & !present
  69. if ($literal > 0 && !isset($this->presentMap[spl_object_hash($package)])) {
  70. if (isset($lockMeansUpdateMap[spl_object_hash($package)]) && !$package instanceof AliasPackage) {
  71. // TODO we end up here sometimes because we prefer the remote package now to get up to date metadata
  72. // TODO define some level of identity here for what constitutes an update and what can be ignored? new kind of metadata only update?
  73. $target = $lockMeansUpdateMap[spl_object_hash($package)];
  74. if ($package->getName() !== $target->getName() || $package->getVersion() !== $target->getVersion()) {
  75. $operations[] = new Operation\UpdateOperation($target, $package, $reason);
  76. }
  77. // avoid updates to one package from multiple origins
  78. $ignoreRemove[spl_object_hash($lockMeansUpdateMap[spl_object_hash($package)])] = true;
  79. unset($lockMeansUpdateMap[spl_object_hash($package)]);
  80. } else {
  81. if ($package instanceof AliasPackage) {
  82. $operations[] = new Operation\MarkAliasInstalledOperation($package, $reason);
  83. } else {
  84. $operations[] = new Operation\InstallOperation($package, $reason);
  85. }
  86. }
  87. }
  88. }
  89. foreach ($this->decisions as $i => $decision) {
  90. $literal = $decision[Decisions::DECISION_LITERAL];
  91. $reason = $decision[Decisions::DECISION_REASON];
  92. $package = $this->pool->literalToPackage($literal);
  93. if ($literal <= 0 && isset($this->presentMap[spl_object_hash($package)]) && !isset($ignoreRemove[spl_object_hash($package)])) {
  94. if ($package instanceof AliasPackage) {
  95. $operations[] = new Operation\MarkAliasUninstalledOperation($package, $reason);
  96. } else {
  97. $operations[] = new Operation\UninstallOperation($package, $reason);
  98. }
  99. }
  100. }
  101. foreach ($this->presentMap as $package) {
  102. if ($package->id === -1 && !isset($ignoreRemove[spl_object_hash($package)])) {
  103. // TODO pass reason parameter to these two operations?
  104. if ($package instanceof AliasPackage) {
  105. $operations[] = new Operation\MarkAliasUninstalledOperation($package);
  106. } else {
  107. $operations[] = new Operation\UninstallOperation($package);
  108. }
  109. }
  110. }
  111. $this->setResultPackages();
  112. return $operations;
  113. }
  114. // TODO make this a bit prettier instead of the two text indexes?
  115. public function setResultPackages()
  116. {
  117. $this->resultPackages = array('non-dev' => array(), 'dev' => array());
  118. foreach ($this->decisions as $i => $decision) {
  119. $literal = $decision[Decisions::DECISION_LITERAL];
  120. if ($literal > 0) {
  121. $package = $this->pool->literalToPackage($literal);
  122. if (!isset($this->unlockableMap[$package->id])) {
  123. $this->resultPackages['non-dev'][] = $package;
  124. }
  125. }
  126. }
  127. }
  128. public function setNonDevPackages(LockTransaction $extractionResult)
  129. {
  130. $packages = $extractionResult->getNewLockPackages(false);
  131. $this->resultPackages['dev'] = $this->resultPackages['non-dev'];
  132. $this->resultPackages['non-dev'] = array();
  133. foreach ($packages as $package) {
  134. foreach ($this->resultPackages['dev'] as $i => $resultPackage) {
  135. // TODO this comparison is probably insufficient, aliases, what about modified versions? I guess they aren't possible?
  136. if ($package->getName() == $resultPackage->getName()) {
  137. $this->resultPackages['non-dev'][] = $resultPackage;
  138. unset($this->resultPackages['dev'][$i]);
  139. }
  140. }
  141. }
  142. }
  143. // TODO additionalFixedRepository needs to be looked at here as well?
  144. public function getNewLockPackages($devMode, $updateMirrors = false)
  145. {
  146. $packages = array();
  147. foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) {
  148. if (!($package instanceof AliasPackage) && !($package instanceof RootAliasPackage)) {
  149. // if we're just updating mirrors we need to reset references to the same as currently "present" packages' references to keep the lock file as-is
  150. // we do not reset references if the currently present package didn't have any, or if the type of VCS has changed
  151. if ($updateMirrors && !isset($this->presentMap[spl_object_hash($package)])) {
  152. foreach ($this->presentMap as $presentPackage) {
  153. if ($package->getName() == $presentPackage->getName() &&
  154. $package->getVersion() == $presentPackage->getVersion() &&
  155. $presentPackage->getSourceReference() &&
  156. $presentPackage->getSourceType() === $package->getSourceType()
  157. ) {
  158. $package->setSourceDistReferences($presentPackage->getSourceReference());
  159. }
  160. }
  161. }
  162. $packages[] = $package;
  163. }
  164. }
  165. return $packages;
  166. }
  167. protected function findPotentialUpdates()
  168. {
  169. $lockMeansUpdateMap = array();
  170. $packages = array();
  171. foreach ($this->decisions as $i => $decision) {
  172. $literal = $decision[Decisions::DECISION_LITERAL];
  173. $package = $this->pool->literalToPackage($literal);
  174. if ($literal <= 0 && isset($this->presentMap[spl_object_hash($package)])) {
  175. $packages[spl_object_hash($package)] = $package;
  176. }
  177. }
  178. // some locked packages are not in the pool and thus, were not decided at all
  179. foreach ($this->presentMap as $package) {
  180. if ($package->id === -1) {
  181. $packages[spl_object_hash($package)] = $package;
  182. }
  183. }
  184. foreach ($packages as $package) {
  185. if ($package instanceof AliasPackage) {
  186. continue;
  187. }
  188. // TODO can't we just look at existing rules?
  189. $updates = $this->policy->findUpdatePackages($this->pool, $package);
  190. $updatesAndPackage = array_merge(array($package), $updates);
  191. foreach ($updatesAndPackage as $update) {
  192. if (!isset($lockMeansUpdateMap[spl_object_hash($update)])) {
  193. $lockMeansUpdateMap[spl_object_hash($update)] = $package;
  194. }
  195. }
  196. }
  197. return $lockMeansUpdateMap;
  198. }
  199. }