[Pitch] API to get the path to the current executable

There are a number of components of the Swift toolchain and out in the wider Swift ecosystem that need to be able to read the path to the current executable. Historically, developers have had to rely on higher-level API like Bundle.executableURL or on platform-specific solutions to get this value.

I propose providing it from the Swift standard library instead, so a developer can write e.g.:

if let executablePath = CommandLine.executablePath {
  posix_spawn(executablePath, ...)
}

Or:

if let executablePath = CommandLine.executablePath {
  let resourcesDirectory = "\(executablePath)-resources"
}
...

And so forth and such as.

Read the full proposal here.

5 Likes

The pain point I've run into repeatedly involves trying to invoke an executableTarget within my package using the a testing harness, and to make that as consistent as possible across multiple platforms (macOS & Linux in my case at the moment, could easily see that expanding to Windows as well).

I think that this proposal might fit with being able to look up a location for the executable in order to invoke it, but from reading the proposal I wasn't entirely sure if that matched up. Is that part of the intent here, or was it more aligned with knowing what your executable path is from within the code when it's running?

The latter: at runtime, find the main executable. This proposal doesn't give you the ability to place the executable in any specific location, nor find other executables.

If I misunderstood, please clarify! :slight_smile:

1 Like

This functionality is particularly interesting from a testing point of view, because I'm not sure what the intended use cases are there. Can you elaborate?

On Apple platforms, where tests are typically built as MH_BUNDLE binaries inside .xctest bundles that are loaded by Xcode's xctest tool, both _NSExecutablePath (used by this proposal's underlying implementation) and Bundle.main.executableURL return the path to that tool (usually something like /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Agents/xctest), not the path to the user's binary. To get that, you usually use Bundle(for: SomeClass.self)....

On non-Apple platforms, since test "bundles" are just standard executables, I would expect the equivalent function (say, checking /proc/self/exe on Linux) to return the path to the user's executable, since there's no separate launcher used there.

So for test-specific use cases, what is the expected use case, when the implementations would return very different results depending on platform?

Those concerns aside, I definitely think this would be good to have in the standard library for plenty of non-test use cases. How safe/correct is the implementation across all platforms with respect to potential static initialization order fiascos? I noticed that the OpenBSD implementation appears to use a static initializer to stash some process information to use later along with sniffing argv.

For more context, I've run into similar issues in the past with the way Swift on Linux tries to sniff the stack to find argv relative to the global environ. Specifically, LLVM profile instrumentation calls setenv early during its execution, which causes environ to be reallocated onto the heap. This breaks CommandLine.arguments if accessed after that, because the Swift runtime tries to search upwards from environ and argv is no longer anywhere near it. These sorts of bugs are really hard to track down and diagnose, so can we guarantee that we avoid introducing new ones?

Swift Testing's specific use case is for exit tests which require spawning new processes for the same executable path. See the equivalent code here in Swift Testing.

The implementation is as safe as the platforms allow and all[1] use API provided by the OS for exactly this purpose; the problems you describe with CommandLine.arguments don't apply here.

OpenBSD is an odd duck. Keep in mind that it's not officially supported by the Swift project. The implementation I have for OpenBSD is a "best effort" that @3405691582 and I settled on. They are the primary maintainer of Swift's OpenBSD support so I'd defer to them for further questions about that specific platform.


  1. Except OpenBSD. Read on… ↩︎

Because, as the proposal says, getting the executable path is nonstandard, it is also completely reasonable for a platform to not implement any API surface for this behavior. Thus, the implementation does a best-effort approach to deriving it based on cwd and PATH. This probably seems reasonable so long as the expectations around it are called out, i.e., mentioning in documentation that the process will abend if the executable path can't be derived.

Is it though?

Snark aside, we do have to deal with the limitations of the APIs that we have in practice, not the APIs we wish existed in operating systems. So I think it's absolutely right that we should document as well as possible which platforms this is known to work correctly on and which platforms may have unexpected behavior based on :waves hands: other runtime conditions that, on the surface, may not appear to be related at all.

I don't disagree in principle, but since OpenBSD is not a supported platform, it would be odd for the documentation to call it out as not working. "Here's an OS we don't offer a toolchain for! This property has edge cases on that OS only! In theory!" :grin:

2 Likes

On Apple platforms, readlink + _NSGetExecutablePath should work, on Windows there's GetModulePath, and usually on Linux you can readlink /proc/self/exe, though I've met weird situations where that path wasn't available (eg. the particular distro decided that /proc should be called something else, or that access to /proc was sandboxed away).

I feel like this has to either return an Optional/be get throws, or fall back to argv[0] and document the caveat that the returned path may be relative, or may not actually exist?

I presume that in cases where Swift is being interpreted (swift script.swift, #!/usr/bin/swift, swift repl) that this will return the path to swift rather than the user's concept of what the main program is, and that probably at least needs to be documented...

2 Likes

Take a look at the implementation PR for exact details.

At this point I've already had enough feedback to tell me to just make it optional (throwing is overkill for these edge cases.) So I'll be amending the pitch and implementation shortly to do that.

The current executable is never going to be a Swift source file, certainly. :slight_smile: The name of the property is executablePath, not programPath or mainFilePath or something like that. We can make sure to document the behaviour for an interpreted program in the symbol's markup.

2 Likes

I've updated the pitch to make the property's value optional.

5 Likes