作者:錢佳衛,研發工程師,產品研發和工程架構部-Client Infrastructure-App Infra-DevOps-Developer Tools
CocoaPods 雲端分析能力是位元組跳動的終端技術團隊(Client Infrastructure) 下 Developer Tools 部門提供的一系列雲化基礎設施之一, Developer Tools 團隊致力於建設下一代行動端雲化基礎設施,團隊通過雲 IDE 技術、分散式構建、編譯連結等技術,優化公司各業務的研發和交付過程中的質量、成本、安全、效率和體驗。
iOS 元件化研發模式下,CocoaPods 已然成為 iOS 業界標準的依賴管理工具。但隨著業務能力不斷拓展迭代,元件數量不斷增多,導致App工程複雜度急劇增大,依賴管理效率嚴重下降,甚至出現潛在的穩定性問題。為了能夠更快、更穩定得管理大型專案的元件依賴,iOS build 部門打造了一套中心化依賴管理服務——雲端依賴分析,從工具鏈的層面收斂了依賴管理流程,加速了決議速度,聚合了失敗問題。
基於 CocoaPods 的 iOS 工程管理,每次執行 pod install,都需要先將元件索引資訊 Spec 倉庫同步到本地,一般都依靠於 git 倉庫的 clone,然後讀取 Podfile、Lockfile 以及其他組態檔,開始進入依賴分析、依賴下載、工程整合等幾個步驟。
雲端分析是一個依賴於位元組跳動自研製品庫平臺,通過工具鏈上傳本地工程構建物料,快速返回依賴分析結果,中心化管理 iOS 工程依賴的雲端服務。雲端分析服務會依賴於製品庫提供所有元件索引資訊;並且通過雲端分析本地工具在環境準備過程中獲取本地工程物料,統一上傳至雲端進行依賴決議任務,雲端藉助於一系列優化手段以及伺服器效能,快速返回一個決議結果,本地接收到決議結果之後進行後續的依賴下載與工程整合過程。
雲端分析的接入方式也極其容易,不需要增加組態檔,也不需要修改原有研發模式,以無侵入、無接入成本、不影響研發流程的方式接入到工程專案中。唯一需要做的,僅僅是在 CocoaPods 工具鏈中加入雲端分析的 RubyGem 外掛,並在 pod install 命令中增加一個開啟優化功能的控制開關引數。
基於 Cocoapods 的 iOS 開發體系對 iOS 的產物管理是非常粗放的,直接將不同的 git 倉庫作為構建產物(podspec 檔案)的索引倉庫,擔當了製品庫的角色。隨著 iOS 工程的複雜化,git 倉庫的檔案資訊增加導致元件索引資訊查詢困難,倉庫的同步速度緩慢。BitNest 製品庫是公司自研的行動端的產物管理系統,用於管理持續整合過程中所產生的構建產物。製品庫將分離在各個 git 倉庫的 podspec 源進行了中心化的管理,通過一套完整的 CLI 指令,能夠快速拉取、查詢 podspec 資訊。雲端分析服務藉助於製品庫能力的幫助,能夠在雲端實時存取一個全量完整的 podspec 源資訊。每次CocoaPods 任務都不需要再去更新 podspec 源資訊,也不會因為不及時更新 podspec 源資訊而找不到最新發版的元件 podspec 資訊。
在介紹快取機制之前,先簡單介紹一下 pod install 中依賴分析的執行流程。在第一次執行的時候(忽略 lockfile),CocoaPods 會通過 DSL 從 Podfile 中讀取具體的 plugin,source,target,pod 等內容,建立相應的物件完成準備階段。在每個 Target 物件中每個 pod 都建立成了 Dependency 物件,並且都會有具體的 Requirements 物件。所有 Target 物件的所有 Dependency 物件都逐個被加入到堆疊中,並建立一個 Graph 依賴節點圖。每個 Dependency 物件根據其 Requirements 去對應的 Source 倉庫尋找對應的 pod,如果 Requirements 中沒有倉庫資訊,就從 podfile 公共 Source 中遍歷尋找。找到對應的 pod 之後,會先建立一個版本列表,並從版本列表中找出所有符合 Requirements 要求的 pod,然後讀取對應是 podspec 檔案內容。決議中會對 Spec 物件中隱式的 pod 建立新的 Dependency 加入到分析堆疊和 Graph 中。如果某個版本的 Spec 在遍歷 Graph 依賴圖時不滿足另一個同名依賴的 Requirements,就會進行出棧回撤和依賴圖回撤,直至所有 Dependency 都被找到對應的 Spec 物件為止,分析就完成了。可見,在 CocoaPods 依賴管理過程中,有大量重複的物件建立和排序查詢過程,極大的降低了研發效率。試想,讓 CocoaPods 任務所需的物件一直保持就緒狀態,每當收到任務請求立即執行依賴分析工作,就可以快速返回結果。雲端分析服務集中化了所有 CocoaPods 的依賴管理任務,針對重複的工作搭建了物件快取機制。採用懶載入的模式,對新增物件進行快取,在下一次任務進來之後立刻進入依賴決議過程。
在分析每個 pod 時,為了能獲取最新版本的 pod 依賴,CocoaPods 會對 source 倉庫中的所有版本號建立對應的 Version 物件,並進行排序。目前,公司內部大部分製品版本已經達到上萬的數量級,而且在不指定 source 源的情況下,二進位制版本和原始碼版本都會被排序並讀取,最終獲取一個滿足要求且最新的版本。由於元件版本號都以 「.」 和 「-」 分段,大部分元件版本都存在4個或者5個欄位以上。這也致使上萬個元件在進行排序的過程中,每次排序對比都需要遍歷4次以上,使時間複雜度提升了好幾倍,極大得增加了耗時。
為了更快得獲取到有序的版本列表,由製品庫服務維護了所有 pod 元件從大到小排序的版本檔案;每增加一個新的 pod 版本,製品庫都會向檔案中插入一個新版本;刪除時,則會刪除相應的版本欄位。
有了有序的版本檔案,雲端分析增加 Version 快取的主要目的是為了將版本分段資訊一直維持在 Version 物件中,可以快速判斷當前 Version 是否滿足依賴的要求。Version 快取可以讓依賴管理過程提速大約10-12秒左右。
雲端分析在無版本快取的情況下,會優先讀取版本檔案中的資料,直接獲得有序的版本列表;如果版本列表長度與 source 中元件版本目錄長度不一致,會回退到原始方法(版本列表出錯,確保分析的正確性)。在快取命中的情況下,也需要判斷快取版本列表長度是否與 pod 版本目錄長度相等(有新增版本,快取未新增),則會從版本列表陣列中查詢出差異版本,並對快取進行修正。
CocoaPods 在從排序版本中查詢滿足依賴要求的 podspec 時,會將所有滿足依賴要求的 podspec 版本內容全部讀取進來,進行依賴決議遍歷。如果在不註明具體版本的情況下,所有版本的 podspec 檔案都將被讀取,並且在不註明具體 source 源的情況下,所有 source 存在的 pod 也都會被讀取。一萬個 podspec 檔案讀取就需要花費 30 秒左右(據不同磁碟而定) 。
雲端分析會對每次分析任務 IO 讀取的 podspec 檔案內容進行快取。在下次任務獲取 Spec 物件時,可以根據 source,pod_name,version 三個欄位直接得到對應的Spec物件。
同時,為了確保 Spec 的正確性,防止 Spec 在不改變版本而更改內容的情況出現。Spec 物件快取是以一個多維陣列的形式存在,通過判斷 podspec 檔案的修改時間,來更新快取中的 podspec 內容為最新提交的,確保 checksum 計算與本地拉倉依賴分析的計算值相同,實現雲端依賴分析的正確性。後續,也會增加 Spec 快取命中次數,Spec 物件過期時間等,實現 Spec 快取的清理策略。
雲端分析也會對分析結果進行快取,下一次遇到相同的分析任務能夠直接複用。雲端在獲取一次物料之後,會對物料做一次全域性 hash 計算和一次分段 hash 計算,分別快取完整的分析結果
和分析結果圖 Graph
。針對下一次分析任務,如果是完全相同的物料可以直接返回一個可用的完整分析結果;如果未匹配,會通過一些 target,platform 等資訊計算出一級平臺資訊 key
,來確定具體 app 資訊;再對所有target 下的元件依賴逐個計算 hash 值,獲得二級 hash 陣列 key
,並對應一個分析結果圖 Graph value;通過模糊匹配的方式對 hash 陣列 key 進行匹配,匹配到依賴個數相同最多的相近圖,來替換物料中的 locked_dependencies,來加速分析。當然,模糊匹配能力也有一定的侷限性,無法對原本上傳 lockfile 物料的分析任務進行加速。
雲端分析會將 CocoaPods 物件轉變為位元組流進行傳輸。具體的上傳物料與分析結果具體如下:
1. 上傳物料
雲端分析工具鏈會將 Podfile 物件、lockfile 生成的 Molinillo Graph 物件、指定的 Source 物件、外掛介面卡,所有的外部源 Specs 物件(具體為指定 git,path 和 podspec 的 pre-release 物件)作為上傳物料。但其實,雲端分析並不需要這些本地物件的全部資訊,可以對這些物件進行剪枝,例如 Podfile 物件僅需要 target_definitions 的連結串列即可;Molinillo Graph 物件僅需要所有 pod 對應的節點,而不需要記錄操作節點的 log;Source 物件僅需要知道 name 和 repo_dir 即可,等等。其中,部分決議優化外掛需要通過外掛介面卡額外傳輸一些設定 Config 物件。
2. 結果返回
雲端分析返回的結果為以 Target 為 key,相應的 Specs 陣列為 value 的 hash 物件。結果返回之前,會先對所有 Spec 的 Source 進行剪枝。由於每個 Spec 對應的 Source 在後續流程中僅使用到 url 的欄位進行分類與生成 lock 檔案。因此,可以刪除 Source 物件其他無用的欄位,最小化傳輸內容,加快響應時間。對返回結果進行剪枝後,傳輸內容大小可以減少大約10MB以上。
為了確保決議結果的正確性和唯一性(single truth),雲端分析相容了位元組跳動內部各 CocoaPods 決議策略優化的工具鏈。根據工程中構建設定引數,雲端分析本地外掛識別出具體的決議策略,並傳遞到雲端分析伺服器並啟用對應決議策略演演算法進行快速決議。同時,結合已有的決議優化策略和雲端的優化加速機制,讓 CocoaPods 的依賴管理流程達到秒級返回。
本文主要分享了目前位元組跳動內部的一種 CocoaPods 雲端化的優化方案,針對大量重複的 iOS 工程流水線構建任務進行了收斂和複用,在保證依賴決議正確性的前提下加速了依賴管理速率,提升了研發效能。目前雲端分析服務已經完成第一階段的開發並落地使用,已被公司內部幾個核心的生產線使用。如頭條接入雲端分析服務後,pipeline 的依賴分析階段耗時加速60%以上。後續,對於 CocoaPods 的下載優化,工程快取服務也已經在技術探索中,相關技術文章將陸續分享,敬請期待!
CocoaPods原理詳解:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzA5MTM1NTc2Ng==&action=getalbum&album_id=1477103239887142918&scene=173&from_msgid=2458325057&from_itemidx=1&count=3&nolastread=1
CocoaPods優化:https://www.infoq.cn/article/adqsbwtvsyzuvh429p8w
我們是位元組的 Client Infrastructure 部門下的 Developer Tools 團隊,團隊成員由 IDE 專家及構建系統專家組成,團隊致力於通過使用者端雲化技術以及編譯構建技術,優化公司各業務的研發和交付過程中的質量、成本、安全、效率和體驗。同時,在實踐的過程中我們也看到了很多令人興奮的新機會,希望有更多對編譯工具鏈技術感興趣的同學加入我們一起探索。
【掃碼投遞簡歷】