feat(compiler): enhance entry file handling with deferred validation

- Replace sapiEntryFile with separate configured path and value properties
- Add sapiEntryConfiguredPath and sapiEntryConfiguredValue for deferred validation
- Move entry file existence checks to final SAPI selection phase
- Add warning when entry is ignored due to non-CLI SAPI configuration
- Update test cases to verify entry file handling behavior
- Remove redundant entry file addition in translator pipeline
- Refactor entry configuration to support CLI SAPI requirement validation
master
韩天峰 2 weeks ago
parent 27371fa734
commit 31ad2664e7
  1. 50
      phpunit/src/CompilerBaseApiTest.php
  2. 28
      src/Build/SourcePipelineTrait.php
  3. 6
      src/CompilerBase.php
  4. 30
      src/Translator.php

@ -1003,14 +1003,60 @@ sources:
- main.php
YAML);
$files = $this->invokeMethod('parseProjectYaml', $projectFile);
$files = $this->compiler->getFiles($projectFile);
$entry = realpath(dirname($projectFile) . '/main.php');
$this->assertSame([], $files);
$this->assertSame($entry, $this->getPropertyValue('sapiEntryFile'));
$this->assertSame([$entry], $this->getPropertyValue('embeddedFiles'));
$this->assertSame([$entry], $this->getPropertyValue('embeddedPhpFiles'));
$this->invokeMethod('validateLoadedProjectConfiguration');
}
public function testEntryWithoutCliIsIgnoredAndRemainsAnAotSource(): void
{
$projectFile = $this->createProjectFile(<<<'YAML'
sapi: embed
entry: main.php
sources:
- main.php
YAML);
$entry = realpath(dirname($projectFile) . '/main.php');
$climate = $this->getPropertyValue('climate');
$climate->output->defaultTo('buffer');
$files = $this->compiler->getFiles($projectFile);
$output = $climate->output->get('buffer')->get();
$this->assertStringContainsString(
'`entry` is ignored because `sapi` does not contain cli',
$output,
);
$this->assertSame([$entry], $files);
$this->assertNull($this->getPropertyValue('sapiEntryFile'));
$this->assertSame([], $this->getPropertyValue('embeddedFiles'));
$this->assertSame([], $this->getPropertyValue('embeddedPhpFiles'));
}
public function testMissingEntryWithoutCliIsIgnoredBeforeFileValidation(): void
{
$projectFile = $this->createProjectFile(<<<'YAML'
sapi: fpm
php-builder: {}
entry: missing.php
sources:
- main.php
YAML);
$climate = $this->getPropertyValue('climate');
$climate->output->defaultTo('buffer');
$files = $this->compiler->getFiles($projectFile);
$this->assertSame([realpath(dirname($projectFile) . '/main.php')], $files);
$this->assertStringContainsString(
'`entry` is ignored because `sapi` does not contain cli',
$climate->output->get('buffer')->get(),
);
$this->assertNull($this->getPropertyValue('sapiEntryFile'));
}
public function testCommandLineEntryConfiguresPhpBuilderCliSapi(): void

