diff --git a/src/Command/ShowCommand.php b/src/Command/ShowCommand.php
index 8203b7af..cb750f11 100644
--- a/src/Command/ShowCommand.php
+++ b/src/Command/ShowCommand.php
@@ -4,20 +4,45 @@
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\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\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;
+use function substr;
+use const DIRECTORY_SEPARATOR;
+
+/** @psalm-import-type PieMetadata from PieInstalledJsonMetadataKeys */
#[AsCommand(
name: 'show',
description: 'List the installed modules and their versions.',
)]
final class ShowCommand extends Command
{
+ public function __construct(
+ private readonly ContainerInterface $container,
+ ) {
+ parent::__construct();
+ }
+
public function configure(): void
{
parent::configure();
@@ -29,15 +54,109 @@ public function execute(InputInterface $input, OutputInterface $output): int
{
$targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);
- $extensions = $targetPlatform->phpBinaryPath->extensions();
+ $piePackages = $this->buildListOfPieInstalledPackages($output, $targetPlatform);
+ $phpEnabledExtensions = $targetPlatform->phpBinaryPath->extensions();
+ $extensionPath = $targetPlatform->phpBinaryPath->extensionPath();
+ $extensionEnding = $targetPlatform->operatingSystem === OperatingSystem::Windows ? '.dll' : '.so';
+
$output->writeln("\n" . 'Loaded extensions:');
array_walk(
- $extensions,
- static function (string $version, string $name) use ($output): void {
- $output->writeln(sprintf('%s:%s', $name, $version));
+ $phpEnabledExtensions,
+ static function (string $version, string $phpExtensionName) use ($output, $piePackages, $extensionPath, $extensionEnding): void {
+ if (! array_key_exists($phpExtensionName, $piePackages)) {
+ $output->writeln(sprintf(' %s:%s', $phpExtensionName, $version));
+
+ return;
+ }
+
+ $piePackage = $piePackages[$phpExtensionName];
+
+ $output->writeln(sprintf(
+ ' %s:%s (from 🥧 %s%s)',
+ $phpExtensionName,
+ $version,
+ $piePackage->prettyNameAndVersion(),
+ self::verifyChecksumInformation(
+ $extensionPath,
+ $phpExtensionName,
+ $extensionEnding,
+ PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($piePackage->composerPackage),
+ ),
+ ));
},
);
return Command::SUCCESS;
}
+
+ /**
+ * @param PieMetadata $installedJsonMetadata
+ * @psalm-param '.dll'|'.so' $extensionEnding
+ */
+ private static function verifyChecksumInformation(
+ string $extensionPath,
+ string $phpExtensionName,
+ string $extensionEnding,
+ array $installedJsonMetadata,
+ ): string {
+ $expectedConventionalBinaryPath = $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)) {
+ 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) {
+ return '';
+ }
+
+ $actualInstalledBinary = BinaryFile::fromFileWithSha256Checksum($expectedConventionalBinaryPath);
+ if ($actualInstalledBinary->checksum !== $pieExpectedChecksum) {
+ return ' ⚠️ was ' . substr($actualInstalledBinary->checksum, 0, 8) . '..., expected ' . substr($pieExpectedChecksum, 0, 8) . '...';
+ }
+
+ return ' ✅';
+ }
+
+ /** @return array */
+ 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,
+ );
+ }
}
diff --git a/src/ComposerIntegration/PieComposerRequest.php b/src/ComposerIntegration/PieComposerRequest.php
index 2679bdd9..405f78b9 100644
--- a/src/ComposerIntegration/PieComposerRequest.php
+++ b/src/ComposerIntegration/PieComposerRequest.php
@@ -27,4 +27,23 @@ public function __construct(
public readonly bool $attemptToSetupIniFile,
) {
}
+
+ /**
+ * Useful for when we don't want to perform any "write" style operations;
+ * for example just reading metadata about the installed system.
+ */
+ public static function noOperation(
+ OutputInterface $pieOutput,
+ TargetPlatform $targetPlatform,
+ ): self {
+ return new PieComposerRequest(
+ $pieOutput,
+ $targetPlatform,
+ new RequestedPackageAndVersion('null', null),
+ PieOperation::Resolve,
+ [],
+ null,
+ false,
+ );
+ }
}
diff --git a/src/ComposerIntegration/PieInstalledJsonMetadataKeys.php b/src/ComposerIntegration/PieInstalledJsonMetadataKeys.php
index 0d7e6067..048bc948 100644
--- a/src/ComposerIntegration/PieInstalledJsonMetadataKeys.php
+++ b/src/ComposerIntegration/PieInstalledJsonMetadataKeys.php
@@ -4,7 +4,29 @@
namespace Php\Pie\ComposerIntegration;
-/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
+use Composer\Package\CompletePackageInterface;
+
+use function array_column;
+use function array_key_exists;
+use function is_string;
+
+/**
+ * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
+ *
+ * @psalm-type PieMetadata = array{
+ * pie-target-platform-php-path?: non-empty-string,
+ * pie-target-platform-php-config-path?: non-empty-string,
+ * pie-target-platform-php-version?: non-empty-string,
+ * pie-target-platform-php-thread-safety?: non-empty-string,
+ * pie-target-platform-php-windows-compiler?: non-empty-string,
+ * pie-target-platform-architecture?: non-empty-string,
+ * pie-configure-options?: non-empty-string,
+ * pie-built-binary?: non-empty-string,
+ * pie-installed-binary-checksum?: non-empty-string,
+ * pie-installed-binary?: non-empty-string,
+ * pie-phpize-binary?: non-empty-string,
+ * }
+ */
enum PieInstalledJsonMetadataKeys: string
{
case TargetPlatformPhpPath = 'pie-target-platform-php-path';
@@ -18,4 +40,26 @@ enum PieInstalledJsonMetadataKeys: string
case BinaryChecksum = 'pie-installed-binary-checksum';
case InstalledBinary = 'pie-installed-binary';
case PhpizeBinary = 'pie-phpize-binary';
+
+ /** @return PieMetadata */
+ public static function pieMetadataFromComposerPackage(CompletePackageInterface $composerPackage): array
+ {
+ $composerPackageExtras = $composerPackage->getExtra();
+
+ $onlyPieExtras = [];
+
+ foreach (array_column(self::cases(), 'value') as $pieMetadataKey) {
+ if (
+ ! array_key_exists($pieMetadataKey, $composerPackageExtras)
+ || ! is_string($composerPackageExtras[$pieMetadataKey])
+ || $composerPackageExtras[$pieMetadataKey] === ''
+ ) {
+ continue;
+ }
+
+ $onlyPieExtras[$pieMetadataKey] = $composerPackageExtras[$pieMetadataKey];
+ }
+
+ return $onlyPieExtras;
+ }
}
diff --git a/src/Platform/TargetPhp/PhpBinaryPath.php b/src/Platform/TargetPhp/PhpBinaryPath.php
index f421b65a..f75392a6 100644
--- a/src/Platform/TargetPhp/PhpBinaryPath.php
+++ b/src/Platform/TargetPhp/PhpBinaryPath.php
@@ -225,11 +225,11 @@ public function operatingSystem(): OperatingSystem
public function operatingSystemFamily(): OperatingSystemFamily
{
- $output = Process::run([
+ $output = self::cleanWarningAndDeprecationsFromOutput(Process::run([
$this->phpBinaryPath,
'-r',
'echo PHP_OS_FAMILY;',
- ]);
+ ]));
$osFamily = OperatingSystemFamily::tryFrom(strtolower(trim($output)));
Assert::notNull($osFamily, 'Could not determine operating system family');
diff --git a/test/unit/ComposerIntegration/PieInstalledJsonMetadataKeysTest.php b/test/unit/ComposerIntegration/PieInstalledJsonMetadataKeysTest.php
new file mode 100644
index 00000000..e689d09e
--- /dev/null
+++ b/test/unit/ComposerIntegration/PieInstalledJsonMetadataKeysTest.php
@@ -0,0 +1,44 @@
+createMock(CompletePackageInterface::class);
+ $composerPackage->expects(self::once())
+ ->method('getExtra')
+ ->willReturn([]);
+
+ self::assertSame([], PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($composerPackage));
+ }
+
+ public function testPieMetadataFromComposerPackageWithPopulatedExtra(): void
+ {
+ $composerPackage = $this->createMock(CompletePackageInterface::class);
+ $composerPackage->expects(self::once())
+ ->method('getExtra')
+ ->willReturn([
+ PieInstalledJsonMetadataKeys::InstalledBinary->value => '/path/to/some/file',
+ PieInstalledJsonMetadataKeys::BinaryChecksum->value => 'some-checksum-value',
+ 'something else' => 'hopefully this does not make it in',
+ ]);
+
+ self::assertEqualsCanonicalizing(
+ [
+ PieInstalledJsonMetadataKeys::InstalledBinary->value => '/path/to/some/file',
+ PieInstalledJsonMetadataKeys::BinaryChecksum->value => 'some-checksum-value',
+ ],
+ PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($composerPackage),
+ );
+ }
+}