diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index f1fefc750..79fea1868 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -57,6 +57,7 @@ jobs: - e2e_dockerfile - e2e_dockerfile_no_extension - e2e_custom_composer_bin + - e2e_installed_dev php: [ '8.1', '8.2', '8.3' ] tools: - 'composer:2.2' diff --git a/Makefile b/Makefile index 73de907ec..60f8bc283 100644 --- a/Makefile +++ b/Makefile @@ -253,7 +253,7 @@ _infection_ci: $(INFECTION_BIN) $(COVERAGE_XML_DIR) $(COVERAGE_JUNIT) vendor #--------------------------------------------------------------------------- .PHONY: test_e2e -test_e2e: e2e_scoper_alias e2e_scoper_expose_symbols e2e_php_settings_checker_no_restart e2e_php_settings_checker_xdebug_enabled e2e_php_settings_checker_readonly_enabled e2e_php_settings_checker_memory_limit_lower e2e_php_settings_checker_memory_limit_higher e2e_symfony e2e_composer_installed_versions e2e_phpstorm_stubs e2e_dockerfile e2e_dockerfile_no_extension e2e_custom_composer_bin e2e_symfony_runtime +test_e2e: e2e_scoper_alias e2e_scoper_expose_symbols e2e_php_settings_checker_no_restart e2e_php_settings_checker_xdebug_enabled e2e_php_settings_checker_readonly_enabled e2e_php_settings_checker_memory_limit_lower e2e_php_settings_checker_memory_limit_higher e2e_symfony e2e_composer_installed_versions e2e_phpstorm_stubs e2e_dockerfile e2e_dockerfile_no_extension e2e_custom_composer_bin e2e_symfony_runtime e2e_installed_dev .PHONY: blackfire diff --git a/Makefile.e2e b/Makefile.e2e index 9d202aa41..0b173447d 100644 --- a/Makefile.e2e +++ b/Makefile.e2e @@ -110,6 +110,11 @@ E2E_CUSTOM_COMPOSER_BIN_ACTUAL_STDERR := $(E2E_CUSTOM_COMPOSER_BIN_OUTPUT_DIR)/a E2E_CUSTOM_COMPOSER_BIN_ACTUAL_COMPILATION := $(E2E_CUSTOM_COMPOSER_BIN_OUTPUT_DIR)/actual-compilation E2E_CUSTOM_COMPOSER_BIN_PHAR := $(E2E_CUSTOM_COMPOSER_BIN_OUTPUT_DIR)/index.phar +E2E_COMPOSER_INSTALLED_DEV_DIR = fixtures/build/dir017 +E2E_COMPOSER_INSTALLED_DEV_OUTPUT_DIR = dist/dir017 +E2E_COMPOSER_INSTALLED_DEV_EXPECTED_OUTPUT := $(E2E_COMPOSER_INSTALLED_DEV_DIR)/expected-output +E2E_COMPOSER_INSTALLED_DEV_ACTUAL_OUTPUT := $(E2E_COMPOSER_INSTALLED_DEV_OUTPUT_DIR)/actual-output + ifeq ($(OS),Darwin) SED = sed -i '' else @@ -414,6 +419,18 @@ e2e_custom_composer_bin: $(SCOPED_BOX_BIN) $(E2E_CUSTOM_COMPOSER_BIN_DIR)/vendor $(E2E_CUSTOM_COMPOSER_BIN_ACTUAL_COMPILATION) +.PHONY: e2e_installed_dev +e2e_installed_dev: $(SCOPED_BOX_BIN) $(E2E_COMPOSER_INSTALLED_DEV_DIR)/vendor + @# Packages an app with dev composer plugins + $(SCOPED_BOX) compile --working-dir=$(E2E_COMPOSER_INSTALLED_DEV_DIR) --no-parallel --ansi + + @mkdir -p $(E2E_COMPOSER_INSTALLED_DEV_OUTPUT_DIR) + php $(E2E_COMPOSER_INSTALLED_DEV_DIR)/index.phar > $(E2E_COMPOSER_INSTALLED_DEV_ACTUAL_OUTPUT) + mv -vf $(E2E_COMPOSER_INSTALLED_DEV_DIR)/index.phar $(E2E_COMPOSER_INSTALLED_DEV_OUTPUT_DIR)/index.phar + + $(DIFF) $(E2E_COMPOSER_INSTALLED_DEV_EXPECTED_OUTPUT) $(E2E_COMPOSER_INSTALLED_DEV_ACTUAL_OUTPUT) + + # # Rules from files #--------------------------------------------------------------------------- @@ -472,6 +489,13 @@ $(E2E_DOCKERFILE_NO_EXT_PHAR): $(SCOPED_BOX_BIN) $(E2E_DOCKERFILE_NO_EXT_DIR)/in $(SCOPED_BOX) compile --ansi --working-dir=$(E2E_DOCKERFILE_NO_EXT_DIR) touch -c $@ +$(E2E_COMPOSER_INSTALLED_DEV_DIR)/vendor: $(E2E_COMPOSER_INSTALLED_DEV_DIR)/composer.lock + composer install --ansi --working-dir=$(E2E_COMPOSER_INSTALLED_DEV_DIR) + touch -c $@ +$(E2E_COMPOSER_INSTALLED_DEV_DIR)/composer.lock: $(E2E_COMPOSER_INSTALLED_DEV_DIR)/composer.json + @echo "$(ERROR_COLOR)$(@) is not up to date. You may want to run the following command:$(NO_COLOR)" + @echo "$$ composer update --lock --working-dir=$(E2E_COMPOSER_INSTALLED_DEV_DIR) && touch -c $(@)" + .PHONY: docker_images docker_images: ./.docker/build diff --git a/fixtures/build/dir017/.gitignore b/fixtures/build/dir017/.gitignore new file mode 100644 index 000000000..2d230ec5c --- /dev/null +++ b/fixtures/build/dir017/.gitignore @@ -0,0 +1,2 @@ +/*.phar +/vendor/ diff --git a/fixtures/build/dir017/box.json.dist b/fixtures/build/dir017/box.json.dist new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/fixtures/build/dir017/box.json.dist @@ -0,0 +1 @@ +{} diff --git a/fixtures/build/dir017/composer.json b/fixtures/build/dir017/composer.json new file mode 100644 index 000000000..d62d6cb25 --- /dev/null +++ b/fixtures/build/dir017/composer.json @@ -0,0 +1,16 @@ +{ + "bin": "index.php", + "require": { + "php": "^8.1", + "nikic/iter": "^2.2" + }, + "require-dev": { + "phpstan/phpstan": "^1.9", + "phpstan/extension-installer": "^1.2" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } + } +} diff --git a/fixtures/build/dir017/composer.lock b/fixtures/build/dir017/composer.lock new file mode 100644 index 000000000..0548fcf3e --- /dev/null +++ b/fixtures/build/dir017/composer.lock @@ -0,0 +1,175 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "9e21f6cdff196792db041ce08ffaf807", + "packages": [ + { + "name": "nikic/iter", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/iter.git", + "reference": "d1323929952ddcb0b06439991f93bde3816a39e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/iter/zipball/d1323929952ddcb0b06439991f93bde3816a39e9", + "reference": "d1323929952ddcb0b06439991f93bde3816a39e9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/iter.func.php", + "src/iter.php", + "src/iter.rewindable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Iteration primitives using generators", + "keywords": [ + "functional", + "generator", + "iterator" + ], + "support": { + "issues": "https://github.com/nikic/iter/issues", + "source": "https://github.com/nikic/iter/tree/v2.2.0" + }, + "time": "2021-08-02T15:04:32+00:00" + } + ], + "packages-dev": [ + { + "name": "phpstan/extension-installer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "f06dbb052ddc394e7896fcd1cfcd533f9f6ace40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f06dbb052ddc394e7896fcd1cfcd533f9f6ace40", + "reference": "f06dbb052ddc394e7896fcd1cfcd533f9f6ace40", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.8.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.2.0" + }, + "time": "2022-10-17T12:59:16+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.9.17", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/204e459e7822f2c586463029f5ecec31bb45a1f2", + "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/phpstan/issues", + "source": "https://github.com/phpstan/phpstan/tree/1.9.17" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "type": "tidelift" + } + ], + "time": "2023-02-08T12:25:00+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/fixtures/build/dir017/expected-output b/fixtures/build/dir017/expected-output new file mode 100644 index 000000000..aad043373 --- /dev/null +++ b/fixtures/build/dir017/expected-output @@ -0,0 +1,2 @@ +The package "nikic/iter" is installed. +The package "phpstan/extension-installer" is NOT installed. diff --git a/fixtures/build/dir017/index.php b/fixtures/build/dir017/index.php new file mode 100644 index 000000000..49b0891be --- /dev/null +++ b/fixtures/build/dir017/index.php @@ -0,0 +1,31 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +use Composer\InstalledVersions; +use function iter\toIter; + +require __DIR__.'/vendor/autoload.php'; + +$packages = toIter([ + 'nikic/iter', + 'phpstan/extension-installer', +]); + +foreach ($packages as $package) { + echo sprintf( + 'The package "%s" is %sinstalled.'.PHP_EOL, + $package, + InstalledVersions::isInstalled($package) ? '' : 'NOT ', + ); +} diff --git a/src/Composer/ComposerOrchestrator.php b/src/Composer/ComposerOrchestrator.php index 1aaec6390..9d0264d7b 100644 --- a/src/Composer/ComposerOrchestrator.php +++ b/src/Composer/ComposerOrchestrator.php @@ -25,6 +25,9 @@ use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\Process; +use function file_exists; +use function file_get_contents; +use function file_put_contents; use function KevinGH\Box\FileSystem\dump_file; use function KevinGH\Box\FileSystem\file_contents; use function preg_replace; @@ -114,7 +117,8 @@ public static function dumpAutoload( $composerExecutable = $composerBin ?? self::retrieveComposerExecutable(); - self::dumpAutoloader($composerExecutable, true === $excludeDevFiles, $logger); + self::removeDevInstallPackages($composerExecutable, $excludeDevFiles, $logger); + self::dumpAutoloader($composerExecutable, $excludeDevFiles, $logger); if ('' !== $prefix) { $autoloadFile = self::retrieveAutoloadFile($composerExecutable, $logger); @@ -177,6 +181,62 @@ private static function retrieveComposerExecutable(): string return $composer; } + private static function removeDevInstallPackages(string $composerExecutable, bool $noDev, CompilerLogger $logger): void + { + if (false === $noDev) { + return; + } + + $composerCommand = [$composerExecutable, 'install', '--no-dev', '--no-scripts', '--no-plugins']; + + if (null !== $verbosity = self::retrieveSubProcessVerbosity($logger->getIO())) { + $composerCommand[] = $verbosity; + } + + if ($logger->getIO()->isDecorated()) { + $composerCommand[] = '--ansi'; + } + + $removeDevInstallPackages = new Process($composerCommand); + + $logger->log( + CompilerLogger::CHEVRON_PREFIX, + $removeDevInstallPackages->getCommandLine(), + OutputInterface::VERBOSITY_VERBOSE, + ); + + $composerInstalledVersionsPath = 'vendor/composer/InstalledVersions.php'; + + if (file_exists($composerInstalledVersionsPath)) { + $composerInstalledVersionsContents = file_get_contents($composerInstalledVersionsPath); + } + + $removeDevInstallPackages->run(null, self::getDefaultEnvVars()); + + if (false === $removeDevInstallPackages->isSuccessful()) { + throw new RuntimeException( + 'Could not remove the dev packages.', + 0, + new ProcessFailedException($removeDevInstallPackages), + ); + } + + if ('' !== $output = $removeDevInstallPackages->getOutput()) { + $logger->getIO()->writeln($output, OutputInterface::VERBOSITY_VERBOSE); + } + + if ('' !== $output = $removeDevInstallPackages->getErrorOutput()) { + $logger->getIO()->writeln($output, OutputInterface::VERBOSITY_VERBOSE); + } + + if (isset($composerInstalledVersionsContents)) { + file_put_contents( + $composerInstalledVersionsPath, + $composerInstalledVersionsContents, + ); + } + } + private static function dumpAutoloader(string $composerExecutable, bool $noDev, CompilerLogger $logger): void { $composerCommand = [$composerExecutable, 'dump-autoload', '--classmap-authoritative']; diff --git a/tests/Composer/ComposerOrchestratorComposer22Test.php b/tests/Composer/ComposerOrchestratorComposer22Test.php index 8f3e7dab9..465d189c9 100644 --- a/tests/Composer/ComposerOrchestratorComposer22Test.php +++ b/tests/Composer/ComposerOrchestratorComposer22Test.php @@ -343,16 +343,6 @@ public function test_it_can_dump_the_autoloader_with_a_composer_json_lock_and_in 'composer.json', 'composer.lock', 'vendor/autoload.php', - 'vendor/beberlei/assert/composer.json', - 'vendor/beberlei/assert/lib/Assert/Assert.php', - 'vendor/beberlei/assert/lib/Assert/Assertion.php', - 'vendor/beberlei/assert/lib/Assert/AssertionChain.php', - 'vendor/beberlei/assert/lib/Assert/AssertionFailedException.php', - 'vendor/beberlei/assert/lib/Assert/functions.php', - 'vendor/beberlei/assert/lib/Assert/InvalidArgumentException.php', - 'vendor/beberlei/assert/lib/Assert/LazyAssertion.php', - 'vendor/beberlei/assert/lib/Assert/LazyAssertionException.php', - 'vendor/beberlei/assert/LICENSE', 'vendor/composer/autoload_classmap.php', 'vendor/composer/autoload_namespaces.php', 'vendor/composer/autoload_psr4.php', diff --git a/tests/Composer/ComposerOrchestratorComposer23Test.php b/tests/Composer/ComposerOrchestratorComposer23Test.php index 65cc51304..3a26e3704 100644 --- a/tests/Composer/ComposerOrchestratorComposer23Test.php +++ b/tests/Composer/ComposerOrchestratorComposer23Test.php @@ -353,16 +353,6 @@ public function test_it_can_dump_the_autoloader_with_a_composer_json_lock_and_in 'composer.json', 'composer.lock', 'vendor/autoload.php', - 'vendor/beberlei/assert/composer.json', - 'vendor/beberlei/assert/lib/Assert/Assert.php', - 'vendor/beberlei/assert/lib/Assert/Assertion.php', - 'vendor/beberlei/assert/lib/Assert/AssertionChain.php', - 'vendor/beberlei/assert/lib/Assert/AssertionFailedException.php', - 'vendor/beberlei/assert/lib/Assert/functions.php', - 'vendor/beberlei/assert/lib/Assert/InvalidArgumentException.php', - 'vendor/beberlei/assert/lib/Assert/LazyAssertion.php', - 'vendor/beberlei/assert/lib/Assert/LazyAssertionException.php', - 'vendor/beberlei/assert/LICENSE', 'vendor/composer/autoload_classmap.php', 'vendor/composer/autoload_namespaces.php', 'vendor/composer/autoload_psr4.php', diff --git a/tests/Composer/ComposerOrchestratorComposer24Test.php b/tests/Composer/ComposerOrchestratorComposer24Test.php index 036163ee8..e25fbc138 100644 --- a/tests/Composer/ComposerOrchestratorComposer24Test.php +++ b/tests/Composer/ComposerOrchestratorComposer24Test.php @@ -377,16 +377,6 @@ public function test_it_can_dump_the_autoloader_with_a_composer_json_lock_and_in 'composer.json', 'composer.lock', 'vendor/autoload.php', - 'vendor/beberlei/assert/composer.json', - 'vendor/beberlei/assert/lib/Assert/Assert.php', - 'vendor/beberlei/assert/lib/Assert/Assertion.php', - 'vendor/beberlei/assert/lib/Assert/AssertionChain.php', - 'vendor/beberlei/assert/lib/Assert/AssertionFailedException.php', - 'vendor/beberlei/assert/lib/Assert/functions.php', - 'vendor/beberlei/assert/lib/Assert/InvalidArgumentException.php', - 'vendor/beberlei/assert/lib/Assert/LazyAssertion.php', - 'vendor/beberlei/assert/lib/Assert/LazyAssertionException.php', - 'vendor/beberlei/assert/LICENSE', 'vendor/composer/autoload_classmap.php', 'vendor/composer/autoload_namespaces.php', 'vendor/composer/autoload_psr4.php', diff --git a/tests/Console/Command/CompileTest.php b/tests/Console/Command/CompileTest.php index f8ccadb48..cff9c860b 100644 --- a/tests/Console/Command/CompileTest.php +++ b/tests/Console/Command/CompileTest.php @@ -85,7 +85,7 @@ class CompileTest extends FileSystemTestCase { use RequiresPharReadonlyOff; - private const NUMBER_OF_FILES = 48; + private const NUMBER_OF_FILES = 50; private const BOX_FILES = [ '/.box/', @@ -138,6 +138,8 @@ class CompileTest extends FileSystemTestCase '/vendor/autoload.php', '/vendor/composer/', '/vendor/composer/ClassLoader.php', + '/vendor/composer/InstalledVersions.php', + '/vendor/composer/installed.php', '/vendor/composer/LICENSE', '/vendor/composer/autoload_classmap.php', '/vendor/composer/autoload_namespaces.php', @@ -644,7 +646,7 @@ public function test_it_can_build_a_phar_with_complete_mapping(): void No recommendation found. No warning found. - // PHAR: 13 files (100B) + // PHAR: 15 files (100B) // You can inspect the generated PHAR with the "info" command. // Memory usage: 5.00MB (peak: 10.00MB), time: 0.00s @@ -709,7 +711,9 @@ public function test_it_can_build_a_phar_with_complete_mapping(): void '/vendor/autoload.php', '/vendor/composer/', '/vendor/composer/ClassLoader.php', + '/vendor/composer/InstalledVersions.php', '/vendor/composer/LICENSE', + '/vendor/composer/installed.php', '/vendor/composer/autoload_classmap.php', '/vendor/composer/autoload_namespaces.php', '/vendor/composer/autoload_psr4.php', @@ -805,7 +809,7 @@ public function test_it_can_build_a_phar_file_in_verbose_mode(): void $shebang = sprintf('#!%s', (new PhpExecutableFinder())->find()); - $expectedNumberOfClasses = 1; + $expectedNumberOfClasses = 4; $expectedNumberOfFiles = self::NUMBER_OF_FILES; dump_file( @@ -888,6 +892,9 @@ public function test_it_can_build_a_phar_file_in_verbose_mode(): void 'rand' => {$rand}, ) ? Dumping the Composer autoloader + > '/usr/local/bin/composer' 'install' '--no-dev' '--no-scripts' '--no-plugins' + Composer install output + > '/usr/local/bin/composer' 'dump-autoload' '--classmap-authoritative' '--no-dev' Generating optimized autoload files (authoritative) Generated optimized autoload files (authoritative) containing {$expectedNumberOfClasses} classes @@ -917,6 +924,7 @@ public function test_it_can_build_a_phar_file_in_verbose_mode(): void $expected, ExitCode::SUCCESS, self::createComposerPathNormalizer(), + self::createComposerInstallNoDevNormalizer(), ); } @@ -930,7 +938,7 @@ public function test_it_can_build_a_phar_file_in_very_verbose_mode(): void $shebang = sprintf('#!%s', (new PhpExecutableFinder())->find()); - $expectedNumberOfClasses = 1; + $expectedNumberOfClasses = 4; $expectedNumberOfFiles = self::NUMBER_OF_FILES; dump_file( @@ -1017,6 +1025,9 @@ public function test_it_can_build_a_phar_file_in_very_verbose_mode(): void 'rand' => {$rand}, ) ? Dumping the Composer autoloader + > '/usr/local/bin/composer' 'install' '--no-dev' '--no-scripts' '--no-plugins' '-v' + Composer install output + > '/usr/local/bin/composer' 'dump-autoload' '--classmap-authoritative' '--no-dev' '-v' Generating optimized autoload files (authoritative) Generated optimized autoload files (authoritative) containing {$expectedNumberOfClasses} classes @@ -1052,6 +1063,7 @@ public function test_it_can_build_a_phar_file_in_very_verbose_mode(): void $expected, ExitCode::SUCCESS, self::createComposerPathNormalizer(), + self::createComposerInstallNoDevNormalizer(), ); } @@ -3085,6 +3097,15 @@ private static function createComposerVersionNormalizer(): callable ); } + private static function createComposerInstallNoDevNormalizer(): callable + { + return static fn (string $output): string => preg_replace( + '/No composer\.lock file present\.[\s\S]*Generating autoload files/', + 'Composer install output', + $output, + ); + } + private function retrievePharFiles(Phar $phar, ?Traversable $traversable = null): array { $root = 'phar://'.str_replace('\\', '/', realpath($phar->getPath())).'/';