1<?php 2 3/* 4 * This file is part of Composer. 5 * 6 * (c) Nils Adermann <naderman@naderman.de> 7 * Jordi Boggiano <j.boggiano@seld.be> 8 * 9 * For the full copyright and license information, please view the LICENSE 10 * file that was distributed with this source code. 11 */ 12 13namespace Composer\Package\Loader; 14 15use Composer\Package\BasePackage; 16use Composer\Package\AliasPackage; 17use Composer\Config; 18use Composer\Package\RootPackageInterface; 19use Composer\Repository\RepositoryFactory; 20use Composer\Package\Version\VersionGuesser; 21use Composer\Package\Version\VersionParser; 22use Composer\Repository\RepositoryManager; 23use Composer\Util\ProcessExecutor; 24 25/** 26 * ArrayLoader built for the sole purpose of loading the root package 27 * 28 * Sets additional defaults and loads repositories 29 * 30 * @author Jordi Boggiano <j.boggiano@seld.be> 31 */ 32class RootPackageLoader extends ArrayLoader 33{ 34 /** 35 * @var RepositoryManager 36 */ 37 private $manager; 38 39 /** 40 * @var Config 41 */ 42 private $config; 43 44 /** 45 * @var VersionGuesser 46 */ 47 private $versionGuesser; 48 49 public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null) 50 { 51 parent::__construct($parser); 52 53 $this->manager = $manager; 54 $this->config = $config; 55 $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor(), $this->versionParser); 56 } 57 58 /** 59 * @param array $config package data 60 * @param string $class FQCN to be instantiated 61 * @param string $cwd cwd of the root package to be used to guess the version if it is not provided 62 * @return RootPackageInterface 63 */ 64 public function load(array $config, $class = 'Composer\Package\RootPackage', $cwd = null) 65 { 66 if (!isset($config['name'])) { 67 $config['name'] = '__root__'; 68 } 69 $autoVersioned = false; 70 if (!isset($config['version'])) { 71 // override with env var if available 72 if (getenv('COMPOSER_ROOT_VERSION')) { 73 $version = getenv('COMPOSER_ROOT_VERSION'); 74 $commit = null; 75 } else { 76 $versionData = $this->versionGuesser->guessVersion($config, $cwd ?: getcwd()); 77 $version = $versionData['version']; 78 $commit = $versionData['commit']; 79 } 80 81 if (!$version) { 82 $version = '1.0.0'; 83 $autoVersioned = true; 84 } 85 86 $config['version'] = $version; 87 if ($commit) { 88 $config['source'] = array( 89 'type' => '', 90 'url' => '', 91 'reference' => $commit, 92 ); 93 $config['dist'] = array( 94 'type' => '', 95 'url' => '', 96 'reference' => $commit, 97 ); 98 } 99 } 100 101 $realPackage = $package = parent::load($config, $class); 102 if ($realPackage instanceof AliasPackage) { 103 $realPackage = $package->getAliasOf(); 104 } 105 106 if ($autoVersioned) { 107 $realPackage->replaceVersion($realPackage->getVersion(), 'No version set (parsed as 1.0.0)'); 108 } 109 110 if (isset($config['minimum-stability'])) { 111 $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability'])); 112 } 113 114 $aliases = array(); 115 $stabilityFlags = array(); 116 $references = array(); 117 foreach (array('require', 'require-dev') as $linkType) { 118 if (isset($config[$linkType])) { 119 $linkInfo = BasePackage::$supportedLinkTypes[$linkType]; 120 $method = 'get'.ucfirst($linkInfo['method']); 121 $links = array(); 122 foreach ($realPackage->$method() as $link) { 123 $links[$link->getTarget()] = $link->getConstraint()->getPrettyString(); 124 } 125 $aliases = $this->extractAliases($links, $aliases); 126 $stabilityFlags = $this->extractStabilityFlags($links, $stabilityFlags, $realPackage->getMinimumStability()); 127 $references = $this->extractReferences($links, $references); 128 } 129 } 130 131 if (isset($links[$config['name']])) { 132 throw new \InvalidArgumentException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL . 133 'Did you accidentally name your root package after an external package?', $config['name'])); 134 } 135 136 $realPackage->setAliases($aliases); 137 $realPackage->setStabilityFlags($stabilityFlags); 138 $realPackage->setReferences($references); 139 140 if (isset($config['prefer-stable'])) { 141 $realPackage->setPreferStable((bool) $config['prefer-stable']); 142 } 143 144 if (isset($config['config'])) { 145 $realPackage->setConfig($config['config']); 146 } 147 148 $repos = RepositoryFactory::defaultRepos(null, $this->config, $this->manager); 149 foreach ($repos as $repo) { 150 $this->manager->addRepository($repo); 151 } 152 $realPackage->setRepositories($this->config->getRepositories()); 153 154 return $package; 155 } 156 157 private function extractAliases(array $requires, array $aliases) 158 { 159 foreach ($requires as $reqName => $reqVersion) { 160 if (preg_match('{^([^,\s#]+)(?:#[^ ]+)? +as +([^,\s]+)$}', $reqVersion, $match)) { 161 $aliases[] = array( 162 'package' => strtolower($reqName), 163 'version' => $this->versionParser->normalize($match[1], $reqVersion), 164 'alias' => $match[2], 165 'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion), 166 ); 167 } 168 } 169 170 return $aliases; 171 } 172 173 private function extractStabilityFlags(array $requires, array $stabilityFlags, $minimumStability) 174 { 175 $stabilities = BasePackage::$stabilities; 176 $minimumStability = $stabilities[$minimumStability]; 177 foreach ($requires as $reqName => $reqVersion) { 178 $constraints = array(); 179 180 // extract all sub-constraints in case it is an OR/AND multi-constraint 181 $orSplit = preg_split('{\s*\|\|?\s*}', trim($reqVersion)); 182 foreach ($orSplit as $orConstraint) { 183 $andSplit = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $orConstraint); 184 foreach ($andSplit as $andConstraint) { 185 $constraints[] = $andConstraint; 186 } 187 } 188 189 // parse explicit stability flags to the most unstable 190 $match = false; 191 foreach ($constraints as $constraint) { 192 if (preg_match('{^[^@]*?@('.implode('|', array_keys($stabilities)).')$}i', $constraint, $match)) { 193 $name = strtolower($reqName); 194 $stability = $stabilities[VersionParser::normalizeStability($match[1])]; 195 196 if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) { 197 continue; 198 } 199 $stabilityFlags[$name] = $stability; 200 $match = true; 201 } 202 } 203 204 if ($match) { 205 continue; 206 } 207 208 foreach ($constraints as $constraint) { 209 // infer flags for requirements that have an explicit -dev or -beta version specified but only 210 // for those that are more unstable than the minimumStability or existing flags 211 $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $constraint); 212 if (preg_match('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) { 213 $name = strtolower($reqName); 214 $stability = $stabilities[$stabilityName]; 215 if ((isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) || ($minimumStability > $stability)) { 216 continue; 217 } 218 $stabilityFlags[$name] = $stability; 219 } 220 } 221 } 222 223 return $stabilityFlags; 224 } 225 226 private function extractReferences(array $requires, array $references) 227 { 228 foreach ($requires as $reqName => $reqVersion) { 229 $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion); 230 if (preg_match('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === ($stabilityName = VersionParser::parseStability($reqVersion))) { 231 $name = strtolower($reqName); 232 $references[$name] = $match[1]; 233 } 234 } 235 236 return $references; 237 } 238} 239