New swift-corelibs-foundation module: FoundationXML

You may remember that when we originally pitched splitting Foundation, it was noted that libxml2 was a dependency it was possibly worth splitting out. I have landed patches to produce the proposed FoundationXML module; similar to FoundationNetworking, end users will need to import this module explicitly to use XML parsing facilities.

For the full details on this and other changes, check out the swift-corelibs-foundation release notes. The relevant portions on the split are also in this post below:

Dependency management release notes

On Darwin, the OS provides prepackaged dependency libraries that allow Foundation to offer a range of disparate functionalities without the need for a developer to fine-tune their dependency usage. Applications that use swift-corelibs-foundation do not have access to this functionality, and thus have to contend with managing additional system dependencies on top of what the Swift runtime and standard library already requires. This hinders packaging Swift applications that port or use Foundation in constrained environments where these dependencies are an issue.

To aid in porting and dependency management, starting in Swift 5.1, some functionality has been moved from Foundation to other related modules. Foundation vends three modules:

  • Foundation
  • FoundationNetworking
  • FoundationXML

On Linux, the Foundation module now only has the same set of dependencies as the Swift standard library itself, rather than requiring linking the libcurl and libxml2 libraries (and their indirect dependencies). The other modules will require additional linking.

The following types, and related functionality, are now only offered if you import the FoundationNetworking module:

  • CachedURLResponse
  • HTTPCookie
  • HTTPCookieStorage
  • HTTPURLResponse
  • URLResponse
  • URLSession
  • URLSessionConfiguration
  • URLSessionDataTask
  • URLSessionDownloadTask
  • URLSessionStreamTask
  • URLSessionTask
  • URLSessionUploadTask
  • URLAuthenticationChallenge
  • URLCache
  • URLCredential
  • URLCredentialStorage
  • URLProtectionSpace
  • URLProtocol

Using this module will cause you to link the libcurl library and its dependencies. Note that the URL structure and the NSURL type are still offered by the Foundation module, and that, with one exception mentioned below, the full range of functionality related to these types is available without additional imports.

The following types, and related functionality, are now only offered if you import the FoundationXML module:

  • XMLDTD
  • XMLDTDNode
  • XMLDocument
  • XMLElement
  • XMLNode
  • XMLParser

Using this module will cause you to link the libxml2 library and its dependencies. Note that property list functionality is available using the Foundation without additional imports, even if they are serialized in the xml1 format. Only direct use of these types requires importing the FoundationXML module.

The recommended way to import these modules in your source file is:

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

#if canImport(FoundationXML)
import FoundationXML
#endif

This allows source that runs on Darwin and on previous versions of Swift to transition to Swift 5.1 correctly.

There are two consequences of this new organization that may affect your code:

  • The module-qualified name for the classes mentioned above has changed. For example, the URLSession class's module-qualified name was Foundation.URLSession in Swift 5.0 and earlier, and FoundationNetworking.URLSession in Swift 5.1. This may affect your use of NSClassFromString, import class… statements and module-name disambiguation in existing source code. See the 'Objective-C Runtime Simulation' section in the release notes for more information.

  • Foundation provides Data(contentsOf:), String(contentsOf:…), Dictionary(contentsOf:…) and other initializers on model classes that take URL arguments. These continue to work with no further dependencies for URLs that have the file scheme (i.e., for which .isFileURL returns true). If you used other URL schemes, these methods would previously cause a download to occur, blocking the current thread until the download finished. If you require this functionality to work in Swift 5.1, your application must link or dynamically load the FoundationNetworking module, or the process will stop with an error message to this effect. Please avoid this usage of the methods. These methods block a thread in your application while networking occurs, which may cause performance degradation and unexpected threading issues if used in concert with the Dispatch module or from the callbacks of a URLSessionTask. Instead, where possible, please migrate to using a URLSession directly.

42 Likes

This is fantastic news @millenomi: thanks for doing this work!

2 Likes

Fantastic news, thank you! :-) Really cool seeing the splitting up of foundation to lessen the main’s chunk of dependencies <3

Excellent. By the way, would they still be published in the Foundation repository?

Are there other parts of foundation that will be broken out in a similar manner?

This is already the case; all three modules are built when the repository builds.

These are the only two dependencies we have that Swift itself doesn’t require, AFAICT. I have personally no more modules to propose splitting off.

2 Likes

Is it possible to build FoundationXML from source to use on iOS, where the system‐provided Foundation lacks its functionality?

The use case is a cross‐platform Swift package that would like to use XMLDocument under the hood.

I wouldn’t say it isn’t possible, but you will not be able to link to libxml there as it isn’t in the iOS SDK, if I recall correctly. The framework also makes a number of assumptions about running that may not be correct on iOS or in general on apps that aren’t carefully linked to avoid referring to symbols on Darwin Foundation.

I strongly recommend against.

Yeah, that’s sort of what I figured. But since the source code is speckled with #if os(iOS) statements, I thought I might as well at least ask.

I am told libxml is available on iOS; my bad. The rest of the point stands, though: there’s some pretty decent amount of work from the code there to anything that can run on Darwin Core Foundation, and I do not recommend maintaining that.

With this change, SwiftLint doesn't compile on Linux with Swift 5.1 anymore.

The incompatibility comes from a transitive dependency, SWXMLHash, where the issue has since been fixed, but the change hasn't been propagated down the dependency chain. I currently don't even know which dependency of SwiftLint pulls in this dependency, but it's not a direct dependency, and in any case, swift package update within the SwiftLint repo doesn't fix it, so it means that at least one other intermediate dependency needs to be updated first.

Was it really necessary to introduce such as breaking change in a minor version? I'm getting somewhat annoyed at the fact that each and every Swift upgrade breaks something on Linux.

It was understood that this was a breaking change, which will require some adoption.

Generally speaking, 5.1 introduces entire classes of breaking changes in Foundation. This allows the library to go from the underspecified, temporary setup it has shipped with until 5.0 into something we can support long term — especially as we look at source compatibility going forward, at possible future directions like a Linux ABI, or at correctly ensuring no source depends on legacy incorrect behavior as we introduce new API surface and start tracking Darwin more closely. Given the inflection point, it was now or not at all.

2 Likes