1. 程式人生 > >基於 Conan 的 C/C++ 持續交付流水線

基於 Conan 的 C/C++ 持續交付流水線

背景


在當下軟體應用的開發過程當中,單槍匹馬或者小作坊式的模式已經很少見了,協作式的開發成為主流。相應的,應用的程式碼也不再是從零開始,而是基於或引用很多已有的、共享的模組,如各種開源的框架和共用庫,或者協作團隊中開發的自研庫,這就是軟體開發中常說的“依賴”。為了更好地管理這種依賴關係,各種開發語言都逐漸發展出了自己的依賴管理系統,如 Java 的 Maven、NodeJS 的 NPM、Python 的 Pypi 等。這些依賴管理系統的日漸完善和廣泛應用,使得相應語言的應用開發更加簡潔、高效,大大推動了軟體應用的發展和普及。


然而相對的,作為軟體開發重要組成部分的 C/C++ 語言,由於其編譯型語言的特性,即應用最終要編譯成為目標機器可直接執行的程式,使得 C/C++ 的依賴管理一直是一個眾所周知的難題和痛點。這主要體現在:

  • 應用二進位制介面不相容。C/C++ 的依賴不僅僅體現在程式碼上,還包括作業系統、架構、編譯器等環境配置上,為了確保一個共享庫與其他庫、整個應用的相容性,必須通過各種配置來描述這些配置的依賴資訊。

  • 編譯構建慢。由於標頭檔案和預處理機制,以及上面提到的相容性,需要額外的機制來提升編譯效率,才能保證只編譯那些需要重新編譯的程式碼。

  • 程式碼連結和內嵌。一個靜態的 C/C++ 庫能夠被另一個庫通過標頭檔案包含的方式引用,而一個共享庫也能嵌入另一個靜態庫。在兩種情形中,當任何依賴變更時,都必須管理哪些庫是需要重新構建的。

  • 生態系統的快速發展。針對不同平臺、不同構建任務及應用場景的編譯器、構建系統層出不窮,導致解決上述問題的工作量不斷地增加。


當然,針對 C/C++ 的依賴管理,業界也開展了很多研究和實踐工作,可惜大多數的效果並不理想。而本文將會介紹一種當前已逐步得到業界關注和認可,並得到大力推廣的 C/C++ 依賴管理方案--Conan,以及如何基於 Conan、持續整合(CI)工具 Jenkins 和全語言製品庫 Artifactory,實現 C/C++ 應用的持續交付流水線。



Conan--C/C++ 的包管理器



Conan 是一個開源專案(FOSS,Free Open Source Software),https://conan.io ,為 C/C++ 的依賴包管理構建了非中心化的管理架構,開發者可以像 Docker Registry 一樣搭建自己專屬的依賴包倉庫。Conan 是跨平臺的解決方案,同時相容各種構建工具,以依賴包的二進位制檔案為基礎來定義、管理依賴關係,使得依賴包的獲取和消費更加符合目標環境和配置的特點,提升了整體編譯的效率。Conan 是基於 Python 語言的,上手門檻較低,也易於擴充套件。在被 JFrog 收購之後,Conan 在保持開源特性的同時,還結合了 JFrog 產品在製品管理、DevOps 工具整合等方面的能力,為開發者提供了更為全面的支援。Conan 在當前 C/C++ 依賴管理領域已得到廣泛關注和快速發展。


通常之前介紹的像 Maven 這樣的依賴管理系統,都包含以下幾個部分:

  • 依賴包倉庫,用以儲存需要引用的依賴包,即各種通用框架或共享模組;

  • 依賴訪問協議,用以描述如何在依賴包倉庫中定位、獲取或上傳共享模組;

  • 依賴描述語言,用以描述如何定義依賴關係,以及後臺對依賴關係的自動解析;

  • 客戶端,根據描述語言中的定義,遵照訪問協議,從倉庫中獲取或上傳相應的依賴包。


Conan 也在這幾方面提供了成熟的解決方案:


  • 依賴包倉庫

