iOS 工程自動化 - 思路整理

4 月份參加 2017@Swift 大會的時候有幸聽到了 @zesming 大佬關於美團組件化的 Topic,有一張圖印象特別深入。html

來自 @zesming 大佬
來自 @zesming 大佬

後來跟 @zesming 大佬溝通怎麼去整理組件自動構建發佈思路的時候他也跟我提到了這張圖。因此我準備圍繞這張圖來整理一下 iOS 工程自動化的思路。ios

基礎知識

首先,咱們須要掌握一些自動構建發佈的基礎知識,主要包含以下幾個方面。git

GitFlow - 規範 git 操做流程

GitFlow 是由 Vincent Driessen 提出的一個 Git 操做流程標準。包含以下幾個關鍵分支:github

名稱 說明
master 主分支
develop 主開發分支,包含肯定即將發佈的代碼
feature 新功能分支,通常一個新功能對應一個分支,對於功能的拆分須要比較合理,以免一些後面沒必要要的代碼衝突
release 發佈分支,發佈時候用的分支,通常測試時候發現的 bug 在這個分支進行修復
hotfix 熱修復分支,緊急修 bug 的時候用

GitFlow 的優點有以下幾點:數據庫

  • 並行開發:GitFlow 能夠很方便的實現並行開發:每一個新功能都會創建一個新的 feature 分支,從而和已經完成的功能隔離開來,並且只有在新功能完成開發的狀況下,其對應的 feature 分支纔會合併到主開發分支上(也就是咱們常常說的 develop 分支)。另外,若是你正在開發某個功能,同時又有一個新的功能須要開發,你只須要提交當前 feature 的代碼,而後建立另一個 feature 分支並完成新功能開發。而後再切回以前的 feature 分支便可繼續完成以前功能的開發。
  • 協做開發:GitFlow 還支持多人協同開發,由於每一個 feature 分支上改動的代碼都只是爲了讓某個新的 feature 能夠獨立運行。同時咱們也很容易知道每一個人都在幹啥。
  • 發佈階段:當一個新 feature 開發完成的時候,它會被合併到 develop 分支,這個分支主要用來暫時保存那些尚未發佈的內容,因此若是須要再開發新的 feature,咱們只須要從 develop 分支建立新分支,便可包含全部已經完成的 feature
  • 支持緊急修復:GitFlow 還包含了 hotfix 分支。這種類型的分支是從某個已經發布的 tag 上建立出來並作一個緊急的修復,並且這個緊急修復隻影響這個已經發布的 tag,而不會影響到你正在開發的新 feature

Glow flow 是如何工做的

新功能都是在 feature 分支上進行開發swift

feature 分支都是從 develop 分支建立,完成後再合併到 develop 分支上,等待發布。xcode

當須要發佈時,咱們從 develop 分支建立一個 release 分支bash

而後這個 release 分支會發布到測試環境進行測試,若是發現問題就在這個分支直接進行修復。在全部問題修復以前,咱們會不停的重複發佈->測試->修復->從新發布->從新測試這個流程。服務器

發佈結束後,這個 release 分支會合併到 developmaster 分支,從而保證不會有代碼丟失。併發

master 分支只跟蹤已經發布的代碼,合併到 master 上的 commit 只能來自 release 分支和 hotfix 分支。

hotfix 分支的做用是緊急修復一些 Bug。

它們都是從 master 分支上的某個 tag 創建,修復結束後再合併到 developmaster 分支上。

GitFlow 工具

若是要在項目中引入 GitFlow,推薦使用 SourceTree 來作客戶端工具,它包含了全部 GitFlow 的流程,可視化操做,很方便。

gitignore - 幹掉那些干擾代碼

爲何提到 gitignore,審過 PR(Pull request) 或者 MR(Merge request)的同窗應該深有體會,當一個帶着一大堆 Pods 庫代碼的 PR/MR 襲來的時候,你的心裏應該是絕望的……因此咱們要了解下怎麼把一些非模塊相關的代碼 ignore 掉。

建立項目倉庫中的 .gitignore

若是你在項目倉庫內建立一個 .gitignore 文件,Git 會根據這個文件來決定哪些文件和目錄是須要忽略的。

注意:若是你想把一個已經被跟蹤的文件 ignore 掉,這是時候新增的規則並不會對這個文件產生做用,你須要先用下面的指令把這個文件設置爲不跟蹤:

git rm --cached FILENAME複製代碼

