『入る学科間違えた高専生』の日記

プログラミングのコードを書いたりする予定です。あとは日記等。あといつまで高専生やねん

SwiftGen を Swift Package Manager で導入する

はじめに

今回は, SwiftGen を Swift Package Manager (以下 SwiftPM ) を使って導入する方法です.

SwiftGen を使用することはたびたびあるかと思います.
今までは私は R.swift でしたが最近は差分ビルドのこととかを考えてこっちに移行しました.

github.com

github.com

また,

xcassets:
  inputs:
    - App/Assets.xcassets
  outputs:
    templateName: swift5
    output: Generated/Assets.swift

のような swiftgen.yml を作るかと思います.
SwiftPM で導入した場合のエラーの解決も紹介します.

あと, SwiftGen の Package.swiftswift-tools-version: 5.3 になったら Swift Package Manager ( PackageDescription ) の Targetresources: [Resource]? が追加されるので簡単に対応できるようになるのではないかと思います.

環境

SwiftGen を CLI として使用する

Mint

先に書くと,当環境では Mint で SwiftGen をビルドすることはできません!

$ mint install SwiftGen/SwiftGen@6.4.0 --verbose

ビルドログ (クリックして開きます)

🌱 Cloning SwiftGen 6.4.0
Cloning into 'github.com_SwiftGen_SwiftGen'...
remote: Enumerating objects: 1059, done.
remote: Counting objects: 100% (1059/1059), done.
remote: Compressing objects: 100% (835/835), done.
remote: Total 1059 (delta 318), reused 486 (delta 164), pack-reused 0
Receiving objects: 100% (1059/1059), 11.15 MiB | 3.26 MiB/s, done.
Resolving deltas: 100% (318/318), done.
Note: switching to '0c67b63f43814a8d7eb71f685f0bf504b03223f3'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

🌱 Resolving package
/private/var/folders/fq/nvfqtqj11nl06cfd24z_yd240000gn/T/mint/github.com_SwiftGen_SwiftGen: error: manifest parse error(s):
<unknown>:0: error: invalid Darwin version number: macos11.3
<unknown>:0: error: invalid version number in '--target=x86_64-apple-macos11.3'
<unknown>:0: error: failed to load module 'Combine'
<unknown>:0: error: invalid Darwin version number: macos11.3
<unknown>:0: error: invalid version number in '--target=x86_64-apple-macos11.3'
/private/var/folders/fq/nvfqtqj11nl06cfd24z_yd240000gn/T/mint/github.com_SwiftGen_SwiftGen/Package.swift:2:8: error: failed to load module 'PackageDescription'
import PackageDescription
       ^
🌱 Encountered error during "swift package resolve"
🌱  Failed to resolve SwiftGen 6.4.0 with SPM

ここらへんなんでビルドできないかは詳しい人に任せた!

Package.swift

となると Package.swift でやるっていうことになりますね.
Xcode のプロジェクトに追加できる SwiftPM は CLI をビルドしていい感じしてくれる仕組みはありませんから.

じゃあとりあえず書いてみるか.

SwiftGen のみを CLI ツールとして管理している場合

シンプルに Package.swift

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "Tools",
    platforms: [
        .macOS(.v11)
    ],
    dependencies: [
        .package(
            url: "https://github.com/SwiftGen/SwiftGen.git",
            .upToNextMajor(from: "6.4.0")
        )
    ],
    targets: [
        .target(name: "Tools", path: "")
    ]
)

のようにしてあげれば上手くいくはず!

$ swift build -c release --product swiftgen

としてあげてビルドしてあげれば ./.build/release/swiftgen にバイナリがビルドされます.

他の CLI ツールも一緒に使う場合

ちなみにここでは XcodeGen を一緒に使っていると想定しますね.

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "Tools",
    platforms: [
        .macOS(.v11)
    ],
    dependencies: [
        .package(
            url: "https://github.com/yonaskolb/XcodeGen.git",
            .upToNextMajor(from: "2.24.0")
        ),
        .package(
            url: "https://github.com/SwiftGen/SwiftGen.git",
            .upToNextMajor(from: "6.4.0")
        )
    ],
    targets: [
        .target(name: "Tools", path: "")
    ]
)

こんな感じを想定します.

Fetching https://github.com/SwiftGen/SwiftGen.git from cache
Fetching https://github.com/yonaskolb/XcodeGen.git from cache
Fetching https://github.com/tid-kijyun/Kanna.git from cache
Fetching https://github.com/jpsim/Yams.git from cache
Fetching https://github.com/SwiftGen/StencilSwiftKit.git from cache
Fetching https://github.com/kylef/Stencil.git from cache
Fetching https://github.com/kylef/PathKit.git from cache
Fetching https://github.com/kylef/Commander.git from cache
error: Dependencies could not be resolved because root depends on 'XcodeGen' 2.24.0..<3.0.0 and root depends on 'SwiftGen' 6.4.0..<7.0.0.
'SwiftGen' is incompatible with 'XcodeGen' because 'SwiftGen' depends on 'PathKit' 0.9.0..<1.0.0 and 'XcodeGen' >= 2.6.0 depends on 'PathKit' 1.0.0..<2.0.0.
'Tools' /path/to/App: warning: found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /path/to/App/App/Info.plist

'Tools' /path/to/App: error: manifest property 'defaultLocalization' not set; it is required in the presence of localized resources

依存関係の解決に失敗します.

はい.ということでどうしようかなって考えてたら,
SwiftFormat がいい感じな解説 *1 を README.md に書いてくれていました.

