From 23ad67b3bed09c5bfc807f76c611b6f6ee0ae59e Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Tue, 12 Apr 2016 13:14:07 +0200 Subject: [PATCH 1/6] Implement support for tilde expansion, mainly for path repositories --- src/Composer/Repository/PathRepository.php | 3 ++- src/Composer/Util/Platform.php | 28 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/PathRepository.php b/src/Composer/Repository/PathRepository.php index 9c850a672..f42658d67 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 8939b9467..cd936672e 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -19,6 +19,32 @@ namespace Composer\Util; */ class Platform { + /** + * Parses magic constructs like tildes in paths. Right now only tildes are supported but we could add support for + * environment variables on various platforms. + * + * @param string $path + * @return string + */ + public static function expandPath($path) + { + // Tilde expansion for *nix + if (!self::isWindows() && 0 === strpos($path, '~/')) { + if (function_exists('posix_getuid') && function_exists('posix_getpwuid')) { + $info = posix_getpwuid(posix_getuid()); + $home = $info['dir']; + } else { + $home = getenv('HOME'); + } + // Cannot be empty or FALSE + if (!$home) { + throw new \RuntimeException(sprintf('No home folder found to expand ~ with in %s', $path)); + } + $path = $home . substr($path, 1); + } + return $path; + } + /** * @return bool Whether the host machine is running a Windows OS */ @@ -26,4 +52,4 @@ class Platform { return defined('PHP_WINDOWS_VERSION_BUILD'); } -} + } From 2e2cec29bd3b5625954e4a7c2928903471112eea Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Tue, 12 Apr 2016 13:44:26 +0200 Subject: [PATCH 2/6] Docs for tilde expansion. --- doc/05-repositories.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 2889a1924..a395f2b4a 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: ``` @@ -667,9 +667,9 @@ 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. +If present on *nix systems leading tildes are expanded to the current user's home folder, which +can be handy when working on teams on the same packages. For example `~/git/mypackage` will +automatically load the mypackage clone from `/home//git/mypackage` for every developer. > **Note:** Repository paths can also contain wildcards like ``*`` and ``?``. > For details, see the [PHP glob function](http://php.net/glob). From 7e71b2bfbca85a8f07c41ace0f8e34248904b6d7 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Tue, 12 Apr 2016 23:07:58 +0200 Subject: [PATCH 3/6] Added support for expanding environment variables in paths, and tilde expansion on Windows. --- src/Composer/Util/Platform.php | 41 +++++++++++++---------- tests/Composer/Test/Util/PlatformTest.php | 13 ++++++- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index cd936672e..530591064 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -20,29 +20,36 @@ namespace Composer\Util; class Platform { /** - * Parses magic constructs like tildes in paths. Right now only tildes are supported but we could add support for - * environment variables on various platforms. + * Parses tildes and environment variables in paths. * * @param string $path * @return string */ public static function expandPath($path) { - // Tilde expansion for *nix - if (!self::isWindows() && 0 === strpos($path, '~/')) { - if (function_exists('posix_getuid') && function_exists('posix_getpwuid')) { - $info = posix_getpwuid(posix_getuid()); - $home = $info['dir']; - } else { - $home = getenv('HOME'); - } - // Cannot be empty or FALSE - if (!$home) { - throw new \RuntimeException(sprintf('No home folder found to expand ~ with in %s', $path)); - } - $path = $home . substr($path, 1); + if (preg_match('#^~[/\\\\]#', $path)) { + return self::getUserDirectory() . substr($path, 1); } - return $path; + return preg_replace_callback(self::isWindows() ? '#^(%(\\w+)%)[/\\\\]#' : '#^(\\$(\\w+))/#', function($matches) { + return getenv($matches[2]) . DIRECTORY_SEPARATOR; + }, $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'); } /** @@ -52,4 +59,4 @@ class Platform { return defined('PHP_WINDOWS_VERSION_BUILD'); } - } +} diff --git a/tests/Composer/Test/Util/PlatformTest.php b/tests/Composer/Test/Util/PlatformTest.php index 4cbe7ffe8..4b0491147 100644 --- a/tests/Composer/Test/Util/PlatformTest.php +++ b/tests/Composer/Test/Util/PlatformTest.php @@ -21,7 +21,18 @@ use Composer\Util\Platform; */ class PlatformTest extends \PHPUnit_Framework_TestCase { - public function testWindows() + public function testExpandPath() + { + putenv('TESTENV=/home/test'); + if (Platform::isWindows()) { + $this->assertEquals('/home/test/myPath', Platform::expandPath('%TESTENV%/myPath')); + } else { + $this->assertEquals('/home/test/myPath', Platform::expandPath('$TESTENV/myPath')); + } + $this->assertEquals((getenv('HOME') ?: getenv('USERPROFILE')) . DIRECTORY_SEPARATOR . '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()); From f5422a441d706f46979c5278b6a8ffddbea95b08 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Tue, 12 Apr 2016 23:51:28 +0200 Subject: [PATCH 4/6] Fixed Windows path separators and updated docs. --- doc/05-repositories.md | 12 +++++++----- src/Composer/Util/Platform.php | 2 +- tests/Composer/Test/Util/PlatformTest.php | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index a395f2b4a..9887fd9fc 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -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 } ``` -If present on *nix systems leading tildes are expanded to the current user's home folder, which -can be handy when working on teams on the same packages. For example `~/git/mypackage` will -automatically load the mypackage clone from `/home//git/mypackage` for every developer. +Leading tildes are expanded to the current user's home folder, and environment +variables are parsed according to host platform. For example `~/git/mypackage` +will automatically load the mypackage clone from `/home//git/mypackage`, +which is equivalent to `$HOME/git/mypackage` on Linux/Mac or +`%USERPROFILE%/git/mypackage` on Windows. > **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/Util/Platform.php b/src/Composer/Util/Platform.php index 530591064..ada67c635 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -31,7 +31,7 @@ class Platform return self::getUserDirectory() . substr($path, 1); } return preg_replace_callback(self::isWindows() ? '#^(%(\\w+)%)[/\\\\]#' : '#^(\\$(\\w+))/#', function($matches) { - return getenv($matches[2]) . DIRECTORY_SEPARATOR; + return getenv($matches[2]) . '/'; }, $path); } diff --git a/tests/Composer/Test/Util/PlatformTest.php b/tests/Composer/Test/Util/PlatformTest.php index 4b0491147..985e25cd1 100644 --- a/tests/Composer/Test/Util/PlatformTest.php +++ b/tests/Composer/Test/Util/PlatformTest.php @@ -29,7 +29,7 @@ class PlatformTest extends \PHPUnit_Framework_TestCase } else { $this->assertEquals('/home/test/myPath', Platform::expandPath('$TESTENV/myPath')); } - $this->assertEquals((getenv('HOME') ?: getenv('USERPROFILE')) . DIRECTORY_SEPARATOR . 'test', Platform::expandPath('~/test')); + $this->assertEquals((getenv('HOME') ?: getenv('USERPROFILE')) . '/test', Platform::expandPath('~/test')); } public function testIsWindows() From c9534d48c16102799c716ba924fc36acb560309a Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 13 Apr 2016 02:02:50 +0200 Subject: [PATCH 5/6] Made env variable parsing in path replacements generic across platforms and replaced old config.php implementation. --- doc/05-repositories.md | 8 ++++---- src/Composer/Config.php | 3 ++- src/Composer/Util/Platform.php | 4 ++-- tests/Composer/Test/ConfigTest.php | 3 +-- tests/Composer/Test/Util/PlatformTest.php | 7 ++----- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/doc/05-repositories.md b/doc/05-repositories.md index 9887fd9fc..703d26266 100644 --- a/doc/05-repositories.md +++ b/doc/05-repositories.md @@ -668,10 +668,10 @@ Forcing mirroring can be useful when deploying or generating package from a mono ``` Leading tildes are expanded to the current user's home folder, and environment -variables are parsed according to host platform. For example `~/git/mypackage` -will automatically load the mypackage clone from `/home//git/mypackage`, -which is equivalent to `$HOME/git/mypackage` on Linux/Mac or -`%USERPROFILE%/git/mypackage` on Windows. +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 4c16913cf..13b456d40 100644 --- a/src/Composer/Config.php +++ b/src/Composer/Config.php @@ -14,6 +14,7 @@ namespace Composer; use Composer\Config\ConfigSourceInterface; use Composer\Downloader\TransportException; +use Composer\Util\Platform; /** * @author Jordi Boggiano @@ -207,7 +208,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/Util/Platform.php b/src/Composer/Util/Platform.php index ada67c635..71aa028c5 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -30,8 +30,8 @@ class Platform if (preg_match('#^~[/\\\\]#', $path)) { return self::getUserDirectory() . substr($path, 1); } - return preg_replace_callback(self::isWindows() ? '#^(%(\\w+)%)[/\\\\]#' : '#^(\\$(\\w+))/#', function($matches) { - return getenv($matches[2]) . '/'; + return preg_replace_callback('#^([\\$%])(\\w+)\\1?(([/\\\\].*)?)#', function($matches) { + return getenv($matches[2]) . $matches[3]; }, $path); } diff --git a/tests/Composer/Test/ConfigTest.php b/tests/Composer/Test/ConfigTest.php index 9fd693122..0d4f6cb6f 100644 --- a/tests/Composer/Test/ConfigTest.php +++ b/tests/Composer/Test/ConfigTest.php @@ -13,7 +13,6 @@ namespace Composer\Test; use Composer\Config; -use Composer\Downloader\TransportException; class ConfigTest extends \PHPUnit_Framework_TestCase { @@ -151,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 985e25cd1..129410d95 100644 --- a/tests/Composer/Test/Util/PlatformTest.php +++ b/tests/Composer/Test/Util/PlatformTest.php @@ -24,11 +24,8 @@ class PlatformTest extends \PHPUnit_Framework_TestCase public function testExpandPath() { putenv('TESTENV=/home/test'); - if (Platform::isWindows()) { - $this->assertEquals('/home/test/myPath', Platform::expandPath('%TESTENV%/myPath')); - } else { - $this->assertEquals('/home/test/myPath', Platform::expandPath('$TESTENV/myPath')); - } + $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')); } From 4b5375f4c07b4d29462820f52aeca42ef204f805 Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Wed, 13 Apr 2016 23:53:27 +0200 Subject: [PATCH 6/6] Need legacy patch for older hardcoded behaviour in handling HOME/USERPROFILE env variables. --- src/Composer/Util/Platform.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Util/Platform.php b/src/Composer/Util/Platform.php index 71aa028c5..7e0bf1dab 100644 --- a/src/Composer/Util/Platform.php +++ b/src/Composer/Util/Platform.php @@ -31,6 +31,10 @@ class Platform 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); }