diff --git a/README-CN.md b/README-CN.md index 29709caf..a61bbf02 100644 --- a/README-CN.md +++ b/README-CN.md @@ -305,7 +305,7 @@ sources: if: PHP_OS_FAMILY == "Windows" # 将文件打包到二进制,并为 ZendVM 提供字节码及内存文件读取。 -bundled-files: +embedded-files: - vendor # 由项目自身的原生构建流程预编译。 @@ -337,19 +337,27 @@ ext-deps: `PHP_VERSION`、`PHP_VERSION_ID` 和 `PHP_OS_FAMILY`。命令行参数优先于 YAML 中的同名配置。原生链接依赖应写入 `link-libs`;`ext-deps` 会生成 `ZEND_MOD_REQUIRED`,缺少所需 PHP 扩展时由 Zend 拒绝加载模块。 -`bundled-files` 支持与 `sources` 相同的文件、目录及条件写法,仅在显式配置时启用。 +`embedded-files` 支持与 `sources` 相同的文件、目录及条件写法,仅在显式配置时启用。 所列文件全部打包进二进制;未通过 `sources` 成功原生编译的 PHP 文件由 OPcache -生成字节码。运行时的 `require` 和 `require_once` 从内存交给 ZendVM 执行, +生成字节码。用于 API 声明的 `.stub.php` 文件仍保留在原始文件包中,不生成可执行字节码。 +其他无法由 OPcache 编译的内嵌 PHP 文件会输出跳过日志,原始文件仍保留,但不进入 +可执行字节码表。运行时的 `require` 和 `require_once` 从内存交给 ZendVM 执行, 无需读取磁盘上的 PHP 文件。构建字节码的 PHP CLI、OPcache 与目标 PHP 运行时需匹配。 +因此使用 `embedded-files` 构建时,宿主机必须提供相应的 `php`/`php.exe` 和 +OPcache 扩展;只有 `tpc` 无法生成字节码。 运行二进制文件时无需 Composer 安装、磁盘 vendor 文件或 OPcache 扩展;Composer 自动加载文件也已嵌入,仍按需加载类。构建时有 OPcache 的情况下,匿名类在首次执行 -对应 `new class` 表达式时从同一字节码表加载。未配置 `bundled-files` 且缺少 +对应 `new class` 表达式时从同一字节码表加载。未配置 `embedded-files` 且缺少 OPcache 时,匿名类退回内嵌 PHP 代码方式。 仅当目录名为 `vendor` 且目录下存在 `autoload.php` 时,才会使用字节码缓存; -目录 mtime 及构建用的 PHP/OPcache 未变化时复用字节码。其他 `bundled-files` +目录 mtime 及构建用的 PHP/OPcache 未变化时复用字节码。其他 `embedded-files` 文件(包括缺少 `autoload.php` 的同名目录)每次构建都重新生成。 修改目录下已有文件不会更新目录 mtime;这种情况可用 `--force` 重新生成 -vendor 字节码。 +vendor 字节码。不可执行的 vendor PHP 文件也会缓存跳过结果,`--force` 可重新尝试。 +编译器生成的匿名类字节码按生成的 PHP 内容及构建用的 PHP/OPcache 单独缓存; +未变化的匿名类不会导致内嵌归档重新编译。 +Windows 上,TypePHP 通过 `rc.exe` 把归档链接为 PE 资源,由 phpx 字节码 helper +读取;归档未变化时复用 `.res` 文件。 通用的 `objects` 列表会把已有 `.o`/`.obj` 文件直接加入链接步骤。TypePHP 不会重新编译这些文件;原生编译器、目标架构、编译参数和增量构建均由项目负责。 使用目标通用参数的原生文件仍应放入 `sources`;仅当某个编译单元需要不同参数且 diff --git a/README.md b/README.md index 2665eed9..46bf51ec 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ sources: if: PHP_OS_FAMILY == "Windows" # Embed files for ZendVM execution and virtual file reads. -bundled-files: +embedded-files: - vendor # Precompiled by the project's external native build. @@ -370,24 +370,36 @@ directory; conditional entries support `PHP_VERSION`, `PHP_VERSION_ID`, and `PHP_OS_FAMILY`. CLI arguments override their YAML counterparts. Native linker dependencies belong in `link-libs`; `ext-deps` writes `ZEND_MOD_REQUIRED` entries so Zend can reject loading when a required PHP extension is missing. -`bundled-files` accepts files or directories with the same conditional syntax. +`embedded-files` accepts files or directories with the same conditional syntax. It is opt-in for embedded binary builds: all listed files are packed into the binary, and PHP files not successfully compiled from `sources` are stored as -OPcache bytecode. `require` and `require_once` load those scripts through -ZendVM without reading their PHP files from disk. The PHP CLI and OPcache used -to build the blobs must match the target PHP runtime. The resulting binary +OPcache bytecode. `.stub.php` API declaration files remain in the raw bundle +and are not compiled as executable bytecode. Other embedded PHP files that +OPcache cannot compile are reported and kept only as raw files; they cannot be +executed from the opcode table. `require` and `require_once` load scripts with +generated bytecode through ZendVM without reading their PHP files from disk. +The PHP CLI and OPcache used +to build the blobs must match the target PHP runtime. Building with +`embedded-files` therefore requires that CLI and its OPcache extension on the +build host; `tpc` alone cannot generate the blobs. The resulting binary does not need Composer installation, vendor files, or an OPcache extension at runtime: Composer's autoload files are embedded and still resolve classes on demand. When OPcache is available at build time, anonymous classes use the same opcode table when their `new class` expression is first executed. Without -`bundled-files`, builds lacking OPcache use embedded PHP code for anonymous +`embedded-files`, builds lacking OPcache use embedded PHP code for anonymous classes instead. Only a `vendor` directory containing `autoload.php` uses the opcode cache. Its blobs are reused while the directory mtime and build PHP/OPcache remain -unchanged. All other `bundled-files` files, including files in a `vendor` +unchanged. All other `embedded-files` files, including files in a `vendor` directory without `autoload.php`, are regenerated on every build. Directory mtime does not change when an existing nested file is edited; use `--force` -to regenerate vendor opcodes in that case. +to regenerate vendor opcodes in that case. Skipped non-executable vendor PHP +files share this cache and are retried with `--force`. +Compiler-generated anonymous class opcodes have a separate cache keyed by +their generated PHP contents and the build PHP/OPcache, so unchanged classes +do not cause the embedded archive to be recompiled. +On Windows, TypePHP links the archive as a PE resource with `rc.exe` and reads +it through the phpx opcode helper. An unchanged archive reuses the `.res` file. The generic `objects` list adds existing `.o`/`.obj` files directly to the link step. TypePHP never recompiles these files; the project owns their native compiler, architecture, flags, and incremental build. Keep native files that diff --git a/project.yml b/project.yml index 2972fa25..c61a5074 100644 --- a/project.yml +++ b/project.yml @@ -35,7 +35,7 @@ sources: - vendor/nikic/php-parser/lib/PhpParser/NodeVisitor.php - vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php -bundled-files: +embedded-files: - ./vendor ignore: diff --git a/src/Build/ResourceCompilationTrait.php b/src/Build/ResourceCompilationTrait.php index dc60103d..20712fe4 100644 --- a/src/Build/ResourceCompilationTrait.php +++ b/src/Build/ResourceCompilationTrait.php @@ -19,7 +19,7 @@ trait ResourceCompilationTrait return false; } $generator = $this->createResourceGenerator(); - return $generator !== null && $generator->hasResource(); + return ($generator !== null && $generator->hasResource()) || $this->embeddedArchiveFile !== null; } public function getResourceRcFile(): string @@ -48,18 +48,39 @@ trait ResourceCompilationTrait } $generator = $this->createResourceGenerator(); - if ($generator === null || !$generator->hasResource()) { + if (($generator === null || !$generator->hasResource()) && $this->embeddedArchiveFile === null) { return; } $rcFile = $this->getResourceRcFile(); - $rcContent = $generator->generate(); + $rcContent = $generator?->generate() ?? ''; + if ($this->embeddedArchiveFile !== null) { + $archive = $this->embeddedArchiveFile; + $archivePath = str_replace('\\', '/', $archive); + $rcContent .= '// archive-sha256: ' . hash_file('sha256', $archive) . PHP_EOL; + $rcContent .= '24103 RCDATA "' . addcslashes($archivePath, '"') . '"' . PHP_EOL; + } $this->writeFile($rcFile, "\xEF\xBB\xBF" . $rcContent); $this->climate->info('Generated resource file: ' . $rcFile); $backend = $this->getCompilerBackend(); if ($backend instanceof Msvc) { $resFile = $this->getResourceResFile(); + $inputs = [$rcContent]; + if ($generator !== null) { + foreach ([$generator->getIconPath(), $generator->getManifestPath()] as $file) { + if ($file !== null && is_file($file)) { + $inputs[] = hash_file('sha256', $file); + } + } + } + $cacheKey = hash('sha256', implode("\n", $inputs)); + $cacheFile = $this->getBuildDir() . '/cache/resource/app_resource.sha256'; + if (!$this->climate->arguments->defined('force') && is_file($resFile) + && is_file($cacheFile) && trim((string) file_get_contents($cacheFile)) === $cacheKey) { + $this->climate->darkGray('Resource compiled: ' . $resFile . ' [cache]'); + return; + } $cmd = $backend->compileResourceFile($rcFile, $resFile); $this->climate->comment($cmd); @@ -75,6 +96,13 @@ trait ResourceCompilationTrait if (!file_exists($resFile)) { $this->error('Resource file not generated: ' . $resFile); } + if (!is_dir(dirname($cacheFile)) && !mkdir(dirname($cacheFile), 0777, true) + && !is_dir(dirname($cacheFile))) { + throw new \RuntimeException('Cannot create resource cache directory: ' . dirname($cacheFile)); + } + if (file_put_contents($cacheFile, $cacheKey . PHP_EOL) === false) { + throw new \RuntimeException('Cannot write resource cache metadata: ' . $cacheFile); + } $this->climate->green('Resource compiled: ' . $resFile); } else { $this->climate->warning('Resource files are only supported with MSVC backend on Windows'); diff --git a/src/Build/SourcePipelineTrait.php b/src/Build/SourcePipelineTrait.php index dfdcdd3c..65557892 100644 --- a/src/Build/SourcePipelineTrait.php +++ b/src/Build/SourcePipelineTrait.php @@ -23,10 +23,10 @@ trait SourcePipelineTrait { use PreparedProjectCacheTrait; - /** @var list PHP files selected by bundled-files. */ + /** @var list PHP files selected by embedded-files. */ private array $bundledPhpFiles = []; - /** @var list All regular files selected by bundled-files. */ + /** @var list All regular files selected by embedded-files. */ private array $bundledFiles = []; /** @var list Selected PHP files not translated to native code. */ @@ -46,6 +46,13 @@ trait SourcePipelineTrait private string $opcodeBuildPhpVersion = ''; private string $opcodeBuildSignature = ''; + private ?string $embeddedArchiveFile = null; + + private function getOpcodeBuildPhpCli(): string + { + return $this->getPhpDir() . ($this->isWindows() ? '/php.exe' : '/bin/php'); + } + /** * Prepare PHP inputs for the Composer php-nano source-composition build. * @@ -197,18 +204,23 @@ trait SourcePipelineTrait } $this->opcodeBuildChecked = true; - $php = $this->getPhpDir() . '/bin/php'; + $php = $this->getOpcodeBuildPhpCli(); if (!is_file($php) || !is_executable($php)) { $this->opcodeBuildProbeError = "Build PHP CLI is not executable: {$php}"; return null; } - foreach ([[], ['-d', 'zend_extension=opcache']] as $extensionArgs) { + $extensionCandidates = [[], ['-d', 'zend_extension=opcache']]; + if ($this->isWindows()) { + $extensionCandidates[] = ['-d', 'zend_extension=' . $this->getPhpDir() . '/ext/php_opcache.dll']; + } + foreach ($extensionCandidates as $extensionArgs) { $process = proc_open( [ $php, '-n', ...$extensionArgs, '-d', 'opcache.enable_cli=1', '-r', 'if (!extension_loaded("Zend OPcache") || !function_exists("opcache_compile_file")) exit(1); ' - . '$extension = ini_get("extension_dir") . DIRECTORY_SEPARATOR . "opcache." . PHP_SHLIB_SUFFIX; ' + . '$extension = PHP_OS_FAMILY === "Windows" ? dirname(PHP_BINARY) . "/ext/php_opcache.dll" ' + . ': ini_get("extension_dir") . DIRECTORY_SEPARATOR . "opcache." . PHP_SHLIB_SUFFIX; ' . 'echo json_encode(["php" => PHP_VERSION, "opcache" => phpversion("Zend OPcache"), ' . '"binary" => [PHP_BINARY, hash_file("sha256", PHP_BINARY)], ' . '"extension" => [$extension, is_file($extension) ? hash_file("sha256", $extension) : null], ' @@ -248,7 +260,7 @@ trait SourcePipelineTrait if ($extensionArgs !== null) { return $extensionArgs; } - $php = $this->getPhpDir() . '/bin/php'; + $php = $this->getOpcodeBuildPhpCli(); $detail = $this->opcodeBuildProbeError === '' ? '' : "\n" . $this->opcodeBuildProbeError; $this->error("Embedded opcode generation requires Zend OPcache for the build PHP CLI: {$php}{$detail}"); } @@ -317,12 +329,31 @@ trait SourcePipelineTrait } // OPcache uses a different file-cache path layout on Windows. if (DIRECTORY_SEPARATOR === '\\' && is_dir($cacheDir)) { + // The drive colon is omitted from OPcache's cache path (C:\foo + // becomes C\foo), after two system-specific cache directories. + $windowsCacheSuffix = str_replace('/', '\\', preg_replace('/^([A-Za-z]):/', '$1', $file)) . '.bin'; + foreach (glob($cacheDir . '/*', GLOB_ONLYDIR) ?: [] as $systemDirectory) { + foreach (glob($systemDirectory . '/*', GLOB_ONLYDIR) ?: [] as $subDirectory) { + $candidate = $subDirectory . DIRECTORY_SEPARATOR . $windowsCacheSuffix; + if (is_file($candidate) && filesize($candidate) > 0) { + $matches[] = $candidate; + } + } + } + if (count($matches) > 1) { + throw new \RuntimeException("Ambiguous OPcache blob for {$file}"); + } + if ($matches !== []) { + return $matches[0]; + } + // Keep supporting other OPcache layouts without assuming the same + // number of system directories in every PHP build. $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($cacheDir, \FilesystemIterator::SKIP_DOTS), ); foreach ($iterator as $cached) { if ($cached->isFile() && $cached->getSize() > 0 - && str_ends_with($cached->getPathname(), $file . '.bin')) { + && str_ends_with($cached->getPathname(), $windowsCacheSuffix)) { if ($matches !== []) { throw new \RuntimeException("Ambiguous OPcache blob for {$file}"); } @@ -333,6 +364,11 @@ trait SourcePipelineTrait return $matches[0] ?? null; } + private function getSkippedOpcodeMarker(string $cacheDir, string $file): string + { + return $cacheDir . '/skipped-' . hash('sha256', $file) . '.txt'; + } + private function clearOpcodeCacheDirectory(string $directory): void { if (!is_dir($directory)) { @@ -375,7 +411,7 @@ trait SourcePipelineTrait throw new \RuntimeException('Embedded opcodes require a native PHP build host matching the target'); } $this->output('Generating embedded opcodes for ' . count($files) . ' PHP files', 'lightBlue'); - $php = $this->getPhpDir() . '/bin/php'; + $php = $this->getOpcodeBuildPhpCli(); $extensionArgs = $this->getOpcodeBuildExtensionArgs(); $phpVersion = $this->opcodeBuildPhpVersion; $otherFiles = []; @@ -464,8 +500,14 @@ PHP $pending = []; $vendorHits = 0; $anonymousHits = 0; + $vendorSkips = 0; foreach ($files as $file) { $cacheDir = $fileCacheDirs[$file]; + if (isset($vendorFiles[$file]) + && is_file($this->getSkippedOpcodeMarker($cacheDir, $file))) { + ++$vendorSkips; + continue; + } if ((isset($vendorFiles[$file]) || isset($anonymousFiles[$file])) && ($blob = $this->findOpcodeBlob($cacheDir, $file)) !== null) { $blobs[$file] = $blob; @@ -482,7 +524,8 @@ PHP $vendorCount = array_sum(array_map('count', $vendorRoots)); $this->output( 'Vendor opcode cache: ' . $vendorHits . ' reused, ' - . ($vendorCount - $vendorHits) . ' to generate', + . ($vendorCount - $vendorHits - $vendorSkips) . ' to generate' + . ($vendorSkips === 0 ? '' : ', ' . $vendorSkips . ' skipped'), 'lightBlue', ); } @@ -512,9 +555,26 @@ PHP fclose($pipes[1]); fclose($pipes[2]); if (proc_close($process) !== 0) { - throw new \RuntimeException("Cannot compile embedded opcode {$file}:\n{$stderr}"); + if (isset($anonymousFiles[$file]) + || !str_contains((string) $stderr, "OPcache could not compile: {$file}")) { + throw new \RuntimeException("Cannot compile embedded opcode {$file}:\n{$stderr}"); + } + // Embedded directories can contain PHP-looking declaration + // files that are not scripts. Keep their raw bytes but do + // not treat them as executable bytecode. + $this->climate->warning('Skipping non-executable embedded PHP file: ' . $file); + if (isset($vendorFiles[$file])) { + $marker = $this->getSkippedOpcodeMarker($cacheDir, $file); + if (file_put_contents($marker, $file . PHP_EOL) === false) { + throw new \RuntimeException("Cannot cache skipped embedded PHP file: {$file}"); + } + } + $this->updateOpcodeProgress($progress, 'Opcodes', ++$completed, count($pending), $this->noProgress ? $file : null); + continue; } - if (trim($stdout) !== $phpVersion) { + // Compiled files may emit PHP deprecation notices on stdout. + // The driver prints its version before compiling the file. + if (strtok((string) $stdout, "\r\n") !== $phpVersion) { throw new \RuntimeException("Build PHP version changed while compiling {$file}"); } $blob = $this->findOpcodeBlob($cacheDir, $file); @@ -580,13 +640,17 @@ PHP } else { unlink($temporaryArchive); } - $assembly = $this->getBuildDir() . '/embedded-files-' . $this->targetName . '.S'; - $quotedArchive = json_encode($archive, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); $archiveHash = hash_file('sha256', $archive); - $asmCode = "# archive-sha256: {$archiveHash}\n.section .rodata\n#ifdef __APPLE__\n.globl _typephp_embedded_archive_start\n.p2align 4\n_typephp_embedded_archive_start:\n#else\n.globl typephp_embedded_archive_start\n.p2align 4\ntypephp_embedded_archive_start:\n#endif\n.incbin {$quotedArchive}\n"; - $this->writeFile($assembly, $asmCode); - $this->generatedProjectSources[$assembly] = true; - $sources[] = $assembly; + if ($this->isWindows()) { + $this->embeddedArchiveFile = $archive; + } else { + $assembly = $this->getBuildDir() . '/embedded-files-' . $this->targetName . '.S'; + $quotedArchive = json_encode($archive, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR); + $asmCode = "# archive-sha256: {$archiveHash}\n.section .rodata\n#ifdef __APPLE__\n.globl _typephp_embedded_archive_start\n.p2align 4\n_typephp_embedded_archive_start:\n#else\n.globl typephp_embedded_archive_start\n.p2align 4\ntypephp_embedded_archive_start:\n#endif\n.incbin {$quotedArchive}\n"; + $this->writeFile($assembly, $asmCode); + $this->generatedProjectSources[$assembly] = true; + $sources[] = $assembly; + } $this->output( 'Packed ' . count($rawIndex) . ' files and ' . count($opcodeIndex) . ' opcode blobs', 'green', @@ -594,7 +658,10 @@ PHP } $code = '#include ' . PHP_EOL; - if ($this->bundledFiles !== [] || $files !== []) { + $hasArchive = $this->bundledFiles !== [] || $files !== []; + if ($hasArchive && $this->isWindows()) { + $code .= 'static const uint8_t *const typephp_embedded_archive_start = typephp_embedded_archive_data();' . PHP_EOL; + } elseif ($hasArchive) { $code .= 'extern "C" const uint8_t typephp_embedded_archive_start[];' . PHP_EOL; } $code .= 'static const typephp_opcode_entry typephp_opcodes[] = {' . PHP_EOL; @@ -665,7 +732,7 @@ PHP // take the highest precedence) $this->applyCommandLineArguments(); if ($this->bundledFiles !== [] && !$this->isBuildModeBin()) { - $this->error('`bundled-files` requires `mode: bin`'); + $this->error('`embedded-files` requires `mode: bin`'); } if ($this->bundledFiles !== []) { $this->getOpcodeBuildExtensionArgs(); diff --git a/src/Generator/Utils.php b/src/Generator/Utils.php index a0c17fd3..b33a9f33 100644 --- a/src/Generator/Utils.php +++ b/src/Generator/Utils.php @@ -158,7 +158,7 @@ trait Utils protected function escapeFileName(string $file): string { - return str_replace('-', '_', $file); + return str_replace(['-', ':', '<', '>', '"', '|', '?', '*'], '_', $file); } protected function unescapeVarName(string $name): string diff --git a/src/Translator.php b/src/Translator.php index a2edb44f..ccde96be 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -1868,6 +1868,9 @@ CODE; $phpxDir = $this->getPhpxDir(); $headerDirs = [$phpxDir . '/include', $phpxDir . '/src/misc']; + if (str_starts_with($cppFile, $phpxDir . '/thirdparty/opcache/')) { + $headerDirs[] = $phpxDir . '/thirdparty/opcache'; + } foreach ($headerDirs as $dir) { if (!is_dir($dir)) { @@ -2021,7 +2024,10 @@ CODE; public function isPhpxMiscFile(string $cppFile): bool { $miscDir = $this->getPhpxDir() . '/src/misc/'; - return str_starts_with($cppFile, $miscDir); + $opcacheDir = $this->getPhpxDir() . '/thirdparty/opcache/'; + return str_starts_with($cppFile, $miscDir) + || (str_starts_with($cppFile, $opcacheDir) + && preg_match('/\/opcode_unserialize_8[45]\.c$/', str_replace('\\', '/', $cppFile)) === 1); } /** @@ -2212,7 +2218,9 @@ CODE; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_main.cc'; if ($this->bundledFiles !== [] || $this->embeddedOpcodeFiles !== []) { $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_opcode_table.cc'; - $versionHeader = $this->getPhpDir() . '/include/php/main/php_version.h'; + $versionHeader = $this->isWindows() + ? $this->getPhpDir() . '/SDK/include/main/php_version.h' + : $this->getPhpDir() . '/include/php/main/php_version.h'; $versionText = is_file($versionHeader) ? file_get_contents($versionHeader) : ''; if (!preg_match('/#define PHP_VERSION_ID\s+(\d+)/', $versionText, $versionMatch)) { throw new \RuntimeException("Cannot determine the target PHP version from {$versionHeader}"); @@ -3866,23 +3874,26 @@ CODE; $list = $this->getFilesFromDir($projectDir); } - // Raw files are bundled; PHP files not emitted by `sources` are also + // Raw files are embedded; PHP files not emitted by `sources` are also // compiled into OPcache blobs for ZendVM execution at runtime. if (array_key_exists('opcode-sources', $cfg)) { - $this->error('`opcode-sources` has been renamed to `bundled-files`'); + $this->error('`opcode-sources` has been renamed to `embedded-files`'); } if (array_key_exists('bundled-files', $cfg)) { - if (!is_array($cfg['bundled-files'])) { - $this->error('`bundled-files` must be an array'); + $this->error('`bundled-files` has been renamed to `embedded-files`'); + } + if (array_key_exists('embedded-files', $cfg)) { + if (!is_array($cfg['embedded-files'])) { + $this->error('`embedded-files` must be an array'); } - foreach ($cfg['bundled-files'] as $entry) { + foreach ($cfg['embedded-files'] as $entry) { [$src, $condition] = $this->parseProjectYamlSourceEntry($entry); if ($condition !== null && !$this->evaluateProjectYamlCondition($condition)) { continue; } $resolved = $this->getAbsolutePath($src, $projectDir); if (!$resolved) { - $this->error('Bundled file or directory does not exist: `' . $src . '`'); + $this->error('Embedded file or directory does not exist: `' . $src . '`'); } if (is_file($resolved)) { $this->bundledFiles[] = $resolved; @@ -3901,11 +3912,14 @@ CODE; sort($this->bundledFiles, SORT_STRING); $this->bundledPhpFiles = array_values(array_filter( $this->bundledFiles, - static fn(string $file): bool => FileScanner::isPhpFile($file), + // PHP extension stubs are API declarations for code generators. + // They remain in the raw archive but must never be executed. + static fn(string $file): bool => FileScanner::isPhpFile($file) + && !str_ends_with($file, '.stub.php'), )); if ($this->bundledFiles !== []) { $this->output( - 'bundled-files: found ' . count($this->bundledFiles) + 'embedded-files: found ' . count($this->bundledFiles) . ' files (' . count($this->bundledPhpFiles) . ' PHP)', 'lightBlue', ); diff --git a/src/compiler.php b/src/compiler.php index e6475b14..56e6aa3a 100644 --- a/src/compiler.php +++ b/src/compiler.php @@ -28,12 +28,7 @@ function main(int $argc, array $argv): void ); } - // The Zend PHP entrypoint loads the consumer project's Composer autoloader - // in bin/bootstrap.php. The AOT compiler starts here directly and therefore - // must load the dependencies packaged alongside tpc itself. - if (!defined('TYPEPHP_PHP_SCRIPT_ENTRY')) { - require_once dirname(__DIR__) . '/vendor/autoload.php'; - } + require_once dirname(__DIR__) . '/vendor/autoload.php'; $completionStatus = CompletionCommand::execute($argv); if ($completionStatus !== null) {