will and way

ただの自分用メモを人に伝える形式で書くことでわかりやすくまとめてるはずのブログ

FastlaneがSwiftで書けるようになった〜

これはSwiftアドベントカレンダーの17日目の記事です。

Swiftの方はプラットフォームに依存しないエントリーを書くべきかと思いましたが、
FastlaneのSwift対応がタイムリーだったのでこっちにしました。
元はSwiftでTCPソケット通信を書こうと思ってたので、年末にでも。それでは本題へ

今回は下記のプロジェクトを元に紹介していきます。

github.com

Fastfileの設定ファイルやその周辺がSwiftで書けるようになりました

fastlaneがもともとRubyなのは周知の事実ですが、rubyの実装をSwiftから叩く実装が2.69.0から入りました。

Swift対応の実装方針としてはブリッジ、フック、そしてlane定義に分かれています。

ブリッジ: Rubyのコマンドをコールするラッパーを自動生成
フック: ブリッジファイルをコールしたり、エントリーポイントとなるコード lane定義: コマンド(ブリッジ部分)を叩いたり、ビルドの設定や環境変数を定義している箇所

そしてこれらが、xcodeプロジェクトで管理できるようになっています。
今回の一番のポイントは「慣れたエディタで慣れた言語をつかえる」。これだけで効率化のイメージが湧きますよね

早速使ってみる

fastlane init swiftでfastlane関連のSwiftのファイルが生成されます。

f:id:matsuokah:20171217013103p:plain

To edit your new Fastfile.swif type: open ./fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeprojというメッセージが出ています。

生成されたプロジェクトの開いて、グループの構成をみると下記のようになっていて、基本的にはFastfile.swiftだけを編集すればOK

* Autogenerated API => いわゆるRubyのブリッジ部分
* Fastfile Components
* Networking => 文字通り
* RunnerCode => フック部分
* Appfile.swift, Fastfile.swift => 設定、ビルドフローの定義ファイル
* 各コマンド(Gymfile.swifなど)

このプロジェクトをビルドするとMacでexecutableなバイナリが出来上がります。
生成されたバイナリを介してfastlaneの部分が実行されています。また、fastlaneコマンドをつかって起動した場合、バイナリ自体のビルドもよしなに走るようになっています。

Fastfileを編集してビルドする

FastfileクラスはLaneFileクラスを継承しています。LaneFileには各実装が織り込まれているのでビルドフローを詳しく知りたい人は読んでみるといいと思います。

つけるメソッド名はdebugLaneのように接尾辞を付ける必要があります。理由はlaneを接尾辞にもつメソッドをフィルタしてlaneを見つけ出してフックしているためです

class Fastfile: LaneFile {
    var fastlaneVersion: String { return "2.69.3" }        
    func debugLane() {        
        buildApp()
        crashlytics(apiToken: "TOKEN", buildSecret: "SECRET")
    }
}

最低限の実装はこれだけで済むはずです。

ここからさらに ConfigurationやExport Methodなど、enumを定義してそれらを扱うクラスを用意すれば省コード化が可能になります。

enum Configuration: String {
    case debug
    case release

    var exportMethod: ExportMethod {
        switch self {
        case .release:
            return .appStore
        default:
            return .development
        }
    }
}

上記はConfigurationの列挙ですが、プロジェクトによってはstagingや準本番のような環境もあるかと思います。

それらの環境変数を洗い出したあとは変数のマネージャクラスを用意すればOK
すべて記載すると長いので、Protocolだけ記載しておきます

protocol BuildContextProtocol {
    
    // Xcode
    var workspace: String { get }
    var scheme: String { get }
    var configuration: String { get }

    // Build
    var buildDir: String { get }
    var ipaName: String { get }
    var ipaPath: String { get }
    var dsymName: String { get }
    var dsymPath: String { get }
    var exportMethod: String { get }
}

最終的には下記のようなコードに収まります。

    func debugLane() {
        desc("Submit a new Beta Build to Crashlytics")
    let buildContext = BuildContext()
    buildContext.build()
    crashlytics(apiToken: "TOKEN"
        , buildSecret: "SECRET"
        , ipaPath: buildContext.ipaPath
        , groups: Fabric.testerGroup, notifications: true
    )

    }

各環境変数・コンフィグの切り替えを省コード化して書いてみたサンプルをおいときます。

GitHub - matsuokah/fastlane-swift-sample

今回省コード化のために抜き出した設定ファイルです

fastlane-swift-sample/BuildContext.swift at master · matsuokah/fastlane-swift-sample · GitHub

ハマったこと

  • FastlaneRunnerをgit管理下に置こうとして失敗する
    • gitignoreに追加しました。また、バイナリなので12MBほどの大きさです。毎度FastlaneのSwift部も勝手にビルドが行われて、バイナリが再度生成されるので追跡しなくていいと思います。
  • ルビーでは配列で扱っている部分もすべてStringになっている
    • Crashlyticsのgroupsとかがそうなのですが、rubyだと["tester1", "tester2"]のように配列を指定できますが、SwiftではインターフェースにはString採用されているので使い方に工夫が必要になりそうです。
  • 現状、メソッドにパラメータを渡していない
    • 試してはいないのですが、フック部では_ = fastfileInstance.perform(NSSelectorFromString(laneMethod))と書かれていることから、引数が使えないのでは?と妄想しています。

まとめ

ということで、まだまだexperimantalで機能的にまだ満たされていない部分もありますがそこはPRポイントですね!
若干のワークアラウンドが発生しますが、FastfileがSwiftで定義でできるようになったことでグルーコードが非常に書きやすくなったかと思います!
個人的にはRubyが書けるならRubyでいいなと思います。あくまでRuby主導なリポジトリですので、Rubyで直接実行できるにこしたことはないです。

GitHub - matsuokah/fastlane-swift-sample