Running a SwiftPM project program on older versions of macOS

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?

Thanks.

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.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

Thanks for the info.

This should Just Work™.

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?

Or something else?

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:

% vtool -show-build .build/apple/Products/Debug/Test76404      
.build/apple/Products/Debug/Test76404 (architecture x86_64):
Load command 9
      cmd LC_VERSION_MIN_MACOSX
  cmdsize 16
  version 10.13
      sdk 15.2
.build/apple/Products/Debug/Test76404 (architecture arm64):
Load command 10
      cmd LC_BUILD_VERSION
  cmdsize 32
 platform MACOS
    minos 11.0
      sdk 15.2
   ntools 1
     tool LD
  version 1115.7.3

The Intel slice deploys back to 10.13 but the Apple silicon slice is clamped to 11.0.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

@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.

2 Likes