From 07fa4255d68d9ee1a34c3c8d9e210925562b8327 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Mar 2024 22:04:58 +0100 Subject: [PATCH] Add support for php extension packages (#11795) * Update schema * Validate php-ext is only set for php-ext or php-ext-zend packages * Make sure the pool builder excludes php-ext/php-ext-zend --- doc/04-schema.md | 3 ++ res/composer-schema.json | 35 +++++++++++++++++++ .../DependencyResolver/PoolBuilder.php | 14 ++++++++ src/Composer/Package/AliasPackage.php | 5 +++ src/Composer/Package/Loader/ArrayLoader.php | 4 +++ .../Package/Loader/ValidatingArrayLoader.php | 6 ++++ src/Composer/Package/Package.php | 20 +++++++++++ src/Composer/Package/PackageInterface.php | 7 ++++ src/Composer/Repository/RepositorySet.php | 1 + ...ckage-and-its-provider-skips-original.test | 10 +++--- 10 files changed, 100 insertions(+), 5 deletions(-) diff --git a/doc/04-schema.md b/doc/04-schema.md index e36ec2111..c4dca5027 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -102,6 +102,9 @@ Out of the box, Composer supports four types: - **composer-plugin:** A package of type `composer-plugin` may provide an installer for other packages that have a custom type. Read more in the [dedicated article](articles/custom-installers.md). +- **php-ext** and **php-ext-zend**: These names are reserved for PHP extension + packages which are written in C. Do not use these types for packages written + in PHP. Only use a custom type if you need custom logic during installation. It is recommended to omit this field and have it default to `library`. diff --git a/res/composer-schema.json b/res/composer-schema.json index a77becb4b..dfbd8beb6 100644 --- a/res/composer-schema.json +++ b/res/composer-schema.json @@ -288,6 +288,41 @@ } } }, + "php-ext": { + "type": "object", + "description": "Settings for PHP extension packages.", + "properties": { + "priority": { + "type": "integer", + "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.", + "minimum": 10, + "maximum": 99, + "example": 80, + "default": 80 + }, + "configure-options": { + "type": "array", + "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string", + "description": "The name of the flag, this would typically be prefixed with `--`, for example, the value 'the-flag' would be passed as `./configure --the-flag`.", + "example": "without-xdebug-compression", + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$" + }, + "description": { + "type": "string", + "description": "The description of what the flag does or means.", + "example": "Disable compression through zlib" + } + } + } + } + } + }, "config": { "type": "object", "description": "Composer options.", diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index d84d40906..82b89cb3a 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -106,6 +106,8 @@ class PoolBuilder private $updateAllowList = []; /** @var array> */ private $skippedLoad = []; + /** @var list */ + private $ignoredTypes = []; /** * If provided, only these package names are loaded @@ -170,6 +172,14 @@ class PoolBuilder $this->temporaryConstraints = $temporaryConstraints; } + /** + * @param list $types + */ + public function setIgnoredTypes(array $types): void + { + $this->ignoredTypes = $types; + } + /** * @param RepositoryInterface[] $repositories */ @@ -416,6 +426,10 @@ class PoolBuilder */ private function loadPackage(Request $request, array $repositories, BasePackage $package, bool $propagateUpdate): void { + if (in_array($package->getType(), $this->ignoredTypes, true)) { + return; + } + $index = $this->indexCounter++; $this->packages[$index] = $package; diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 75d3158cc..932ea3658 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -351,6 +351,11 @@ class AliasPackage extends BasePackage return $this->aliasOf->getIncludePaths(); } + public function getPhpExt(): ?array + { + return $this->aliasOf->getPhpExt(); + } + public function getReleaseDate(): ?\DateTimeInterface { return $this->aliasOf->getReleaseDate(); diff --git a/src/Composer/Package/Loader/ArrayLoader.php b/src/Composer/Package/Loader/ArrayLoader.php index daa540bd8..887f2913b 100644 --- a/src/Composer/Package/Loader/ArrayLoader.php +++ b/src/Composer/Package/Loader/ArrayLoader.php @@ -228,6 +228,10 @@ class ArrayLoader implements LoaderInterface $package->setIncludePaths($config['include-path']); } + if (isset($config['php-ext'])) { + $package->setPhpExt($config['php-ext']); + } + if (!empty($config['time'])) { $time = Preg::isMatch('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time']; diff --git a/src/Composer/Package/Loader/ValidatingArrayLoader.php b/src/Composer/Package/Loader/ValidatingArrayLoader.php index a94448a61..6d1388e23 100644 --- a/src/Composer/Package/Loader/ValidatingArrayLoader.php +++ b/src/Composer/Package/Loader/ValidatingArrayLoader.php @@ -247,6 +247,12 @@ class ValidatingArrayLoader implements LoaderInterface } } + $this->validateArray('php-ext'); + if (isset($this->config['php-ext']) && !in_array($this->config['type'] ?? '', ['php-ext', 'php-ext-zend'], true)) { + $this->errors[] = 'php-ext can only be set by packages of type "php-ext" or "php-ext-zend" which must be C extensions'; + unset($this->config['php-ext']); + } + $unboundConstraint = new Constraint('=', '10000000-dev'); foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) { diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index 295bbd249..e1b5755f1 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -98,6 +98,8 @@ class Package extends BasePackage protected $isDefaultBranch = false; /** @var mixed[] */ protected $transportOptions = []; + /** @var array{priority?: int, config?: array}|null */ + protected $phpExt = null; /** * Creates a new in memory package. @@ -590,6 +592,24 @@ class Package extends BasePackage return $this->includePaths; } + /** + * Sets the list of paths added to PHP's include path. + * + * @param array{priority?: int, config?: array}|null $phpExt List of directories. + */ + public function setPhpExt(?array $phpExt): void + { + $this->phpExt = $phpExt; + } + + /** + * @inheritDoc + */ + public function getPhpExt(): ?array + { + return $this->phpExt; + } + /** * Sets the notification URL */ diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 04e2f05f8..a6af933b5 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -323,6 +323,13 @@ interface PackageInterface */ public function getIncludePaths(): array; + /** + * Returns the settings for php extension packages + * + * @return array{priority?: int, config?: array}|null + */ + public function getPhpExt(): ?array; + /** * Stores a reference to the repository that owns the package */ diff --git a/src/Composer/Repository/RepositorySet.php b/src/Composer/Repository/RepositorySet.php index 48cf424a4..2a9a49b7d 100644 --- a/src/Composer/Repository/RepositorySet.php +++ b/src/Composer/Repository/RepositorySet.php @@ -314,6 +314,7 @@ class RepositorySet public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null): Pool { $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints); + $poolBuilder->setIgnoredTypes(['php-ext', 'php-ext-zend']); foreach ($this->repositories as $repo) { if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) { diff --git a/tests/Composer/Test/Fixtures/installer/install-package-and-its-provider-skips-original.test b/tests/Composer/Test/Fixtures/installer/install-package-and-its-provider-skips-original.test index 70a74be8d..4f7309e7a 100644 --- a/tests/Composer/Test/Fixtures/installer/install-package-and-its-provider-skips-original.test +++ b/tests/Composer/Test/Fixtures/installer/install-package-and-its-provider-skips-original.test @@ -6,17 +6,17 @@ Install package and it's replacer skips the original { "type": "package", "package": [ - { "name": "ext-foo", "version": "1.0.0" }, - { "name": "ext-foo/fork", "version": "0.5.0", "replace": { "ext-foo": "1.0.*" } } + { "name": "example/foo", "version": "1.0.0" }, + { "name": "example/foo-fork", "version": "0.5.0", "replace": { "example/foo": "1.0.*" } } ] } ], "require": { - "ext-foo": "1.0.0", - "ext-foo/fork": "0.5.*" + "example/foo": "1.0.0", + "example/foo-fork": "0.5.*" } } --RUN-- install --EXPECT-- -Installing ext-foo/fork (0.5.0) +Installing example/foo-fork (0.5.0)