Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3fccef1
Fix help for package+version command hint
asgrim Feb 7, 2025
ea26307
Split out PIE package installed.json processor
asgrim Feb 7, 2025
e1d2f92
Do not purge packages so they remain available in pie show even after…
asgrim Feb 7, 2025
ff18d8e
Improve BinaryFile with verify methods
asgrim Feb 12, 2025
154921f
Added component to remove INI entries for a package
asgrim Feb 12, 2025
31c3a92
Added component to uninstall the installed binary
asgrim Feb 12, 2025
48c3372
ComposerIntegrationHandler no longer __invoke
asgrim Feb 17, 2025
97dcf13
Add method to remove a require from pie.json
asgrim Feb 17, 2025
0b08684
Added uninstall to PieOperation enum
asgrim Feb 17, 2025
1ab49d6
Added ComposerIntegrationHandler#runUninstall
asgrim Feb 17, 2025
7ce8f44
Added Composer uninstall processor for PiePackageInstaller
asgrim Feb 17, 2025
edb5448
Use Composer in InstalledPiePackages
asgrim Feb 20, 2025
9f420e8
Fix uninstall and complete tests
asgrim Feb 24, 2025
7800733
Disable uninstall command for Windows
asgrim Feb 25, 2025
cc3d533
Use sudo to remove the binary if no permissions given
asgrim Feb 25, 2025
393ead4
Move BinaryFile into new File namespace
asgrim Feb 26, 2025
80edcb1
Util to capture and return errors running a function
asgrim Feb 26, 2025
21597e5
Added Sudo locator
asgrim Feb 26, 2025
7a21a9d
Added file util to write to a file with sudo
asgrim Feb 26, 2025
b451bf5
Use sudo to write INI file to remove the INI entry
asgrim Feb 26, 2025
acaeac4
Only run SudoFilePutTest if allowed
asgrim Feb 26, 2025
1d3ac87
Display pie.json path if in verbose mode
asgrim Feb 28, 2025
6e53759
Use sudo helper in other sudo locations
asgrim Feb 28, 2025
0856813
Use sudo for INI additions and file unlinking
asgrim Feb 28, 2025
7d45866
Only try to chmod file in SudoFilePut if it already exists
asgrim Feb 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/pie
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use Php\Pie\Command\RepositoryAddCommand;
use Php\Pie\Command\RepositoryListCommand;
use Php\Pie\Command\RepositoryRemoveCommand;
use Php\Pie\Command\ShowCommand;
use Php\Pie\Command\UninstallCommand;
use Php\Pie\Util\PieVersion;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
Expand All @@ -37,6 +38,7 @@ $application->setCommandLoader(new ContainerCommandLoader(
'repository:list' => RepositoryListCommand::class,
'repository:add' => RepositoryAddCommand::class,
'repository:remove' => RepositoryRemoveCommand::class,
'uninstall' => UninstallCommand::class,
]
));

Expand Down
8 changes: 8 additions & 0 deletions features/uninstall-extensions.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
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
Then the extension should not be installed anymore
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
requireCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnSkippedTests="true"
displayDetailsOnTestsThatTriggerWarnings="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
Expand Down
2 changes: 1 addition & 1 deletion src/Building/Build.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace Php\Pie\Building;

use Php\Pie\BinaryFile;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;
Expand Down
2 changes: 1 addition & 1 deletion src/Building/UnixBuild.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace Php\Pie\Building;

use Php\Pie\BinaryFile;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
Expand Down
2 changes: 1 addition & 1 deletion src/Building/WindowsBuild.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace Php\Pie\Building;

use Php\Pie\BinaryFile;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\WindowsExtensionAssetName;
Expand Down
2 changes: 1 addition & 1 deletion src/Command/BuildCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public function execute(InputInterface $input, OutputInterface $output): int
);

