Skip to content

Commit b6f78bf

Browse files
authored
Merge pull request #536 from asgrim/dev-php-versions-issues
Fix `-dev` php versions issues
2 parents 28eec8c + 6cd8573 commit b6f78bf

5 files changed

Lines changed: 204 additions & 10 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/FetchDependencyStatusesTest.php

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,24 @@
1010
use Composer\Package\CompletePackage;
1111
use Composer\Package\Link;
1212
use Composer\Semver\Constraint\Constraint;
13+
use Composer\Semver\VersionParser;
1314
use Php\Pie\DependencyResolver\FetchDependencyStatuses;
15+
use Php\Pie\Platform\Architecture;
16+
use Php\Pie\Platform\OperatingSystem;
17+
use Php\Pie\Platform\OperatingSystemFamily;
1418
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
1519
use Php\Pie\Platform\TargetPlatform;
1620
use PHPUnit\Framework\Attributes\CoversClass;
21+
use PHPUnit\Framework\Attributes\DataProvider;
1722
use PHPUnit\Framework\TestCase;
1823

24+
use function assert;
25+
26+
use const PHP_MAJOR_VERSION;
27+
use const PHP_MINOR_VERSION;
28+
use const PHP_RELEASE_VERSION;
29+
use const PHP_VERSION;
30+
1931
#[CoversClass(FetchDependencyStatuses::class)]
2032
final class FetchDependencyStatusesTest extends TestCase
2133
{
@@ -26,15 +38,44 @@ public function testNoRequiresReturnsEmptyArray(): void
2638
self::assertEquals([], (new FetchDependencyStatuses())(TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess(), null, null), $this->createMock(Composer::class), $package));
2739
}
2840

29-
public function testRequiresReturnsListOfStatuses(): void
41+
/** @return array<non-empty-string, array{0: non-empty-string, 1: non-empty-string}> */
42+
public function phpVersionProvider(): array
43+
{
44+
return [
45+
'8.2.0' => ['8.2.0', '8.2.0'],
46+
'8.2.0-dev' => ['8.2.0', '8.2.0-dev'],
47+
'8.2.0-alpha' => ['8.2.0', '8.2.0-alpha'],
48+
'8.2.0-RC1' => ['8.2.0', '8.2.0-RC1'],
49+
PHP_VERSION => [PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION, PHP_VERSION],
50+
];
51+
}
52+
53+
#[DataProvider('phpVersionProvider')]
54+
public function testRequiresReturnsListOfStatuses(string $version, string $versionWithExtra): void
3055
{
31-
$php = PhpBinaryPath::fromCurrentProcess();
56+
$php = $this->createMock(PhpBinaryPath::class);
57+
$php->method('operatingSystem')->willReturn(OperatingSystem::NonWindows);
58+
$php->method('operatingSystemFamily')->willReturn(OperatingSystemFamily::Linux);
59+
$php->method('machineType')->willReturn(Architecture::x86_64);
60+
$php->expects(self::any())
61+
->method('version')
62+
->willReturn($version);
63+
$php->expects(self::any())
64+
->method('phpVersionWithExtra')
65+
->willReturn($versionWithExtra);
66+
$php->expects(self::any())
67+
->method('extensions')
68+
->willReturn(['Core' => $versionWithExtra, 'standard' => $versionWithExtra]);
69+
70+
$versionParser = new VersionParser();
71+
$parsedPhpVersion = $versionParser->parseConstraints($php->phpVersionWithExtra());
72+
assert($parsedPhpVersion instanceof Constraint);
3273

3374
$package = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3');
3475
$package->setRequires([
35-
'ext-core' => new Link('__root__', 'ext-core', new Constraint('=', $php->version() . '.0')),
36-
'ext-nonsense_extension' => new Link('__root__', 'ext-nonsense_extension', new Constraint('=', '*')),
37-
'ext-standard' => new Link('__root__', 'ext-standard', new Constraint('<', '1.0.0.0')),
76+
'ext-core' => new Link('__root__', 'ext-core', $versionParser->parseConstraints('= ' . $php->phpVersionWithExtra())),
77+
'ext-nonsense_extension' => new Link('__root__', 'ext-nonsense_extension', $versionParser->parseConstraints('*')),
78+
'ext-standard' => new Link('__root__', 'ext-standard', $versionParser->parseConstraints('< 1.0.0')),
3879
]);
3980

4081
$deps = (new FetchDependencyStatuses())(
@@ -45,8 +86,8 @@ public function testRequiresReturnsListOfStatuses(): void
4586

4687
self::assertCount(3, $deps);
4788

48-
self::assertSame('ext-core: == ' . $php->version() . '.0', $deps[0]->asPrettyString());
49-
self::assertSame('ext-nonsense_extension: == * 🚫 (not installed)', $deps[1]->asPrettyString());
50-
self::assertSame('ext-standard: < 1.0.0.0 🚫 (your version is ' . $php->version() . '.0)', $deps[2]->asPrettyString());
89+
self::assertSame('ext-core: = ' . $php->phpVersionWithExtra() . '', $deps[0]->asPrettyString());
90+
self::assertSame('ext-nonsense_extension: * 🚫 (not installed)', $deps[1]->asPrettyString());
91+
self::assertSame('ext-standard: < 1.0.0 🚫 (your version is ' . $parsedPhpVersion->getVersion() . ')', $deps[2]->asPrettyString());
5192
}
5293
}

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)