RootPackageLoader.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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\Package\Loader;
  12. use Composer\Package\BasePackage;
  13. use Composer\Package\Version\VersionParser;
  14. use Composer\Repository\RepositoryManager;
  15. use Composer\Util\ProcessExecutor;
  16. /**
  17. * ArrayLoader built for the sole purpose of loading the root package
  18. *
  19. * Sets additional defaults and loads repositories
  20. *
  21. * @author Jordi Boggiano <j.boggiano@seld.be>
  22. */
  23. class RootPackageLoader extends ArrayLoader
  24. {
  25. private $manager;
  26. private $process;
  27. public function __construct(RepositoryManager $manager, VersionParser $parser = null, ProcessExecutor $process = null)
  28. {
  29. $this->manager = $manager;
  30. $this->process = $process ?: new ProcessExecutor();
  31. parent::__construct($parser);
  32. }
  33. public function load($config)
  34. {
  35. if (!isset($config['name'])) {
  36. $config['name'] = '__root__';
  37. }
  38. if (!isset($config['version'])) {
  39. // override with env var if available
  40. if (getenv('COMPOSER_ROOT_VERSION')) {
  41. $version = getenv('COMPOSER_ROOT_VERSION');
  42. } else {
  43. $version = $this->guessVersion($config);
  44. }
  45. $config['version'] = $version;
  46. } else {
  47. $version = $config['version'];
  48. }
  49. $package = parent::load($config);
  50. $aliases = array();
  51. $stabilityFlags = array();
  52. $references = array();
  53. foreach (array('require', 'require-dev') as $linkType) {
  54. if (isset($config[$linkType])) {
  55. $aliases = $this->extractAliases($config[$linkType], $aliases);
  56. $stabilityFlags = $this->extractStabilityFlags($config[$linkType], $stabilityFlags);
  57. $references = $this->extractReferences($config[$linkType], $references);
  58. }
  59. }
  60. $package->setAliases($aliases);
  61. $package->setStabilityFlags($stabilityFlags);
  62. $package->setReferences($references);
  63. if (isset($config['minimum-stability'])) {
  64. $package->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability']));
  65. }
  66. if (isset($config['repositories'])) {
  67. foreach ($config['repositories'] as $index => $repo) {
  68. if (isset($repo['packagist']) && $repo['packagist'] === false) {
  69. continue;
  70. }
  71. if (!is_array($repo)) {
  72. throw new \UnexpectedValueException('Repository '.$index.' should be an array, '.gettype($repo).' given');
  73. }
  74. if (!isset($repo['type'])) {
  75. throw new \UnexpectedValueException('Repository '.$index.' ('.json_encode($repo).') must have a type defined');
  76. }
  77. $repository = $this->manager->createRepository($repo['type'], $repo);
  78. $this->manager->addRepository($repository);
  79. }
  80. $package->setRepositories($config['repositories']);
  81. }
  82. return $package;
  83. }
  84. private function extractAliases(array $requires, array $aliases)
  85. {
  86. foreach ($requires as $reqName => $reqVersion) {
  87. if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $reqVersion, $match)) {
  88. $aliases[] = array(
  89. 'package' => strtolower($reqName),
  90. 'version' => $this->versionParser->normalize($match[1]),
  91. 'alias' => $match[2],
  92. 'alias_normalized' => $this->versionParser->normalize($match[2]),
  93. );
  94. }
  95. }
  96. return $aliases;
  97. }
  98. private function extractStabilityFlags(array $requires, array $stabilityFlags)
  99. {
  100. $stabilities = BasePackage::$stabilities;
  101. foreach ($requires as $reqName => $reqVersion) {
  102. // parse explicit stability flags
  103. if (preg_match('{^[^,\s]*?@('.implode('|', array_keys($stabilities)).')$}i', $reqVersion, $match)) {
  104. $name = strtolower($reqName);
  105. $stability = $stabilities[VersionParser::normalizeStability($match[1])];
  106. if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
  107. continue;
  108. }
  109. $stabilityFlags[$name] = $stability;
  110. continue;
  111. }
  112. // infer flags for requirements that have an explicit -dev or -beta version specified for example
  113. if (preg_match('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) {
  114. $name = strtolower($reqName);
  115. $stability = $stabilities[$stabilityName];
  116. if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
  117. continue;
  118. }
  119. $stabilityFlags[$name] = $stability;
  120. }
  121. }
  122. return $stabilityFlags;
  123. }
  124. private function extractReferences(array $requires, array $references)
  125. {
  126. foreach ($requires as $reqName => $reqVersion) {
  127. if (preg_match('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === ($stabilityName = VersionParser::parseStability($reqVersion))) {
  128. $name = strtolower($reqName);
  129. $references[$name] = $match[1];
  130. }
  131. }
  132. return $references;
  133. }
  134. private function guessVersion(array $config)
  135. {
  136. // try to fetch current version from git branch
  137. if (function_exists('proc_open') && 0 === $this->process->execute('git branch --no-color --no-abbrev -v', $output)) {
  138. $branches = array();
  139. $isFeatureBranch = true;
  140. $version = null;
  141. foreach ($this->process->splitLines($output) as $branch) {
  142. if ($branch && preg_match('{^(?:\* ) *(?:[^/ ]+?/)?(\S+|\(no branch\)) *([a-f0-9]+) .*$}', $branch, $match)) {
  143. if ($match[1] === '(no branch)') {
  144. $version = 'dev-'.$match[2];
  145. $isFeatureBranch = true;
  146. } else {
  147. $version = $this->versionParser->normalizeBranch($match[1]);
  148. $isFeatureBranch = 0 === strpos($version, 'dev-');
  149. if ('9999999-dev' === $version) {
  150. $version = 'dev-'.$match[1];
  151. }
  152. }
  153. }
  154. if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
  155. if (preg_match('{^(?:\* )? *(?:[^/ ]+?/)?(\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
  156. $branches[] = $match[1];
  157. }
  158. }
  159. }
  160. if (!$isFeatureBranch) {
  161. return $version;
  162. }
  163. // ignore feature branches if they have no branch-alias or self.version is used
  164. // and find the branch they came from to use as a version instead
  165. if ((isset($config['extra']['branch-alias']) && !isset($config['extra']['branch-alias'][$version]))
  166. || strpos(json_encode($config), '"self.version"')
  167. ) {
  168. $branch = preg_replace('{^dev-}', '', $version);
  169. $length = PHP_INT_MAX;
  170. foreach ($branches as $candidate) {
  171. // do not compare against other feature branches
  172. if ($candidate === $branch || !preg_match('{^(master|trunk|default|develop|\d+\..+)$}', $candidate)) {
  173. continue;
  174. }
  175. if (0 !== $this->process->execute('git rev-list '.$candidate.'..'.$branch, $output)) {
  176. continue;
  177. }
  178. if (strlen($output) < $length) {
  179. $length = strlen($output);
  180. $version = $this->versionParser->normalizeBranch($candidate);
  181. if ('9999999-dev' === $version) {
  182. $version = 'dev-'.$match[1];
  183. }
  184. }
  185. }
  186. }
  187. return $version;
  188. }
  189. return '1.0.0';
  190. }
  191. }