try {
($this->composerIntegrationHandler)(
$this->composerIntegrationHandler->runInstall(
$package,
$composer,
$targetPlatform,
Expand Down
22 changes: 15 additions & 7 deletions src/Command/CommandHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use InvalidArgumentException;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform as PiePlatform;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Php\Pie\Platform\TargetPhp\PhpizePath;
Expand Down Expand Up @@ -64,27 +65,27 @@ public static function configurePhpConfigOptions(Command $command): void
InputOption::VALUE_REQUIRED,
'The path to the `php` binary to use as the target PHP platform on ' . OperatingSystem::Windows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_PATH . '=C:\usr\php7.4.33\php.exe',
);
$command->addOption(
self::OPTION_WITH_PHPIZE_PATH,
null,
InputOption::VALUE_REQUIRED,
'The path to the `phpize` binary to use as the target PHP platform, e.g. --' . self::OPTION_WITH_PHPIZE_PATH . '=/usr/bin/phpize7.4',
);
}

public static function configureDownloadBuildInstallOptions(Command $command): void
{
$command->addArgument(
self::ARG_REQUESTED_PACKAGE_AND_VERSION,
InputArgument::REQUIRED,
'The extension name and version constraint to use, in the format {ext-name}{?:{?version-constraint}{?@stability}}, for example `xdebug/xdebug:^3.4@alpha`, `xdebug/xdebug:@alpha`, `xdebug/xdebug:^3.4`, etc.',
'The PIE package name and version constraint to use, in the format {vendor/package}{?:{?version-constraint}{?@stability}}, for example `xdebug/xdebug:^3.4@alpha`, `xdebug/xdebug:@alpha`, `xdebug/xdebug:^3.4`, etc.',
);
$command->addOption(
self::OPTION_MAKE_PARALLEL_JOBS,
'j',
InputOption::VALUE_REQUIRED,
'Override many jobs to run in parallel when running compiling (this is passed to "make -jN" during build). PIE will try to detect this by default.',
);
$command->addOption(
self::OPTION_WITH_PHPIZE_PATH,
null,
InputOption::VALUE_REQUIRED,
'The path to the `phpize` binary to use as the target PHP platform, e.g. --' . self::OPTION_WITH_PHPIZE_PATH . '=/usr/bin/phpize7.4',
);
$command->addOption(
self::OPTION_SKIP_ENABLE_EXTENSION,
null,
Expand Down Expand Up @@ -168,6 +169,13 @@ public static function determineTargetPlatformFromInputs(InputInterface $input,
$targetPlatform->architecture->name,
$phpBinaryPath->phpBinaryPath,
));
$output->writeln(
sprintf(
'<info>Using pie.json:</info> %s',
PiePlatform::getPieJsonFilename($targetPlatform),
),
OutputInterface::VERBOSITY_VERBOSE,
);

return $targetPlatform;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Command/DownloadCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public function execute(InputInterface $input, OutputInterface $output): int
$output->writeln(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

try {
($this->composerIntegrationHandler)(
$this->composerIntegrationHandler->runInstall(
$package,
$composer,
$targetPlatform,
Expand Down
2 changes: 1 addition & 1 deletion src/Command/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function execute(InputInterface $input, OutputInterface $output): int
);

try {
($this->composerIntegrationHandler)(
$this->composerIntegrationHandler->runInstall(
$package,
$composer,
$targetPlatform,
Expand Down
81 changes: 28 additions & 53 deletions src/Command/ShowCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,21 @@

namespace Php\Pie\Command;

use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Php\Pie\BinaryFile;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\BinaryFileFailedVerification;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\TargetPlatform;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;

use function array_combine;
use function array_filter;
use function array_key_exists;
use function array_map;
use function array_walk;
use function file_exists;
use function sprintf;
Expand All @@ -38,6 +34,7 @@
final class ShowCommand extends Command
{
public function __construct(
private readonly InstalledPiePackages $installedPiePackages,
private readonly ContainerInterface $container,
) {
parent::__construct();
Expand All @@ -54,7 +51,15 @@ public function execute(InputInterface $input, OutputInterface $output): int
{
$targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);

$piePackages = $this->buildListOfPieInstalledPackages($output, $targetPlatform);
$composer = PieComposerFactory::createPieComposer(
$this->container,
PieComposerRequest::noOperation(
new NullOutput(),
$targetPlatform,
),
);

$piePackages = $this->installedPiePackages->allPiePackages($composer);
$phpEnabledExtensions = $targetPlatform->phpBinaryPath->extensions();
$extensionPath = $targetPlatform->phpBinaryPath->extensionPath();
$extensionEnding = $targetPlatform->operatingSystem === OperatingSystem::Windows ? '.dll' : '.so';
Expand Down Expand Up @@ -99,64 +104,34 @@ private static function verifyChecksumInformation(
string $extensionEnding,
array $installedJsonMetadata,
): string {
$expectedConventionalBinaryPath = $extensionPath . DIRECTORY_SEPARATOR . $phpExtensionName . $extensionEnding;
$actualBinaryPathByConvention = $extensionPath . DIRECTORY_SEPARATOR . $phpExtensionName . $extensionEnding;

// The extension may not be in the usual path (since you can specify a full path to an extension in the INI file)
if (! file_exists($expectedConventionalBinaryPath)) {
if (! file_exists($actualBinaryPathByConvention)) {
return '';
}

$pieExpectedBinaryPath = array_key_exists(PieInstalledJsonMetadataKeys::InstalledBinary->value, $installedJsonMetadata) ? $installedJsonMetadata[PieInstalledJsonMetadataKeys::InstalledBinary->value] : null;
$pieExpectedChecksum = array_key_exists(PieInstalledJsonMetadataKeys::BinaryChecksum->value, $installedJsonMetadata) ? $installedJsonMetadata[PieInstalledJsonMetadataKeys::BinaryChecksum->value] : null;

// Some other kind of mismatch of file path, or we don't have a stored checksum available
if ($expectedConventionalBinaryPath !== $pieExpectedBinaryPath || $pieExpectedChecksum === null) {
if (
$pieExpectedBinaryPath === null
|| $pieExpectedChecksum === null
|| $pieExpectedBinaryPath !== $actualBinaryPathByConvention
) {
return '';
}

$actualInstalledBinary = BinaryFile::fromFileWithSha256Checksum($expectedConventionalBinaryPath);
if ($actualInstalledBinary->checksum !== $pieExpectedChecksum) {
return ' ⚠️ was ' . substr($actualInstalledBinary->checksum, 0, 8) . '..., expected ' . substr($pieExpectedChecksum, 0, 8) . '...';
$expectedBinaryFileFromMetadata = new BinaryFile($pieExpectedBinaryPath, $pieExpectedChecksum);
$actualBinaryFile = BinaryFile::fromFileWithSha256Checksum($actualBinaryPathByConvention);

try {
$expectedBinaryFileFromMetadata->verifyAgainstOther($actualBinaryFile);
} catch (BinaryFileFailedVerification) {
return ' ⚠️ was ' . substr($actualBinaryFile->checksum, 0, 8) . '..., expected ' . substr($expectedBinaryFileFromMetadata->checksum, 0, 8) . '...';
}

return ' ✅';
}

/** @return array<non-empty-string, Package> */
private function buildListOfPieInstalledPackages(
OutputInterface $output,
TargetPlatform $targetPlatform,
): array {
$composerInstalledPackages = array_map(
static function (CompletePackageInterface $package): Package {
return Package::fromComposerCompletePackage($package);
},
array_filter(
PieComposerFactory::createPieComposer(
$this->container,
PieComposerRequest::noOperation(
$output,
$targetPlatform,
),
)
->getRepositoryManager()
->getLocalRepository()
->getPackages(),
static function (BasePackage $basePackage): bool {
return $basePackage instanceof CompletePackageInterface;
},
),
);

return array_combine(
array_map(
/** @return non-empty-string */
static function (Package $package): string {
return $package->extensionName()->name();
},
$composerInstalledPackages,
),
$composerInstalledPackages,
);
}
}
Loading