[Pitch] Swift runtime availability

Hey folks,

I've written a pitch related to extending Swift's availability checking to accommodate an ABI stable Swift runtime on non-Apple platforms. Here's a snippet from the Motivation section:

In the future, Swift could become ABI stable on non-Apple platforms. For a given platform, the Swift runtime might either be distributed with the operating system, as it is on macOS, or it could be distributed separately using some mechanism like a package manager. In the former case, extending the Swift compiler to add platform-specific availability for that operating system would be sufficient to allow developers to back deploy Swift binaries. When the Swift runtime is distributed separately from the operating system, though, a platform independent notion of Swift runtime availability is needed instead. This proposal introduces the platform independent Swift availability domain to satisfy both use cases and make the task of writing cross platform code simpler.

I'd love your feedback on this proposal. I'm especially interested in hearing about design challenges or potential issues that maintainers of cross platform packages might face. Thanks for reading!

13 Likes

Overall, big +1 from me. This is going to make writing availability annotations a lot easier.

Could the proposal spell out the impact on the DocC generated availability? In particular, DocC is currently only showing the availability on ABI stable platforms when an API got introduced; however, for folks using Swift on non-ABI stable platforms they need to know which Swift version an API got introduced. Is this new Swift availability visible in the symbol graph so that DocC could leverage this? cc @ronnqvist

Code may be built with an implicit minimum Swift runtime version by specifying the -min-swift-runtime-version compiler flag. This effectively supplies the compiler with a "deployment target" in terms of the Swift runtime version:

Could the proposal include a section about how this relates to the minimum platform versions in the Package.swift manifest? Should we be able to spell those minimum platform versions with Swift runtime versions instead?

4 Likes

Interesting pitch, wonder what the use case is though. Given that Swift is only "ABI stable on Apple's operating systems" and " When compiling for targets that do not offer an ABI stable Swift runtime, Swift availability constraints will be ignored by the compiler," plus " On platforms like macOS where the Swift runtime ships built-in, platform availability can be inferred from Swift availability," it seems like nobody will be able to use this new feature to distinguish anything different, at least as the platforms stand today.

Is the idea that initial users will build the Swift runtimes themselves, say on linux servers, and patch the stdlib source to enable this feature for non-ABI-stable platforms too? Or are there any plans to stabilize the Swift on linux ABI on some distros in the coming year?

Just wondering what concrete future use cases you're anticipating, given that it doesn't seem like this feature will do anything different today.

2 Likes

I came to ask the same thing. Or, rather, I'd be so bold and answer "yes, we should be able to spell that".

I'd argue that for the vast majority of "general purpose" packages the platforms section is really not great in its current form. Nobody cares about the various version numbers of say tvOS, and it is all about the swift runtime + libs version compatibility.

let package = Package(
    name: "my-fantastic-package",
    platforms: [
        .swiftRuntime(.v6_3) //or maybe .swift(.v6_3)
    ],
    products: [...]
)

Are there any reasons to not "just" include the SwiftPM work in this proposal?

6 Likes

Swift Testing needs to mark symbols available based on compiler version rather than runtime version. Is there a way to specify that here? If not, can we make sure it's added? Thanks!

5 Likes

In general I think it's good, but I can see people using this just to simplify gating an Apple-specific functionality that is actually not related to Swift runtime, which makes the code looks somewhat more cross-platform than it actually is, and maybe confusing to newcomers. It's inevitable, but I think it might be a good idea to explicitly discourage this in the doc. (To be clear, the example in pitch is a cross platform Foundation feature and is IMO a perfect usecase.)

As a sidenote, now that Apple aligned OS versions, it might also be a good chance to have a unified appleOS availability domain, thus encouraging people to not abuse this Swift runtime version check. Since it's Apple specific I'm not sure if it's something pitch-able on Swift Evolution though.

6 Likes

We really need to be careful adding things to the SupportedPlatforms in the package manifest. Today, all it does is manage the versioned triple when building and to make sure dependencies are buildable with that triple. And it really is intended for SDK API compatibility, not Swift Runtime. That would be an orthogonal concept.

In order to do this properly we need to have a richer definition of what a platform is so it can have attributes like swift runtime version and what it means to be compatible. And so that we can meet similar needs for Linux and Windows SDK compatibility.

4 Likes

Yes, DocC will be able to leverage the presence of @available(Swift ..., *) attributes to render these versions in documentation.

I definitely think that the impact on SwiftPM ought to be part of this proposal. I'm interested in hearing more about how folks would expect this information to be represented in the package manifest as I haven't yet come up with an informed opinion about this. @dschaefer2 I don't entirely agree that it's orthogonal, though. When compiling for macOS, constraining the minimum Swift runtime to 6.2 is the same as specifying that the minimum version of macOS is 26 since that is the version of macOS in which Swift 6.2 shipped. Separate target triple versions and minimum Swift runtime versions only make sense once there is a platform for which there is a standalone ABI stable Swift runtime, distributed separately from the OS. I think we ought to have a design that makes sense for both built-in Swift runtimes and standalone Swift runtimes.

