diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-15 07:37:45 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-15 07:37:45 +0900 |
| commit | 548463bad1f72c97f68b47f54a263a4f4ad87b3a (patch) | |
| tree | 1285d740611be3ad175f9e57c61d880b500dd818 /scripts/linters | |
| parent | ed8694f89eb7702eb7e617af29f8f444f32b8d3c (diff) | |
| download | php-shirabe-548463bad1f72c97f68b47f54a263a4f4ad87b3a.tar.gz php-shirabe-548463bad1f72c97f68b47f54a263a4f4ad87b3a.tar.zst php-shirabe-548463bad1f72c97f68b47f54a263a4f4ad87b3a.zip | |
feat(php-rpc): embed the Composer PHP runtime in the executable
Plugins and scripts need the real `Composer\` classes and the packages
Composer depends on, which so far came from a checkout found through
SHIRABE_COMPOSER_PHP_DIR or a path next to the workspace. Neither exists
for a distributed binary.
The build script now archives those PHP sources into a phar the way
Compiler.php does and the executable carries it. The worker maps it with
Phar::loadPhar and reads a content-addressed sentinel back to tell a
bundle it can use from one it cannot; where its PHP cannot open the phar,
the bundle is unpacked once into the cache directory and autoloaded from
there. SHIRABE_COMPOSER_PHP_DIR still overrides both for development.
PHP locates a phar's manifest by the first __HALT_COMPILER(); token in
the file, so the executable must hold no other copy of it: phar.rs builds
the token at run time, and a linter keeps further literals out of the
sources that reach the binary.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'scripts/linters')
| -rwxr-xr-x | scripts/linters/lint | 4 | ||||
| -rw-r--r-- | scripts/linters/src/Linters/NoHaltCompilerLiteral.php | 79 |
2 files changed, 83 insertions, 0 deletions
diff --git a/scripts/linters/lint b/scripts/linters/lint index 5f59945d..e90c3e0c 100755 --- a/scripts/linters/lint +++ b/scripts/linters/lint @@ -11,6 +11,7 @@ use Shirabe\Lint\Linters\NoBannedUse; use Shirabe\Lint\Linters\NoDecorativeSectionComment; use Shirabe\Lint\Linters\NoExceptionDowncast; use Shirabe\Lint\Linters\NoFormatTrailingComma; +use Shirabe\Lint\Linters\NoHaltCompilerLiteral; use Shirabe\Lint\Linters\NoModRs; use Shirabe\Lint\Linters\NoStdCollectionsMaps; use Shirabe\Lint\Linters\NoUseAsAlias; @@ -39,6 +40,9 @@ $runner = new Runner($rootDir, [ 'crates/shirabe/src/package/loader/root_package_loader.rs', 'crates/shirabe-spdx-licenses/src/spdx_licenses.rs', ]], + [new NoHaltCompilerLiteral(), [ + 'crates/shirabe-php-rpc/build.rs', + ]], [new NoModRs(), []], [new NoStdCollectionsMaps(), []], [new NoUseAsAlias(), []], diff --git a/scripts/linters/src/Linters/NoHaltCompilerLiteral.php b/scripts/linters/src/Linters/NoHaltCompilerLiteral.php new file mode 100644 index 00000000..30520ff7 --- /dev/null +++ b/scripts/linters/src/Linters/NoHaltCompilerLiteral.php @@ -0,0 +1,79 @@ +<?php + +declare(strict_types=1); + +namespace Shirabe\Lint\Linters; + +use Shirabe\Lint\Linter; +use Shirabe\Lint\Support\FileFinder; +use Shirabe\Lint\Support\Paths; + +final class NoHaltCompilerLiteral implements Linter +{ + // Assembled so that this file does not contain what it looks for. + private const TOKEN = '__' . 'HALT_COMPILER();'; + + public function name(): string + { + return 'no_halt_compiler_literal'; + } + + public function failureIntro(): string + { + return "Found a literal `" . self::TOKEN . "`.\n" + . "The executable carries the Composer runtime bundle as a phar that PHP finds by\n" + . "scanning for the first occurrence of that token, and every literal here ends up in\n" + . "the same binary, so an earlier one shadows the bundle. Build the token at run time\n" + . "instead — see " + . '`halt_compiler_token` in `crates/shirabe-php-shim/src/phar.rs`:'; + } + + public function check(string $rootDir, array $excludes): array + { + $errors = []; + + foreach ($this->embeddedFiles($rootDir) as $path) { + $relative = Paths::relativeTo($rootDir, $path); + if (in_array($relative, $excludes, true)) { + continue; + } + + foreach (file($path) as $idx => $raw) { + if (!str_contains($raw, self::TOKEN)) { + continue; + } + + $errors[] = "{$relative}:" . ($idx + 1) . ': ' . trim($raw); + } + } + + return $errors; + } + + /** + * The sources whose bytes reach the binary: Rust code, and the PHP files the Rust code + * embeds with `include_str!`. + * + * @return list<string> + */ + private function embeddedFiles(string $rootDir): array + { + $paths = FileFinder::rustFiles($rootDir); + + foreach (glob("{$rootDir}/crates/*/php", GLOB_ONLYDIR) ?: [] as $phpDir) { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($phpDir, \FilesystemIterator::SKIP_DOTS), + ); + foreach ($iterator as $file) { + /** @var \SplFileInfo $file */ + if ($file->isFile()) { + $paths[] = $file->getPathname(); + } + } + } + + sort($paths); + + return $paths; + } +} |
