Browse Source

Merge pull request #551 from Seldaek/config

Add Config class and system-wide config management, fixes #513
Nils Adermann 13 years ago
parent
commit
f93ec9daa4

+ 2 - 7
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)) {

+ 10 - 0
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;

+ 93 - 0
src/Composer/Config.php

@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+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);
+    }
+}

+ 77 - 50
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);
     }
 }

+ 9 - 8
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()

+ 8 - 7
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);
     }

+ 5 - 2
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);
     }
 
     /**

+ 4 - 3
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 <j.boggiano@seld.be>
@@ -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();
     }