Skip to content
Merged
15 changes: 9 additions & 6 deletions .github/workflows/build-assets.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -68,6 +68,8 @@ jobs:
needs:
- build-phar
runs-on: ${{ matrix.operating-system }}
env:
SPC_VERSION: 2.8.5
strategy:
fail-fast: false
matrix:
Expand All@@ -76,7 +78,7 @@ jobs:
- ubuntu-24.04-arm
- macos-15-intel
- macos-26
- windows-2025
# - windows-2025 - disabled for now, seems broken
permissions:
# id-token:write is required for build provenance attestation.
id-token: write
Expand All@@ -92,33 +94,34 @@ jobs:
# Source URL: https://static-php.dev/en/guide/manual-build.html#build-locally-using-spc-binary-recommended
case "${{ matrix.operating-system }}" in
ubuntu-24.04)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
curl -fsSL -o spc.tgz https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-linux-x86_64.tar.gz
;;

ubuntu-24.04-arm)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
curl -fsSL -o spc.tgz https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-linux-aarch64.tar.gz
;;

macos-15-intel)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
curl -fsSL -o spc.tgz https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-x86_64.tar.gz
;;

macos-26)
curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
curl -fsSL -o spc.tgz https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-aarch64.tar.gz
;;

*)
echo "unsupported operating system: ${{ matrix.operating-system }}"
exit 1
;;
esac
tar zxvf spc.tgz
chmod +x spc
echo "SPC_BINARY=./spc" >> $GITHUB_ENV
echo "PIE_BINARY_OUTPUT=pie-${{ runner.os }}-${{ runner.arch }}" >> $GITHUB_ENV
- name: Download SPC (Windows)
if: runner.os == 'Windows'
run: |
curl.exe -fsSL -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
curl.exe -fsSL -o spc.exe https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-windows-x64.exe
chmod +x spc.exe
echo "SPC_BINARY=.\spc.exe" >> $env:GITHUB_ENV
echo "PIE_BINARY_OUTPUT=pie-${{ runner.os }}-${{ runner.arch }}.exe" >> $env:GITHUB_ENV
Expand Down
11 changes: 8 additions & 3 deletions src/Command/InstallExtensionsForProjectCommand.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -167,13 +167,18 @@ public function execute(InputInterface $input, OutputInterface $output): int

