Factory.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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;
  12. use Composer\Config\JsonConfigSource;
  13. use Composer\Json\JsonFile;
  14. use Composer\IO\IOInterface;
  15. use Composer\Package\Archiver;
  16. use Composer\Repository\RepositoryManager;
  17. use Composer\Repository\RepositoryInterface;
  18. use Composer\Util\ProcessExecutor;
  19. use Composer\Util\RemoteFilesystem;
  20. use Composer\Util\Filesystem;
  21. use Symfony\Component\Console\Formatter\OutputFormatterStyle;
  22. use Composer\EventDispatcher\EventDispatcher;
  23. use Composer\Autoload\AutoloadGenerator;
  24. use Composer\Package\Version\VersionParser;
  25. /**
  26. * Creates a configured instance of composer.
  27. *
  28. * @author Ryan Weaver <ryan@knplabs.com>
  29. * @author Jordi Boggiano <j.boggiano@seld.be>
  30. * @author Igor Wiedler <igor@wiedler.ch>
  31. * @author Nils Adermann <naderman@naderman.de>
  32. */
  33. class Factory
  34. {
  35. /**
  36. * @return string
  37. * @throws \RuntimeException
  38. */
  39. protected static function getHomeDir()
  40. {
  41. $home = getenv('COMPOSER_HOME');
  42. $cacheDir = getenv('COMPOSER_CACHE_DIR');
  43. $userDir = rtrim(getenv('HOME'), '/');
  44. $followXDG = false;
  45. if (!$home) {
  46. if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
  47. $home = getenv('APPDATA') . '/Composer';
  48. } elseif (getenv('XDG_CONFIG_DIRS')) {
  49. // XDG Base Directory Specifications
  50. $followXDG = true;
  51. $xdgConfig = getenv('XDG_CONFIG_HOME');
  52. if (!$xdgConfig) {
  53. $xdgConfig = $userDir . '/.config';
  54. }
  55. $home = $xdgConfig . '/composer';
  56. } else {
  57. $home = $userDir . '/.composer';
  58. }
  59. }
  60. return $home;
  61. }
  62. /**
  63. * @param string $home
  64. *
  65. * @return string
  66. */
  67. protected static function getCacheDir($home)
  68. {
  69. $cacheDir = getenv('COMPOSER_CACHE_DIR');
  70. if (!$cacheDir) {
  71. if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
  72. if ($cacheDir = getenv('LOCALAPPDATA')) {
  73. $cacheDir .= '/Composer';
  74. } else {
  75. $cacheDir = $home . '/cache';
  76. }
  77. } elseif (getenv('XDG_CONFIG_DIRS')) {
  78. $followXDG = true;
  79. $xdgCache = getenv('XDG_CACHE_HOME');
  80. if (!$xdgCache) {
  81. $xdgCache = $userDir . '/.cache';
  82. }
  83. $cacheDir = $xdgCache . '/composer';
  84. } else {
  85. $cacheDir = $home . '/.cache';
  86. }
  87. }
  88. // Protect directory against web access. Since HOME could be
  89. // the www-data's user home and be web-accessible it is a
  90. // potential security risk
  91. foreach (array($home, $cacheDir) as $dir) {
  92. if (!file_exists($dir . '/.htaccess')) {
  93. if (!is_dir($dir)) {
  94. @mkdir($dir, 0777, true);
  95. }
  96. @file_put_contents($dir . '/.htaccess', 'Deny from all');
  97. }
  98. }
  99. // Move content of old composer dir to XDG
  100. if ($followXDG && file_exists($userDir . '/.composer')) {
  101. // migrate to XDG
  102. @rename($userDir . '/.composer/config.json', $home . '/config.json');
  103. @unlink($userDir . '/.composer/.htaccess');
  104. @unlink($userDir . '/.composer/cache/.htaccess');
  105. foreach (glob($userDir . '/.composer/cache/*') as $oldCacheDir) {
  106. @rename($oldCacheDir, $cacheDir . '/' . basename($oldCacheDir));
  107. }
  108. @rmdir($userDir . '/.composer/cache');
  109. @rmdir($userDir . '/.composer');
  110. }
  111. $config = new Config();
  112. // add dirs to the config
  113. $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir)));
  114. // load global config
  115. $file = new JsonFile($home.'/config.json');
  116. if ($file->exists()) {
  117. if ($io && $io->isDebug()) {
  118. $io->write('Loading config file ' . $file->getPath());
  119. }
  120. $config->merge($file->read());
  121. }
  122. $config->setConfigSource(new JsonConfigSource($file));
  123. // load global auth file
  124. $file = new JsonFile($config->get('home').'/auth.json');
  125. if ($file->exists()) {
  126. if ($io && $io->isDebug()) {
  127. $io->write('Loading config file ' . $file->getPath());
  128. }
  129. $config->merge(array('config' => $file->read()));
  130. }
  131. $config->setAuthConfigSource(new JsonConfigSource($file, true));
  132. return $config;
  133. }
  134. public static function getComposerFile()
  135. {
  136. return trim(getenv('COMPOSER')) ?: './composer.json';
  137. }
  138. public static function createAdditionalStyles()
  139. {
  140. return array(
  141. 'highlight' => new OutputFormatterStyle('red'),
  142. 'warning' => new OutputFormatterStyle('black', 'yellow'),
  143. );
  144. }
  145. public static function createDefaultRepositories(IOInterface $io = null, Config $config = null, RepositoryManager $rm = null)
  146. {
  147. $repos = array();
  148. if (!$config) {
  149. $config = static::createConfig($io);
  150. }
  151. if (!$rm) {
  152. if (!$io) {
  153. throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
  154. }
  155. $factory = new static;
  156. $rm = $factory->createRepositoryManager($io, $config);
  157. }
  158. foreach ($config->getRepositories() as $index => $repo) {
  159. if (!is_array($repo)) {
  160. throw new \UnexpectedValueException('Repository '.$index.' ('.json_encode($repo).') should be an array, '.gettype($repo).' given');
  161. }
  162. if (!isset($repo['type'])) {
  163. throw new \UnexpectedValueException('Repository '.$index.' ('.json_encode($repo).') must have a type defined');
  164. }
  165. $name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index;
  166. while (isset($repos[$name])) {
  167. $name .= '2';
  168. }
  169. $repos[$name] = $rm->createRepository($repo['type'], $repo);
  170. }
  171. return $repos;
  172. }
  173. /**
  174. * Creates a Composer instance
  175. *
  176. * @param IOInterface $io IO instance
  177. * @param array|string|null $localConfig either a configuration array or a filename to read from, if null it will
  178. * read from the default filename
  179. * @param bool $disablePlugins Whether plugins should not be loaded
  180. * @throws \InvalidArgumentException
  181. * @throws \UnexpectedValueException
  182. * @return Composer
  183. */
  184. public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false)
  185. {
  186. // load Composer configuration
  187. if (null === $localConfig) {
  188. $localConfig = static::getComposerFile();
  189. }
  190. if (is_string($localConfig)) {
  191. $composerFile = $localConfig;
  192. $file = new JsonFile($localConfig, new RemoteFilesystem($io));
  193. if (!$file->exists()) {
  194. if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
  195. $message = 'Composer could not find a composer.json file in '.getcwd();
  196. } else {
  197. $message = 'Composer could not find the config file: '.$localConfig;
  198. }
  199. $instructions = 'To initialize a project, please create a composer.json file as described in the http://getcomposer.org/ "Getting Started" section';
  200. throw new \InvalidArgumentException($message.PHP_EOL.$instructions);
  201. }
  202. $file->validateSchema(JsonFile::LAX_SCHEMA);
  203. $localConfig = $file->read();
  204. }
  205. // Load config and override with local config/auth config
  206. $config = static::createConfig($io);
  207. $config->merge($localConfig);
  208. if (isset($composerFile)) {
  209. if ($io && $io->isDebug()) {
  210. $io->write('Loading config file ' . $composerFile);
  211. }
  212. $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json');
  213. if ($localAuthFile->exists()) {
  214. if ($io && $io->isDebug()) {
  215. $io->write('Loading config file ' . $localAuthFile->getPath());
  216. }
  217. $config->merge(array('config' => $localAuthFile->read()));
  218. $config->setAuthConfigSource(new JsonConfigSource($localAuthFile, true));
  219. }
  220. }
  221. // load auth configs into the IO instance
  222. $io->loadConfiguration($config);
  223. $vendorDir = $config->get('vendor-dir');
  224. $binDir = $config->get('bin-dir');
  225. // setup process timeout
  226. ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
  227. // initialize composer
  228. $composer = new Composer();
  229. $composer->setConfig($config);
  230. // initialize event dispatcher
  231. $dispatcher = new EventDispatcher($composer, $io);
  232. // initialize repository manager
  233. $rm = $this->createRepositoryManager($io, $config, $dispatcher);
  234. // load local repository
  235. $this->addLocalRepository($rm, $vendorDir);
  236. // load package
  237. $parser = new VersionParser;
  238. $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io));
  239. $package = $loader->load($localConfig);
  240. // initialize installation manager
  241. $im = $this->createInstallationManager();
  242. // Composer composition
  243. $composer->setPackage($package);
  244. $composer->setRepositoryManager($rm);
  245. $composer->setInstallationManager($im);
  246. // initialize download manager
  247. $dm = $this->createDownloadManager($io, $config, $dispatcher);
  248. $composer->setDownloadManager($dm);
  249. $composer->setEventDispatcher($dispatcher);
  250. // initialize autoload generator
  251. $generator = new AutoloadGenerator($dispatcher, $io);
  252. $composer->setAutoloadGenerator($generator);
  253. // add installers to the manager
  254. $this->createDefaultInstallers($im, $composer, $io);
  255. $globalRepository = $this->createGlobalRepository($config, $vendorDir);
  256. $pm = $this->createPluginManager($composer, $io, $globalRepository);
  257. $composer->setPluginManager($pm);
  258. if (!$disablePlugins) {
  259. $pm->loadInstalledPlugins();
  260. }
  261. // purge packages if they have been deleted on the filesystem
  262. $this->purgePackages($rm, $im);
  263. // init locker if possible
  264. if (isset($composerFile)) {
  265. $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
  266. ? substr($composerFile, 0, -4).'lock'
  267. : $composerFile . '.lock';
  268. $locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile));
  269. $composer->setLocker($locker);
  270. }
  271. return $composer;
  272. }
  273. /**
  274. * @param IOInterface $io
  275. * @param Config $config
  276. * @param EventDispatcher $eventDispatcher
  277. * @return Repository\RepositoryManager
  278. */
  279. protected function createRepositoryManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
  280. {
  281. $rm = new RepositoryManager($io, $config, $eventDispatcher);
  282. $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
  283. $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
  284. $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository');
  285. $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository');
  286. $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository');
  287. $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository');
  288. $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository');
  289. $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository');
  290. $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository');
  291. return $rm;
  292. }
  293. /**
  294. * @param Repository\RepositoryManager $rm
  295. * @param string $vendorDir
  296. */
  297. protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
  298. {
  299. $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json')));
  300. }
  301. /**
  302. * @param Config $config
  303. * @param string $vendorDir
  304. * @return Repository\InstalledFilesystemRepository|null
  305. */
  306. protected function createGlobalRepository(Config $config, $vendorDir)
  307. {
  308. if ($config->get('home') == $vendorDir) {
  309. return null;
  310. }
  311. $path = $config->get('home').'/vendor/composer/installed.json';
  312. if (!file_exists($path)) {
  313. return null;
  314. }
  315. return new Repository\InstalledFilesystemRepository(new JsonFile($path));
  316. }
  317. /**
  318. * @param IO\IOInterface $io
  319. * @param Config $config
  320. * @param EventDispatcher $eventDispatcher
  321. * @return Downloader\DownloadManager
  322. */
  323. public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null)
  324. {
  325. $cache = null;
  326. if ($config->get('cache-files-ttl') > 0) {
  327. $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./');
  328. }
  329. $dm = new Downloader\DownloadManager($io);
  330. switch ($config->get('preferred-install')) {
  331. case 'dist':
  332. $dm->setPreferDist(true);
  333. break;
  334. case 'source':
  335. $dm->setPreferSource(true);
  336. break;
  337. case 'auto':
  338. default:
  339. // noop
  340. break;
  341. }
  342. $dm->setDownloader('git', new Downloader\GitDownloader($io, $config));
  343. $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config));
  344. $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config));
  345. $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config));
  346. $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache));
  347. $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache));
  348. $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache));
  349. $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache));
  350. $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache));
  351. $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache));
  352. return $dm;
  353. }
  354. /**
  355. * @param Config $config The configuration
  356. * @param Downloader\DownloadManager $dm Manager use to download sources
  357. *
  358. * @return Archiver\ArchiveManager
  359. */
  360. public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null)
  361. {
  362. if (null === $dm) {
  363. $io = new IO\NullIO();
  364. $io->loadConfiguration($config);
  365. $dm = $this->createDownloadManager($io, $config);
  366. }
  367. $am = new Archiver\ArchiveManager($dm);
  368. $am->addArchiver(new Archiver\PharArchiver);
  369. return $am;
  370. }
  371. /**
  372. * @param Composer $composer
  373. * @param IOInterface $io
  374. * @param RepositoryInterface $globalRepository
  375. * @return Plugin\PluginManager
  376. */
  377. protected function createPluginManager(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null)
  378. {
  379. return new Plugin\PluginManager($composer, $io, $globalRepository);
  380. }
  381. /**
  382. * @return Installer\InstallationManager
  383. */
  384. protected function createInstallationManager()
  385. {
  386. return new Installer\InstallationManager();
  387. }
  388. /**
  389. * @param Installer\InstallationManager $im
  390. * @param Composer $composer
  391. * @param IO\IOInterface $io
  392. */
  393. protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io)
  394. {
  395. $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null));
  396. $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library'));
  397. $im->addInstaller(new Installer\PluginInstaller($io, $composer));
  398. $im->addInstaller(new Installer\MetapackageInstaller($io));
  399. }
  400. /**
  401. * @param Repository\RepositoryManager $rm
  402. * @param Installer\InstallationManager $im
  403. */
  404. protected function purgePackages(Repository\RepositoryManager $rm, Installer\InstallationManager $im)
  405. {
  406. $repo = $rm->getLocalRepository();
  407. foreach ($repo->getPackages() as $package) {
  408. if (!$im->isPackageInstalled($repo, $package)) {
  409. $repo->removePackage($package);
  410. }
  411. }
  412. }
  413. /**
  414. * @param IOInterface $io IO instance
  415. * @param mixed $config either a configuration array or a filename to read from, if null it will read from
  416. * the default filename
  417. * @param bool $disablePlugins Whether plugins should not be loaded
  418. * @return Composer
  419. */
  420. public static function create(IOInterface $io, $config = null, $disablePlugins = false)
  421. {
  422. $factory = new static();
  423. return $factory->createComposer($io, $config, $disablePlugins);
  424. }
  425. }