Share

RSS

Swift on Windows: Readdle's Experience and Tools

At Readdle, we’re building the future of email with Spark. It’s the best personal email client and a revolutionary email for teams. Millions of people use it on iOS, Android, and Mac to love their email again. We want Spark to be a cross-platform email client, and our team is already working on the Windows version many of our users are waiting for. Great things take time to build, and that’s why we can’t share any ETA yet.

In this article, we’d love to share our experience and practical insights in Swift on Windows to demonstrate possible starting points, real use cases, and capabilities of Swift, as well as to inspire others to do their best with the tools we used.

We started experimenting with Swift on Windows more than a year ago. By that time we already released Spark for Android which uses Swift to share core code with iOS/macOS, and the opportunity to extend to one more platform was really tempting.

Despite some functionality being unready as of yet, Swift on Windows turned out to be fully satisfying our needs. In fact, some third party C/C++ dependencies gave us more headaches than Swift did itself. All business logic of Spark is located in a separate Core module. A pack of modules, actually, but we refer to them as Core. This allows us to use any UI framework on the target platform: AppKit on macOS, UIKit on iOS, native UI Toolkit on Android. So, basically, we had to port Spark Core on Windows. After all initial concepts were proved, it was mostly routine day-to-day work to bring it alive on Windows.

What we have now:

  • Swift toolchain and SDK build tool
  • 9 Swift modules (255 739 SLOC, 2 133 source files)
  • 3 third party swift modules
  • 1452 tests (powered by XCTest)
  • Windows-based CI to keep all tests green
  • Heterogenous build system (partially CMake, partially custom scripts)

Swift Toolchain and SDK

Getting started with Swift on Windows is pretty easy. Just follow instructions from the marvelous //swift/build project. But we needed something different. As Windows support was still being developed, there was a need to investigate various issues in tools and libraries. We needed a tool that allows us to build our own Swift snapshots. And rebuild some specific parts. And do it many, many times a day.

We started with a simplistic and straightforward script based on Compnerd’s pipelines. Over time it has evolved into an agile tool, helping us to create Swift snapshots by schedule, track build state, and apply custom patches.

Third-party modules

Pure Swift modules like CryptoSwift and OAuthSwift almost worked right out of the box. Let’s dive into details.

  1. CryptoSwift is a growing collection of standard and secure cryptographic algorithms implemented in Swift. This library clearly needs no introduction. We just trivially adjusted imports of system libraries, replaced arc4random_uniform calls with random(in:) from Swift standard library and used Windows VirtualLock function as a replacement for mlock. As far as I know, the actual version of CryptoSwift already got arc4random_uniform removed, making it even more compatible with Windows by default.
  2. OAuthSwift. The name speaks for itself. Along with replacing arc4random_uniform calls and imports adjusting, we had to write a replacement for CFStringConvertEncodingToIANACharSetName, as CoreFoundation is not publicly available on Windows. OAuthSwift also contains some UI-related code tied to macOS/iOS frameworks, but as Spark Core does not deal with UI, we could just exclude such sources from the build, no problem.

