Skip to content

fix(build): create output parent directory before native link - #157

Merged
matyhtf merged 1 commit into
swoole:masterfrom
alwaysLinger:fix/link-output-parent-directory
Oct 12, 2026
Merged

matyhtf merged 1 commit into
swoole:masterfrom
alwaysLinger:fix/link-output-parent-directory

Conversation

@alwaysLinger

@alwaysLinger alwaysLinger commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Observed failure

When the configured output path lives inside a directory that does not exist yet, the build compiles every source (minutes on a real project) and only then dies with a bare linker error:

# project.yml
name: app
sources:
  - app.php
output: artifacts/app
$ php bin/tpc.php project.yml -O2
...
Successfully compiled 6 files
/usr/bin/ld: cannot open output file /tmp/etest/artifacts/app: No such file or directory
collect2: error: ld returned 1 exit status
Fatal error: link failed: /tmp/etest/artifacts/app

The compiler could not have produced the artifact regardless of how long it compiled: the linker never creates parent directories, and nothing before the link creates them either.

Root cause

NativeBuilder::link() (src/Build/NativeBuilder.php) execs the link command without ensuring the target's parent directory exists. The Nano path is asymmetric evidence that this is an omission rather than a design choice: NativeSourceProjectBuilder already calls ensureDirectory(dirname($project->output)) right before linking (src/Build/NativeSourceProjectBuilder.php), but the plain tpc.php file.php -o ... / project.yml path has no equivalent.

Fix

NativeBuilder::link() now ensures the output parent directory exists (recursively) before invoking the linker, following the same ensureDirectory() pattern already used on the Nano path and in AtomicFile, EmbeddedArchiveBuilder and EmbeddedOpcodeGenerator:

private function ensureDirectory(string $directory): void
{
    if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) {
        throw new \RuntimeException("Cannot create output directory: {$directory}");
    }
}

Translator::linkNativeTarget() converts the exception into the regular CLI fatal error, so a misconfigured output path now fails with Cannot create output directory: ... instead of a raw ld message.

Both entry points go through the same link call, so output: in project.yml and the -o CLI flag are covered, as is the embed SAPI target (which links through linkNativeTarget() as well).

@matyhtf
matyhtf merged commit f394d6a into swoole:master Oct 12, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants