01 背景
由于導航應用中的地圖渲染、導航等核心功能對性能要求很高,所以高德地圖客戶端中大量功能采用 C++ 實作,隨著業務的飛速發展,僅地圖引擎庫就有40多個模塊,工程配置極其復雜,原有的構建及持續集成技術已無法滿足日益增長的需求變化,
除了以百萬計的代碼行數帶來的復雜度外,高德地圖客戶端中的 C++ 引擎庫工程(以下簡稱引擎庫)的構建和持續集成還面臨以下幾個挑戰:
- 支持多團隊協作:多團隊意味著多作業系統多 IDE ,降低不同作業系統和不同 IDE 下的工程配置的難度是重點要解決的難題之一;
- 支持多業務線定制:引擎庫為手機、車機、開放平臺等業務線提供支持,而各個業務線的訴求不同,所以需要具備按功能構建的能力;
- 支持車機環境:在諸多業務線中,高德地圖有一個非常特殊的業務線,即車機(AMAP AUTO),車機直接面對各大車廠和眾多設備商,環境多為定制化,構建工具鏈各式各樣,如果針對每個車機環境都定制一套構建組態檔,那么其維護成本將非常高,所以如何用一套構建配置滿足車機的多樣化構建需求成為亟需解決的問題;
此外,由于歷史原因,引擎庫中原始碼和依賴庫混雜,都存放于 Git 倉庫中,這樣會帶來兩個問題:
- 隨著構建次數不斷增加,Git 倉庫越來越大,代碼與依賴庫檢出越來越慢,極大影響本地開發以及打包效率;
- 缺乏統一管理,依賴關系混亂,經常出現因為依賴問題而導致的構建失敗,或者雖然構建成功但運行時發生錯誤的情況;
上述的挑戰和歷史遺留問題嚴重阻礙了研發效能的提升,為此,我們對現有的構建及持續集成工具進行了深入的研究和分析,并結合自身的業務特性,最終發展出高德地圖 C++ 本地構建工具 Abtor 和持續集成工具 Amap CI ,
02 本地構建
現有工具分析
C++ 是一門靠近底層的語言,不同的硬體、作業系統、編譯器,再加上交叉編譯,導致 C++ 構建的難度非常高,針對這些問題,C++ 社區涌現出許多優秀的構建工具,比如大名鼎鼎的 Make 和 CMake ,
Make,即 GNU Make ,于1988年發布,是一個用來執行 Makefile 的工具,Makefile 的基本語法包括目標、依賴和命令等,使用程序中,當某些檔案變了,只有直接或者間接依賴這些檔案的目標才需要重新構建,這樣大大提升了編譯速度,
Make 和 Makefile 的組合可以看作專案管理工具,但它們過于基礎,在跨平臺的使用方面有很高的門檻和較多的限制,此外大專案的構建還會遇到 Makefile 嚴重膨脹的問題,
CMake 產生于2000年,是一個跨平臺的編譯、測驗以及打包工具,它將組態檔轉化為 Makefile ,并運行 Make 命令將原始碼編譯成可執行程式或庫,CMake 屬于 Make 系列,組態檔比 Makefile 具有可讀性,支持跨平臺構建,構建性能高,
但是 CMake 也有兩項明顯不足,一是組態檔的復雜度遠高于其它現代語言,對于 CMake 語法初學者有一定的學習成本,二是與不同 IDE 的配合使用不夠友好,
可以看出 Make 和 CMake 的抽象度還是比較低,從而對構建人員的要求過高,為了降低構建成本,C++ 社區又出現了一些新的 C++ 構建工具,現在使用較廣泛的包括 Google 的 Bazel 和 Ninja ,以及 SCons ,這些工具的特點和不足如下:

