diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 1b0ad4770..492a94edd 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -25,15 +25,10 @@ class Cache private $root; private $enabled = true; - public function __construct(IOInterface $io, $cacheKey = null) + public function __construct(IOInterface $io, $cacheDir) { $this->io = $io; - - if (defined('PHP_WINDOWS_VERSION_MAJOR')) { - $this->root = getenv('APPDATA') . rtrim('/Composer/cache/' . $cacheKey, '/') . '/'; - } else { - $this->root = getenv('HOME') . rtrim('/.composer/cache/' . $cacheKey, '/') . '/'; - } + $this->root = rtrim($cacheDir, '/\\') . '/'; if (!is_dir($this->root)) { if (!@mkdir($this->root, 0777, true)) { diff --git a/src/Composer/Composer.php b/src/Composer/Composer.php index 54b2c496a..0ad14c450 100644 --- a/src/Composer/Composer.php +++ b/src/Composer/Composer.php @@ -43,6 +43,16 @@ class Composer return $this->package; } + public function setConfig(Config $config) + { + $this->config = $config; + } + + public function getConfig() + { + return $this->config; + } + public function setLocker(Locker $locker) { $this->locker = $locker; diff --git a/src/Composer/Config.php b/src/Composer/Config.php new file mode 100644 index 000000000..9cb08d73f --- /dev/null +++ b/src/Composer/Config.php @@ -0,0 +1,93 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +/** + * @author Jordi Boggiano + */ +class Config +{ + private $config; + + public function __construct() + { + // load defaults + $this->config = array( + 'process-timeout' => 300, + 'vendor-dir' => 'vendor', + 'bin-dir' => '{$vendor-dir}/bin', + ); + } + + /** + * Merges new config values with the existing ones (overriding) + * + * @param array $config + */ + public function merge(array $config) + { + // override defaults with given config + if (!empty($config['config']) && is_array($config['config'])) { + $this->config = array_merge_recursive($this->config, $config['config']); + } + } + + /** + * Returns a setting + * + * @param string $key + * @return mixed + */ + public function get($key) + { + switch ($key) { + case 'vendor-dir': + case 'bin-dir': + case 'process-timeout': + // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config + $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); + return $this->process(getenv($env) ?: $this->config[$key]); + + case 'home': + return rtrim($this->process($this->config[$key]), '/\\'); + + default: + return $this->process($this->config[$key]); + } + } + + /** + * Checks whether a setting exists + * + * @param string $key + * @return Boolean + */ + public function has($key) + { + return array_key_exists($key, $this->config); + } + + /** + * Replaces {$refs} inside a config string + * + * @param string a config string that can contain {$refs-to-other-config} + * @return string + */ + private function process($value) + { + $config = $this; + return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config) { + return $config->get($match[1]); + }, $value); + } +} diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 526ba5bf3..4f3b799c8 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -27,66 +27,84 @@ use Composer\Util\RemoteFilesystem; */ class Factory { + public static function createConfig() + { + // load main Composer configuration + if (!$home = getenv('COMPOSER_HOME')) { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $home = getenv('APPDATA') . '/Composer'; + } else { + $home = getenv('HOME') . '/.composer'; + } + } + + $config = new Config(); + + $file = new JsonFile($home.'/config.json'); + if ($file->exists()) { + $config->merge($file->read()); + } + + // add home dir to the config + $config->merge(array('config' => array('home' => $home))); + + return $config; + } + /** * Creates a Composer instance * + * @param IOInterface $io IO instance + * @param mixed $localConfig either a configuration array or a filename to read from, if null it will read from the default filename * @return Composer */ - public function createComposer(IOInterface $io, $composerFile = null) + public function createComposer(IOInterface $io, $localConfig = null) { // load Composer configuration - if (null === $composerFile) { - $composerFile = getenv('COMPOSER') ?: 'composer.json'; + if (null === $localConfig) { + $localConfig = getenv('COMPOSER') ?: 'composer.json'; } - $file = new JsonFile($composerFile, new RemoteFilesystem($io)); - if (!$file->exists()) { - if ($composerFile === 'composer.json') { - $message = 'Composer could not find a composer.json file in '.getcwd(); - } else { - $message = 'Composer could not find the config file: '.$composerFile; + if (is_string($localConfig)) { + $composerFile = $localConfig; + $file = new JsonFile($localConfig, new RemoteFilesystem($io)); + + if (!$file->exists()) { + if ($localConfig === 'composer.json') { + $message = 'Composer could not find a composer.json file in '.getcwd(); + } else { + $message = 'Composer could not find the config file: '.$localConfig; + } + $instructions = 'To initialize a project, please create a composer.json file as described in the http://getcomposer.org/ "Getting Started" section'; + throw new \InvalidArgumentException($message.PHP_EOL.$instructions); } - $instructions = 'To initialize a project, please create a composer.json file as described in the http://getcomposer.org/ "Getting Started" section'; - throw new \InvalidArgumentException($message.PHP_EOL.$instructions); + + $file->validateSchema(JsonFile::LAX_SCHEMA); + $localConfig = $file->read(); } // Configuration defaults - $composerConfig = array( - 'vendor-dir' => 'vendor', - 'process-timeout' => 300, - ); + $config = $this->createConfig(); + $config->merge($localConfig); - $packageConfig = $file->read(); - $file->validateSchema(JsonFile::LAX_SCHEMA); - - if (isset($packageConfig['config']) && is_array($packageConfig['config'])) { - $packageConfig['config'] = array_merge($composerConfig, $packageConfig['config']); - } else { - $packageConfig['config'] = $composerConfig; - } - - $vendorDir = getenv('COMPOSER_VENDOR_DIR') ?: $packageConfig['config']['vendor-dir']; - if (!isset($packageConfig['config']['bin-dir'])) { - $packageConfig['config']['bin-dir'] = $vendorDir.'/bin'; - } - $binDir = getenv('COMPOSER_BIN_DIR') ?: $packageConfig['config']['bin-dir']; + $vendorDir = $config->get('vendor-dir'); + $binDir = $config->get('bin-dir'); // setup process timeout - $processTimeout = getenv('COMPOSER_PROCESS_TIMEOUT') ?: $packageConfig['config']['process-timeout']; - ProcessExecutor::setTimeout((int) $processTimeout); + ProcessExecutor::setTimeout((int) $config->get('process-timeout')); // initialize repository manager - $rm = $this->createRepositoryManager($io); + $rm = $this->createRepositoryManager($io, $config); // load default repository unless it's explicitly disabled - $packageConfig = $this->addPackagistRepository($packageConfig); + $localConfig = $this->addPackagistRepository($localConfig); // load local repository $this->addLocalRepository($rm, $vendorDir); // load package $loader = new Package\Loader\RootPackageLoader($rm); - $package = $loader->load($packageConfig); + $package = $loader->load($localConfig); // initialize download manager $dm = $this->createDownloadManager($io); @@ -97,24 +115,27 @@ class Factory // purge packages if they have been deleted on the filesystem $this->purgePackages($rm, $im); - // init locker - $lockFile = substr($composerFile, -5) === '.json' ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; - $locker = new Package\Locker(new JsonFile($lockFile, new RemoteFilesystem($io)), $rm, md5_file($composerFile)); - // initialize composer $composer = new Composer(); + $composer->setConfig($config); $composer->setPackage($package); - $composer->setLocker($locker); $composer->setRepositoryManager($rm); $composer->setDownloadManager($dm); $composer->setInstallationManager($im); + // init locker if possible + if (isset($composerFile)) { + $lockFile = substr($composerFile, -5) === '.json' ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; + $locker = new Package\Locker(new JsonFile($lockFile, new RemoteFilesystem($io)), $rm, md5_file($composerFile)); + $composer->setLocker($locker); + } + return $composer; } - protected function createRepositoryManager(IOInterface $io) + protected function createRepositoryManager(IOInterface $io, Config $config) { - $rm = new RepositoryManager($io); + $rm = new RepositoryManager($io, $config); $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository'); $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository'); $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository'); @@ -131,18 +152,19 @@ class Factory $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/.composer/installed.json'))); } - protected function addPackagistRepository(array $packageConfig) + protected function addPackagistRepository(array $localConfig) { $loadPackagist = true; $packagistConfig = array( 'type' => 'composer', 'url' => 'http://packagist.org' ); - if (isset($packageConfig['repositories'])) { - foreach ($packageConfig['repositories'] as $key => $repo) { + + if (isset($localConfig['repositories'])) { + foreach ($localConfig['repositories'] as $key => $repo) { if (isset($repo['packagist'])) { if (true === $repo['packagist']) { - $packageConfig['repositories'][$key] = $packagistConfig; + $localConfig['repositories'][$key] = $packagistConfig; } $loadPackagist = false; @@ -150,14 +172,14 @@ class Factory } } } else { - $packageConfig['repositories'] = array(); + $localConfig['repositories'] = array(); } if ($loadPackagist) { - $packageConfig['repositories'][] = $packagistConfig; + $localConfig['repositories'][] = $packagistConfig; } - return $packageConfig; + return $localConfig; } public function createDownloadManager(IOInterface $io) @@ -194,10 +216,15 @@ class Factory } } - static public function create(IOInterface $io, $composerFile = null) + /** + * @param IOInterface $io IO instance + * @param mixed $config either a configuration array or a filename to read from, if null it will read from the default filename + * @return Composer + */ + static public function create(IOInterface $io, $config = null) { $factory = new static(); - return $factory->createComposer($io, $composerFile); + return $factory->createComposer($io, $config); } } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 450dfdf33..9778f294b 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -16,6 +16,7 @@ use Composer\Package\Loader\ArrayLoader; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Json\JsonFile; use Composer\Cache; +use Composer\Config; use Composer\IO\IOInterface; use Composer\Util\RemoteFilesystem; @@ -29,20 +30,20 @@ class ComposerRepository extends ArrayRepository protected $packages; protected $cache; - public function __construct(array $config, IOInterface $io) + public function __construct(array $repoConfig, IOInterface $io, Config $config) { - if (!preg_match('{^\w+://}', $config['url'])) { + if (!preg_match('{^\w+://}', $repoConfig['url'])) { // assume http as the default protocol - $config['url'] = 'http://'.$config['url']; + $repoConfig['url'] = 'http://'.$repoConfig['url']; } - $config['url'] = rtrim($config['url'], '/'); - if (function_exists('filter_var') && !filter_var($config['url'], FILTER_VALIDATE_URL)) { - throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$config['url']); + $repoConfig['url'] = rtrim($repoConfig['url'], '/'); + if (function_exists('filter_var') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) { + throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']); } - $this->url = $config['url']; + $this->url = $repoConfig['url']; $this->io = $io; - $this->cache = new Cache($io, preg_replace('{[^a-z0-9.]}', '-', $this->url)); + $this->cache = new Cache($io, $config->get('home').'/cache/'.preg_replace('{[^a-z0-9.]}', '-', $this->url)); } protected function initialize() diff --git a/src/Composer/Repository/PearRepository.php b/src/Composer/Repository/PearRepository.php index a73d2c8b4..9c4bf7ba7 100644 --- a/src/Composer/Repository/PearRepository.php +++ b/src/Composer/Repository/PearRepository.php @@ -16,6 +16,7 @@ use Composer\IO\IOInterface; use Composer\Package\Loader\ArrayLoader; use Composer\Util\RemoteFilesystem; use Composer\Json\JsonFile; +use Composer\Config; use Composer\Downloader\TransportException; /** @@ -31,18 +32,18 @@ class PearRepository extends ArrayRepository private $io; private $rfs; - public function __construct(array $config, IOInterface $io, RemoteFilesystem $rfs = null) + public function __construct(array $repoConfig, IOInterface $io, Config $config, RemoteFilesystem $rfs = null) { - if (!preg_match('{^https?://}', $config['url'])) { - $config['url'] = 'http://'.$config['url']; + if (!preg_match('{^https?://}', $repoConfig['url'])) { + $repoConfig['url'] = 'http://'.$repoConfig['url']; } - if (function_exists('filter_var') && !filter_var($config['url'], FILTER_VALIDATE_URL)) { - throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$config['url']); + if (function_exists('filter_var') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) { + throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$repoConfig['url']); } - $this->url = rtrim($config['url'], '/'); - $this->channel = !empty($config['channel']) ? $config['channel'] : null; + $this->url = rtrim($repoConfig['url'], '/'); + $this->channel = !empty($repoConfig['channel']) ? $repoConfig['channel'] : null; $this->io = $io; $this->rfs = $rfs ?: new RemoteFilesystem($this->io); } diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index 4935ef68c..8dd684082 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -13,6 +13,7 @@ namespace Composer\Repository; use Composer\IO\IOInterface; +use Composer\Config; /** * Repositories manager. @@ -27,10 +28,12 @@ class RepositoryManager private $repositories = array(); private $repositoryClasses = array(); private $io; + private $config; - public function __construct(IOInterface $io) + public function __construct(IOInterface $io, Config $config) { $this->io = $io; + $this->config = $config; } /** @@ -94,7 +97,7 @@ class RepositoryManager } $class = $this->repositoryClasses[$type]; - return new $class($config, $this->io); + return new $class($config, $this->io, $this->config); } /** diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 80a1390e5..00499b5bb 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/src/Composer/Repository/VcsRepository.php @@ -19,6 +19,7 @@ use Composer\Package\PackageInterface; use Composer\Package\AliasPackage; use Composer\Package\Loader\ArrayLoader; use Composer\IO\IOInterface; +use Composer\Config; /** * @author Jordi Boggiano @@ -32,7 +33,7 @@ class VcsRepository extends ArrayRepository protected $versionParser; protected $type; - public function __construct(array $config, IOInterface $io, array $drivers = null) + public function __construct(array $repoConfig, IOInterface $io, Config $config = null, array $drivers = null) { $this->drivers = $drivers ?: array( 'github' => 'Composer\Repository\Vcs\GitHubDriver', @@ -43,9 +44,9 @@ class VcsRepository extends ArrayRepository 'hg' => 'Composer\Repository\Vcs\HgDriver', ); - $this->url = $config['url']; + $this->url = $repoConfig['url']; $this->io = $io; - $this->type = isset($config['type']) ? $config['type'] : 'vcs'; + $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs'; $this->verbose = $io->isVerbose(); }