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
15 changes: 9 additions & 6 deletions .github/workflows/build-assets.yml
Original file line number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 number Diff line number Diff 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