Skip to content

Commit 2e0ac3f

Browse files
committed
Check buildProvider when installing for bundled PHP extensions
1 parent 27b5d30 commit 2e0ac3f

File tree

8 files changed

+113
-0
lines changed

8 files changed

+113
-0
lines changed

src/Command/BuildCommand.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Php\Pie\ComposerIntegration\PieComposerFactory;
1010
use Php\Pie\ComposerIntegration\PieComposerRequest;
1111
use Php\Pie\ComposerIntegration\PieOperation;
12+
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
1213
use Php\Pie\DependencyResolver\DependencyResolver;
1314
use Php\Pie\DependencyResolver\InvalidPackageName;
1415
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
@@ -88,6 +89,11 @@ public function execute(InputInterface $input, OutputInterface $output): int
8889
$targetPlatform,
8990
$this->container,
9091
);
92+
} catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
93+
$output->writeln('');
94+
$output->writeln('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');
95+
96+
return self::INVALID;
9197
}
9298

9399
$output->writeln(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

src/Command/DownloadCommand.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Php\Pie\ComposerIntegration\PieComposerFactory;
1010
use Php\Pie\ComposerIntegration\PieComposerRequest;
1111
use Php\Pie\ComposerIntegration\PieOperation;
12+
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
1213
use Php\Pie\DependencyResolver\DependencyResolver;
1314
use Php\Pie\DependencyResolver\InvalidPackageName;
1415
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
@@ -90,6 +91,11 @@ public function execute(InputInterface $input, OutputInterface $output): int
9091
$targetPlatform,
9192
$this->container,
9293
);
94+
} catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
95+
$output->writeln('');
96+
$output->writeln('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');
97+
98+
return self::INVALID;
9399
}
94100

95101
$output->writeln(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

src/Command/InfoCommand.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Php\Pie\ComposerIntegration\PieComposerFactory;
88
use Php\Pie\ComposerIntegration\PieComposerRequest;
99
use Php\Pie\ComposerIntegration\PieOperation;
10+
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
1011
use Php\Pie\DependencyResolver\DependencyResolver;
1112
use Php\Pie\DependencyResolver\InvalidPackageName;
1213
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
@@ -87,6 +88,11 @@ public function execute(InputInterface $input, OutputInterface $output): int
8788
$targetPlatform,
8889
$this->container,
8990
);
91+
} catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
92+
$output->writeln('');
93+
$output->writeln('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');
94+
95+
return self::INVALID;
9096
}
9197

9298
$output->writeln(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

src/Command/InstallCommand.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Php\Pie\ComposerIntegration\PieComposerFactory;
1010
use Php\Pie\ComposerIntegration\PieComposerRequest;
1111
use Php\Pie\ComposerIntegration\PieOperation;
12+
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
1213
use Php\Pie\DependencyResolver\DependencyResolver;
1314
use Php\Pie\DependencyResolver\InvalidPackageName;
1415
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
@@ -103,6 +104,11 @@ public function execute(InputInterface $input, OutputInterface $output): int
103104
$targetPlatform,
104105
$this->container,
105106
);
107+
} catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
108+
$output->writeln('');
109+
$output->writeln('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');
110+
111+
return self::INVALID;
106112
}
107113

108114
$output->writeln(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Php\Pie\DependencyResolver;
6+
7+
use RuntimeException;
8+
9+
use function sprintf;
10+
11+
use const PHP_EOL;
12+
13+
class BundledPhpExtensionRefusal extends RuntimeException
14+
{
15+
public static function forPackage(Package $package): self
16+
{
17+
return new self(sprintf(
18+
'Bundled PHP extension %s should be installed by your distribution, not by PIE.%s%sCombining installation methods of bundled PHP extensions can lead to confusing and unintended consequences.%s%sIf you are really sure, you want to install %s using PIE, re-run the command with the --force flag.',
19+
$package->name(),
20+
PHP_EOL,
21+
PHP_EOL,
22+
PHP_EOL,
23+
PHP_EOL,
24+
$package->name(),
25+
));
26+
}
27+
}

src/DependencyResolver/Package.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ public function extensionName(): ExtensionName
163163
return $this->extensionName;
164164
}
165165

