DRYな備忘録

Don't Repeat Yourself.

【iOS】ビルドバージョンの自動インクリメントのSwift実装(PlistBuddyからの脱却)

背景

Xcodeのプロジェクトをやっていて、CI/CDなどを整えていると、iOSプロジェクトのビルドバージョン( 1.0 (N) のNの部分)を自動でインクリメントとかしたくなることがある。

PlistBuddyを使え

macOSだとPlistBuddyというコマンドが/usr/libexec/PlistBuddyにあるので、それを使えばよい。

% /usr/libexec/PlistBuddy
Usage: PlistBuddy [-cxh] <file.plist>
    -c "<command>" execute command, otherwise run in interactive mode
    -x output will be in the form of an xml plist where appropriate
    -h print the complete help info, with command guide

%
% /usr/libexec/PlistBuddy -c "Set :CFBundleVersion 2" ./BuildVersionExample/Info.plist
%

Info.plist に以下のような変化がある。

f:id:otiai10:20200312204540p:plain

PlistBuddyはなにをやってるの

このコマンドによって生成されたdiffがこちら

f:id:otiai10:20200312204632p:plain

Info.plistというXMLなファイルのプロパティがちょっと変わってるだけですね。

ゴール

CI/CDマシンはmacOSである前提はありつつも、ローカルマシンの実行ファイルに依存するのはなんだか気に食わないので、ひとまずこの限られた機能だけSwiftで実装できないかな、というのがゴール。せっかくなので、CocoaPodsに公開とかもしてみたいです。

技術概観

以下の技術課題に分解されるかと思う。

  1. マシンにビルドインなswiftないしswiftcを使ってCLIツールは作れるか?
  2. Swiftコードから、XML、とくにplistのread/writeは可能か?
  3. インクリメント(できるだろナメてんのか)
  4. CocoaPodsへの公開は(カジュアルに)できるのか?
  5. 検証: pod install で自作のpodを落としてきて使えるか

1. Swiftを使った簡単なCLIの実装

参考にしたもの

どうやら --type=executable というのを使えば1発っぽいな

% swift --version
Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
Target: x86_64-apple-darwin18.7.0
% mkdir -p ~/proj/swift/SwiftExampleCommand
% cd ~/proj/swift/SwiftExampleCommand
% git init
% swift package init --type=executable
Creating executable package: SwiftExampleCommand
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/SwiftExampleCommand/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/SwiftExampleCommandTests/
Creating Tests/SwiftExampleCommandTests/SwiftExampleCommandTests.swift
Creating Tests/SwiftExampleCommandTests/XCTestManifests.swift

f:id:otiai10:20200312211830p:plain

ほーう。main.swiftや、Testsも自動で生成されているのがわかる。

とりあえず

% swift build
[3/3] Linking SwiftExampleCommand

とすると、.buildディレクトリが生成されており、

f:id:otiai10:20200312212331p:plain

うーん、かんたん。いちおうmain.swiftを覗いて、ちょっと細工をし、いちいちbuildすんのめんどいなということで適当にrunとか試してみると

f:id:otiai10:20200312212849p:plain

うーん、かわいいやんけ。

2. SwiftコードからXMLのread/write

参考にしたもの

FoundationからPropertyListSerializationというクラスが利用できるが、とりあえずXMLParserで具合を確認してみる。

github.com

どうやらXMLParserっていうのはXML<>といったトークンをパースして、各要素をwalkthroughするprotocolを提供しているようで、delegateXMLDocumentなどを使って、最終的にxmlStringなどを取得してfileに書き込めばいいと思う。

が、めんどくさいことこの上ないので、PropertyListSerializationを使ってみます。

f:id:otiai10:20200313023139p:plain

github.com

ばっちり動いたやんけ。コードもすっきり。

その他の参考:

3. インクリメント

しらんけど、上記でNSMutableObjectで取ってるのでValueにAnyが来ており、これをIntとしてインクリして返すのがよいかと思われる。

参考にしたもの

こんな感じで

plist[propKey] = {(currentVersion: Any?) -> Int in
    if let v: Int = currentVersion as? Int {
        return v + 1
    }
    return 1
}(plist[propKey])

github.com

4. CocoaPodsへの公開

参考にしたもの

その他、実装上参考にしたもの

成果物

github.com

ほんで、

% pod trunk register otiai10@gmail.com "Hiromu Ochiai" --description='my macbook pro'
% pod trunk push MiniBuddy.podspec

f:id:otiai10:20200314034524p:plain

かんたんだった

pod installで自作のpodを落としてきて使う

こういうPodfile書いてpod installしたらちゃんと落ちてきた

target 'MiniBuddyExample' do
  use_frameworks!
  pod 'MiniBuddy'
end

だけど、以下の問題がある

  • 実行ファイルがビルドされてtrunkにpushされるかと思ったがそうではなく、ばっちりソースしか落ちてきてない
    • [追記] これは、spec.source_filesに配布したいものだけを含めることで治った
  • ちゃんとLICENSEファイル配置してるんだけど[!] Unable to read the license file LICENSE for the spec MiniBuddy (0.1.0)のエラーが出る

雑感

  • エジプトも地球でした

DRYな備忘録として