Use SPM to build iOS target

It's quite different, because swift build only builds standalone command-line executables for macOS. iOS (as well as macOS, for non-command-line apps) requires handling assets/resources (invoking tools like actool and ibtool), compiling localized strings files, configuring Info.plist files, and then copying them into the appropriate .app bundle structure, in addition to signing that you already metioned. Then, if they wanted to support other targets than just basic iOS apps (like app extensions, or framework bundles), then they'd have to handle the differences between those kinds of bundles as well.

Could all of those features be added to SwiftPM? Technically, sure. But it's probably out of scope for the Swift project, since none of those features above are strictly about Swift, and it would require the SPM team to constantly chase new OS product types and duplicate work already done in Xcode. If you want to see a bit of how complex reinventing this can be, a good place to look would be the Bazel rules for bundling Apple apps.

It wouldn't surprise me at some point to see some kind of support for included assets/runtime data files as part of a Swift package, but that's a lot less complexity than building full apps.

I haven't watched the sessions from this week yet, but now that packages are supported for iOS in Xcode, is there a way to use swift build or xcodebuild without an xcodeproj to build or test your library with the iOS configuration?

the point is to build a library. SPM in Xcode can't do the app by itself too.

2 Likes

I'd imagine all that would be possible should this SPM proposal advance further: Extensible Build Tools

Latching onto the “library” keyword, and assuming a transient Xcode project file is allowed, that has already been possible for quite some time:

swift package generate-xcodeproj
xcodebuild build -sdk iphoneos -scheme 'MyPackage-Package'
xcodebuild test -destination 'name=iPhone 8' -scheme 'MyPackage-Package'

(but replace “MyPackage” with the actual package name declared in the manifest.)

1 Like

xcodebuild doesn't work without an .xcodeproj.

xcodebuild can build Swift Package without .xcodeproj with passing product name to -scheme option and also recognizes -destination option.
e.g.

6 Likes

That's Xcode-11, and according to the WWDC videos, if the .xcodeproj file is re-generated, xcodebuild will re-generate it from the Package.swift manifest. So, xcodebuild still needs a .xcodeproj to build a target or targets. If you are not using Swift packages (for example, an all Objective-C/C++) application, not very helpful.

xcodebuild archive on a Swift package's "scheme" doesn't produce the same result than running xcodebuild archive on an Xcode project... to be specific, they both create a .framework but the Swift package doesn't export headers.

Just in case anyone is still interested, it is possible to build SwiftPM libraries and executables for iOS (yes, even executables).

swift build --arch arm64 -Xswiftc -sdk -Xswiftc /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.4.sdk -Xswiftc -target -Xswiftc arm64-apple-ios14.0

It does have some quirks, for example it outputs the built products to the .build/arm64-apple-macosx directory (meaning that swift package clean is required when switching from building for macOS to building for iOS and vice versa. But the built executable does successfully run on iOS which is all that really matters.

I am using this capability in swift-bundler, my tool for creating macOS apps without Xcode (and soon iOS apps too). The ios branch of swift-bundler can successfully build, bundle, sign and run an executable Swift package product as an iOS app on a physical iPhone.

5 Likes

Is it possible to configure this in the Package.swift file somehow? Does this also work on a simulator by selecting a scheme and a device and running from inside Xcode?

This is a big step forward since the original post.

Currently Xcode has no support for running Swift packages as proper apps (it just compiles the code into an executable file and runs that, which works semi fine on macos, but doesn’t work at all on iOS due to it’s restricted nature). And I haven’t tried to use this technique to build for the simulator yet (the target triple and sdk would probably both need changing).

I’ll post back here once Swift Bundler can run swift package executable products as apps on physical ios devices and simulators, and can generate xcode schemes for doing so through xcode :)

2 Likes

Oh wow, that would have been super handy to know when we built the build system for the Swift Package Index. Right now, we’re having an awkward bifurcation of using SPM and xcodebuild depending on the platform.

It’s probably not worth switching now that we have a working system but perhaps it’d be a way to handle legacy build once SPM gains proper support for iOS builds etc. Thanks for sharing this!

Do you have any idea how reliable it is?

That's probably good :) Doing this is not a supported way to target iOS which may lead to various features not working as expected, e.g. binary dependencies or plugins.

I guess that answers my question :smile:

(Although it’d have been only for the purpose of building, not running, but I can see how unreliable a signal that would likely be, across all packages.)

So far I haven't run into any issues. I've even managed to successfully compile Delta Client (a Minecraft client written in Swift) and run it on iOS with this method (i did have to add -Xcc -isysroot -Xcc /path/to/iossdk -Xcc --target=arm64-apple-ios15.4 to the swift build flags because of dependencies containing c code). Delta Client relies on numerous packages and all of them compile without issues. The main dependencies are swift-log, swift-argument-parser, swift-parsing, swift-nio, ZippyJSON, ZipFoundation, IDZSwiftCommonCrypto and FirebladeECS.

However, I have not tried any executable targets with binary dependencies or plugins, if you have an example iOS project somewhere that uses either of those features let me know. If it's an xcodeproj, that's fine because Swift Bundler can convert Xcodeprojs to Swift Bundler projects automatically (Swift Bundler projects are swift packages with a configuration file containing app bundle creation related configuration).

Ok, I've found an unavoidable issue with this approach now. Dependencies that have a platform condition are always evaluated using the platform being compiled on (I checked the SwiftPM source code and as far as a I can see there is no option to change this).

For Swift Bundler I will be switching to integrating with the swift-package-manager library so that I can have more control and ensure that everything correctly targets iOS (like Xcode would).

This is a significant bug that plagues other platforms such as Android and WASI. Can you point me to where you found the cause so I can fix it.

Now that I look a bit closer, I can only see it being an issue for Darwin platforms (the part of the code that I'm looking at should correctly identify that Android is being built for). The bug is in BuildParameters.swift. If the destination triple is Darwin, that code assumes that it's macOS. But that assumption only affects Package manifest evaluation as far as I can see, because elsewhere the full triple is used.

It seems to be scattered all over the place, there's another place in BuildPlan line 106 where it assumes Darwin means macOS. There are probably more places too.