-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Improve stream wrapper support #12396
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
base: main
Are you sure you want to change the base?
Conversation
return strpos($path, '/') === 0 | ||
|| substr($path, 1, 1) === ':' | ||
|| strpos($path, '\\\\') === 0 | ||
|| Preg::isMatch($this->streamWrappersRegex, $path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this should be used inside Platform::realpath for the PR to have any effect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. This change is for places where a non-absolute path is prepended with the working dir, e.g.
$dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir); |
which is unnecessary for stream paths.
That said, I do need to look closer at where it is used, e.g.
composer/src/Composer/Repository/Vcs/SvnDriver.php
Lines 364 to 366 in 85ff84d
if ($fs->isAbsolutePath($url)) { | |
return 'file://' . strtr($url, '\\', '/'); | |
} |
Per the linked bug yes maybe we should always realpath n+1 times on windows, for as long as we get a new path, to make sure we resolve junctions.. On linux the double realpath is useless. As for Platform::getCwd I do not think it should realpath, as you don't always want to realpath it AFAIR.
I guess those could be modified too for consistency. |
@@ -56,13 +56,19 @@ public static function getCwd(bool $allowEmpty = false): string | |||
|
|||
/** | |||
* Infallible realpath version that falls back on the given $path if realpath is not working | |||
* | |||
* NB: Do not remove recursive `realpath()` calls. Fixes failing Windows `realpath()` implementation. | |||
* @see https://bugs.php.net/bug.php?id=72738 | |||
*/ | |||
public static function realpath(string $path): string | |||
{ | |||
$realPath = realpath($path); | |||
if ($realPath === false) { | |||
return $path; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return $path; | |
return file_exists($realPath) || is_dir($realpath); |
I was applying the realpath()
to Platform::realpath()
change across the other cases, and they are sometimes followed by if($realpathResult === false){ }
blocks. E.g.
composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php
Lines 50 to 53 in 85ff84d
$sourcesRealPath = realpath($sources); | |
if ($sourcesRealPath === false) { | |
throw new \RuntimeException('Could not realpath() the source directory "'.$sources.'"'); | |
} |
I'm thinking Platform::realpath()
should return false
if the path is incorrect, which is inherent in a realpath('/does/not/exist')
call, and which the stream wrapper should handle correctly.
(and revert 198a7ea)
I need to look at the original reason Platform::realpath()
was introduced: nicolas-grekas@3d1fb9d
Any suggestion on writing tests on this? I often use antecedent/patchwork which allows redefining built in PHP functions. Although I see composer/composer is using PHPUnit in an unconventional way.
I have applied Platform::realpath()
wherever it was not followed by a false
check: d1081a3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking
Platform::realpath()
should returnfalse
if the path is incorrect, which is inherent in arealpath('/does/not/exist')
call, and which the stream wrapper should handle correctly.
Yes, this complicates Platform::realpath() usage.. Ideally we avoid returning false, because checking for it everywhere is a pain. I can see with stream wrappers it's probably more likely to return false, but what are conditions where they would do that? Can we recover from this or should we just throw?
The fallback to return $path;
that we currently have in Platform::realpath is because in theory realpath shouldn't fail when the path exists, and we don't call it if the path doesn't exist AFAIK. I have never been able to conclusively say what might trigger a false otherwise, so I put this fallback in place and nobody ever complained so either it isn't used or it works 🤷🏻♂️ .
I'd probably be fine throwing in case false is returned, and then we see what happens in the real world, if it causes issues we can still revert to falling back for non-stream-wrapper paths I guess to restore the previous behavior.
Stream wrapper usage is definitely very fringe, so I am a bit worried of degrading the overall experience just to fix this.
Any suggestion on writing tests on this? I often use antecedent/patchwork which allows redefining built in PHP functions. Although I see composer/composer is using PHPUnit in an unconventional way.
You should be able to use patchwork as it requires php 7.1+, so just give it a shot I guess..
if (false === $resolvedPath) { | ||
continue; | ||
} | ||
$resolvedPath = Platform::realpath($installPath . '/' . $updir); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a special case where we aren't checking file_exists before, and thus using the false
return value as indication that the path doesn't exist and can be skipped.
So I'd probably add a file_exists check before, assuming realpath is changed to throw on failure. Or try/catch to be safer perhaps, because I suppose even if the file exists it might still throw in some weird conditions.
@@ -142,7 +143,7 @@ public function compile(string $pharFile = 'composer.phar'): void | |||
__DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe', | |||
__DIR__ . '/../../vendor/symfony/console/Resources/completion.bash', | |||
] as $file) { | |||
$extraFiles[$file] = realpath($file); | |||
$extraFiles[$file] = Platform::realpath($file); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one should be moved below the file_exists check that happens after realpath..
@@ -199,7 +199,7 @@ public function doRun(InputInterface $input, OutputInterface $output): int | |||
&& $input->hasParameterOption('-h', true) === false | |||
) { | |||
$dir = dirname(Platform::getCwd(true)); | |||
$home = realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/'); | |||
$home = Platform::realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one too I'd probably try/catch and use $home = Platform::realpath('/')
as fallback. Because otherwise if HOME points to an invalid dir somehow this will blow up in bad ways for people, and this is a fairly hot code path so I'd rather not risk major issues.
*/ | ||
public static function realpath(string $path): string | ||
{ | ||
$realPath = realpath($path); | ||
if ($realPath === false) { | ||
return $path; | ||
} | ||
if ($realPath !== $path) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ($realPath !== $path) { | |
if (self::isWindows() && $realPath !== $path) { |
Co-authored-by: Jordi Boggiano <j.boggiano@seld.be>
realpath()
always returnsfalse
for stream wrappers. This PR changes:Factory
andAutoloadGenerator
to use the existingComposer\Util\Platform::realpath()
function instead of PHP'srealpath()
Filesystem::isAbsolutePath()
to returntrue
for stream wrappers' pathsSee:
Platform::realpath()
, released 2.8.3, Nov 2024PhpStan CI check fails for
SuggestedPackagesReporter.php
which is not related to this PR.Edit:
There are an additional ~58 uses of regular
realpath()
in the code (grep -rn src -e "[^t:]realpath(" | grep -v '//' | grep -v "\s*\*" | wc -l
), across 30 files (grep -rl src -e "[^t:]realpath(" | wc -l
). An okay find & replace regex for PhpStorm is(^((?!//|\*).)*(?<!function |get|::|>))(realpath\()
,$1Platform::$3
but it has some false positives, particularlyBinaryInstaller
's$globalsCode
string, and the files may needuse Composer\Util\Platform;
added.And in
AutoloadGenerator
:composer/src/Composer/Autoload/AutoloadGenerator.php
Lines 214 to 217 in 85ff84d
Maybe the double-realpath call should be moved into
Platform::realpath()
and thePlatform::getCwd()
should always return itsrealpath()
?