Conan 在 JFrog 的公網製品分發平臺 JFrog Bintray 上搭建了公共的依賴包倉庫conan-center(https://conan.bintray.com),開發者可以直接在這裡獲取所需的各種公共依賴包。


同時,開發者還可以在本地搭建原生的 conan_server,或者直接利用 JFrog Artifactory 製品倉庫,做為私有的依賴包倉庫。


  • 依賴訪問協議

Conan 通過“<包名>/<版本號>@<所有者>/<成熟度>”的命名規則來定位一個依賴包,其中“<所有者>/<成熟度>”(user/channel)定義了一個類似於名稱空間(NameSpace)的機制,用於區分針對同一個共享庫的不同實現。


每一個依賴包都分為 recipe 和 package 兩個部分。 recipe 定義了依賴包的基本資訊、依賴關係、構建方法等基本資訊,package 則根據目標環境和配置,如作業系統、架構、編譯器等(即 Conan 中的 setting)的不同,儲存對應的二進位制實現。這樣,客戶端訪問時,先根據命名規則定位到 recipe,再根據目標 setting 的不同選擇對應的二進位制 package 來下載、使用。


在 conan-center 中,各種公共庫都根據目標 setting 的不同提供了大量的二進位制 package 供開發者直接使用,大大提高了 C/C++ 應用的編譯效率。


  • 依賴描述語言

Conan提供了簡單明瞭的依賴關係描述方式,在後續的示例中會做詳細解讀。


  • 客戶端

Conan 的客戶端提供了豐富的命令列命令,能夠方便地實現依賴關係的解析和依賴包的管理。


Conan 客戶端的安裝也非常簡便,大家可以參考文件自己實踐(https://docs.conan.io/en/latest/installation.html)。


安裝好之後,我們可以執行第一個命令:

可以看到 Conan 客戶端已經預先配置好了與公共庫 conan-center 的連線,開發者可以直接使用其中的公共依賴包。


本文後續將通過示例來展示 Conan 如何利用這些解決方案來提供服務。



Conan 應用示例


本文將基於一個簡單的 C++ 應用來展示如何使用 Conan,其程式碼可以在https://github.com/xingao0803/demo-poco-timer.git  中獲得,供大家參考。


  • 描述依賴關係

示例中的 C++ 應用,timer.cpp,是一個簡單的 timer 程式,引用了公共庫 POCO:

為了在 Conan 中描述與 POCO 的依賴關係,需要編寫 conanfile.txt,這就是上一節提到的依賴描述語言。

其中,[requires] 部分列出了本應用需要的依賴包,這裡是 POCO,1.8.0.1版本,而且是由 pocoproject 提供的穩定(stable)版本。


[generators] 則列出了編譯本應用使用的編譯器型別。Conan 提供的公開示例大多是基於 cmake 的,這裡改用更為通用的 compiler_args,不限定編譯器的型別。


當然,這裡只是一個簡單的例子,conanfile.txt 的更多內容請參考 Conan 官方文件https://docs.conan.io/en/latest/reference/conanfile_txt.html。


我們知道,在軟體開發中,除了程式碼中的直接依賴,還會有各種傳遞依賴。而 conanfile.txt 裡只列出了直接依賴。Conan 的客戶端提供了“conan info”命令來解析所有的依賴關係。


在 conanfile.txt 所在目錄執行:

本應用相關的各種依賴傳遞關係就會在 denpendencies.html 裡展示出來,如下圖:

此時再執行:

可以看到,相關依賴包的 recipe 已經下載到本地 cache 裡了。


  • 下載依賴包二進位制檔案

“conan info” 命令只是下載了依賴包的 recipe。要下載對應 setting 的 package 二進位制包,用以編譯,還需要執行 “conan install” 命令。


在 conanfile.txt 所在目錄建立構建子目錄,並執行 “conan install”:

從執行結果可以看出,Conan 的客戶端根據本地的 setting 設定和依賴包的 recipe,自動從 conan-center 上獲取對應的二進位制 package,下載到本地 cache 裡。


當然,由於 C/C++ 生態系統的快速發展,Conan 現有的二進位制 package 不一定能夠覆蓋所有的 setting 組合。“—build=missing” 引數就是指定在沒有 setting 對應的二進位制 package 時,根據 recipe 中定義的方法,自動從依賴包的原始碼編譯出對應的二進位制包,儲存在本地 cache 中。例如:當我在 macbook 上執行上述操作時,POCO 就需要進行重新編譯:

當我們再次執行 “conan install” 命令時可以發現,此時相關的依賴包已經可以直接從本地 cache 獲取了,避免了重複的網路訪問和編譯工作。


  • 上傳到私有依賴包倉庫

在當前團隊協作開發的模式下,僅僅把依賴包下載到本地 cache 是遠遠不夠的,我們還需要讓整個團隊都能夠分享這些依賴包。這樣即避免了重複的網路訪問和編譯,又保證了團隊中依賴引用的一致性。此時就需要引入私有的依賴包倉庫。


Conan 提供了原生的 conan_server 作為本地化部署的私有倉庫,可以參考文件https://docs.conan.io/en/latest/uploading_packages/running_your_server.html。


而這裡我們要推薦 JFrog 的 Artifactory 全語言製品倉庫。Artifactory 不僅僅可以做為 Conan 倉庫,其全語言的支援能力還使其能夠同時提供 Maven、NPM、Docker 等依賴倉庫的服務。 Artifactory 還提供元資料的能力,也就是可以在倉庫儲存製品的屬性上記錄整個 DevOps 過程中的關鍵資料。此外,Artifactory 還可以和 Jenkins 緊密整合,在 Jenkins Pipeline 中提供針對各種開發語言的 DSL,方便持續交付流水線的開發與編排。Artifactory 全語言、元資料,以及整合 Jenkins 的優勢在後續示例中都能夠得到體現。


Artifactory 的相關資訊可以參見文件https://www.jfrog.com/confluence/display/RTF/Welcome+to+Artifactory ,“JFrog 傑蛙 DevOps” 微信公眾號上也有很多相關文章和視訊課程供大家參考。此外,JFrog Artifactory 還針對 Conan 的應用推出了社群版-- Artifactory CE,https://docs.conan.io/en/latest/uploading_packages/artifactory_ce.html,供大家使用。


為了使用 Artifactory 建設私有的 Conan 依賴包倉庫,我們需要在 Artifactory 建立一個 Conan 型別的 local repository。建立方法參見https://www.jfrog.com/confluence/display/RTF/Conan+Repositories 。


然後,我們需要執行 Conan 的客戶端命令 “conan remote add” 和 “conna user” 加入這個私有倉庫。具體的執行方法在 Artifactory Repository 的  “Set Me Up” 部分有清晰的描述:

其中 <REMOTE> 作為這個私有倉庫的別名,會在後續的命令中用來指定使用該倉庫。


私用倉庫建立好之後,我們可以使用 “conan upload” 命令來上傳下載到本地 cache 的依賴包:

其中 -r <REMOTE> 用以指定目標私有倉庫,--all 指定同時上傳 recipe 和 package。

執行之後,在 Artifactory 的 Conan repository 裡就可以看到上傳的依賴包了。

之後當再次需要使用這些依賴包時,就可以在執行 “conan install” 時利用 “-r <REMOTE>” 引數指定從私有倉庫獲取了。


  • 編譯C++應用

執行 “conan install” 之後,除了會下載相應依賴包的二進位制 package 之外,還會自動生成編譯相關的引數引用文件。如在前面的示例中,build 目錄下會自動生成 conanbuildinfo.args 檔案,其中包含了編譯過程中如何引用相應依賴包的引數設定。當編譯時,可以直接引用這些引數:

可以看出,基於 Conan 的依賴管理,只要通過 conanfile.txt 描述依賴關係,通過 conan install 命令下載依賴包 package,我們就可以便捷地完成 C++ 應用的編譯。


  • 管理C++應用

當然,從應用開發過程來看,僅僅完成編譯還是不夠的,我們還需要管理好編譯產出的可執行程式,供後續測試、部署、釋出等環節使用。


通常會把應用編譯好的可執行程式儲存到程式碼管理系統,如 git 或 svn 中,或者檔案伺服器,如 ftp 上。但這種方式會丟失掉應用程式特定的 setting 資訊,而且會對現有系統造成效能上的影響。


這裡我們還是推薦使用 Artifactory,利用其 Generic repository 來儲存可執行程式。Artifactory 全語言的支援使得能夠在同一個倉庫中統一管理 Conan 依賴包和編譯好的可執行程式。而其企業級高可用的特效能夠保證製品訪問的效能和穩定。


在 Artifactory 裡建立一個 Generic 的 local repository,在其 “Set Me Up” 部分可以看到如何利用 Artifactory 的 rest api 來上傳和下載相應的可執行程式:

同時,我們可以在 repository 裡通過設定不同的目錄來記錄可執行程式的版本。

針對可執行程式特定的 setting,也可以通過Artifactoy 的元資料能力記錄到對應程式的屬性上。


在執行完 “conan install” 之後,build 目錄裡還會自動生成 conaninfo.txt 檔案,記錄了本應用及其依賴相關的各種配置引數,其中 [settings]、[full_settings] 部分記錄了相關的 setting 資訊:

可以將這些 setting 引數提取出來,利用 Artifactory 的 rest api 寫入屬性:

在 Artifactory 的 repository 裡就可以看到這些屬性了:

此外,Artifactory 還提供了 AQL(Artifactory Query Language),使得我們能夠基於這些元資料屬性設定查詢條件,從而定位與目標 setting 匹配的可執行程式。


設定 aql 檔案 1timer.aql 如下:

再利用 Artifactory 的查詢 rest api,我們就可以定位和下載符合 setting 需求的可執行程式,供後續的測試、部署等環節使用了。

同時,我們還可以利用 Artifactory 元資料的能力,將後續環節的關鍵資料,如測試結果,也記錄在該程式的屬性中,從而為質量監控審批、出錯回溯檢查等提供資料支援。



C/C++持續交付流水線


前面的示例,我們利用 Conan 依賴管理和 Artifactory 私有倉庫實現了完整的 C++ 應用開發流程。在當前 DevOps 應用背景下,我們還需要能夠自動化地重複執行這一流程,以實現 C++ 應用的持續交付。這就需要利用 Conan、Artifactory 和 Jenkins 這一類工具的整合來實現。


Artifactory 提供了 Jenkins 的外掛,支援 Jenkins Pipeline 中針對 Artifactory 和 Conan 的 DSL,可以很方便、直觀的實現 Artifactory 和 Conan 的各種操作。前面示例中的各種操作,都可以利用相應的 DSL 語句,整合到 Jenkins Pipeline 當中,如:

完整的 Jenkins Pipeline 程式碼在https://github.com/xingao0803/demo-poco-timer.git  中可以找到,供大家參考。


在 Jenkins 中執行該 Pipeline,就可以實現 timer 應用的自動化持續交付流水線:



總結


C/C++ 的依賴管理一直是軟體開發領域的痛點。本文介紹了當前得到廣泛關注和迅速發展的 C/C++ 依賴管理解決方案--Conan 的基本原理和應用流程。同時,利用 timer 應用示例,展示瞭如何基於 Conan、Artifactory 和 Jenkins,實現C/C++應用的持續交付流水線。


當然,本文中的示例只展示了 Conan 最基本的應用場景,大家可以根據相關資料,學習和研究 Conan 更多的案例。我們也會陸續推出後續文章,為大家進一步展示 Conan 的特性和應用方式。



文章作者:高欣,JFrog中國高階架構師



參考文獻

  • Conan 官網https://conan.io

  • JFrog 官網 http://www.jfrogchina.com

  • Artifactory 文件 https://www.jfrog.com/confluence/display/RTF/Welcome+to+Artifactory

  • Artifactory社群版 https://docs.conan.io/en/latest/uploading_packages/artifactory_ce.html

  • 示例程式碼 https://github.com/xingao0803/demo-poco-timer.git