From f1c20ba19b581ea38b7d20d47cf445f5ed4b09c1 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 6 Mar 2025 22:22:09 +0000 Subject: [PATCH 1/2] Added mechanism to move in-use extension out the way --- features/uninstall-extensions.feature | 2 -- src/Command/UninstallCommand.php | 11 ------- src/Downloading/DownloadUrlMethod.php | 1 + src/File/SudoCreate.php | 1 + src/File/SudoUnlink.php | 10 +++++- src/File/WindowsDelete.php | 47 +++++++++++++++++++++++++++ src/Installing/WindowsInstall.php | 18 +++------- 7 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 src/File/WindowsDelete.php diff --git a/features/uninstall-extensions.feature b/features/uninstall-extensions.feature index 154c10cf..1167897e 100644 --- a/features/uninstall-extensions.feature +++ b/features/uninstall-extensions.feature @@ -1,7 +1,5 @@ Feature: Extensions can be uninstalled with PIE - # See https://github.com/php/pie/issues/190 for why this is non-Windows - @non-windows Example: An extension can be uninstalled Given an extension was previously installed When I run a command to uninstall an extension diff --git a/src/Command/UninstallCommand.php b/src/Command/UninstallCommand.php index 0e6997bf..0cd46dc9 100644 --- a/src/Command/UninstallCommand.php +++ b/src/Command/UninstallCommand.php @@ -5,7 +5,6 @@ namespace Php\Pie\Command; use Composer\Composer; -use Composer\Util\Platform; use Php\Pie\ComposerIntegration\ComposerIntegrationHandler; use Php\Pie\ComposerIntegration\PieComposerFactory; use Php\Pie\ComposerIntegration\PieComposerRequest; @@ -54,16 +53,6 @@ public function configure(): void public function execute(InputInterface $input, OutputInterface $output): int { - if (Platform::isWindows()) { - /** - * @todo add support for uninstalling in Windows - see - * {@link https://github.com/php/pie/issues/190} for details - */ - $output->writeln('Uninstalling extensions on Windows is not currently supported.'); - - return 1; - } - if (! TargetPlatform::isRunningAsRoot()) { $output->writeln('This command may need elevated privileges, and may prompt you for your password.'); } diff --git a/src/Downloading/DownloadUrlMethod.php b/src/Downloading/DownloadUrlMethod.php index 397de170..6a5a95d8 100644 --- a/src/Downloading/DownloadUrlMethod.php +++ b/src/Downloading/DownloadUrlMethod.php @@ -10,6 +10,7 @@ use Php\Pie\Platform\TargetPlatform; use Php\Pie\Platform\WindowsExtensionAssetName; +/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ enum DownloadUrlMethod: string { case ComposerDefaultDownload = 'composer-default'; diff --git a/src/File/SudoCreate.php b/src/File/SudoCreate.php index e7ba4899..9a58c003 100644 --- a/src/File/SudoCreate.php +++ b/src/File/SudoCreate.php @@ -13,6 +13,7 @@ use function is_writable; use function touch; +/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ final class SudoCreate { public static function file(string $filename): void diff --git a/src/File/SudoUnlink.php b/src/File/SudoUnlink.php index 4f1bb542..1cfe33a6 100644 --- a/src/File/SudoUnlink.php +++ b/src/File/SudoUnlink.php @@ -4,6 +4,7 @@ namespace Php\Pie\File; +use Composer\Util\Platform; use Php\Pie\Util\CaptureErrors; use Php\Pie\Util\Process; use Symfony\Component\Process\Exception\ProcessFailedException; @@ -12,6 +13,7 @@ use function is_writable; use function unlink; +/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ final class SudoUnlink { public static function singleFile(string $filename): void @@ -20,7 +22,7 @@ public static function singleFile(string $filename): void return; } - if (is_writable($filename)) { + if (! Platform::isWindows() && is_writable($filename)) { $capturedErrors = []; $unlinkSuccessful = CaptureErrors::for( static fn () => unlink($filename), @@ -34,6 +36,12 @@ public static function singleFile(string $filename): void return; } + if (Platform::isWindows()) { + WindowsDelete::usingMoveToTemp($filename); + + return; + } + if (! Sudo::exists()) { throw FailedToUnlinkFile::fromNoPermissions($filename); } diff --git a/src/File/WindowsDelete.php b/src/File/WindowsDelete.php new file mode 100644 index 00000000..edae1ac5 --- /dev/null +++ b/src/File/WindowsDelete.php @@ -0,0 +1,47 @@ +writeln('Copied extras: ' . $destinationPathname); } - /** - * @link https://github.com/php/pie/issues/20 - * - * @todo this should be improved in future to try to automatically set up the ext - */ - $output->writeln(sprintf( - 'You must now add "%s=%s" to your php.ini', - $downloadedPackage->package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension', - $downloadedPackage->package->extensionName()->name(), - )); - $binaryFile = BinaryFile::fromFileWithSha256Checksum($destinationDllName); ($this->setupIniFile)( @@ -124,6 +112,10 @@ private function copyExtensionDll(TargetPlatform $targetPlatform, DownloadedPack $destinationDllName = $targetPlatform->phpBinaryPath->extensionPath() . DIRECTORY_SEPARATOR . 'php_' . $downloadedPackage->package->extensionName()->name() . '.dll'; + if (file_exists($destinationDllName)) { + WindowsDelete::usingMoveToTemp($destinationDllName); + } + if (! copy($sourceDllName, $destinationDllName) || ! file_exists($destinationDllName) && ! is_file($destinationDllName)) { throw new RuntimeException('Failed to install DLL to ' . $destinationDllName); } From 20501aac4ae93b54d67263a9b010bc30d503f6a1 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 6 Mar 2025 22:38:27 +0000 Subject: [PATCH 2/2] Fix regex for matching INI extension|zend_extension directives --- src/Installing/Ini/RemoveIniEntryWithFileGetContents.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Installing/Ini/RemoveIniEntryWithFileGetContents.php b/src/Installing/Ini/RemoveIniEntryWithFileGetContents.php index 6b4235c5..8cc9bee9 100644 --- a/src/Installing/Ini/RemoveIniEntryWithFileGetContents.php +++ b/src/Installing/Ini/RemoveIniEntryWithFileGetContents.php @@ -60,7 +60,7 @@ static function (string $path) use ($additionalIniDirectory): bool { } $regex = sprintf( - '/^(%s\w*=\w*%s)/m', + '/^(%s\s*=\s*%s)/m', $package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension', $package->extensionName()->name(), );