From c996618ce8afb6733c95b4793795e6263af5b675 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 7 Aug 2025 21:51:44 +0100 Subject: [PATCH] Show if a package can be updated with the given constraints --- src/Command/ShowCommand.php | 35 ++++++++++++- test/install-bundled-php-exts.php | 2 +- test/integration/Command/ShowCommandTest.php | 52 ++++++++++++++++++++ 3 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/Command/ShowCommand.php b/src/Command/ShowCommand.php index aea0af66..341baf17 100644 --- a/src/Command/ShowCommand.php +++ b/src/Command/ShowCommand.php @@ -7,6 +7,9 @@ use Php\Pie\ComposerIntegration\PieComposerFactory; use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys; +use Php\Pie\DependencyResolver\RequestedPackageAndVersion; +use Php\Pie\DependencyResolver\ResolveDependencyWithComposer; +use Php\Pie\DependencyResolver\UnableToResolveRequirement; use Php\Pie\File\BinaryFile; use Php\Pie\File\BinaryFileFailedVerification; use Php\Pie\Platform as PiePlatform; @@ -20,6 +23,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; +use Webmozart\Assert\Assert; use function array_diff; use function array_key_exists; @@ -44,6 +48,7 @@ final class ShowCommand extends Command public function __construct( private readonly InstalledPiePackages $installedPiePackages, private readonly ContainerInterface $container, + private readonly ResolveDependencyWithComposer $resolveDependencyWithComposer, ) { parent::__construct(); } @@ -93,6 +98,7 @@ public function execute(InputInterface $input, OutputInterface $output): int $extensionPath = $targetPlatform->phpBinaryPath->extensionPath(); $extensionEnding = $targetPlatform->operatingSystem === OperatingSystem::Windows ? '.dll' : '.so'; $piePackagesMatched = []; + $rootPackageRequires = $composer->getPackage()->getRequires(); $output->writeln(sprintf( "\n" . '%s:', @@ -100,7 +106,7 @@ public function execute(InputInterface $input, OutputInterface $output): int )); array_walk( $phpEnabledExtensions, - static function (string $version, string $phpExtensionName) use ($showAll, $output, $piePackages, $extensionPath, $extensionEnding, &$piePackagesMatched): void { + function (string $version, string $phpExtensionName) use ($composer, $rootPackageRequires, $targetPlatform, $showAll, $output, $piePackages, $extensionPath, $extensionEnding, &$piePackagesMatched): void { if (! array_key_exists($phpExtensionName, $piePackages)) { if ($showAll) { $output->writeln(sprintf(' %s:%s', $phpExtensionName, $version)); @@ -111,9 +117,33 @@ static function (string $version, string $phpExtensionName) use ($showAll, $outp $piePackage = $piePackages[$phpExtensionName]; $piePackagesMatched[] = $phpExtensionName; + $packageName = $piePackage->name(); + $packageRequirement = $rootPackageRequires[$piePackage->name()]->getPrettyConstraint(); + + try { + Assert::stringNotEmpty($packageName); + Assert::stringNotEmpty($packageRequirement); + + $latestPackage = ($this->resolveDependencyWithComposer)( + $composer, + $targetPlatform, + new RequestedPackageAndVersion($packageName, $packageRequirement), + false, + ); + } catch (UnableToResolveRequirement) { + $latestPackage = null; + } + + $updateNotice = ''; + if ($latestPackage !== null && $latestPackage->version() !== $piePackage->version()) { + $updateNotice = sprintf( + ' — new version %s available', + $latestPackage->version(), + ); + } $output->writeln(sprintf( - ' %s:%s (from 🥧 %s%s)', + ' %s:%s (from 🥧 %s%s)%s', $phpExtensionName, $version, $piePackage->prettyNameAndVersion(), @@ -123,6 +153,7 @@ static function (string $version, string $phpExtensionName) use ($showAll, $outp $extensionEnding, PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($piePackage->composerPackage()), ), + $updateNotice, )); }, ); diff --git a/test/install-bundled-php-exts.php b/test/install-bundled-php-exts.php index b37b64d2..e5f6d55c 100644 --- a/test/install-bundled-php-exts.php +++ b/test/install-bundled-php-exts.php @@ -51,7 +51,7 @@ } } -echo Process::run(['bin/pie', 'show', '--with-php-config=' . $phpBinaryPath->phpConfigPath()]); +echo Process::run(['bin/pie', 'show', '--with-php-config=' . $phpBinaryPath->phpConfigPath()], timeout: 60); if ($anyFailures) { exit(1); diff --git a/test/integration/Command/ShowCommandTest.php b/test/integration/Command/ShowCommandTest.php index 4790d354..4fc7358b 100644 --- a/test/integration/Command/ShowCommandTest.php +++ b/test/integration/Command/ShowCommandTest.php @@ -4,18 +4,29 @@ namespace Php\PieIntegrationTest\Command; +use InvalidArgumentException; +use Php\Pie\Command\InstallCommand; use Php\Pie\Command\ShowCommand; +use Php\Pie\ComposerIntegration\PieJsonEditor; use Php\Pie\Container; +use Php\Pie\Platform as PiePlatform; +use Php\Pie\Util\Process; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Webmozart\Assert\Assert; use function get_loaded_extensions; use function phpversion; +use const PHP_VERSION_ID; + #[CoversClass(ShowCommand::class)] final class ShowCommandTest extends TestCase { + private const TEST_PACKAGE = 'asgrim/example-pie-extension'; + private CommandTester $commandTester; public function setUp(): void @@ -38,4 +49,45 @@ public function testExecute(): void ); } } + + public function testExecuteWithAvailableUpdates(): void + { + if (PHP_VERSION_ID >= 80500) { + self::markTestSkipped('This test can only run on PHP 8.4 or lower'); + } + + try { + $phpConfig = Process::run(['which', 'php-config']); + Assert::stringNotEmpty($phpConfig); + } catch (ProcessFailedException | InvalidArgumentException) { + self::markTestSkipped('This test can only run on systems with php-config'); + } + + $installCommand = new CommandTester(Container::factory()->get(InstallCommand::class)); + $installCommand->execute([ + 'requested-package-and-version' => self::TEST_PACKAGE . ':2.0.2', + '--with-php-config' => $phpConfig, + ]); + $installCommand->assertCommandIsSuccessful(); + + PieJsonEditor::fromTargetPlatform( + PiePlatform\TargetPlatform::fromPhpBinaryPath( + PiePlatform\TargetPhp\PhpBinaryPath::fromPhpConfigExecutable( + $phpConfig, + ), + 1, + ), + ) + ->addRequire(self::TEST_PACKAGE, '^2.0'); + + $this->commandTester->execute(['--with-php-config' => $phpConfig]); + $this->commandTester->assertCommandIsSuccessful(); + + $outputString = $this->commandTester->getDisplay(); + + self::assertStringMatchesFormat( + '%Aexample_pie_extension:%S (from %S asgrim/example-pie-extension:2.0.2%S) — new version %S available%A', + $outputString, + ); + } }