I'd like to have my SPM command-line program project be built to run on older versions of macOS.
According to Apple docs, Xcode 14 - 16.2 can build Swift executables that deploy back to macOS 10.13.
If I successfully build my code in Xcode 14.2+ on macOS 12, 13, 14, or 15, will it run on older macOS versions back to 10.13 if my Package.swift contains:
let package = Package(
…,
platforms: [
.macOS(.v10_13)
],
…
To build, I run swift build --configuration release --arch <arch> from my project root folder once for each architecture, where <arch> = arm64 for Apple Silicon & x86_64 for Intel.
Are any other command-line options, Package.swift settings, or anything else necessary for built executables to work on older macOS versions?
If I build a Universal 2 executable using swift build --arch arm64 --arch x86_64, will that run on macOS 10.13+, or are Universal 2 executables only compatible with later versions of macOS?
This should Just Work™. However, if you’re going to support ancient OS releases, you really need to test your product on those releases. I’ve seen plenty of examples of cases where things like this should work and don’t.
My experience is that VMs are a great way to run tests like this.
are Universal 2 executables only compatible with later versions of macOS?
Support for universal binaries goes all the way back to the 68K-to-Intel transition at NeXT. It was also used by Apple for the Intel-to-PowerPC (prior to Mac OS X 10.0), PowerPC-to-Intel, and Intel-to-Apple silicon transitions. So, yeah, that’s not an issue (-:
One oddity is that the build system will clamp the minimum deployment target of your Apple silicon code to macOS 11, because that’s the initial OS release for Apple silicon.
Does that mean that if I build using Xcode 16.x on macOS 15.x, as long as I have .macOS(.v10_13) in my Package.swift, the built executable should work back through macOS 10.13, but I should test it on each supported macOS major version, just to be sure?
One oddity is that the build system will clamp the minimum deployment target of your Apple silicon code to macOS 11, because that’s the initial OS release for Apple silicon.
Do you mean that swift build --arch arm64 … will only verify that a SwiftPM project marked .macOS(.v10_13) will work on macOS 11+, regardless of .macOS(.v10_13) & regardless of whether --arch x86_64 is also passed as an argument? So the executable might work on macOS 10.x, but the build process won't try to ensure it?
Or do you mean that swift build --arch arm64 … explicitly generates an executable that cannot work on macOS 10.x?
the built executable should work back through macOS 10.13
Yes.
I should test it on each supported macOS major version, just to be sure?
Yes.
Way back in the day one of my colleagues wrote Q&A OV01 Test What You Ship, and that advice is as valid today as it ever was.
Do you mean that swift build --arch arm64 … will only verify
that a SwiftPM project marked .macOS(.v10_13) will work on
macOS 11+
No. Rather, each slice of the Mach-O will have a different minimum OS release.
This is actually an interesting edge case in the deployment target story. Consider this package:
% cat Package.swift
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Test76404",
platforms: [.macOS(.v10_13)],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "Test76404"),
]
)
% cat Sources/main.swift
import CoreImage
func main() {
CIPlugIn.loadAllPlugIns()
}
main()
where the loadAllPlugIns() method was deprecated in 10.15. Now build this for both architectures:
% swift build --arch x86_64 --arch arm64
…
%
Note that there is no deprecation warning, even for the Apple silicon architecture. But look at the resulting tool:
@eskimo surely is aware, but it was not only the 68K-to-Intel back at NeXT, we (as a third party) actually shipped our frontend applications Quad-Fat-Binary (m68K, Intel, PA-RISC and SPARC) for some time, so it was not just dual-architectures in use, but even quad. We dropped m68k after a while and just shipped triple-fat binaries though.
It was quite cool in 1995 or so to tick all those architecture boxes in ProjectBuilder and see it churn away.