見出し画像

Swift MacroをMintでキャッシュ Now in REALITY Tech #105

先日、オフィスのキッチンでホットケーキを焼いて定例をしたiOSチームのあおやまです。 

REALITYのiOSアプリではSwift Macroを導入した際に、ビルド時間が伸びる問題が発生しました。
そこで、Swift Packge製ツールの管理ツールのMintでキャッシュし、ビルド時間を改善しました。


Swift Macro導入の背景

Xcode 15からデバッグコンソールが強化され、Loggerを使ったログのファイル、行、モジュールなどが表示されるようになりした。
REALITYではログ関数を展開して、Loggerでコンソールに出力するコードとFirebaseへ送信するコードに展開するSwift Macroを作成しました。

Swift Macroとビルド時間

Swift Macroのおかげでログを簡潔に記述できるようになりましたが、ビルド時間が伸びる問題が発生しました。
Build Timelineを見ると、クリーンビルド時にSwift Syntaxのビルドが約20秒かかっています。

この問題に頭を悩ませていたところ、こちらの記事を見つけ、Swift Macroを定義したexecutableTargetをプレビルドすることで、ビルド時間を削減できることを知り、REALITYでもチャレンジしました。

Swift MacroをexecutableTargetでプレビルド

executableTargetの作成は、先ほどのブログを大変参考にさせて頂きました。


このnoteではREALITYに導入する上で工夫した2点をメインにご紹介します。

Swift MacroをMintでキャッシュ

REALITYのiOSアプリでは、CocoaPodsを卒業し、ツールはMint、ライブラリはSwift Package Managerで管理しています。

Mintで管理するためにSwift MacroをexecutableTargetとして公開します。

Mintはlocal packageに対応していないので、swift-macro-pluginのリポジトリを新しく作成し、executableTargetのLoggerMacroPluginをターゲットに追加します。

// Package.swift
let package = Package(
    name: "swift-macro-plugin",
    products: [
        .executable(
            name: "LoggerMacroPlugin",
            targets: ["LoggerMacroPlugin"]
        )
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),
    ],
    targets: [
        .executableTarget(
            name: "LoggerMacroPlugin",
            dependencies: [
                .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
                .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
            ]
        )
    ]
)

LoggerMacroPluginにCompilerPluginを継承したLoggerMacroPluginを作成し、DebugLoggerMacroなどを公開します。

// LoggerMacroPlugin.swift
@main
struct LoggerMacroPlugin: CompilerPlugin {
    let providingMacros: [Macro.Type] = [
        DebugLoggerMacro.self, // ...
    ]
}
struct DebugLoggerMacro: ExpressionMacro {
    static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) -> ExprSyntax {
        // ...
    }
}

swift-macro-pluginのリポジトリをMintfileに追加します。
(内製ツールのrelease versionを切る運用をしていないので、コミットハッシュを指定しています。)

// Mintfile
yonaskolb/xcodegen@2.39.1
...
git@github.com:reality-inc/swift-macro-plugin.git@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Mintを利用してビルドします。
このビルドしたLoggerMacroPluginを後述するOTHER_SWIFT_FLAGSの設定で取り込みます。

$ mint bootstrap

XcodeGenの設定

REALITYのiOSアプリではXcodeGenでxcprojectを作成しています。
また、機能ごとにモジュールを分割しています。
-load-plugin-executableの設定は、マクロを展開する全てのモジュールで設定される必要があるようです。
ログのSwift Macroはその特性上、どのモジュールからも呼び出される可能性があります。
そこで、projectのOTHER_SWIFT_FLAGSに-load-plugin-executableを設定しました。

# project.yml
settings:
  OTHER_SWIFT_FLAGS: -load-plugin-executable $(SRCROOT)/.mint/bin/LoggerMacroPlugin#LoggerMacroPlugin

これで、RealityLoggerモジュールに定義されたmacro定義が解決されるようになりました。

public macro logDebug(
    _ message: String,
    category: LogCategory = .default
) -> Void = #externalMacro(module: "LoggerMacroPlugin", type: "DebugLoggerMacro")

結果

これらの対応の結果、Build Timelineを見ると、Swift Syntaxのビルドが無くなり、ビルド時間が短縮されました。

まとめ

REALITYのiOSアプリではSwift Macroを導入した際に、ビルド時間が伸びる問題が発生しました。
そこで、Swift MacroのexecutableTargetを作成し、Swift Package製ツールの管理ツールのMintでプレビルドしました。
その対応の結果、Swift Syntaxのビルドが無くなり、ビルド時間が短縮されました。