diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 2889a1924..703d26266 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -609,8 +609,8 @@ update to the latest version. ### Path In addition to the artifact repository, you can use the path one, which allows -you to depend on a relative directory. This can be especially useful when dealing -with monolith repositories. +you to depend on a local directory, either absolute or relative. This can be +especially useful when dealing with monolithic repositories. For instance, if you have the following directory structure in your repository: ``` @@ -649,8 +649,8 @@ the console will read `Symlinked from ../../packages/my-package`. If symlinking is _not_ possible the package will be copied. In that case, the console will output `Mirrored from ../../packages/my-package`. -Instead of default fallback strategy you can force to use symlink with `"symlink": true` or -mirroring with `"symlink": false` option. +Instead of default fallback strategy you can force to use symlink with `"symlink": true` +or mirroring with `"symlink": false` option. Forcing mirroring can be useful when deploying or generating package from a monolithic repository. ```json @@ -667,9 +667,11 @@ Forcing mirroring can be useful when deploying or generating package from a mono } ``` - - -Instead of using a relative path, an absolute path can also be used. +Leading tildes are expanded to the current user's home folder, and environment +variables are parsed in both Windows and Linux/Mac notations. For example +`~/git/mypackage` will automatically load the mypackage clone from +`/home//git/mypackage`, equivalent to `$HOME/git/mypackage` or +`%USERPROFILE%/git/mypackage`. > **Note:** Repository paths can also contain wildcards like ``*`` and ``?``. > For details, see the [PHP glob function](http://php.net/glob). diff --git a/src/Composer/Config.php b/src/Composer/Config.php index aaca4e4c9..59306f9d2 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -15,6 +15,7 @@ namespace Composer; use Composer\Config\ConfigSourceInterface; use Composer\Downloader\TransportException; use Composer\IO\IOInterface; +use Composer\Util\Platform; /** * @author Jordi Boggiano @@ -210,7 +211,7 @@ class Config $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); $val = rtrim($this->process($this->getComposerEnv($env) ?: $this->config[$key], $flags), '/\\'); - $val = preg_replace('#^(\$HOME|~)(/|$)#', rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '/\\') . '/', $val); + $val = Platform::expandPath($val); if (substr($key, -4) !== '-dir') { return $val; diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index cb93710e7..4acdb03c7 100644 --- a/src/Composer/Repository/PathRepository.php +++ b/src/Composer/Repository/PathRepository.php @@ -18,6 +18,7 @@ use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Version\VersionGuesser; use Composer\Package\Version\VersionParser; +use Composer\Util\Platform; use Composer\Util\ProcessExecutor; /** @@ -101,7 +102,7 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn } $this->loader = new ArrayLoader(null, true); - $this->url = $repoConfig['url']; + $this->url = Platform::expandPath($repoConfig['url']); $this->process = new ProcessExecutor($io); $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser()); $this->repoConfig = $repoConfig; diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index 252041d66..1b2b4031f 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -19,6 +19,43 @@ namespace Composer\Util; */ class Platform { + /** + * Parses tildes and environment variables in paths. + * + * @param string $path + * @return string + */ + public static function expandPath($path) + { + if (preg_match('#^~[/\\\\]#', $path)) { + return self::getUserDirectory() . substr($path, 1); + } + return preg_replace_callback('#^([\\$%])(\\w+)\\1?(([/\\\\].*)?)#', function($matches) { + // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons + if (Platform::isWindows() && $matches[2] == 'HOME') { + return (getenv('HOME') ?: getenv('USERPROFILE')) . $matches[3]; + } + return getenv($matches[2]) . $matches[3]; + }, $path); + } + + /** + * @return string The formal user home as detected from environment parameters + * @throws \RuntimeException If the user home could not reliably be determined + */ + public static function getUserDirectory() + { + if (false !== ($home = getenv('HOME'))) { + return $home; + } elseif (self::isWindows() && false !== ($home = getenv('USERPROFILE'))) { + return $home; + } elseif (function_exists('posix_getuid') && function_exists('posix_getpwuid')) { + $info = posix_getpwuid(posix_getuid()); + return $info['dir']; + } + throw new \RuntimeException('Could not determine user directory'); + } + /** * @return bool Whether the host machine is running a Windows OS */ diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php index 619e8d55f..0d35faf6a 100644 --- a/tests/Composer/Test/ConfigTest.php +++ b/tests/Composer/Test/ConfigTest.php @@ -150,7 +150,7 @@ class ConfigTest extends \PHPUnit_Framework_TestCase $home = rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '\\/'); $this->assertEquals('b', $config->get('c')); - $this->assertEquals($home.'/', $config->get('bin-dir')); + $this->assertEquals($home, $config->get('bin-dir')); $this->assertEquals($home.'/foo', $config->get('cache-dir')); } diff --git a/tests/Composer/Test/Util/PlatformTest.php b/tests/Composer/Test/Util/PlatformTest.php index 4cbe7ffe8..129410d95 100644 --- a/tests/Composer/Test/Util/PlatformTest.php +++ b/tests/Composer/Test/Util/PlatformTest.php @@ -21,7 +21,15 @@ use Composer\Util\Platform; */ class PlatformTest extends \PHPUnit_Framework_TestCase { - public function testWindows() + public function testExpandPath() + { + putenv('TESTENV=/home/test'); + $this->assertEquals('/home/test/myPath', Platform::expandPath('%TESTENV%/myPath')); + $this->assertEquals('/home/test/myPath', Platform::expandPath('$TESTENV/myPath')); + $this->assertEquals((getenv('HOME') ?: getenv('USERPROFILE')) . '/test', Platform::expandPath('~/test')); + } + + public function testIsWindows() { // Compare 2 common tests for Windows to the built-in Windows test $this->assertEquals(('\\' === DIRECTORY_SEPARATOR), Platform::isWindows());