XcodeGen 導入教學與心得
事情是這樣開始的
因為公司的商業模式要大改,所以決定從 backend 到 frontend 都重寫。由於是重新開始,所有會寫 iOS 的同事都一起來開發,幾乎每次的 merge 都會加一堆新檔案,常常造成 project file conflict 。由於 project file 是 xml 格式的檔案,在 conflict 的時候內容非常難看,在解的時候就會比較辛苦,大家每天在解 conflict 就花一堆時間。
這時候找到的解法是 XcodeGen,在 sourcde code 裡面不存在 project file,XcodeGen 會依照設定檔(project.yml)跟目前的資料夾架構,去產生新的 project file,既然 project file 不存在,那就沒有衝突的問題,大家在開發的時候就可以把時間放在功能本身。
專案的需求如下:
- 使用 CocoaPods 管理第三方套件,同時也有手動安裝的 framework
- 切分四種 api 環境,每種環境再分成 develop 跟 release,共需要 8 個 configuration,每個 configuration 內容有些不同。
- 有 4 個 target,分別是 app 本身,一個 extension,兩個 test target
- 在 CI/CD 出版的時候使用 fastlane
看完 xocdegen 的文件,發現這些需求都可以滿足,甚至有些一開始沒想到的,看 XcodeGen 的文件才知道可以這樣拆分。我是覺得這個專案的複雜度蠻夠的,這個專案都可以,更簡單的專案一定沒問題。
那就開始囉~~
首先要安裝 XcodeGen,我是用 homebrew
brew install xcodegen
好了以後要依照目前的專案架構設定對應的設定檔,這是導入過程中最難的一步。但是也有好處,在寫的時候會重新學習 project file 裡面的欄位參數是在幹嘛,比較辛苦但是也比較扎實。對一個 iOS 開發為專業的工程師來說,這是本來就該知道但是很多人都靠 IDE 閃避掉的內容。
了解 project.yml
project.yml 是 XcodeGen 的設定檔,產生 project file 的來源,首先要學會怎麼寫。或者是說怎麼抄。
Spec
https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md
別人的寫法,可以抄很重要
https://github.com/yonaskolb/XcodeGen/blob/master/Docs/Examples.md
推薦看這兩個
https://github.com/minvws/nl-covid19-notification-app-ios/blob/master/project.yml
https://github.com/atelier-socle/AppRepositoryTemplate/blob/master/xcodegen/project_ios.yml
看完這個 spec 你應該也知道要怎麼設檔案架構。或者可以看本文最後面,我有放 project.yml。
首先遇到的問題是專案的 configuration 太多,都寫在 project.yml 的話會太大,所以作法是把這些原本的設定都寫在 xcconfig 裡面,project.yml 只需要指定每個 configuration 要使用哪個 xcconfig 就好。
要改的東西太多了,能不能 export?
目標
原本的 project -> 產生一堆 xcconfig -> 用 xcodegen 做出新的 project
什麼是 xcconfig
https://nshipster.com/xcconfig/
每一個參數的解釋,因為這些參數實在是太多太複雜了,Matt 做了一個網站幫助解釋每個欄位。
https://xcodebuildsettings.com/
產生 xcconfig,原本找到的作法
https://stackoverflow.com/questions/11164876/is-there-a-way-export-xcode-build-settings-to-xcconfig-file
xcodebuild -scheme "schemeName" -configuration "Debug" -showBuildSettings >> mynew.xcconfig
但是否來發現有更簡單的方式。謝謝 James Dempsey,推薦用這個。
https://github.com/dempseyatgithub/BuildSettingExtractor
產出的 xcconfig 還有大量註解,使用上非常簡單,把 project file 拖進去就好了,會產生對應的 xcconfig。
離題一下
James Dempsey 是前 Apple 工程師,WWDC 傳奇人物,在古老的 session 最後面試會開放 QA 的,他在講 QA 環節都不開放 QA 而是唱一首歌,他有組一個樂團叫 breakpoints。歌在 Apple music 跟 Spotify 上面都找得到,歌名都是從工程師生活常見的名詞。
以下是他之前的表演影片,這首歌叫 Model View Controller,場地是 WWDC sesssion 後錄的,從畫質上看得出來年代久遠。
最新的影片,去年 2020 WWDC 改成 online,breakpoints 不會讓我們失望,還是每年推出新單曲,新的表演,看了才知道 breakpoints 樂團經過這些人的演變,人數直逼 AKB48 。
整合 CocoaPods
讓我們回到 XcodeGen,這個時候 project file 已經完成,檔案都加進來了,不同 scheme 也拆好了,接下來整合 cocoapods 就結束了吧。把 pod install 加到 post command 裡面吧。
pod install 以後出現 warning,實際上是 error,會 build 不過
[!] CocoaPods did not set the base configuration of your project because your project already has a custom config set. In order for CocoaPods integration to work at all, please either set the base configurations of the target `mazu` to `Target Support Files/Pods-mazu/Pods-mazu.prod.xcconfig` or include the `Target Support Files/Pods-mazu/Pods-mazu.prod.xcconfig` in your build configuration (`configs/mazu-PROD.xcconfig`).
解法:
在每個 xcconfig 裡面 include cocoapod 產生出來的 xcconfig。原本 cocoapod 的做法是直接做出一個新的 xcconfig 取代,但是這樣會跟 cocoapod 整合太深,而且沒有任何註解。現在這個方式未來需要把 cocoapod 移掉的時候只需要把這行移掉就好。
#include "../Pods/Target Support Files/Pods-mazu/Pods-mazu.prod.xcconfig"
到此為止,我們已經成功導入 XcodeGen,做出一個可以 build/archive 的 project file 了。接下來來講一些小問題。
實作時遇到的問題:
1. md files 消失
習慣會把 md 加到 project file 裡面,解釋這個 module 是在幹嘛的,或者是寫一些 sample code,原本如下:
- 加到 target 裡面,會 compile error,因為把 md 檔也拿去 compile
- exclude 裡面寫 **/*.md,就全部 md file 都消失了
解法
在 filegroup 裡面加進來整個 mazu folder,但是在 target 裡面設定 exclude
2. 騙人的 xcconfig
- BuildSettingExtractor export 出的 xcconfig file 很多
- 光看到產出數量就想放棄了
解法
雖然 generate 跑出 4*9 個 xcconfig,但是有用到的只有 project 跟 主 app target 的而已,仔細看 unit test 跟 UITest 的所有 xcconfig 內容都是一樣的,直覺就是讓所有 configuration 都使用同一個 xcconfig file,但是跑完 cocoapod 以後還是有 error,就乾脆都不寫 configFiles。想不到就過了。這樣一來 unit test 跟 UITest 的 xcconfig 都沒用到,可以全刪了。
之後要解決的問題:
- 新的 extension target
- build phase 的 shell script,有示範 R.swift 跟 swiftlint
- 3rd party framework 或者 carthage 或者 SPM,目前只用 cocoapods
[最重要] 要改 yaml 還是 xcconfig?
因為產生 project file 的時候,設定可以寫在 project.yml 或者 xcconfig,以下是在設定時的建議。
- 如果是整個 project 都會影響到的: 改 yaml,像是增加 configuration 名字,最低支援版本
- 如果是整個 target 都會影響的,改 yaml target 裡面,像是用了什麼 SPM,build phase 要執行什麼 shell script
- 如果是 info plist 裡面可以控制的,可以直接改 info plist,或者改 yaml,可以參考這份
- 如果是每個 config 有不一樣的設定,像是 optimal level,改 xcconfig ,像是使用不同 code sign
最後來上一個 sample,這是目前專案的 project.yml ,可以看出除了之前提到的需求外,還整合了 R.swift 跟 Swiftlint
後記
這篇文章其實是我在 2月到 Cocoaheads Taipei 分享內容,當時沒有錄影,而且當時剛導入 XcodeGen,還不確定效果怎樣,經過 3 個月同事們每天努力噴發一堆 code 發一推 merge request,沒有聽到有任何 project conflict 的怨言,全部都是檔案本身的 conflict,讓大家把時間都花在寫 code 本身,當時導入的目標的確有達成。可喜可賀。