🔍

Xcode 16にビルドインされたswift-formatでビルド時に静的解析する

2024/10/31に公開

はじめに

Xcode 16からswift-formatが同梱されるようになりました。(具体的には、Swift 6のツールチェーンの一部になりました)

本記事では、フォーマットではなく、ビルトインされたswift-formatを使った静的解析する方法を2つ説明します。というのも、ちょっとした落とし穴があったためです。

swift-formatがXcode 16から動かなくなったという方もこちらが参考になるはずです。

フォーマットの実行方法はtreastrainさんの記事が大変参考になりますので、こちらもぜひ参照ください。

https://zenn.dev/treastrain/articles/8f461a75731562

方法1: Run Scriptでswift formatコマンドを実行する

以下の手順でビルド時にLintを実行できるようにします。

  1. Project > Build PhaseでRun Script Phaseを追加します。
    追加したPhaseをCompile Sourcesより前に移動します。

  2. 以下のコマンドを追加します。

swift format lint -r Sources Tests

今回の場合は、SourcesTestsが検査したい対象のフォルダです。これまでは必要であったswift-formatへのパス解決が不要になっている点がポイントです。

  1. Project > Build SettingsでUser Script SandboxingNOに設定します。

ここがちょっとした落とし穴です。YESにしていると全く検査が動作しません。エラーにはなりませんので要注意です。

なお、Xcode 15以降ではプロジェクト作成時にENABLE_USER_SCRIPT_SANDBOXING=YESが設定されているようでした。

ただ、User Script SandboxingYESにしたままでLintを実行する方法はないのでしょうか?それが次の方法になります。

方法2: Xcode Build Tool Pluginを利用する

  1. プロジェクト内にSwift packageを追加します。
mkdir BuildTool
cd BuildTool
swift package init --type build-tool-plugin --name swift-format-plugin
  1. 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 sourceFiles = directoryURL.appending(path: "Sources")
    let testFiles = 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",
          sourceFiles.path(),
          testFiles.path(),
        ],
        inputFiles: [],
        outputFiles: []
      )
    ]
  }
}
#endif
  1. Xcode project内にLocal packageとしてを追加した[1]上で、Project > Build Phases > Run Build Tool Plug-insに、build-toolsを追加します。

これでUser Script Sandboxingが有効であってもLintが動作するようになります!

脚注
  1. Editing a package dependency as a local package https://developer.apple.com/documentation/xcode/editing-a-package-dependency-as-a-local-package ↩︎

Discussion