@ -521,11 +521,33 @@ trait SourcePipelineTrait
if ($this->isSapiBuild() && !$this->isPhpBuilderBuild()) {
$this->error('The cli and fpm SAPIs require `php-builder`');
}
if ($this->hasSapi('cli') && $this->sapiEntryFile === null) {
if ($this->hasSapi('cli') && $this->sapiEntryConfiguredPath === null) {
$this->error('`sapi` containing cli requires an `entry` PHP file');
}
if ($this->sapiEntryFile !== null && !$this->hasSapi('cli')) {
$this->error('`entry` requires `sapi` to contain cli');
if ($this->sapiEntryConfiguredPath !== null && !$this->hasSapi('cli')) {
$this->climate->warning('`entry` is ignored because `sapi` does not contain cli');
$this->sapiEntryFile = null;
$this->sapiEntryConfiguredPath = null;
$this->sapiEntryConfiguredValue = null;
}
if ($this->sapiEntryConfiguredPath !== null) {
$entry = realpath($this->sapiEntryConfiguredPath);
if ($entry === false || !is_file($entry)) {
$value = $this->sapiEntryConfiguredValue ?? $this->sapiEntryConfiguredPath;
$this->error('Entry file does not exist: `' . $value . '`');
}
if (!FileScanner::isPhpFile($entry) || str_ends_with($entry, '.stub.php')) {
$this->error('`entry` must select an executable PHP file');
}
$this->sapiEntryFile = $entry;
if (!in_array($this->sapiEntryFile, $this->embeddedFiles, true)) {
$this->embeddedFiles[] = $this->sapiEntryFile;
sort($this->embeddedFiles, SORT_STRING);
}
if (!in_array($this->sapiEntryFile, $this->embeddedPhpFiles, true)) {
$this->embeddedPhpFiles[] = $this->sapiEntryFile;
sort($this->embeddedPhpFiles, SORT_STRING);
}
}
if ($this->embeddedFiles !== [] && !$this->isBuildModeBin() && !$this->isSapiBuild()) {
$this->error('`embedded-files` requires `mode: bin`');

@ -563,8 +563,12 @@ class CompilerBase implements PropertyAccessContext
/** @var list<string> */
protected array $phpBuilderExtensions = [];
/** Absolute path of the embedded primary script used by the CLI SAPI. */
/** Canonical path of the active embedded primary script used by the CLI SAPI. */
protected ?string $sapiEntryFile = null;
/** Resolved entry candidate awaiting validation against the final SAPI selection. */
protected ?string $sapiEntryConfiguredPath = null;
/** Original entry spelling retained for diagnostics. */
protected ?string $sapiEntryConfiguredValue = null;
protected ?string $sapiPhpSourceDirectory = null;
protected ?string $sapiPhpBuildDirectory = null;
protected ?string $sapiPhpPrefix = null;

@ -4392,9 +4392,6 @@ CODE;
}
}
}
if ($this->sapiEntryFile !== null) {
$this->embeddedFiles[] = $this->sapiEntryFile;
}
$this->embeddedFiles = array_values(array_unique($this->embeddedFiles));
sort($this->embeddedFiles, SORT_STRING);
$this->embeddedPhpFiles = array_values(array_filter(
@ -4685,11 +4682,7 @@ CODE;
$this->resourceConfig['manifest'] = $manifest;
}
$list = $this->filterIgnoredFiles($list);
if ($this->sapiEntryFile !== null) {
$list = array_values(array_diff($list, [$this->sapiEntryFile]));
}
return $list;
return $this->filterIgnoredFiles($list);
}
private function configurePhpBuilder(PhpBuilderConfiguration $configuration): void
@ -4707,21 +4700,12 @@ CODE;
$this->error('`entry` must be a non-empty PHP file path');
}
$value = trim($value);
$entryPath = $this->resolvePath($value, $baseDirectory, 'Entry path');
$entry = realpath($entryPath);
if ($entry === false || !is_file($entry)) {
$this->error('Entry file does not exist: `' . $value . '`');
}
if (!FileScanner::isPhpFile($entry) || str_ends_with($entry, '.stub.php')) {
$this->error('`entry` must select an executable PHP file');
}
$this->sapiEntryFile = $entry;
if (!in_array($entry, $this->embeddedFiles, true)) {
$this->embeddedFiles[] = $entry;
}
if (!in_array($entry, $this->embeddedPhpFiles, true)) {
$this->embeddedPhpFiles[] = $entry;
}
// Existence and file-type checks are deliberately deferred until the
// final SAPI selection is known. Command-line --sapi may override YAML,
// and entry has no semantics unless that final selection contains CLI.
$this->sapiEntryConfiguredValue = $value;
$this->sapiEntryConfiguredPath = $this->resolvePath($value, $baseDirectory, 'Entry path');
$this->sapiEntryFile = null;
}
private function configureSapiTargets(string|array $value): void

Loading…
Cancel
Save