Updates for Swift in the upcoming CMake 3.29

CMake 3.29.0-rc1 was released on February 13th. This release landed two major developer-experience improvements. Here's what to expect and the changes you'll need to make in order to make use of them.

  • CMake now supports generating compile-commands for Swift. This means that you can use SourceKit-LSP with your CMake projects for improved editor support. For the best SourceKit-LSP experience, add add_compile_options($<$<COMPILE_LANGUAGE:Swift>:-index-store-path ${CMAKE_BINARY_DIR}/IndexStore/index>) flag early in your project to ensure that the Swift compiler is generating index data.

  • Swift targets won't rebuild if changes to the source file don't warrant changes to the output. The driver doesn't update timestamps on outputs if changes to the source don't warrant changing them. Changing a comment in a source won't affect the generated object, so the object file isn't updated. Ninja would see that the timestamp on the source file is newer than the object file output and schedule a rebuild. Ninja is now restat'ing Swift build outputs to track what is actually up-to-date avoiding unecessary rebuilds, so you should see faster rebuild times and more aggressive subtree pruning. Thanks @z2oh!

CMake received a fairly major overhaul in how it models Swift builds in order to support this. For CMake users, the change to be aware of is the introduction of a variable and target property to control the compilation mode. The CMAKE_Swift_COMPILATION_MODE variable sets the default for whether a target is built single-file, incrementally, or with whole-module optimizations enabled. The swift_COMPILATION_MODE target property enables you to control whether specific targets are built with WMO or incrementally. The default behavior is to build incrementally.

Allowed values for the variable and target property are

  • wholemodule
  • incremental
  • singlefile

As this change is not backward compatible with existing projects, CMake policy CMP0157 controls whether CMake uses the new build model or the old. CMP0157 must be set to NEW before Swift is enabled, either with project(${project name} Swift) or with enable_language(Swift) or it will be considered off for the entire project, including nested projects using FetchContent. Ensure that you have audited your project for forced -wmo and -whole-module-optimization flags to Swift targets before setting CMP0157 to NEW or you may find that your build graph is broken.

Here is an example of how to migrate a simple project to the new model.
Wel start with the following project, which manually passes -whole-module-optimization to the executable build target:

cmake_minimum_required(VERSION 3.28)
project(Example Swift)

add_executable(MyExecutable hello.swift foo.swift bar.swift)
target_compile_options(MyExecutable PRIVATE -whole-module-optimization)

Before declaring the project and enabling Swift, we set CMP0157 to NEW. If CMP0157 is not set to NEW, Swift_COMPILATION_MODE and CMAKE_Swift_COMPILATION_MODE will have no effect. Next, we replace the -whole-module-optimization compile option by setting the Swift_COMPILATION_MODE to wholemodule on the executable target. For folks using the Xcode generator with your projects, you will notice that this now works with your Xcode builds.

cmake_minimum_required(VERSION 3.29)
cmake_policy(SET CMP0157 NEW)
project(Example Swift)

add_executable(MyExecutable hello.swift foo.swift bar.swift)
set_target_properties(MyExecutable PROPERTIES Swift_COMPILATION_MODE wholemodule)