建立全局的 .gitignore

這個其實沒啥意思,不過仍是看一下怎麼設置吧:

git config --global core.excludesfile ~/.gitignore_global複製代碼

iOSer 須要的 .gitignore

Github 官方模版給出的建議以下:

# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output

# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/複製代碼

經過這個文件咱們能夠看到,忽略的內容主要包含下面幾種:

  • 構建產生的目錄和文件;
  • 各類臨時配置文件;
  • Obj-C / Swift 相關的特定文件;
  • CocoaPods 第三方 Pods 庫目錄;
  • Carthage 構建目錄;
  • fastlane 構建產生的文件;
  • 代碼注入工具產生的目錄及文件。

咱們能夠根據自身項目的狀況來對這些配置進行相應的調整和定製。

githooks - 背後的「男人」

注:本段主要翻譯自 githooks 官方文檔。

githooks 簡介

githooks 是指 git 在執行某些特定操做時會觸發的一系列程序,有點相似數據庫中的觸發器,由用戶編寫。默認狀況下,這些程序都被放置在 $GIT_DIR/hooks 目錄下,固然,咱們也能夠經過 git 的環境配置變量 core.hooksPath 來改變這個目錄。

githooks 的類型有不少中,這些 hook 會在各個特定操做時幫你自動完成一些你想要作的操做,好比:在提交代碼到 develop 分支的時候自動打包。

目前支持的 githooks

git commit 流程

pre-commit

git commit 觸發,能夠經過 --no-verify 選項來跳過,這個 hook 不須要參數,在獲得提交消息並開始提交(commit)前被調用,若是返回非 0,則會致使 git commit 失敗。

當默認的 pre-commit 鉤子被啓用時,若是它發現文件尾部有空白行,這次提交就會被終止。

若是進行 git commit 的命令沒有指定一個編輯器來修改提交信息(commit message),任何的 git commit hook 被調用時都會帶上環境變量 GIT_EDITOR=:

prepare-commit-msg

執行 git commit 命令後,在默認提交消息準備好後但編輯器啓動前,這個 hook 就會被調用。

