Skip to content

Commit bc893d0

Browse files
committed
535: prevent bundled PHP extns from installing on '-dev' versions of PHP
1 parent 28eec8c commit bc893d0

4 files changed

Lines changed: 155 additions & 2 deletions

File tree

src/DependencyResolver/BundledPhpExtensionRefusal.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Php\Pie\DependencyResolver;
66

7+
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
78
use RuntimeException;
89

910
use function sprintf;
@@ -24,4 +25,12 @@ public static function forPackage(Package $package): self
2425
$package->name(),
2526
));
2627
}
28+
29+
public static function forPhpExtraVersion(PhpBinaryPath $phpBinaryPath): self
30+
{
31+
return new self(sprintf(
32+
'Cannot install bundled PHP extension for non-stable versions of PHP (detected: %s)',
33+
$phpBinaryPath->phpVersionWithExtra(),
34+
));
35+
}
2736
}

src/DependencyResolver/ResolveDependencyWithComposer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use function in_array;
1818
use function preg_match;
1919
use function sprintf;
20+
use function str_ends_with;
2021

2122
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
2223
final class ResolveDependencyWithComposer implements DependencyResolver
@@ -109,6 +110,10 @@ private function assertBuildProviderProvidersBundledExtensions(TargetPlatform $t
109110
return;
110111
}
111112

113+
if (str_ends_with($targetPlatform->phpBinaryPath->phpVersionWithExtra(), '-dev')) {
114+
throw BundledPhpExtensionRefusal::forPhpExtraVersion($targetPlatform->phpBinaryPath);
115+
}
116+
112117
$buildProvider = $targetPlatform->phpBinaryPath->buildProvider();
113118
$identifiedBuildProvider = false;
114119
$note = '<options=bold,underscore;fg=red>Note:</> ';

src/Platform/TargetPhp/PhpBinaryPath.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,19 @@ public function version(): string
328328
return $phpVersion;
329329
}
330330

331+
/** @return non-empty-string */
332+
public function phpVersionWithExtra(): string
333+
{
334+
$phpVersionWithExtra = self::cleanWarningAndDeprecationsFromOutput(Process::run([
335+
$this->phpBinaryPath,
336+
'-r',
337+
'echo PHP_VERSION;',
338+
]));
339+
Assert::stringNotEmpty($phpVersionWithExtra, 'Could not determine PHP_VERSION');
340+
341+
return $phpVersionWithExtra;
342+
}
343+
331344
/** @return non-empty-string */
332345
public function majorMinorVersion(): string
333346
{

test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
use Composer\Repository\RepositoryFactory;
1616
use Composer\Repository\RepositoryManager;
1717
use Composer\Semver\Constraint\Constraint;
18+
use Php\Pie\ComposerIntegration\BundledPhpExtensionsRepository;
1819
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
20+
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
1921
use Php\Pie\DependencyResolver\IncompatibleOperatingSystemFamily;
2022
use Php\Pie\DependencyResolver\IncompatibleThreadSafetyMode;
2123
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
2224
use Php\Pie\DependencyResolver\ResolveDependencyWithComposer;
2325
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
26+
use Php\Pie\ExtensionType;
2427
use Php\Pie\Platform\Architecture;
2528
use Php\Pie\Platform\OperatingSystem;
2629
use Php\Pie\Platform\OperatingSystemFamily;
@@ -49,13 +52,21 @@ public function setUp(): void
4952
]);
5053
$this->localRepo = $this->createMock(InstalledRepositoryInterface::class);
5154
$this->localRepo->method('getPackages')->willReturn([
52-
new CompletePackage('already/installed1', '1.2.3.0', '1.2.3'),
55+
new CompletePackage('a/installed1', '1.2.3.0', '1.2.3'),
5356
$packageWithReplaces,
5457
]);
5558

59+
$bundledPhpPackage = new CompletePackage('php/bundled', '8.3.0', '8.3.0.0');
60+
$bundledPhpPackage->setType(ExtensionType::PhpModule->value);
61+
5662
$repoManager = $this->createMock(RepositoryManager::class);
5763
$repoManager->method('getRepositories')
58-
->willReturn([new CompositeRepository(RepositoryFactory::defaultReposWithDefaultManager(new NullIO()))]);
64+
->willReturn([
65+
new CompositeRepository([
66+
...RepositoryFactory::defaultReposWithDefaultManager(new NullIO()),
67+
new BundledPhpExtensionsRepository([$bundledPhpPackage]),
68+
]),
69+
]);
5970
$repoManager->method('getLocalRepository')->willReturn($this->localRepo);
6071

