Swift Package Manager and supported platforms / avoiding `@available(macOS 10.15.0, *)`

The description of how to use the platforms in a SPM manifest (Package.swift) seems to be ambiguous:

Citation:

By default, the Swift Package Manager assigns a predefined minimum deployment version for each supported platforms unless you configure supported platforms using the platforms API. This predefined deployment version is the oldest deployment target version that the installed SDK supports for a given platform.

So what does this mean:

  • If a platforms entry is used, do I have to list every platform I want to target?
  • Or do I only add an entry for the target where I need a certain version?

I would like to avoid @available(macOS 10.15.0, *) at every function that uses async (not to add those seems to be OK for a normal build, but not for profiling(*)). I would like to build for macOS >= 10.15, Linux, and Windows. But as documented Platform or SupportedPlatform does not have a windows entry, and even .linux cannot be added in Package.swift.


(*): Maybe this has something to do with SPM vs. Xcode projects, as profiling with Xcode is only possible for Xcode project and not for SPM projects because of the authentication error that occurs for SPM projects when trying to profile.

No. As you noted, you couldn't do that even if you wanted to. The platforms directive is intended to behave much like the @available directive, but where the * is implicit, and implies "all other platforms". So only the platforms you specify are affected.

Don't sweat, I have experienced your exact confusion in the past. The platforms property is poorly named.

1 Like

platforms: [.macOS(.v10_15)] is almost the same as slapping @available(macOS 10.15, *) on every symbol in the package, except that there is no equivalent to if #available to enable clients to use it conditionally. That makes platforms very convenient for a top‐level application with no clients. But it also makes it a terrible mistake for any library package, as clients inevitably fork to convert to the more lenient @available, and then your package exists double in the ecosystem with clashing identity and you become forever associated with dependency hell.

This seems like an overly broad statement, e.g. if someone builds a library on top of SwiftUI, it doesn't really make sense to offer it to clients with deployment targets that don't even have SwiftUI.

I think the general rule is, if you make a library that builds entirely on an API or framework with limited availability, platforms is a good choice. If the library offers additional functionality that's dependent on a newer deployment target, availability with broader platforms is the tool of choice.

1 Like

If the client has three targets, a data model along with a command line parser and a GUI interface for interacting with it, then the client either restricts the first two targets needlessly to recent platforms, drops your package as a dependency, or forks your package to fix the availability.

While the Swift toolchain packages themselves tend to go for option 1, in the wild I see 2 and 3 being way more popular. Option 2 seems favoured regarding potential new dependencies, which get rejected, and option 3 seems favoured whenever an established dependency adds platform restrictions later on.

Good insights. So I think:

  • If the core of your library really needs e.g. a certain macOS version, the platforms entry should be OK.
  • If just a portion of your library needs a certain version and important parts of it do not, better use availabe annotations where needed.

(Update: See my next comment.)

Another point is if you annotate your library with these version infos at all: E.g. you can create and use a library that uses async/await very well without any version annotation, but if you then want to do profiling using an Xcode project, the profiler (instruments) will complain. So I think:

  • If you do use features not available on older platforms, you should use version annotations….

…but I am not sure how far this should go, and even how to know about using features that need version annotations (besides complaining when profiling, the compiler and/or Xcode are not very helpful here).

Also, the question arises if or when certain version annotations will get out-dated i.e. superfluous at a certain point, e.g. should platforms: [.macOS(.v10_15)] still be necessary in let´s say five years from now? My current use case is indeed async/await.

I also think that better documentation is needed here (or am I overlooking something?). Maybe a document about best practices with a link to it on the Swift documentation page?

Ah, just when reading my last comment again I really understood one of the problems mentioned:

From the point of a library that really needs a certain version, the platforms entry seems to be OK, but this forces another package using this library to the same (or higher) platforms entry although the there might be parts of the package that work well without this library. Hmm…

…So:

  • As a first „rule“, use available and not platforms for a library.
  • If it is clear that your application or any application using your library will only be used in a certain context, you might use platforms to avoid the many available annotations that you else would have to use.