經過上述對現有 C++ 構建工具的研究和分析,可以得出每個工具既有所長又有不足的結論,再考慮到高德地圖引擎庫工程面臨的挑戰和歷史遺留問題,我們發現以上工具沒有一個可以完美契合業務需求,且改造成本非常高,所以我們決定基于 CMake 自建 C++ 本地構建工具,即現在引擎庫工程使用的 Abtor ,
Abtor
首先,我們需要解釋一個問題,即 Abtor 是什么?
Abtor 是一個 C++ 跨平臺構建工具,Abtor 采用 Python 撰寫構建腳本,生成 CMake 組態檔,并通過內置 CMake 組件生成構建檔案,最終產出可執行程式或庫,它抽象出構建描述,使得復雜的編譯器和連接器對開發者透明;它提供強大的內置功能,從而有效的降低開發者撰寫構建腳本的難度,
其次,我們需要闡述一個問題,即Abtor的構建流程是什么?

如上圖所示,Abtor 構建的整個流程為:
- 撰寫 Abtor 構建腳本;
- 決議 Abtor 構建腳本;
- 檢測依賴關系,識別沖突,并從阿里 OSS 上下載所需依賴;
- 生成CMakeLists.txt,并通過內置的 CMake 生成 Makefile 檔案;
- 編譯,鏈接,生成對應平臺的目標檔案;
- 將目標檔案發布到阿里 OSS ;
除此之外,還增加了控制訪問發布庫權限的功能,用于保證發布庫的安全,
最后,我們需要探討一個問題,即Abtor解決了什么?
在開篇背景中,我們提到阻礙研發效能的一些挑戰和問題,這就是 Abtor 需要解決的,所以 Abtor 具備以下特點:
- 更廣泛的跨平臺:支持 MacOS 、iOS、Android、 Linux、Windows、QNX 等平臺;
- 有效的多團隊協作:較好得與 IDE 結合,并支持一套配置生成不同專案工程,從而達到工程配置一致化;
- 高定制化:支持工具鏈及構建引數的靈活定制,并通過內置工具鏈配置為車機復雜的構建提供強有力的支持;
- 原始碼與依賴分離:支持原始碼依賴與庫依賴,原始碼通過Git管理,構建庫存放于阿里云,原始碼與產物完全分離;
- 良好的構建性能:快速構建大型專案,從而提高開發效率;
從上述特點可看到,Abtor 有效地解決了已有的構建工具在高德業務中面臨的痛點,但是冰凍三尺,非一日之寒,Abtor 也是在不斷地完善中,下面重點介紹一下 Abtor 發展程序中遇到的三個問題,
工程配置一致化
在日常開發程序中,工程專案的除錯作業尤為重要,高德地圖客戶端中的 C++ 引擎庫工程的開發人員涉及幾個部門和諸多小組,這些組擅長的技術堆疊,使用的平臺和習慣的開發工具都大為不同,如果針對每一個平臺都單獨建立相應的工程配置,那么作業量及后續維護成本可想而知,
基于以上原因,Abtor 內置與 IDE 結合的功能,即開發者可以通過一套配置并結合 Abtor 命令一鍵生成工程配置,實作在不同平臺的工程配置的一致化,工程配置一致化為引擎庫開發帶來以下幾個收益:
- 命令簡單,降低學習成本,開發者只需熟記 abtorw project [IDE name];
- 組態檔不會因為 IDE 的增加而迅速膨脹,開發者更換構建命令,比如 abtorw project xcode 或者abtorw project vs2015,即可生成對應的專案工程;
- 有利于部門間的協作及新人的快速融入,開發者可以根據喜好選擇 IDE 進行開發,大大提高開發效率;
- 目前Abtor支持的IDE有 Xcode、Android Studio、Visual Studio、Qt Creator、CLion等,
復雜車機環境的構建
作為高德地圖一條非常重要的業務線,車機面對的構建環境復雜多變,廠商往往會自行定制工具鏈,如果每接入一個設備,所有工程專案都需要修改組態檔,那么這個成本還是非常高的,為了解決這個問題,Abtor 提供兩種做法:
- 內置工具鏈配置:對于開發者完全透明,他不需要修改任何配置即可構建相應平臺的產物;
- 支持自定義配置插件:開發者按照規則撰寫配置插件,構建時 Abtor 會檢測插件,并根據設定的工具鏈及構建引數進行構建;
除此之外,我們對所有的車機環境進行了 Docker 化處理,并通過 Docker 控制中心統一管理車機 Docker 環境的上線與下線,再利用上述 Abtor 的內置工具鏈配置功能內置車機構建引數,實作開發者無感知的環境切換等操作,有效地解決了復雜車機環境的構建問題,
基于 Docker 的車機構建主要步驟如下:
- 工具鏈安裝:一般由廠商提供,我們會將該工具鏈安裝到基礎 Docker 鏡像中;
- Docker 發布:將鏡像發布到 Docker 倉庫;
- Abtor 適配:一次性適配工具鏈,并內置配置,開發者可通過 Abtor 版本升級使用該配置;
- 服務配置更新:由 Jenkins 管理,支持分批更新 Abtor 版本,不影響當下編譯需求;
- 服務監控: 由 Jenkins 管理,定時檢測服務狀態,例外態的 Docker 服務將自動被重啟;
基于Docker的車機構建關系圖如下:
依賴管理
依賴問題是所有構建工具都避免不了的問題,在這其中,菱形依賴問題尤為常見,如下圖所示,假設 A 依賴了 B 和 C ,B 和 C 又分別依賴了不同版本的 D,而 D 之間只存在很小的差異,這是可以編譯通過的,但最終在運行時可能會出現意想不到的問題,
如果沒有一種機制來檢測,菱形依賴是很難被發現,而產生的后果又可能是非常嚴重的,比如導致線上出現大面積的崩潰等,所以依賴問題的分析與解決非常重要,

