Skip to content

Commit 99cbdf9

Browse files
committed
Restart into PHPStan Turbo and pass the extension to the workers
1 parent 8733317 commit 99cbdf9

4 files changed

Lines changed: 79 additions & 12 deletions

File tree

‎bin/rector.php‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
use Composer\XdebugHandler\XdebugHandler;
66
use Nette\Utils\Json;
7+
use PHPStan\PharAutoloader;
8+
use PHPStan\Turbo\TurboExtensionEnabler;
9+
use PHPStan\Turbo\TurboProcessRestarter;
710
use Rector\Bootstrap\AutoloadFileParameterResolver;
811
use Rector\Bootstrap\RectorConfigsResolver;
912
use Rector\ChangesReporting\Output\JsonOutputFormatter;
@@ -30,6 +33,26 @@
3033
$autoloadIncluder = new AutoloadIncluder();
3134
$autoloadIncluder->includeDependencyOrRepositoryVendorAutoloadIfExists();
3235

36+
// PHPStan Turbo replaces php-parser classes, so it is activated before preload.php declares them.
37+
// The same single restart also turns Xdebug off.
38+
$phpstanBootstrapFile = __DIR__ . '/../../../phpstan/phpstan/bootstrap.php';
39+
if (! class_exists(PharAutoloader::class) && is_file($phpstanBootstrapFile)) {
40+
// Rector installed as a dependency: the project autoloader that loads PHPStan comes after preload.php,
41+
// so load PHPStan's bootstrap now, and mark it as loaded with the identifier Composer gives the file
42+
$GLOBALS['__composer_autoload_files'][md5('phpstan/phpstan:bootstrap.php')] = true;
43+
require_once $phpstanBootstrapFile;
44+
}
45+
46+
if (class_exists(PharAutoloader::class)) {
47+
$phpstanPharPath = dirname((string) new ReflectionClass(PharAutoloader::class)->getFileName()) . '/phpstan.phar';
48+
TurboProcessRestarter::restartIfSuitable($_SERVER['argv'], $phpstanPharPath, 'rector');
49+
TurboExtensionEnabler::trustOwnTypesIfSuitable($_SERVER['argv'], $phpstanPharPath);
50+
TurboExtensionEnabler::activateIfCompatible($phpstanPharPath);
51+
unset($phpstanPharPath);
52+
}
53+
54+
unset($phpstanBootstrapFile);
55+
3356
final class AutoloadIncluder
3457
{
3558
/**

‎composer.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"nikic/php-parser": "^5.9",
2525
"ondram/ci-detector": "^4.2",
2626
"phpstan/phpdoc-parser": "^2.3.3",
27-
"phpstan/phpstan": "^2.3.0",
27+
"phpstan/phpstan": "^2.3.2",
2828
"react/child-process": "^0.6.5",
2929
"react/event-loop": "^1.6",
3030
"react/socket": "^1.17",

‎src/Parallel/Command/WorkerCommandLineFactory.php‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Rector\Parallel\Command;
66

7+
use PHPStan\Turbo\TurboExtensionSelector;
78
use Rector\ChangesReporting\Output\JsonOutputFormatter;
89
use Rector\Configuration\Option;
910
use Rector\FileSystem\FilePathHelper;
@@ -46,7 +47,7 @@ public function create(
4647
$commandArguments = array_merge(['process'], $commandArguments);
4748
}
4849

49-
$args = [PHP_BINARY, $mainScript, ...$commandArguments];
50+
$args = [PHP_BINARY, ...$this->createTurboExtensionArguments(), $mainScript, ...$commandArguments];
5051
$workerCommandArray = [];
5152

5253
$mainCommand = $this->commandFromReflectionFactory->create($mainCommandClass);
@@ -140,6 +141,28 @@ private function shouldSkipOption(InputInterface $input, string $optionName): bo
140141
return $optionName === Option::OUTPUT_FORMAT || $optionName === Option::CONFIG;
141142
}
142143

144+
/**
145+
* Spawned workers do not inherit command-line -d options, so PHPStan Turbo is passed to them explicitly.
146+
* This also covers a main process that did not restart into Turbo, for example without pcntl.
147+
*
148+
* @return string[]
149+
*/
150+
private function createTurboExtensionArguments(): array
151+
{
152+
$extensionPath = TurboExtensionSelector::findExtensionForWorkers();
153+
if ($extensionPath === null) {
154+
return [];
155+
}
156+
157+
// quote the value, so PHP parses it as a string even when the path contains an operator like ~
158+
return [
159+
'-d',
160+
"extension='" . $extensionPath . "'",
161+
'-d',
162+
TurboExtensionSelector::EXTENSION_PATH_INI . "='" . $extensionPath . "'",
163+
];
164+
}
165+
143166
/**
144167
* @return string[]
145168
*/

‎tests/Parallel/Command/WorkerCommandLineFactoryTest.php‎

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Rector\Tests\Parallel\Command;
66

77
use Iterator;
8+
use PHPStan\Turbo\TurboExtensionSelector;
89
use PHPUnit\Framework\Attributes\DataProvider;
910
use Rector\ChangesReporting\Output\ConsoleOutputFormatter;
1011
use Rector\Configuration\Option;
@@ -72,7 +73,7 @@ public static function provideDataSpacedMainScript(): Iterator
7273
self::COMMAND => 'process',
7374
Option::SOURCE => ['src'],
7475
],
75-
"'" . PHP_BINARY . "' '" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
76+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
7677
];
7778

