Bokeh

Making sense of the mess in my head

Swiftly and SourceKit-LSP issue follow-up

Introduction

A few weeks ago, I wrote a post about Swiftly and mentioned that I experienced issues with code-completion and the likes when doing embedded development in VSCode. I pointed to a SourceKit-LSP error I was seeing but did not take the time to investigate much further.

Last week, Kuba Mracek kindly tagged me in a GitHub issue he opened in the SourceKit-LSP project.
The issue describes a similar problem and Ben Barham provided a workaround for it.

compile_commands.json

Based on the explanation in the aforementioned issue, I double-checked the content of the generated compile_commands.json file.

{
  "directory": "…",
  "command": "/Users/…/.swiftly/bin/swiftc -target armv7em-none-none-eabi …",
  "file": "/Users/…/EmbeddedSwift-nRF52-BLEExamples/L3_E2/Main.swift",
  "output": "CMakeFiles/app_swift.dir/Main.swift.obj"
},

Here we can see that the command refers to swiftc within the .swiftly folder in my home directory.
Looking at that file (swiftc -> /Users/…/.swiftly/bin/swiftly), we can see it’s in fact a symbolic link to the swiftly executable itself.

Pointing to the proper compiler

Based on the comment in the issue, I tried to set the SWIFT_EXEC environment variable appropriately. Working in VSCode and with the nRF Connect SDK, I used a similar technique to what I did before when defining environment variables and added the following snippet to the .vscode/settings.json file within my project

    "nrf-connect.west.env": {        
        "$base": "terminal",
        "SWIFT_EXEC": "$(swiftly use -p)/usr/bin/swiftc"
    },

But this did not change anything.

Looking at Swift - Frequently Asked Questions, I can see SWIFT_EXEC being mentioned, but in the context of SwiftPM or Xcode, not CMake.

Searching the CMake documentation, I instead found that SWIFTC was the environment variable that indicated the Swift compiler to use. And so tried this instead

    "nrf-connect.west.env": {        
        "$base": "terminal",
        "SWIFTC": "$(swiftly use -p)/usr/bin/swiftc"
    },

but this gave an error

  Could not find compiler set in environment variable SWIFTC

  $(swiftly use -p)/usr/bin/swiftc.

It looks like the environment variable does not get evaluated but is used verbatim instead.

I searched the VSCode documentation on User and workspace settings and Variables reference but could not really find a way to make this work.

So I tried hardcoding the path

    "nrf-connect.west.env": {        
        "$base": "terminal",
        "SWIFTC": "/Users/…/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/swiftc"
    },

and this worked fine.

In compile_commands.json the command references the actual swiftc compiler and code-completion now works as expected (if you have a Swift file opened in an editor, close and re-open it for things to work properly)

{
  "directory": "…",
  "command": "/Users/…/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/swiftc -target armv7em-none-none-eabi …",
  "file": "/Users/…/EmbeddedSwift-nRF52-BLEExamples/L3_E2/Main.swift",
  "output": "CMakeFiles/app_swift.dir/Main.swift.obj"
},

This works but we’ve now hardcoded the path to our swiftc compiler. Using the swift-latest.xctoolchain symbolic link makes it slightly better though, as this we won’t need to change the configuration every time we install a new snapshot release and it’s probably the version we want to use anyway.

But can we do slightly better?

Set the compiler from within CMake

Taking another look at what the CMake documentation says about SWIFTC, we read that it’s used to set the value of the CMAKE_Swift_COMPILER variable.

Looking at our existing CMakeLists.txt file, we see that at the top, certain similar variables are already set

# Use the armv7em-none-none-eabi target triple for Swift
set(CMAKE_Swift_COMPILER_TARGET armv7em-none-none-eabi)
# Enable "wmo" as needed by Embedded Swift
set(CMAKE_Swift_COMPILATION_MODE wholemodule)

Let’s try something similar

execute_process(
	COMMAND bash -c "swiftly use -p"
	OUTPUT_VARIABLE toolchainPath
	OUTPUT_STRIP_TRAILING_WHITESPACE
)
set (CMAKE_Swift_COMPILER "${toolchainPath}/usr/bin/swiftc")

This works fine and we can indeed see in compile_commands.json that the actual path to the currently selected toolchain is used

{
  "directory": "…",
  "command": "/Users/…/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/bin/swiftc -target armv7em-none-none-eabi -j 10 …",
  "file": "/Users/…/EmbeddedSwift/Public/EmbeddedSwift-nRF52-BLEExamples/L3_E2/Main.swift",
  "output": "CMakeFiles/app_swift.dir/Main.swift.obj"
},

Info

I first had an issue properly building the path, caused by an extra newline in the output of the command.
Looking at the execute_process — CMake 4.0.1 Documentation pointed me to the OUTPUT_STRIP_TRAILING_WHITESPACE option that solved the issue.
It’s always a good idea to check the doc!

Being generic

The above change in the CMakeLists.txt works great when Swiftly is installed but what happens if it’s not.

My first reaction was to be cautious and test for the presence of Swiftly before executing the code

find_program(SWIFTLY "swiftly")
IF(SWIFTLY)
    execute_process(
        COMMAND bash -c "swiftly use -p"
        OUTPUT_VARIABLE toolchainPath
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    set (CMAKE_Swift_COMPILER "${toolchainPath}/usr/bin/swiftc")
ENDIF()

So in this case, we’re falling back to the old behavior and defining the TOOLCHAIN environment variable to indicate to compiler to use.

While writing this post, as I wanted to explain why I made the above test, I tried the original code without Swiftly installed. And to my surprise, it also worked.

The explanation is that, if Swiftly is not installed, the toolchainPath variable is empty. And CMAKE_Swift_COMPILER resolves to /usr/bin/swiftc, which is executing the proper compiler.

Conclusion

In this post, I explained how to get code-completion working within VSCode when installing the Swift toolchain using Swiftly.

A big thank you to Kuba Mracek and Ben Barham for providing the initial information on the workaround. I’m grateful for the spirit of the Swift community, with so many people helping each other and striving to help grow the platform.

I’m hoping the blog posts I write and the code I open source are a small contribution to this. If you have any topic you’d like me to cover, don’t hesitate to reach out on Mastodon.