166+
public function isBundledPhpExtension(): bool
167+
{
168+
return str_starts_with($this->name(), 'php/');
169+
}
170+
166171
public function name(): string
167172
{
168173
return $this->name;

src/DependencyResolver/ResolveDependencyWithComposer.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@
1212
use Php\Pie\ExtensionType;
1313
use Php\Pie\Platform\TargetPlatform;
1414
use Php\Pie\Platform\ThreadSafetyMode;
15+
use Symfony\Component\Console\Output\OutputInterface;
1516

1617
use function in_array;
1718
use function preg_match;
19+
use function sprintf;
1820

1921
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
2022
final class ResolveDependencyWithComposer implements DependencyResolver
2123
{
2224
public function __construct(
25+
private readonly OutputInterface $output,
2326
private readonly QuieterConsoleIO $arrayCollectionIo,
2427
) {
2528
}
@@ -62,6 +65,7 @@ public function __invoke(
6265

6366
$piePackage = Package::fromComposerCompletePackage($package);
6467

68+
$this->assertBuildProviderProvidersBundledExtensions($targetPlatform, $piePackage, $forceInstallPackageVersion);
6569
$this->assertCompatibleOsFamily($targetPlatform, $piePackage);
6670
$this->assertCompatibleThreadSafetyMode($targetPlatform->threadSafety, $piePackage);
6771

@@ -95,4 +99,48 @@ private function assertCompatibleOsFamily(TargetPlatform $targetPlatform, Packag
9599
);
96100
}
97101
}
102+
103+
private function assertBuildProviderProvidersBundledExtensions(TargetPlatform $targetPlatform, Package $piePackage, bool $forceInstallPackageVersion): void
104+
{
105+
if (! $piePackage->isBundledPhpExtension()) {
106+
return;
107+
}
108+
109+
$buildProvider = $targetPlatform->phpBinaryPath->buildProvider();
110+
$identifiedBuildProvider = false;
111+
$note = '<options=bold,underscore;fg=red>Note:</> ';
112+
113+
if ($buildProvider === 'https://github.com/docker-library/php') {
114+
$identifiedBuildProvider = true;
115+
$this->output->writeln(sprintf(
116+
'<comment>%sYou should probably use "docker-php-ext-install %s" instead</comment>',
117+
$note,
118+
$piePackage->extensionName()->name(),
119+
));
120+
}
121+
122+
if ($buildProvider === 'Debian') {
123+
$identifiedBuildProvider = true;
124+
$this->output->writeln(sprintf(
125+
'<comment>%sYou should probably use "apt install php%s-%s" or "apt install php-%s" (or similar) instead</comment>',
126+
$note,
127+
$targetPlatform->phpBinaryPath->majorMinorVersion(),
128+
$piePackage->extensionName()->name(),
129+
$piePackage->extensionName()->name(),
130+
));
131+
}
132+
133+
if ($buildProvider === 'Remi\'s RPM repository <https://rpms.remirepo.net/> #StandWithUkraine') {
134+
$identifiedBuildProvider = true;
135+
$this->output->writeln(sprintf(
136+
'<comment>%sYou should probably use "dnf install php-%s" instead</comment>',
137+
$note,
138+
$piePackage->extensionName()->name(),
139+
));
140+
}
141+
142+
if ($identifiedBuildProvider && ! $forceInstallPackageVersion) {
143+
throw BundledPhpExtensionRefusal::forPackage($piePackage);
144+
}
145+
}
98146
}

test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use PHPUnit\Framework\Attributes\DataProvider;
3131
use PHPUnit\Framework\MockObject\MockObject;
3232
use PHPUnit\Framework\TestCase;
33+
use Symfony\Component\Console\Output\OutputInterface;
3334

