diff --git a/composer.json b/composer.json index cd7c7a1f66..88e2729105 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "composer-runtime-api": "^2.0", "clue/ndjson-react": "^1.0", "composer/ca-bundle": "^1.2", + "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.3", "fidry/cpu-core-counter": "^0.5.0", "hoa/compiler": "3.17.08.08", diff --git a/composer.lock b/composer.lock index 8ccc5c9d9f..37783b1e98 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "15c269b06d7fb91ea60c99e978d51483", + "content-hash": "fa85814150e9cc7a232f2da08daec7b7", "packages": [ { "name": "clue/ndjson-react", @@ -217,6 +217,87 @@ ], "time": "2024-03-19T10:26:25+00:00" }, + { + "name": "composer/semver", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2023-08-31T09:50:34+00:00" + }, { "name": "composer/xdebug-handler", "version": "3.0.5", diff --git a/src/Command/CommandHelper.php b/src/Command/CommandHelper.php index 32fd7bae98..d38aee7f23 100644 --- a/src/Command/CommandHelper.php +++ b/src/Command/CommandHelper.php @@ -2,6 +2,7 @@ namespace PHPStan\Command; +use Composer\Semver\Semver; use Composer\XdebugHandler\XdebugHandler; use Nette\DI\Helpers; use Nette\DI\InvalidConfigurationException; @@ -23,6 +24,8 @@ use PHPStan\File\FileExcluder; use PHPStan\File\FileFinder; use PHPStan\File\FileHelper; +use PHPStan\File\SimpleRelativePathHelper; +use PHPStan\Internal\ComposerHelper; use PHPStan\Internal\DirectoryCreator; use PHPStan\Internal\DirectoryCreatorException; use PHPStan\PhpDoc\StubFilesProvider; @@ -279,6 +282,43 @@ public static function begin( $additionalConfigFiles[] = $includedFilePath; } } + + if ( + count($additionalConfigFiles) > 0 + && $generatedConfigReflection->hasConstant('PHPSTAN_VERSION_CONSTRAINT') + ) { + $generatedConfigPhpStanVersionConstraint = $generatedConfigReflection->getConstant('PHPSTAN_VERSION_CONSTRAINT'); + if ($generatedConfigPhpStanVersionConstraint !== null) { + $phpstanSemverVersion = ComposerHelper::getPhpStanVersion(); + if ( + $phpstanSemverVersion !== ComposerHelper::UNKNOWN_VERSION + && !str_contains($phpstanSemverVersion, '@') + && !Semver::satisfies($phpstanSemverVersion, $generatedConfigPhpStanVersionConstraint) + ) { + $errorOutput->writeLineFormatted('Running PHPStan with incompatible extensions'); + $errorOutput->writeLineFormatted('You\'re running PHPStan from a different Composer project'); + $errorOutput->writeLineFormatted('than the one where you installed extensions.'); + $errorOutput->writeLineFormatted(''); + $errorOutput->writeLineFormatted(sprintf('Your PHPStan version is: %s', $phpstanSemverVersion)); + $errorOutput->writeLineFormatted(sprintf('Installed PHPStan extensions support: %s', $generatedConfigPhpStanVersionConstraint)); + + $errorOutput->writeLineFormatted(''); + if (isset($_SERVER['argv'][0]) && is_file($_SERVER['argv'][0])) { + $mainScript = $_SERVER['argv'][0]; + $errorOutput->writeLineFormatted(sprintf('PHPStan is running from: %s', $currentWorkingDirectoryFileHelper->absolutizePath(dirname($mainScript)))); + } + + $errorOutput->writeLineFormatted(sprintf('Extensions were installed in: %s', dirname($generatedConfigDirectory, 3))); + $errorOutput->writeLineFormatted(''); + + $simpleRelativePathHelper = new SimpleRelativePathHelper($currentWorkingDirectory); + $errorOutput->writeLineFormatted(sprintf('Run PHPStan with %s to fix this problem.', $simpleRelativePathHelper->getRelativePath(dirname($generatedConfigDirectory, 3) . '/bin/phpstan'))); + + $errorOutput->writeLineFormatted(''); + throw new InceptionNotSuccessfulException(); + } + } + } } if ( diff --git a/src/Internal/ComposerHelper.php b/src/Internal/ComposerHelper.php index 322d7f1048..e1995bc34d 100644 --- a/src/Internal/ComposerHelper.php +++ b/src/Internal/ComposerHelper.php @@ -17,6 +17,8 @@ final class ComposerHelper { + public const UNKNOWN_VERSION = 'Unknown version'; + private static ?string $phpstanVersion = null; /** @return array|null */ @@ -75,7 +77,7 @@ public static function getPhpStanVersion(): string $installed = require __DIR__ . '/../../vendor/composer/installed.php'; $rootPackage = $installed['root'] ?? null; if ($rootPackage === null) { - return self::$phpstanVersion = 'Unknown version'; + return self::$phpstanVersion = self::UNKNOWN_VERSION; } if (preg_match('/[^v\d.]/', $rootPackage['pretty_version']) === 0) {