Skip to content

Conversation

allevato
Copy link
Member

This started out as a weird debugging journey. We had a team running swift-format as part of a Bazel action to format generated Swift code. The action basically just ran swift-format $input > $output. This action started failing (generating no output) in this environment because the Bazel action workspace was set up such that $input was a symlink to the actual file. Even if I updated the action to pass --follow-symlinks, the old FileIterator code had a fallthrough that didn't correctly handle the case where a path passed directly on the command line was a symlink to a file instead of a directory.

The reason that this actually started failing was because we finally updated our internal toolchain to use the new swift-foundation instead of the legacy swift-corelibs-foundation (before it was layered on top of swift-foundation). Something in the implementation of FileManager changed subtly and started giving us URLs back that pointed to symlinks when previously we were getting them pre-resolved, which revealed the above issue.

While I was in here, I decided to add support for following symlinks to other symlinks, just to make sure that didn't become an issue in the future.

Copy link
Contributor

@bnbarham bnbarham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I was in here, I decided to add support for following symlinks to other symlinks, just to make sure that didn't become an issue in the future.

Feels like a fairly breaking change if that were to happen, but it's just a small extra loop so 🤷‍♂️

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because it has bitten me multiple times in the past, could you double check that that this works correctly for /tmp on macOS (which is a symlink to /private/tmp) but resolvingSymlinksInPath returns the normalized /tmp regardless.

Comment on lines +182 to +188
while followSymlinks && fileType == .typeSymbolicLink,
let destination = try? FileManager.default.destinationOfSymbolicLink(atPath: url.path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we detect circles here if you have a symlink that points to itself?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; fixed. Symlink cycles (either a link pointing to itself, or a multi-link cycle) are silently ignored now. We could warn on this, but we'd have to plumb DiagnosticsEngine down into FileIterator, which I didn't really want to do here if we can avoid it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as we don’t infinite loop, I think the behavior is OK.

Also, it only just occurs to me, why can’t we use resolvingSymlinksInPath/realpath here if we follow all symlinks?

In SourceKit-LSP, we have the following code to avoid the /tmp vs /private/tmp issue: https://github.com/swiftlang/sourcekit-lsp/blob/3e7f350246dc86072a9dfc88f25782dc86dd1c9a/Sources/SwiftExtensions/URLExtensions.swift#L40-L57

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good question. That realpath code seems to work on macOS from local testing, so let me see if it works on Linux/Windows. Assuming they both consistently resolve all symlinks deeply or stop when a cycle is reached on all platforms, then that would remove the need for our own loop.

Copy link
Member Author

@allevato allevato Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sigh, no good. Based on CI results and local testing, it looks like resolvingSymlinksInPath on Linux pre-5.x Foundation doesn't correctly resolve through multiple symlinks. So we'd need a special case to handle those, which would essentially be the code I had earlier anyway.

Maybe one day when we drop 5.x support, we can clean this all up.

@allevato allevato force-pushed the symlink-tweaks branch 2 times, most recently from 3c83909 to 8d5c78f Compare February 26, 2025 17:53
@allevato allevato force-pushed the symlink-tweaks branch 2 times, most recently from d3c336c to 01094d1 Compare February 26, 2025 20:10
@allevato allevato merged commit 6933505 into swiftlang:main Feb 26, 2025
38 checks passed
@allevato allevato deleted the symlink-tweaks branch February 26, 2025 20:48
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.

3 participants