Here are three common tips for making your sources more compatible with Windows:

  1. Replace arc4random_uniform with corresponding random(in:) call:

    let rand = arc4random_uniform(length) // Before
    let rand = UInt32.random(in: 0..

  2. When missing functions from C standard library, add ucrt import:

    #if canImport(ucrt)
    import ucrt
    #endif

  3. swift-corelibs-foundation has split Foundation into three parts: Foundation, FoundationNetwork, and FoundationXML. If your code uses network functions (URLSession, etc) you probably would need to add FoundationNetwork import:

    #if canImport(FoundationNetworking)
    import FoundationNetworking
    #endif

Build System

At that early point, Swift Package Manager on Windows was too far from completion. Even CMake, being the most agile build tool for Swift on Windows at the moment, had several caveats when we started. But hey, we are engineers, aren’t we? Swift, as a modern llvm-based compiler, behaves pretty friendly. You won’t need a herd of cryptic compiler flags. Just pass source files to the compiler, specify what kind of output you want to get – library or executable, ask for -Optimizations if necessary, specify search paths for imports and libraries, and voilà!

I recommend to check out these examples for CMake-based builds, but for even simpler experiments you could use plain Command Line:

set SWIFTC=%SystemDrive%\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin\swiftc.exe
set SDKROOT=%SystemDrive%\Library\Developer\Platforms\Windows.platform\Developer\SDKs\Windows.sdk
set SWIFTFLAGS=-sdk %SDKROOT% -I %SDKROOT%\usr\lib\swift -L %SDKROOT%\usr\lib\swift\windows -O

%SWIFTC% -emit-executable %SWIFTFLAGS% HelloWorld.swift -o HelloWorld.exe


Sure, a live product contains a lot of components and dependencies. Even a minimal build system needs to resolve search paths for every target, handle built products and resources, provide a convenient way to configure build process, and track build errors. In the end, we had a bunch of Powershell scripts doing all that and doing well. We are still planning to move to Swift Package Manager once it is ready, but it is more like a step to unifying builds over all platforms we support.

Swift and Node.js

Another challenge was to decide how to implement the user interface. After an extensive discussion, we ended up with Electron as the front-end part of future Spark for Windows. That meant we not only needed to be able to build Spark Core on Windows but also use it as a loadable addon for Node.js.

Node.js addon in pure Swift? That appeared to be surprisingly easy. Due to the crossplatform nature of Node.js, we were able to use macOS as a development platform with Xcode as IDE. Node-gyp, a tool for building native addons, can generate a valid Xcode project with Swift sources. Swift, in its turn, perfectly imports N-API headers.

The tricky part is the entry point. The valid addon should register itself, and the registration code is easier to write in C. While calling C from Swift is not a problem at all, we don’t have an official way to do a reverse thing: Call Swift from C. Well, no worries, no official way doesn’t mean there’s no way at all. We have the @_cdecl attribute! According to this post from Joe Groff, there are some risks of using it, as well as some limitations, but for just one simple call it worked flawlessly.

So we made an addon for Node.js in Swift. As mentioned above, we used node-gyp and Xcode. Obviously, it won’t work on Windows. On the other hand, CMake is the tool of choice for mixed-language cross-platform projects. Though building the sources is not the only thing node-gyp does. It also takes care of providing relevant compiler flags and headers for specific Node.js version. If only there were some tool like node-gyp, but backed by CMake... And there is such a tool! Cmake-js does exactly what we need. If we knew about it from the beginning, we’d implement a macOS version with it as well.

And more…

Unfortunately, this article can’t cover everything we would like to share about Swift on Windows. Mostly focused on what we did, we are planning to take a closer look at how we did that in a separate story.

Wrapping up

Since the first day we started, Swift on Windows did a giant step forward in terms of platform support and stability. If you are thinking about extending your existing application codebase to platforms other than macOS/iOS, you absolutely can do it with Swift now, or at least soon. If you are maintaining a small Swift library, you could easily add Windows support already!

I want to thank the Swift community for all the efforts made for supporting Windows. Special thanks to Saleem Abdulrasool for his invaluable contribution, guidance, and support.

Sources

  1. Getting Started with //swift/build on Windows – https://github.com/compnerd/swift-build/blob/master/docs/GettingStartedWindows.md
  2. //swift/build Pipelines – https://compnerd.visualstudio.com/swift-build/_build
  3. Swift on Windows Snapshots and build Tool – https://github.com/readdle/swift-windows-gha
  4. Swift CMake Examples – https://github.com/compnerd/swift-build-examples
  5. node-gyp – Node.js native addon build tool – https://github.com/nodejs/node-gyp
  6. CMake.js – Node.js native addon build tool – https://github.com/cmake-js/cmake-js

Alexander Smarus
Product Engineering Lead at Spark



Become better at email

The most effective business email tips straight to your inbox every month 🚀