If this version shouldn't be specified in the supportedPlatforms list, where should it go in the manifest instead? If platform and Swift runtime versions are specified separately, how should we handle contradictory values like Swift 6.2 and macOS 15 in the same manifest?

That's right. Out of the gate this proposal would only offer a more concise, platform agnostic syntax for specifying availability which is currently only relevant on Apple's platforms. It also removes one of the barriers to shipping ABI stable Swift on other platforms in the future, though.

That's an interesting use case that I'd like to hear more about. I don't think it fits in this proposal as it's a very different application of availability that doesn't seem essential to the problem this proposal is designed to solve. I can add it as a Future Direction, though, to ensure we consider the impact that having third kind of "Swift" availability in the future ought have on the design.

I totally agree that there's an opportunity to simplify availability for targeting Apple's platforms specifically. However, you're also right that such a change would be a vendor prerogative and not subject to a Swift evolution proposal (just like when support for visionOS availability was introduced).

4 Likes

Very nice improvement overall, I think this is a great direction.

I agree about @available(Swift, deprecated) being un-necessary, we should probably diagnose it better though as "use @available(*, deprecated) instead". I do have one consistency question though. We do allow:

@available(Swift, introduced: 6.0, deprecated: 6.1, obsoleted: 6.2)
public func thisWasAMistake()

And we did spell out we won't allow:

// Use @available(*, unavailable) instead
@available(Swift, unavailable) // error: 'unavailable' cannot be used in '@available' attribute for Swift
func unavailableInSwift() { }

What about the following though?

@available(Swift 6.0)
@available(Swift 6.1, deprecated, message: "...")

Should this be equivalent to the introduced/deprecated dance? I'm asking since I'm wondering if this wouldn't give more context and information than just *, as we're informing the user since which Swift version they can use some other API instead of this one etc.

1 Like

Yeah, it would be easy enough to offer a fix-it.

If you wanted to specify a message: along with a versioned deprecation you'd spell it like this with the existing syntax:

@available(Swift, introduced: 6.0, deprecated: 6.1, message: "...")

I think this proposal should avoid introducing any new @available grammar if we can avoid it; the existing grammar is complex enough as it is.

3 Likes

If you do end up introducing new grammar, please check with me or the other Swift Testing code owners before enabling it as we need to parse @available attributes on test functions and will need to be able to handle the new syntax.

I think that's good enough; happy to keep the proposal minimal and not invent new spellings here. Thanks

1 Like

I agree that it should be considered. But as I mentioned we need to be really careful with it. I think you’ll run into a lot of issues as you introduce this.

As an example, we are not going to get rid of the SDK version check so if you add this runtime check it will be a separate clause anyway and you will get the same conflict. And what if Swift 6.3 also has a minimum version of macOS 26.

My bigger concern is where this mapping is managed. Hardcoding it in SwiftPM seems to come with a lot of risk. I’d like to see this be pluggable and data driven from the SDKs or runtime. And I’d like us to consider what it means on other platforms. What does it mean on Linux and Windows and any other new platform the community adds support for. I’m also trying to understand how we can bring the SupportedPlatforms concept to all of them.

1 Like

