Skip to content

fix(driver): flatten build artifact paths - #1894

Open
ghaith wants to merge 4 commits into
masterfrom
fix/PRG-4448
Open

fix(driver): flatten build artifact paths#1894
ghaith wants to merge 4 commits into
masterfrom
fix/PRG-4448

Conversation

@ghaith

@ghaith ghaith commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Attribution: this branch, its tests and this description were produced by Claude
(Claude Code) working in my repository, not written directly by me. I have reviewed the
change, but please review it as machine-authored work. The commits carry a
Co-Authored-By: Claude trailer.

Fixes PRG-4448.

Problem

Every object, bitcode and IR artifact was named after the path of its source and created
under that path inside the build directory, so a source outside the project mirrored its
whole absolute path. The directory of the target was added twice on top of that, once by the
driver and once by GeneratedModule::persist.

On Windows a second problem compounded it: fs::canonicalize returns a verbatim path
(\?\C:\...) while the project root does not, so the comparison that selects the relative
name never matched and the absolute path was mirrored even for sources inside the project.

The result goes past the 260 characters the Windows file system allows without long path
support. The compiler itself does not fail, because the standard library hands absolute paths
to the file system in verbatim form, so the damage lands on everything else: the reporter of
PRG-4448 hit it copying their workspace in File Explorer.

Solution

Artifacts get a flat name of the form <file name>-<digest>.<extension>, for example
main.st-116b35a8828869ac.o, and all of them land directly in <build>/<target>/.

  • The digest is a SipHash-1-3 of the whole key with a fixed zero key, so a name is identical
    on every run, process and platform. It is what keeps two units with the same file name in
    different directories apart now that they share one directory.
  • The key of a unit is its path relative to the project, or its full path when it lives
    outside the project. The project root is canonicalized before the comparison, so the
    relative form is also reached on Windows.
  • The directory of the target is added by the driver only. GeneratedModule::persist writes
    to the directory it is given.

The compiler now adds at most 70 characters to the build directory. Unchanged: the final
artifact of a link still lands in <build>/<target>/<output>, and the header generator still
writes next to the source or into the requested directory.

Validation on Windows

Ran the Eclipse tool's own compile command unchanged, varying only the compiler binary, with
the standard library at a 202-character delivery path outside the workspace:

before after
longest artifact path 445 233
xcopy of the build output 3 of 31 files, exit code 0 31 of 31

The old path reproduces the shape reported in the ticket, doubled target directory included.
The 3-of-31 copy is the reported failure: silent truncation with a success exit code.

Tests

compiler/plc_driver/src/tests/artifact_names.rs covers the naming, with platform dependent
scenarios mirrored between a linux and a windows module. tests/integration/build_artifacts.rs
covers the layout end to end, including a build through the build subcommand where the
project root comes from plc.json. Both suites already run in the Windows job.

The flat assertion, the character budget and the 260-character limit were each confirmed red
against the pre-fix code on Windows before this went in.

🤖 Generated with Claude Code

ghaith and others added 4 commits August 27, 2026 14:08
Problem: Every object, bitcode and IR artifact was named after the path of
its source and created under that path inside the build directory, so a
source outside of the project mirrored its whole absolute path. The
directory of the target was added twice on top of that. The resulting paths
go past the 260 character limit of the Windows file system, which breaks
file operations on the workspace.

Solution: Name artifacts `<file name>-<digest of the unit>.<extension>` and
keep all of them directly in the directory of the target. The digest keeps
two units with the same file name apart now that they share a directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The path length fix was implemented and tested on Linux, while the file
system it targets is Windows. The note lists the invariants the tests hold,
the open points to validate on Windows and the tests worth adding there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The naming of the build artifacts was implemented and tested on Linux, while
the file system it targets is Windows. Reverting the fix and running the suite
on Windows reproduces the broken state there: the directory of the target
appears twice, the absolute path of an external source is mirrored below it,
and the scenario of the path limit reaches 374 characters.

Closes the open points of the validation note:

- The relative form of a key is reached on Windows. `canonicalize` returns a
  verbatim path for a unit, so a project root that is not canonicalized never
  prefixes it; the new end-to-end test through the `build` subcommand, where
  the root comes from the `plc.json`, names its artifacts after the path
  relative to the project.
- Casing is normalized by the canonicalization of the key, not by the digest.
  A source referenced with another casing keeps its one artifact.
- A UNC path, a mapped drive, a reserved device name and a source name outside
  of ASCII all keep the readable part of the name and build.
- A build directory that is already past 260 characters needs no diagnostic:
  the standard library hands absolute paths to the file system in their
  verbatim form, so the compiler builds through it. What fails on such a path
  are the tools around the compiler, which is what the fix is for.

Left open, as before: nothing removes the mirrored tree that an older `plc`
wrote into a build directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A library is pulled in with one glob per extension, `include/*.st` and
`include/*.pli` for the standard library, so two units can share a stem and
differ only in their extension. Once the stem reaches the limit of the readable
part, the cut falls on the dot of the source extension and both names lose it,
which leaves the digest as the only thing telling the two artifacts apart.

The existing tests covered the two halves of this separately, a cut stem and an
extension that differs, but not the combination the standard library can
actually produce.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Build Artifacts

🐧 Linux

Artifact Link Size
deb-x86_64 Download 40.8 MB
schema Download 0.0 MB
stdlib Download 39.4 MB
plc-x86_64 Download 43.5 MB
deb-aarch64 Download 31.9 MB
plc-aarch64 Download 43.4 MB

From workflow run

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.

1 participant