diff --git a/legacy/phpstan-baseline.neon b/legacy/phpstan-baseline.neon index 246d95cc6..46e66db0c 100644 --- a/legacy/phpstan-baseline.neon +++ b/legacy/phpstan-baseline.neon @@ -7122,12 +7122,6 @@ parameters: count: 1 path: src/Service/Shell.php - - - message: '#^Method Platformsh\\Cli\\Service\\Shell\:\:runProcess\(\) should return int but returns int\|null\.$#' - identifier: return.type - count: 1 - path: src/Service/Shell.php - - message: '#^Parameter \#1 \$messages of method Symfony\\Component\\Console\\Output\\OutputInterface\:\:write\(\) expects iterable\|string, string\|null given\.$#' identifier: argument.type diff --git a/legacy/src/Service/Config.php b/legacy/src/Service/Config.php index fb33936ba..38b4ed046 100644 --- a/legacy/src/Service/Config.php +++ b/legacy/src/Service/Config.php @@ -547,7 +547,7 @@ public function getVersion(): string return $this->version; } $version = $this->getWithDefault('application.version', '@version-placeholder@'); - if (str_starts_with((string) $version, '@') && str_ends_with((string) $version, '@')) { + if (str_starts_with((string) $version, '@') && str_ends_with((string) $version, '@') && !str_starts_with(CLI_ROOT, 'phar://')) { // Silently try getting the version from Git. $tag = (new Shell())->execute(['git', 'describe', '--tags'], CLI_ROOT); if (is_string($tag) && str_starts_with($tag, 'v')) { diff --git a/legacy/src/Service/Shell.php b/legacy/src/Service/Shell.php index 69ad5c87c..dc9d0d3d9 100644 --- a/legacy/src/Service/Shell.php +++ b/legacy/src/Service/Shell.php @@ -213,9 +213,14 @@ private function runProcess(Process $process, bool $mustRun = false, bool $quiet $output->write(preg_replace('/(^|[\n\r]+)(.)/', '$1 $2', $buffer)); } }); - } catch (ProcessFailedException) { + } catch (ProcessFailedException $e) { if (!$mustRun) { - return $process->getExitCode(); + // The exit code is null if the process failed to start. + return $process->getExitCode() ?? 1; + } + // A process that failed to start has no output to report. + if (!$process->isStarted()) { + throw $e; } // The default for Symfony's ProcessFailedException is to print the // entire STDOUT and STDERR. But if $quiet is disabled, then the user @@ -278,6 +283,10 @@ protected function findWhere(string $command, bool $noticeOnError = true): strin public function exceptionMeansCommandDoesNotExist(ProcessFailedException $e): bool { $process = $e->getProcess(); + // A process that failed to start has no exit code or output to check. + if (!$process->isStarted()) { + return false; + } if ($process->getExitCode() === 127) { return true; } diff --git a/legacy/tests/Service/ShellServiceTest.php b/legacy/tests/Service/ShellServiceTest.php index 5fe54cff5..c33e38827 100644 --- a/legacy/tests/Service/ShellServiceTest.php +++ b/legacy/tests/Service/ShellServiceTest.php @@ -7,9 +7,13 @@ use PHPUnit\Framework\TestCase; use Platformsh\Cli\Exception\ProcessFailedException; use Platformsh\Cli\Service\Shell; +use Platformsh\Cli\Tests\HasTempDirTrait; +use Symfony\Component\Process\Exception\ProcessStartFailedException; class ShellServiceTest extends TestCase { + use HasTempDirTrait; + /** * Test Shell::execute(). */ @@ -47,4 +51,27 @@ public function testMustExecute(): void $this->expectException(ProcessFailedException::class); $shell->mustExecute(['which', 'nonexistent']); } + + /** + * Test Shell::execute() when the process cannot start, so has no exit code. + */ + public function testExecuteStartFailure(): void + { + $this->tempDirSetUp(); + assert($this->tempDir !== null); + + // A phar:// directory passes is_dir() but cannot be used by proc_open(). + $archive = new \PharData($this->tempDir . '/test.tar'); + $archive->addFromString('dir/file.txt', 'test'); + $dir = 'phar://' . $this->tempDir . '/test.tar/dir'; + + $shell = new Shell(); + $this->assertFalse($shell->execute(['pwd'], $dir)); + try { + $shell->mustExecute(['pwd'], $dir); + $this->fail('Expected a ProcessStartFailedException'); + } catch (ProcessStartFailedException $e) { + $this->assertFalse($shell->exceptionMeansCommandDoesNotExist($e)); + } + } }