Introducing: An SDK for building iOS/macOS apps on Linux (experimental!)

With support for SE-0387 in the works, some of the most important pieces are finally in place to make it super easy to cross-compile Darwin apps on Linux. To show this off, I've put together a proof of concept SDK for compiling iOS and macOS apps on Ubuntu:

Once you build and install the SDK, using it is as simple as

swift build --experimental-swift-sdk ios16

Scope

Keep in mind that this doesn't aim to augment SwiftPM (specifically swift-build)'s feature set, so the result of swift build will be an executable/.dylib/.a MachO file. If you build an .executableTarget with an @main, this is all you need to get a SwiftUI Mac app going!

The story is a bit trickier on iOS: you need to bundle the executable into a .app, codesign it, and then deploy it. All of this is possible too (you can sign with sdk/toolset/bin/ldid and deploy with ideviceinstaller), but automating it is out of scope for this SDK. I do plan to simplify this in the future, but that's another project for another day.

Finally, there's no support for running iOS/macOS unit tests on Linux since simulating/emulating a Darwin environment is a whole different ballgame. The non-solution solution is to make your core logic platform-agnostic and run unit tests natively on Linux for now.

Caveats

Support for Swift SDKs in SwiftPM is highly experimental atm, so the SDK has a few workarounds for this. macOS works relatively well already, but iOS support is a bit buggier, using unspeakable hacks to get SwiftPM to understand what iOS even is; for example iOS xcframeworks don't work. There are open SwiftPM PRs (some mine, some from others) around these issues so some of them should be resolved soon.

In general, Swift SDK support is incomplete atm so don't count on everything working smoothly until the "experimental" label is dropped. For example, SourceKit LSP doesn't seem to support SDKs yet so if you want to get that working you need yet more hacks (it's possible but shall be left as an exercise for the reader). That it's possible to build a SwiftUI app on Linux despite the WIP nature of this feature is pretty amazing.

Future Directions

As more of the Swift SDK spec is implemented, it should help simplify installation and usage. I'd eventually like to build a tool for one-click building, packaging, signing, and deployment — if you're interested in using/contributing to this, let me know!

Shameless plug: I'm doing a talk on cross-platform Swift at Swift TO next month, where I'll talk about this and more. Check it out if you're in the area!

24 Likes

Amazing work! I was literally just going down this rabbit hole a few days ago.

If we can cross compile for windows too from Linux, we're golden.

One step at a time of course.

3 Likes

It's absolutely amazing news that cross compilation is working! Somehow I hadn't even heard of these efforts until now :sweat_smile:

I think that kind of automation would fit nicely into Swift Bundler! It already does that kind of thing on macOS (for macOS and iOS apps), and most of the code is relatively portable (it can already be compiled on Linux but doesn't cross compile for Darwin yet of course).

Having the ability to cross-compile apps for iOS on Linux would be an absolutely amazing step towards cross-platform Swift development! I hadn't imagined it would arrive so soon :)

4 Likes

I learned about Swift Bundler a few days ago and was considering reaching out! FYI Bundler's manifest parsing seems to be failing on Linux atm but it shouldn't be too hard to fix — I see that you're invoking swiftc -frontend directly; the frontend is considered an implementation detail so really you should be calling into the driver (i.e. swiftc) which should allow you to elide most of the flags you're currently passing, as well as to skip the manual clang linker invocation. With that in place it should definitely be possible to build an iOS app through Bundler on Linux :)

Food for thought: it'd be interesting to shim AppleProductTypes (which drives Swift Playgrounds-based apps) to create a canonical app package format that's compatible with Playgrounds, avoiding the need for a separate bundler toml file. IDE/LSP support would be a bit complicated, though probably achievable by adding -Xbuild-tools-swiftc -I/path/to/custom/lib where lib contains the custom AppleProductTypes.swiftmodule. With this you could declare an app in Package.swift:

let package = Package(
  name: "my-app",
  products: [
    .iOSApplication(
      name: "MyApp",
      targets: ["MyApp"],
      bundleIdentifier: "com.my.app",
      ...
    ),
  ],
  targets: [
    .executableTarget(name: "MyApp", path: "."),
  ]
)

EDIT: something to experiment with:

LIB="$(xcode-select -p)"/../SharedFrameworks/SwiftPM.framework/SharedSupport/ManifestAPI
swiftc -L $LIB -I $LIB -Xlinker -rpath -Xlinker $LIB \
  -package-description-version 5.9.0 \
  Package.swift -o manifest
./manifest -fileno 1
5 Likes

Marvelous work!

Ah whoops :sweat_smile: I just copied what SwiftPM was doing to load the manifest because I was initially just using the SwiftPackageManager package to load the manifest (before running into issues when the package didn't match the user's installed Swift compiler). Looks like that wasn't a very robust approach, I'll look into not relying on the frontend (didn't know that it was considered an implementation detail).

Thanks for the link, I had always wondered how that worked! That could certainly be an interesting experiment. For now it'd probably maximise forward compatibility to continue using the Bundler.toml configuration file, because apple could just change how AppleProductTypes works without notice (and it possibly isn't respected by the Swift compiler for Linux? I haven't checked).

Thanks for the snippet, that looks like it's exactly what I need.

I'm a bit busy with work the next few days, but I'll look into fixing the Linux issue when I get the time.

2 Likes