diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index d8175ac6..32049290 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.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 diff --git a/src/Build/SourcePipelineTrait.php b/src/Build/SourcePipelineTrait.php index f3286fbd..51d95dab 100644 --- a/src/Build/SourcePipelineTrait.php +++ b/src/Build/SourcePipelineTrait.php @@ -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`'); diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 9118e113..b70827d7 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -563,8 +563,12 @@ class CompilerBase implements PropertyAccessContext /** @var list */ 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; diff --git a/src/Translator.php b/src/Translator.php index e060d909..b07379bc 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -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