From bfb058b2ac2580295a6c425ba79705e0307dcd65 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Tue, 1 Jul 2025 21:53:44 +0100 Subject: [PATCH 01/19] Repository for installing PHP bundled extensions --- .../BundledPhpExtensionsRepository.php | 135 ++++++++++++++++++ .../PieComposerFactory.php | 6 + 2 files changed, 141 insertions(+) create mode 100644 src/ComposerIntegration/BundledPhpExtensionsRepository.php diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php new file mode 100644 index 00000000..b85d38d1 --- /dev/null +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -0,0 +1,135 @@ +, + * type?: ExtensionType, + * }> + */ + private static array $bundledPhpExtensions = [ + ['name' => 'bcmath'], + ['name' => 'bz2'], + ['name' => 'calendar'], + [ + 'name' => 'com_dotnet', + 'os-families' => [OperatingSystemFamily::Windows], + ], + ['name' => 'ctype'], + ['name' => 'curl'], + // ['name' => 'date'], // config0.m4 + ['name' => 'dba'], + ['name' => 'dl_test'], + ['name' => 'dom'], + ['name' => 'enchant'], + ['name' => 'exif'], + ['name' => 'ffi'], // @todo ext name mismatch + ['name' => 'gd'], + ['name' => 'gettext'], + ['name' => 'gmp'], + // ['name' => 'hash'], // make runs but no .so? + ['name' => 'iconv'], + ['name' => 'intl'], + // ['name' => 'json'], // make runs but no .so? + ['name' => 'ldap'], + // ['name' => 'lexbor'], // recent split it seems + // ['name' => 'libxml'], // config0.m4 + ['name' => 'mbstring'], + // ['name' => 'mysqli'], // build failure + // ['name' => 'mysqlnd'], // config9.m4 + // ['name' => 'odbc'], // build failure + [ + 'name' => 'opcache', // @todo ext name mismatch + 'type' => ExtensionType::ZendExtension, + ], + // ['name' => 'openssl'], // config0.m4 + ['name' => 'pcntl'], + // ['name' => 'pcre'], // config0.m4 + // ['name' => 'pdo'], // build failure + // ['name' => 'pdo_dblib'], // build failure + // ['name' => 'pdo_firebird'], // build failure + // ['name' => 'pdo_mysql'], // build failure + // ['name' => 'pdo_odbc'], // build failure + // ['name' => 'pdo_pgsql'], // build failure + // ['name' => 'pdo_sqlite'], // build failure + // ['name' => 'pgsql'], // build failure + // ['name' => 'phar'], // build failure + ['name' => 'posix'], + // ['name' => 'random'], // make runs but no .so? + ['name' => 'readline'], + // ['name' => 'reflection'], // make runs but no .so? + ['name' => 'session'], + ['name' => 'shmop'], + ['name' => 'simplexml'], // @todo ext name mismatch + // ['name' => 'skeleton'], // config.m4.in + ['name' => 'snmp'], + ['name' => 'soap'], + ['name' => 'sockets'], + ['name' => 'sodium'], + // ['name' => 'spl'], // make runs but no .so? + // ['name' => 'sqlite3'], // config0.m4 + // ['name' => 'standard'], // make runs but no .so? + ['name' => 'sysvmsg'], + ['name' => 'sysvsem'], + ['name' => 'sysvshm'], + ['name' => 'tidy'], + // ['name' => 'tokenizer'], // build failure + // ['name' => 'uri'], // new ext, need to apply version constraint + ['name' => 'xml'], + ['name' => 'xmlreader'], + ['name' => 'xmlwriter'], + ['name' => 'xsl'], + // ['name' => 'zend_test'], // build failure + ['name' => 'zip'], + // ['name' => 'zlib'], // config0.m4 + ]; + + public static function forTargetPlatform(TargetPlatform $targetPlatform): self + { + $phpVersion = $targetPlatform->phpBinaryPath->version(); + + return new self(array_map( + static function (array $extension) use ($phpVersion): Package { + $package = new CompletePackage('php/' . $extension['name'], $phpVersion . '.0', $phpVersion); + $package->setType(($extension['type'] ?? ExtensionType::PhpModule)->value); + $package->setDistType('zip'); + $package->setDistUrl(sprintf('https://github.com/php/php-src/archive/refs/tags/php-%s.zip', $phpVersion)); + $package->setDistReference(sprintf('php-%s', $phpVersion)); + $phpExt = [ + 'extension-name' => $extension['name'], + 'build-path' => 'ext/' . $extension['name'], + ]; + + if (array_key_exists('os-families', $extension)) { + $phpExt['os-families'] = array_map( + static fn (OperatingSystemFamily $osFamily) => $osFamily->value, + $extension['os-families'], + ); + } + + $package->setPhpExt($phpExt); + + return $package; + }, + self::$bundledPhpExtensions, + )); + } +} diff --git a/src/ComposerIntegration/PieComposerFactory.php b/src/ComposerIntegration/PieComposerFactory.php index ba9dcd28..b7c2bb05 100644 --- a/src/ComposerIntegration/PieComposerFactory.php +++ b/src/ComposerIntegration/PieComposerFactory.php @@ -63,6 +63,12 @@ public static function createPieComposer( true, ); + $composer + ->getRepositoryManager() + ->addRepository(BundledPhpExtensionsRepository::forTargetPlatform( + $composerRequest->targetPlatform, + )); + OverrideDownloadUrlInstallListener::selfRegister($composer, $io, $container, $composerRequest); RemoveUnrelatedInstallOperations::selfRegister($composer, $composerRequest); From 620785ec67dde8813ced2d380f81bcdee09738d7 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 2 Jul 2025 19:54:14 +0100 Subject: [PATCH 02/19] Remove built-in PHP extensions and fix config0.m4/config9.m4 --- src/Building/UnixBuild.php | 24 +++++++++++++++++++ .../BundledPhpExtensionsRepository.php | 18 ++++---------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/Building/UnixBuild.php b/src/Building/UnixBuild.php index ec91385f..d5b6ed93 100644 --- a/src/Building/UnixBuild.php +++ b/src/Building/UnixBuild.php @@ -15,8 +15,11 @@ use function count; use function file_exists; use function implode; +use function rename; use function sprintf; +use const DIRECTORY_SEPARATOR; + /** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ final class UnixBuild implements Build { @@ -90,6 +93,25 @@ public function __invoke( return BinaryFile::fromFileWithSha256Checksum($expectedSoFile); } + private function renamesToConfigM4(DownloadedPackage $downloadedPackage, OutputInterface $output): void + { + $configM4 = $downloadedPackage->extractedSourcePath . DIRECTORY_SEPARATOR . 'config.m4'; + if (file_exists($configM4)) { + return; + } + + $output->writeln('config.m4 does not exist; checking alternatives', OutputInterface::VERBOSITY_VERY_VERBOSE); + foreach (['config0.m4', 'config9.m4'] as $alternateConfigM4) { + $fullPathToAlternate = $downloadedPackage->extractedSourcePath . DIRECTORY_SEPARATOR . $alternateConfigM4; + if (file_exists($fullPathToAlternate)) { + $output->writeln(sprintf('Renaming %s to config.m4', $alternateConfigM4), OutputInterface::VERBOSITY_VERY_VERBOSE); + rename($fullPathToAlternate, $configM4); + + return; + } + } + } + /** @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback */ private function phpize( PhpizePath $phpize, @@ -103,6 +125,8 @@ private function phpize( $output->writeln('Running phpize step using: ' . implode(' ', $phpizeCommand) . ''); } + $this->renamesToConfigM4($downloadedPackage, $output); + Process::run( $phpizeCommand, $downloadedPackage->extractedSourcePath, diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index b85d38d1..80215671 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -35,7 +35,6 @@ class BundledPhpExtensionsRepository extends ArrayRepository ], ['name' => 'ctype'], ['name' => 'curl'], - // ['name' => 'date'], // config0.m4 ['name' => 'dba'], ['name' => 'dl_test'], ['name' => 'dom'], @@ -45,24 +44,20 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'gd'], ['name' => 'gettext'], ['name' => 'gmp'], - // ['name' => 'hash'], // make runs but no .so? ['name' => 'iconv'], ['name' => 'intl'], - // ['name' => 'json'], // make runs but no .so? ['name' => 'ldap'], // ['name' => 'lexbor'], // recent split it seems - // ['name' => 'libxml'], // config0.m4 ['name' => 'mbstring'], // ['name' => 'mysqli'], // build failure - // ['name' => 'mysqlnd'], // config9.m4 + ['name' => 'mysqlnd'], // ['name' => 'odbc'], // build failure [ 'name' => 'opcache', // @todo ext name mismatch 'type' => ExtensionType::ZendExtension, ], - // ['name' => 'openssl'], // config0.m4 + ['name' => 'openssl'], ['name' => 'pcntl'], - // ['name' => 'pcre'], // config0.m4 // ['name' => 'pdo'], // build failure // ['name' => 'pdo_dblib'], // build failure // ['name' => 'pdo_firebird'], // build failure @@ -73,20 +68,15 @@ class BundledPhpExtensionsRepository extends ArrayRepository // ['name' => 'pgsql'], // build failure // ['name' => 'phar'], // build failure ['name' => 'posix'], - // ['name' => 'random'], // make runs but no .so? ['name' => 'readline'], - // ['name' => 'reflection'], // make runs but no .so? ['name' => 'session'], ['name' => 'shmop'], ['name' => 'simplexml'], // @todo ext name mismatch - // ['name' => 'skeleton'], // config.m4.in ['name' => 'snmp'], ['name' => 'soap'], ['name' => 'sockets'], ['name' => 'sodium'], - // ['name' => 'spl'], // make runs but no .so? - // ['name' => 'sqlite3'], // config0.m4 - // ['name' => 'standard'], // make runs but no .so? + ['name' => 'sqlite3'], ['name' => 'sysvmsg'], ['name' => 'sysvsem'], ['name' => 'sysvshm'], @@ -99,7 +89,7 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'xsl'], // ['name' => 'zend_test'], // build failure ['name' => 'zip'], - // ['name' => 'zlib'], // config0.m4 + ['name' => 'zlib'], ]; public static function forTargetPlatform(TargetPlatform $targetPlatform): self From aa7e975de6d7e3839c731094ca21c6837eeb66f7 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 2 Jul 2025 21:18:41 +0100 Subject: [PATCH 03/19] Added php/mysqli --- .../BundledPhpExtensionsRepository.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index 80215671..8ddf1347 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -23,6 +23,7 @@ class BundledPhpExtensionsRepository extends ArrayRepository * name: non-empty-string, * os-families?: non-empty-list, * type?: ExtensionType, + * priority?: int, * }> */ private static array $bundledPhpExtensions = [ @@ -49,7 +50,10 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'ldap'], // ['name' => 'lexbor'], // recent split it seems ['name' => 'mbstring'], - // ['name' => 'mysqli'], // build failure + [ + 'name' => 'mysqli', + 'priority' => 90, // must load after mysqlnd + ], ['name' => 'mysqlnd'], // ['name' => 'odbc'], // build failure [ @@ -81,13 +85,13 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'sysvsem'], ['name' => 'sysvshm'], ['name' => 'tidy'], - // ['name' => 'tokenizer'], // build failure + // ['name' => 'tokenizer'], // build failure - make: *** No rule to make target '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.y', needed by '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.c'. Stop. // ['name' => 'uri'], // new ext, need to apply version constraint ['name' => 'xml'], ['name' => 'xmlreader'], ['name' => 'xmlwriter'], ['name' => 'xsl'], - // ['name' => 'zend_test'], // build failure + // ['name' => 'zend_test'], // build failure - ext/zend_test/test.c:48:11: fatal error: libxml/globals.h: No such file or directory ['name' => 'zip'], ['name' => 'zlib'], ]; @@ -115,6 +119,10 @@ static function (array $extension) use ($phpVersion): Package { ); } + if (array_key_exists('priority', $extension)) { + $phpExt['priority'] = $extension['priority']; + } + $package->setPhpExt($phpExt); return $package; From 178379eddb94ef7dfc3ed308b20c8544b4ff3366 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 2 Jul 2025 21:43:02 +0100 Subject: [PATCH 04/19] Map special extension names when listing installed PIE packages --- src/ComposerIntegration/BundledPhpExtensionsRepository.php | 6 +++--- src/Platform/InstalledPiePackages.php | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index 8ddf1347..4a0866a1 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -41,7 +41,7 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'dom'], ['name' => 'enchant'], ['name' => 'exif'], - ['name' => 'ffi'], // @todo ext name mismatch + ['name' => 'ffi'], ['name' => 'gd'], ['name' => 'gettext'], ['name' => 'gmp'], @@ -57,7 +57,7 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'mysqlnd'], // ['name' => 'odbc'], // build failure [ - 'name' => 'opcache', // @todo ext name mismatch + 'name' => 'opcache', 'type' => ExtensionType::ZendExtension, ], ['name' => 'openssl'], @@ -75,7 +75,7 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'readline'], ['name' => 'session'], ['name' => 'shmop'], - ['name' => 'simplexml'], // @todo ext name mismatch + ['name' => 'simplexml'], ['name' => 'snmp'], ['name' => 'soap'], ['name' => 'sockets'], diff --git a/src/Platform/InstalledPiePackages.php b/src/Platform/InstalledPiePackages.php index d303d9a8..c3f8e082 100644 --- a/src/Platform/InstalledPiePackages.php +++ b/src/Platform/InstalledPiePackages.php @@ -47,7 +47,12 @@ static function (BasePackage $basePackage): bool { array_map( /** @return non-empty-string */ static function (Package $package): string { - return $package->extensionName()->name(); + return match ($package->extensionName()->name()) { + 'ffi' => 'FFI', + 'opcache' => 'Zend OPcache', + 'simplexml' => 'SimpleXML', + default => $package->extensionName()->name(), + }; }, $composerInstalledPackages, ), From a133ed6e1a8d374d89fe75d9c6d64284702dc641 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 3 Jul 2025 20:17:07 +0100 Subject: [PATCH 05/19] Apply version constraints to bundled PHP extensions --- .../BundledPhpExtensionsRepository.php | 65 ++++++++++++------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index 4a0866a1..b91db81c 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -5,7 +5,9 @@ namespace Php\Pie\ComposerIntegration; use Composer\Package\CompletePackage; +use Composer\Package\Link; use Composer\Package\Package; +use Composer\Package\Version\VersionParser; use Composer\Repository\ArrayRepository; use Php\Pie\ExtensionType; use Php\Pie\Platform\OperatingSystemFamily; @@ -18,9 +20,9 @@ class BundledPhpExtensionsRepository extends ArrayRepository { /** - * @todo add version constraints * @var list, * type?: ExtensionType, * priority?: int, @@ -37,40 +39,41 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'ctype'], ['name' => 'curl'], ['name' => 'dba'], - ['name' => 'dl_test'], + ['name' => 'dl_test', 'version' => '>= 8.2.0'], ['name' => 'dom'], - ['name' => 'enchant'], + ['name' => 'enchant', 'version' => '>= 5.2.0'], ['name' => 'exif'], - ['name' => 'ffi'], + ['name' => 'ffi', 'version' => '>= 7.4.0'], ['name' => 'gd'], ['name' => 'gettext'], ['name' => 'gmp'], ['name' => 'iconv'], - ['name' => 'intl'], + ['name' => 'intl', 'version' => '>= 5.3.0'], ['name' => 'ldap'], - // ['name' => 'lexbor'], // recent split it seems + ['name' => 'lexbor', 'version' => '>= 8.5.0'], ['name' => 'mbstring'], [ 'name' => 'mysqli', 'priority' => 90, // must load after mysqlnd ], - ['name' => 'mysqlnd'], + ['name' => 'mysqlnd', 'version' => '>= 5.3.0'], // ['name' => 'odbc'], // build failure [ 'name' => 'opcache', 'type' => ExtensionType::ZendExtension, + 'version' => '>= 5.5.0', ], ['name' => 'openssl'], ['name' => 'pcntl'], - // ['name' => 'pdo'], // build failure - // ['name' => 'pdo_dblib'], // build failure - // ['name' => 'pdo_firebird'], // build failure - // ['name' => 'pdo_mysql'], // build failure - // ['name' => 'pdo_odbc'], // build failure - // ['name' => 'pdo_pgsql'], // build failure - // ['name' => 'pdo_sqlite'], // build failure + // ['name' => 'pdo', 'version' => '>= 5.1.0'], // build failure + // ['name' => 'pdo_dblib', 'version' => '>= 5.1.0'], // build failure + // ['name' => 'pdo_firebird', 'version' => '>= 5.1.0'], // build failure + // ['name' => 'pdo_mysql', 'version' => '>= 5.1.0'], // build failure + // ['name' => 'pdo_odbc', 'version' => '>= 5.1.0'], // build failure + // ['name' => 'pdo_pgsql', 'version' => '>= 5.1.0'], // build failure + // ['name' => 'pdo_sqlite', 'version' => '>= 5.1.0'], // build failure // ['name' => 'pgsql'], // build failure - // ['name' => 'phar'], // build failure + // ['name' => 'phar', 'version' => '>= 5.3.0'], // build failure ['name' => 'posix'], ['name' => 'readline'], ['name' => 'session'], @@ -79,32 +82,46 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'snmp'], ['name' => 'soap'], ['name' => 'sockets'], - ['name' => 'sodium'], - ['name' => 'sqlite3'], + ['name' => 'sodium', 'version' => '>= 7.2.0'], + ['name' => 'sqlite3', 'version' => '>= 5.3.0'], ['name' => 'sysvmsg'], ['name' => 'sysvsem'], ['name' => 'sysvshm'], ['name' => 'tidy'], // ['name' => 'tokenizer'], // build failure - make: *** No rule to make target '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.y', needed by '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.c'. Stop. - // ['name' => 'uri'], // new ext, need to apply version constraint + ['name' => 'uri', 'version' => '>= 8.5.0'], ['name' => 'xml'], - ['name' => 'xmlreader'], - ['name' => 'xmlwriter'], + ['name' => 'xmlreader', 'version' => '>= 5.1.0'], + ['name' => 'xmlwriter', 'version' => '>= 5.2.0'], ['name' => 'xsl'], - // ['name' => 'zend_test'], // build failure - ext/zend_test/test.c:48:11: fatal error: libxml/globals.h: No such file or directory - ['name' => 'zip'], + // ['name' => 'zend_test', 'version' => '>= 7.2.0'], // build failure - ext/zend_test/test.c:48:11: fatal error: libxml/globals.h: No such file or directory + ['name' => 'zip', 'version' => '>= 5.2.0'], ['name' => 'zlib'], ]; public static function forTargetPlatform(TargetPlatform $targetPlatform): self { - $phpVersion = $targetPlatform->phpBinaryPath->version(); + $versionParser = new VersionParser(); + $phpVersion = $targetPlatform->phpBinaryPath->version(); return new self(array_map( - static function (array $extension) use ($phpVersion): Package { + static function (array $extension) use ($versionParser, $phpVersion): Package { + if (! array_key_exists('version', $extension)) { + $extension['version'] = $phpVersion; + } + $package = new CompletePackage('php/' . $extension['name'], $phpVersion . '.0', $phpVersion); $package->setType(($extension['type'] ?? ExtensionType::PhpModule)->value); $package->setDistType('zip'); + $package->setRequires([ + 'php' => new Link( + 'php/' . $extension['name'], + 'php', + $versionParser->parseConstraints($extension['version']), + 'requires', + $extension['version'], + ), + ]); $package->setDistUrl(sprintf('https://github.com/php/php-src/archive/refs/tags/php-%s.zip', $phpVersion)); $package->setDistReference(sprintf('php-%s', $phpVersion)); $phpExt = [ From 113108b493cf6e9b62331eb237af0f0fd60e0e4a Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 3 Jul 2025 20:54:02 +0100 Subject: [PATCH 06/19] Document remaining build failures for bundled PHP extensions --- .../BundledPhpExtensionsRepository.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index b91db81c..4e4e111d 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -57,7 +57,7 @@ class BundledPhpExtensionsRepository extends ArrayRepository 'priority' => 90, // must load after mysqlnd ], ['name' => 'mysqlnd', 'version' => '>= 5.3.0'], - // ['name' => 'odbc'], // build failure + ['name' => 'odbc'], // build failure - cp: cannot stat '/usr/local/lib/odbclib.a': No such file or directory configure: error: ODBC header file '/usr/local/incl/sqlext.h' not found! [ 'name' => 'opcache', 'type' => ExtensionType::ZendExtension, @@ -65,15 +65,15 @@ class BundledPhpExtensionsRepository extends ArrayRepository ], ['name' => 'openssl'], ['name' => 'pcntl'], - // ['name' => 'pdo', 'version' => '>= 5.1.0'], // build failure - // ['name' => 'pdo_dblib', 'version' => '>= 5.1.0'], // build failure - // ['name' => 'pdo_firebird', 'version' => '>= 5.1.0'], // build failure - // ['name' => 'pdo_mysql', 'version' => '>= 5.1.0'], // build failure - // ['name' => 'pdo_odbc', 'version' => '>= 5.1.0'], // build failure - // ['name' => 'pdo_pgsql', 'version' => '>= 5.1.0'], // build failure - // ['name' => 'pdo_sqlite', 'version' => '>= 5.1.0'], // build failure - // ['name' => 'pgsql'], // build failure - // ['name' => 'phar', 'version' => '>= 5.3.0'], // build failure + // ['name' => 'pdo', 'version' => '>= 5.1.0'], // build failure - make: *** [Makefile:206: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo/ext/pdo/pdo_sql_parser.c] Error 127 + // ['name' => 'pdo_dblib', 'version' => '>= 5.1.0'], // build failure - configure: error: Cannot find FreeTDS in known installation directories. + // ['name' => 'pdo_firebird', 'version' => '>= 5.1.0'], // build failure - configure: error: libfbclient not found. + ['name' => 'pdo_mysql', 'version' => '>= 5.1.0'], // build failure - make: *** [Makefile:207: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo_mysql/ext/pdo_mysql/mysql_sql_parser.c] Error 127 + // ['name' => 'pdo_odbc', 'version' => '>= 5.1.0'], // build failure - configure: error: Unknown ODBC flavour yes + // ['name' => 'pdo_pgsql', 'version' => '>= 5.1.0'], // build failure - make: *** [Makefile:207: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo_pgsql/ext/pdo_pgsql/pgsql_sql_parser.c] Error 127 + // ['name' => 'pdo_sqlite', 'version' => '>= 5.1.0'], // build failure - make: *** [Makefile:207: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo_sqlite/ext/pdo_sqlite/sqlite_sql_parser.c] Error 127 + ['name' => 'pgsql'], + // ['name' => 'phar', 'version' => '>= 5.3.0'], // build failure - config.status: error: cannot find input file: '/phar.1.in' ['name' => 'posix'], ['name' => 'readline'], ['name' => 'session'], From 965a91474023e38101aaa6d8be4caf7780994393 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 3 Jul 2025 21:13:13 +0100 Subject: [PATCH 07/19] Checking more extensions build on different PHP version --- .../BundledPhpExtensionsRepository.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index 4e4e111d..4a938f91 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -57,7 +57,7 @@ class BundledPhpExtensionsRepository extends ArrayRepository 'priority' => 90, // must load after mysqlnd ], ['name' => 'mysqlnd', 'version' => '>= 5.3.0'], - ['name' => 'odbc'], // build failure - cp: cannot stat '/usr/local/lib/odbclib.a': No such file or directory configure: error: ODBC header file '/usr/local/incl/sqlext.h' not found! + // ['name' => 'odbc'], // build failure - cp: cannot stat '/usr/local/lib/odbclib.a': No such file or directory configure: error: ODBC header file '/usr/local/incl/sqlext.h' not found! [ 'name' => 'opcache', 'type' => ExtensionType::ZendExtension, @@ -68,10 +68,10 @@ class BundledPhpExtensionsRepository extends ArrayRepository // ['name' => 'pdo', 'version' => '>= 5.1.0'], // build failure - make: *** [Makefile:206: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo/ext/pdo/pdo_sql_parser.c] Error 127 // ['name' => 'pdo_dblib', 'version' => '>= 5.1.0'], // build failure - configure: error: Cannot find FreeTDS in known installation directories. // ['name' => 'pdo_firebird', 'version' => '>= 5.1.0'], // build failure - configure: error: libfbclient not found. - ['name' => 'pdo_mysql', 'version' => '>= 5.1.0'], // build failure - make: *** [Makefile:207: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo_mysql/ext/pdo_mysql/mysql_sql_parser.c] Error 127 + ['name' => 'pdo_mysql', 'version' => '>= 5.1.0'], // ['name' => 'pdo_odbc', 'version' => '>= 5.1.0'], // build failure - configure: error: Unknown ODBC flavour yes - // ['name' => 'pdo_pgsql', 'version' => '>= 5.1.0'], // build failure - make: *** [Makefile:207: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo_pgsql/ext/pdo_pgsql/pgsql_sql_parser.c] Error 127 - // ['name' => 'pdo_sqlite', 'version' => '>= 5.1.0'], // build failure - make: *** [Makefile:207: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo_sqlite/ext/pdo_sqlite/sqlite_sql_parser.c] Error 127 + ['name' => 'pdo_pgsql', 'version' => '>= 5.1.0'], + ['name' => 'pdo_sqlite', 'version' => '>= 5.1.0'], ['name' => 'pgsql'], // ['name' => 'phar', 'version' => '>= 5.3.0'], // build failure - config.status: error: cannot find input file: '/phar.1.in' ['name' => 'posix'], From fb0f66b31685036883ed386253aa4ebccb8a4897 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Mon, 7 Jul 2025 20:35:21 +0100 Subject: [PATCH 08/19] Removed bundled PHP exts that don't make sense to be installable with PIE --- .../BundledPhpExtensionsRepository.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index 4a938f91..251569da 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -32,14 +32,9 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'bcmath'], ['name' => 'bz2'], ['name' => 'calendar'], - [ - 'name' => 'com_dotnet', - 'os-families' => [OperatingSystemFamily::Windows], - ], ['name' => 'ctype'], ['name' => 'curl'], ['name' => 'dba'], - ['name' => 'dl_test', 'version' => '>= 8.2.0'], ['name' => 'dom'], ['name' => 'enchant', 'version' => '>= 5.2.0'], ['name' => 'exif'], @@ -50,7 +45,6 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'iconv'], ['name' => 'intl', 'version' => '>= 5.3.0'], ['name' => 'ldap'], - ['name' => 'lexbor', 'version' => '>= 8.5.0'], ['name' => 'mbstring'], [ 'name' => 'mysqli', @@ -89,12 +83,10 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'sysvshm'], ['name' => 'tidy'], // ['name' => 'tokenizer'], // build failure - make: *** No rule to make target '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.y', needed by '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.c'. Stop. - ['name' => 'uri', 'version' => '>= 8.5.0'], ['name' => 'xml'], ['name' => 'xmlreader', 'version' => '>= 5.1.0'], ['name' => 'xmlwriter', 'version' => '>= 5.2.0'], ['name' => 'xsl'], - // ['name' => 'zend_test', 'version' => '>= 7.2.0'], // build failure - ext/zend_test/test.c:48:11: fatal error: libxml/globals.h: No such file or directory ['name' => 'zip', 'version' => '>= 5.2.0'], ['name' => 'zlib'], ]; From b7f8033961f61e9b6ca0fdb832c924a64df86419 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Mon, 7 Jul 2025 20:30:17 +0100 Subject: [PATCH 09/19] Added integration test to validate bundled PHP extension builds --- .github/workflows/continuous-integration.yml | 56 +++++++++++++++++++ .../BundledPhpExtensionsRepository.php | 2 +- test/install-bundled-php-exts.php | 51 +++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 test/install-bundled-php-exts.php diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index f19d5d6f..5efbc839 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -45,6 +45,62 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + bundled-php-extension-tests: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: + - ubuntu-latest + php-versions: + - '8.1' + - '8.2' + - '8.3' + - '8.4' + #- '8.5' # @todo enable + steps: + - name: Install platform dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libcurl4-openssl-dev \ + liblmdb-dev \ + libdb-dev \ + libqdbm-dev \ + libenchant-2-dev \ + libexif-dev \ + libgd-dev \ + libpng-dev \ + libtiff-dev \ + libfreetype-dev \ + libfreetype6 \ + libfontconfig1-dev \ + libgmp-dev \ + libssl-dev \ + libsodium-dev \ + libxml2-dev \ + libonig-dev \ + libldap-dev \ + libedit-dev \ + libsnmp-dev \ + libtidy-dev \ + libxslt1-dev \ + libsasl2-dev \ + libzip-dev + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: none, mbstring + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 + - name: Composer Install + run: composer install --ignore-platform-reqs + - name: Run bundled PHP install test + run: sudo php test/install-bundled-php-exts.php + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + behaviour-tests: runs-on: ${{ matrix.operating-system }} strategy: diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index 251569da..9866c409 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -46,11 +46,11 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'intl', 'version' => '>= 5.3.0'], ['name' => 'ldap'], ['name' => 'mbstring'], + ['name' => 'mysqlnd', 'version' => '>= 5.3.0'], [ 'name' => 'mysqli', 'priority' => 90, // must load after mysqlnd ], - ['name' => 'mysqlnd', 'version' => '>= 5.3.0'], // ['name' => 'odbc'], // build failure - cp: cannot stat '/usr/local/lib/odbclib.a': No such file or directory configure: error: ODBC header file '/usr/local/incl/sqlext.h' not found! [ 'name' => 'opcache', diff --git a/test/install-bundled-php-exts.php b/test/install-bundled-php-exts.php new file mode 100644 index 00000000..4de0fc72 --- /dev/null +++ b/test/install-bundled-php-exts.php @@ -0,0 +1,51 @@ + $package->getName(), + BundledPhpExtensionsRepository::forTargetPlatform( + TargetPlatform::fromPhpBinaryPath( + $phpBinaryPath, + null, + ), + ) + ->getPackages(), +); + +$anyFailures = false; + +foreach ($packageNames as $packageName) { + $cmd = [ + 'sudo', + 'bin/pie', + 'install', + $packageName, + '--with-php-config=' . $phpBinaryPath->phpConfigPath(), + ]; + + echo ' - ' . implode(' ', $cmd) . PHP_EOL; + + try { + Process::run($cmd, timeout: null); + } catch (Throwable $e) { + echo $e->__toString() . PHP_EOL; + $anyFailures = true; + } +} + +echo Process::run(['bin/pie', 'show', '--with-php-config=' . $phpBinaryPath->phpConfigPath()]); + +if ($anyFailures) { + exit(1); +} From c1544dfd97aaa1f7a20ac3d6bf90648955ebc47c Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Tue, 15 Jul 2025 21:42:58 +0100 Subject: [PATCH 10/19] Specify more detailed requirements to ensure upstream dependencies are met --- .../BundledPhpExtensionsRepository.php | 121 +++++++++++++----- 1 file changed, 89 insertions(+), 32 deletions(-) diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index 9866c409..3ef3a552 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -13,7 +13,9 @@ use Php\Pie\Platform\OperatingSystemFamily; use Php\Pie\Platform\TargetPlatform; +use function array_combine; use function array_key_exists; +use function array_keys; use function array_map; use function sprintf; @@ -22,7 +24,7 @@ class BundledPhpExtensionsRepository extends ArrayRepository /** * @var list, * os-families?: non-empty-list, * type?: ExtensionType, * priority?: int, @@ -36,17 +38,32 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'curl'], ['name' => 'dba'], ['name' => 'dom'], - ['name' => 'enchant', 'version' => '>= 5.2.0'], + [ + 'name' => 'enchant', + 'require' => ['php' => '>= 5.2.0'], + ], ['name' => 'exif'], - ['name' => 'ffi', 'version' => '>= 7.4.0'], - ['name' => 'gd'], + [ + 'name' => 'ffi', + 'require' => ['php' => '>= 7.4.0'], + ], + // ['name' => 'gd'], // build failure - ext/gd/gd.c:79:11: fatal error: ft2build.h: No such file or directory ['name' => 'gettext'], ['name' => 'gmp'], ['name' => 'iconv'], - ['name' => 'intl', 'version' => '>= 5.3.0'], + [ + 'name' => 'intl', + 'require' => ['php' => '>= 5.3.0'], + ], ['name' => 'ldap'], ['name' => 'mbstring'], - ['name' => 'mysqlnd', 'version' => '>= 5.3.0'], + [ + 'name' => 'mysqlnd', + 'require' => [ + 'php' => '>= 5.3.0', + 'ext-openssl' => '*', + ], + ], [ 'name' => 'mysqli', 'priority' => 90, // must load after mysqlnd @@ -55,19 +72,28 @@ class BundledPhpExtensionsRepository extends ArrayRepository [ 'name' => 'opcache', 'type' => ExtensionType::ZendExtension, - 'version' => '>= 5.5.0', + 'require' => ['php' => '>= 5.5.0'], ], - ['name' => 'openssl'], +// ['name' => 'openssl'], // Not building in CI ['name' => 'pcntl'], - // ['name' => 'pdo', 'version' => '>= 5.1.0'], // build failure - make: *** [Makefile:206: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo/ext/pdo/pdo_sql_parser.c] Error 127 - // ['name' => 'pdo_dblib', 'version' => '>= 5.1.0'], // build failure - configure: error: Cannot find FreeTDS in known installation directories. - // ['name' => 'pdo_firebird', 'version' => '>= 5.1.0'], // build failure - configure: error: libfbclient not found. - ['name' => 'pdo_mysql', 'version' => '>= 5.1.0'], - // ['name' => 'pdo_odbc', 'version' => '>= 5.1.0'], // build failure - configure: error: Unknown ODBC flavour yes - ['name' => 'pdo_pgsql', 'version' => '>= 5.1.0'], - ['name' => 'pdo_sqlite', 'version' => '>= 5.1.0'], + // ['name' => 'pdo', 'require' => ['php' => '>= 5.1.0']], // build failure - make: *** [Makefile:206: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo/ext/pdo/pdo_sql_parser.c] Error 127 + // ['name' => 'pdo_dblib', 'require' => ['php' => '>= 5.1.0']], // build failure - configure: error: Cannot find FreeTDS in known installation directories. + // ['name' => 'pdo_firebird', 'require' => ['php' => '>= 5.1.0']], // build failure - configure: error: libfbclient not found. +// [ +// 'name' => 'pdo_mysql', +// 'require' => ['php' => '>= 5.1.0'], +// ], // Not building in CI + // ['name' => 'pdo_odbc', 'require' => ['php' => '>= 5.1.0']], // build failure - configure: error: Unknown ODBC flavour yes +// [ +// 'name' => 'pdo_pgsql', +// 'require' => ['php' => '>= 5.1.0'], +// ], // Not building in CI +// [ +// 'name' => 'pdo_sqlite', +// 'require' => ['php' => '>= 5.1.0'], +// ], // Not building in CI ['name' => 'pgsql'], - // ['name' => 'phar', 'version' => '>= 5.3.0'], // build failure - config.status: error: cannot find input file: '/phar.1.in' + // ['name' => 'phar', 'require' => ['php' => '>= 5.3.0']], // build failure - config.status: error: cannot find input file: '/phar.1.in' ['name' => 'posix'], ['name' => 'readline'], ['name' => 'session'], @@ -76,18 +102,40 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'snmp'], ['name' => 'soap'], ['name' => 'sockets'], - ['name' => 'sodium', 'version' => '>= 7.2.0'], - ['name' => 'sqlite3', 'version' => '>= 5.3.0'], + [ + 'name' => 'sodium', + 'require' => ['php' => '>= 7.2.0'], + ], + [ + 'name' => 'sqlite3', + 'require' => ['php' => '>= 5.3.0'], + ], ['name' => 'sysvmsg'], ['name' => 'sysvsem'], ['name' => 'sysvshm'], ['name' => 'tidy'], // ['name' => 'tokenizer'], // build failure - make: *** No rule to make target '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.y', needed by '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.c'. Stop. ['name' => 'xml'], - ['name' => 'xmlreader', 'version' => '>= 5.1.0'], - ['name' => 'xmlwriter', 'version' => '>= 5.2.0'], +// [ +// 'name' => 'xmlreader', +// 'require' => [ +// 'php' => '>= 5.1.0', +// 'ext-xml' => '*', +// 'ext-dom' => '*', +// ], +// ], // Not building in CI + [ + 'name' => 'xmlwriter', + 'require' => [ + 'php' => '>= 5.2.0', + 'ext-xml' => '*', + ], + ], ['name' => 'xsl'], - ['name' => 'zip', 'version' => '>= 5.2.0'], + [ + 'name' => 'zip', + 'require' => ['php' => '>= 5.2.0'], + ], ['name' => 'zlib'], ]; @@ -98,22 +146,31 @@ public static function forTargetPlatform(TargetPlatform $targetPlatform): self return new self(array_map( static function (array $extension) use ($versionParser, $phpVersion): Package { - if (! array_key_exists('version', $extension)) { - $extension['version'] = $phpVersion; + if (! array_key_exists('require', $extension)) { + $extension['require'] = ['php' => $phpVersion]; } + $requireLinks = array_map( + static function (string $target, string $constraint) use ($extension, $versionParser): Link { + return new Link( + 'php/' . $extension['name'], + $target, + $versionParser->parseConstraints($constraint), + 'requires', + $constraint, + ); + }, + array_keys($extension['require']), + $extension['require'], + ); + $package = new CompletePackage('php/' . $extension['name'], $phpVersion . '.0', $phpVersion); $package->setType(($extension['type'] ?? ExtensionType::PhpModule)->value); $package->setDistType('zip'); - $package->setRequires([ - 'php' => new Link( - 'php/' . $extension['name'], - 'php', - $versionParser->parseConstraints($extension['version']), - 'requires', - $extension['version'], - ), - ]); + $package->setRequires(array_combine( + array_map(static fn (Link $link) => $link->getTarget(), $requireLinks), + $requireLinks, + )); $package->setDistUrl(sprintf('https://github.com/php/php-src/archive/refs/tags/php-%s.zip', $phpVersion)); $package->setDistReference(sprintf('php-%s', $phpVersion)); $phpExt = [ From ca8924c6ed816efa32c5e5c873e9adf5ee4d0747 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 16 Jul 2025 21:18:02 +0100 Subject: [PATCH 11/19] Further bundled PHP extension configurations # Conflicts: # src/Platform/TargetPhp/PhpBinaryPath.php --- .github/workflows/continuous-integration.yml | 5 +- src/Building/UnixBuild.php | 6 + .../BundledPhpExtensionsRepository.php | 153 ++++++++++++++---- .../FindMatchingPackages.php | 4 +- 4 files changed, 133 insertions(+), 35 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5efbc839..1ce6be11 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -62,6 +62,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ + g++ gcc make autoconf libtool bison re2c pkg-config unzip \ libcurl4-openssl-dev \ liblmdb-dev \ libdb-dev \ @@ -85,12 +86,14 @@ jobs: libtidy-dev \ libxslt1-dev \ libsasl2-dev \ + libpq-dev \ + libsqlite3-dev \ libzip-dev - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - extensions: none, mbstring + extensions: none, mbstring, libxml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/checkout@v4 diff --git a/src/Building/UnixBuild.php b/src/Building/UnixBuild.php index d5b6ed93..18958e8c 100644 --- a/src/Building/UnixBuild.php +++ b/src/Building/UnixBuild.php @@ -4,6 +4,7 @@ namespace Php\Pie\Building; +use Php\Pie\ComposerIntegration\BundledPhpExtensionsRepository; use Php\Pie\Downloading\DownloadedPackage; use Php\Pie\File\BinaryFile; use Php\Pie\Platform\TargetPhp\PhpizePath; @@ -174,6 +175,11 @@ private function make( $makeCommand[] = sprintf('-j%d', $targetPlatform->makeParallelJobs); } + $makeCommand = BundledPhpExtensionsRepository::augmentMakeCommandForPhpBundledExtensions( + $makeCommand, + $downloadedPackage, + ); + if ($output->isVerbose()) { $output->writeln('Running make step with: ' . implode(' ', $makeCommand) . ''); } diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index 3ef3a552..a4a51961 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -9,14 +9,20 @@ use Composer\Package\Package; use Composer\Package\Version\VersionParser; use Composer\Repository\ArrayRepository; +use Php\Pie\Downloading\DownloadedPackage; use Php\Pie\ExtensionType; use Php\Pie\Platform\OperatingSystemFamily; use Php\Pie\Platform\TargetPlatform; +use Php\Pie\Util\Process; +use RuntimeException; +use Symfony\Component\Process\Exception\ProcessFailedException; use function array_combine; use function array_key_exists; use function array_keys; use function array_map; +use function in_array; +use function realpath; use function sprintf; class BundledPhpExtensionsRepository extends ArrayRepository @@ -37,7 +43,13 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'ctype'], ['name' => 'curl'], ['name' => 'dba'], - ['name' => 'dom'], + [ + 'name' => 'dom', + 'require' => [ + 'php' => '>= 5.2.0', + 'ext-libxml' => '*', + ], + ], [ 'name' => 'enchant', 'require' => ['php' => '>= 5.2.0'], @@ -67,8 +79,18 @@ class BundledPhpExtensionsRepository extends ArrayRepository [ 'name' => 'mysqli', 'priority' => 90, // must load after mysqlnd + 'require' => [ + 'php' => '>= 5.3.0', + /** + * Note: Whilst mysqli can be built without mysqlnd (you could + * specify `--with-mysqli=...`, we have to make installation + * with PIE practical at least to start with. We can look at + * improving this later, but for now something is better than + * nothing :) + */ + 'ext-mysqlnd' => '*', + ], ], - // ['name' => 'odbc'], // build failure - cp: cannot stat '/usr/local/lib/odbclib.a': No such file or directory configure: error: ODBC header file '/usr/local/incl/sqlext.h' not found! [ 'name' => 'opcache', 'type' => ExtensionType::ZendExtension, @@ -76,31 +98,51 @@ class BundledPhpExtensionsRepository extends ArrayRepository ], // ['name' => 'openssl'], // Not building in CI ['name' => 'pcntl'], - // ['name' => 'pdo', 'require' => ['php' => '>= 5.1.0']], // build failure - make: *** [Makefile:206: /home/james/.config/pie/php8.4_64f029c38a947437b5385bfed58650fb/vendor/php/pdo/ext/pdo/pdo_sql_parser.c] Error 127 - // ['name' => 'pdo_dblib', 'require' => ['php' => '>= 5.1.0']], // build failure - configure: error: Cannot find FreeTDS in known installation directories. - // ['name' => 'pdo_firebird', 'require' => ['php' => '>= 5.1.0']], // build failure - configure: error: libfbclient not found. -// [ -// 'name' => 'pdo_mysql', -// 'require' => ['php' => '>= 5.1.0'], -// ], // Not building in CI - // ['name' => 'pdo_odbc', 'require' => ['php' => '>= 5.1.0']], // build failure - configure: error: Unknown ODBC flavour yes -// [ -// 'name' => 'pdo_pgsql', -// 'require' => ['php' => '>= 5.1.0'], -// ], // Not building in CI -// [ -// 'name' => 'pdo_sqlite', -// 'require' => ['php' => '>= 5.1.0'], -// ], // Not building in CI + [ + 'name' => 'pdo', + 'require' => ['php' => '>= 5.1.0'], + ], + [ + 'name' => 'pdo_mysql', + 'require' => [ + 'php' => '>= 5.1.0', + 'ext-pdo' => '*', + ], + ], + [ + 'name' => 'pdo_pgsql', + 'require' => [ + 'php' => '>= 5.1.0', + 'ext-pdo' => '*', + ], + ], + [ + 'name' => 'pdo_sqlite', + 'require' => [ + 'php' => '>= 5.1.0', + 'ext-pdo' => '*', + ], + ], ['name' => 'pgsql'], - // ['name' => 'phar', 'require' => ['php' => '>= 5.3.0']], // build failure - config.status: error: cannot find input file: '/phar.1.in' ['name' => 'posix'], ['name' => 'readline'], ['name' => 'session'], ['name' => 'shmop'], - ['name' => 'simplexml'], + [ + 'name' => 'simplexml', + 'require' => [ + 'php' => '>= 5.2.0', + 'ext-libxml' => '*', + ], + ], ['name' => 'snmp'], - ['name' => 'soap'], + [ + 'name' => 'soap', + 'require' => [ + 'php' => '>= 5.2.0', + 'ext-libxml' => '*', + ], + ], ['name' => 'sockets'], [ 'name' => 'sodium', @@ -114,24 +156,35 @@ class BundledPhpExtensionsRepository extends ArrayRepository ['name' => 'sysvsem'], ['name' => 'sysvshm'], ['name' => 'tidy'], - // ['name' => 'tokenizer'], // build failure - make: *** No rule to make target '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.y', needed by '/home/james/workspace/oss/php-src/ext/tokenizer/Zend/zend_language_parser.c'. Stop. - ['name' => 'xml'], -// [ -// 'name' => 'xmlreader', -// 'require' => [ -// 'php' => '>= 5.1.0', -// 'ext-xml' => '*', -// 'ext-dom' => '*', -// ], -// ], // Not building in CI + [ + 'name' => 'xml', + 'require' => [ + 'php' => '>= 5.2.0', + 'ext-libxml' => '*', + ], + ], + [ + 'name' => 'xmlreader', + 'require' => [ + 'php' => '>= 5.1.0', + 'ext-libxml' => '*', + 'ext-dom' => '*', + ], + ], [ 'name' => 'xmlwriter', 'require' => [ 'php' => '>= 5.2.0', - 'ext-xml' => '*', + 'ext-libxml' => '*', + ], + ], + [ + 'name' => 'xsl', + 'require' => [ + 'php' => '>= 5.2.0', + 'ext-libxml' => '*', ], ], - ['name' => 'xsl'], [ 'name' => 'zip', 'require' => ['php' => '>= 5.2.0'], @@ -196,4 +249,38 @@ static function (string $target, string $constraint) use ($extension, $versionPa self::$bundledPhpExtensions, )); } + + private static function findRe2c(): string + { + try { + return Process::run(['which', 're2c']); + } catch (ProcessFailedException $processFailed) { + throw new RuntimeException('Unable to find re2c on the system', previous: $processFailed); + } + } + + /** + * @param list $makeCommand + * + * @return list + */ + public static function augmentMakeCommandForPhpBundledExtensions(array $makeCommand, DownloadedPackage $downloadedPackage): array + { + if ($downloadedPackage->package->name() === 'php/xmlreader') { + $makeCommand[] = 'EXTRA_CFLAGS=-I' . realpath($downloadedPackage->extractedSourcePath . '/../..'); + } + + if ( + in_array($downloadedPackage->package->name(), [ + 'php/pdo', + 'php/pdo_mysql', + 'php/pdo_pgsql', + 'php/pdo_sqlite', + ]) + ) { + $makeCommand[] = 'RE2C=' . self::findRe2c(); + } + + return $makeCommand; + } } diff --git a/src/Installing/InstallForPhpProject/FindMatchingPackages.php b/src/Installing/InstallForPhpProject/FindMatchingPackages.php index 8886ad4d..46239baf 100644 --- a/src/Installing/InstallForPhpProject/FindMatchingPackages.php +++ b/src/Installing/InstallForPhpProject/FindMatchingPackages.php @@ -8,6 +8,7 @@ use Composer\Repository\RepositoryInterface; use OutOfRangeException; +use function array_key_exists; use function array_merge; use function count; use function usort; @@ -33,7 +34,8 @@ public function for(Composer $pieComposer, string $searchTerm): array } usort($matches, static function (array $a, array $b): int { - return $b['downloads'] <=> $a['downloads']; + return (array_key_exists('downloads', $b) ? $b['downloads'] : 0) + <=> (array_key_exists('downloads', $a) ? $a['downloads'] : 0); }); return $matches; From c0c72a33513cc5ab04ae240876a39c147a5bc9e2 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Tue, 22 Jul 2025 21:26:51 +0100 Subject: [PATCH 12/19] Added PhpBinaryPath#buildProvider to fetch build provider, if set --- src/Platform/TargetPhp/PhpBinaryPath.php | 19 ++++++++++++++++ .../Platform/TargetPhp/PhpBinaryPathTest.php | 22 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/Platform/TargetPhp/PhpBinaryPath.php b/src/Platform/TargetPhp/PhpBinaryPath.php index dcd5ee9b..d5ad10d6 100644 --- a/src/Platform/TargetPhp/PhpBinaryPath.php +++ b/src/Platform/TargetPhp/PhpBinaryPath.php @@ -175,6 +175,25 @@ public function loadedIniConfigurationFile(): string|null return null; } + /** @return non-empty-string|null */ + public function buildProvider(): string|null + { + /** + * Newer versions of PHP will have a `PHP_BUILD_PROVIDER` constant + * defined - {@link https://github.com/php/php-src/pull/19157} + */ + if ( + preg_match('/Build Provider([ =>\t]*)(.*)/', $this->phpinfo(), $m) + && array_key_exists(2, $m) + && $m[2] !== '' + && $m[2] !== '(none)' + ) { + return $m[2]; + } + + return null; + } + /** * Returns a map where the key is the name of the extension and the value is the version ('0' if not defined) * diff --git a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php index 6b8d8c92..e32c6abb 100644 --- a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php +++ b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php @@ -342,4 +342,26 @@ public function testAssertExtensionFailsWhenNotLoaded(): void 'hopefully_this_extension_name_is_not_real_otherwise_this_test_will_fail', )); } + + public function testBuildProviderWhenConfigured(): void + { + $phpBinary = $this->createPartialMock(PhpBinaryPath::class, ['phpinfo']); + + $phpBinary->expects(self::once()) + ->method('phpinfo') + ->willReturn('Build Provider => My build provider'); + + self::assertSame('My build provider', $phpBinary->buildProvider()); + } + + public function testBuildProviderNullWhenNotConfigured(): void + { + $phpBinary = $this->createPartialMock(PhpBinaryPath::class, ['phpinfo']); + + $phpBinary->expects(self::once()) + ->method('phpinfo') + ->willReturn(''); + + self::assertNull($phpBinary->buildProvider()); + } } From f24783859d3670f45c358a3d62be9e02ce7eded3 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Mon, 28 Jul 2025 19:49:42 +0100 Subject: [PATCH 13/19] Check buildProvider when installing for bundled PHP extensions --- src/Command/BuildCommand.php | 6 +++ src/Command/DownloadCommand.php | 6 +++ src/Command/InfoCommand.php | 6 +++ src/Command/InstallCommand.php | 6 +++ .../BundledPhpExtensionRefusal.php | 27 +++++++++++ src/DependencyResolver/Package.php | 5 ++ .../ResolveDependencyWithComposer.php | 48 +++++++++++++++++++ .../ResolveDependencyWithComposerTest.php | 9 ++++ 8 files changed, 113 insertions(+) create mode 100644 src/DependencyResolver/BundledPhpExtensionRefusal.php diff --git a/src/Command/BuildCommand.php b/src/Command/BuildCommand.php index 09e0b668..0fe3deb2 100644 --- a/src/Command/BuildCommand.php +++ b/src/Command/BuildCommand.php @@ -9,6 +9,7 @@ use Php\Pie\ComposerIntegration\PieComposerFactory; use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; +use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal; use Php\Pie\DependencyResolver\DependencyResolver; use Php\Pie\DependencyResolver\InvalidPackageName; use Php\Pie\DependencyResolver\UnableToResolveRequirement; @@ -88,6 +89,11 @@ public function execute(InputInterface $input, OutputInterface $output): int $targetPlatform, $this->container, ); + } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) { + $output->writeln(''); + $output->writeln('' . $bundledPhpExtensionRefusal->getMessage() . ''); + + return self::INVALID; } $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix())); diff --git a/src/Command/DownloadCommand.php b/src/Command/DownloadCommand.php index fc659236..64d0f074 100644 --- a/src/Command/DownloadCommand.php +++ b/src/Command/DownloadCommand.php @@ -9,6 +9,7 @@ use Php\Pie\ComposerIntegration\PieComposerFactory; use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; +use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal; use Php\Pie\DependencyResolver\DependencyResolver; use Php\Pie\DependencyResolver\InvalidPackageName; use Php\Pie\DependencyResolver\UnableToResolveRequirement; @@ -90,6 +91,11 @@ public function execute(InputInterface $input, OutputInterface $output): int $targetPlatform, $this->container, ); + } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) { + $output->writeln(''); + $output->writeln('' . $bundledPhpExtensionRefusal->getMessage() . ''); + + return self::INVALID; } $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix())); diff --git a/src/Command/InfoCommand.php b/src/Command/InfoCommand.php index fa3d6593..d31c9a36 100644 --- a/src/Command/InfoCommand.php +++ b/src/Command/InfoCommand.php @@ -7,6 +7,7 @@ use Php\Pie\ComposerIntegration\PieComposerFactory; use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; +use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal; use Php\Pie\DependencyResolver\DependencyResolver; use Php\Pie\DependencyResolver\InvalidPackageName; use Php\Pie\DependencyResolver\UnableToResolveRequirement; @@ -87,6 +88,11 @@ public function execute(InputInterface $input, OutputInterface $output): int $targetPlatform, $this->container, ); + } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) { + $output->writeln(''); + $output->writeln('' . $bundledPhpExtensionRefusal->getMessage() . ''); + + return self::INVALID; } $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix())); diff --git a/src/Command/InstallCommand.php b/src/Command/InstallCommand.php index a0a3359d..b78632ed 100644 --- a/src/Command/InstallCommand.php +++ b/src/Command/InstallCommand.php @@ -9,6 +9,7 @@ use Php\Pie\ComposerIntegration\PieComposerFactory; use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\PieOperation; +use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal; use Php\Pie\DependencyResolver\DependencyResolver; use Php\Pie\DependencyResolver\InvalidPackageName; use Php\Pie\DependencyResolver\UnableToResolveRequirement; @@ -103,6 +104,11 @@ public function execute(InputInterface $input, OutputInterface $output): int $targetPlatform, $this->container, ); + } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) { + $output->writeln(''); + $output->writeln('' . $bundledPhpExtensionRefusal->getMessage() . ''); + + return self::INVALID; } $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix())); diff --git a/src/DependencyResolver/BundledPhpExtensionRefusal.php b/src/DependencyResolver/BundledPhpExtensionRefusal.php new file mode 100644 index 00000000..e780d109 --- /dev/null +++ b/src/DependencyResolver/BundledPhpExtensionRefusal.php @@ -0,0 +1,27 @@ +name(), + PHP_EOL, + PHP_EOL, + PHP_EOL, + PHP_EOL, + $package->name(), + )); + } +} diff --git a/src/DependencyResolver/Package.php b/src/DependencyResolver/Package.php index 2e4d31e8..692a2009 100644 --- a/src/DependencyResolver/Package.php +++ b/src/DependencyResolver/Package.php @@ -163,6 +163,11 @@ public function extensionName(): ExtensionName return $this->extensionName; } + public function isBundledPhpExtension(): bool + { + return str_starts_with($this->name(), 'php/'); + } + public function name(): string { return $this->name; diff --git a/src/DependencyResolver/ResolveDependencyWithComposer.php b/src/DependencyResolver/ResolveDependencyWithComposer.php index 9e4fe603..2eb46309 100644 --- a/src/DependencyResolver/ResolveDependencyWithComposer.php +++ b/src/DependencyResolver/ResolveDependencyWithComposer.php @@ -12,14 +12,17 @@ use Php\Pie\ExtensionType; use Php\Pie\Platform\TargetPlatform; use Php\Pie\Platform\ThreadSafetyMode; +use Symfony\Component\Console\Output\OutputInterface; use function in_array; use function preg_match; +use function sprintf; /** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */ final class ResolveDependencyWithComposer implements DependencyResolver { public function __construct( + private readonly OutputInterface $output, private readonly QuieterConsoleIO $arrayCollectionIo, ) { } @@ -62,6 +65,7 @@ public function __invoke( $piePackage = Package::fromComposerCompletePackage($package); + $this->assertBuildProviderProvidersBundledExtensions($targetPlatform, $piePackage, $forceInstallPackageVersion); $this->assertCompatibleOsFamily($targetPlatform, $piePackage); $this->assertCompatibleThreadSafetyMode($targetPlatform->threadSafety, $piePackage); @@ -95,4 +99,48 @@ private function assertCompatibleOsFamily(TargetPlatform $targetPlatform, Packag ); } } + + private function assertBuildProviderProvidersBundledExtensions(TargetPlatform $targetPlatform, Package $piePackage, bool $forceInstallPackageVersion): void + { + if (! $piePackage->isBundledPhpExtension()) { + return; + } + + $buildProvider = $targetPlatform->phpBinaryPath->buildProvider(); + $identifiedBuildProvider = false; + $note = 'Note: '; + + if ($buildProvider === 'https://github.com/docker-library/php') { + $identifiedBuildProvider = true; + $this->output->writeln(sprintf( + '%sYou should probably use "docker-php-ext-install %s" instead', + $note, + $piePackage->extensionName()->name(), + )); + } + + if ($buildProvider === 'Debian') { + $identifiedBuildProvider = true; + $this->output->writeln(sprintf( + '%sYou should probably use "apt install php%s-%s" or "apt install php-%s" (or similar) instead', + $note, + $targetPlatform->phpBinaryPath->majorMinorVersion(), + $piePackage->extensionName()->name(), + $piePackage->extensionName()->name(), + )); + } + + if ($buildProvider === 'Remi\'s RPM repository #StandWithUkraine') { + $identifiedBuildProvider = true; + $this->output->writeln(sprintf( + '%sYou should probably use "dnf install php-%s" instead', + $note, + $piePackage->extensionName()->name(), + )); + } + + if ($identifiedBuildProvider && ! $forceInstallPackageVersion) { + throw BundledPhpExtensionRefusal::forPackage($piePackage); + } + } } diff --git a/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php b/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php index 18680bea..a8eb2a4c 100644 --- a/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php +++ b/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php @@ -30,6 +30,7 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Output\OutputInterface; #[CoversClass(ResolveDependencyWithComposer::class)] final class ResolveDependencyWithComposerTest extends TestCase @@ -80,6 +81,7 @@ public function testPackageThatCanBeResolved(): void ); $package = (new ResolveDependencyWithComposer( + $this->createMock(OutputInterface::class), $this->createMock(QuieterConsoleIO::class), ))($this->composer, $targetPlatform, new RequestedPackageAndVersion('asgrim/example-pie-extension', '^1.0'), false); @@ -123,6 +125,7 @@ public function testPackageThatCannotBeResolvedThrowsException(array $platformOv $this->expectException(UnableToResolveRequirement::class); (new ResolveDependencyWithComposer( + $this->createMock(OutputInterface::class), $this->createMock(QuieterConsoleIO::class), ))( $this->composer, @@ -161,6 +164,7 @@ public function testUnresolvedPackageCanBeInstalledWithForceOption(array $platfo $this->expectException(UnableToResolveRequirement::class); $package = (new ResolveDependencyWithComposer( + $this->createMock(OutputInterface::class), $this->createMock(QuieterConsoleIO::class), ))( $this->composer, @@ -212,6 +216,7 @@ public function testZtsOnlyPackageCannotBeInstalledOnNtsSystem(): void $this->expectException(IncompatibleThreadSafetyMode::class); $this->expectExceptionMessage('This extension does not support being installed on a non-Thread Safe PHP installation'); (new ResolveDependencyWithComposer( + $this->createMock(OutputInterface::class), $this->createMock(QuieterConsoleIO::class), ))( $this->composer, @@ -260,6 +265,7 @@ public function testNtsOnlyPackageCannotBeInstalledOnZtsSystem(): void $this->expectException(IncompatibleThreadSafetyMode::class); $this->expectExceptionMessage('This extension does not support being installed on a Thread Safe PHP installation'); (new ResolveDependencyWithComposer( + $this->createMock(OutputInterface::class), $this->createMock(QuieterConsoleIO::class), ))( $this->composer, @@ -308,6 +314,7 @@ public function testExtensionCanOnlyBeInstalledIfOsFamilyIsCompatible(): void $this->expectException(IncompatibleOperatingSystemFamily::class); $this->expectExceptionMessage('This extension does not support the "linux" operating system family. It is compatible with the following families: "solaris", "darwin"'); (new ResolveDependencyWithComposer( + $this->createMock(OutputInterface::class), $this->createMock(QuieterConsoleIO::class), ))( $this->composer, @@ -356,6 +363,7 @@ public function testExtensionCanOnlyBeInstalledIfOsFamilyIsNotInCompatible(): vo $this->expectException(IncompatibleOperatingSystemFamily::class); $this->expectExceptionMessage('This extension does not support the "darwin" operating system family. It is incompatible with the following families: "darwin", "solaris".'); (new ResolveDependencyWithComposer( + $this->createMock(OutputInterface::class), $this->createMock(QuieterConsoleIO::class), ))( $this->composer, @@ -392,6 +400,7 @@ public function testPackageThatCanBeResolvedWithReplaceConflict(): void ); $package = (new ResolveDependencyWithComposer( + $this->createMock(OutputInterface::class), $this->createMock(QuieterConsoleIO::class), ))($this->composer, $targetPlatform, new RequestedPackageAndVersion('asgrim/example-pie-extension', '^1.0'), false); From 8c4aea2f842f37a49ebe68b2f9adcfbd12339cd6 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Mon, 28 Jul 2025 20:21:19 +0100 Subject: [PATCH 14/19] Enabled PHP 8.5 for bundled PHP exts --- .github/workflows/continuous-integration.yml | 2 +- src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1ce6be11..6b8ee5bb 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -56,7 +56,7 @@ jobs: - '8.2' - '8.3' - '8.4' - #- '8.5' # @todo enable + - '8.5' steps: - name: Install platform dependencies run: | diff --git a/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php b/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php index 68db5e87..4c7ef9f4 100644 --- a/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php +++ b/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php @@ -100,7 +100,6 @@ private function assertCertificateSignedByTrustedRoot(Attestation $attestation): { $attestationCertificateInfo = openssl_x509_parse($attestation->certificate); - // @todo process in place to make sure this gets updated frequently enough: gh attestation trusted-root > resources/trusted-root.jsonl $trustedRootJsonLines = explode("\n", trim(file_get_contents($this->trustedRootFilePath))); /** From a7548e68083682bc5e502d51534a7d65ea9fb622 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Mon, 28 Jul 2025 20:28:26 +0100 Subject: [PATCH 15/19] Update composer/composer to 2.8.10 to overcome composer/composer#12471 --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index b4c2c1e6..d002bcfe 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ ], "require": { "php": "8.1.*||8.2.*||8.3.*||8.4.*||8.5.*", - "composer/composer": "^2.8.9", + "composer/composer": "^2.8.10", "composer/pcre": "^3.3.2", "composer/semver": "^3.4.3", "fidry/cpu-core-counter": "^1.2", diff --git a/composer.lock b/composer.lock index 7a4b00ad..3663e94c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3006380a459a925f6f7ab8c60088cf36", + "content-hash": "22b9d91ed0b0ad3f3ccd5a5afd5b1732", "packages": [ { "name": "composer/ca-bundle", @@ -157,16 +157,16 @@ }, { "name": "composer/composer", - "version": "2.8.9", + "version": "2.8.10", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d" + "reference": "53834f587d7ab2527eb237459d7b94d1fb9d4c5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d", - "reference": "b4e6bff2db7ce756ddb77ecee958a0f41f42bd9d", + "url": "https://api.github.com/repos/composer/composer/zipball/53834f587d7ab2527eb237459d7b94d1fb9d4c5a", + "reference": "53834f587d7ab2527eb237459d7b94d1fb9d4c5a", "shasum": "" }, "require": { @@ -251,7 +251,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.8.9" + "source": "https://github.com/composer/composer/tree/2.8.10" }, "funding": [ { @@ -267,7 +267,7 @@ "type": "tidelift" } ], - "time": "2025-05-13T12:01:37+00:00" + "time": "2025-07-10T17:08:33+00:00" }, { "name": "composer/metadata-minifier", From 2962d921bec1223c7b6df96404e9be28edfc34c1 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Mon, 28 Jul 2025 21:29:57 +0100 Subject: [PATCH 16/19] Use PHP_VERSION constant --- src/Platform/TargetPhp/PhpBinaryPath.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Platform/TargetPhp/PhpBinaryPath.php b/src/Platform/TargetPhp/PhpBinaryPath.php index d5ad10d6..a878046f 100644 --- a/src/Platform/TargetPhp/PhpBinaryPath.php +++ b/src/Platform/TargetPhp/PhpBinaryPath.php @@ -269,7 +269,7 @@ public function version(): string $phpVersion = self::cleanWarningAndDeprecationsFromOutput(Process::run([ $this->phpBinaryPath, '-r', - 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "." . PHP_RELEASE_VERSION;', + 'echo PHP_VERSION;', ])); Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version'); From 19fcc8a3ff5bdc0d3b34c683502f54d7833f3de8 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 30 Jul 2025 20:03:21 +0100 Subject: [PATCH 17/19] Ensure we are using 8.5.0alpha3 --- .github/workflows/continuous-integration.yml | 2 +- test/install-bundled-php-exts.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 6b8ee5bb..c0c30014 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -56,7 +56,7 @@ jobs: - '8.2' - '8.3' - '8.4' - - '8.5' + - '8.5.0alpha3' steps: - name: Install platform dependencies run: | diff --git a/test/install-bundled-php-exts.php b/test/install-bundled-php-exts.php index 4de0fc72..d88a7e79 100644 --- a/test/install-bundled-php-exts.php +++ b/test/install-bundled-php-exts.php @@ -30,7 +30,7 @@ 'sudo', 'bin/pie', 'install', - $packageName, + $packageName . ':@dev', '--with-php-config=' . $phpBinaryPath->phpConfigPath(), ]; From a955ffde58ce7e9eee5a44702f0d75c0be5295d7 Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Wed, 30 Jul 2025 20:32:29 +0100 Subject: [PATCH 18/19] Install PHP from src --- .github/workflows/continuous-integration.yml | 45 +++++++++++++------ .../BundledPhpExtensionsRepository.php | 26 ++++++++++- test/install-bundled-php-exts.php | 9 +++- .../Platform/TargetPhp/PhpBinaryPathTest.php | 4 +- 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index c0c30014..74d0fd4f 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -52,12 +52,16 @@ jobs: operating-system: - ubuntu-latest php-versions: - - '8.1' - - '8.2' - - '8.3' - - '8.4' - - '8.5.0alpha3' + - '8.1.33' + - '8.2.29' + - '8.3.23' + - '8.4.10' + - '8.5.0alpha2' steps: + - name: "Purge built-in PHP version" + run: | + echo "libmemcached11 php* hhvm libhashkit2" | xargs -n 1 sudo apt-get purge --assume-yes || true + sudo apt-add-repository --remove ppa:ondrej/php -y - name: Install platform dependencies run: | sudo apt-get update @@ -89,18 +93,33 @@ jobs: libpq-dev \ libsqlite3-dev \ libzip-dev - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - extensions: none, mbstring, libxml - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Set php-src download URL" + run: echo "php_src_download_url=https://www.php.net/distributions/php-${{ matrix.php-versions }}.tar.gz" >> $GITHUB_ENV + - name: "Set php-src download URL (8.5 pre-release)" + if: ${{ startsWith(matrix.php-versions, '8.5.') }} + run: echo "php_src_download_url=https://downloads.php.net/~edorian/php-${{ matrix.php-versions }}.tar.gz" >> $GITHUB_ENV + - name: "Install PHP ${{ matrix.php-versions }}" + run: | + mkdir -p /tmp/php + mkdir -p /tmp/php.ini.d + cd /tmp/php + echo "Downloading release from ${{ env.php_src_download_url }} ..." + wget -O php.tgz ${{ env.php_src_download_url }} + tar zxf php.tgz + rm php.tgz + ls -l + cd * + ls -l + ./buildconf --force + ./configure --disable-dom --disable-xml --disable-xmlreader --disable-xmlwriter --disable-json --with-openssl --with-config-file-scan-dir=/tmp/php.ini.d + make -j$(nproc) + sudo make install + cd $GITHUB_WORKSPACE - uses: actions/checkout@v4 - name: Composer Install run: composer install --ignore-platform-reqs - name: Run bundled PHP install test - run: sudo php test/install-bundled-php-exts.php + run: sudo php test/install-bundled-php-exts.php php-config env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/ComposerIntegration/BundledPhpExtensionsRepository.php b/src/ComposerIntegration/BundledPhpExtensionsRepository.php index a4a51961..3c6a9f20 100644 --- a/src/ComposerIntegration/BundledPhpExtensionsRepository.php +++ b/src/ComposerIntegration/BundledPhpExtensionsRepository.php @@ -21,6 +21,8 @@ use function array_key_exists; use function array_keys; use function array_map; +use function count; +use function implode; use function in_array; use function realpath; use function sprintf; @@ -266,8 +268,28 @@ private static function findRe2c(): string */ public static function augmentMakeCommandForPhpBundledExtensions(array $makeCommand, DownloadedPackage $downloadedPackage): array { - if ($downloadedPackage->package->name() === 'php/xmlreader') { - $makeCommand[] = 'EXTRA_CFLAGS=-I' . realpath($downloadedPackage->extractedSourcePath . '/../..'); + $extraCflags = []; + if ( + in_array($downloadedPackage->package->name(), [ + 'php/xmlreader', + 'php/dom', + ]) + ) { + $path = (string) realpath($downloadedPackage->extractedSourcePath . '/../..'); + if ($path !== '') { + $extraCflags[] = '-I' . $path; + } + } + + if ($downloadedPackage->package->name() === 'php/dom') { + $path = (string) realpath($downloadedPackage->extractedSourcePath . '/../../ext/lexbor'); + if ($path !== '') { + $extraCflags[] = '-I' . $path; + } + } + + if (count($extraCflags)) { + $makeCommand[] = 'EXTRA_CFLAGS=' . implode(' ', $extraCflags); } if ( diff --git a/test/install-bundled-php-exts.php b/test/install-bundled-php-exts.php index d88a7e79..b37b64d2 100644 --- a/test/install-bundled-php-exts.php +++ b/test/install-bundled-php-exts.php @@ -10,7 +10,14 @@ require __DIR__ . '/../vendor/autoload.php'; -$phpBinaryPath = PhpBinaryPath::fromCurrentProcess(); +$phpConfigPath = $argv[1] ?? ''; + +if ($phpConfigPath === '') { + echo 'Usage: ' . __FILE__ . ' ' . PHP_EOL; + exit(1); +} + +$phpBinaryPath = PhpBinaryPath::fromPhpConfigExecutable($phpConfigPath); $packageNames = array_map( static fn (PackageInterface $package): string => $package->getName(), diff --git a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php index e32c6abb..4e97f9cb 100644 --- a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php +++ b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php @@ -45,7 +45,7 @@ use const PHP_MAJOR_VERSION; use const PHP_MINOR_VERSION; use const PHP_OS_FAMILY; -use const PHP_RELEASE_VERSION; +use const PHP_VERSION; #[CoversClass(PhpBinaryPath::class)] final class PhpBinaryPathTest extends TestCase @@ -101,7 +101,7 @@ public function testVersionFromCurrentProcess(): void $phpBinary = PhpBinaryPath::fromCurrentProcess(); self::assertSame( - sprintf('%s.%s.%s', PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION), + PHP_VERSION, $phpBinary->version(), ); self::assertNull($phpBinary->phpConfigPath()); From b4e35dd0ef7c6bf4d482047f96361a4391dfb00c Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 31 Jul 2025 21:02:57 +0100 Subject: [PATCH 19/19] Repository test for bundled PHP extension --- .../BundledPhpExtensionsRepositoryTest.php | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 test/unit/ComposerIntegration/BundledPhpExtensionsRepositoryTest.php diff --git a/test/unit/ComposerIntegration/BundledPhpExtensionsRepositoryTest.php b/test/unit/ComposerIntegration/BundledPhpExtensionsRepositoryTest.php new file mode 100644 index 00000000..d85678fb --- /dev/null +++ b/test/unit/ComposerIntegration/BundledPhpExtensionsRepositoryTest.php @@ -0,0 +1,203 @@ + */ + public static function bundledRepositoryPackageNames(): array + { + return [ + ['php/bcmath'], + ['php/bz2'], + ['php/calendar'], + ['php/ctype'], + ['php/curl'], + ['php/dba'], + ['php/dom'], + ['php/enchant'], + ['php/exif'], + ['php/ffi'], + ['php/gettext'], + ['php/gmp'], + ['php/iconv'], + ['php/intl'], + ['php/ldap'], + ['php/mbstring'], + ['php/mysqlnd'], + ['php/mysqli'], + ['php/opcache'], + ['php/pcntl'], + ['php/pdo'], + ['php/pdo_mysql'], + ['php/pdo_pgsql'], + ['php/pdo_sqlite'], + ['php/pgsql'], + ['php/posix'], + ['php/readline'], + ['php/session'], + ['php/shmop'], + ['php/simplexml'], + ['php/snmp'], + ['php/soap'], + ['php/sockets'], + ['php/sodium'], + ['php/sqlite3'], + ['php/sysvmsg'], + ['php/sysvsem'], + ['php/sysvshm'], + ['php/tidy'], + ['php/xml'], + ['php/xmlreader'], + ['php/xmlwriter'], + ['php/xsl'], + ['php/zip'], + ['php/zlib'], + ]; + } + + #[DataProvider('bundledRepositoryPackageNames')] + public function testBundledRepository(string $packageName): void + { + $phpBinary = $this->createMock(PhpBinaryPath::class); + $phpBinary->expects(self::once()) + ->method('version') + ->willReturn('8.1.0'); + + $repository = BundledPhpExtensionsRepository::forTargetPlatform( + new TargetPlatform( + OperatingSystem::NonWindows, + OperatingSystemFamily::Linux, + $phpBinary, + Architecture::x86_64, + ThreadSafetyMode::NonThreadSafe, + 1, + null, + ), + ); + + $package = $repository->findPackage($packageName, '8.1.0'); + self::assertNotNull($package); + self::assertSame($packageName, $package->getName()); + self::assertSame('8.1.0', $package->getPrettyVersion()); + } + + public function testMakeCommandForXmlReader(): void + { + $phpPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_test_bundled_', true); + $xmlReaderPath = $phpPath . DIRECTORY_SEPARATOR . 'ext' . DIRECTORY_SEPARATOR . 'xmlreader'; + mkdir($xmlReaderPath, recursive: true); + + self::assertEquals( + ['EXTRA_CFLAGS=-I' . realpath($phpPath)], + BundledPhpExtensionsRepository::augmentMakeCommandForPhpBundledExtensions( + [], + DownloadedPackage::fromPackageAndExtractedPath( + new Package( + $this->createMock(CompletePackageInterface::class), + ExtensionType::PhpModule, + ExtensionName::normaliseFromString('xmlreader'), + 'php/xmlreader', + '1.2.3', + null, + ), + realpath($xmlReaderPath), + ), + ), + ); + } + + public function testMakeCommandForDom(): void + { + $phpPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_test_bundled_', true); + $domPath = $phpPath . DIRECTORY_SEPARATOR . 'ext' . DIRECTORY_SEPARATOR . 'dom'; + $lexborPath = $phpPath . DIRECTORY_SEPARATOR . 'ext' . DIRECTORY_SEPARATOR . 'lexbor'; + mkdir($domPath, recursive: true); + mkdir($lexborPath, recursive: true); + + self::assertEquals( + ['EXTRA_CFLAGS=-I' . realpath($phpPath) . ' -I' . realpath($lexborPath)], + BundledPhpExtensionsRepository::augmentMakeCommandForPhpBundledExtensions( + [], + DownloadedPackage::fromPackageAndExtractedPath( + new Package( + $this->createMock(CompletePackageInterface::class), + ExtensionType::PhpModule, + ExtensionName::normaliseFromString('dom'), + 'php/dom', + '1.2.3', + null, + ), + realpath($domPath), + ), + ), + ); + } + + /** @return list */ + public static function dependantsOnRe2c(): array + { + return [ + ['pdo'], + ['pdo_mysql'], + ['pdo_pgsql'], + ['pdo_sqlite'], + ]; + } + + #[DataProvider('dependantsOnRe2c')] + public function testMakeCommandForRe2cDependants(string $extensionName): void + { + try { + $re2cPath = Process::run(['which', 're2c']); + } catch (ProcessFailedException) { + self::markTestSkipped('re2c not installed'); + } + + self::assertEquals( + ['RE2C=' . $re2cPath], + BundledPhpExtensionsRepository::augmentMakeCommandForPhpBundledExtensions( + [], + DownloadedPackage::fromPackageAndExtractedPath( + new Package( + $this->createMock(CompletePackageInterface::class), + ExtensionType::PhpModule, + ExtensionName::normaliseFromString($extensionName), + 'php/' . $extensionName, + '1.2.3', + null, + ), + '/path/to/ext', + ), + ), + ); + } +}