7879
yield [
@@ -81,7 +82,7 @@ public static function provideDataSpacedMainScript(): Iterator
8182
Option::SOURCE => ['src'],
8283
'--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME,
8384
],
84-
"'" . PHP_BINARY . "' '" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
85+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
8586
];
8687

8788
yield [
@@ -91,7 +92,7 @@ public static function provideDataSpacedMainScript(): Iterator
9192
'--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME,
9293
'--' . Option::DEBUG => true,
9394
],
94-
"'" . PHP_BINARY . "' '" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
95+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
9596
];
9697

9798
yield [
@@ -102,7 +103,7 @@ public static function provideDataSpacedMainScript(): Iterator
102103
'--' . Option::DEBUG => true,
103104
'--' . Option::XDEBUG => true,
104105
],
105-
"'" . PHP_BINARY . "' '" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --xdebug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
106+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --xdebug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
106107
];
107108
}
108109

@@ -143,7 +144,7 @@ public static function provideData(): Iterator
143144
self::COMMAND => 'process',
144145
Option::SOURCE => ['src'],
145146
],
146-
"'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
147+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
147148
];
148149

149150
yield [
@@ -152,7 +153,7 @@ public static function provideData(): Iterator
152153
Option::SOURCE => ['src'],
153154
'--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME,
154155
],
155-
"'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
156+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
156157
];
157158

158159
yield [
@@ -162,7 +163,7 @@ public static function provideData(): Iterator
162163
'--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME,
163164
'--' . Option::DEBUG => true,
164165
],
165-
"'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
166+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
166167
];
167168

168169
yield [
@@ -173,7 +174,7 @@ public static function provideData(): Iterator
173174
'--' . Option::DEBUG => true,
174175
'--' . Option::XDEBUG => true,
175176
],
176-
"'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --xdebug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
177+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --xdebug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
177178
];
178179

179180
yield [
@@ -183,7 +184,7 @@ public static function provideData(): Iterator
183184
'--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME,
184185
'--' . Option::MEMORY_LIMIT => '-1',
185186
],
186-
"'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --memory-limit='-1' --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
187+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --memory-limit='-1' --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
187188
];
188189

189190
yield [
@@ -193,10 +194,30 @@ public static function provideData(): Iterator
193194
'--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME,
194195
'--' . Option::PHP => true,
195196
],
196-
"'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --php --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
197+
"'" . PHP_BINARY . "' " . self::turboExtensionArguments() . "'" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --php --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi",
197198
];
198199
}
199200

201+
/**
202+
* The worker gets PHPStan Turbo through two -d options, when PHPStan ships a binary for this runtime
203+
*/
204+
private static function turboExtensionArguments(): string
205+
{
206+
$extensionPath = TurboExtensionSelector::findExtensionForWorkers();
207+
if ($extensionPath === null) {
208+
return '';
209+
}
210+
211+
$arguments = [
212+
'-d',
213+
"extension='" . $extensionPath . "'",
214+
'-d',
215+
TurboExtensionSelector::EXTENSION_PATH_INI . "='" . $extensionPath . "'",
216+
];
217+
218+
return implode(' ', array_map(escapeshellarg(...), $arguments)) . ' ';
219+
}
220+
200221
private function cleanUpEmptyQuoteExpectedCommandOutput(string $result): string
201222
{
202223
if ($this->isWindows()) {

0 commit comments

Comments
 (0)