RootPackageLoader.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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\AliasPackage;
  14. use Composer\Config;
  15. use Composer\Factory;
  16. use Composer\Package\Version\VersionParser;
  17. use Composer\Repository\RepositoryManager;
  18. use Composer\Repository\Vcs\HgDriver;
  19. use Composer\IO\NullIO;
  20. use Composer\Util\ProcessExecutor;
  21. /**
  22. * ArrayLoader built for the sole purpose of loading the root package
  23. *
  24. * Sets additional defaults and loads repositories
  25. *
  26. * @author Jordi Boggiano <j.boggiano@seld.be>
  27. */
  28. class RootPackageLoader extends ArrayLoader
  29. {
  30. private $manager;
  31. private $config;
  32. private $process;
  33. public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, ProcessExecutor $process = null)
  34. {
  35. $this->manager = $manager;
  36. $this->config = $config;
  37. $this->process = $process ?: new ProcessExecutor();
  38. parent::__construct($parser);
  39. }
  40. public function load(array $config, $class = 'Composer\Package\RootPackage')
  41. {
  42. if (!isset($config['name'])) {
  43. $config['name'] = '__root__';
  44. }
  45. if (!isset($config['version'])) {
  46. // override with env var if available
  47. if (getenv('COMPOSER_ROOT_VERSION')) {
  48. $version = getenv('COMPOSER_ROOT_VERSION');
  49. } else {
  50. $version = $this->guessVersion($config);
  51. }
  52. if (!$version) {
  53. $version = '1.0.0';
  54. }
  55. $config['version'] = $version;
  56. } else {
  57. $version = $config['version'];
  58. }
  59. $realPackage = $package = parent::load($config, $class);
  60. if ($realPackage instanceof AliasPackage) {
  61. $realPackage = $package->getAliasOf();
  62. }
  63. $aliases = array();
  64. $stabilityFlags = array();
  65. $references = array();
  66. foreach (array('require', 'require-dev') as $linkType) {
  67. if (isset($config[$linkType])) {
  68. $linkInfo = BasePackage::$supportedLinkTypes[$linkType];
  69. $method = 'get'.ucfirst($linkInfo['method']);
  70. $links = array();
  71. foreach ($realPackage->$method() as $link) {
  72. $links[$link->getTarget()] = $link->getConstraint()->getPrettyString();
  73. }
  74. $aliases = $this->extractAliases($links, $aliases);
  75. $stabilityFlags = $this->extractStabilityFlags($links, $stabilityFlags);
  76. $references = $this->extractReferences($links, $references);
  77. }
  78. }
  79. $realPackage->setAliases($aliases);
  80. $realPackage->setStabilityFlags($stabilityFlags);
  81. $realPackage->setReferences($references);
  82. if (isset($config['minimum-stability'])) {
  83. $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability']));
  84. }
  85. if (isset($config['prefer-stable'])) {
  86. $realPackage->setPreferStable((bool) $config['prefer-stable']);
  87. }
  88. $repos = Factory::createDefaultRepositories(null, $this->config, $this->manager);
  89. foreach ($repos as $repo) {
  90. $this->manager->addRepository($repo);
  91. }
  92. $realPackage->setRepositories($this->config->getRepositories());
  93. return $package;
  94. }
  95. private function extractAliases(array $requires, array $aliases)
  96. {
  97. foreach ($requires as $reqName => $reqVersion) {
  98. if (preg_match('{^([^,\s#]+)(?:#[^ ]+)? +as +([^,\s]+)$}', $reqVersion, $match)) {
  99. $aliases[] = array(
  100. 'package' => strtolower($reqName),
  101. 'version' => $this->versionParser->normalize($match[1], $reqVersion),
  102. 'alias' => $match[2],
  103. 'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion),
  104. );
  105. }
  106. }
  107. return $aliases;
  108. }
  109. private function extractStabilityFlags(array $requires, array $stabilityFlags)
  110. {
  111. $stabilities = BasePackage::$stabilities;
  112. foreach ($requires as $reqName => $reqVersion) {
  113. // parse explicit stability flags
  114. if (preg_match('{^[^,\s]*?@('.implode('|', array_keys($stabilities)).')$}i', $reqVersion, $match)) {
  115. $name = strtolower($reqName);
  116. $stability = $stabilities[VersionParser::normalizeStability($match[1])];
  117. if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
  118. continue;
  119. }
  120. $stabilityFlags[$name] = $stability;
  121. continue;
  122. }
  123. // infer flags for requirements that have an explicit -dev or -beta version specified for example
  124. $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion);
  125. if (preg_match('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) {
  126. $name = strtolower($reqName);
  127. $stability = $stabilities[$stabilityName];
  128. if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
  129. continue;
  130. }
  131. $stabilityFlags[$name] = $stability;
  132. }
  133. }
  134. return $stabilityFlags;
  135. }
  136. private function extractReferences(array $requires, array $references)
  137. {
  138. foreach ($requires as $reqName => $reqVersion) {
  139. $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion);
  140. if (preg_match('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === ($stabilityName = VersionParser::parseStability($reqVersion))) {
  141. $name = strtolower($reqName);
  142. $references[$name] = $match[1];
  143. }
  144. }
  145. return $references;
  146. }
  147. private function guessVersion(array $config)
  148. {
  149. if (function_exists('proc_open')) {
  150. $version = $this->guessGitVersion($config);
  151. if (null !== $version) {
  152. return $version;
  153. }
  154. return $this->guessHgVersion($config);
  155. }
  156. }
  157. private function guessGitVersion(array $config)
  158. {
  159. // try to fetch current version from git branch as a tag
  160. if (0 === $this->process->execute('git describe --exact-match --tags', $output)) {
  161. return $this->versionParser->normalize(rtrim($output));
  162. }
  163. // try to fetch current version from git branch
  164. if (0 === $this->process->execute('git branch --no-color --no-abbrev -v', $output)) {
  165. $branches = array();
  166. $isFeatureBranch = false;
  167. $version = null;
  168. // find current branch and collect all branch names
  169. foreach ($this->process->splitLines($output) as $branch) {
  170. if ($branch && preg_match('{^(?:\* ) *(\S+|\(no branch\)) *([a-f0-9]+) .*$}', $branch, $match)) {
  171. if ($match[1] === '(no branch)') {
  172. $version = 'dev-'.$match[2];
  173. $isFeatureBranch = true;
  174. } else {
  175. $version = $this->versionParser->normalizeBranch($match[1]);
  176. $isFeatureBranch = 0 === strpos($version, 'dev-');
  177. if ('9999999-dev' === $version) {
  178. $version = 'dev-'.$match[1];
  179. }
  180. }
  181. }
  182. if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
  183. if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
  184. $branches[] = $match[1];
  185. }
  186. }
  187. }
  188. if (!$isFeatureBranch) {
  189. return $version;
  190. }
  191. // try to find the best (nearest) version branch to assume this feature's version
  192. $version = $this->guessFeatureVersion($config, $version, $branches, 'git rev-list %candidate%..%branch%');
  193. return $version;
  194. }
  195. }
  196. private function guessHgVersion(array $config)
  197. {
  198. // try to fetch current version from hg branch
  199. if (0 === $this->process->execute('hg branch', $output)) {
  200. $branch = trim($output);
  201. $version = $this->versionParser->normalizeBranch($branch);
  202. $isFeatureBranch = 0 === strpos($version, 'dev-');
  203. if ('9999999-dev' === $version) {
  204. $version = 'dev-'.$branch;
  205. }
  206. if (!$isFeatureBranch) {
  207. return $version;
  208. }
  209. // re-use the HgDriver to fetch branches (this properly includes bookmarks)
  210. $config = array('url' => getcwd());
  211. $driver = new HgDriver($config, new NullIO(), $this->config, $this->process);
  212. $branches = array_keys($driver->getBranches());
  213. // try to find the best (nearest) version branch to assume this feature's version
  214. $version = $this->guessFeatureVersion($config, $version, $branches, 'hg log -r "not ancestors(\'%candidate%\') and ancestors(\'%branch%\')" --template "{node}\\n"');
  215. return $version;
  216. }
  217. }
  218. private function guessFeatureVersion(array $config, $version, array $branches, $scmCmdline)
  219. {
  220. // ignore feature branches if they have no branch-alias or self.version is used
  221. // and find the branch they came from to use as a version instead
  222. if ((isset($config['extra']['branch-alias']) && !isset($config['extra']['branch-alias'][$version]))
  223. || strpos(json_encode($config), '"self.version"')
  224. ) {
  225. $branch = preg_replace('{^dev-}', '', $version);
  226. $length = PHP_INT_MAX;
  227. foreach ($branches as $candidate) {
  228. // do not compare against other feature branches
  229. if ($candidate === $branch || !preg_match('{^(master|trunk|default|develop|\d+\..+)$}', $candidate, $match)) {
  230. continue;
  231. }
  232. $cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline);
  233. if (0 !== $this->process->execute($cmdLine, $output)) {
  234. continue;
  235. }
  236. if (strlen($output) < $length) {
  237. $length = strlen($output);
  238. $version = $this->versionParser->normalizeBranch($candidate);
  239. if ('9999999-dev' === $version) {
  240. $version = 'dev-'.$match[1];
  241. }
  242. }
  243. }
  244. }
  245. return $version;
  246. }
  247. }