當下,市面上 Java 有比較成熟的依賴管理解決方案,如 Maven 等,但 C++ 并沒有,為此 Abtor 專門建立依賴管理的機制來確保編譯的正確性,
Abtor 的依賴管理是怎么做的呢?這里提供一個思路供大家參考:
- 建立 Abtor 服務端,用做庫發布,以及處理依賴關系;
- 每個庫在云端構建完,都會把庫依賴的版本資訊存放于云端資料庫中;
- 本地/云端構建前 Abtor 會決議出所有依賴庫的版本資訊;
- 遞回查找這些子庫對應的依賴資訊,即可羅列出所有依賴庫的資訊;
- 檢測依賴庫串列中是否存在不同版本號的相同庫名:
- 如果沒有相同庫名,則繼續執行構建;
- 如果有相同庫名,則說明依賴庫之間存在沖突問題,此時中斷構建,并顯示沖突的庫資訊,待開發者解決完沖突后方可繼續執行構建;
根據上述思路,我們保證了庫依賴的一致性,避免了菱形依賴問題,另外,如果某個庫被其它庫所依賴且有更新,那么依賴它的庫也應當隨之構建,以確保依賴的一致性,這種對依賴構建的觸發更新我們放到 Amap CI 上實作,在第三節會進行詳細介紹,
工程實踐
在介紹完 Abtor 的一些基本原理后,我們將介紹 Abtor 在日常開發中是如何使用的,
下圖是 Abtor 工程專案的目錄結構,其中有兩類檔案是開發者需要關心的,一類是源檔案目錄(src),一類是 Abtor 核心組態檔(abtor.proj),
abtor_demo ├── ABTOR │ └── wrapper │ ├── abtor-wrapper.properties # 組態檔,可指定Abtor版本資訊 │ └── abtor-wrapper.py # 下載Abtor版本并呼叫Abtor入口函式 ├── abtor.proj # Abtor核心組態檔 ├── abtorw # Linux/Mac下的初始執行腳本 ├── abtorw.bat # Windows下的初始執行腳本 └── src └── main.c # 要編譯的源檔案
源檔案目錄的組織形式與 Make 系列構建工具沒有太大區別,下面重點看一下Abtor核心組態檔:
#!/usr/bin/python # -*- coding: UTF-8 -*- # 以下內容為python語法 # 指定編譯的原始碼 header_dirs_list = [abtor_path("include")] # 依賴的頭檔案目錄 binary_src_list = [abtor_path("src/main.c")] # 原始碼 cflags = " -std=c99 -W -Wall " cxxflags = " -W -Wall " # 指定編譯二進制 abtor_ccxx_binary( name = 'demo', c_flags = cflags, cxx_flags = cxxflags, deps = ["add:1.0.0.0"], # 指定依賴的庫資訊 include_dirs = header_dirs_list; srcs = binary_src_list )
從上圖可以看出,Abtor核心組態檔具有以下幾個特點:
- 采用Python撰寫,易上手;
- 抽象類似 abtor_ccxx_binary 等的構建描述,降低使用門檻;
- 提供諸如 abtor_path 等的內置功能,提高開發效率;
通過以上的對源檔案目錄組織及 Abtor 核心組態檔撰寫,我們就完成了專案的Abtor配置化,接著可以通過Abtor內置的命令構建、發布或直接生成專案工程,我們相信,即使開發者不是很精通構建原理,依然可以無障礙地使用Abtor進行構建與發布,
03 持續集成
面臨的問題
如下圖所示,整個開發作業流程可分為幾個階段:編碼->構建->集成->測驗->交付->部署,在使用Abtor解決本地構建遇到的一系列挑戰與問題后,我們開始將目光轉移到了整個持續集成階段,

