diff --git a/docs/extension-maintainers.md b/docs/extension-maintainers.md index 89575e47..9b6036cf 100644 --- a/docs/extension-maintainers.md +++ b/docs/extension-maintainers.md @@ -329,6 +329,12 @@ in the traditional manner. } ``` +> [!NOTE] +> The `pie build` command does not use `pre-packaged-binary` by default, so +> that it always compiles from source. If `pre-packaged-binary` is the only +> `download-url-method` listed, `pie build` will fail for your extension, and +> end users will need to use `pie install` instead. + ##### `os-families` restrictions The `os-families` and `os-families-exclude` directive allow extention maintainers diff --git a/features/build-extensions.feature b/features/build-extensions.feature index 109435e2..02a3b630 100644 --- a/features/build-extensions.feature +++ b/features/build-extensions.feature @@ -16,6 +16,11 @@ Feature: Extensions can be built with PIE When I run a command to build an extension with configure options Then the extension should have been built with options + # pie build --suppress-download-url-method=pre-packaged-source + Example: A pre-packaged binary can be used for a build when explicitly requested + When I run a command to build an extension suppressing a different download method + Then the pre-packaged binary should have been used instead of building + # pie build Example: Multiple extensions can be built at once When I run a command to build multiple extensions diff --git a/src/Command/BuildCommand.php b/src/Command/BuildCommand.php index b3c360e0..7f8725da 100644 --- a/src/Command/BuildCommand.php +++ b/src/Command/BuildCommand.php @@ -17,6 +17,7 @@ use Php\Pie\DependencyResolver\InvalidPackageName; use Php\Pie\DependencyResolver\ResolvedPackageRequest; use Php\Pie\DependencyResolver\UnableToResolveRequirement; +use Php\Pie\Downloading\DownloadUrlMethod; use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages; use Php\Pie\Platform\PackageManager; use Php\Pie\SelfManage\BuildTools\CheckAllBuildTools; @@ -50,6 +51,7 @@ public function configure(): void parent::configure(); CommandHelper::configureDownloadBuildInstallOptions($this); + CommandHelper::suppressDownloadUrlMethodsByDefault($this, DownloadUrlMethod::PrePackagedBinary); } public function execute(InputInterface $input, OutputInterface $output): int diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 64dab692..92720bfa 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -350,6 +350,17 @@ public static function noDev(InputInterface $input): bool return $input->hasOption(self::OPTION_NO_DEV) && $input->getOption(self::OPTION_NO_DEV); } + public static function suppressDownloadUrlMethodsByDefault(Command $command, DownloadUrlMethod ...$downloadUrlMethods): void + { + $command + ->getDefinition() + ->getOption(self::OPTION_SUPPRESS_DOWNLOAD_URL_METHOD) + ->setDefault(array_values(array_map( + static fn (DownloadUrlMethod $downloadUrlMethod): string => $downloadUrlMethod->value, + $downloadUrlMethods, + ))); + } + /** @return list */ public static function determineSuppressedDownloadUrlMethods(InputInterface $input): array { diff --git a/src/ComposerIntegration/Listeners/AllDownloadUrlMethodsSuppressed.php b/src/ComposerIntegration/Listeners/AllDownloadUrlMethodsSuppressed.php index 825b7ea0..d0cea688 100644 --- a/src/ComposerIntegration/Listeners/AllDownloadUrlMethodsSuppressed.php +++ b/src/ComposerIntegration/Listeners/AllDownloadUrlMethodsSuppressed.php @@ -18,4 +18,12 @@ public static function forPackage(Package $piePackage): self $piePackage->name(), )); } + + public static function forBuildOfPackageOnlyProvidingPrePackagedBinaries(Package $piePackage): self + { + return new self(sprintf( + 'Cannot build %1$s from source, as it only provides pre-packaged binaries. Use "pie install %1$s" to install the pre-packaged binary instead.', + $piePackage->name(), + )); + } } diff --git a/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php b/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php index 641916a8..199acf4e 100644 --- a/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php +++ b/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php @@ -14,6 +14,7 @@ use Composer\Package\CompletePackageInterface; use Composer\Util\HttpDownloader; use Php\Pie\ComposerIntegration\PieComposerRequest; +use Php\Pie\ComposerIntegration\PieOperation; use Php\Pie\DependencyResolver\Package; use Php\Pie\Downloading\DownloadUrlMethod; use Php\Pie\Downloading\PackageReleaseAssets; @@ -90,6 +91,10 @@ function (OperationInterface $operation): void { } if ($remainingDownloadUrlMethods === []) { + if ($this->composerRequest->operation === PieOperation::Build && $downloadUrlMethods === [DownloadUrlMethod::PrePackagedBinary]) { + throw AllDownloadUrlMethodsSuppressed::forBuildOfPackageOnlyProvidingPrePackagedBinaries($piePackage); + } + throw AllDownloadUrlMethodsSuppressed::forPackage($piePackage); } diff --git a/test/behaviour/CliContext.php b/test/behaviour/CliContext.php index d5f87a36..360bd79b 100644 --- a/test/behaviour/CliContext.php +++ b/test/behaviour/CliContext.php @@ -201,13 +201,6 @@ public function theExtensionShouldHaveBeenBuilt(): void return; } - if (str_contains($this->output, 'Found prebuilt archive')) { - Assert::contains($this->output, 'Found prebuilt archive'); - Assert::contains($this->output, 'Pre-packaged binary found'); - - return; - } - Assert::contains($this->output, 'phpize complete.'); Assert::contains($this->output, 'Configure complete'); Assert::contains($this->output, 'Build complete:'); @@ -230,16 +223,35 @@ public function theExtensionShouldHaveBeenBuiltWithOptions(): void return; } - if (str_contains($this->output, 'Found prebuilt archive')) { - Assert::contains($this->output, 'Found prebuilt archive'); - Assert::contains($this->output, 'Pre-packaged binary found'); + Assert::contains($this->output, 'phpize complete.'); + Assert::contains($this->output, 'Configure complete with options: --with-hello-name=sup'); + Assert::contains($this->output, 'Build complete:'); + } + + #[When('I run a command to build an extension suppressing a different download method')] + public function iRunACommandToBuildAnExtensionSuppressingOnlyADifferentDownloadMethod(): void + { + $this->runPieCommand(['build', 'asgrim/example-pie-extension', '--suppress-download-url-method=pre-packaged-source', '-vv']); + } + + #[Then('the pre-packaged binary should have been used instead of building')] + public function thePrePackagedBinaryShouldHaveBeenUsedInsteadOfBuilding(): void + { + if (str_contains($this->output . $this->errorOutput, 'Failed locating asset [pre-packaged-binary]')) { + throw new PendingException('The extension has no pre-packaged binary for this PHP version - skipping'); + } + + $this->assertCommandSuccessful(); + + if (Platform::isWindows()) { + Assert::contains($this->output, 'Nothing to do on Windows'); return; } - Assert::contains($this->output, 'phpize complete.'); - Assert::contains($this->output, 'Configure complete with options: --with-hello-name=sup'); - Assert::contains($this->output, 'Build complete:'); + Assert::contains($this->output, 'Found prebuilt archive'); + Assert::contains($this->output, 'Pre-packaged binary found'); + Assert::notContains($this->output, 'phpize complete.'); } #[When('I run a command to install an extension')] diff --git a/test/unit/Command/CommandHelperTest.php b/test/unit/Command/CommandHelperTest.php index a8b2cf38..367c8309 100644 --- a/test/unit/Command/CommandHelperTest.php +++ b/test/unit/Command/CommandHelperTest.php @@ -415,6 +415,34 @@ public function testDetermineSuppressedDownloadUrlMethodsParsesGivenValues(): vo ); } + public function testDownloadUrlMethodsSuppressedByDefaultAreUsedWhenOptionIsNotGiven(): void + { + $command = new Command(); + $input = new ArrayInput([]); + CommandHelper::configureDownloadBuildInstallOptions($command); + CommandHelper::suppressDownloadUrlMethodsByDefault($command, DownloadUrlMethod::PrePackagedBinary); + CommandHelper::validateInput($input, $command); + + self::assertSame( + [DownloadUrlMethod::PrePackagedBinary], + CommandHelper::determineSuppressedDownloadUrlMethods($input), + ); + } + + public function testDownloadUrlMethodsSuppressedByDefaultAreReplacedWhenOptionIsGiven(): void + { + $command = new Command(); + $input = new ArrayInput(['--suppress-download-url-method' => ['pre-packaged-source']]); + CommandHelper::configureDownloadBuildInstallOptions($command); + CommandHelper::suppressDownloadUrlMethodsByDefault($command, DownloadUrlMethod::PrePackagedBinary); + CommandHelper::validateInput($input, $command); + + self::assertSame( + [DownloadUrlMethod::PrePackagedSourceDownload], + CommandHelper::determineSuppressedDownloadUrlMethods($input), + ); + } + public function testDetermineSuppressedDownloadUrlMethodsThrowsForInvalidValue(): void { $command = new Command(); diff --git a/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php b/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php index 2e706841..a04eeb83 100644 --- a/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php +++ b/test/unit/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListenerTest.php @@ -813,4 +813,56 @@ public function testSuppressingAllDownloadUrlMethodsWillThrowException(): void $this->expectException(AllDownloadUrlMethodsSuppressed::class); $listener($installerEvent); } + + public function testBuildingPackageThatOnlyProvidesPrePackagedBinariesExplainsItCannotBeBuilt(): void + { + $composerPackage = new CompletePackage('foo/bar', '1.2.3.0', '1.2.3'); + $composerPackage->setDistType('zip'); + $composerPackage->setDistUrl('https://example.com/git-archive-zip-url'); + $composerPackage->setPhpExt([ + 'extension-name' => 'foobar', + 'download-url-method' => ['pre-packaged-binary'], + ]); + + $installerEvent = new InstallerEvent( + InstallerEvents::PRE_OPERATIONS_EXEC, + $this->composer, + $this->io, + false, + true, + new Transaction([], [$composerPackage]), + ); + + $this->container + ->expects(self::never()) + ->method('get'); + + $listener = new OverrideDownloadUrlInstallListener( + $this->composer, + $this->io, + $this->container, + new PieComposerRequest( + $this->createMock(IOInterface::class), + new TargetPlatform( + OperatingSystem::NonWindows, + OperatingSystemFamily::Linux, + PhpBinaryPath::fromCurrentProcess(), + Architecture::x86_64, + ThreadSafetyMode::NonThreadSafe, + 1, + WindowsCompiler::VC15, + null, + ), + [new RequestedPackageAndVersion('foo/bar', '^1.1')], + PieOperation::Build, + [], + false, + suppressedDownloadUrlMethods: [DownloadUrlMethod::PrePackagedBinary], + ), + ); + + $this->expectException(AllDownloadUrlMethodsSuppressed::class); + $this->expectExceptionMessage('Cannot build foo/bar from source, as it only provides pre-packaged binaries. Use "pie install foo/bar" to install the pre-packaged binary instead.'); + $listener($installerEvent); + } }