diff --git a/doc/03-cli.md b/doc/03-cli.md index 1adc75395..8c855a286 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -675,9 +675,11 @@ The `COMPOSER_HOME` var allows you to change the Composer home directory. This is a hidden, global (per-user on the machine) directory that is shared between all projects. -By default it points to `/home//.composer` on \*nix, -`/Users//.composer` on OSX and -`C:\Users\\AppData\Roaming\Composer` on Windows. +By default it points to `C:\Users\\AppData\Roaming\Composer` on Windows +and `/Users//.composer` on OSX. On *nix systems that follow the [XDG Base +Directory Specifications](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html), +it points to `$XDG_CONFIG_HOME/composer`. On other *nix systems, it points to +`/home//.composer`. #### COMPOSER_HOME/config.json diff --git a/doc/06-config.md b/doc/06-config.md index 89003ed82..fd1988136 100644 --- a/doc/06-config.md +++ b/doc/06-config.md @@ -89,9 +89,10 @@ into this directory. ## cache-dir -Defaults to `$COMPOSER_HOME/cache` on unix systems and -`C:\Users\\AppData\Local\Composer` on Windows. Stores all the caches used -by Composer. See also [COMPOSER_HOME](03-cli.md#composer-home). +Defaults `C:\Users\\AppData\Local\Composer` on Windows, +`$XDG_CACHE_HOME/composer` on unix systems that follow the XDG Base Directory +Specifications, and `$home/cache` on other unix systems. Stores all the caches +used by Composer. See also [COMPOSER_HOME](03-cli.md#composer-home). ## cache-files-dir diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 9610920fe..581c45ab6 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -70,7 +70,7 @@ EOT $remoteFilesystem = Factory::createRemoteFilesystem($io, $config); $cacheDir = $config->get('cache-dir'); - $rollbackDir = $config->get('home'); + $rollbackDir = $config->get('data-dir'); $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; // check if current dir is writable and if not try the cache dir from settings diff --git a/src/Composer/Config.php b/src/Composer/Config.php index a9c3e9eda..92f91b117 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -30,6 +30,7 @@ class Config 'vendor-dir' => 'vendor', 'bin-dir' => '{$vendor-dir}/bin', 'cache-dir' => '{$home}/cache', + 'data-dir' => '{$home}', 'cache-files-dir' => '{$cache-dir}/files', 'cache-repo-dir' => '{$cache-dir}/repo', 'cache-vcs-dir' => '{$cache-dir}/vcs', @@ -172,6 +173,7 @@ class Config case 'vendor-dir': case 'bin-dir': case 'process-timeout': + case 'data-dir': case 'cache-dir': case 'cache-files-dir': case 'cache-repo-dir': diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 084a6f0d0..940f84a12 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -40,26 +40,75 @@ use Seld\JsonLint\JsonParser; class Factory { /** - * @throws \RuntimeException + * + * @return boolean + */ + private static function useXdg() + { + foreach (array_keys($_SERVER) as $key) { + if (substr($key, 0, 4) === 'XDG_') { + return true; + } + } + + return false; + } + + /** * @return string + * @throws \RuntimeException + */ + private static function getUserDir() + { + if (!getenv('HOME')) { + throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly'); + } + $userDir = rtrim(getenv('HOME'), '/'); + + return $userDir; + } + + /** + * @return string + * @throws \RuntimeException */ protected static function getHomeDir() { $home = getenv('COMPOSER_HOME'); - if (!$home) { - if (defined('PHP_WINDOWS_VERSION_MAJOR')) { - if (!getenv('APPDATA')) { - throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly'); - } - $home = strtr(getenv('APPDATA'), '\\', '/') . '/Composer'; - } else { - if (!getenv('HOME')) { - throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly'); - } - $home = rtrim(getenv('HOME'), '/') . '/.composer'; - } + if ($home) { + return $home; } + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + if (!getenv('APPDATA')) { + throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly'); + } + $home = strtr(getenv('APPDATA'), '\\', '/') . '/Composer'; + + return $home; + } + + $userDir = self::getUserDir(); + + if (is_dir($userDir . '/.composer')) { + $home = $userDir . '/.composer'; + + return $home; + } + + if (self::useXdg()) { + // XDG Base Directory Specifications + $xdgConfig = getenv('XDG_CONFIG_HOME'); + if (!$xdgConfig) { + $xdgConfig = $userDir . '/.config'; + } + $home = $xdgConfig . '/composer'; + + return $home; + } + + $home = $userDir . '/.composer'; + return $home; } @@ -70,24 +119,83 @@ class Factory protected static function getCacheDir($home) { $cacheDir = getenv('COMPOSER_CACHE_DIR'); - if (!$cacheDir) { - if (defined('PHP_WINDOWS_VERSION_MAJOR')) { - if ($cacheDir = getenv('LOCALAPPDATA')) { - $cacheDir .= '/Composer'; - } else { - $cacheDir = $home . '/cache'; - } - $cacheDir = strtr($cacheDir, '\\', '/'); - } else { - $cacheDir = $home.'/cache'; - } + if ($cacheDir) { + return $cacheDir; } + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + if ($cacheDir = getenv('LOCALAPPDATA')) { + $cacheDir .= '/Composer'; + } else { + $cacheDir = $home . '/cache'; + } + $cacheDir = strtr($cacheDir, '\\', '/'); + + return $cacheDir; + } + + $userDir = self::getUserDir(); + + if ($home === $userDir . '/.composer' && is_dir($home . '/cache')) { + $cacheDir = $home . '/cache'; + + return $cacheDir; + } + + if (self::useXdg()) { + $xdgCache = getenv('XDG_CACHE_HOME'); + if (!$xdgCache) { + $xdgCache = $userDir . '/.cache'; + } + $cacheDir = $xdgCache . '/composer'; + + return $cacheDir; + } + + $cacheDir = $home . '/cache'; + return $cacheDir; } /** - * @param IOInterface|null $io + * @param string $home + * + * @return string + */ + protected static function getDataDir($home) + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $dataDir = strtr($home, '\\', '/'); + + return $dataDir; + } + + $userDir = self::getUserDir(); + + if ($home === $userDir . '/.composer') { + $cacheDir = $home; + + return $cacheDir; + } + + if (self::useXdg()) { + $xdgData = getenv('XDG_DATA_HOME'); + if (!$xdgData) { + $userDir = self::getUserDir(); + $xdgData = $userDir . '/.local/share'; + } + $dataDir = $xdgData . '/composer'; + + return $dataDir; + } + + $dataDir = $home; + + return $dataDir; + } + + /** + * @param IOInterface|null $io * @return Config */ public static function createConfig(IOInterface $io = null, $cwd = null) @@ -97,11 +205,12 @@ class Factory // determine home and cache dirs $home = self::getHomeDir(); $cacheDir = self::getCacheDir($home); + $dataDir = self::getDataDir($home); // Protect directory against web access. Since HOME could be // the www-data's user home and be web-accessible it is a // potential security risk - foreach (array($home, $cacheDir) as $dir) { + foreach (array($home, $cacheDir, $dataDir) as $dir) { if (!file_exists($dir . '/.htaccess')) { if (!is_dir($dir)) { @mkdir($dir, 0777, true); @@ -113,7 +222,7 @@ class Factory $config = new Config(true, $cwd); // add dirs to the config - $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir))); + $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir, 'data-dir' => $dataDir))); // load global config $file = new JsonFile($config->get('home').'/config.json');