array_walk(
$extensionsRequired,
function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePackages, $input, &$anyErrorsHappened): void {
function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePackages, $input, &$anyErrorsHappened, $targetPlatform): void {
$extension = ExtensionName::normaliseFromString($link->getTarget());
$linkRequiresConstraint = $link->getPrettyConstraint();

$piePackagesForExtension = $installedPiePackages
->findByPhpFormattedExtensionName($extension->phpFormattedExtensionName())
->onlyVerifiedFor($targetPlatform);

$piePackageVersion = null;
if (in_array($extension->name(), array_keys($installedPiePackages))) {
$piePackageVersion = $installedPiePackages[$extension->name()]->version();

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

$piePackageVersionMatchesLinkConstraint = null;
Expand Down
188 changes: 73 additions & 115 deletions src/Command/ShowCommand.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,15 +10,14 @@
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\DependencyResolver\ResolveDependencyWithComposer;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\BinaryFileFailedVerification;
use Php\Pie\Platform as PiePlatform;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Util\Emoji;
use Php\Pie\Util\PackageVerificationStatus;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
Expand All@@ -28,15 +27,11 @@
use Webmozart\Assert\Assert;

use function array_diff;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_walk;
use function count;
use function file_exists;
use function rtrim;
use function sprintf;
use function substr;

use const DIRECTORY_SEPARATOR;

/** @phpstan-import-type PieMetadata from PieInstalledJsonMetadataKeys */
#[AsCommand(
Expand DownExpand Up@@ -99,8 +94,6 @@ public function execute(InputInterface $input, OutputInterface $output): int

$piePackages = $this->installedPiePackages->allPiePackages($composer);
$phpEnabledExtensions = $targetPlatform->phpBinaryPath->extensions();
$extensionPath = $targetPlatform->phpBinaryPath->extensionPath();
$extensionEnding = $targetPlatform->operatingSystem === OperatingSystem::Windows ? '.dll' : '.so';
$piePackagesMatched = [];
$rootPackageRequires = $composer->getPackage()->getRequires();

Expand All@@ -110,141 +103,106 @@ public function execute(InputInterface $input, OutputInterface $output): int
));
array_walk(
$phpEnabledExtensions,
function (string $version, string $phpExtensionName) use ($composer, $rootPackageRequires, $targetPlatform, $showAll, $piePackages, $extensionPath, $extensionEnding, &$piePackagesMatched): void {
if (! array_key_exists($phpExtensionName, $piePackages)) {
function (string $version, string $phpExtensionName) use ($composer, $rootPackageRequires, $targetPlatform, $showAll, $piePackages, &$piePackagesMatched): void {
$pieMatchesForExtension = $piePackages->findByPhpFormattedExtensionName($phpExtensionName);

if (! count($pieMatchesForExtension)) {
if ($showAll) {
$this->io->write(sprintf(' <comment>%s:%s</comment>', $phpExtensionName, $version));
}

return;
}

$piePackage = $piePackages[$phpExtensionName];
$piePackagesMatched[] = $phpExtensionName;
$packageName= $piePackage->name();
$packageRequirement = $rootPackageRequires[$piePackage->name()]->getPrettyConstraint();
foreach ($pieMatchesForExtension->packages() as $piePackage) {
$packageName = $piePackage->name();
$verificationStatus = $piePackage->verifyPackageStatus($targetPlatform);
$packageRequirement = $rootPackageRequires[$packageName]->getPrettyConstraint();

try {
// Don't check for updates for bundled PHP extensions
if ($piePackage->isBundledPhpExtension()) {
throw new BundledPhpExtensionRefusal();
if ($verificationStatus === PackageVerificationStatus::InstalledBinaryMetadataMissing) {
continue;
}

Assert::stringNotEmpty($packageName);
Assert::stringNotEmpty($packageRequirement);

$latestConstrainedPackage = ($this->resolveDependencyWithComposer)(
$composer,
$targetPlatform,
new RequestedPackageAndVersion($packageName, $packageRequirement),
false,
);

$latestPackage = ($this->resolveDependencyWithComposer)(
$composer,
$targetPlatform,
new RequestedPackageAndVersion($packageName, '*'),
false,
);
} catch (UnableToResolveRequirement | BundledPhpExtensionRefusal) {
$latestConstrainedPackage = null;
$latestPackage = null;
}
$piePackagesMatched[] = $packageName;

try {
// Don't check for updates for bundled PHP extensions
if ($piePackage->isBundledPhpExtension()) {
throw new BundledPhpExtensionRefusal();
}

Assert::stringNotEmpty($packageName);
Assert::stringNotEmpty($packageRequirement);

$latestConstrainedPackage = ($this->resolveDependencyWithComposer)(
$composer,
$targetPlatform,
new RequestedPackageAndVersion($packageName, $packageRequirement),
false,
);

$latestPackage = ($this->resolveDependencyWithComposer)(
$composer,
$targetPlatform,
new RequestedPackageAndVersion($packageName, '*'),
false,
);
} catch (UnableToResolveRequirement | BundledPhpExtensionRefusal) {
$latestConstrainedPackage = null;
$latestPackage = null;
}

$updateNotice = '';
if ($latestConstrainedPackage !== null && $latestConstrainedPackage->version() !== $piePackage->version()) {
$updateNotice = sprintf(
', upgradable to %s (within %s)',
$latestConstrainedPackage->version(),
$packageRequirement,
);
}
$updateNotice = '';
if ($latestConstrainedPackage !== null && $latestConstrainedPackage->version() !== $piePackage->version()) {
$updateNotice = sprintf(
', upgradable to %s (within %s)',
$latestConstrainedPackage->version(),
$packageRequirement,
);
}

if ($latestPackage !== null && $latestPackage->version() !== $latestConstrainedPackage->version()) {
$updateNotice .= sprintf(', latest version is %s', $latestPackage->version());
}
if ($latestPackage !== null && $latestPackage->version() !== $latestConstrainedPackage->version()) {
$updateNotice .= sprintf(', latest version is %s', $latestPackage->version());
}

$this->io->write(sprintf(
' <info>%s:%s</info> (from 🥧 <info>%s</info>%s)%s',
$phpExtensionName,
$version,
$piePackage->prettyNameAndVersion(),
self::verifyChecksumInformation(
$extensionPath,
$this->io->write(sprintf(
' <info>%s:%s</info> (from 🥧 <info>%s</info> %s)%s',
$phpExtensionName,
$extensionEnding,
PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($piePackage->composerPackage()),
),
$updateNotice,
));
$version,
$piePackage->prettyNameAndVersion(),
$verificationStatus->description(),
$updateNotice,
));
}
},
);

if (! $showAll && ! count($piePackagesMatched)) {
$this->io->write('(none)');
}

$unmatchedPiePackages = array_diff(array_keys($piePackages), $piePackagesMatched);
$unmatchedPiePackageNames = array_diff(array_map(static fn (Package $piePackage) => $piePackage->name(), $piePackages->packages()), $piePackagesMatched);

if (count($unmatchedPiePackages)) {
if (count($unmatchedPiePackageNames)) {
$this->io->write(sprintf(
'%s %s <options=bold,underscore>PIE packages not loaded:</>',
"\n",
Emoji::WARNING,
));
$this->io->write('These extensions were installed with PIE but are not currently enabled.' . "\n");
$this->io->write('These extensions were set up with PIE but are not currently enabled.' . "\n");

foreach ($unmatchedPiePackages as $unmatchedPiePackage) {
$this->io->write(sprintf(' - %s', $piePackages[$unmatchedPiePackage]->prettyNameAndVersion()));
foreach ($unmatchedPiePackageNames as $unmatchedPiePackageName) {
$unmatchedPiePackage = $piePackages->findByPackageName($unmatchedPiePackageName);

$message = match ($unmatchedPiePackage->verifyPackageStatus($targetPlatform)) {
PackageVerificationStatus::ChecksumMetadataMissing => '- was built but not installed yet.',
PackageVerificationStatus::InstalledBinaryMetadataMissing => '- was downloaded but has not been built yet.',
default => '- installed but not enabled in INI file',
};
$this->io->write(rtrim(sprintf(' - %s %s', $unmatchedPiePackage->prettyNameAndVersion(), $message)));
}
}

return Command::SUCCESS;
}

/**
* @param PieMetadata $installedJsonMetadata
* @phpstan-param '.dll'|'.so' $extensionEnding
*/
private static function verifyChecksumInformation(
string $extensionPath,
string $phpExtensionName,
string $extensionEnding,
array $installedJsonMetadata,
): string {
$actualBinaryPathByConvention = $extensionPath . DIRECTORY_SEPARATOR . $phpExtensionName . $extensionEnding;

// The extension may not be in the usual path (since you can specify a full path to an extension in the INI file)
if (! file_exists($actualBinaryPathByConvention)) {
return '';
}

$pieExpectedBinaryPath = array_key_exists(PieInstalledJsonMetadataKeys::InstalledBinary->value, $installedJsonMetadata) ? $installedJsonMetadata[PieInstalledJsonMetadataKeys::InstalledBinary->value] : null;
$pieExpectedChecksum = array_key_exists(PieInstalledJsonMetadataKeys::BinaryChecksum->value, $installedJsonMetadata) ? $installedJsonMetadata[PieInstalledJsonMetadataKeys::BinaryChecksum->value] : null;

// Some other kind of mismatch of file path, or we don't have a stored checksum available
if (
$pieExpectedBinaryPath === null
|| $pieExpectedChecksum === null
|| $pieExpectedBinaryPath !== $actualBinaryPathByConvention
) {
return '';
}

$expectedBinaryFileFromMetadata = new BinaryFile($pieExpectedBinaryPath, $pieExpectedChecksum);
$actualBinaryFile = BinaryFile::fromFileWithSha256Checksum($actualBinaryPathByConvention);

try {
$expectedBinaryFileFromMetadata->verifyAgainstOther($actualBinaryFile);
} catch (BinaryFileFailedVerification) {
return sprintf(
' %s was %s..., expected %s...',
Emoji::WARNING,
substr($actualBinaryFile->checksum, 0, 8),
substr($expectedBinaryFileFromMetadata->checksum, 0, 8),
);
}

return ' ' . Emoji::GREEN_CHECKMARK;
}
}
2 changes: 1 addition & 1 deletion src/Command/UninstallCommand.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,7 +109,7 @@ private function findPiePackageByPackageName(string $packageToRemove, Composer $
{
$piePackages = $this->installedPiePackages->allPiePackages($composer);

foreach ($piePackages as $piePackage) {
foreach ($piePackages->packages() as $piePackage) {
if ($piePackage->name() === $packageToRemove) {
return $piePackage;
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,7 @@ public function __construct(PhpBinaryPath $phpBinaryPath, Composer $composer, In

$piePackages = $installedPiePackages->allPiePackages($composer);
$extensionsBeingReplacedByPiePackages = [];
foreach ($piePackages as $piePackage) {
foreach ($piePackages->packages() as $piePackage) {
foreach ($piePackage->composerPackage()->getReplaces() as $replaceLink) {
$target = $replaceLink->getTarget();
if (
Expand Down
Loading
Loading