From 4d3ec06e978e524ffb665ea66ff2ce6d71281f1a Mon Sep 17 00:00:00 2001 From: James Titcumb Date: Thu, 5 Mar 2026 11:55:54 +0000 Subject: [PATCH] 435: added flags to control system dependency scanning behaviour --- src/Command/CommandHelper.php | 31 ++++++++++ src/Command/InstallCommand.php | 22 ++++--- .../PrescanSystemDependencies.php | 29 +++++++++- test/end-to-end/Dockerfile | 6 +- .../PrescanSystemDependenciesTest.php | 57 ++++++++++++++++--- .../FetchDependencyStatusesTest.php | 4 +- 6 files changed, 126 insertions(+), 23 deletions(-) diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 62f08e7..5fb3757 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -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); diff --git a/src/Command/InstallCommand.php b/src/Command/InstallCommand.php index 6ca24df..c74c8f7 100644 --- a/src/Command/InstallCommand.php +++ b/src/Command/InstallCommand.php @@ -105,14 +105,20 @@ final class InstallCommand extends Command ), ); - // @todo flag to disable this check - try { - ($this->prescanSystemDependencies)($composer, $targetPlatform, $requestedNameAndVersion); - } catch (Throwable $anything) { - $this->io->writeError( - 'Skipping system dependency pre-scan due to exception: ' . $anything->getMessage(), - verbosity: IOInterface::VERBOSE, - ); + if (CommandHelper::shouldCheckSystemDependencies($input)) { + try { + ($this->prescanSystemDependencies)( + $composer, + $targetPlatform, + $requestedNameAndVersion, + CommandHelper::autoInstallSystemDependencies($input), + ); + } catch (Throwable $anything) { + $this->io->writeError( + 'Skipping system dependency pre-scan due to exception: ' . $anything->getMessage(), + verbosity: IOInterface::VERBOSE, + ); + } } try { diff --git a/src/DependencyResolver/DependencyInstaller/PrescanSystemDependencies.php b/src/DependencyResolver/DependencyInstaller/PrescanSystemDependencies.php index 4490652..4b3a303 100644 --- a/src/DependencyResolver/DependencyInstaller/PrescanSystemDependencies.php +++ b/src/DependencyResolver/DependencyInstaller/PrescanSystemDependencies.php @@ -36,8 +36,12 @@ class PrescanSystemDependencies ) { } - public function __invoke(Composer $composer, TargetPlatform $targetPlatform, RequestedPackageAndVersion $requestedNameAndVersion): void - { + public function __invoke( + Composer $composer, + TargetPlatform $targetPlatform, + RequestedPackageAndVersion $requestedNameAndVersion, + bool $autoInstallIfMissing, + ): void { if ($this->packageManager === null) { $this->io->writeError('Skipping pre-scan of system dependencies, as a supported package manager could not be detected.', verbosity: IOInterface::VERBOSE); @@ -83,10 +87,29 @@ class PrescanSystemDependencies } $proposedInstallCommand = implode(' ', $this->packageManager->installCommand($packageManagerPackages)); - $this->io->write(sprintf('Installing missing system dependencies: %s', $proposedInstallCommand)); + + if (! $this->io->isInteractive() && ! $autoInstallIfMissing) { + $this->io->writeError('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 . ''); + $this->io->writeError(''); + + return; + } + + $this->io->write(sprintf('Need to install missing system dependencies: %s', $proposedInstallCommand)); + + if ($this->io->isInteractive() && ! $autoInstallIfMissing) { + if (! $this->io->askConfirmation('Would you like to install them now?', false)) { + $this->io->write('Ok, but things might not work. Just so you know.'); + + return; + } + } try { $this->packageManager->install($packageManagerPackages); + + $this->io->write('Missing system dependencies have been installed.'); } catch (Throwable $anything) { $this->io->writeError(sprintf('Failed to install missing system dependencies: %s', $anything->getMessage())); } diff --git a/test/end-to-end/Dockerfile b/test/end-to-end/Dockerfile index abd5c11..09e941e 100644 --- a/test/end-to-end/Dockerfile +++ b/test/end-to-end/Dockerfile @@ -47,14 +47,14 @@ RUN mkdir -p /opt/php \ && make install ENV PATH="$PATH:/opt/php/bin" COPY --from=build_pie_phar /app/pie.phar /usr/local/bin/pie -RUN pie install -v php/sodium +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 php/sodium +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 php/sodium +RUN pie install -v --auto-install-system-dependencies php/sodium diff --git a/test/unit/DependencyResolver/DependencyInstaller/PrescanSystemDependenciesTest.php b/test/unit/DependencyResolver/DependencyInstaller/PrescanSystemDependenciesTest.php index 46a1cba..8f292c0 100644 --- a/test/unit/DependencyResolver/DependencyInstaller/PrescanSystemDependenciesTest.php +++ b/test/unit/DependencyResolver/DependencyInstaller/PrescanSystemDependenciesTest.php @@ -53,7 +53,7 @@ final class PrescanSystemDependenciesTest extends TestCase $this->io, ); - ($scanner)($this->composer, $this->targetPlatform, new RequestedPackageAndVersion('foo/foo', null)); + ($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.', @@ -89,7 +89,7 @@ final class PrescanSystemDependenciesTest extends TestCase new DependencyStatus('lib-bar', $versionParser->parseConstraints('^2.0'), new Constraint('=', '2.5.1.0')), ]); - ($scanner)($this->composer, $this->targetPlatform, $request); + ($scanner)($this->composer, $this->targetPlatform, $request, true); self::assertStringContainsString( 'All system dependencies are already installed.', @@ -124,7 +124,7 @@ final class PrescanSystemDependenciesTest extends TestCase new DependencyStatus('lib-bar', $versionParser->parseConstraints('^1.0'), null), ]); - ($scanner)($this->composer, $this->targetPlatform, $request); + ($scanner)($this->composer, $this->targetPlatform, $request, true); $outputString = $this->io->getOutput(); self::assertStringContainsString('Extension foo/foo has unmet dependencies: lib-bar', $outputString); @@ -164,7 +164,7 @@ final class PrescanSystemDependenciesTest extends TestCase new DependencyStatus('lib-bar', $versionParser->parseConstraints('^1.0'), null), ]); - ($scanner)($this->composer, $this->targetPlatform, $request); + ($scanner)($this->composer, $this->targetPlatform, $request, true); $outputString = $this->io->getOutput(); self::assertStringContainsString('Extension foo/foo has unmet dependencies: lib-bar', $outputString); @@ -204,7 +204,7 @@ final class PrescanSystemDependenciesTest extends TestCase new DependencyStatus('lib-bar', $versionParser->parseConstraints('^1.0'), null), ]); - ($scanner)($this->composer, $this->targetPlatform, $request); + ($scanner)($this->composer, $this->targetPlatform, $request, true); $outputString = $this->io->getOutput(); self::assertStringContainsString('Extension foo/foo has unmet dependencies: lib-bar', $outputString); @@ -244,11 +244,54 @@ final class PrescanSystemDependenciesTest extends TestCase new DependencyStatus('lib-bar', $versionParser->parseConstraints('^1.0'), null), ]); - ($scanner)($this->composer, $this->targetPlatform, $request); + ($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('Installing missing system dependencies: echo "fake installing libbar-dev"', $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); } } diff --git a/test/unit/DependencyResolver/FetchDependencyStatusesTest.php b/test/unit/DependencyResolver/FetchDependencyStatusesTest.php index 03c21bd..bcd6a9c 100644 --- a/test/unit/DependencyResolver/FetchDependencyStatusesTest.php +++ b/test/unit/DependencyResolver/FetchDependencyStatusesTest.php @@ -45,8 +45,8 @@ final class FetchDependencyStatusesTest extends TestCase self::assertCount(3, $deps); - self::assertSame('ext-core: == 8.4.17.0 ✅', $deps[0]->asPrettyString()); + 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 8.4.17.0)', $deps[2]->asPrettyString()); + self::assertSame('ext-standard: < 1.0.0.0 🚫 (your version is ' . $php->version() . '.0)', $deps[2]->asPrettyString()); } }