Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/phar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,46 @@
- '.github/workflows/phar.yml'
- '.github/scripts/**'

turbo-missing-note:
name: "Note about a phar without PHPStan Turbo"
needs: compiler-tests
runs-on: "ubuntu-latest"
timeout-minutes: 10

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

# the compiler-tests job runs the phar on PHP 8.2, where no binary exists
# and the note is not printed
- name: "Install PHP"
uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same shivammathur/setup-php action, pinned to the same commit, that phar.yml already uses in 13 other steps. The new job needs PHP 8.3 or newer, so it installs PHP the same way as the other jobs.

with:
coverage: "none"
php-version: "8.4"

- name: "Download PHAR"
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: phar-file
path: bare

# a copy of phpstan.phar alone prints the note, the same phar with a
# turbo-ext/ directory next to it (as in the Composer package) does not
- name: "Check the note"
run: |
printf '<?php\n\necho 1;\n' > test.php
php bare/phpstan.phar analyse -l 0 --no-progress test.php 2> bare.err
cat bare.err
grep -q 'PHPStan runs without PHPStan Turbo' bare.err
mkdir -p with-turbo-ext/turbo-ext
cp bare/phpstan.phar with-turbo-ext/
php with-turbo-ext/phpstan.phar analyse -l 0 --no-progress test.php 2> with-turbo-ext.err
cat with-turbo-ext.err
! grep -q 'PHPStan Turbo' with-turbo-ext.err

turbo-version:
name: "Turbo Extension Version Check"
runs-on: "ubuntu-latest"
Expand Down
11 changes: 11 additions & 0 deletions src/Command/AnalyseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use PHPStan\Internal\DirectoryCreatorException;
use PHPStan\ShouldNotHappenException;
use PHPStan\Turbo\TurboExtensionEnabler;
use PHPStan\Turbo\TurboExtensionSelector;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
Expand All @@ -56,6 +57,7 @@
use function is_string;
use function microtime;
use function pathinfo;
use function preg_match;
use function rewind;
use function sprintf;
use function str_contains;
Expand Down Expand Up @@ -340,6 +342,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
TurboExtensionEnabler::EXPECTED_EXTENSION_VERSION,
$phpstanVersion === ComposerHelper::UNKNOWN_VERSION ? 'your PHPStan version' : sprintf('PHPStan %s', $phpstanVersion),
));
} elseif (TurboExtensionSelector::isMissingNextToPhar()) {
$phpstanVersion = ComposerHelper::getPhpStanVersion();
$errorOutput->getStyle()->note(sprintf(
'PHPStan runs without PHPStan Turbo.' . "\n"
. 'With PHPStan Turbo, analysis is up to 60%% faster.' . "\n"
. 'Install PHPStan with Composer: composer require --dev phpstan/phpstan' . "\n"
. 'Or install PHPStan Turbo with PIE: pie install %s',
preg_match('/^\d+\.\d+\.\d+$/', $phpstanVersion) === 1 ? sprintf('phpstan/turbo:%s', $phpstanVersion) : 'phpstan/turbo',
));
}

if ($pro) {
Expand Down
67 changes: 51 additions & 16 deletions src/Turbo/TurboExtensionSelector.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Phar;
use function dirname;
use function file_get_contents;
use function is_dir;
use function is_file;
use function php_uname;
use function sprintf;
Expand Down Expand Up @@ -74,32 +75,38 @@ public static function findExtensionForWorkers(): ?string
}

/**
* Locates the distributed extension binary for the current platform —
* present only next to a phar-based installation.
* Whether PHPStan runs from a bare copy of phpstan.phar, without the
* turbo-ext/ directory the phpstan/phpstan Composer package ships next to
* it, on a runtime the extension is built for, and no php.ini loads the
* extension either.
*/
public static function findExtension(): ?string
public static function isMissingNextToPhar(): bool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should only error for php-runtime-versions which are compatible with turbo

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm should it share most logic with findExtension?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5a24d48: both methods now share one helper. The note only shows where findExtension() would look for a binary. That means PHP 8.3 or newer, not a debug build, a phar, and a platform with binaries.

There is no upper bound yet, in either method. A bare phar on a future PHP 8.7 would show the note before an 8.7 binary exists. Should I add one?

{
if (PHP_VERSION_ID < self::MINIMUM_PHP_VERSION_ID) {
return null;
$platformDirectory = self::getPlatformDirectoryNextToPhar();
if ($platformDirectory === null) {
return false;
}
if ((bool) PHP_DEBUG) {
return null;
if (TurboExtensionEnabler::isLoaded()) {
return false;
}

$pharPath = Phar::running(false);
if ($pharPath === '') {
return null;
}
return !is_dir(dirname($platformDirectory));
}

$platform = self::resolvePlatformDirectory(PHP_OS_FAMILY, php_uname('m'), self::isMusl());
if ($platform === null) {
/**
* Locates the distributed extension binary for the current platform —
* present only next to a phar-based installation.
*/
public static function findExtension(): ?string
{
$platformDirectory = self::getPlatformDirectoryNextToPhar();
if ($platformDirectory === null) {
return null;
}

$file = sprintf(
'%s/turbo-ext/%s/phpstan_turbo-%d.%d%s.%s',
dirname($pharPath),
$platform,
'%s/phpstan_turbo-%d.%d%s.%s',
$platformDirectory,
PHP_MAJOR_VERSION,
PHP_MINOR_VERSION,
(bool) PHP_ZTS ? '-zts' : '',
Expand All @@ -118,6 +125,34 @@ public static function findExtension(): ?string
return $file;
}

/**
* The directory of turbo-ext/ next to the running phar that holds the
* binaries for this platform, whether it exists or not. Null when no
* binary is built for this runtime: PHP older than the minimum, a debug
* build, a run from source, or a platform without binaries.
*/
private static function getPlatformDirectoryNextToPhar(): ?string
{
if (PHP_VERSION_ID < self::MINIMUM_PHP_VERSION_ID) {
return null;
}
if ((bool) PHP_DEBUG) {
return null;
}

$pharPath = Phar::running(false);
if ($pharPath === '') {
return null;
}

$platform = self::resolvePlatformDirectory(PHP_OS_FAMILY, php_uname('m'), self::isMusl());
if ($platform === null) {
return null;
}

return sprintf('%s/turbo-ext/%s', dirname($pharPath), $platform);
}

/**
* The shared core the extension binary loads, in the same directory.
*/
Expand Down
Loading