Swiftly and Swift 6.1 released
Introduction
End of March was a busy time for Apple, with the release of swiftly 1.0 and a few days later Swift 6.1.
Let’s explore what this means for toolchain selection, mostly when doing Embedded Swift development on macOS.
Xcode toolchain
Xcode 16.3 comes bundled with Swift 6.1. If you don’t install anything else, open a terminal and check the current version of swift
swift -print-target-info
{
"compilerVersion": "Apple Swift version 6.1 (swiftlang-6.1.0.110.21 clang-1700.0.13.3)",
"target": {
"triple": "arm64-apple-macosx15.0",
"unversionedTriple": "arm64-apple-macosx",
"moduleTriple": "arm64-apple-macos",
"compatibilityLibraries": [ ],
"librariesRequireRPath": false
},
"paths": {
"runtimeLibraryPaths": [
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx",
"/usr/lib/swift"
],
"runtimeLibraryImportPaths": [
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx"
],
"runtimeResourcePath": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift"
}
}
You see a final 6.1 release
Info
Running swift --version
or swift -v --version
gives us some information but the above command also gives information about the runtime path.
Creating a main.swift
file with this one-liner
print("Hello World!")
we can compile it using
swift main.swift
this generates an executable main
file.
Let’s try running
swiftc -enable-experimental-feature Embedded main.swift
that gives an error
error: Whole module optimization (wmo) must be enabled with embedded Swift.
This indicates that the compiler recognizes Embedded mode.
But running
swiftc -enable-experimental-feature Embedded -wmo main.swift
gives
<unknown>:0: error: module 'Swift' cannot be imported in embedded Swift mode
We could maybe try to specify a target ? Let’s check what targets are available.
clang -print-targets
Registered Targets:
aarch64 - AArch64 (little endian)
aarch64_32 - AArch64 (little endian ILP32)
aarch64_be - AArch64 (big endian)
arm - ARM
arm64 - ARM64 (little endian)
arm64_32 - ARM64 (little endian ILP32)
armeb - ARM (big endian)
thumb - Thumb
thumbeb - Thumb (big endian)
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
Clearly not all that’s required for embedded, but let’s try one
swiftc -enable-experimental-feature Embedded -wmo -target thumb-none-none-eabi main.swift
error: unableToFind(tool: "swift-autolink-extract")
And what about just creating an object file, no linking
swiftc -enable-experimental-feature Embedded -wmo -c -target thumb-none-none-eabi main.swift
<unknown>:0: error: unable to load standard library for target 'thumb-none-none-eabi'
Enough for the default Swift installation, let’s check with another one.
Manually downloading a toolchain
Since starting with Embedded Swift, what I’ve done so far is go to https://www.swift.org/install/macos/ and download the latest development snapshot. This would give me a .pkg
file. Installing it would add a toolchain under /Library/Developer/Toolchains
.
I then extract the toolchain identifier using
plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist
org.swift.62202504031a
And references the toolchain I want to use with
export TOOLCHAINS='org.swift.62202504031a'
Info
This only works if you have Xcode installed, see https://forums.swift.org/t/embedded-swift-on-esp32-with-idf-error-on-build/72230/8 for more details.
Info
When using nRF Connect SDK from within VSCode, I added this variable to the West environment, as explained in nrfx-blink Step by Step.
With this in place, let’s check the Swift version we now have
swift -print-target-info
{
"compilerVersion": "Apple Swift version 6.2-dev (LLVM 3f3fde0d5f85709, Swift 0c5fd6a3017961d)",
"target": {
"triple": "arm64-apple-macosx15.0",
"unversionedTriple": "arm64-apple-macosx",
"moduleTriple": "arm64-apple-macos",
"platform": "macosx",
"arch": "arm64",
"pointerWidthInBits": 64,
"pointerWidthInBytes": 8,
"swiftRuntimeCompatibilityVersion": "6.0",
"compatibilityLibraries": [ ],
"librariesRequireRPath": false
},
"paths": {
"runtimeLibraryPaths": [
"/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift/macosx",
"/usr/lib/swift"
],
"runtimeLibraryImportPaths": [
"/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift/macosx"
],
"runtimeResourcePath": "/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift"
}
}
Running
swiftc -enable-experimental-feature Embedded -wmo main.swift
now works and generates an executable main
file.
We also have many more targets
clang -print-targets
Registered Targets:
aarch64 - AArch64 (little endian)
aarch64_32 - AArch64 (little endian ILP32)
aarch64_be - AArch64 (big endian)
arm - ARM
arm64 - ARM64 (little endian)
arm64_32 - ARM64 (little endian ILP32)
armeb - ARM (big endian)
avr - Atmel AVR Microcontroller
mips - MIPS (32-bit big endian)
mips64 - MIPS (64-bit big endian)
mips64el - MIPS (64-bit little endian)
mipsel - MIPS (32-bit little endian)
ppc32 - PowerPC 32
ppc32le - PowerPC 32 LE
ppc64 - PowerPC 64
ppc64le - PowerPC 64 LE
riscv32 - 32-bit RISC-V
riscv64 - 64-bit RISC-V
systemz - SystemZ
thumb - Thumb
thumbeb - Thumb (big endian)
wasm32 - WebAssembly 32-bit
wasm64 - WebAssembly 64-bit
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
So let’s try building for one of them.
swiftc -enable-experimental-feature Embedded -wmo -c -target riscv32-none-none-eabi main.swift
properly generates a main.o object file.
Enter swiftly
Installing swiftly is straightforward, go to Swift.org - Getting Started with Swiftly on macOS and follow the instructions.
As noted in the documentation, running swiftly init
automatically downloads the latest swift toolchain.
Let’s check the installed version.
swift -print-target-info
{
"compilerVersion": "Apple Swift version 6.1 (swift-6.1-RELEASE)",
"target": {
"triple": "arm64-apple-macosx15.0",
"unversionedTriple": "arm64-apple-macosx",
"moduleTriple": "arm64-apple-macos",
"compatibilityLibraries": [ ],
"librariesRequireRPath": false
},
"paths": {
"runtimeLibraryPaths": [
"/Users/…/Library/Developer/Toolchains/swift-6.1-RELEASE.xctoolchain/usr/lib/swift/macosx",
"/usr/lib/swift"
],
"runtimeLibraryImportPaths": [
"/Users/…/Library/Developer/Toolchains/swift-6.1-RELEASE.xctoolchain/usr/lib/swift/macosx"
],
"runtimeResourcePath": "/Users/…/Library/Developer/Toolchains/swift-6.1-RELEASE.xctoolchain/usr/lib/swift"
}
}
The version reported is very similar to the one displayed for the default Xcode version, except it doesn’t show a specific swiftlang and clang version.
Let’s try to compile in Embedded mode
swiftc -enable-experimental-feature Embedded -wmo main.swift
error: link command failed with exit code 1 (use -v to see invocation)
ld: library 'System' not found
clang: error: linker command failed with exit code 1 (use -v to see invocation)
This also results in an error, but only during the linking phase.
What if we just generate an object file?
swiftc -enable-experimental-feature Embedded -wmo -c main.swift
properly generates a main.o object file.
And what about other targets?
clang -print-targets
Registered Targets:
aarch64 - AArch64 (little endian)
aarch64_32 - AArch64 (little endian ILP32)
aarch64_be - AArch64 (big endian)
arm - ARM
arm64 - ARM64 (little endian)
arm64_32 - ARM64 (little endian ILP32)
armeb - ARM (big endian)
avr - Atmel AVR Microcontroller
mips - MIPS (32-bit big endian)
mips64 - MIPS (64-bit big endian)
mips64el - MIPS (64-bit little endian)
mipsel - MIPS (32-bit little endian)
ppc32 - PowerPC 32
ppc32le - PowerPC 32 LE
ppc64 - PowerPC 64
ppc64le - PowerPC 64 LE
riscv32 - 32-bit RISC-V
riscv64 - 64-bit RISC-V
systemz - SystemZ
thumb - Thumb
thumbeb - Thumb (big endian)
wasm32 - WebAssembly 32-bit
wasm64 - WebAssembly 64-bit
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
swiftc -enable-experimental-feature Embedded -wmo -c -target riscv32-none-none-eabi main.swift
also properly generates a main.o object file.
By the way, the clang version is quite similar to the one reported by the default Xcode toolchain
clang --version
Apple clang version 17.0.0 (https://github.com/swiftlang/llvm-project.git 901f89886dcd5d1eaf07c8504d58c90f37b0cfdf)
Target: arm64-apple-darwin24.4.0
Thread model: posix
InstalledDir: /Users/…/Library/Developer/Toolchains/swift-6.1-RELEASE.xctoolchain/usr/bin
Build config: +assertions
Using snapshot versions
Swiftly allows us to download additional toolchains, including snapshot ones. Let’s see what’s available
swiftly list-available main-snapshot
Available main development snapshot toolchains
----------------------------------------------
main-snapshot-2025-04-03
main-snapshot-2025-04-02
main-snapshot-2025-03-28
main-snapshot-2025-03-25
…
This lists a lot of versions, going way back to 2021.
Let’s install and use that latest one available at the time of writing (which is the same version as the one I manually downloaded above).
swiftly install main-snapshot-2025-04-03
swiftly use main-snapshot-2025-04-03
And check the version
swift -print-target-info
{
"compilerVersion": "Apple Swift version 6.2-dev (LLVM 3f3fde0d5f85709, Swift 0c5fd6a3017961d)",
"target": {
"triple": "arm64-apple-macosx15.0",
"unversionedTriple": "arm64-apple-macosx",
"moduleTriple": "arm64-apple-macos",
"platform": "macosx",
"arch": "arm64",
"pointerWidthInBits": 64,
"pointerWidthInBytes": 8,
"swiftRuntimeCompatibilityVersion": "6.0",
"compatibilityLibraries": [ ],
"librariesRequireRPath": false
},
"paths": {
"runtimeLibraryPaths": [
"/Users/…/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift/macosx",
"/usr/lib/swift"
],
"runtimeLibraryImportPaths": [
"/Users/…/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift/macosx"
],
"runtimeResourcePath": "/Users/…/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift"
}
}
Just like with the main version, building in Embedded mode fails
swiftc -enable-experimental-feature Embedded -wmo main.swift
error: link command failed with exit code 1 (use -v to see invocation)
ld: library 'System' not found
clang: error: linker command failed with exit code 1 (use -v to see invocation)
What? This is really strange, it worked when we installed this toolchain manually and referenced it with the TOOLCHAIN
variable. So why does it fail here?
Are the toolchains different? Let’s check with
diff -r /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain ~/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain
No difference, they’re exactly the same.
I have so far no explanation for the different behaviors.
Let’s move on, building just an object file
swiftc -enable-experimental-feature Embedded -wmo -c main.swift
including for another target
swiftc -enable-experimental-feature Embedded -wmo -c -target riscv32-none-none-eabi main.swift
works fine, similar to the main toolchain.
By the way, if you try to build for a non-existent target, it provides you with a list of the supported ones.
swiftc -enable-experimental-feature Embedded -wmo -target m68k-none-none-eabi main.swift
<unknown>:0: error: could not find module 'Swift' for target 'm68k-none-none-eabi'; found: riscv32-none-none-eabi, arm64e-apple-none-macho, armv4t-none-none-eabi, riscv64-none-none-eabi, i686-unknown-none-elf, armv7-apple-none-macho, armv6m-none-none-eabi, aarch64-none-none-elf, wasm64-unknown-none-wasm, arm64-apple-macos, x86_64-unknown-none-elf, arm64-apple-none-macho, armv7-none-none-eabi, armv7m-apple-none-macho, arm64e-apple-ios, armv7em-none-none-eabi, arm64e-apple-macos, x86_64-apple-macos, avr-none-none-elf, armv6-apple-none-macho, armv6m-apple-none-macho, wasm32-unknown-none-wasm, arm64-apple-ios, armv7em-apple-none-macho, armv6-none-none-eabi, at: /Users/…/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift/embedded/Swift.swiftmodule
Toolchain priorities
With the snapshot version installed via swiftly, I tried to perform other tests with the toolchain I manually downloaded and so set the TOOLCHAIN
variable.
export TOOLCHAINS='org.swift.62202504031a'
However, printing the version information shows I’m still using the toolchain installed via swiftly.
swift -print-target-info
{
"compilerVersion": "Apple Swift version 6.2-dev (LLVM 3f3fde0d5f85709, Swift 0c5fd6a3017961d)",
"target": {
"triple": "arm64-apple-macosx15.0",
"unversionedTriple": "arm64-apple-macosx",
"moduleTriple": "arm64-apple-macos",
"platform": "macosx",
"arch": "arm64",
"pointerWidthInBits": 64,
"pointerWidthInBytes": 8,
"swiftRuntimeCompatibilityVersion": "6.0",
"compatibilityLibraries": [ ],
"librariesRequireRPath": false
},
"paths": {
"runtimeLibraryPaths": [
"/Users/…/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift/macosx",
"/usr/lib/swift"
],
"runtimeLibraryImportPaths": [
"/Users/…/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift/macosx"
],
"runtimeResourcePath": "/Users/…/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/lib/swift"
}
}
As we’ve seen above, the toolchain installed manually and via swiftly are identical. But they reside at different locations on the filesystem.
Since the one installed by swiftly resides in my user account, and the manually installed one is in a system-wide location, the swiftly version takes precedence.
Logging in as another user that does not use swiftly, setting the TOOLCHAINS
variable and checking the version correctly indicates that I’m using the manually installed version.
Impact for Embedded Swift
As shown in nrfx-blink Step by Step, I used to have the following snippet in my settings.json
file for Visual Studio Code.
"nrf-connect.west.env": {
"$base": "terminal",
"TOOLCHAINS": "org.swift.61202408261a"
}
This ensured the proper toolchain would be used when building Embedded Swift projects based on nRF Connect SDK.
This is not required anymore, the toolchain selected by swiftly is properly used by West.
But code-completion and jump-to-definition features, although working for C code (e.g. from BridgingHeader.h
), are broken for Swift files.
Looking at the SourceKit-LSP logs (under Output tab, SourceKit Language Server), we see this
[Error - 17:27:48] Request textDocument/codeAction failed.
Message: No language service for 'file:///Users/…/Development/Nelcea/EmbeddedSwift/Public/EmbeddedSwift-nRF52-Examples/Timer/Main.swift' found
Code: -32001
I have not found a way to make it work so far, except mostly going back to the old way of working.
I say mostly because I still used swiftly to install the snapshot toolchain, but I commented out the call to ~/.swiftly/env.sh
from my .zprofile
file and re-added the TOOLCHAINS
definition in settings.json
.
Conclusion
In this post, I’ve shown how I used to manually select a toolchain for my builds and how swiftly changes that.
I’ve also shown how different toolchains behave when working in Embedded mode.
However, some behaviors are not always what I expected, and I don’t know exactly what some error messages imply. If you have more information on what’s happening here, don’t hesitate to reach out on Mastodon.
I did not really talk about Swift 6.1 in this post. I will simple mention that it introduces package traits in packages definitions. This is indicated as a useful option for Embedded Swift but given all environments I use are CMake based, I haven’t looked into that yet.