`#if os(Darwin)`, a shorthand for checking for Darwin platforms

I've made a working implementation here, with CI tests passing & I've tested locally.

Previously discussed in 2016 in this thread.

The idea is basically just adding one #if os directive but all inclusive for Darwin platforms. Currently, to check for Darwin platforms, you have to type out a lengthy statement:

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
// Darwin code
#else
// Non Darwin code
#endif

You will see stuff like that very frequently in Apple's own Swift code, the issue is that this is not future-proof for any future Darwin platforms, and quite lengthy to type out & read, which is why I believe this would be better:

#if os(Darwin)
// Darwin code
#else
// Non Darwin code
#endif

I've also considered a @available support, however, honestly, that's not realistic as most developers don't know the Darwin version number for specific iOS/macOS/tvOS/watchOS releases.

As mentioned above in this post, I've made a working implementation of this, and have tested successfully with CI and locally.

Please let me know what you think.

20 Likes

Excellent. I have been missing a simple way to distinguish APPLE from !APPLE like this for ages. In the meantime I have been using

#if canImport(ObjectiveC)

instead, which works, but is not quite the correct semantics.

4 Likes

That's one of the reasons I made that impl, I've seen people do stuff such as

  • #if _runtime(_ObjC)
  • #if canImport(ObjectiveC)
  • #if canImport(Darwin)
    Which, I don't believe it would be good practice to distinguish Apple platforms from non-Apple platforms using those methods, hopefully #if os(Darwin) changes that if it does pass.
5 Likes

I'll admit to using canImport:

#if canImport(Darwin)

import Darwin
typealias DirectoryStreamPointer = UnsafeMutablePointer<DIR>?

#elseif canImport(Glibc)

import Glibc
typealias DirectoryStreamPointer = OpaquePointer?

#else

#error("Unsupported Platform")

#endif

I think proper #if support would be nicer, but I am a bit torn with this platform specific conditionals that seem to be in vogue - I was raised with conditionals on features ('have pthreads', 'have kqueue', 'have epoll' etc) - that might be different for the same platform depending on version, but that is a different aspect...)

4 Likes

We’ve generally tried to make sure that #if os(osName) options are mutually exclusive—that is, that there’s only one osName that will be true at a time—to avoid the confusing tangle of target defines that we see in Objective-C, where for instance TARGET_OS_MAC is true on iOS. I’m not sure if we’ve actually held the line on that, though.

Having said that, I think it’d be wonderful to have this functionality—just perhaps not in os(…).

4 Likes

Actually, my first thought on how to implement this was to introduce something like #if platform, with Darwin and Unix being options, though, to be honest, I couldn't figure it out.

Thinking out loud here—what about #if kernel?

1 Like

Hmm, I'm torn on this. On the one hand, this makes sense for the Darwin based platforms, and also would fix up the os(Android) || os(Linux) cases. On the other hand, what about baremetal platforms (i.e. no kernel) and platforms such as cygwin? Would it make sense to return "NT" as the kernel even though the user may expect Linux? Another thing to consider is something like personalities.

As a crappy compromise, something like #if vendor(Apple) could work to group together the Apple OSes perhaps?

1 Like

I don’t get it, why the new vendor statement? #if os(Darwin) makes most sense here, it works with the existing #if os statements we have, don’t get why #if vendor would be better

1 Like

I'd imagine we could do kernel(none) && os(...) and kernel(nt) && targetEnvironment(cygwin)?

To maintain os options as mutually exclusive.

2 Likes

I kind of like the idea of #if kernel, though some may argue it’s out of scope for the Swift compiler to be giving that information..

1 Like

If this is a tangible goal which is really that important, we could instead do #if platform and provide options to check for Darwin, Unix, or BSD in general from there, though that may confuse people due to the wording being similar to #if os.

1 Like

I think that this could work, but I do worry about the complexity. I may want to build the code for BSD to run on Linux with the BSD personality. That is to say, I expect most users would not expect the following behavior:

#if os(Linux) && kernel(BSD)
// this is valid
#endif

I think that platform works better than kernel (cygwin and MinGW are more platforms than kernels or OSes, it also covers WASI well IMO). I'm not sure I understand the "Unix" category. Would that be SCO Unix? SystemV Unix? All the Unix derivates?

That’d be up for discussion if such an idea does get far. With that being said, I’m still against not using #if os for Darwin, I do get a goal was to keep #if os options mutually exclusive however #if os is already familiar to most Swift programmers, especially ones writing cross platform code.

I actually think this is correct. If you're trying to guard import Darwin and uses of the Darwin module, then isn't canImport(Darwin) expressing precisely the thing you want?

What exactly is the API surface of the os(Darwin) SDK? Can I assume only that it includes the Darwin module? If so, is it not a synonym for canImport(Darwin)?

Or does os(Darwin) also imply the availability of other Apple frameworks, such as SwiftUI? What about frameworks that are not available on every Apple platform, such as AppKit and UIKit, or the recently-announced ClockKit?

Correct me if I'm wrong, but I don't believe Apple has ever defined a set of frameworks that all Darwin-family platforms will support. Pretty much the only thing that seems reasonable to assume is that import Darwin will work on a Darwin platform (kind of, by definition). IMO, it's up to Apple's platform team whether or not they want to introduce an expanded concept of a "Darwin OS" which includes more than just that one module, and which other modules os(Darwin) should include.

On a related note, hopefully one day we'll add some kind of CStdlib module, which will cut down the boilerplate for cross-platform code. So you could just import CStdlib, and handle the platform differences as and where they occur.

5 Likes

It’s an alias to check for the 4 Darwin platforms. I think that’s reasonable for anyone to think.

It is the very exact same as

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)

Which, if you read apple’s own cross-platform Swift code, you’ll find is used extensively, #if os(Darwin), in the implementation I have made, is the exact same.

How much of that code predates canImport?

Are developers choosing not to use canImport? Is it proving to be inadequate in some way?

None.

For this purpose? Seems so.

Regardless of all these, I still think it would be just better to have the Darwin alias to make existing code using the #if os statements more readable

Is it possible that is just a style choice? That it isn't due to a functionality gap in canImport?

If canImport really is deficient in some way, it would be nice to know more about that. At least for me, I write plenty of cross-platform code, and canImport(Darwin) seems to be working fine.

I'd be interested to know which limitations developers are encountering. I'm not sure how we can judge whether os(Darwin) would be better, without learning more about why developers aren't using the existing solution. Currently it seems we're just guessing at why developers happened to write a thing in a certain way.

7 Likes

I do agree it was correct, that was a bad example - I picked the first one I had - here's perhaps a better example:

    public static var defaultPluginExtension: String {
        #if canImport(Darwin)
            return "dylib"
        #elseif canImport(Glibc)
            return "so"
        #else
            #error("Unsupported Platform")
        #endif
    }

There it feels a bit strange to use canImport, but it's the best mechanism I found.

4 Likes