持續集成是指軟體個人研發的部分向軟體整體部分交付,頻繁進行集成以便更快地發現其中的錯誤,它源自極限編程(XP),是 XP最初的12種實踐之一,對于引擎庫來說,持續集成方案應該具備一次性批量構建不同平臺不同架構目標檔案的能力,同時也應當具備運維管理和訊息管理的能力等,
最初高德引擎庫使用 Jenkins 進行持續集成,因為引擎庫開發采用在 Git 倉庫上拉取分支的方式進行版本管理,所以每次版本迭代都需要手動建立 Jenkins Job,修改相應腳本,另外還需要額外搭建一個依賴庫關系的 Jenkins Job 做聯動編譯,
假設有100個專案,那么每個版本迭代都需要手動創建101個 Jenkins Job ,每次版本迭代都重復類似的操作,中間需要大量的協調作業,隨著迭代版本越來越多,這些 Jenkins Job 變得不可維護,這是 Jenkins 持續集成方案在高德引擎庫開發程序中遇到的非常嚴重的問題,
基于上述原因,我們迫切得需要這樣一個持續集成系統:開發者不用維護Jenkins,不需要部署構建環境,可以不了解構建細節,只需要通過某個觸發事件就能夠構建出所有平臺的目標檔案,于是我們決定自建持續集成平臺,即 Amap CI,
Amap CI
Amap CI 平臺使用Gitlab的Git Webhook實作持續集成,其中,Gitlab 接收開發者的 tag push 事件,回呼 CI平臺的后臺服務,然后后臺服務根據構建機器的運行情況進行任務的分發,當構建任務較多時,CI平臺會等待直到有構建資源才進行任務的再分配,
Amap CI 平臺由任務管理、Jenkins管理、構建管理、通知管理、網頁前端展示等幾部分組成,整體架構圖如下:

通過 Amap CI 平臺,我們達到了以下幾個目的:
- 可擴容:所有構建機器通過注冊的方式接入,構建機器擴容變得非常容易,減輕構建峰值帶來的壓力;
- 可視化:Abtor Server 對于開發者是透明的,CI 平臺與 Abtor Server 互動,為開發者提供沖突檢查、依賴查看及庫下載等可視化功能;
- 智能化: CI 平臺內置標準的 Jenkins Job 構建模板,開發者不感知這些模板,也無須做任何的修改,他們只需要通過 Git 提交一個 tag 資訊即可實作全平臺的構建,從而實作一鍵打 tag 構建;
- 自動化:服務分析 Gitlab hook tag 的 push 資訊并拉取代碼,然后決議對應的組態檔和要構建的所有平臺資訊,根據這些資訊CI平臺分配構建機器,并執行 Abtor 命令進行構建與發布,所有這些皆自動完成;
- 即時性:構建啟動后會發送釘釘訊息,訊息除了概要資訊外還附加了構建的鏈接等,開發者可以點擊鏈接跟蹤進度情況,構建成功或失敗也都會發送訊息,從而使得開發者可以及時進行下一步作業或處理構建錯誤;
- 可擴展:CI平臺提供可擴展的對接方式,方便高德或阿里的其它平臺對接,比如泰坦平臺、CT平臺、Aone等,從而實作編碼、構建、測驗和發布的開發倍訓;
在上述目的中,對 Amap CI 平臺最重要的是自動化,下面我們重點介紹一下自動化中的整樹聯動編譯,

