Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix UNC/WSL-path issues when running in Windows #9995

Merged
merged 2 commits into from Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/Composer/Factory.php
Expand Up @@ -385,7 +385,7 @@ public function createComposer(IOInterface $io, $localConfig = null, $disablePlu
$composer->setPackage($package);

// load local repository
$this->addLocalRepository($io, $rm, $vendorDir, $package);
$this->addLocalRepository($io, $rm, $vendorDir, $package, $process);

// initialize installation manager
$im = $this->createInstallationManager($loop, $io, $dispatcher);
Expand Down Expand Up @@ -458,9 +458,14 @@ public static function createGlobal(IOInterface $io, $disablePlugins = false)
* @param Repository\RepositoryManager $rm
* @param string $vendorDir
*/
protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir, RootPackageInterface $rootPackage)
protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir, RootPackageInterface $rootPackage, ProcessExecutor $process = null)
{
$rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io), true, $rootPackage));
$fs = null;
if ($process) {
$fs = new Filesystem($process);
}

$rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io), true, $rootPackage, $fs));
}

/**
Expand Down
32 changes: 21 additions & 11 deletions src/Composer/Repository/FilesystemRepository.php
Expand Up @@ -31,6 +31,7 @@ class FilesystemRepository extends WritableArrayRepository
protected $file;
private $dumpVersions;
private $rootPackage;
private $filesystem;

/**
* Initializes filesystem repository.
Expand All @@ -39,12 +40,13 @@ class FilesystemRepository extends WritableArrayRepository
* @param bool $dumpVersions
* @param ?RootPackageInterface $rootPackage Must be provided if $dumpVersions is true
*/
public function __construct(JsonFile $repositoryFile, $dumpVersions = false, RootPackageInterface $rootPackage = null)
public function __construct(JsonFile $repositoryFile, $dumpVersions = false, RootPackageInterface $rootPackage = null, Filesystem $filesystem = null)
{
parent::__construct();
$this->file = $repositoryFile;
$this->dumpVersions = $dumpVersions;
$this->rootPackage = $rootPackage;
$this->filesystem = $filesystem ?: new Filesystem;
if ($dumpVersions && !$rootPackage) {
throw new \InvalidArgumentException('Expected a root package instance if $dumpVersions is true');
}
Expand Down Expand Up @@ -100,14 +102,24 @@ public function write($devMode, InstallationManager $installationManager)
{
$data = array('packages' => array(), 'dev' => $devMode, 'dev-package-names' => array());
$dumper = new ArrayDumper();
$fs = new Filesystem();
$repoDir = dirname($fs->normalizePath($this->file->getPath()));

// make sure the directory is created so we can realpath it
// as realpath() does some additional normalizations with network paths that normalizePath does not
// and we need to find shortest path correctly
$repoDir = dirname($this->file->getPath());
$this->filesystem->ensureDirectoryExists($repoDir);

$repoDir = $this->filesystem->normalizePath(realpath($repoDir));
$installPaths = array();

foreach ($this->getCanonicalPackages() as $package) {
$pkgArray = $dumper->dump($package);
$path = $installationManager->getInstallPath($package);
$installPath = ('' !== $path && null !== $path) ? $fs->findShortestPath($repoDir, $fs->isAbsolutePath($path) ? $path : getcwd() . '/' . $path, true) : null;
$installPath = null;
if ('' !== $path && null !== $path) {
$normalizedPath = $this->filesystem->normalizePath($this->filesystem->isAbsolutePath($path) ? $path : getcwd() . '/' . $path);
$installPath = $this->filesystem->findShortestPath($repoDir, $normalizedPath, true);
}
$installPaths[$package->getName()] = $installPath;

$pkgArray['install-path'] = $installPath;
Expand All @@ -130,9 +142,9 @@ public function write($devMode, InstallationManager $installationManager)
if ($this->dumpVersions) {
$versions = $this->generateInstalledVersions($installationManager, $installPaths, $devMode, $repoDir);

$fs->filePutContentsIfModified($repoDir.'/installed.php', '<?php return ' . $this->dumpToPhpCode($versions) . ';'."\n");
$this->filesystem->filePutContentsIfModified($repoDir.'/installed.php', '<?php return ' . $this->dumpToPhpCode($versions) . ';'."\n");
$installedVersionsClass = file_get_contents(__DIR__.'/../InstalledVersions.php');
$fs->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass);
$this->filesystem->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass);

\Composer\InstalledVersions::reload($versions);
}
Expand All @@ -154,8 +166,7 @@ private function dumpToPhpCode(array $array = array(), $level = 0)
$lines .= "array(),\n";
}
} elseif ($key === 'install_path' && is_string($value)) {
$fs = new Filesystem();
if ($fs->isAbsolutePath($value)) {
if ($this->filesystem->isAbsolutePath($value)) {
$lines .= var_export($value, true) . ",\n";
} else {
$lines .= "__DIR__ . " . var_export('/' . $value, true) . ",\n";
Expand Down Expand Up @@ -203,9 +214,8 @@ private function generateInstalledVersions(InstallationManager $installationMana
}

if ($package instanceof RootPackageInterface) {
$fs = new Filesystem();
$to = getcwd();
$installPath = $fs->findShortestPath($repoDir, $to, true);
$to = $this->filesystem->normalizePath(realpath(getcwd()));
$installPath = $this->filesystem->findShortestPath($repoDir, $to, true);
} else {
$installPath = $installPaths[$package->getName()];
}
Expand Down
14 changes: 10 additions & 4 deletions src/Composer/Util/Filesystem.php
Expand Up @@ -544,7 +544,13 @@ public function normalizePath($path)
$parts = array();
$path = strtr($path, '\\', '/');
$prefix = '';
$absolute = false;
$absolute = '';

// extract windows UNC paths e.g. \\foo\bar
if (strpos($path, '//') === 0 && \strlen($path) > 2) {
$absolute = '//';
$path = substr($path, 2);
}

// extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
if (preg_match('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) {
Expand All @@ -553,13 +559,13 @@ public function normalizePath($path)
}

if (strpos($path, '/') === 0) {
$absolute = true;
$absolute = '/';
$path = substr($path, 1);
}

$up = false;
foreach (explode('/', $path) as $chunk) {
if ('..' === $chunk && ($absolute || $up)) {
if ('..' === $chunk && ($absolute !== '' || $up)) {
array_pop($parts);
$up = !(empty($parts) || '..' === end($parts));
} elseif ('.' !== $chunk && '' !== $chunk) {
Expand All @@ -568,7 +574,7 @@ public function normalizePath($path)
}
}

return $prefix.($absolute ? '/' : '').implode('/', $parts);
return $prefix.((string) $absolute).implode('/', $parts);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/Composer/Test/Mock/FactoryMock.php
Expand Up @@ -46,7 +46,7 @@ protected function loadRootPackage(RepositoryManager $rm, Config $config, Versio
return new \Composer\Package\Loader\RootPackageLoader($rm, $config, $parser, new VersionGuesserMock(), $io);
}

protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir, RootPackageInterface $rootPackage)
protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir, RootPackageInterface $rootPackage, ProcessExecutor $process = null)
{
}

Expand Down
9 changes: 7 additions & 2 deletions tests/Composer/Test/Repository/FilesystemRepositoryTest.php
Expand Up @@ -16,6 +16,7 @@
use Composer\Repository\FilesystemRepository;
use Composer\Test\TestCase;
use Composer\Json\JsonFile;
use Composer\Util\Filesystem;

class FilesystemRepositoryTest extends TestCase
{
Expand Down Expand Up @@ -83,13 +84,17 @@ public function testRepositoryWrite()
{
$json = $this->createJsonFileMock();

$repoDir = realpath(sys_get_temp_dir()).'/repo_write_test/';
$fs = new Filesystem();
$fs->removeDirectory($repoDir);

$repository = new FilesystemRepository($json);
$im = $this->getMockBuilder('Composer\Installer\InstallationManager')
->disableOriginalConstructor()
->getMock();
$im->expects($this->exactly(2))
->method('getInstallPath')
->will($this->returnValue('/foo/bar/vendor/woop/woop'));
->will($this->returnValue($repoDir.'/vendor/woop/woop'));

$json
->expects($this->once())
Expand All @@ -98,7 +103,7 @@ public function testRepositoryWrite()
$json
->expects($this->once())
->method('getPath')
->will($this->returnValue('/foo/bar/vendor/composer/installed.json'));
->will($this->returnValue($repoDir.'/vendor/composer/installed.json'));
$json
->expects($this->once())
->method('exists')
Expand Down
1 change: 1 addition & 0 deletions tests/Composer/Test/Util/FilesystemTest.php
Expand Up @@ -220,6 +220,7 @@ public function provideNormalizedPaths()
array('../src', 'Foo/Bar/../../../src'),
array('c:../b', 'c:.\\..\\a\\..\\b'),
array('phar://c:../Foo', 'phar://c:../Foo'),
array('//foo/bar', '\\\\foo\\bar'),
);
}

Expand Down