Using built-in swift-format for linting in Xcode 16 build phases

Using built-in `swift-format` for linting in Xcode 16 build phases

swift-format has been included in the Swift Toolechain since Swift 6. It allows us to run it with swift format command and eliminates the need to build or install swift-format binary or manage different versions for each Swift release.1

Thanks to this integration, we can now use swift-format in Xcode 16 as well. As shown in the cover image, you can format a file by selecting Editor > Structure > Format File with “swift-format” from the Xcode menu bar.

However, my goal during development is not just formatting, but linting the code during the build process.

In this post, I will explain how to run linting with swift format and how to resolve a specific issue you might encounter.

How-to 1: Running the swift format in a Run Script

Follow these steps to enable linting during th build process:

  1. Select your project file, go to “Build Phases” and add a “Run Script” Phase. Move the newly added phase before “Compile Sources”.

  2. Add the following command. In this case, Sources and Tests are the folders you want to lint.

Terminal window
swift format lint -r Sources Tests

That’s it! Simple, right?

The key point here is that you no longer need to manage things like the path to swift-format, which was previously required.

However…there is one pitfall with this method!

The problem is that, in some cases, this setup doesn’t work at all. Worse yet, no errors occur during the linting phase, even though it works perfectly fine in Terminal.

After some investigation, I figured out that the issue is with the “User Script Sandboxing” setting in Build Settings of my project.

By setting this to NO, the script perfectly works.

Setting User Script Sandboxing to NO

Since Xcode 15, the build setting, ENABLE_USER_SCRIPT_SANDBOXING=YES, is set by default when creating projects. In Xcode 14 and before, this setting was not set, meaning it was implicitly set to NO as far as I confirmed.

But is it OK to leave it set to NO? Is there a way to run linting while it’s still set to YES?

Of course, there is. Adding a simple Xcode build tool plugin lets you achieve this.

Next, I’ll describe how to do that.

How-to 2: Using an Xcode Build Tool Plugin

  1. Add a Swift package to your project:
Terminal window
mkdir BuildTool
cd BuildTool
swift package init --type build-tool-plugin --name swift-format-plugin
  1. Modify swift_format_plugin.swift as follows:
BuildTools/Plugins/swift-format-plugin.swift
import PackagePlugin
@main
struct swift_format_plugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
return []
}
}
#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin
extension swift_format_plugin: XcodeBuildToolPlugin {
func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
let directoryURL = context.xcodeProject.directoryURL;
let configFile = directoryURL.appending(path: ".swift-format")
let sourcesDir = directoryURL.appending(path: "Sources")
let testsDir = directoryURL.appending(path: "Tests")
return [
.buildCommand(
displayName: "Run swift format(xcode)",
executable: try context.tool(named: "swift").url,
arguments: [
"format",
"lint",
"--configuration",
configFile.path(),
"-r",
sourcesDir.path(),
testsDir.path(),
],
inputFiles: [],
outputFiles: []
)
]
}
}
#endif
  1. Add this as a Local package in your Xcode project2, then go to Project > Build Phases > Run Build Tool Plug-ins and add swift-format-plugin target.

Adding swift_format_plugin

Now, linting will work even with User Script Sandboxing enabled!

Wrap up

Which method you choose depends on your project. In some cases, adding a plugin might feel excessive. On the other hand, you may want to keep sandboxing enabled due to security concerns.

Oh, you’re using SwiftFormat/SwiftLint? Well, to each their own.

I hope this article helps with your app development.

Footnotes

  1. swift-format: Included in the Swift Toolchain https://github.com/swiftlang/swift-format?tab=readme-ov-file#included-in-the-swift-toolchain

  2. Editing a package dependency as a local package https://developer.apple.com/documentation/xcode/editing-a-package-dependency-as-a-local-package