3435
#[CoversClass(ResolveDependencyWithComposer::class)]
3536
final class ResolveDependencyWithComposerTest extends TestCase
@@ -80,6 +81,7 @@ public function testPackageThatCanBeResolved(): void
8081
);
8182

8283
$package = (new ResolveDependencyWithComposer(
84+
$this->createMock(OutputInterface::class),
8385
$this->createMock(QuieterConsoleIO::class),
8486
))($this->composer, $targetPlatform, new RequestedPackageAndVersion('asgrim/example-pie-extension', '^1.0'), false);
8587

@@ -123,6 +125,7 @@ public function testPackageThatCannotBeResolvedThrowsException(array $platformOv
123125
$this->expectException(UnableToResolveRequirement::class);
124126

125127
(new ResolveDependencyWithComposer(
128+
$this->createMock(OutputInterface::class),
126129
$this->createMock(QuieterConsoleIO::class),
127130
))(
128131
$this->composer,
@@ -161,6 +164,7 @@ public function testUnresolvedPackageCanBeInstalledWithForceOption(array $platfo
161164
$this->expectException(UnableToResolveRequirement::class);
162165

163166
$package = (new ResolveDependencyWithComposer(
167+
$this->createMock(OutputInterface::class),
164168
$this->createMock(QuieterConsoleIO::class),
165169
))(
166170
$this->composer,
@@ -212,6 +216,7 @@ public function testZtsOnlyPackageCannotBeInstalledOnNtsSystem(): void
212216
$this->expectException(IncompatibleThreadSafetyMode::class);
213217
$this->expectExceptionMessage('This extension does not support being installed on a non-Thread Safe PHP installation');
214218
(new ResolveDependencyWithComposer(
219+
$this->createMock(OutputInterface::class),
215220
$this->createMock(QuieterConsoleIO::class),
216221
))(
217222
$this->composer,
@@ -260,6 +265,7 @@ public function testNtsOnlyPackageCannotBeInstalledOnZtsSystem(): void
260265
$this->expectException(IncompatibleThreadSafetyMode::class);
261266
$this->expectExceptionMessage('This extension does not support being installed on a Thread Safe PHP installation');
262267
(new ResolveDependencyWithComposer(
268+
$this->createMock(OutputInterface::class),
263269
$this->createMock(QuieterConsoleIO::class),
264270
))(
265271
$this->composer,
@@ -308,6 +314,7 @@ public function testExtensionCanOnlyBeInstalledIfOsFamilyIsCompatible(): void
308314
$this->expectException(IncompatibleOperatingSystemFamily::class);
309315
$this->expectExceptionMessage('This extension does not support the "linux" operating system family. It is compatible with the following families: "solaris", "darwin"');
310316
(new ResolveDependencyWithComposer(
317+
$this->createMock(OutputInterface::class),
311318
$this->createMock(QuieterConsoleIO::class),
312319
))(
313320
$this->composer,
@@ -356,6 +363,7 @@ public function testExtensionCanOnlyBeInstalledIfOsFamilyIsNotInCompatible(): vo
356363
$this->expectException(IncompatibleOperatingSystemFamily::class);
357364
$this->expectExceptionMessage('This extension does not support the "darwin" operating system family. It is incompatible with the following families: "darwin", "solaris".');
358365
(new ResolveDependencyWithComposer(
366+
$this->createMock(OutputInterface::class),
359367
$this->createMock(QuieterConsoleIO::class),
360368
))(
361369
$this->composer,
@@ -392,6 +400,7 @@ public function testPackageThatCanBeResolvedWithReplaceConflict(): void
392400
);
393401

394402
$package = (new ResolveDependencyWithComposer(
403+
$this->createMock(OutputInterface::class),
395404
$this->createMock(QuieterConsoleIO::class),
396405
))($this->composer, $targetPlatform, new RequestedPackageAndVersion('asgrim/example-pie-extension', '^1.0'), false);
397406

0 commit comments

Comments
 (0)