它接受 1 到 3 個參數。第 1 個是包含了提交消息的文件的名字。第 2 個是提交消息的來源,它能夠是:

  • message(若是指定了 -m 或者 -F 選項)
  • template(若是指定了 -t 選項,或者在 git config中開啓了 commit.template 選項)
  • merge(若是本次提交是一次合併,或者存在文件 .git/MERGE_MSG
  • squash(若是存在文件 .git/SQUASH_MSG
  • commit 而且第 3 個參數是一個提交的 SHA1 值(若是指定了 -c,-C 或者 --amend 選項)

若是返回值不是 0,那麼 git commit 命令就會被停止。

這個 hook 的目的是用來在工做時編輯信息文件,而且不會被 --no-verify 選項跳過。一個非 0 值意味着 hook 工做失敗,會終止提交。它不該該用來做爲 pre-commit 鉤子的替代。

commit-msg

git commit 觸發,能夠經過 --no-verify 選項來跳過,接受 1 個參數,這個參數包含了提交消息的文件的名字,若是返回非 0,則會停止 git commit 命令。

這個 hook 能夠用來規範提交信息,好比把信息格式化成項目定製的標準格式,或者發現提交信息不符合格式時拒絕此次提交。

post-commit

git commit 觸發,在提交後被調用,不能影響 git commit 的結果。

git push 流程

pre-push

git push 運行期間,更新了遠程引用但還沒有傳送對象時執行。若是返回非 0,將停止推送過程。它接受遠程分支的名字和位置做爲參數,同時從標準輸入中讀取一系列待更新的引用。 你能夠在推送開始以前,用它驗證對引用的更新操做。

pre-receive

服務端處理來自客戶端的推送操做時,最早被調用的 hook。它從標準輸入獲取一系列被推送的引用。若是它以非 0 值退出,全部的推送內容都不會被接受。你能夠用這個 hook 阻止對 non-fast-forward 的更新,或者對該推送所修改的全部引用和文件進行訪問控制。

update

pre-receive hook 十分相似,不一樣之處在於它會爲每個準備更新的分支各運行一次。假如推送者同時向多個分支推送內容,pre-receive 只運行一次,相比之下 update 則會爲每個被推送的分支各運行一次。它不會從標準輸入讀取內容,而是接受三個參數:引用的分支名;推送前引用所指向內容的 SHA-1 值;以及用戶準備推送內容的 SHA-1 值。若是這個 hook 以非 0 值退出,只有相應的那一個引用會被拒絕;其他的依然會被更新。

post-receive

在整個過程完結之後運行,能夠用來更新其餘系統服務或者通知用戶。它接受與 pre-receive 相同的標準輸入數據。它的用途包括給某個郵件列表發信,通知持續集成服務器,或者更新缺陷追蹤系統 —— 甚至能夠經過分析提交信息來決定某個問題是否應該被開啓、修改或者關閉。該腳本沒法停止 push 進程,不過客戶端在它結束運行以前將保持鏈接狀態,因此若是你想作其餘操做需謹慎使用它,由於它將耗費你很長的一段時間。

post-update

由遠程倉庫的 git-receive-pack 調用,也是就是本地倉庫完成 git push 時。這個指令只能用來作通知,不能改變 git-receive-pack 的結果。

push-to-checkout

由遠程倉庫的 git-receive-pack 調用,也是就是本地倉庫完成 git push 時。若是這個 hook 返回非 0 值,則會中斷 git push 操做。

applypatch-msg & pre-applypatch & post-applypatch

git am 觸發,主要用於引入第三方 patch 的時候用。由於項目中暫時沒有這樣的使用場景,因此不作具體研究。

pre-rebase

git rebase 觸發,能夠用來防止某個分支被 rebase。這個 hook 接受 1 到 2 個參數。第 1 個參數是上游分支,第 2 個參數是將要執行 rebase 的分支。

post-checkout

git checkout 成功運行後執行。你能夠根據你的項目環境用它調整你的工做目錄。包括放入大的二進制文件、自動生成文檔或進行其餘相似這樣的操做。

post-merge

git merge 成功運行後執行。你能夠用它恢復 Git 沒法跟蹤的工做區數據,好比權限數據。這個 hook 也能夠用來驗證某些在 Git 控制以外的文件是否存在,這樣你就能在工做區改變時,把這些文件複製進來。

post-rewrite

這個 hook 被那些會替換提交記錄的命令調用,好比 git commit --amendgit rebase(不過不包括 git filter-branch)。 它惟一的參數是觸發重寫的命令名,同時從標準輸入中接受一系列重寫的提交記錄。 這個 hook 的用途很大程度上跟 post-checkoutpost-merge 差很少。

sendemail-validate

這個 hook 由 git send-email 觸發,它接受一個參數:包含 e-mail 接受者郵箱的文件名。若是這個 hook 返回非 0 值,git send-email 就會被停止。

pre-auto-gc

Git 的一些平常操做在運行時,偶爾會調用 git gc --auto 進行垃圾回收。這個 hook 會在垃圾回收開始以前被調用,能夠用它來提醒你如今要回收垃圾了,或者依情形判斷是否要中斷回收。

Cocoapods - 大管家

作爲一個第三方庫依賴管理工具,Cocoapods 在模塊化開發中扮演了很是核心的角色。關於 Cocoapods 的功能和介紹這裏就不一一陳述了,主要看一下和模塊化開發過程當中相關的一些東西。

自定義 Cocoapods 模板

咱們通常用 pod lib create 這個指令來建立一個模塊,其實這個指令還有一個選項:--template-url=URL,用來指定生成庫的模板,官方模板地址是:github.com/CocoaPods/p…,根據這個模板生成的工程目錄結構以下:

MyLib
  ├── .travis.yml
  ├── _Pods.xcproject
  ├── Example
  │   ├── MyLib
  │   ├── MyLib.xcodeproj
  │   ├── MyLib.xcworkspace
  │   ├── Podfile
  │   ├── Podfile.lock
  │   ├── Pods
  │   └── Tests
  ├── LICENSE
  ├── MyLib.podspec
  ├── Pod
  │   ├── Assets
  │   └── Classes
  │     └── RemoveMe.[swift/m]
  └── README.md複製代碼

也就是說,咱們能夠 fork 這份官方模版地址,而後定製咱們本身的模板(包括主工程和業務模塊),增長自定義的功能。好比:

  • 添加全部基礎庫依賴。
  • 添加私有 Cocoapods 倉庫。
  • 添加 .gitignore 文件。
  • 添加自定義腳本。

私有 Cocoapods 倉庫

和主工程同樣,模塊也是須要作版本管理的,目前來看比較好的方式就是經過 Cocoapods 來發布版本。因此咱們須要準備一個本身的私有 Cocoapods 倉庫,其實很簡單,首先在內網 git 上創建一個空倉庫,而後在本地執行一下下面的指令便可:

pod repo add [Spec name] [Git url]複製代碼

而後就是在發佈庫的時候注意一下,用以下指令便可發佈到私有倉庫內:

pod repo push [Spec name] [Lib name].podspec複製代碼

Cocoapods 引用第三方庫的幾種方式

使用過 Cocoapods 的童鞋應該都知道,Cocoapods 的引用方式有三種:

方式 例子 說明
版本號引用 pod 'Alamofire', '~> 3.0' 這種方式引用的是已經發布的版本,包含了 >``>=``<``<=``~> 幾種版本限制符號,其中~>符號表明只更新最新的小版本號,好比 ~> 1.0.0 則只會更新到 1.0.x 的最新版本,而不會更新 1.x.0 以上的版本
本地路徑引用 pod 'Alamofire', :path => '~/Documents/Alamofire' 這種方式直接引用本地的代碼,這種方式下對引用庫的修改仍然會提交到引用庫的 git 上,而不會提交到主工程。
遠程 git 路徑引用 pod 'Alamofire', :git => 'github.com/Alamofire/A…' 這種方式直接引用遠程 git 代碼,不須要引用的庫進行發佈,並且還支持 :branch =>:tag =>:commit => 三種選項

流程分析

下面就是我對 @zesming 大佬分享的流程圖中各個過程的一些思考。

業務方需求到開發

根據 GitFlow 的規範,新的需求走的是 feature 的流程。這裏咱們應該能夠開發一些輔助開發的腳本。

一鍵新建業務模塊和主工程 feature 分支

組件化到了一個完整階段的時候,主工程應該是沒有代碼的,只是一個殼。可是在發展階段的時候,主工程還會包含一些業務代碼,因此咱們在開發某個 feature 的時候,每每是模塊內有一些具體業務代碼,主工程還有一些調用代碼,這個時候就須要在主工程和業務模塊都新建一樣的 feature 分支,因此咱們在主工程增長一個腳本。這個腳本的參數包含:

  • 業務模塊名;
  • 業務模塊本地路徑;
  • feature 分支名。

執行過程以下:

  1. 爲主工程建立 feature 分支;
  2. 進入業務模塊所在目錄,爲業務模塊建立 feature 分支;
  3. 設置主工程 Podfile 中業務模塊引用方式爲本地路徑引用;
  4. 打開主工程和業務模塊 Example 工程。

一鍵切換主工程開發調試狀態

一鍵切換主工程開發狀態是指某些業務模塊須要依賴主工程來進行調試的時候(PS:固然,比較理想的狀態是業務模塊能夠獨立運行,不過通常狀況下,理想很美好,現實很殘酷😂),須要將 Podfile 中這個模塊的引用方式修改成本地路徑的引用方式。這樣主工程代碼的修改和業務模塊代碼的修改會分別提交到各自的 git 倉庫,從而實現邊調試邊開發邊提交代碼。

這個功能考慮用腳本的方式來實現,放置在主工程的經常使用腳本目錄下。參數應該包含:

  • 業務模塊名;
  • 業務模塊本地路徑;
  • 業務模塊 feature 分支名 [可選]。

執行的操做應該包含:

  1. 若是指定了業務模塊 feature 分支名,則須要先給業務模塊新建分支,不然直接執行第 2 步;
  2. 修改主工程 Podfile 中業務模塊的引用方式爲本地路徑引用;
  3. pod install
  4. 關閉主工程並從新打開。

一鍵切換主工程提交狀態

業務模塊開發調試完成以後,須要將主工程恢復到正常的狀態並提交。

這個功能仍是用腳本的方式實現,放置在主工程的經常使用腳本目錄下。參數應該包含:

  • 業務模塊名;
  • 業務模塊遠程 git 地址;
  • 業務模塊 feature 分支名。

執行的操做應該包含:

  1. 根據當前業務模塊本地路徑,提交業務模塊代碼;
  2. 修改主工程 Podfile 中業務模塊的引用方式爲遠程 git 引用;
  3. pod install
  4. 關閉主工程並從新打開。

提交代碼到業務代碼倉庫

這個過程咱們主要經過 githooks 來作一些一些自動檢測。包括:

  • OCLint
  • 單元測試

發佈組件到內網 Pods

準備階段

這個階段作的事情主要是檢測當前須要發佈的全部分支是否都已經提交 PR / MR 併合併到了 develop 分支。

注:這裏對分支的命名會有一些規則上的要求比。如在分支名內須要帶上當前須要發佈的版本號,從而能夠經過這個版本號匹配到當前須要發佈的全部分支。

發佈 -> 測試 -> bug fix -> 再次發佈階段

這裏須要把 GitFlow 和 Cocoapods 的發佈流程結合起來。

考慮到通常須要依賴主工程來進行測試,咱們須要在主工程增長一個本地腳原本輔助發佈,包含的參數以下:

  • 當前版本號;
  • 業務模塊本地路徑;
  • 主工程 feature 分支名[可選]。

實現的功能以下:

  1. 若是提供了主工程 feature 分支名,須要先切換主工程分支;不然跳過這一步;
  2. 根據傳入的當前版本號從 develop 分支創建 release 分支。若是已經存在,則跳過這一步;
  3. 將主工程 git 倉庫中 Podfile 引用該模塊的方式替換爲引用遠程 git 倉庫的 release 分支;
  4. 而後在主工程執行 pod update [模塊名] 更新代碼;
  5. 推送主工程代碼到遠程 git 倉庫。
  6. 經過 githooks 的方式打包主工程並提交測試;
  7. 測試過程當中若是存在問題,則經過一鍵切換主工程開發調試狀態腳本直接切換開發狀態並進行 bug fix;
  8. bug fix 以後從新執行第 1 步。

測試完成發佈到內網 Pods 階段

測試完成後就能夠發佈業務模塊到內網 Pods 了。這裏咱們在業務模塊工程內準備一個腳本,參數以下:

  • 當前版本號。
  • 主工程本地路徑。

實現的功能以下:

  1. 首先咱們要修改 .podspec 文件中的版本號爲傳入的當前版本號,並提交 push 到 release 分支。
  2. 而後根據 GitFlow 的 release 流程,合併 release 分支到 develop 分支和 master 分支,而後在 master 分支創建對應版本號的 tag 並 push 到遠程 git 倉庫。
  3. 而後就能夠發佈業務模塊了,發佈完成以後切換到主工程路徑將主工程 Podfile 中對該模塊的引用方式修改成版本號引用。

集成組件到主工程

由於以前發佈組件的時候已經將主工程對各業務模塊的引用方式修改成版本號引用。因此這個階段咱們只須要驗證一下當前主工程引用的是不是各業務模塊的最新發布版本便可。參數以下:

  • 主工程依賴的全部業務模塊列表

完成的功能以下:

檢測主工程依賴模塊的全部業務模塊的最新版本是否和 Podfile 中指定的一致,若是不一致,報錯。

發佈主工程

準備階段

這個階段作的事情有兩點:

  1. 檢測主工程當前須要發佈的全部分支是否都已經提交 PR / MR 併合併到了 develop 分支。
  2. 檢測主工程當前引用的全部業務模塊是否都爲版本號引用。

發佈 -> 測試 -> bug fix -> 再次發佈階段

這個階段能夠理解爲集成測試階段。一樣是主工程中的一個腳本。參數以下:

  • 當前版本號。
  1. 根據傳入的當前版本號從 develop 分支創建 release 分支。若是已經存在,則跳過這一步;
  2. 推送主工程代碼到遠程 git 倉庫;
  3. 經過 githooks 的方式打包主工程並提交測試;
  4. 測試過程當中若是存在問題,則經過一鍵切換主工程開發調試狀態腳本直接切換開發狀態並進行 bug fix;
  5. bug fix 以後從新執行第 1 步。

測試完成發佈

  1. 根據 GitFlow 的 release 流程,合併 release 分支到 develop 分支和 master 分支,而後在 master 分支創建對應版本號的 tag 並 push 到遠程 git 倉庫;
  2. 經過 githooks 的方式打包主工程併發布。

總結

本文只是一個不成熟的思考,後續實施的過程當中可能會對一些細節進行改善,也歡迎你們對我思路中不合理的地方進行指正,我會很是感激。

下一篇的內容應該是業務方需求到開發這個過程當中的一些具體的實踐記錄。敬請期待!

參考資料

Introducing GitFlow:datasift.github.io/gitflow/Int…

Using Git / Ignoring files:
help.github.com/articles/ig…

githooks:git-scm.com/docs/githoo…

自定義 Git - Git 鉤子:git-scm.com/book/zh/v2/…

相關文章
相關標籤/搜索