6172
$this->composer = $this->createMock(Composer::class);
@@ -415,4 +426,119 @@ public function testPackageThatCanBeResolvedWithReplaceConflict(): void
415426
self::assertSame('asgrim/example-pie-extension', $package->name());
416427
self::assertStringStartsWith('1.', $package->version());
417428
}
429+
430+
public function testBundledExtensionCannotBeInstalledOnDevPhpVersion(): void
431+
{
432+
$phpBinaryPath = $this->createMock(PhpBinaryPath::class);
433+
$phpBinaryPath->expects(self::any())
434+
->method('version')
435+
->willReturn('8.3.0');
436+
$phpBinaryPath->expects(self::any())
437+
->method('phpVersionWithExtra')
438+
->willReturn('8.3.0-dev');
439+
440+
$targetPlatform = new TargetPlatform(
441+
OperatingSystem::NonWindows,
442+
OperatingSystemFamily::Linux,
443+
$phpBinaryPath,
444+
Architecture::x86_64,
445+
ThreadSafetyMode::ThreadSafe,
446+
1,
447+
null,
448+
null,
449+
);
450+
451+
$resolver = new ResolveDependencyWithComposer(
452+
$this->createMock(IOInterface::class),
453+
$this->createMock(QuieterConsoleIO::class),
454+
);
455+
$requestedPackage = new RequestedPackageAndVersion('php/bundled', null);
456+
457+
$this->expectException(BundledPhpExtensionRefusal::class);
458+
$this->expectExceptionMessage('Cannot install bundled PHP extension for non-stable versions of PHP');
459+
$resolver->__invoke($this->composer, $targetPlatform, $requestedPackage, false);
460+
}
461+
462+
/** @return array<non-empty-string, array{0: non-empty-string}> */
463+
public function buildProvidersWithBundledExtensionWarnings(): array
464+
{
465+
return [
466+
'Docker' => ['https://github.com/docker-library/php'],
467+
'Debian/Ubuntu' => ['Debian'],
468+
'Remi Repo' => ['Remi\'s RPM repository <https://rpms.remirepo.net/> #StandWithUkraine'],
469+
'Brew' => ['Homebrew'],
470+
];
471+
}
472+
473+
#[DataProvider('buildProvidersWithBundledExtensionWarnings')]
474+
public function testBundledExtensionWillNotInstallOnBuildProviderWithoutForce(string $buildProvider): void
475+
{
476+
$phpBinaryPath = $this->createMock(PhpBinaryPath::class);
477+
$phpBinaryPath->expects(self::any())
478+
->method('version')
479+
->willReturn('8.3.0');
480+
$phpBinaryPath->expects(self::any())
481+
->method('phpVersionWithExtra')
482+
->willReturn('8.3.0');
483+
$phpBinaryPath->expects(self::any())
484+
->method('buildProvider')
485+
->willReturn($buildProvider);
486+
487+
$targetPlatform = new TargetPlatform(
488+
OperatingSystem::NonWindows,
489+
OperatingSystemFamily::Linux,
490+
$phpBinaryPath,
491+
Architecture::x86_64,
492+
ThreadSafetyMode::ThreadSafe,
493+
1,
494+
null,
495+
null,
496+
);
497+
498+
$resolver = new ResolveDependencyWithComposer(
499+
$this->createMock(IOInterface::class),
500+
$this->createMock(QuieterConsoleIO::class),
501+
);
502+
$requestedPackage = new RequestedPackageAndVersion('php/bundled', null);
503+
504+
$this->expectException(BundledPhpExtensionRefusal::class);
505+
$this->expectExceptionMessage('Bundled PHP extension php/bundled should be installed by your distribution, not by PIE');
506+
$resolver->__invoke($this->composer, $targetPlatform, $requestedPackage, false);
507+
}
508+
509+
#[DataProvider('buildProvidersWithBundledExtensionWarnings')]
510+
public function testBundledExtensionWillInstallOnBuildProviderWithForce(string $buildProvider): void
511+
{
512+
$phpBinaryPath = $this->createMock(PhpBinaryPath::class);
513+
$phpBinaryPath->expects(self::any())
514+
->method('version')
515+
->willReturn('8.3.0');
516+
$phpBinaryPath->expects(self::any())
517+
->method('phpVersionWithExtra')
518+
->willReturn('8.3.0');
519+
$phpBinaryPath->expects(self::any())
520+
->method('buildProvider')
521+
->willReturn($buildProvider);
522+
523+
$targetPlatform = new TargetPlatform(
524+
OperatingSystem::NonWindows,
525+
OperatingSystemFamily::Linux,
526+
$phpBinaryPath,
527+
Architecture::x86_64,
528+
ThreadSafetyMode::ThreadSafe,
529+
1,
530+
null,
531+
null,
532+
);
533+
534+
$resolver = new ResolveDependencyWithComposer(
535+
$this->createMock(IOInterface::class),
536+
$this->createMock(QuieterConsoleIO::class),
537+
);
538+
$requestedPackage = new RequestedPackageAndVersion('php/bundled', null);
539+
540+
$package = $resolver->__invoke($this->composer, $targetPlatform, $requestedPackage, true);
541+
542+
self::assertSame('php/bundled', $package->name());
543+
}
418544
}

0 commit comments

Comments
 (0)