Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions features/install-in-php-project.feature
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,15 @@ Feature: Extensions for a PHP project can be installed with PIE
Given I am in a PHP project that has missing extensions
When I run a command to install the extensions without package selections
Then I should see information on how to select packages for install

# pie install
Example: PIE running in a PHP project with out of date extensions installed by PIE
Given I am in a PHP project that has an outdated extension installed with PIE
When I run a command to install the extensions with package selections
Then I should see the outdated extension is updated

# pie install
Example: PIE running in a PHP project with out of date extensions installed externally
Given I am in a PHP project that has an outdated extension installed externally
When I run a command to install the extensions with package selections
Then I should see the outdated extension has not been updated
20 changes: 16 additions & 4 deletions src/Command/InstallExtensionsForProjectCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Php\Pie\Installing\InstallForPhpProject\CheckExtensionStatus;
use Php\Pie\Installing\InstallForPhpProject\ComposerFactoryForProject;
use Php\Pie\Installing\InstallForPhpProject\DetermineExtensionsRequired;
use Php\Pie\Installing\InstallForPhpProject\ExtensionInstalledStatus;
use Php\Pie\Installing\InstallForPhpProject\InstallPiePackageFromPath;
use Php\Pie\Installing\InstallForPhpProject\InstallSelectedPackage;
use Php\Pie\Installing\InstallForPhpProject\NoMatchingPackagesFound;
Expand All @@ -37,8 +38,8 @@
use Throwable;
use Webmozart\Assert\Assert;

use function array_change_key_case;
use function array_filter;
use function array_keys;
use function array_map;
use function array_values;
use function count;
Expand Down Expand Up @@ -138,7 +139,7 @@ private function handlePhpProject(InputInterface $input, RootPackageInterface $r
),
);

$phpEnabledExtensions = array_map('strtolower', array_keys($targetPlatform->phpBinaryPath->extensions()));
$phpEnabledExtensions = array_change_key_case($targetPlatform->phpBinaryPath->extensions());
$installedPiePackages = $this->installedPiePackages->allPiePackages($pieComposer);