./Tools/ToolsSwiftGen/Package.swift を作成して以下のようにしました.

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "ToolsSwiftGen",
    platforms: [
        .macOS(.v11)
    ],
    dependencies: [
        .package(
            url: "https://github.com/SwiftGen/SwiftGen.git",
            .upToNextMajor(from: "6.4.0")
        )
    ],
    targets: [
        .target(name: "ToolsSwiftGen", path: "")
    ]
)

パッケージ名とターゲット名を一緒にしておくと良いそうです!

App.xcodeproj 等がある場所で

Fetching https://github.com/tid-kijyun/Kanna.git from cache
Fetching https://github.com/jpsim/Yams.git from cache
Fetching https://github.com/kylef/Stencil.git from cache
Fetching https://github.com/SwiftGen/StencilSwiftKit.git from cache
Fetching https://github.com/kylef/Spectre.git from cache
Fetching https://github.com/SwiftGen/SwiftGen.git from cache
Fetching https://github.com/kylef/Commander.git from cache
Fetching https://github.com/kylef/PathKit.git from cache
Cloning https://github.com/kylef/Stencil.git
Resolving https://github.com/kylef/Stencil.git at 0.13.1
Cloning https://github.com/SwiftGen/SwiftGen.git
Resolving https://github.com/SwiftGen/SwiftGen.git at 6.4.0
Cloning https://github.com/SwiftGen/StencilSwiftKit.git
Resolving https://github.com/SwiftGen/StencilSwiftKit.git at 2.7.2
Cloning https://github.com/kylef/PathKit.git
Resolving https://github.com/kylef/PathKit.git at 0.9.2
Cloning https://github.com/tid-kijyun/Kanna.git
Resolving https://github.com/tid-kijyun/Kanna.git at 5.2.7
Cloning https://github.com/kylef/Spectre.git
Resolving https://github.com/kylef/Spectre.git at 0.9.2
Cloning https://github.com/jpsim/Yams.git
Resolving https://github.com/jpsim/Yams.git at 4.0.6
Cloning https://github.com/kylef/Commander.git
Resolving https://github.com/kylef/Commander.git at 0.9.1
'ToolsSwiftGen' /path/to/App/Tools/ToolsSwiftGen: warning: Source files for target ToolsSwiftGen should be located under /path/to/App/Tools/ToolsSwiftGen
/path/to/App/Tools/ToolsSwiftGen/.build/checkouts/PathKit/Sources/PathKit.swift:98:14: warning: 'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'Path' to 'Hashable' by implementing 'hash(into:)' instead
  public var hashValue: Int {
             ^
/path/to/App/Tools/ToolsSwiftGen/.build/checkouts/Stencil/Sources/Node.swift:65:9: warning: variable 'components' was never mutated; consider changing to 'let' constant
    var components = token.components()
    ~~~ ^
    let
/path/to/App/Tools/ToolsSwiftGen/.build/checkouts/Stencil/Sources/Template.swift:11:3: warning: 'internal(set)' modifier is redundant for an internal property
  internal(set) var environment: Environment
  ^~~~~~~~~~~~~~

/path/to/App/Tools/ToolsSwiftGen/.build/checkouts/StencilSwiftKit/Sources/StencilSwiftKit/Environment.swift:10:3: warning: 'public' modifier is redundant for instance method declared in a public extension
  public func registerStencilSwiftExtensions() {
  ^~~~~~~

[15/15] Build complete!

そうするとこちらは, ./Tools/ToolsSwiftGen/.build/release/swiftgen にバイナリがビルドされます!
これでようやくいい感じになりました.

実際に swiftgen を実行する

$ ./Tools/ToolsSwiftGen/.build/release/swiftgen
Template named swift5 not found. Use `swiftgen template list` to list available named templates or use `templatePath` to specify a template by its full path.

テンプレートが見つかりません!!
ここでようやく本題になりました.

テンプレートエラーの解決

SwiftGenCLI としてビルドするときに流れるログとか SwiftGen のバイナリ自体を使用するときなどに .build が少なからず自動で作られます.
そこにリポジトリの checkouts などが保存されることを知っているのでそこに指定を当ててあげれば解決できます.

結論は templatePath を指定することでこれが解決できます.

swiftgen.yml を以下のように outputs を変更します.

xcassets:
  inputs:
    - App/Assets.xcassets
  outputs:
    - templatePath: Tools/ToolsSwiftGen/.build/checkouts/SwiftGen/templates/xcassets/swift5.stencil
      output: Generated/Assets.swift
$ ./Tools/ToolsSwiftGen/.build/release/swiftgen
File written: Generated/Assets.swift

f:id:nanashinodonbee:20210910214749p:plain

CocoaPods

一応...

前述した通り, CocoaPods で使用する場合は本当に何も考える必要がありませんね.(要出典)
詳しくは知らないけどうまくいくっていう事実はある.

pod 'SwiftGen'

最後に

だらだら長くなりましたが,今回は SwiftGen を Swift Package Manager で導入したときにテンプレートのエラーが発生することに対するテンプレートの指定方法のご紹介でした.

結論は前章で書いたとおり, templateName ではなく templatePath を使って swift5.stencil相対パス指定するということでした.

CocoaPods をまだ剥がしきれない場合は無理して SwiftPM に移行は別にする必要はないかと思いますが, SwiftGen のために SwiftPM 移行を頑張ってるのに上手く行かないから諦めるみたいな方を救えたらと思います(私がそう)

参考記事

qiita.com