【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
に以下のような変化がある。
PlistBuddyはなにをやってるの
このコマンドによって生成されたdiffがこちら
Info.plist
というXMLなファイルのプロパティがちょっと変わってるだけですね。
ゴール
CI/CDマシンはmacOSである前提はありつつも、ローカルマシンの実行ファイルに依存するのはなんだか気に食わないので、ひとまずこの限られた機能だけSwiftで実装できないかな、というのがゴール。せっかくなので、CocoaPodsに公開とかもしてみたいです。
技術概観
以下の技術課題に分解されるかと思う。
- マシンにビルドインな
swift
ないしswiftc
を使ってCLIツールは作れるか? - Swiftコードから、XML、とくにplistのread/writeは可能か?
- インクリメント(できるだろナメてんのか)
- CocoaPodsへの公開は(カジュアルに)できるのか?
- 検証:
pod install
で自作のpodを落としてきて使えるか
1. Swiftを使った簡単なCLIの実装
参考にしたもの
- https://developer.apple.com/documentation/xcode/creating_a_standalone_swift_package_with_xcode
- Lets build a Command line app in Swift - Quick Code - Medium
- An Introduction to Swift Package Manager | raywenderlich.com
どうやら --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
ほーう。main.swift
や、Testsも自動で生成されているのがわかる。
とりあえず
% swift build [3/3] Linking SwiftExampleCommand
とすると、.build
ディレクトリが生成されており、
うーん、かんたん。いちおうmain.swift
を覗いて、ちょっと細工をし、いちいちbuildすんのめんどいなということで適当にrun
とか試してみると
うーん、かわいいやんけ。
2. SwiftコードからXMLのread/write
参考にしたもの
- https://developer.apple.com/documentation/foundation/xmlparser
- https://developer.apple.com/documentation/foundation/propertylistserialization
- Simple Xml Parser in Swift Language for Mac OSX – Debugging Everything
- Parse XML iOS Tutorial - iOScreator
- A Beginner’s Guide to XML Parsing in Swift
- How to parse XML in Swift 3 - Complex XML Construction - Using Swift - Swift Forums
- GitHub - drmohundro/SWXMLHash: Simple XML parsing in Swift
- swiftxml/swiftxml.swift at master · kelvin13/swiftxml · GitHub
- How To: Working With Plist In Swift – LearnAppMaking
Foundation
からPropertyListSerialization
というクラスが利用できるが、とりあえずXMLParser
で具合を確認してみる。
どうやらXMLParser
っていうのはXMLの<
や>
といったトークンをパースして、各要素をwalkthroughするprotocolを提供しているようで、delegateにXMLDocument
などを使って、最終的にxmlString
などを取得してfileに書き込めばいいと思う。
が、めんどくさいことこの上ないので、PropertyListSerialization
を使ってみます。
ばっちり動いたやんけ。コードもすっきり。
その他の参考:
- xcode - PlistBuddy or code to display version information iOS - Stack Overflow
- How to loop over arrays – Hacking with Swift
- Get class name of object as string in Swift - Stack Overflow
- ios - Find an item and change value in custom object array - Swift - Stack Overflow
- ios - Swift Cannot assign through subscript: subscript is get only - Stack Overflow
- objective c - What is an NSCFDictionary? - Stack Overflow
- swift - Read and write a String from text file - Stack Overflow
3. インクリメント
しらんけど、上記でNSMutableObjectで取ってるのでValueにAnyが来ており、これをIntとしてインクリして返すのがよいかと思われる。
参考にしたもの
- How to convert a String to an Int - free Swift 5.1 example code and tips
- How to convert Any to Int in Swift - Stack Overflow
- ios - Converting String to Int with Swift - Stack Overflow
- ios - How to convert Any to Int in swift? - Stack Overflow
- Convert String To Int (And Back) – LearnAppMaking
- ios - Protocol type cannot conform to protocol because only concrete types can conform to protocols - Stack Overflow
- https://docs.swift.org/swift-book/LanguageGuide/Closures.html
- Swift From Scratch: Variables and Constants
こんな感じで
plist[propKey] = {(currentVersion: Any?) -> Int in if let v: Int = currentVersion as? Int { return v + 1 } return 1 }(plist[propKey])
4. CocoaPodsへの公開
参考にしたもの
- CocoaPods.org
- CocoaPods Guides - Using Pod Lib Create
- CocoaPods Guides - Getting Started
- CocoaPods Guides - Making a CocoaPod
- CocoaPods Guides - Podspec Syntax Reference
- CocoaPods Guides - Home
- CocoaPods Guides - Getting setup with Trunk
- CocoaPodsで自作ライブラリの公開 | Yuuki Nishiyama
その他、実装上参考にしたもの
- Multi-Line Comments in Ruby? - Stack Overflow
- GitHub - Carthage/Commandant: Type-safe command line argument handling
- How to add local Swift Package as dependency? - Using Swift - Swift Forums
- How to use Result in Swift – Hacking with Swift
- Switching in Swift
- https://developer.apple.com/documentation/swift/keyvaluepairs
- What are KeyValuePairs? - free Swift 5.1 example code and tips
- Get Array Size in Swift - count function - Examples
- https://developer.apple.com/documentation/swift/array
- [Swift] 三項演算子のハマり所 - Qiita
- swift - Check for nil with guard instead of if? - Stack Overflow
- The power of Result types in Swift | Swift by Sundell
- ios - Generate your own Error code in swift 3 - Stack Overflow
- https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html
- Conditionals In Swift With If, Else If, Else – LearnAppMaking
- Error Handling in Swift 3: Try, Try?, and Try! - Joyce Matos - Medium
- How to check object is nil or not in swift? - Stack Overflow
- swift - Check if key exists in dictionary of type [Type:Type?] - Stack Overflow
成果物
ほんで、
% pod trunk register otiai10@gmail.com "Hiromu Ochiai" --description='my macbook pro' % pod trunk push MiniBuddy.podspec
かんたんだった
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 specMiniBuddy (0.1.0)
のエラーが出る
雑感
- エジプトも地球でした
DRYな備忘録として