mirror of
https://github.com/php/pie.git
synced 2026-03-23 23:12:17 +01:00
Merge pull request #526 from asgrim/435-prompt-to-install-missing-system-deps
435: prompt to install missing system deps
This commit is contained in:
33
README.md
33
README.md
@@ -2,11 +2,12 @@
|
||||
|
||||
## What is PIE?
|
||||
|
||||
PIE is a new installer for PHP extensions, intended to eventually replace PECL.
|
||||
It is distributed as a [PHAR](https://www.php.net/manual/en/intro.phar.php),
|
||||
just like Composer, and works in a similar way to Composer, but it installs PHP
|
||||
extensions (PHP Modules or Zend Extensions) to your PHP installation, rather
|
||||
than pulling PHP packages into your project or library.
|
||||
PIE is the official installer for PHP extensions, which replaces
|
||||
[PECL](https://pecl.php.net/) (which is now deprecated). PIE is distributed as a
|
||||
[PHAR](https://www.php.net/manual/en/intro.phar.php), just like Composer, and
|
||||
works in a similar way to Composer, but it installs PHP extensions (PHP Modules
|
||||
or Zend Extensions) to your PHP installation, rather than pulling PHP packages
|
||||
into your project or library.
|
||||
|
||||
# Using PIE - what do I need to get started?
|
||||
|
||||
@@ -15,12 +16,8 @@ than pulling PHP packages into your project or library.
|
||||
You will need PHP 8.1 or newer to run PIE, but PIE can install an extension to
|
||||
any other installed PHP version.
|
||||
|
||||
On Linux, you will need a build toolchain installed. On Debian/Ubuntu type
|
||||
systems, you could run something like:
|
||||
|
||||
```shell
|
||||
sudo apt install gcc make autoconf libtool bison re2c pkg-config php-dev
|
||||
```
|
||||
On Linux/OSX, if any build tools needed are missing, PIE will ask if you would
|
||||
like to automatically install them first (this is a new feature in 1.4.0).
|
||||
|
||||
On Windows, you do not need any build toolchain installed, since PHP extensions
|
||||
for Windows are distributed as pre-compiled packages containing the extension
|
||||
@@ -38,7 +35,9 @@ Further installation details can be found in the [usage](./docs/usage.md) docs.
|
||||
This documentation assumes you have moved `pie.phar` into your `$PATH`, e.g.
|
||||
`/usr/local/bin/pie` on non-Windows systems or created an alias in your shell RC file.
|
||||
|
||||
## Installing a single extension using PIE
|
||||
## Using PIE
|
||||
|
||||
### Installing a single extension using PIE
|
||||
|
||||
You can install an extension using the `install` command. For example, to
|
||||
install the `example_pie_extension` extension, you would run:
|
||||
@@ -57,7 +56,7 @@ You must now add "extension=example_pie_extension" to your php.ini
|
||||
$
|
||||
```
|
||||
|
||||
## Installing all extensions for a PHP project
|
||||
### Installing all extensions for a PHP project
|
||||
|
||||
When in your PHP project, you can install any missing top-level extensions:
|
||||
|
||||
@@ -87,6 +86,12 @@ The following packages may be suitable, which would you like to install:
|
||||
Finished checking extensions.
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> If you are running PIE in a non-interactive shell (for example, CI, a
|
||||
> container), pass the `--allow-non-interactive-project-install` flag to run
|
||||
> this command. It may still fail if more than one PIE package provides a
|
||||
> particular extension.
|
||||
|
||||
## Extensions that support PIE
|
||||
|
||||
A list of extensions that support PIE can be found on
|
||||
@@ -105,6 +110,6 @@ A list of extensions that support PIE can be found on
|
||||
If you are an extension maintainer wanting to add PIE support to your extension,
|
||||
please read [extension-maintainers](./docs/extension-maintainers.md).
|
||||
|
||||
## More documentation...
|
||||
# More documentation...
|
||||
|
||||
The full documentation for PIE can be found in [usage](./docs/usage.md) docs.
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"bnf/phpstan-psr-container": "^1.1",
|
||||
"doctrine/coding-standard": "^14.0.0",
|
||||
"phpstan/phpstan": "^2.1.38",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpstan/phpstan-webmozart-assert": "^2.0",
|
||||
"phpunit/phpunit": "^10.5.63"
|
||||
},
|
||||
|
||||
58
composer.lock
generated
58
composer.lock
generated
@@ -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": "6392877b0b53e6d18d3d156173dcbba3",
|
||||
"content-hash": "1138a5a4004fa55c3068062f3a2adc43",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
@@ -3362,6 +3362,62 @@
|
||||
],
|
||||
"time": "2026-01-30T17:12:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
"version": "2.0.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan-phpunit.git",
|
||||
"reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6ab598e1bc106e6827fd346ae4a12b4a5d634c32",
|
||||
"reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"phpstan/phpstan": "^2.1.32"
|
||||
},
|
||||
"conflict": {
|
||||
"phpunit/phpunit": "<7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nikic/php-parser": "^5",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0",
|
||||
"phpunit/phpunit": "^9.6"
|
||||
},
|
||||
"type": "phpstan-extension",
|
||||
"extra": {
|
||||
"phpstan": {
|
||||
"includes": [
|
||||
"extension.neon",
|
||||
"rules.neon"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PHPStan\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "PHPUnit extensions and rules for PHPStan",
|
||||
"keywords": [
|
||||
"static analysis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.16"
|
||||
},
|
||||
"time": "2026-02-14T09:05:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-webmozart-assert",
|
||||
"version": "2.0.0",
|
||||
|
||||
@@ -345,11 +345,16 @@ The list of accepted OS families: "windows", "bsd", "darwin", "solaris", "linux"
|
||||
|
||||
#### Extension dependencies
|
||||
|
||||
Extension authors may define some dependencies in `require`, but practically,
|
||||
Extension authors may define some dependencies in `require`, but typically,
|
||||
most extensions would not need to define dependencies, except for the PHP
|
||||
versions supported by the extension. Dependencies on other extensions may be
|
||||
defined, for example `ext-json`. However, dependencies on a regular PHP package
|
||||
(such as `monolog/monolog`) SHOULD NOT be specified in your `require` section.
|
||||
versions supported by the extension, and system libraries.
|
||||
|
||||
Dependencies on a regular PHP package (such as `monolog/monolog`) SHOULD NOT be
|
||||
specified in your extension's `require` section.
|
||||
|
||||
##### Dependencies on other extensions
|
||||
|
||||
Dependencies on other extensions may be defined, for example `ext-json`.
|
||||
|
||||
It is worth noting that if your extension does define a dependency on another
|
||||
dependency, and this is not available, someone installing your extension would
|
||||
@@ -360,6 +365,47 @@ Cannot use myvendor/myextension's latest version 1.2.3 as it requires
|
||||
ext-something * which is missing from your platform.
|
||||
```
|
||||
|
||||
##### System Library Dependencies
|
||||
|
||||
In PIE 1.4.0, the ability for extension authors to define system library
|
||||
dependencies was added, and in some cases, automatically install them.
|
||||
|
||||
The following libraries are supported at the moment. **If you would like to add
|
||||
a library, please [open a discussion](https://github.com/php/pie/discussions)
|
||||
in the first instance.** Don't just open a PR without discussing first please!
|
||||
|
||||
We are adding libraries and improving this feature over time. If the automatic
|
||||
install of a system dependency that is supported below in your package manager
|
||||
is NOT working, then please [report a bug](https://github.com/php/pie/issues).
|
||||
|
||||
| Library | Checked by PIE | Auto-installs in |
|
||||
|---------------|----------------|--------------------|
|
||||
| lib-curl | ✅ | apt, apk, dnf, yum |
|
||||
| lib-enchant | ✅ | ❌ |
|
||||
| lib-enchant-2 | ✅ | ❌ |
|
||||
| lib-sodium | ✅ | apt, apk, dnf, yum |
|
||||
| lib-ffi | ✅ | apt, apk, dnf, yum |
|
||||
| lib-xslt | ✅ | apt, apk, dnf, yum |
|
||||
| lib-zip | ✅ | apt, apk, dnf, yum |
|
||||
| lib-png | ✅ | ❌ |
|
||||
| lib-avif | ✅ | ❌ |
|
||||
| lib-webp | ✅ | ❌ |
|
||||
| lib-jpeg | ✅ | apt, apk, dnf, yum |
|
||||
| lib-xpm | ✅ | ❌ |
|
||||
| lib-freetype2 | ✅ | ❌ |
|
||||
| lib-gdlib | ✅ | ❌ |
|
||||
| lib-gmp | ✅ | ❌ |
|
||||
| lib-sasl | ✅ | ❌ |
|
||||
| lib-onig | ✅ | ❌ |
|
||||
| lib-odbc | ✅ | ❌ |
|
||||
| lib-capstone | ✅ | ❌ |
|
||||
| lib-pcre | ✅ | ❌ |
|
||||
| lib-edit | ✅ | ❌ |
|
||||
| lib-snmp | ✅ | ❌ |
|
||||
| lib-argon2 | ✅ | ❌ |
|
||||
| lib-uriparser | ✅ | ❌ |
|
||||
| lib-exslt | ✅ | ❌ |
|
||||
|
||||
#### Checking the extension will work
|
||||
|
||||
First up, you can use `composer validate` to check your `composer.json` is
|
||||
|
||||
@@ -281,6 +281,31 @@ pie install example/some-extension --with-some-library-name=/path/to/the/lib
|
||||
pie install example/some-extension --with-some-library-name=/path/to/the/lib --enable-some-functionality
|
||||
```
|
||||
|
||||
### Build tools check
|
||||
|
||||
PIE will attempt to check the presence of build tools (such as gcc, make, etc.)
|
||||
before running. If any are missing, an interactive prompt will ask if you would
|
||||
like to install the missing tools. If you are running in non-interactive mode
|
||||
(for example, in a CI pipeline, container build, etc), PIE will **not**
|
||||
install these tools automatically. If you would like to install the build tools
|
||||
in a non-interactive terminal, pass the `--auto-install-build-tools` and the
|
||||
prompt will be skipped.
|
||||
|
||||
To skip the build tools check entirely, pass the `--no-build-tools-check` flag.
|
||||
|
||||
### System library dependencies check
|
||||
|
||||
PIE will attempt to check the presence of system library dependencies before
|
||||
installing an extension. If any are missing, an interactive prompt will ask if
|
||||
you would like to install the missing tools. If you are running in
|
||||
non-interactive mode (for example, in a CI pipeline, container build, etc), PIE
|
||||
will **not** install these dependencies automatically. If you would like to
|
||||
install the system dependencies in a non-interactive terminal, pass the
|
||||
`--auto-install-system-dependencies` and the prompt will be skipped.
|
||||
|
||||
To skip the dependencies check entirely, pass the
|
||||
`--no-system-dependencies-check` flag.
|
||||
|
||||
### Configuring the INI file
|
||||
|
||||
PIE will automatically try to enable the extension by adding `extension=...` or
|
||||
|
||||
@@ -288,6 +288,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Platform.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(Composer\\Package\\BasePackage\)\: mixed\)\|null, Closure\(Composer\\Package\\CompletePackageInterface\)\: Php\\Pie\\DependencyResolver\\Package given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Platform/InstalledPiePackages.php
|
||||
|
||||
-
|
||||
message: '#^Call to function array_key_exists\(\) with 1 and array\{non\-falsy\-string, non\-empty\-string, non\-empty\-string\} will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
@@ -354,18 +360,6 @@ parameters:
|
||||
count: 1
|
||||
path: test/integration/Command/InstallCommandTest.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$originalClassName of method PHPUnit\\Framework\\TestCase\:\:createMock\(\) expects class\-string\<object\>, string given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: test/integration/Command/InstallExtensionsForProjectCommandTest.php
|
||||
|
||||
-
|
||||
message: '#^Unable to resolve the template type RealInstanceType in call to method PHPUnit\\Framework\\TestCase\:\:createMock\(\)$#'
|
||||
identifier: argument.templateType
|
||||
count: 1
|
||||
path: test/integration/Command/InstallExtensionsForProjectCommandTest.php
|
||||
|
||||
-
|
||||
message: '#^Call to function assert\(\) with true will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
|
||||
@@ -2,6 +2,7 @@ includes:
|
||||
- phpstan-baseline.neon
|
||||
- vendor/bnf/phpstan-psr-container/extension.neon
|
||||
- vendor/phpstan/phpstan-webmozart-assert/extension.neon
|
||||
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||
|
||||
parameters:
|
||||
level: 10
|
||||
|
||||
@@ -11,17 +11,19 @@ use Php\Pie\ComposerIntegration\PieComposerFactory;
|
||||
use Php\Pie\ComposerIntegration\PieComposerRequest;
|
||||
use Php\Pie\ComposerIntegration\PieOperation;
|
||||
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
|
||||
use Php\Pie\DependencyResolver\DependencyInstaller\PrescanSystemDependencies;
|
||||
use Php\Pie\DependencyResolver\DependencyResolver;
|
||||
use Php\Pie\DependencyResolver\InvalidPackageName;
|
||||
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
|
||||
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\SelfManage\BuildTools\CheckAllBuildTools;
|
||||
use Php\Pie\SelfManage\BuildTools\PackageManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Throwable;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
@@ -34,6 +36,7 @@ final class BuildCommand extends Command
|
||||
public function __construct(
|
||||
private readonly ContainerInterface $container,
|
||||
private readonly DependencyResolver $dependencyResolver,
|
||||
private readonly PrescanSystemDependencies $prescanSystemDependencies,
|
||||
private readonly ComposerIntegrationHandler $composerIntegrationHandler,
|
||||
private readonly FindMatchingPackages $findMatchingPackages,
|
||||
private readonly IOInterface $io,
|
||||
@@ -88,6 +91,22 @@ final class BuildCommand extends Command
|
||||
),
|
||||
);
|
||||
|
||||
if (CommandHelper::shouldCheckSystemDependencies($input)) {
|
||||
try {
|
||||
($this->prescanSystemDependencies)(
|
||||
$composer,
|
||||
$targetPlatform,
|
||||
$requestedNameAndVersion,
|
||||
CommandHelper::autoInstallSystemDependencies($input),
|
||||
);
|
||||
} catch (Throwable $anything) {
|
||||
$this->io->writeError(
|
||||
'<comment>Skipping system dependency pre-scan due to exception:</comment> ' . $anything->getMessage(),
|
||||
verbosity: IOInterface::VERBOSE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$package = ($this->dependencyResolver)(
|
||||
$composer,
|
||||
|
||||
@@ -63,6 +63,8 @@ final class CommandHelper
|
||||
private const OPTION_NO_CACHE = 'no-cache';
|
||||
private const OPTION_AUTO_INSTALL_BUILD_TOOLS = 'auto-install-build-tools';
|
||||
private const OPTION_SUPPRESS_BUILD_TOOLS_CHECK = 'no-build-tools-check';
|
||||
private const OPTION_AUTO_INSTALL_SYSTEM_DEPENDENCIES = 'auto-install-system-dependencies';
|
||||
private const OPTION_SUPPRESS_SYSTEM_DEPENDENCIES_CHECK = 'no-system-dependencies-check';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
@@ -154,6 +156,19 @@ final class CommandHelper
|
||||
'Do not perform the check to see if build tools are present on the system.',
|
||||
);
|
||||
|
||||
$command->addOption(
|
||||
self::OPTION_AUTO_INSTALL_SYSTEM_DEPENDENCIES,
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'If system dependencies missing, automatically install them, instead of prompting.',
|
||||
);
|
||||
$command->addOption(
|
||||
self::OPTION_SUPPRESS_SYSTEM_DEPENDENCIES_CHECK,
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Do not perform the check to see if system dependencies are present on the system.',
|
||||
);
|
||||
|
||||
/**
|
||||
* Allows additional options for the `./configure` command to be passed here.
|
||||
* Note, this means you probably need to call {@see self::validateInput()} to validate the input manually...
|
||||
@@ -267,6 +282,22 @@ final class CommandHelper
|
||||
|| ! $input->getOption(self::OPTION_SUPPRESS_BUILD_TOOLS_CHECK);
|
||||
}
|
||||
|
||||
public static function autoInstallSystemDependencies(InputInterface $input): bool
|
||||
{
|
||||
return $input->hasOption(self::OPTION_AUTO_INSTALL_SYSTEM_DEPENDENCIES)
|
||||
&& $input->getOption(self::OPTION_AUTO_INSTALL_SYSTEM_DEPENDENCIES);
|
||||
}
|
||||
|
||||
public static function shouldCheckSystemDependencies(InputInterface $input): bool
|
||||
{
|
||||
if (Platform::isWindows()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $input->hasOption(self::OPTION_SUPPRESS_SYSTEM_DEPENDENCIES_CHECK)
|
||||
|| ! $input->getOption(self::OPTION_SUPPRESS_SYSTEM_DEPENDENCIES_CHECK);
|
||||
}
|
||||
|
||||
public static function requestedNameAndVersionPair(InputInterface $input): RequestedPackageAndVersion
|
||||
{
|
||||
$requestedPackageString = $input->getArgument(self::ARG_REQUESTED_PACKAGE_AND_VERSION);
|
||||
|
||||
@@ -5,17 +5,15 @@ declare(strict_types=1);
|
||||
namespace Php\Pie\Command;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Php\Pie\ComposerIntegration\PhpBinaryPathBasedPlatformRepository;
|
||||
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\FetchDependencyStatuses;
|
||||
use Php\Pie\DependencyResolver\InvalidPackageName;
|
||||
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
|
||||
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
|
||||
use Php\Pie\Platform\InstalledPiePackages;
|
||||
use Php\Pie\Platform\ThreadSafetyMode;
|
||||
use Php\Pie\Util\Emoji;
|
||||
use Psr\Container\ContainerInterface;
|
||||
@@ -24,7 +22,6 @@ use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function sprintf;
|
||||
@@ -38,6 +35,7 @@ final class InfoCommand extends Command
|
||||
public function __construct(
|
||||
private readonly ContainerInterface $container,
|
||||
private readonly DependencyResolver $dependencyResolver,
|
||||
private readonly FetchDependencyStatuses $fetchDependencyStatuses,
|
||||
private readonly FindMatchingPackages $findMatchingPackages,
|
||||
private readonly IOInterface $io,
|
||||
) {
|
||||
@@ -127,30 +125,11 @@ final class InfoCommand extends Command
|
||||
));
|
||||
|
||||
$this->io->write("\n<options=bold,underscore>Dependencies:</>");
|
||||
$requires = $package->composerPackage()->getRequires();
|
||||
|
||||
if (count($requires) > 0) {
|
||||
/** @var array<string, list<Constraint>> $platformConstraints */
|
||||
$platformConstraints = [];
|
||||
$composerPlatform = new PhpBinaryPathBasedPlatformRepository($targetPlatform->phpBinaryPath, $composer, new InstalledPiePackages(), null);
|
||||
foreach ($composerPlatform->getPackages() as $platformPackage) {
|
||||
$platformConstraints[$platformPackage->getName()][] = new Constraint('==', $platformPackage->getVersion());
|
||||
}
|
||||
|
||||
foreach ($requires as $requireName => $requireLink) {
|
||||
$packageStatus = sprintf(' %s: %s %%s', $requireName, $requireLink->getConstraint()->getPrettyString());
|
||||
if (! array_key_exists($requireName, $platformConstraints)) {
|
||||
$this->io->write(sprintf($packageStatus, Emoji::PROHIBITED . ' (not installed)'));
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($platformConstraints[$requireName] as $constraint) {
|
||||
if ($requireLink->getConstraint()->matches($constraint)) {
|
||||
$this->io->write(sprintf($packageStatus, Emoji::GREEN_CHECKMARK));
|
||||
} else {
|
||||
$this->io->write(sprintf($packageStatus, Emoji::PROHIBITED . ' (your version is ' . $constraint->getVersion() . ')'));
|
||||
}
|
||||
}
|
||||
$dependencyStatuses = ($this->fetchDependencyStatuses)($targetPlatform, $composer, $package->composerPackage());
|
||||
if (count($dependencyStatuses) > 0) {
|
||||
foreach ($dependencyStatuses as $dependencyStatus) {
|
||||
$this->io->write(' ' . $dependencyStatus->asPrettyString());
|
||||
}
|
||||
} else {
|
||||
$this->io->write(' No dependencies.');
|
||||
|
||||
@@ -11,18 +11,20 @@ use Php\Pie\ComposerIntegration\PieComposerFactory;
|
||||
use Php\Pie\ComposerIntegration\PieComposerRequest;
|
||||
use Php\Pie\ComposerIntegration\PieOperation;
|
||||
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
|
||||
use Php\Pie\DependencyResolver\DependencyInstaller\PrescanSystemDependencies;
|
||||
use Php\Pie\DependencyResolver\DependencyResolver;
|
||||
use Php\Pie\DependencyResolver\InvalidPackageName;
|
||||
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
|
||||
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
use Php\Pie\SelfManage\BuildTools\CheckAllBuildTools;
|
||||
use Php\Pie\SelfManage\BuildTools\PackageManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Throwable;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
@@ -35,6 +37,7 @@ final class InstallCommand extends Command
|
||||
public function __construct(
|
||||
private readonly ContainerInterface $container,
|
||||
private readonly DependencyResolver $dependencyResolver,
|
||||
private readonly PrescanSystemDependencies $prescanSystemDependencies,
|
||||
private readonly ComposerIntegrationHandler $composerIntegrationHandler,
|
||||
private readonly InvokeSubCommand $invokeSubCommand,
|
||||
private readonly FindMatchingPackages $findMatchingPackages,
|
||||
@@ -102,6 +105,22 @@ final class InstallCommand extends Command
|
||||
),
|
||||
);
|
||||
|
||||
if (CommandHelper::shouldCheckSystemDependencies($input)) {
|
||||
try {
|
||||
($this->prescanSystemDependencies)(
|
||||
$composer,
|
||||
$targetPlatform,
|
||||
$requestedNameAndVersion,
|
||||
CommandHelper::autoInstallSystemDependencies($input),
|
||||
);
|
||||
} catch (Throwable $anything) {
|
||||
$this->io->writeError(
|
||||
'<comment>Skipping system dependency pre-scan due to exception:</comment> ' . $anything->getMessage(),
|
||||
verbosity: IOInterface::VERBOSE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$package = ($this->dependencyResolver)(
|
||||
$composer,
|
||||
|
||||
@@ -146,6 +146,10 @@ class PhpBinaryPathBasedPlatformRepository extends PlatformRepository
|
||||
$this->addPackage($lib);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructions for PIE to install these libraries, if they are missing, should be added
|
||||
* into {@see \Php\Pie\DependencyResolver\DependencyInstaller\SystemDependenciesDefinition::default()}
|
||||
*/
|
||||
private function addLibrariesUsingPkgConfig(): void
|
||||
{
|
||||
$this->detectLibraryWithPkgConfig('curl', 'libcurl');
|
||||
|
||||
@@ -28,6 +28,7 @@ use Php\Pie\Command\ShowCommand;
|
||||
use Php\Pie\Command\UninstallCommand;
|
||||
use Php\Pie\ComposerIntegration\MinimalHelperSet;
|
||||
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
|
||||
use Php\Pie\DependencyResolver\DependencyInstaller\SystemDependenciesDefinition;
|
||||
use Php\Pie\DependencyResolver\DependencyResolver;
|
||||
use Php\Pie\DependencyResolver\ResolveDependencyWithComposer;
|
||||
use Php\Pie\Downloading\GithubPackageReleaseAssets;
|
||||
@@ -39,6 +40,7 @@ use Php\Pie\Installing\Uninstall;
|
||||
use Php\Pie\Installing\UninstallUsingUnlink;
|
||||
use Php\Pie\Installing\UnixInstall;
|
||||
use Php\Pie\Installing\WindowsInstall;
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\SelfManage\BuildTools\CheckAllBuildTools;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\ConsoleEvents;
|
||||
@@ -209,6 +211,20 @@ final class Container
|
||||
|
||||
$container->alias(Ini\RemoveIniEntryWithFileGetContents::class, Ini\RemoveIniEntry::class);
|
||||
|
||||
$container->singleton(
|
||||
PackageManager::class,
|
||||
static function (): PackageManager|null {
|
||||
return PackageManager::detect();
|
||||
},
|
||||
);
|
||||
|
||||
$container->singleton(
|
||||
SystemDependenciesDefinition::class,
|
||||
static function (): SystemDependenciesDefinition {
|
||||
return SystemDependenciesDefinition::default();
|
||||
},
|
||||
);
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Php\Pie\DependencyResolver\DependencyInstaller;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Php\Pie\DependencyResolver\DependencyResolver;
|
||||
use Php\Pie\DependencyResolver\DependencyStatus;
|
||||
use Php\Pie\DependencyResolver\FetchDependencyStatuses;
|
||||
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
use Throwable;
|
||||
|
||||
use function array_filter;
|
||||
use function array_key_exists;
|
||||
use function array_map;
|
||||
use function array_unique;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
|
||||
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
|
||||
class PrescanSystemDependencies
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DependencyResolver $dependencyResolver,
|
||||
private readonly FetchDependencyStatuses $fetchDependencyStatuses,
|
||||
private readonly SystemDependenciesDefinition $systemDependenciesDefinition,
|
||||
private readonly PackageManager|null $packageManager,
|
||||
private readonly IOInterface $io,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(
|
||||
Composer $composer,
|
||||
TargetPlatform $targetPlatform,
|
||||
RequestedPackageAndVersion $requestedNameAndVersion,
|
||||
bool $autoInstallIfMissing,
|
||||
): void {
|
||||
if ($this->packageManager === null) {
|
||||
$this->io->writeError('<comment>Skipping pre-scan of system dependencies, as a supported package manager could not be detected.</comment>', verbosity: IOInterface::VERBOSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->write(sprintf('Checking system dependencies are present for extension %s', $requestedNameAndVersion->prettyNameAndVersion()), verbosity: IOInterface::VERBOSE);
|
||||
|
||||
$package = ($this->dependencyResolver)(
|
||||
$composer,
|
||||
$targetPlatform,
|
||||
$requestedNameAndVersion,
|
||||
true,
|
||||
);
|
||||
|
||||
$unmetDependencies = array_filter(
|
||||
($this->fetchDependencyStatuses)($targetPlatform, $composer, $package->composerPackage()),
|
||||
static function (DependencyStatus $dependencyStatus): bool {
|
||||
return ! $dependencyStatus->satisfied();
|
||||
},
|
||||
);
|
||||
|
||||
if (! count($unmetDependencies)) {
|
||||
$this->io->write('All system dependencies are already installed.', verbosity: IOInterface::VERBOSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->write(
|
||||
sprintf('Extension %s has unmet dependencies: %s', $requestedNameAndVersion->prettyNameAndVersion(), implode(', ', array_map(static fn (DependencyStatus $status): string => $status->name, $unmetDependencies))),
|
||||
verbosity: IOInterface::VERBOSE,
|
||||
);
|
||||
|
||||
$packageManagerPackages = array_values(array_unique(array_filter(array_map(
|
||||
fn (DependencyStatus $unmetDependency): string|null => $this->packageManagerPackageForDependency($unmetDependency, $this->packageManager),
|
||||
$unmetDependencies,
|
||||
))));
|
||||
|
||||
if (! count($packageManagerPackages)) {
|
||||
$this->io->writeError('No system dependencies could be installed automatically by PIE.', verbosity: IOInterface::VERBOSE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$proposedInstallCommand = implode(' ', $this->packageManager->installCommand($packageManagerPackages));
|
||||
|
||||
if (! $this->io->isInteractive() && ! $autoInstallIfMissing) {
|
||||
$this->io->writeError('<warning>You are not running in interactive mode, and you did not provide the --auto-install-system-dependencies flag.');
|
||||
$this->io->writeError('You may need to run: ' . $proposedInstallCommand . '</warning>');
|
||||
$this->io->writeError('');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->write(sprintf('<info>Need to install missing system dependencies:</info> %s', $proposedInstallCommand));
|
||||
|
||||
if ($this->io->isInteractive() && ! $autoInstallIfMissing) {
|
||||
if (! $this->io->askConfirmation('<question>Would you like to install them now?</question>', false)) {
|
||||
$this->io->write('<comment>Ok, but things might not work. Just so you know.</comment>');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->packageManager->install($packageManagerPackages);
|
||||
|
||||
$this->io->write('<info>Missing system dependencies have been installed.</info>');
|
||||
} catch (Throwable $anything) {
|
||||
$this->io->writeError(sprintf('<info>Failed to install missing system dependencies:</info> %s', $anything->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private function packageManagerPackageForDependency(DependencyStatus $unmetDependency, PackageManager $packageManager): string|null
|
||||
{
|
||||
$depName = str_replace('lib-', '', $unmetDependency->name);
|
||||
|
||||
if (! array_key_exists($depName, $this->systemDependenciesDefinition->definition)) {
|
||||
$this->io->writeError(
|
||||
sprintf('Could not automatically install "%s", as PIE does not have the package manager definition.', $unmetDependency->name),
|
||||
verbosity: IOInterface::VERBOSE,
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! array_key_exists($packageManager->value, $this->systemDependenciesDefinition->definition[$depName])) {
|
||||
$this->io->writeError(
|
||||
sprintf('Could not automatically install "%s", as PIE does not have a definition for "%s"', $unmetDependency->name, $packageManager->value),
|
||||
verbosity: IOInterface::VERBOSE,
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$packageManagerPackage = $this->systemDependenciesDefinition->definition[$depName][$packageManager->value];
|
||||
|
||||
// Note: ideally, we should also parse the version constraint. This initial iteration will ignore that, to be improved later.
|
||||
$this->io->write(
|
||||
sprintf('Adding %s package %s to be installed for %s', $packageManager->value, $packageManagerPackage, $unmetDependency->name),
|
||||
verbosity: IOInterface::VERBOSE,
|
||||
);
|
||||
|
||||
return $packageManagerPackage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Php\Pie\DependencyResolver\DependencyInstaller;
|
||||
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
|
||||
class SystemDependenciesDefinition
|
||||
{
|
||||
/** @param array<non-empty-string, array<non-empty-string, non-empty-string>> $definition */
|
||||
public function __construct(public readonly array $definition)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the existence of these libraries should be added into
|
||||
* {@see \Php\Pie\ComposerIntegration\PhpBinaryPathBasedPlatformRepository::addLibrariesUsingPkgConfig()}
|
||||
*/
|
||||
public static function default(): self
|
||||
{
|
||||
return new self([
|
||||
'sodium' => [
|
||||
PackageManager::Apt->value => 'libsodium-dev',
|
||||
PackageManager::Apk->value => 'libsodium-dev',
|
||||
PackageManager::Dnf->value => 'pkgconfig(libsodium)',
|
||||
PackageManager::Yum->value => 'pkgconfig(libsodium)',
|
||||
],
|
||||
'jpeg' => [
|
||||
PackageManager::Apt->value => 'libjpeg-dev',
|
||||
PackageManager::Apk->value => 'libjpeg-turbo-dev',
|
||||
PackageManager::Dnf->value => 'pkgconfig(libjpeg)',
|
||||
PackageManager::Yum->value => 'pkgconfig(libjpeg)',
|
||||
],
|
||||
'zip' => [
|
||||
PackageManager::Apt->value => 'libzip-dev',
|
||||
PackageManager::Apk->value => 'libzip-dev',
|
||||
PackageManager::Dnf->value => 'pkgconfig(libzip)',
|
||||
PackageManager::Yum->value => 'pkgconfig(libzip)',
|
||||
],
|
||||
'xslt' => [
|
||||
PackageManager::Apt->value => 'libxslt1-dev',
|
||||
PackageManager::Apk->value => 'libxslt-dev',
|
||||
PackageManager::Dnf->value => 'pkgconfig(libxslt)',
|
||||
PackageManager::Yum->value => 'pkgconfig(libxslt)',
|
||||
],
|
||||
'ffi' => [
|
||||
PackageManager::Apt->value => 'libffi-dev',
|
||||
PackageManager::Apk->value => 'libffi-dev',
|
||||
PackageManager::Dnf->value => 'pkgconfig(libffi)',
|
||||
PackageManager::Yum->value => 'pkgconfig(libffi)',
|
||||
],
|
||||
'curl' => [
|
||||
PackageManager::Apt->value => 'libcurl4-openssl-dev',
|
||||
PackageManager::Apk->value => 'curl-dev',
|
||||
PackageManager::Dnf->value => 'pkgconfig(libcurl)',
|
||||
PackageManager::Yum->value => 'pkgconfig(libcurl)',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
41
src/DependencyResolver/DependencyStatus.php
Normal file
41
src/DependencyResolver/DependencyStatus.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Php\Pie\DependencyResolver;
|
||||
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Semver\Constraint\ConstraintInterface;
|
||||
use Php\Pie\Util\Emoji;
|
||||
|
||||
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 DependencyStatus
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $name,
|
||||
public readonly ConstraintInterface $requireConstraint,
|
||||
public readonly Constraint|null $installedVersion,
|
||||
) {
|
||||
}
|
||||
|
||||
public function asPrettyString(): string
|
||||
{
|
||||
$statusTemplate = sprintf('%s: %s %%s', $this->name, $this->requireConstraint->getPrettyString());
|
||||
if ($this->installedVersion === null) {
|
||||
return sprintf($statusTemplate, Emoji::PROHIBITED . ' (not installed)');
|
||||
}
|
||||
|
||||
if (! $this->requireConstraint->matches($this->installedVersion)) {
|
||||
return sprintf($statusTemplate, Emoji::PROHIBITED . ' (your version is ' . $this->installedVersion->getVersion() . ')');
|
||||
}
|
||||
|
||||
return sprintf($statusTemplate, Emoji::GREEN_CHECKMARK);
|
||||
}
|
||||
|
||||
public function satisfied(): bool
|
||||
{
|
||||
return $this->installedVersion !== null && $this->requireConstraint->matches($this->installedVersion);
|
||||
}
|
||||
}
|
||||
48
src/DependencyResolver/FetchDependencyStatuses.php
Normal file
48
src/DependencyResolver/FetchDependencyStatuses.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Php\Pie\DependencyResolver;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Package\CompletePackageInterface;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Php\Pie\ComposerIntegration\PhpBinaryPathBasedPlatformRepository;
|
||||
use Php\Pie\Platform\InstalledPiePackages;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
|
||||
/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
|
||||
class FetchDependencyStatuses
|
||||
{
|
||||
/** @return list<DependencyStatus> */
|
||||
public function __invoke(TargetPlatform $targetPlatform, Composer $composer, CompletePackageInterface $package): array
|
||||
{
|
||||
$requires = $package->getRequires();
|
||||
|
||||
if (count($requires) <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array<string, Constraint> $platformConstraints */
|
||||
$platformConstraints = [];
|
||||
$composerPlatform = new PhpBinaryPathBasedPlatformRepository($targetPlatform->phpBinaryPath, $composer, new InstalledPiePackages(), null);
|
||||
foreach ($composerPlatform->getPackages() as $platformPackage) {
|
||||
$platformConstraints[$platformPackage->getName()] = new Constraint('==', $platformPackage->getVersion());
|
||||
}
|
||||
|
||||
$checkedPackages = [];
|
||||
|
||||
foreach ($requires as $requireName => $requireLink) {
|
||||
$checkedPackages[] = new DependencyStatus(
|
||||
$requireName,
|
||||
$requireLink->getConstraint(),
|
||||
array_key_exists($requireName, $platformConstraints) ? $platformConstraints[$requireName] : null,
|
||||
);
|
||||
}
|
||||
|
||||
return $checkedPackages;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,9 @@ namespace Php\Pie\Platform;
|
||||
use Composer\Composer;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\CompletePackageInterface;
|
||||
use InvalidArgumentException;
|
||||
use Php\Pie\DependencyResolver\Package;
|
||||
use Php\Pie\ExtensionName;
|
||||
|
||||
use function array_combine;
|
||||
use function array_filter;
|
||||
@@ -38,6 +40,12 @@ class InstalledPiePackages
|
||||
->getLocalRepository()
|
||||
->getPackages(),
|
||||
static function (BasePackage $basePackage): bool {
|
||||
try {
|
||||
ExtensionName::determineFromComposerPackage($basePackage);
|
||||
} catch (InvalidArgumentException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $basePackage instanceof CompletePackageInterface;
|
||||
},
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Php\Pie\SelfManage\BuildTools;
|
||||
namespace Php\Pie\Platform;
|
||||
|
||||
use Php\Pie\File\Sudo;
|
||||
use Php\Pie\Platform;
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Php\Pie\SelfManage\BuildTools;
|
||||
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
use Symfony\Component\Process\ExecutableFinder;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Php\Pie\SelfManage\BuildTools;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
use Throwable;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Php\Pie\SelfManage\BuildTools;
|
||||
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\Platform\TargetPhp\PhpizePath;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
use RuntimeException;
|
||||
|
||||
@@ -31,3 +31,30 @@ RUN apt-get remove --allow-remove-essential -y apt
|
||||
USER linuxbrew
|
||||
RUN pie install --auto-install-build-tools -v asgrim/example-pie-extension
|
||||
RUN pie show
|
||||
|
||||
FROM ubuntu AS test_pie_installs_system_deps_on_ubuntu
|
||||
RUN apt-get update && apt install -y unzip curl wget gcc make autoconf libtool bison re2c pkg-config libzip-dev libssl-dev libonig-dev
|
||||
RUN mkdir -p /opt/php \
|
||||
&& mkdir -p /tmp/php \
|
||||
&& cd /tmp/php \
|
||||
&& wget -O php.tgz https://www.php.net/distributions/php-8.4.17.tar.gz \
|
||||
&& tar zxf php.tgz \
|
||||
&& rm php.tgz \
|
||||
&& cd * \
|
||||
&& ./buildconf --force \
|
||||
&& ./configure --prefix=/opt/php --disable-all --enable-phar --enable-filter --enable-mbstring --with-openssl --with-iconv --with-zip \
|
||||
&& make -j$(nproc) \
|
||||
&& make install
|
||||
ENV PATH="$PATH:/opt/php/bin"
|
||||
COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie
|
||||
RUN pie install -v --auto-install-system-dependencies php/sodium
|
||||
|
||||
FROM alpine AS test_pie_installs_system_deps_on_alpine
|
||||
RUN apk add php php-phar php-mbstring php-iconv php-openssl bzip2-dev libbz2 build-base autoconf bison re2c libtool php84-dev
|
||||
COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie
|
||||
RUN pie install -v --auto-install-system-dependencies php/sodium
|
||||
|
||||
FROM fedora AS test_pie_installs_system_deps_on_fedora
|
||||
RUN dnf install -y php php-pecl-zip unzip gcc make autoconf bison re2c libtool php-devel
|
||||
COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie
|
||||
RUN pie install -v --auto-install-system-dependencies php/sodium
|
||||
|
||||
@@ -58,17 +58,15 @@ final class InstallExtensionsForProjectCommandTest extends TestCase
|
||||
$container->method('get')->willReturnCallback(
|
||||
/** @param class-string $service */
|
||||
function (string $service): mixed {
|
||||
switch ($service) {
|
||||
case QuieterConsoleIO::class:
|
||||
return new QuieterConsoleIO(
|
||||
new ArrayInput([]),
|
||||
new BufferedOutput(),
|
||||
new MinimalHelperSet(['question' => new QuestionHelper()]),
|
||||
);
|
||||
|
||||
default:
|
||||
return $this->createMock($service);
|
||||
}
|
||||
/** @var class-string $service */
|
||||
return match ($service) {
|
||||
QuieterConsoleIO::class => new QuieterConsoleIO(
|
||||
new ArrayInput([]),
|
||||
new BufferedOutput(),
|
||||
new MinimalHelperSet(['question' => new QuestionHelper()]),
|
||||
),
|
||||
default => $this->createMock($service),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Php\PieUnitTest\DependencyResolver\DependencyInstaller;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\BufferIO;
|
||||
use Composer\Package\CompletePackage;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Php\Pie\DependencyResolver\DependencyInstaller\PrescanSystemDependencies;
|
||||
use Php\Pie\DependencyResolver\DependencyInstaller\SystemDependenciesDefinition;
|
||||
use Php\Pie\DependencyResolver\DependencyResolver;
|
||||
use Php\Pie\DependencyResolver\DependencyStatus;
|
||||
use Php\Pie\DependencyResolver\FetchDependencyStatuses;
|
||||
use Php\Pie\DependencyResolver\Package;
|
||||
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
|
||||
#[CoversClass(PrescanSystemDependencies::class)]
|
||||
final class PrescanSystemDependenciesTest extends TestCase
|
||||
{
|
||||
private readonly DependencyResolver&MockObject $dependencyResolver;
|
||||
private readonly FetchDependencyStatuses&MockObject $fetchDependencyStatuses;
|
||||
private readonly BufferIO $io;
|
||||
private readonly Composer&MockObject $composer;
|
||||
private readonly TargetPlatform&MockObject $targetPlatform;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->dependencyResolver = $this->createMock(DependencyResolver::class);
|
||||
$this->fetchDependencyStatuses = $this->createMock(FetchDependencyStatuses::class);
|
||||
$this->io = new BufferIO(verbosity: StreamOutput::VERBOSITY_VERBOSE);
|
||||
$this->composer = $this->createMock(Composer::class);
|
||||
$this->targetPlatform = $this->createMock(TargetPlatform::class);
|
||||
}
|
||||
|
||||
public function testNoPackageManager(): void
|
||||
{
|
||||
$scanner = new PrescanSystemDependencies(
|
||||
$this->dependencyResolver,
|
||||
$this->fetchDependencyStatuses,
|
||||
new SystemDependenciesDefinition([]),
|
||||
null,
|
||||
$this->io,
|
||||
);
|
||||
|
||||
($scanner)($this->composer, $this->targetPlatform, new RequestedPackageAndVersion('foo/foo', null), true);
|
||||
|
||||
self::assertStringContainsString(
|
||||
'Skipping pre-scan of system dependencies, as a supported package manager could not be detected.',
|
||||
$this->io->getOutput(),
|
||||
);
|
||||
}
|
||||
|
||||
public function testAllDependenciesSatisfied(): void
|
||||
{
|
||||
$scanner = new PrescanSystemDependencies(
|
||||
$this->dependencyResolver,
|
||||
$this->fetchDependencyStatuses,
|
||||
new SystemDependenciesDefinition([]),
|
||||
PackageManager::Test,
|
||||
$this->io,
|
||||
);
|
||||
|
||||
$request = new RequestedPackageAndVersion('foo/foo', null);
|
||||
$composerPackage = new CompletePackage('foo/foo', '1.0.0.0', '1.0.0');
|
||||
$piePackage = Package::fromComposerCompletePackage($composerPackage);
|
||||
$this->dependencyResolver->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->composer, $this->targetPlatform, $request, true)
|
||||
->willReturn($piePackage);
|
||||
|
||||
$versionParser = new VersionParser();
|
||||
|
||||
$this->fetchDependencyStatuses->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->targetPlatform, $this->composer, $composerPackage)
|
||||
->willReturn([
|
||||
new DependencyStatus('lib-foo', $versionParser->parseConstraints('^1.0'), new Constraint('=', '1.0.0.0')),
|
||||
new DependencyStatus('lib-bar', $versionParser->parseConstraints('^2.0'), new Constraint('=', '2.5.1.0')),
|
||||
]);
|
||||
|
||||
($scanner)($this->composer, $this->targetPlatform, $request, true);
|
||||
|
||||
self::assertStringContainsString(
|
||||
'All system dependencies are already installed.',
|
||||
$this->io->getOutput(),
|
||||
);
|
||||
}
|
||||
|
||||
public function testMissingDependencyThatDoesNotHaveAnyPackageManagerDefinition(): void
|
||||
{
|
||||
$scanner = new PrescanSystemDependencies(
|
||||
$this->dependencyResolver,
|
||||
$this->fetchDependencyStatuses,
|
||||
new SystemDependenciesDefinition([]),
|
||||
PackageManager::Test,
|
||||
$this->io,
|
||||
);
|
||||
|
||||
$request = new RequestedPackageAndVersion('foo/foo', null);
|
||||
$composerPackage = new CompletePackage('foo/foo', '1.0.0.0', '1.0.0');
|
||||
$piePackage = Package::fromComposerCompletePackage($composerPackage);
|
||||
$this->dependencyResolver->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->composer, $this->targetPlatform, $request, true)
|
||||
->willReturn($piePackage);
|
||||
|
||||
$versionParser = new VersionParser();
|
||||
|
||||
$this->fetchDependencyStatuses->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->targetPlatform, $this->composer, $composerPackage)
|
||||
->willReturn([
|
||||
new DependencyStatus('lib-bar', $versionParser->parseConstraints('^1.0'), null),
|
||||
]);
|
||||
|
||||
($scanner)($this->composer, $this->targetPlatform, $request, true);
|
||||
|
||||
$outputString = $this->io->getOutput();
|
||||
self::assertStringContainsString('Extension foo/foo has unmet dependencies: lib-bar', $outputString);
|
||||
self::assertStringContainsString('Could not automatically install "lib-bar", as PIE does not have the package manager definition.', $outputString);
|
||||
self::assertStringContainsString('No system dependencies could be installed automatically by PIE.', $outputString);
|
||||
}
|
||||
|
||||
public function testMissingDependencyThatDoesNotHaveMyPackageManagerDefinition(): void
|
||||
{
|
||||
$scanner = new PrescanSystemDependencies(
|
||||
$this->dependencyResolver,
|
||||
$this->fetchDependencyStatuses,
|
||||
new SystemDependenciesDefinition([
|
||||
'bar' => [
|
||||
PackageManager::Apt->value => 'libbar-dev',
|
||||
PackageManager::Apk->value => 'libbar-dev',
|
||||
],
|
||||
]),
|
||||
PackageManager::Test,
|
||||
$this->io,
|
||||
);
|
||||
|
||||
$request = new RequestedPackageAndVersion('foo/foo', null);
|
||||
$composerPackage = new CompletePackage('foo/foo', '1.0.0.0', '1.0.0');
|
||||
$piePackage = Package::fromComposerCompletePackage($composerPackage);
|
||||
$this->dependencyResolver->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->composer, $this->targetPlatform, $request, true)
|
||||
->willReturn($piePackage);
|
||||
|
||||
$versionParser = new VersionParser();
|
||||
|
||||
$this->fetchDependencyStatuses->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->targetPlatform, $this->composer, $composerPackage)
|
||||
->willReturn([
|
||||
new DependencyStatus('lib-bar', $versionParser->parseConstraints('^1.0'), null),
|
||||
]);
|
||||
|
||||
($scanner)($this->composer, $this->targetPlatform, $request, true);
|
||||
|
||||
$outputString = $this->io->getOutput();
|
||||
self::assertStringContainsString('Extension foo/foo has unmet dependencies: lib-bar', $outputString);
|
||||
self::assertStringContainsString('Could not automatically install "lib-bar", as PIE does not have a definition for "test"', $outputString);
|
||||
self::assertStringContainsString('No system dependencies could be installed automatically by PIE.', $outputString);
|
||||
}
|
||||
|
||||
public function testMissingDependenciesFailToInstall(): void
|
||||
{
|
||||
$scanner = new PrescanSystemDependencies(
|
||||
$this->dependencyResolver,
|
||||
$this->fetchDependencyStatuses,
|
||||
new SystemDependenciesDefinition([
|
||||
'bar' => [
|
||||
PackageManager::Apk->value => 'hopefully-this-package-does-not-exist-in-apk',
|
||||
PackageManager::Test->value => 'libbar-dev',
|
||||
],
|
||||
]),
|
||||
PackageManager::Apk,
|
||||
$this->io,
|
||||
);
|
||||
|
||||
$request = new RequestedPackageAndVersion('foo/foo', null);
|
||||
$composerPackage = new CompletePackage('foo/foo', '1.0.0.0', '1.0.0');
|
||||
$piePackage = Package::fromComposerCompletePackage($composerPackage);
|
||||
$this->dependencyResolver->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->composer, $this->targetPlatform, $request, true)
|
||||
->willReturn($piePackage);
|
||||
|
||||
$versionParser = new VersionParser();
|
||||
|
||||
$this->fetchDependencyStatuses->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->targetPlatform, $this->composer, $composerPackage)
|
||||
->willReturn([
|
||||
new DependencyStatus('lib-bar', $versionParser->parseConstraints('^1.0'), null),
|
||||
]);
|
||||
|
||||
($scanner)($this->composer, $this->targetPlatform, $request, true);
|
||||
|
||||
$outputString = $this->io->getOutput();
|
||||
self::assertStringContainsString('Extension foo/foo has unmet dependencies: lib-bar', $outputString);
|
||||
self::assertStringContainsString('Failed to install missing system dependencies', $outputString);
|
||||
}
|
||||
|
||||
public function testMissingDependenciesAreSuccessfullyInstalled(): void
|
||||
{
|
||||
$scanner = new PrescanSystemDependencies(
|
||||
$this->dependencyResolver,
|
||||
$this->fetchDependencyStatuses,
|
||||
new SystemDependenciesDefinition([
|
||||
'bar' => [
|
||||
PackageManager::Apt->value => 'libbar-dev',
|
||||
PackageManager::Apk->value => 'libbar-dev',
|
||||
PackageManager::Test->value => 'libbar-dev',
|
||||
],
|
||||
]),
|
||||
PackageManager::Test,
|
||||
$this->io,
|
||||
);
|
||||
|
||||
$request = new RequestedPackageAndVersion('foo/foo', null);
|
||||
$composerPackage = new CompletePackage('foo/foo', '1.0.0.0', '1.0.0');
|
||||
$piePackage = Package::fromComposerCompletePackage($composerPackage);
|
||||
$this->dependencyResolver->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->composer, $this->targetPlatform, $request, true)
|
||||
->willReturn($piePackage);
|
||||
|
||||
$versionParser = new VersionParser();
|
||||
|
||||
$this->fetchDependencyStatuses->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->targetPlatform, $this->composer, $composerPackage)
|
||||
->willReturn([
|
||||
new DependencyStatus('lib-bar', $versionParser->parseConstraints('^1.0'), null),
|
||||
]);
|
||||
|
||||
($scanner)($this->composer, $this->targetPlatform, $request, true);
|
||||
|
||||
$outputString = $this->io->getOutput();
|
||||
self::assertStringContainsString('Extension foo/foo has unmet dependencies: lib-bar', $outputString);
|
||||
self::assertStringContainsString('Adding test package libbar-dev to be installed for lib-bar', $outputString);
|
||||
self::assertStringContainsString('Need to install missing system dependencies: echo "fake installing libbar-dev"', $outputString);
|
||||
}
|
||||
|
||||
public function testMissingDependenciesAreNotInstalledWhenShouldNotAutoInstallAndNonInteractive(): void
|
||||
{
|
||||
$scanner = new PrescanSystemDependencies(
|
||||
$this->dependencyResolver,
|
||||
$this->fetchDependencyStatuses,
|
||||
new SystemDependenciesDefinition([
|
||||
'bar' => [
|
||||
PackageManager::Apt->value => 'libbar-dev',
|
||||
PackageManager::Apk->value => 'libbar-dev',
|
||||
PackageManager::Test->value => 'libbar-dev',
|
||||
],
|
||||
]),
|
||||
PackageManager::Test,
|
||||
$this->io,
|
||||
);
|
||||
|
||||
$request = new RequestedPackageAndVersion('foo/foo', null);
|
||||
$composerPackage = new CompletePackage('foo/foo', '1.0.0.0', '1.0.0');
|
||||
$piePackage = Package::fromComposerCompletePackage($composerPackage);
|
||||
$this->dependencyResolver->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->composer, $this->targetPlatform, $request, true)
|
||||
->willReturn($piePackage);
|
||||
|
||||
$versionParser = new VersionParser();
|
||||
|
||||
$this->fetchDependencyStatuses->expects(self::once())
|
||||
->method('__invoke')
|
||||
->with($this->targetPlatform, $this->composer, $composerPackage)
|
||||
->willReturn([
|
||||
new DependencyStatus('lib-bar', $versionParser->parseConstraints('^1.0'), null),
|
||||
]);
|
||||
|
||||
($scanner)($this->composer, $this->targetPlatform, $request, false);
|
||||
|
||||
$outputString = $this->io->getOutput();
|
||||
self::assertStringContainsString('Extension foo/foo has unmet dependencies: lib-bar', $outputString);
|
||||
self::assertStringContainsString('Adding test package libbar-dev to be installed for lib-bar', $outputString);
|
||||
self::assertStringContainsString('You are not running in interactive mode, and you did not provide the --auto-install-system-dependencies flag.', $outputString);
|
||||
self::assertStringContainsString('You may need to run: echo "fake installing libbar-dev"', $outputString);
|
||||
self::assertStringNotContainsString('Need to install missing system dependencies', $outputString);
|
||||
}
|
||||
}
|
||||
45
test/unit/DependencyResolver/DependencyStatusTest.php
Normal file
45
test/unit/DependencyResolver/DependencyStatusTest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Php\PieUnitTest\DependencyResolver;
|
||||
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Semver\Constraint\MatchAllConstraint;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Php\Pie\DependencyResolver\DependencyStatus;
|
||||
use Php\Pie\Util\Emoji;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(DependencyStatus::class)]
|
||||
final class DependencyStatusTest extends TestCase
|
||||
{
|
||||
public function testDependencyNotInstalled(): void
|
||||
{
|
||||
$dependencyStatus = new DependencyStatus('foo', new Constraint('>', '2.0.0'), null);
|
||||
self::assertSame('foo: > 2.0.0 ' . Emoji::PROHIBITED . ' (not installed)', $dependencyStatus->asPrettyString());
|
||||
self::assertFalse($dependencyStatus->satisfied());
|
||||
}
|
||||
|
||||
public function testDependencyInstalledAndMatchesAllConstraint(): void
|
||||
{
|
||||
$dependencyStatus = new DependencyStatus('foo', new MatchAllConstraint(), new Constraint('=', '1.0.0.0'));
|
||||
self::assertSame('foo: * ' . Emoji::GREEN_CHECKMARK, $dependencyStatus->asPrettyString());
|
||||
self::assertTrue($dependencyStatus->satisfied());
|
||||
}
|
||||
|
||||
public function testDependencyInstalledAndMatchesSemverConstraint(): void
|
||||
{
|
||||
$dependencyStatus = new DependencyStatus('foo', (new VersionParser())->parseConstraints('^1.0'), new Constraint('=', '1.0.0.0'));
|
||||
self::assertSame('foo: ^1.0 ' . Emoji::GREEN_CHECKMARK, $dependencyStatus->asPrettyString());
|
||||
self::assertTrue($dependencyStatus->satisfied());
|
||||
}
|
||||
|
||||
public function testDependencyInstalledButMismatchingVersion(): void
|
||||
{
|
||||
$dependencyStatus = new DependencyStatus('foo', new Constraint('>', '2.0.0'), new Constraint('=', '1.2.3.0'));
|
||||
self::assertSame('foo: > 2.0.0 ' . Emoji::PROHIBITED . ' (your version is 1.2.3.0)', $dependencyStatus->asPrettyString());
|
||||
self::assertFalse($dependencyStatus->satisfied());
|
||||
}
|
||||
}
|
||||
52
test/unit/DependencyResolver/FetchDependencyStatusesTest.php
Normal file
52
test/unit/DependencyResolver/FetchDependencyStatusesTest.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Php\PieUnitTest\DependencyResolver;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\CompletePackage;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Php\Pie\DependencyResolver\FetchDependencyStatuses;
|
||||
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(FetchDependencyStatuses::class)]
|
||||
final class FetchDependencyStatusesTest extends TestCase
|
||||
{
|
||||
public function testNoRequiresReturnsEmptyArray(): void
|
||||
{
|
||||
$package = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3');
|
||||
|
||||
self::assertEquals([], (new FetchDependencyStatuses())(TargetPlatform::fromPhpBinaryPath(PhpBinaryPath::fromCurrentProcess(), null, null), $this->createMock(Composer::class), $package));
|
||||
}
|
||||
|
||||
public function testRequiresReturnsListOfStatuses(): void
|
||||
{
|
||||
$php = PhpBinaryPath::fromCurrentProcess();
|
||||
|
||||
$package = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3');
|
||||
$package->setRequires([
|
||||
'ext-core' => new Link('__root__', 'ext-core', new Constraint('=', $php->version() . '.0')),
|
||||
'ext-nonsense_extension' => new Link('__root__', 'ext-nonsense_extension', new Constraint('=', '*')),
|
||||
'ext-standard' => new Link('__root__', 'ext-standard', new Constraint('<', '1.0.0.0')),
|
||||
]);
|
||||
|
||||
$deps = (new FetchDependencyStatuses())(
|
||||
TargetPlatform::fromPhpBinaryPath($php, null, null),
|
||||
Factory::create($this->createMock(IOInterface::class)),
|
||||
$package,
|
||||
);
|
||||
|
||||
self::assertCount(3, $deps);
|
||||
|
||||
self::assertSame('ext-core: == ' . $php->version() . '.0 ✅', $deps[0]->asPrettyString());
|
||||
self::assertSame('ext-nonsense_extension: == * 🚫 (not installed)', $deps[1]->asPrettyString());
|
||||
self::assertSame('ext-standard: < 1.0.0.0 🚫 (your version is ' . $php->version() . '.0)', $deps[2]->asPrettyString());
|
||||
}
|
||||
}
|
||||
@@ -39,4 +39,22 @@ final class InstalledPiePackagesTest extends TestCase
|
||||
self::assertSame('bar2', $packages['bar2']->extensionName()->name());
|
||||
self::assertSame('foo/bar2', $packages['bar2']->name());
|
||||
}
|
||||
|
||||
public function testInvalidExtensionNamesAreFilteredOut(): void
|
||||
{
|
||||
$localRepo = $this->createMock(InstalledRepositoryInterface::class);
|
||||
$localRepo->method('getPackages')->willReturn([
|
||||
new CompletePackage('foo/invalid-extension-name', '1.2.3.0', '1.2.3'),
|
||||
new CompletePackage('invalid-extension-name', '1.2.3.0', '1.2.3'),
|
||||
new CompletePackage('invalid_extension_name', '1.2.3.0', '1.2.3'),
|
||||
]);
|
||||
|
||||
$repoManager = $this->createMock(RepositoryManager::class);
|
||||
$repoManager->method('getLocalRepository')->willReturn($localRepo);
|
||||
|
||||
$composer = $this->createMock(Composer::class);
|
||||
$composer->method('getRepositoryManager')->willReturn($repoManager);
|
||||
|
||||
self::assertCount(0, (new InstalledPiePackages())->allPiePackages($composer));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Php\PieUnitTest\SelfManage\BuildTools;
|
||||
namespace Php\PieUnitTest\Platform;
|
||||
|
||||
use Php\Pie\SelfManage\BuildTools\PackageManager;
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -400,10 +400,8 @@ final class PhpBinaryPathTest extends TestCase
|
||||
$php = PhpBinaryPath::fromPhpBinaryPath($phpPath);
|
||||
self::assertArrayHasKey('Core', $php->extensions());
|
||||
self::assertNotEmpty($php->extensionPath());
|
||||
self::assertInstanceOf(OperatingSystem::class, $php->operatingSystem());
|
||||
self::assertNotEmpty($php->version());
|
||||
self::assertNotEmpty($php->majorMinorVersion());
|
||||
self::assertInstanceOf(Architecture::class, $php->machineType());
|
||||
self::assertGreaterThan(0, $php->phpIntSize());
|
||||
self::assertNotEmpty($php->phpinfo());
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Php\PieUnitTest\SelfManage\BuildTools;
|
||||
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
use Php\Pie\SelfManage\BuildTools\BinaryBuildToolFinder;
|
||||
use Php\Pie\SelfManage\BuildTools\PackageManager;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -8,12 +8,12 @@ use Composer\IO\BufferIO;
|
||||
use Php\Pie\Platform\Architecture;
|
||||
use Php\Pie\Platform\OperatingSystem;
|
||||
use Php\Pie\Platform\OperatingSystemFamily;
|
||||
use Php\Pie\Platform\PackageManager;
|
||||
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
|
||||
use Php\Pie\Platform\TargetPlatform;
|
||||
use Php\Pie\Platform\ThreadSafetyMode;
|
||||
use Php\Pie\SelfManage\BuildTools\BinaryBuildToolFinder;
|
||||
use Php\Pie\SelfManage\BuildTools\CheckAllBuildTools;
|
||||
use Php\Pie\SelfManage\BuildTools\PackageManager;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
Reference in New Issue
Block a user