From d31a19cc2886a4d4667390dc13c9bf839f5494e3 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Mon, 23 Jun 2025 10:29:18 +0100 Subject: [PATCH 1/5] Check already installed ext version against requirement --- .../InstallExtensionsForProjectCommand.php | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/Command/InstallExtensionsForProjectCommand.php b/src/Command/InstallExtensionsForProjectCommand.php index c5746a35..b3de806a 100644 --- a/src/Command/InstallExtensionsForProjectCommand.php +++ b/src/Command/InstallExtensionsForProjectCommand.php @@ -5,6 +5,7 @@ namespace Php\Pie\Command; use Composer\Package\Link; +use Composer\Package\Version\VersionParser; use OutOfRangeException; use Php\Pie\ComposerIntegration\PieComposerFactory; use Php\Pie\ComposerIntegration\PieComposerRequest; @@ -16,6 +17,7 @@ use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages; use Php\Pie\Installing\InstallForPhpProject\InstallPiePackageFromPath; use Php\Pie\Installing\InstallForPhpProject\InstallSelectedPackage; +use Php\Pie\Platform\InstalledPiePackages; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -53,6 +55,7 @@ final class InstallExtensionsForProjectCommand extends Command public function __construct( private readonly ComposerFactoryForProject $composerFactoryForProject, private readonly DetermineExtensionsRequired $determineExtensionsRequired, + private readonly InstalledPiePackages $installedPiePackages, private readonly FindMatchingPackages $findMatchingPackages, private readonly InstallSelectedPackage $installSelectedPackage, private readonly InstallPiePackageFromPath $installPiePackageFromPath, @@ -138,28 +141,58 @@ public function execute(InputInterface $input, OutputInterface $output): int ); $phpEnabledExtensions = array_keys($targetPlatform->phpBinaryPath->extensions()); + $installedPiePackages = $this->installedPiePackages->allPiePackages($pieComposer); $anyErrorsHappened = false; array_walk( $extensionsRequired, - function (Link $link) use ($pieComposer, $phpEnabledExtensions, $input, $output, $helper, &$anyErrorsHappened): void { + function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePackages, $input, $output, $helper, &$anyErrorsHappened): void { $extension = ExtensionName::normaliseFromString($link->getTarget()); + $piePackageVersion = null; + if (in_array($extension->name(), array_keys($installedPiePackages))) { + $piePackageVersion = $installedPiePackages[$extension->name()]->version(); + } + + $piePackageVersionMatchesLinkConstraint = null; + if ($piePackageVersion !== null) { + $piePackageVersionMatchesLinkConstraint = $link + ->getConstraint() + ->matches( + (new VersionParser())->parseConstraints($piePackageVersion), + ); + } + if (in_array($extension->name(), $phpEnabledExtensions)) { + if ($piePackageVersion !== null && $piePackageVersionMatchesLinkConstraint === false) { + $output->writeln(sprintf( + '%s: %s:%s ⚠️ Version %s is installed, but does not meet the version requirement %s', + $link->getDescription(), + $link->getTarget(), + $link->getPrettyConstraint(), + $piePackageVersion, + $link->getConstraint()->getPrettyString(), + )); + + return; + } + $output->writeln(sprintf( - '%s: %s ✅ Already installed', + '%s: %s:%s ✅ Already installed', $link->getDescription(), - $link, + $link->getTarget(), + $link->getPrettyConstraint(), )); return; } $output->writeln(sprintf( - '%s: %s ⚠️ Missing', + '%s: %s:%s 🚫 Missing', $link->getDescription(), - $link, + $link->getTarget(), + $link->getPrettyConstraint(), )); try { From 6430ecae7bb1b694454b8c6d5661a7cd7486054f Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Mon, 23 Jun 2025 10:42:33 +0100 Subject: [PATCH 2/5] Add class constants for emoji --- .../InstallExtensionsForProjectCommand.php | 10 +++++++--- src/Command/SelfUpdateCommand.php | 7 ++++++- src/Command/SelfVerifyCommand.php | 4 +++- src/Command/ShowCommand.php | 16 +++++++++++++--- src/Installing/SetupIniFile.php | 6 ++++-- .../Verify/FallbackVerificationUsingOpenSsl.php | 6 +++++- .../Verify/GithubCliAttestationVerification.php | 4 +++- src/Util/Emoji.php | 13 +++++++++++++ .../InstallExtensionsForProjectCommandTest.php | 2 +- 9 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 src/Util/Emoji.php diff --git a/src/Command/InstallExtensionsForProjectCommand.php b/src/Command/InstallExtensionsForProjectCommand.php index b3de806a..c55e2eb2 100644 --- a/src/Command/InstallExtensionsForProjectCommand.php +++ b/src/Command/InstallExtensionsForProjectCommand.php @@ -18,6 +18,7 @@ use Php\Pie\Installing\InstallForPhpProject\InstallPiePackageFromPath; use Php\Pie\Installing\InstallForPhpProject\InstallSelectedPackage; use Php\Pie\Platform\InstalledPiePackages; +use Php\Pie\Util\Emoji; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -167,10 +168,11 @@ function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePac if (in_array($extension->name(), $phpEnabledExtensions)) { if ($piePackageVersion !== null && $piePackageVersionMatchesLinkConstraint === false) { $output->writeln(sprintf( - '%s: %s:%s ⚠️ Version %s is installed, but does not meet the version requirement %s', + '%s: %s:%s %s Version %s is installed, but does not meet the version requirement %s', $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint(), + Emoji::WARNING, $piePackageVersion, $link->getConstraint()->getPrettyString(), )); @@ -179,20 +181,22 @@ function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePac } $output->writeln(sprintf( - '%s: %s:%s ✅ Already installed', + '%s: %s:%s %s Already installed', $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint(), + Emoji::GREEN_CHECKMARK, )); return; } $output->writeln(sprintf( - '%s: %s:%s 🚫 Missing', + '%s: %s:%s %s Missing', $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint(), + Emoji::PROHIBITED, )); try { diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php index 60676cb5..00cd1b96 100644 --- a/src/Command/SelfUpdateCommand.php +++ b/src/Command/SelfUpdateCommand.php @@ -16,6 +16,7 @@ use Php\Pie\SelfManage\Update\ReleaseMetadata; use Php\Pie\SelfManage\Verify\FailedToVerifyRelease; use Php\Pie\SelfManage\Verify\VerifyPieReleaseUsingAttestation; +use Php\Pie\Util\Emoji; use Php\Pie\Util\PieVersion; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Attribute\AsCommand; @@ -146,7 +147,11 @@ public function execute(InputInterface $input, OutputInterface $output): int SudoFilePut::contents($fullPathToSelf, file_get_contents($pharFilename->filePath)); unlink($pharFilename->filePath); - $output->writeln('✅ PIE has been upgraded to ' . $latestRelease->tag . ''); + $output->writeln(sprintf( + '%s PIE has been upgraded to %s', + Emoji::GREEN_CHECKMARK, + $latestRelease->tag, + )); $this->exitSuccessfully(); } diff --git a/src/Command/SelfVerifyCommand.php b/src/Command/SelfVerifyCommand.php index 2978eddb..4d71aaa6 100644 --- a/src/Command/SelfVerifyCommand.php +++ b/src/Command/SelfVerifyCommand.php @@ -14,6 +14,7 @@ use Php\Pie\SelfManage\Update\ReleaseMetadata; use Php\Pie\SelfManage\Verify\FailedToVerifyRelease; use Php\Pie\SelfManage\Verify\VerifyPieReleaseUsingAttestation; +use Php\Pie\Util\Emoji; use Php\Pie\Util\PieVersion; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Attribute\AsCommand; @@ -80,7 +81,8 @@ public function execute(InputInterface $input, OutputInterface $output): int } $output->writeln(sprintf( - '✅ You are running an authentic PIE version %s.', + '%s You are running an authentic PIE version %s.', + Emoji::GREEN_CHECKMARK, $latestRelease->tag, )); diff --git a/src/Command/ShowCommand.php b/src/Command/ShowCommand.php index 2bb5abdb..aea0af66 100644 --- a/src/Command/ShowCommand.php +++ b/src/Command/ShowCommand.php @@ -12,6 +12,7 @@ use Php\Pie\Platform as PiePlatform; use Php\Pie\Platform\InstalledPiePackages; use Php\Pie\Platform\OperatingSystem; +use Php\Pie\Util\Emoji; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -133,7 +134,11 @@ static function (string $version, string $phpExtensionName) use ($showAll, $outp $unmatchedPiePackages = array_diff(array_keys($piePackages), $piePackagesMatched); if (count($unmatchedPiePackages)) { - $output->writeln("\n" . ' ⚠️ PIE packages not loaded:'); + $output->writeln(sprintf( + '%s %s PIE packages not loaded:', + "\n", + Emoji::WARNING, + )); $output->writeln('These extensions were installed with PIE but are not currently enabled.' . "\n"); foreach ($unmatchedPiePackages as $unmatchedPiePackage) { @@ -179,9 +184,14 @@ private static function verifyChecksumInformation( try { $expectedBinaryFileFromMetadata->verifyAgainstOther($actualBinaryFile); } catch (BinaryFileFailedVerification) { - return ' ⚠️ was ' . substr($actualBinaryFile->checksum, 0, 8) . '..., expected ' . substr($expectedBinaryFileFromMetadata->checksum, 0, 8) . '...'; + return sprintf( + ' %s was %s..., expected %s...', + Emoji::WARNING, + substr($actualBinaryFile->checksum, 0, 8), + substr($expectedBinaryFileFromMetadata->checksum, 0, 8), + ); } - return ' ✅'; + return ' ' . Emoji::GREEN_CHECKMARK; } } diff --git a/src/Installing/SetupIniFile.php b/src/Installing/SetupIniFile.php index e169246c..ed053c2a 100644 --- a/src/Installing/SetupIniFile.php +++ b/src/Installing/SetupIniFile.php @@ -9,6 +9,7 @@ use Php\Pie\File\BinaryFile; use Php\Pie\Installing\Ini\SetupIniApproach; use Php\Pie\Platform\TargetPlatform; +use Php\Pie\Util\Emoji; use Symfony\Component\Console\Output\OutputInterface; use function sprintf; @@ -34,7 +35,8 @@ public function __invoke( && $this->setupIniApproach->setup($targetPlatform, $downloadedPackage, $binaryFile, $output) ) { $output->writeln(sprintf( - '✅ Extension is enabled and loaded in %s', + '%s Extension is enabled and loaded in %s', + Emoji::GREEN_CHECKMARK, $targetPlatform->phpBinaryPath->phpBinaryPath, )); } else { @@ -42,7 +44,7 @@ public function __invoke( $output->writeln('Automatic extension enabling was skipped.', OutputInterface::VERBOSITY_VERBOSE); } - $output->writeln('⚠️ Extension has NOT been automatically enabled.'); + $output->writeln(sprintf('%s Extension has NOT been automatically enabled.', Emoji::WARNING)); $output->writeln(sprintf( 'You must now add "%s=%s" to your php.ini', $downloadedPackage->package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension', diff --git a/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php b/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php index aca1f7cd..68db5e87 100644 --- a/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php +++ b/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php @@ -10,6 +10,7 @@ use OpenSSLAsymmetricKey; use Php\Pie\File\BinaryFile; use Php\Pie\SelfManage\Update\ReleaseMetadata; +use Php\Pie\Util\Emoji; use Symfony\Component\Console\Output\OutputInterface; use Webmozart\Assert\Assert; @@ -89,7 +90,10 @@ public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilenam $output->writeln('#' . $attestationIndex . ': DSSE payload signature verified with certificate.', OutputInterface::VERBOSITY_VERBOSE); } - $output->writeln('✅ Verified the new PIE version (using fallback verification)'); + $output->writeln(sprintf( + '%s Verified the new PIE version (using fallback verification)', + Emoji::GREEN_CHECKMARK, + )); } private function assertCertificateSignedByTrustedRoot(Attestation $attestation): void diff --git a/src/SelfManage/Verify/GithubCliAttestationVerification.php b/src/SelfManage/Verify/GithubCliAttestationVerification.php index 901246d5..af98ea1c 100644 --- a/src/SelfManage/Verify/GithubCliAttestationVerification.php +++ b/src/SelfManage/Verify/GithubCliAttestationVerification.php @@ -6,12 +6,14 @@ use Php\Pie\File\BinaryFile; use Php\Pie\SelfManage\Update\ReleaseMetadata; +use Php\Pie\Util\Emoji; use Php\Pie\Util\Process; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\ExecutableFinder; use function implode; +use function sprintf; /** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ final class GithubCliAttestationVerification implements VerifyPiePhar @@ -47,6 +49,6 @@ public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilenam throw FailedToVerifyRelease::fromGhCliFailure($releaseMetadata, $processFailedException); } - $output->writeln('✅ Verified the new PIE version'); + $output->writeln(sprintf('%s Verified the new PIE version', Emoji::GREEN_CHECKMARK)); } } diff --git a/src/Util/Emoji.php b/src/Util/Emoji.php new file mode 100644 index 00000000..a368414f --- /dev/null +++ b/src/Util/Emoji.php @@ -0,0 +1,13 @@ +commandTester->assertCommandIsSuccessful(); self::assertStringContainsString('Checking extensions for your project my/project', $outputString); self::assertStringContainsString('requires: my/project requires ext-standard (== *) ✅ Already installed', $outputString); - self::assertStringContainsString('requires: my/project requires ext-foobar (== *) ⚠️ Missing', $outputString); + self::assertStringContainsString('requires: my/project requires ext-foobar (== *) 🚫 Missing', $outputString); } public function testInstallingExtensionsForPieProject(): void From ee0477829088a18b723daf42581fa3473c171dfc Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 25 Jun 2025 19:27:15 +0100 Subject: [PATCH 3/5] Adjust output in project installer --- ...InstallExtensionsForProjectCommandTest.php | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/test/integration/Command/InstallExtensionsForProjectCommandTest.php b/test/integration/Command/InstallExtensionsForProjectCommandTest.php index ecad8991..90227f61 100644 --- a/test/integration/Command/InstallExtensionsForProjectCommandTest.php +++ b/test/integration/Command/InstallExtensionsForProjectCommandTest.php @@ -20,6 +20,7 @@ use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages; use Php\Pie\Installing\InstallForPhpProject\InstallPiePackageFromPath; use Php\Pie\Installing\InstallForPhpProject\InstallSelectedPackage; +use Php\Pie\Platform\InstalledPiePackages; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -41,6 +42,7 @@ final class InstallExtensionsForProjectCommandTest extends TestCase private CommandTester $commandTester; private ComposerFactoryForProject&MockObject $composerFactoryForProject; private FindMatchingPackages&MockObject $findMatchingPackages; + private InstalledPiePackages&MockObject $installedPiePackages; private InstallSelectedPackage&MockObject $installSelectedPackage; private QuestionHelper&MockObject $questionHelper; private InstallPiePackageFromPath&MockObject $installPiePackage; @@ -69,6 +71,7 @@ function (string $service): mixed { $this->composerFactoryForProject = $this->createMock(ComposerFactoryForProject::class); $this->findMatchingPackages = $this->createMock(FindMatchingPackages::class); + $this->installedPiePackages = $this->createMock(InstalledPiePackages::class); $this->installSelectedPackage = $this->createMock(InstallSelectedPackage::class); $this->installPiePackage = $this->createMock(InstallPiePackageFromPath::class); $this->questionHelper = $this->createMock(QuestionHelper::class); @@ -76,6 +79,7 @@ function (string $service): mixed { $cmd = new InstallExtensionsForProjectCommand( $this->composerFactoryForProject, new DetermineExtensionsRequired(), + $this->installedPiePackages, $this->findMatchingPackages, $this->installSelectedPackage, $this->installPiePackage, @@ -91,8 +95,12 @@ public function testInstallingExtensionsForPhpProject(): void { $rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3'); $rootPackage->setRequires([ - 'ext-standard' => new Link('my/project', 'ext-standard', new Constraint('=', '*'), Link::TYPE_REQUIRE), - 'ext-foobar' => new Link('my/project', 'ext-foobar', new Constraint('=', '*'), Link::TYPE_REQUIRE), + 'ext-standard' => new Link('my/project', 'ext-standard', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*'), + 'ext-foobar' => new Link('my/project', 'ext-foobar', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*'), +// 'ext-mismatching' => new Link('my/project', 'ext-mismatching', new MultiConstraint([ +// new Constraint('>=', '2.0.0.0-dev'), +// new Constraint('<', '3.0.0.0-dev'), +// ]), Link::TYPE_REQUIRE, '^2.0'), ]); $this->composerFactoryForProject->method('rootPackage')->willReturn($rootPackage); @@ -107,6 +115,17 @@ public function testInstallingExtensionsForPhpProject(): void $this->composerFactoryForProject->method('composer')->willReturn($composer); +// $this->installedPiePackages->method('allPiePackages')->willReturn([ +// 'mismatching' => new Package( +// $this->createMock(CompletePackageInterface::class), +// ExtensionType::PhpModule, +// ExtensionName::normaliseFromString('mismatching'), +// 'vendor/mismatching', +// '1.9.3', +// null, +// ), +// ]); + $this->findMatchingPackages->method('for')->willReturn([ ['name' => 'vendor1/foobar', 'description' => 'The official foobar implementation'], ['name' => 'vendor2/afoobar', 'description' => 'An improved async foobar extension'], @@ -127,8 +146,8 @@ public function testInstallingExtensionsForPhpProject(): void $this->commandTester->assertCommandIsSuccessful(); self::assertStringContainsString('Checking extensions for your project my/project', $outputString); - self::assertStringContainsString('requires: my/project requires ext-standard (== *) ✅ Already installed', $outputString); - self::assertStringContainsString('requires: my/project requires ext-foobar (== *) 🚫 Missing', $outputString); + self::assertStringContainsString('requires: ext-standard:* ✅ Already installed', $outputString); + self::assertStringContainsString('requires: ext-foobar:* 🚫 Missing', $outputString); } public function testInstallingExtensionsForPieProject(): void From 6bdb4c92f5a0530539b242c7dbf884e057d99f42 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 25 Jun 2025 20:39:18 +0100 Subject: [PATCH 4/5] Use PHP project's require version to specify which ext version to install --- .../InstallExtensionsForProjectCommand.php | 16 +++++++++++----- src/Command/SelfUpdateCommand.php | 3 ++- src/Command/SelfVerifyCommand.php | 3 ++- src/Container.php | 6 ++++++ src/File/FullPathToSelf.php | 8 ++++++-- .../InstallSelectedPackage.php | 6 +++++- .../InstallSelectedPackageTest.php | 4 +++- 7 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/Command/InstallExtensionsForProjectCommand.php b/src/Command/InstallExtensionsForProjectCommand.php index c55e2eb2..ebfe2b9d 100644 --- a/src/Command/InstallExtensionsForProjectCommand.php +++ b/src/Command/InstallExtensionsForProjectCommand.php @@ -149,7 +149,8 @@ public function execute(InputInterface $input, OutputInterface $output): int array_walk( $extensionsRequired, function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePackages, $input, $output, $helper, &$anyErrorsHappened): void { - $extension = ExtensionName::normaliseFromString($link->getTarget()); + $extension = ExtensionName::normaliseFromString($link->getTarget()); + $linkRequiresConstraint = $link->getPrettyConstraint(); $piePackageVersion = null; if (in_array($extension->name(), array_keys($installedPiePackages))) { @@ -171,7 +172,7 @@ function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePac '%s: %s:%s %s Version %s is installed, but does not meet the version requirement %s', $link->getDescription(), $link->getTarget(), - $link->getPrettyConstraint(), + $linkRequiresConstraint, Emoji::WARNING, $piePackageVersion, $link->getConstraint()->getPrettyString(), @@ -184,7 +185,7 @@ function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePac '%s: %s:%s %s Already installed', $link->getDescription(), $link->getTarget(), - $link->getPrettyConstraint(), + $linkRequiresConstraint, Emoji::GREEN_CHECKMARK, )); @@ -195,7 +196,7 @@ function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePac '%s: %s:%s %s Missing', $link->getDescription(), $link->getTarget(), - $link->getPrettyConstraint(), + $linkRequiresConstraint, Emoji::PROHIBITED, )); @@ -242,9 +243,14 @@ static function (array $match): string { return; } + $requestInstallConstraint = ''; + if ($linkRequiresConstraint !== '*') { + $requestInstallConstraint = ':' . $linkRequiresConstraint; + } + try { $this->installSelectedPackage->withPieCli( - substr($selectedPackageAnswer, 0, (int) strpos($selectedPackageAnswer, ':')), + substr($selectedPackageAnswer, 0, (int) strpos($selectedPackageAnswer, ':')) . $requestInstallConstraint, $input, $output, ); diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php index 00cd1b96..ea9a1529 100644 --- a/src/Command/SelfUpdateCommand.php +++ b/src/Command/SelfUpdateCommand.php @@ -43,6 +43,7 @@ public function __construct( private readonly string $githubApiBaseUrl, private readonly QuieterConsoleIO $io, private readonly ContainerInterface $container, + private readonly FullPathToSelf $fullPathToSelf, ) { parent::__construct(); } @@ -139,7 +140,7 @@ public function execute(InputInterface $input, OutputInterface $output): int return Command::FAILURE; } - $fullPathToSelf = (new FullPathToSelf())(); + $fullPathToSelf = ($this->fullPathToSelf)(); $output->writeln( sprintf('Writing new version to %s', $fullPathToSelf), OutputInterface::VERBOSITY_VERBOSE, diff --git a/src/Command/SelfVerifyCommand.php b/src/Command/SelfVerifyCommand.php index 4d71aaa6..b5fd3dad 100644 --- a/src/Command/SelfVerifyCommand.php +++ b/src/Command/SelfVerifyCommand.php @@ -35,6 +35,7 @@ public function __construct( private readonly string $githubApiBaseUrl, private readonly QuieterConsoleIO $io, private readonly ContainerInterface $container, + private readonly FullPathToSelf $fullPathToSelf, ) { parent::__construct(); } @@ -65,7 +66,7 @@ public function execute(InputInterface $input, OutputInterface $output): int $httpDownloader = new HttpDownloader($this->io, $composer->getConfig()); $authHelper = new AuthHelper($this->io, $composer->getConfig()); $latestRelease = new ReleaseMetadata(PieVersion::get(), 'blah'); - $pharFilename = BinaryFile::fromFileWithSha256Checksum((new FullPathToSelf())()); + $pharFilename = BinaryFile::fromFileWithSha256Checksum(($this->fullPathToSelf)()); $verifyPiePhar = VerifyPieReleaseUsingAttestation::factory($this->githubApiBaseUrl, $httpDownloader, $authHelper); try { diff --git a/src/Container.php b/src/Container.php index 02233a06..b756e232 100644 --- a/src/Container.php +++ b/src/Container.php @@ -27,6 +27,7 @@ use Php\Pie\DependencyResolver\ResolveDependencyWithComposer; use Php\Pie\Downloading\GithubPackageReleaseAssets; use Php\Pie\Downloading\PackageReleaseAssets; +use Php\Pie\File\FullPathToSelf; use Php\Pie\Installing\Ini; use Php\Pie\Installing\Install; use Php\Pie\Installing\Uninstall; @@ -43,6 +44,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventDispatcher; +use function getcwd; use function str_starts_with; /** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ @@ -115,6 +117,10 @@ static function (ConsoleCommandEvent $event) use (&$displayedBanner): void { ->needs('$githubApiBaseUrl') ->give('https://api.github.com'); + $container->when(FullPathToSelf::class) + ->needs('$originalCwd') + ->give(getcwd()); + $container->singleton( Build::class, static function (ContainerInterface $container): Build { diff --git a/src/File/FullPathToSelf.php b/src/File/FullPathToSelf.php index 8f559120..01a6e1e0 100644 --- a/src/File/FullPathToSelf.php +++ b/src/File/FullPathToSelf.php @@ -7,7 +7,6 @@ use RuntimeException; use function array_key_exists; -use function getcwd; use function is_string; use function preg_match; use function realpath; @@ -17,6 +16,11 @@ /** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ class FullPathToSelf { + /** @psalm-suppress PossiblyUnusedMethod no direct reference; used in service locator */ + public function __construct(private readonly string $originalCwd) + { + } + /** @return non-empty-string */ public function __invoke(): string { @@ -28,7 +32,7 @@ public function __invoke(): string return $this->isAbsolutePath($phpSelf) ? $phpSelf - : (getcwd() . DIRECTORY_SEPARATOR . $phpSelf); + : ($this->originalCwd . DIRECTORY_SEPARATOR . $phpSelf); } private function isAbsolutePath(string $path): bool diff --git a/src/Installing/InstallForPhpProject/InstallSelectedPackage.php b/src/Installing/InstallForPhpProject/InstallSelectedPackage.php index 3245f754..62022f69 100644 --- a/src/Installing/InstallForPhpProject/InstallSelectedPackage.php +++ b/src/Installing/InstallForPhpProject/InstallSelectedPackage.php @@ -21,10 +21,14 @@ /** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ class InstallSelectedPackage { + public function __construct(private readonly FullPathToSelf $fullPathToSelf) + { + } + public function withPieCli(string $selectedPackage, InputInterface $input, OutputInterface $output): void { $process = [ - (new FullPathToSelf())(), + ($this->fullPathToSelf)(), 'install', $selectedPackage, ]; diff --git a/test/unit/Installing/InstallForPhpProject/InstallSelectedPackageTest.php b/test/unit/Installing/InstallForPhpProject/InstallSelectedPackageTest.php index 87414fc6..f9c63e32 100644 --- a/test/unit/Installing/InstallForPhpProject/InstallSelectedPackageTest.php +++ b/test/unit/Installing/InstallForPhpProject/InstallSelectedPackageTest.php @@ -5,6 +5,7 @@ namespace Php\PieUnitTest\Installing\InstallForPhpProject; use Composer\Util\Platform; +use Php\Pie\File\FullPathToSelf; use Php\Pie\Installing\InstallForPhpProject\InstallSelectedPackage; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; @@ -13,6 +14,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\BufferedOutput; +use function getcwd; use function trim; #[CoversClass(InstallSelectedPackage::class)] @@ -33,7 +35,7 @@ public function testWithPieCli(): void ); $output = new BufferedOutput(); - (new InstallSelectedPackage())->withPieCli( + (new InstallSelectedPackage(new FullPathToSelf(getcwd())))->withPieCli( 'foo/bar', $input, $output, From aaf9373c1785852bdeda36e9067bc29a4a61987c Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 25 Jun 2025 21:34:22 +0100 Subject: [PATCH 5/5] Update InstallExtensionsForProjectCommandTest to use version for the missing package --- .../Command/InstallExtensionsForProjectCommandTest.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/integration/Command/InstallExtensionsForProjectCommandTest.php b/test/integration/Command/InstallExtensionsForProjectCommandTest.php index 90227f61..f4d6c7b7 100644 --- a/test/integration/Command/InstallExtensionsForProjectCommandTest.php +++ b/test/integration/Command/InstallExtensionsForProjectCommandTest.php @@ -10,6 +10,7 @@ use Composer\Repository\InstalledArrayRepository; use Composer\Repository\RepositoryManager; use Composer\Semver\Constraint\Constraint; +use Composer\Semver\Constraint\MultiConstraint; use Php\Pie\Command\InstallExtensionsForProjectCommand; use Php\Pie\ComposerIntegration\MinimalHelperSet; use Php\Pie\ComposerIntegration\PieJsonEditor; @@ -96,7 +97,10 @@ public function testInstallingExtensionsForPhpProject(): void $rootPackage = new RootPackage('my/project', '1.2.3.0', '1.2.3'); $rootPackage->setRequires([ 'ext-standard' => new Link('my/project', 'ext-standard', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*'), - 'ext-foobar' => new Link('my/project', 'ext-foobar', new Constraint('=', '*'), Link::TYPE_REQUIRE, '*'), + 'ext-foobar' => new Link('my/project', 'ext-foobar', new MultiConstraint([ + new Constraint('>=', '1.2.0.0-dev'), + new Constraint('<', '2.0.0.0-dev'), + ]), Link::TYPE_REQUIRE, '^1.2'), // 'ext-mismatching' => new Link('my/project', 'ext-mismatching', new MultiConstraint([ // new Constraint('>=', '2.0.0.0-dev'), // new Constraint('<', '3.0.0.0-dev'), @@ -135,7 +139,7 @@ public function testInstallingExtensionsForPhpProject(): void $this->installSelectedPackage->expects(self::once()) ->method('withPieCli') - ->with('vendor1/foobar'); + ->with('vendor1/foobar:^1.2'); $this->commandTester->execute( [], @@ -147,7 +151,7 @@ public function testInstallingExtensionsForPhpProject(): void $this->commandTester->assertCommandIsSuccessful(); self::assertStringContainsString('Checking extensions for your project my/project', $outputString); self::assertStringContainsString('requires: ext-standard:* ✅ Already installed', $outputString); - self::assertStringContainsString('requires: ext-foobar:* 🚫 Missing', $outputString); + self::assertStringContainsString('requires: ext-foobar:^1.2 🚫 Missing', $outputString); } public function testInstallingExtensionsForPieProject(): void