Skip to content

Generate XOJIT preview thunks for source files reached through a symlink#1465

Merged
pmattos merged 2 commits into
swiftlang:mainfrom
pmattos:fix-xojit-preview-symlinked-source
Jun 19, 2026
Merged

Generate XOJIT preview thunks for source files reached through a symlink#1465
pmattos merged 2 commits into
swiftlang:mainfrom
pmattos:fix-xojit-preview-symlinked-source

Conversation

@pmattos

@pmattos pmattos commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Context

When SwiftUI Previews run in XOJIT mode, the previews client asks Swift Build — per source file — for the compile command that builds that file's "preview thunk", via generatePreviewInfo(.thunkInfo), identifying the file by path. In SwiftCompilerSpec.generatePreviewInfo, Swift Build locates that path among the target's compile inputs and hands it to LibSwiftDriver.frontendCommandLine as the primary input, which produces the swift-frontend invocation for the thunk.

That lookup compares paths by exact string. If the source file was recorded in the build description under a different — but equivalent — spelling than the one in the request, nothing matches: frontendCommandLine returns no command line, generatePreviewInfo returns an empty result, and the client reports noPreviewInfos, so the preview never renders.

This happens for standalone Swift packages whose sources live under a symlinked directory. On macOS, temporary directories such as /tmp are symlinks to /private/tmp, and SwiftPM's new package PIF builder symlink-resolves source paths (to match what the index store records). The compile command then holds /private/tmp/.../View.swift while the preview request still carries the unresolved /tmp/.../View.swift — the same file, a different spelling, no match.

Fix

In the XOJIT branch of SwiftCompilerSpec.generatePreviewInfo, match the requested source file against the command's inputs by resolved path (ie, FSProxy.realpath), and use the spelling that actually appears in the command as the primary input. The output-file-map and the VFS overlay are keyed on that same spelling so they line up with what the driver resolves. When the requested and recorded paths already agree — e.g. the legacy package PIF builder, which doesn't resolve symlinks — this is a no-op.

Testing

Adds previewXOJITThunkInfoResolvesSymlinkedSourcePath to PreviewsBuildOperationTests. Rather than standing up a real package, it reproduces the spelling mismatch with a single symlink: it builds a one-file app under a real directory and then requests the preview thunk through a symlink to that directory, so the requested path and the build's input path resolve to the same file but differ as strings. It asserts that exactly one preview info comes back and that the returned compile command references the real (build) spelling, never the symlinked request path.

Without the fix the test returns zero preview infos (ie, the noPreviewInfos condition); with it one, as expected.

Fixes rdar://176386125 (and related to rdar://179887248).

When SwiftUI Previews run in XOJIT mode, SwiftCompilerSpec.generatePreviewInfo(.thunkInfo)
locates the requested source file among the compile command's inputs and hands it to
LibSwiftDriver.frontendCommandLine as the primary input. The lookup compared paths by
exact string, so when the build description recorded the source under a symlink-resolved
spelling (e.g. /private/tmp/...) while the preview request used the unresolved path
(e.g. /tmp/...), no input matched: the driver returned no command line and
generatePreviewInfo came back empty, which clients report as noPreviewInfos. This affects
standalone Swift packages whose sources live under a symlinked directory such as /tmp
(a symlink to /private/tmp on Darwin).

Match the requested source file against the command's inputs by resolved path (realpath),
and key the output-file-map and VFS overlay on the spelling that actually appears in the
command. No-op when the paths already agree.

Add previewXOJITThunkInfoResolvesSymlinkedSourcePath, which reproduces the mismatch via a
symlink and fails without this change (zero preview infos), passing with it.

rdar://176386125
@pmattos

pmattos commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@swift-ci test

Comment thread Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift Outdated
@pmattos pmattos requested a review from rjmansfield as a code owner June 18, 2026 18:15
@pmattos pmattos force-pushed the fix-xojit-preview-symlinked-source branch from f00e5d6 to 3459bd9 Compare June 18, 2026 18:37
@pmattos

pmattos commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@swift-ci test

@pmattos pmattos enabled auto-merge (squash) June 18, 2026 18:52
@pmattos

pmattos commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

@swift-ci test

@pmattos pmattos merged commit 4d166bd into swiftlang:main Jun 19, 2026
19 of 20 checks passed
vsarunas pushed a commit to ordo-one/swift-build that referenced this pull request Jun 30, 2026
…ink (swiftlang#1465)

CONTEXT

When SwiftUI Previews run in XOJIT mode, the previews client asks Swift Build — per source
file — for the compile command that builds that file's "preview thunk", via
generatePreviewInfo(.thunkInfo), identifying the file by path. In
SwiftCompilerSpec.generatePreviewInfo, Swift Build locates that path among the target's
compile inputs and hands it to LibSwiftDriver.frontendCommandLine as the primary input,
which produces the swift-frontend invocation for the thunk.

That lookup compares paths by exact string. If the source file was recorded in the build
description under a different — but equivalent — spelling than the one in the request,
nothing matches: frontendCommandLine returns no command line, generatePreviewInfo returns
an empty result, and the client reports noPreviewInfos, so the preview never renders.

This happens for standalone Swift packages whose sources live under a symlinked directory.
On macOS, temporary directories such as /tmp are symlinks to /private/tmp, and SwiftPM's
new package PIF builder symlink-resolves source paths (to match what the index store
records). The compile command then holds /private/tmp/.../View.swift while the preview
request still carries the unresolved /tmp/.../View.swift — the same file,
a different spelling, no match.

FIX

In the XOJIT branch of SwiftCompilerSpec.generatePreviewInfo, match the requested source
file against the command's inputs by resolved path (ie, FSProxy.realpath), and use the
spelling that actually appears in the command as the primary input. The output-file-map
and the VFS overlay are keyed on that same spelling so they line up with what the driver
resolves. When the requested and recorded paths already agree — e.g. the legacy package
PIF builder, which doesn't resolve symlinks — this is a no-op.

TESTING

Adds previewXOJITThunkInfoResolvesSymlinkedSourcePath to PreviewsBuildOperationTests.
Rather than standing up a real package, it reproduces the spelling mismatch with a single
symlink: it builds a one-file app under a real directory and then requests the preview
thunk through a symlink to that directory, so the requested path and the build's input
path resolve to the same file but differ as strings. It asserts that exactly one preview
info comes back and that the returned compile command references the real (build)
spelling, never the symlinked request path.

Without the fix the test returns zero preview infos (ie, the noPreviewInfos condition);
with it one, as expected.

Fixes rdar://176386125 (and related to rdar://179887248).
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