Change value of #file literal for release builds

As expected, Swift uses the compile time value to populate #file special literals, however this means my customers are seeing my local project directory printed when my app crashes due to a fatalError() call, e.g:

Fatal error: BOOM: file /Users/ian/code/ileitch/periphery/PeripheryCmd/Code/main.swift, line 10

I think this is confusing for some customers to see as it's not a directory on their local machine. Obviously I can switch to a different directory for release builds, but I was wondering if there's a compiler flag to alter the default value? Ideally I'd like it to just output only the name of the swift file, or a relative path.

5 Likes

AFAICT, this is more of an Xcode problem than a Swift problem. It looks like the path that gets embedded for #file is the exact path to the source file that gets passed on the command line to swiftc. If you invoke swiftc Foo.swift, then #file == "Foo.swift", and if you invoke swiftc ./Foo.swift, then #file == "./Foo.swift".

The reason you're seeing the full paths is because Xcode passes them that way to the compiler. I don't see a setting to control this behavior, but I could be missing something.

In my case, I'm using 'swift build' to create the binary.

I wonder if it would make sense then for SwiftPM to not pass absolute paths, but rather update the working directory of the process before invoking the compiler.

5 Likes

I would +1 this as I just ran into this problem when using swift pm... absolute paths are... not optimal in most situations i can think of...

2 Likes

Can you take your file literal, init a path, pull off the last component, and log that? (Not near a working machine right now to try myself.)

I requested this a few years ago and it was not well supported: https://forums.swift.org/t/pitch-introducing-filename-debug-identifier

Upon reading that thread, I'd disagree—it looks like it was quite well supported, but the discussion just trailed off, unfortunately.

Lately I've been working on path remapping for hermetic debug info; I'd be interested to know what people's answers are to the following two questions:

  1. Do you consider #file a debugging-only construct?
  2. Can you think of a situation where you would want paths returned by #file to look different than the paths embedded in your binary's debug symbols?

If the answers to these are "yes" and "no", respectively, then I'd propose that debug path remapping could also solve the same problem for #file. But I'd also like to hear if there are reasons we shouldn't do that that I haven't considered.

2 Likes

There are several ways to approach this:

  • Add a native lastPathComponent in Swift Foundation for String
  • Add a new #fileName literal
  • Use #file but have it detect optimized builds where asserts cannot fire and produce a lopped string when these conditions hold, otherwise the existing behavior of #file
  • Retool Xcode and let Swift and Swift Foundation be.

What would be most valuable?

1 Like

I created a branch that introduces #fileName literal.

The diffs are here: https://github.com/apple/swift/compare/master...erica:filename

Screen shot:

09%20PM

I am partial to the #filename literal.

#fileName sounds great, but it doesn't really solve my issue, since fatalError still uses #file. Also in general I think I'd like all uses of #file to be altered, vs. having to change every use to #fileName (which would be unreasonable given that 3rd party dependencies may be using #file).

My favored solution is a compiler argument that alters #file output relative to a given project path.

1 Like

I agree that a relative path would be better than just the file name. Passing the path it should be relative to to the compiler and having it fall back to absolute paths sounds reasonable to me.

The only thing where this might bite us later could be a constexpr/macro/whatever model where finding the path to the current file would then become a little more cumbersome (either have to get the base directory as well or make #file behave a little different in that context).

Adding more literals with slightly different path components doesn't feel like the right solution here. It increases the API surface of the language and it makes assumptions that the basename of the file is the only part that matters, but that doesn't have to be the case; someone could have their sources organized in directories with the same basename appearing in multiple directories, even in the same module.

I'm not sure your reply to my post addressed the questions I had, so I'll add some more information to clarify. If we add support to swiftc for a feature like Clang's -fdebug-prefix-map, we'll be able to remap paths in debug info in the following way, for example:

  • You have a file at /Users/username/My Code/Swift/MyCoolProject/Sources/MyClass.swift.
  • You compile it with swiftc ... -debug-prefix-map="/Users/username/My Code/Swift/"= ... (remapping the path parts you don't want exposed to the empty string).
  • Now the debug info embedded in your binary/dSYMs would have the file path MyCoolProject/Sources/MyClass.swift instead of the absolute paths.

I'm suggesting in this thread that we could apply the same remapping to #file as well and that would be suitable for most people's purposes, assuming that people don't wish for their #file strings to be different than the paths in the debug symbols.

What I'm trying to figure out is whether that's a reasonable assumption.

4 Likes

Using #fileName instead of #file for fatalError, XCTAssert, etc. is not a good solution. We might have two files dirA/C.swift and dirB/C.swift -- we would want to be able to tell where the error occurred.

I don't think there's a sound way around using paths relative to the project root.

There's not always a good notion of "project root" either. A SwiftPM package always has all the source files in one place, but an Xcode project may have them strewn across multiple directories. That said, an 80% solution might be good enough here. (We could also have a warning if #file is ever used with a file that's not relative to whatever's specified as the "project root".)

1 Like

Might it be reasonable to have project root be specified as a compiler flag, and #file will emit paths relative to this path? If the project is poorly organized you get things like the following, but that should be considered a user "error", and not something for the compiler to try and cope with on its own.

root: /Users/someone/projects/foo/
source file: /Users/someone/projects/bar/a.swift
#file: ../bar/a.swift

3 Likes

This is precisely what I suggested above about reüsing -debug-prefix-map if it's accepted. That would kill two birds with one stone, if the assumption holds that people would want the same #file strings that also show up in their binaries' debug symbols.

1 Like

Yes. Or a separate flag, to accommodate those who don't. I really don't have an opinion either way, and would support either one.

Sounds good to me