When you refer to "SDK version checks", what do you mean? If you're talking about the contents of platforms: argument in the manifest then I think that terminology is a bit confusing. The versions specified there do not interact with the version of the SDK present at build time. They indicate the minimum versions of OSes at runtime that the sources are compatible with. The term for this setting that I'm most familiar with is "deployment target" (that's how Xcode renders its equivalent settings).

If we land on a design where it is necessary for SwiftPM to map versions I agree that it should be data-driven and not baked in to the SwiftPM executable. It's probably better to avoid requiring any build system to do any mapping at all, though. Let's enumerate some possible high-level designs and their implications.

  1. Don't offer a way to specify minimum Swift runtime version in the package manifest at all. Until there is an ABI stable distribution of the Swift runtime that is separate from an OS, it isn't strictly necessary to have a way to configure a minimum Swift runtime version in package manifests. Developers can continue to specify deployment targets using per-platform entries in platforms:. In other words, we could just delay confronting the problem until it becomes necessary to do so, at the expense of missing an opportunity to simplify manifests for cross-platform packages.

  2. Accept a Swift runtime version in the platforms list in the manifest. For example:

    let package = Package(
      name: "PackageExample",
      platforms: [
        .macOS(.v26),
        .swiftRuntime(.6_2),
      ],
      // ...
    

    I'm imagining that SwiftPM would only pick one value from this list for a given build, so it would need to determine when the .swiftRuntime entry applies. In this example, when building for macOS the .macOS entry is more specific so I think it should be preferred. When compiling for iOS, though, the .swiftRuntime version would apply.

  3. Allow both a Swift runtime version and a platform version to be specified separately in the manifest. For example:

    let package = Package(
      name: "PackageExample",
      platforms: [
        .macOS(.v26),
      ],
      minimumSwiftRuntimeVersion: .6_2,
      // ...
    

    In this design, minimumSwiftRuntimeVersion: would apply regardless of target platform. The deployment target specified in platforms:, if relevant to the target platform, would also apply simultaneously.

Between options 2 and 3, I think 3 is probably the better design long term because it decouples platform versions and Swift runtime versions conceptually, which may be necessary for future platforms. Regardless of which design is picked, we need to think through how the compiler invocation will be constructed. If we don't want SwiftPM to be responsible for doing any mapping between Swift runtime and platform versions, then I think that implies SwiftPM ought to just take the deployment target and Swift runtime versions that apply to the build and pass them verbatim to the compiler in -target and -min-swift-runtime-version respectively.

The Swift compiler needs to handle these arguments in a well defined way when targeting a platforms with a built-in Swift runtime. I can think of two options:

  • Require the version specified in the -target to match the deployment target inferred from -min-swift-runtime-version. This would be consistent and predictable, but is probably too restrictive, since in practice it would mean that entries in platforms: that are compatible with the minimumSwiftRuntimeVersion: value are required instead of optional.
  • Prefer the higher or the lower of the two deployment targets. I think preferring the higher deployment target makes the most sense. This would allow most packages to just specify minimumSwiftRuntimeVersion: and leave platforms: empty unless they needed a higher deployment target on some platform.

I think my conclusion is that:

  • Package manifests should specify a minimum Swift runtime version separately from platforms:.
  • SwiftPM should pass the minimum Swift runtime version directly to the compiler without performing any mapping.
  • When targeting platforms with built-in Swift runtimes, the compiler should tolerate contradictory -target and -min-swift-runtime-version values. The effective deployment target is the higher of the two values.
4 Likes

I am very much in favor of this direction. Addressing some of the discussion below...

This information will be available to DocC on all platforms (whether ABI stable or not), but it is not precisely what you're asking for. This covers runtime availability, not API availability. For example, the Span APIs were introduced in Swift 6.2 (that's when the APIs because availability), but back-deploy back to the Swift 5.1 runtime. Their Swift runtime availability, which is what is covered by this proposal, is therefore Swift 5.1.

We don't currently track what Swift version introduced a specific API in the standard library, so the information you're requesting for DocC to display would need to either be retroactively added (possibly on top of another availability feature) or kept in a separate database.

"Plans" is perhaps too strong a word, but the specific use case that this proposal unlocks is exactly as you say: we can start packaging ABI-stable Swift runtimes for various Linux distributions, separate from the toolchain distribution.

I don't understand the scenario you're worried about. Swift's @available and if #available ignores availability for any platform that one isn't compiler for. So if you have code like this:

if #available(macOS 15.0, *) { ... }

the body of the if will execute on macOS >= 15.0 and every other platform that the code is compiled for. The only place it doesn't run is macOS < 15. Now, it might not successfully compile for another ABI-stable platform (such as iOS 18) if it uses something that was introduced in iOS 18 or newer, and you'd have to expand the #available check to cover more of those platforms.

If we use the proposed Swift runtime availability:

if #available(Swift 6.0, *) { ... }

this is strictly more portable: it now correctly compiles for all ABI-stable platforms that ship the Swift 6.0 runtime (including, e.g., iOS 18), and is ignored anywhere that does not have a stable ABI.

This is one of the reasons I'm excited about this proposal: it provides a spelling for availability that is both more ergonomic and more portable, while opening up ABI stability to more platforms going forward.

I don't think that last bit is correct. The triple is specifies the deployment target, i.e., the minimum version of the OS that the code should run on. The Swift runtime availability version has the same property, and where the Swift runtime ships in a particular version of the OS, the two are tied together. The SDK version is mostly independent of the deployment target: you can use a newer SDK to deploy to older OS versions, and binaries built against older SDKs will still run on newer OS versions.

This all sounds right to be. I'd like to think about one more aspect of how the build system talks to the compiler: right now, the build system passes down a versioned OS triple for platforms where that is significant, e.g., -target arm64-apple-macos15.0. Could we get the build system out of the business of picking an IS version number of one wasn't explicitly specified in the package manifest? For example, it could pass down -target arm64-apple-macos -min-swift-runtime-version 6.0 and the compiler would infer the macOS deployment version from the Swift runtime version.

Doug

7 Likes

This is one of the reasons I'm excited about this proposal: it provides a spelling for availability that is both more ergonomic and more portable, while opening up ABI stability to more platforms going forward.

I'm all for more portable code. However, the problem is that on Apple platforms, there's a lot of features unrelated to Swift runtime that is gated to each OS's version. It's inevitable that some would start to use if #available(Swift 6.2, *) to gate usage of example Liquid Glass API, instead of doing the per-OS dance (which is now 5 platforms in total). This IMO makes the code less understandable to new comers, as it looks like Liquid Glass is somehow implemented in Swift runtime, while in practice, it's just the coincidence of Swift 6.2 runtime is shipped in the version of whatever Apple OS that Liquid Glass API is first shipped.

2 Likes

That's a reasonable concern, but I suspect it would be solved by the hypothetical “appleOS 26.0” alias mentioned earlier in the thread. My understanding is that these specifiers are not managed by the Swift evolution process though, so it probably shouldn't affect this proposal.

2 Likes

Check out what Swift Testing does here:

/// @Metadata {
///   @Available(Swift, introduced: 6.2)
///   @Available(Xcode, introduced: 26.0)
/// }
public struct Attachment<AttachableValue> where AttachableValue: Attachable & ~Copyable {
  // ...
}

Yes, IMO this is something that should be supplied by the operating system (or whatever ships the Swift libraries). Conceptually, this declares the compatibility with the OS-supplied additional (Swift) libraries.

On Linux, I don’t think anything other than a general unversioned “Linux” platform makes sense to ship as part of Swift itself, as Linux systems as a whole may have nothing in common other than the kernel. However, a hypothetical distribution that comes with system libraries written in Swift (or C, which I assume also has availability annotations with Clang) should be able to declare its own “FooOS x.y” availability where it makes sense – edit: to be clear, I don’t think this makes sense to have in the case of rolling release, or to cover non-core additional packages you can install with a package manager – without having to patch the compiler and/or SwiftPM, which then represents the versioning for those system libraries. (Compiling for a different/lower minimum operating system version other than the current one is orthogonal to this, because most systems come with glibc which does not support this right now which would be the biggest blocker, even if all the Swift parts did. I don’t know how it is with other libcs.)

Worth noting that if implemented like this, this would essentially make the platform name a global namespace. If collisions are a realistic concern, perhaps encouraging using reverse-DNS names is a good idea, like with bundles, perhaps with an additional short local name mapping for packages specifically written for that OS or family of OSes.

Windows is a bit of a different case, since I don’t assume Microsoft will bundle Swift with the OS any time soon :^) – while it might still be of interest to supply availability information in this case, for example for WinAPI feature detection, which should map pretty cleanly to the availability system currently available on Apple platforms, it could be possible to include detection support in the official Swift distributions itself.

As for a completely spitballing idea on how this could work in practice (I have no idea how availability annotations currently work on Apple platforms), both of these could be implemented as a platform-agnostic protocol that comes with Swift itself, and a bundle shipped by the platform which provides an implementation of that protocol. The bundle could then query something like IsWindowsVersionOrGreater (on Windows), optionally supply additional compiler arguments for the target version if compiling for different platform versions is supported, including C version defines or linking binaries to itself for if #available support. This can then be loaded by SwiftPM to check platform support for the package, loaded by the compiler to check @available, and linked by the final binary to check if #available. (Making this completely data-driven would be great, but I don’t know if it’s doable if you want availability checking for Windows with a non-bundled SDK that may run on any version. Unless you special-case Windows.)

1 Like

All new and existing APIs in the Swift standard libraries should be given Swift availability attributes instead of platform specific ones. The APIs in other libraries that are distributed with the Swift runtime, such as Testing or Foundation, should also be given Swift availability attributes.

To avoid confusion, the existing swift availability domain, which represents availability with respect to enabled Swift language mode, should be renamed to SwiftLanguageMode:

I think a runtime availability feature is going to very good to have, and I agree with prior comments that it stands on its own merits even if on all current (i.e., Apple) platforms it's in lockstep with OS availability.

I am worried about this minor aspect regarding the spelling, however. Yes, the pitch is for Swift as opposed to swift (and even proposes to rename the latter). But the problem at hand is that we have overloaded on the meaning of "Swift", so I think the solution must be therefore to avoid further overloading rather than a kind of "musical chairs" as to what gets to claim the unadorned term.

Something like SwiftRuntime (*) seems entirely self-explanatory, for example; it is not overly verbose, cannot be confused with any other "Swift" appellation new or old, totally sidesteps any prior naming mistakes, and avoids the code churn of requiring uses to adjust existing lower-s swift usages.

(*) or, honestly, Runtime—the one given is that it's Swift.

4 Likes