- Proposal: SE-0513
- Authors: Jonathan Grynspan
- Review Manager: Tony Allevato
- Status: Returned for revision
- Implementation: swiftlang/swift#85496
- Review: (pitch) (review) (returned for revision)
This proposal adds to the Swift standard library an interface for reading the path to the currently-executing binary. This value is useful to developers who need to spawn additional processes or who need to present information about the current program to the user.
There is no portable way (e.g. a C standard library function or POSIX
specification) to get the path to the current executable. Historically,
developers have to write their own platform-specific implementation to get this
value or have reached for argv[0] thinking it contains said path. We can do
better and provide a common interface for this functionality across the
platforms that Swift supports.
Note
Regarding argv[0]: argv[0] is not required by POSIX nor any revision of
the C language standard to contain the path to the executable. From §5.1.2.3.2
of the C23 standard (entitled "Program startup"):
If the value of
argcis greater than zero, the string pointed to byargv[0]represents the program name;argv[0][0]shall be the null character if the program name is not available from the host environment.
In practice, argv[0] is controlled by the parent process and may contain a
relative or partial path or even an unrelated string.
Swift, like most modern languages, provides access to information about the
program's environment such as its command-line arguments (including argv[0]).
Those arguments are read by the Swift runtime in a platform-specific manner.
We should take a similar approach to read the executable path and provide it to
developers if needed.
There are a number of modules in the Swift toolchain and its ecosystem that would benefit from a way to get the executable path, including:
- Foundation
- Swift Argument Parser
- Swift Testing
- swift-subprocess
I propose adding a new read-only property named executablePath to the existing
CommandLine
type in the standard library.
While C and C++ (without Boost) do not provide an equivalent API, other modern languages do:
| Language | Equivalent API |
|---|---|
| C++ (with Boost) | boost::dll::program_location() |
| D | std.file.thisExePath() |
| Go | os.Executable() |
| Haskell | System.Environment.getExecutablePath |
| Rust | std::env::current_exe() |
| Zig | std.fs.selfExePath() |
The following new API is added to the standard library:
extension CommandLine {
/// The path to the current executable.
///
/// The value of this property may not be canonical. If you need the canonical
/// path to the current executable, you can pass the value of this property to
/// `realpath()` (`_wfullpath()` on Windows) or use `URL` to standardize the
/// path.
///
/// If the path to the current executable could not be determined, the value
/// of this property is `nil`.
///
/// - Important: On some systems, it is possible to move an executable file on
/// disk while it is running. If the current executable file is moved, the
/// value of this property is not updated to its new path.
public static var executablePath: FilePath? { get }
}The implementation does not attempt to resolve symlinks or other forms of
indirection in the path provided by the underlying OS API call1.
This is a pragmatic decision: in the common case, a symlink is not present and
the I/O necessary to try and resolve it is wasted effort. If there is a
symlink, its presence is not necessarily a problem for the calling code. Callers
that need to resolve symlinks in this path can manually call realpath()
(_wfullpath() on Windows) or equivalent API as needed. Note that, as of today,
FilePath does not provide a wrapper interface around realpath() (such an
interface is beyond the scope of this proposal, but if one is added in the
future we can update the documentation for executablePath accordingly).
If the current executable is moved on disk after it starts, the underlying system may or may not update the path it reports. This is ultimately a platform-specific implementation detail and one that we cannot reliably work around, but neither can developers who implement their own version of this property. The documentation therefore warns developers of the possibility.
On platforms where this property cannot be implemented (generally because the
underlying operating system does not provide an interface for querying the
executable path), the value of this property is always nil.
This change is additive. Developers (if any) who have already added an
executablePath property to CommandLine may need to rename that property or
replace it with this one.
This proposal is purely an extension of the standard library which can be implemented without any ABI support.
On Darwin, the property can be back-deployed because it is implemented atop existing API provided by the operating system.
This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source or ABI compatibility.
N/A
-
Doing nothing.
-
Using the implementation and interfaces in Foundation (e.g.
Bundle.executableURL). Foundation is commonly imported by Swift packages and projects, but it is fairly "high up" in the stack. Some components cannot link to it (such as the standard library) or have significant constraints when linking to it (such as Swift Testing). -
Exposing the property as an instance of
Stringinstead ofFilePath. The original version of this proposal did so, but we currently expect thatFilePathwill be brought from the swift-system package into the standard library with SE-NNNN.FilePathrepresents a better interface for path strings as it can handle invalid Unicode sequences ("bag-o'-bytes encoding"). -
Exposing the property as a C string rather than as a Swift value. We could provide an interface that produces an
UnsafePointer<CChar>(orUnsafePointer<CWideChar>on Windows), aSpan<CChar>, aContiguousArray<CChar>, etc. We could still provide such an interface if needed, but it is straightforward to get a platform C string from an instance ofFilePathusingwithPlatformString(_:). -
Making the property's type non-optional. The initial version of this proposal presented a non-optional property that aborted if the path was unavailable. This makes it difficult for developers to recover from a low-level failure, but a failure to get the executable path does not necessarily imply a fatal error in the program.
-
Making the property's getter throwing. The failure modes for this property are all edge cases. Throwing an error is a bit too "heavyweight" for the API's expected use cases. It is unlikely the program or the user could recover from a thrown error in a way that would allow the API to succeed the next time it is called, so callers would probably end up ignoring errors with
try?or similar.
Footnotes
-
On some platforms (namely Linux) the API itself consists of reading a symlink, which we do out of necessity. ↩