How to make sure my code is cross platform?

Is there a way to check if a Foundation class, method etc is available for example on Linux? I'm asking because trying to use FileHandle like this:

do {
    textures[URL(string: pathname)!
        .deletingPathExtension()
        .lastPathComponent
    ] = try FileHandle(forReadingAtPath: pathname)?.readToEnd()
} catch {
    fatalError(error.localizedDescription)
}

gives me an error that .readToEnd() is only available from a certain version of macOS Catalina. I can fix this by limiting the package to platforms: [ .macOS(.v11) ] but I'm really confused, because the documentation for Foundation apis only includes version compatibility with Apple operating systems.

Does this mean FileHandle is not available on Linux? Or just that .readToEnd isn't? Or if it is available on Linux, why is it not available on an older version of macOS? Where can I find such information?

It would be nice to know from the documentation if something is available on linux or windows. I prefer Xcode for writing Swift packages, and I don't want to have to clone my project on a linux system and then windows system, and load it in VSCode to make sure it works, every time I have to use an api I'm not sure is cross platform. Swift is not as popular as C or C++ or even Rust for certain use cases, so finding information online is much more difficult and often impossible.

4 Likes

The platforms info limits only the versions for the platforms that are mentioned.

For Linux, you do not have the same platform problem because Swift is not part of the operation system installation but you choose which installation of Swift you are using. You only need a enough recent Swift version.

But then, there are some gaps (or were?) of Foundation not supported on non-Apple systems. I would do a test on the platforms supported by your application anyway. For some functionalities, you need to do imports like the following:

#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
    import FoundationNetworking // for URLRequest and URLSession
#endif
2 Likes

Okay, thank you :slight_smile:
Good to know, though I still feel like the documentation is confusing. When an api has a minimum version it's compatible with, or when something is called NSSomething I would assume it's not part of the language, but rather dependent on some OS functionality.

In general, from what I've seen online, the connection to Apple operating systems makes some people dismiss Swift as an iOS/macOS application programming language, even though it's a really good language on Linux, which makes me somewhat sad.

2 Likes

@tomerd Maybe you know if there are any plans or discussions to maybe move the API documentation of the standard libraries to swift.org and also help for a better understanding / avoiding misunderstandings of platform availability? (Sorry for picking you, you seem to attend most meetings of the website workgroup.)

1 Like

NSSomething should never work outside Apple‘s platforms, but NSSomething is sometimes mentioned in the API description of things that are not NSSomething themselves and very well available on Linux & Co.

Oh, I see,
yeah I was referring to NSData being in the return documentation for .readToEnd():

The data available through the file handle up to the maximum size that can be represented by an NSData object or, if a communications channel, until an end-of-file indicator is returned.

edit:
Plus, all the NS things show up in completion suggestions with "Foundation not imported" for some reason, and when I have:

import class Foundation.Bundle
import struct Foundation.Data
import struct Foundation.URL
import class Foundation.FileHandle

they show up without the warning, even though they aren't actually imported and using them would give an error that they cannot be found in scope.

unfortunately, there really isn’t a substitute for comprehensive multi-platform CI. for a long-term solution, i would really invest some time in setting up an actions workflow (inspiration) that runs on every PR to prevent these kinds of issues.

4 Likes

Well, it's not that unfortunate, I think :slight_smile:

Personally I judge code by the philosophy that "if it isn't tested, it doesn't work". Just assume it's broken. I trust tests which execute code and check the results way more than I trust my own reading of the source code.

It gives you a lot more confidence in your code. How do I know my library works on platform X? Because I ran it there, and it did what it was supposed to do.

2 Likes

On Apple platforms, Foundation (and Swift itself) is shipped with the OS, so all of it is "OS functionality"—and indeed that is the reason why there's a minimum version required for some APIs. On Linux, that is not the case and there are usually no such requirements.

A number of NS* classes are part of the open-source Foundation port and are available on Linux.

1 Like

I do have CI set up for one project that I finished, and it did help when there was some difference between importing Darwin and Glibc, where I had to explicitly convert an ioctl() argument to a UInt on Linux :slight_smile:

I just meant to say the documentation makes it unclear whether something is available on a platform or not

You should use this pattern instead:

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
4 Likes

If the error message says that .readToEnd is only available on macOS xyz or later, then .readToEnd is only available in the specificied version of macOS or later. FileHandle itself may have different platform restrictions. this information is available in the documentation. Look at the top of the page. Whether an API is available on Linux is a separate question. This information is not available in the documentation. You'll have to try building the code on Linux.

And they keep their NS* prefix? I would have thought that this prefix gets dropped as soon as implemented for “pure” Swift.

Thanks, this looks better (of course, not having to do this at all or always having to import FoundationNetworking would be even better).

Both Data and NSData are available in Foundation so that old code relying on NSData still works and can be migrated at its own pace, I assume this is applicable to other types too.

This is not something you'd like to do on platforms where additional dependencies are undesirable. FoundationNetworking on Linux depends on libcurl and has certain caveats related to static linking. Similarly FoundationXML depends on libxml2. One wouldn't want to inadvertently bring these dependencies into their project if these modules were always re-exported by the main Foundation module.

1 Like

Then it would be a good thing if you would have to do these imports on Apple systems, too, i.e. also splitting Foundation for these platforms, right?

So they will be gone one day? I think they should, and should already be marked as deprecated.

That would break a lot of code. Too much to really be viable.

I think we should allow you to write import FoundationNetworking unconditionally, though. It would just be a no-op or empty module on Apple systems.

They are unlikely to ever be gone. See the swift-corelibs-foundation README.

You may not agree with that rationale (I don't), but there it is.

1 Like

Foundation is a system library on Apple platforms, so using networking or XML APIs doesn't have the same impact on your project.

There is no plan of which I'm aware to deprecate NS* types supported in the open-source Foundation library. It wouldn't make sense to deprecate types simply because they begin with the letters "NS", and users have no need to "migrate" any working code they have which uses those APIs.