diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php index 31ff5798..bc7897ca 100644 --- a/src/Command/SelfUpdateCommand.php +++ b/src/Command/SelfUpdateCommand.php @@ -14,6 +14,7 @@ use Php\Pie\Platform; use Php\Pie\SelfManage\Update\Channel; use Php\Pie\SelfManage\Update\FetchPieReleaseFromGitHub; +use Php\Pie\SelfManage\Update\IsBrewInstallation; use Php\Pie\SelfManage\Update\ReleaseIsNewer; use Php\Pie\SelfManage\Update\ReleaseMetadata; use Php\Pie\SelfManage\Verify\FailedToVerifyRelease; @@ -30,6 +31,8 @@ use Throwable; use function file_get_contents; +use function is_link; +use function realpath; use function sprintf; use function unlink; @@ -87,6 +90,15 @@ public function execute(InputInterface $input, OutputInterface $output): int return Command::FAILURE; } + $originalPathToSelf = ($this->fullPathToSelf)(); + $fullPathToSelf = is_link($originalPathToSelf) ? (realpath($originalPathToSelf) ?: $originalPathToSelf) : $originalPathToSelf; + + if ((new IsBrewInstallation())($fullPathToSelf, $originalPathToSelf)) { + $this->io->writeError('Aborting! PIE was installed with Brew, you should upgrade with `brew upgrade pie`.'); + + return Command::FAILURE; + } + $settings = new Settings(Platform::getPieBaseWorkingDirectory()); $updateChannel = $settings->updateChannel(); @@ -197,7 +209,6 @@ public function execute(InputInterface $input, OutputInterface $output): int return Command::FAILURE; } - $fullPathToSelf = ($this->fullPathToSelf)(); $this->io->write( sprintf('Writing new version to %s', $fullPathToSelf), verbosity: IOInterface::VERBOSE, diff --git a/src/SelfManage/Update/IsBrewInstallation.php b/src/SelfManage/Update/IsBrewInstallation.php new file mode 100644 index 00000000..ab951423 --- /dev/null +++ b/src/SelfManage/Update/IsBrewInstallation.php @@ -0,0 +1,19 @@ + */ + public static function pathProvider(): array + { + return [ + 'regular-path' => ['/home/user/.local/bin/pie.phar', '/home/user/.local/bin/pie.phar', false], + 'both-regular-usr-local' => ['/usr/local/bin/pie', '/usr/local/bin/pie', false], + 'intel-mac-cellar-direct' => ['/usr/local/Cellar/pie/1.0/bin/pie', '/usr/local/Cellar/pie/1.0/bin/pie', true], + 'apple-silicon-cellar-direct' => ['/opt/homebrew/Cellar/pie/1.0/bin/pie', '/opt/homebrew/Cellar/pie/1.0/bin/pie', true], + 'resolved-is-cellar' => ['/opt/homebrew/Cellar/pie/1.0/bin/pie', '/usr/local/bin/pie', true], + 'original-is-cellar' => ['/usr/local/bin/pie', '/opt/homebrew/Cellar/pie/1.0/bin/pie', true], + ]; + } + + #[DataProvider('pathProvider')] + public function testIsBrewInstallationWithPaths( + string $resolvedPath, + string $originalPath, + bool $expected, + ): void { + self::assertSame($expected, (new IsBrewInstallation())($resolvedPath, $originalPath)); + } + + #[RequiresOperatingSystemFamily('Linux')] + public function testSymlinkAtRegularPathPointingIntoBrewCellarIsDetected(): void + { + $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_brew_test_', true); + $cellarFile = $tmpDir . '/opt/homebrew/Cellar/pie/1.0/pie.phar'; + $symlinkPath = $tmpDir . '/pie'; + + mkdir($tmpDir . '/opt/homebrew/Cellar/pie/1.0', 0777, true); + touch($cellarFile); + symlink($cellarFile, $symlinkPath); + + try { + $resolvedPath = realpath($symlinkPath); + self::assertIsString($resolvedPath); + self::assertTrue((new IsBrewInstallation())($resolvedPath, $symlinkPath)); + } finally { + (new Filesystem())->remove($tmpDir); + } + } + + #[RequiresOperatingSystemFamily('Linux')] + public function testSymlinkAtRegularPathPointingToNonBrewPathIsNotDetected(): void + { + $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_brew_test_', true); + $regularFile = $tmpDir . '/regular/pie.phar'; + $symlinkPath = $tmpDir . '/pie'; + + mkdir($tmpDir . '/regular', 0777, true); + touch($regularFile); + symlink($regularFile, $symlinkPath); + + try { + $resolvedPath = realpath($symlinkPath); + self::assertIsString($resolvedPath); + self::assertFalse((new IsBrewInstallation())($resolvedPath, $symlinkPath)); + } finally { + (new Filesystem())->remove($tmpDir); + } + } + + #[RequiresOperatingSystemFamily('Linux')] + public function testSymlinkInCellarPointingToRegularPathIsDetectedViaOriginalPath(): void + { + $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_brew_test_', true); + $regularFile = $tmpDir . '/regular/pie.phar'; + $symlinkPath = $tmpDir . '/opt/homebrew/Cellar/pie/1.0/pie'; + + mkdir($tmpDir . '/regular', 0777, true); + mkdir($tmpDir . '/opt/homebrew/Cellar/pie/1.0', 0777, true); + touch($regularFile); + symlink($regularFile, $symlinkPath); + + try { + $resolvedPath = realpath($symlinkPath); + self::assertIsString($resolvedPath); + self::assertTrue((new IsBrewInstallation())($resolvedPath, $symlinkPath)); + } finally { + (new Filesystem())->remove($tmpDir); + } + } +}