整樹聯動編譯
在第二部分中我們提到了一個問題,即如果某個庫被其它庫所依賴且有更新,那么依賴它的庫也應當隨之構建,以確保依賴的一致性,這是構建自動化的關鍵點之一,Amap CI 采用整樹聯動編譯的方案來解決這個問題,
開發者在CI平臺上建立對應的版本構建樹,構建樹中羅列了各個庫之間的構建順序,如下圖所示,CI平臺會根據這棵構建樹進行構建,被依賴的庫優先構建,完成后再自動觸發其上級的庫構建,以此類推,最終形成一棵多叉樹,在這棵多叉樹上,從葉子節點開始按層級順序逐級并發構建對應的庫,這就是整樹聯動編譯,
根據上述思路,我們保證了持續集成時的依賴一致性,開發者只需關心自己負責的庫,打個 tag ,即可觸發生成所有依賴該庫的庫,從而避免了依賴不一致的問題,
工程實踐
在介紹完 Amap CI 的一些基本原理后,我們將介紹日常開發中應該如何使用Amap CI,
一個新的工程專案在集成到 Amap CI 平臺時,首先需要將CI平臺的 web hook 網址增加到 Gitlab 的配置中,然后撰寫組態檔 CI_CONFIG.json ,至此一個新的專案已集成完成,非常簡單,下面我們重點介紹一下 CI_CONFIG.json ,
CI_CONFIG.json 是核心組態檔,一次撰寫,無需再修改,它的結構如下:
CI_CONFIG.json DEMO:(json) { "mail":"[email protected]", # 郵件通知 "arch":"Android,iOS,Mac,Ubuntu64,Windows", # 構建的平臺 "build_vars":"-v -V", # 構建引數 "modules":{ # 構建的模塊串列 "amap":{ # 模塊名為amap "features":[ # 功能串列 { "name":"feature1", # 設定功能名為feature1 "macro":"-DFEATURE_DEMO1=True" # 宏控:FEATURE_DEMO1 }, { "name":"feature2", # 設定功能名為feature2 "macro":"-DFEATURE_DEMO2=True" # 宏控:FEATURE_DEMO2 } ] }, "auto":{ # 模塊名為auto "features":[ # 功能串列 { "name":"feature1", # 設定功能名為feature1 "macro":"-DFEATURE_DEMO1=True" # 宏控:FEATURE_DEMO1 }, { "name":"feature3", # 設定功能名為feature3 "macro":"-DFEATURE_DEMO3=True" # 宏控:FEATURE_DEMO3 } ] } } }
從上圖可以看出,組態檔描述了郵件通知、構建的平臺、構建引數等資訊,同時還為多業務線定制提供了良好的支持,
Amap CI 構建時讀取上述檔案,決議不同專案中配置的宏,并通過引數傳遞給 Abtor ,另一方面開發者在代碼中利用這些宏進行代碼隔離,構建時會根據這些宏選擇對應的原始碼進行編譯,從而支持多條業務線不同的需求,達到代碼層面的最大復用,
目前 Amap CI 接入的專案數有幾百個,編譯的次數達到幾十萬次級別,同時在構建性能和構建成功率方面相比之前都有了大幅度的提高,現在仍舊不斷有新的專案接入到構建平臺上,可以說 Amap CI 平臺是高德地圖客戶端 C++ 工程快速迭代開發的堅實保障,
04 未來展望
從2016年年中調研現有構建工具算起,到現在三年有余,三年很長,足以讓我們將構想變成現實,足以讓我們不斷完善 Abtor ,足以讓我們發展出 Amap CI ,三年又很短,對于一個系統開發生命周期而言,這僅僅是萌芽階段,我們的征途才剛剛開始,
關于未來,我們的規劃是向開發倍訓方向發展,即打通編碼、構建、集成、測驗、交付和部署等各個環節中的鏈路,解決業務開發倍訓的問題,實作整個開發流程自動化,進一步把開發者從繁瑣的流程中解放出來,使得這些人員有精力去做更有價值的事情,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/109849.html
標籤:Html/Css
上一篇:負邊距與雙飛翼布局
下一篇:關于 JS this