$anyErrorsHappened = false;
Expand Down Expand Up @@ -205,7 +206,7 @@ function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePac
* Returns false if any error happened whilst trying to install the extension; returns true if was already
* installed, or it was successfully installed if needed.
*
* @param list<string> $phpEnabledExtensions
* @param array<string, string> $phpEnabledExtensions
* @param array<non-empty-string, non-empty-string> $extensionToPackageSelections
*/
private function handleSingleExtensionRequiredByPhpProject(
Expand All @@ -222,10 +223,21 @@ private function handleSingleExtensionRequiredByPhpProject(
->onlyVerifiedFor($targetPlatform);

// Check if the extension is already installed, if it is, return early.
if (($this->checkExtensionStatus)($link, $piePackagesForExtension, $phpEnabledExtensions)) {
$installedStatus = ($this->checkExtensionStatus)($link, $piePackagesForExtension, $phpEnabledExtensions);
if ($installedStatus === ExtensionInstalledStatus::Installed) {
return true;
}

// It's not a PIE-enabled extension
if ($installedStatus === ExtensionInstalledStatus::ConstraintNotMet && ! $piePackagesForExtension->count()) {
$this->io->writeError(sprintf(
'<warning>%s didn\'t match the required version constraints, but was not installed by PIE, so PIE will not update this extension automatically.</warning>',
$extension->nameWithExtPrefix(),
));

return false;
}

try {
$requestedPackageAndVersion = ($this->selectPackageForExtension)(
$extension,
Expand Down
23 changes: 5 additions & 18 deletions src/ComposerIntegration/PhpBinaryPathBasedPlatformRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
use Composer\Composer;
use Composer\Package\CompletePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;
use Composer\Semver\VersionParser;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionVersion;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\PkgConfig;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
Expand Down Expand Up @@ -134,27 +134,14 @@ public static function forTargetPlatform(TargetPlatform $targetPlatform, Compose

private function packageForExtension(string $name, string $prettyVersion): CompletePackageInterface
{
$extraDescription = '';

try {
$version = $this->versionParser->normalize($prettyVersion);
} catch (UnexpectedValueException) {
$extraDescription = ' (actual version: ' . $prettyVersion . ')';
if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) {
$prettyVersion = $match[1];
} else {
$prettyVersion = '0';
}

$version = $this->versionParser->normalize($prettyVersion);
}
$extVersion = ExtensionVersion::parse($prettyVersion);

$package = new CompletePackage(
'ext-' . str_replace(' ', '-', strtolower($name)),
$version,
$prettyVersion,
$extVersion->normalised,
$extVersion->originalOrModified,
);
$package->setDescription('The ' . $name . ' PHP extension' . $extraDescription);
$package->setDescription('The ' . $name . ' PHP extension' . ($extVersion->wasModified() ? ' (actual version: ' . $prettyVersion . ')' : ''));
$package->setType('php-ext');

return $package;
Expand Down
45 changes: 45 additions & 0 deletions src/ExtensionVersion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Php\Pie;

use Composer\Pcre\Preg;
use Composer\Semver\VersionParser;
use UnexpectedValueException;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class ExtensionVersion
{
private function __construct(
public readonly string $original,
public readonly string $originalOrModified,
public readonly string $normalised,
) {
}

public static function parse(string $original): self
{
$versionParser = new VersionParser();
$originalOrModified = $original;

try {
$normalised = $versionParser->normalize($original);
} catch (UnexpectedValueException) {
if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $original, $match)) {
$originalOrModified = $match[1];
} else {
$originalOrModified = '0';
}

$normalised = $versionParser->normalize($originalOrModified);
}

return new self($original, $originalOrModified, $normalised);
}

public function wasModified(): bool
{
return $this->original !== $this->originalOrModified;
}
}
37 changes: 17 additions & 20 deletions src/Installing/InstallForPhpProject/CheckExtensionStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
use Composer\Package\Link;
use Composer\Package\Version\VersionParser;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionVersion;
use Php\Pie\Platform\PiePackageList;
use Php\Pie\Util\Emoji;

use function array_key_exists;
use function count;
use function in_array;
use function sprintf;
use function strtolower;

Expand All @@ -23,47 +24,43 @@ public function __construct(private readonly IOInterface $io)
{
}

/**
* Check if an extension is installed; returns true if it's installed, but outputs a warning if the version
* constraint doesn't match. Returns true of the extension is missing.
*
* @param list<string> $phpEnabledExtensions
*/
/** @param array<string, string> $phpEnabledExtensions */
public function __invoke(
Link $link,
PiePackageList $piePackagesForExtension,
array $phpEnabledExtensions,
): bool {
$extension = ExtensionName::normaliseFromString($link->getTarget());
$linkRequiresConstraint = $link->getPrettyConstraint();
): ExtensionInstalledStatus {
$extension = ExtensionName::normaliseFromString($link->getTarget());
$lowercasedExtensionName = strtolower($extension->name());
$linkRequiresConstraint = $link->getPrettyConstraint();

$piePackageVersion = null;
$installedPieOrExtensionVersion = $phpEnabledExtensions[$lowercasedExtensionName] ?? null;

if (count($piePackagesForExtension) === 1) {
$piePackageVersion = $piePackagesForExtension->onlyOne()->version();
$installedPieOrExtensionVersion = $piePackagesForExtension->onlyOne()->version();
}

$piePackageVersionMatchesLinkConstraint = null;
if ($piePackageVersion !== null) {
if ($installedPieOrExtensionVersion !== null) {
$piePackageVersionMatchesLinkConstraint = $link
->getConstraint()
->matches(
(new VersionParser())->parseConstraints($piePackageVersion),
(new VersionParser())->parseConstraints(ExtensionVersion::parse($installedPieOrExtensionVersion)->normalised),
);
}

if (in_array(strtolower($extension->name()), $phpEnabledExtensions)) {
if ($piePackageVersion !== null && $piePackageVersionMatchesLinkConstraint === false) {
if (array_key_exists($lowercasedExtensionName, $phpEnabledExtensions)) {
if ($installedPieOrExtensionVersion !== null && $piePackageVersionMatchesLinkConstraint === false) {
$this->io->write(sprintf(
'%s: <comment>%s:%s</comment> %s Version %s is installed, but does not meet the version requirement',
$link->getDescription(),
$extension->nameWithExtPrefix(),
$linkRequiresConstraint,
Emoji::WARNING,
$piePackageVersion,
$installedPieOrExtensionVersion,
));

return true;
return ExtensionInstalledStatus::ConstraintNotMet;
}

$this->io->write(sprintf(
Expand All @@ -74,7 +71,7 @@ public function __invoke(
Emoji::GREEN_CHECKMARK,
));

return true;
return ExtensionInstalledStatus::Installed;
}

$this->io->write(sprintf(
Expand All @@ -85,6 +82,6 @@ public function __invoke(
Emoji::PROHIBITED,
));

return false;
return ExtensionInstalledStatus::Missing;
}
}
12 changes: 12 additions & 0 deletions src/Installing/InstallForPhpProject/ExtensionInstalledStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

enum ExtensionInstalledStatus
{
case Installed;
case Missing;
case ConstraintNotMet;
}
2 changes: 1 addition & 1 deletion test/assets/example-php-project/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"require": {
"php": "^8.0",
"ext-redis": "*",
"ext-example_pie_extension": "^2.0"
"ext-example_pie_extension": "^2.0.9"
}
}
4 changes: 2 additions & 2 deletions test/assets/example-php-project/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading