1.持續集成 (CI) ?
- 這是一個高級主題,需要具備 Conan 的基礎知識。請先閱讀并練習用戶教程。
- 本節面向設計和實施涉及 Conan 包的生產 CI 管道的 DevOps 和構建工程師。如果不是這種情況,您可以跳過本節。
持續集成 (CI) 對不同用戶和組織有不同的含義。在本教程中,我們將涵蓋用戶更改其包的源代碼并希望自動為這些包構建新二進制文件,以及計算這些新包更改是否能夠順利集成或破壞組織主要產品的場景。
在本教程中,我們將使用這個小型項目,該項目使用多個包(默認是靜態庫)來構建兩個應用程序:一個視頻游戲和一個地圖查看器實用程序。游戲和地圖查看器是我們的最終“產品”,即分發給用戶的內容:
game/1.0 -> engine/1.0 -> (ai/1.0, graphics/1.0, mathlib/1.0)
mapviewer/1.0 -> graphics/1.0
依賴圖中的所有包都使用版本范圍聲明其直接依賴關系。例如,game
包含 requires("engine/[>=1.0 <2]")
,因此依賴項的新的補丁和次要版本將自動被使用,而無需修改配方。
注意:重要說明
- 本節是動手實踐教程。旨在通過復制命令在您的機器上重現。
- 本教程展示了解決 CI 問題的一些工具、良好實踐和常用方法。但這并非唯一的方式。不同的組織可能有不同的需求和優先級、不同的構建服務能力和預算、不同的規模等。所介紹的原則和實踐可能需要調整。
- 如有任何問題或反饋,請在 https://github.com/conan-io/conan/issues 提交新工單。
- 然而,一些原則和最佳實踐對所有方法都是通用的。例如,包的不可變性、使用倉庫升級(promotions)而不是使用通道(channel)來實現此目的等都是應遵循的良好實踐。
1.1 包與產品管道
當開發人員對某個包的源代碼進行更改時,我們將整個系統的 CI 視為由兩個不同的部分或管道組成:包管道和產品管道。
- 包管道 (Packages Pipeline):負責在單個包的代碼更改時構建該包。如果需要,它會為不同的配置(平臺、架構、構建類型等)構建它。
- 產品管道 (Products Pipeline):負責構建組織的主要“產品”(實現最終應用程序或可交付成果的包),并確保依賴項中的更改和新版本能夠正確集成,必要時重建依賴圖中的任何中間包。
思路是,如果某個開發人員對 ai
包進行了更改,產生了新的 ai/1.1.0
版本,包管道將首先構建這個新版本。但這個新版本可能會意外破壞或需要重建某些消費者包。如果我們的組織主要產品是 game/1.0
和 mapviewer/1.0
,那么產品管道可以被觸發,在這種情況下,它將重建受更改影響的 engine/1.0
和 game/1.0
,而所有其他包將保持不變。
1.2 倉庫與升級 (Repositories and Promotions)
多個服務器端倉庫的概念對于 CI 非常重要。在本教程中,我們將使用 3 個倉庫:
develop
:這是開發人員在其機器上配置的主要倉庫,用于conan install
依賴項并進行工作。因此,它預期相當穩定,類似于 git 中的共享“develop”分支,并且該倉庫應包含組織預定義平臺的預編譯二進制文件,這樣開發人員和 CI 就不需要反復--build=missing
并從源代碼構建。packages
:此倉庫將用于臨時上傳由“包管道”構建的包,避免直接上傳到develop
倉庫造成干擾,直到這些包完全驗證通過。products
:此倉庫將用于臨時上傳由“產品管道”構建的包,同時構建和測試新的依賴項更改不會破壞主要“產品”。
升級 (Promotions) 是使包從一個管道可用于另一個管道的機制。將上述包管道和產品管道與倉庫連接起來,將有兩個升級步驟:
- 當包管道為單個包構建了所有不同配置的所有二進制文件并上傳到
packages
倉庫后,該包的新版本和更改可以被視為“正確”并升級(復制)到products
倉庫。 - 當產品管道已從源代碼構建了所有需要重新構建的包(因為
products
倉庫中有新的包版本),并檢查了組織的“產品”(如game/1.0
和mapviewer/1.0
)沒有被破壞后,這些包就可以從products
倉庫升級(復制)到develop
倉庫,供所有其他開發人員和 CI 使用。
注意:
- 不可變性 (Immutability) 在包管理和 DevOps 中很重要。強烈反對修改通道 (channel) 來實現升級,請參閱包升級。
- 版本控制方法很重要。本教程將遵循默認的 Conan 版本控制方法,詳情請參閱此處。
本教程僅模擬開發流程。在生產系統中,還會有其他倉庫和升級步驟,例如用于 QA 團隊的測試倉庫,以及最終用戶的發布倉庫,這樣包在通過驗證后可以從 develop
升級到 testing
再到 release
。有關升級的更多信息,請閱讀包升級。
讓我們開始教程,進入下一節進行項目設置:
1.2.1 項目設置 (Project setup)
本教程所需的代碼位于 examples2
倉庫中,克隆它并進入文件夾:
$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/ci/game
服務器倉庫設置 (Server repositories setup)
我們需要在同一個服務器上有 3 個不同的倉庫。請確保您有一個正在運行的 Artifactory 實例可用。您可以從下載頁面下載免費的 Artifactory CE 并在自己的計算機上運行,或者使用 Docker:
$ docker run --name artifactory -d -p 8081:8081 -p 8082:8082 releases-docker.jfrog.io/jfrog/artifactory-cpp-ce:7.63.12
# 可以用 "docker stop artifactory" 停止
啟動后,您可以訪問 http://localhost:8081/
查看(用戶:“admin”,密碼:“password”)。如果您有另一個可用的 Artifactory,也可以使用,前提是您可以在那里創建新倉庫。
第一步,登錄 Web UI 并創建 3 個不同的本地倉庫,分別命名為 develop
、packages
和 products
。
然后,根據 project_setup.py
文件,需要配置以下環境變量。請定義 ARTIFACTORY_URL
、ARTIFACTORY_USER
和/或 ARTIFACTORY_PASSWORD
(如果需要)以適應您的設置:
# TODO: 這必須由用戶配置
SERVER_URL = os.environ.get("ARTIFACTORY_URL", "http://localhost:8081/artifactory/api/conan")
USER = os.environ.get("ARTIFACTORY_USER", "admin")
PASSWORD = os.environ.get("ARTIFACTORY_PASSWORD", "password")
初始依賴圖 (Initial dependency graph)
警告:
- 項目的初始化將刪除服務器上
develop
、products
和packages
三個倉庫的內容。 examples2/ci/game
文件夾包含一個.conanrc
文件,該文件定義了一個本地緩存,因此在本教程中執行的命令不會污染或更改您的主 Conan 緩存。
$ python project_setup.py
這將執行幾個任務:清理服務器倉庫,為依賴圖創建初始的 Debug 和 Release 二進制文件,將它們上傳到 develop
倉庫,然后清理本地緩存。請注意,在此示例中,我們為了方便使用 Debug 和 Release 作為不同的配置,但在實際情況中,這些將是不同的配置,例如 Windows/X86_64、Linux/x86_64、Linux/armv8 等,并在不同的計算機上運行。
設置完成后,可以檢查到定義了 3 個遠程倉庫,但只有 develop
遠程是啟用的,并且本地緩存中沒有包:
$ conan remote list
products: http://localhost:8081/artifactory/api/conan/products [Verify SSL: True, Enabled: False]
develop: http://localhost:8081/artifactory/api/conan/develop [Verify SSL: True, Enabled: True]
packages: http://localhost:8081/artifactory/api/conan/packages [Verify SSL: True, Enabled: False]$ conan list *
Found 0 pkg/version recipes matching * in local cache
Local Cache
WARN: There are no matching recipe references
重要提示: 遠程倉庫的順序很重要。如果啟用了 products
倉庫,它將比 develop
具有更高的優先級,因此如果它包含新版本,將從那里獲取。
服務器 develop
倉庫中的這個依賴圖是我們教程的起點,被假定為項目的功能性和穩定的“開發”狀態,開發人員可以 conan install
以在任何不同的包上工作。
1.2.2 包管道 (Packages Pipeline)
包管道負責在開發人員向組織倉庫的源代碼提交更改時,為不同的配置和平臺構建、創建和上傳包的二進制文件。例如,如果開發人員對 ai
包進行了一些更改,改進了庫的某些功能,并將版本提升到 ai/1.1.0
。如果組織需要支持 Windows 和 Linux 平臺,那么包管道將在認為更改有效之前為新的 ai/1.1.0
構建 Windows 和 Linux 的二進制文件。如果某些配置在特定平臺下構建失敗,通常認為更改無效并停止處理這些更改,直到代碼被修復。
對于包管道,我們將從 ai
配方中的簡單源代碼更改開始,模擬對 ai
包的改進,為我們的游戲提供更好的算法。
讓我們對 ai
包進行以下更改:
- 更改
ai/src/ai.cpp
函數的實現,將消息從Some Artificial
改為SUPER BETTER Artificial
。 - 將
ai/include/ai.h
中的默認intelligence=0
值更改為新的intelligence=50
。 - 最后,提升版本號。由于我們對包的公共頭文件進行了更改,建議提升次要版本號,因此讓我們編輯
ai/conanfile.py
文件并將version = "1.1.0"
定義在那里(而不是之前的 1.0)。請注意,如果我們對ai
公共 API 進行了破壞性更改,建議改為更改主版本號并創建新的 2.0 版本。
包管道將負責為新的 ai/1.1.0
構建不同的包二進制文件,并將它們上傳到 packages
二進制倉庫,以避免干擾或對其他開發人員和 CI 作業造成潛在問題。
如果管道成功,它將把這些包升級(復制)到 products
二進制倉庫,否則停止。
在構建 ai/1.1.0
的這些二進制包時,需要考慮幾個方面。以下教程小節以遞增的復雜性解釋了相同的工作。
注意 所有命令都可以在倉庫的 run_example.py
文件中找到。該文件主要供維護者和測試使用,但在出現問題時可能作為參考。
包管道:單一配置 (Package pipeline: single configuration)
我們將從最簡單的情況開始,即我們只需要構建 1 種配置,并且該配置可以在當前的 CI 機器上構建。
正如我們在介紹不同服務器二進制倉庫時所描述的,想法是包構建默認只使用 develop
倉庫,該倉庫對于開發人員和 CI 作業被認為是穩定的。
此管道從干凈狀態開始,緩存中沒有包,并且只啟用了 develop
倉庫。
在這種配置下,CI 作業可以簡單地執行:
$ cd ai
$ conan create . --build="missing:ai/*"
...
ai/1.1.0: SUPER BETTER Artificial Intelligence for aliens (Release)!
ai/1.1.0: Intelligence level=50
注意 --build="missing:ai/*"
在某些情況下可能不是完全必要的,但在其他情況下可以節省時間。例如,如果開發人員只更改了倉庫的 README,根本沒有提升版本號,Conan 將不會生成新的配方修訂版,并將其檢測為無操作,從而避免不必要地從源代碼重建二進制文件。
如果我們處于單一配置場景并且構建正確,對于這種簡單情況,我們不需要升級,只需將構建的包直接上傳到 products
倉庫就足夠了,產品管道稍后會獲取它。
# 我們不想干擾開發人員或 CI,上傳到 products
$ conan remote enable products
$ conan upload "ai*" -r=products -c
$ conan remote disable products
這是一個非常簡單的場景,讓我們轉向更現實的場景:需要構建多個配置。
包管道:多配置 (Package pipeline: multi configuration)
在上一節中,我們只構建了 1 種配置。本節將涵蓋需要構建超過 1 種配置的情況。為了方便起見,我們將在這里使用 Release 和 Debug 配置,但在實際情況下,這些配置更像是 Windows、Linux、OSX,為不同的架構構建,交叉構建等。
讓我們開始清理緩存:
$ conan remove "*" -c # 確保沒有上次運行的包
我們將在計算機上順序創建 2 種配置的包,但請注意這些通常會在不同的計算機上運行,因此 CI 系統通常會并行啟動不同配置的構建。
# Release 構建 (Listing 1)
$ cd ai # 如果尚未在 "ai" 文件夾內
$ conan create . --build="missing:ai/*" -s build_type=Release --format=json > graph.json
$ conan list --graph=graph.json --graph-binaries=build --format=json > built.json
$ conan remote enable packages
$ conan upload -l=built.json -r=packages -c --format=json > uploaded_release.json
$ conan remote disable packages# Debug 構建 (Listing 2)
$ conan create . --build="missing:ai/*" -s build_type=Debug --format=json > graph.json
$ conan list --graph=graph.json --graph-binaries=build --format=json > built.json
$ conan remote enable packages
$ conan upload -l=built.json -r=packages -c --format=json > uploaded_debug.json
$ conan remote disable packages
當 Release 和 Debug 配置都成功完成后,我們將在倉庫中有這些包:
當 ai/1.1.0
的所有不同二進制文件都已正確構建后,包管道可以認為其工作成功,并決定升級這些二進制文件。但需要進一步的包構建和檢查,因此包管道不是將它們升級到 develop
倉庫,而是可以將它們升級到 products
二進制倉庫。由于所有其他開發人員和 CI 都使用 develop
倉庫,在此階段也不會破壞任何人:
# 從 packages 升級到 product (Listing 3)
# 聚合包列表
$ conan pkglist merge -l uploaded_release.json -l uploaded_debug.json --format=json > uploaded.json
$ conan remote enable packages
$ conan remote enable products
# 使用 Conan download/upload 命令進行升級
# (慢,可以使用 art:promote 自定義命令改進)
$ conan download --list=uploaded.json -r=packages --format=json > promote.json
$ conan upload --list=promote.json -r=products -c
$ conan remote disable packages
$ conan remote disable products
第一步使用 conan pkglist merge
命令將“Release”和“Debug”配置的包列表合并為一個 uploaded.json
包列表。此列表將用于運行升級。
在此示例中,我們使用了緩慢的 conan download
+ conan upload
升級方式。使用 conan art:promote
擴展命令可以更高效。
運行升級后,服務器中將有以下包:
總結:
- 我們構建了 2 種不同的配置,Release 和 Debug(可以是 Windows/Linux 或其他),并將它們上傳到
packages
倉庫。 - 當所有配置的所有包二進制文件都成功構建后,我們將它們從
packages
升級到products
倉庫,使它們可用于產品管道。 - 在包創建過程中捕獲了包列表,并合并為一個列表來運行升級。
我們還沒有考慮的一個方面是,在構建過程中 ai/1.1.0
的依賴項可能會發生變化。轉到下一節,了解如何使用鎖定文件實現更一致的多配置構建。
包管道:使用鎖定文件的多配置 (Package pipeline: multi configuration using lockfiles)
在之前的示例中,我們為 ai/1.1.0
構建了 Debug 和 Release 包二進制文件。在現實世界場景中,要構建的二進制文件將用于不同的平臺(Windows、Linux、嵌入式),不同的架構,并且通常無法在同一臺機器上構建,需要不同的計算機。
前面的示例有一個重要的假設:ai/1.1.0
的依賴項在構建過程中完全不會改變。在許多場景中,這個假設不成立,例如,如果有任何其他并發的 CI 作業,并且一個成功的作業在 develop
倉庫中發布了一個新的 mathlib/1.1
版本。
那么有可能 ai/1.1.0
的一個構建,例如在 Linux 服務器上運行的較早開始,使用先前的 mathlib/1.0
版本作為依賴項,而 Windows 服務器稍后啟動,它們的構建將使用最近的 mathlib/1.1
版本作為依賴項。這是一個非常不希望的情況,同一個 ai/1.1.0
版本的二進制文件使用了不同的依賴項版本。這可能導致后續的圖解析問題,或者更糟的是,發布后不同平臺的行為不同。
避免這種依賴項差異的方法是強制使用相同的依賴項版本和修訂版,這可以通過使用鎖定文件 (lockfiles) 來實現。
創建和應用鎖定文件相對簡單。創建和升級配置的過程將與上一節相同,只是應用了鎖定文件。
創建鎖定文件 (Creating the lockfile)
讓我們像往常一樣確保我們從干凈的狀態開始:
$ conan remove "*" -c # 確保沒有上次運行的包
然后我們可以創建鎖定文件 conan.lock
:
# 為 Release 配置捕獲一個鎖定文件
$ conan lock create . -s build_type=Release --lockfile-out=conan.lock
# 擴展鎖定文件以覆蓋 Debug 配置(如果存在特定于 Debug 的依賴項)
$ conan lock create . -s build_type=Debug --lockfile=conan.lock --lockfile-out=conan.lock
請注意,不同的配置、使用不同的配置文件或設置可能會導致不同的依賴圖。一個鎖定文件可用于鎖定不同的配置,但重要的是要迭代不同的配置/配置文件并將它們的信息捕獲到鎖定文件中。
注意: conan.lock
是默認參數,如果存在 conan.lock
文件,它可能會被 conan install/create
和其他圖命令自動使用。這可以簡化許多命令,但本教程為了清晰和教學原因展示了完整的顯式命令。
conan.lock
文件可以檢查,它將類似于:
{"version": "0.5","requires": ["mathlib/1.0#f2b05681ed843bf50d8b7b7bdb5163ea%1724319985.398"],"build_requires": [],"python_requires": [],"config_requires": []
}
如我們所見,它鎖定了 mathlib/1.0
依賴項的版本和修訂版。
有了鎖定文件,創建不同的配置完全相同,但向 conan create
步驟提供 --lockfile=conan.lock
參數,它將保證無論是否存在新的 mathlib/1.1
版本或新的修訂版,都將始終使用確切的 mathlib/1.0#f2b05681ed843bf50d8b7b7bdb5163ea
依賴項。以下構建可以并行啟動但在不同時間執行,它們仍將始終使用相同的 mathlib/1.0
依賴項:
# Release 構建 (Listing 4)
$ cd ai # 如果尚未在 "ai" 文件夾內
$ conan create . --build="missing:ai/*" --lockfile=conan.lock -s build_type=Release --format=json > graph.json
$ conan list --graph=graph.json --graph-binaries=build --format=json > built.json
$ conan remote enable packages
$ conan upload -l=built.json -r=packages -c --format=json > uploaded_release.json
$ conan remote disable packages# Debug 構建 (Listing 5)
$ conan create . --build="missing:ai/*" --lockfile=conan.lock -s build_type=Debug --format=json > graph.json
$ conan list --graph=graph.json --graph-binaries=build --format=json > built.json
$ conan remote enable packages
$ conan upload -l=built.json -r=packages -c --format=json > uploaded_debug.json
$ conan remote disable packages
注意與前一示例的唯一修改是添加了 --lockfile=conan.lock
。升級也將與之前相同:
# 從 packages 升級到 product (Listing 6)
# 聚合包列表
$ conan pkglist merge -l uploaded_release.json -l uploaded_debug.json --format=json > uploaded.json
$ conan remote enable packages
$ conan remote enable products
# 使用 Conan download/upload 命令進行升級
# (慢,可以使用 art:promote 自定義命令改進)
$ conan download --list=uploaded.json -r=packages --format=json > promote.json
$ conan upload --list=promote.json -r=products -c
$ conan remote disable packages
$ conan remote disable products
最終結果將與上一節相同,但這次保證了 Debug 和 Release 二進制文件都是使用完全相同的 mathlib
版本構建的:
現在我們在 products
倉庫中有了新的 ai/1.1.0
二進制文件,我們可以認為包管道已完成,并進入下一節,構建和檢查我們的產品,看看這個新的 ai/1.1.0
版本是否能正確集成。
1.2.3 產品管道 (Products Pipeline)
產品管道回答了一個更具挑戰性的問題:我的“產品”是否能用包的新版本正確構建?以及它們的依賴項?這是真正的“持續集成”部分,其中不同包中的更改會針對組織重要的產品進行測試,以檢查它們是否能干凈地集成或破壞。
讓我們繼續上面的例子,如果我們現在有一個新的 ai/1.1.0
包,它會破壞現有的 game/1.0
和/或 mapviewer/1.0
應用程序嗎?是否需要從源代碼重新構建那些直接或間接依賴 ai
包的現有包?在本教程中,我們將 game/1.0
和 mapviewer/1.0
視為我們的“產品”,但稍后將進一步解釋這個概念,特別是為什么從“產品”角度思考很重要,而不是試圖在 CI 中顯式地自上而下建模依賴關系。
在我們的示例中,這個產品管道的本質是,上傳到 products
倉庫的新 ai/1.1.0
版本會自動落入定義的有效版本范圍內,并且我們的版本控制方法意味著這樣的次要版本提升將需要從源代碼構建其消費者,在本例中是 engine/1.0
和 game/1.0
,并且按照特定的順序,而所有其他包將保持不變。知道哪些包需要從源代碼構建以及按什么順序,并執行該構建以檢查主要組織產品是否仍然能使用新的依賴項版本正常工作,是產品管道的責任。
什么是產品 (What are the products)
產品是組織(公司、團隊、項目)交付的最終軟件產物,為這些產物的用戶提供一些價值。在這個例子中,我們將 game/1.0
和 mapviewer/1.0
視為“產品”。請注意,可以定義同一包的不同版本作為產品,例如,如果我們必須為不同客戶維護不同版本的游戲,我們可以有 game/1.0
和 game/2.3
以及 mapviewer
的不同版本作為產品。
“產品”方法除了關注業務價值的優勢外,還有另一個非常重要的優勢:它避免了在 CI 層對依賴圖進行建模。一個常見的嘗試是嘗試建模反向依賴模型,即在 CI 級別表示給定包的依賴者或消費者。在我們的示例中,如果我們為構建 ai
包配置了一個作業,我們可以有另一個用于 engine
包的作業,該作業在 ai
作業之后觸發,以某種方式在 CI 系統中配置這種拓撲結構。
但是這種方法根本無法擴展,并且有非常重要的限制:
- 上面的例子相對簡單,但實際上依賴圖可以有更多的包,甚至幾百個,這使得在 CI 中定義所有包之間的依賴關系非常繁瑣且容易出錯。
- 依賴關系隨時間演變,使用新版本,一些依賴關系被移除,新的依賴關系被添加。在 CI 級別建模的倉庫之間的簡單關系可能導致非常低效、緩慢且耗時的 CI,如果不是脆弱的并且由于某些依賴關系更改而不斷中斷的話。
- 發生在依賴圖下游的組合性質,一個相對穩定的頂層依賴,例如
mathlib/1.0
可能被多個消費者使用,如ai/1.0
、ai/1.1
、ai/1.2
,而每個消費者又可能被多個engine
不同版本使用,依此類推。僅構建消費者的最新版本在許多情況下是不夠的,而構建所有版本的成本將極其高昂。 - “反向”依賴模型,即詢問給定包的“依賴者”在實踐中極具挑戰性,特別是在像 Conan 這樣的去中心化方法中,包可以存儲在不同的倉庫中,包括不同的服務器,并且沒有所有包及其關系的中央數據庫。此外,“反向”依賴模型與直接模型類似,是有條件的。由于依賴關系可以基于任何配置(設置、選項)進行條件化,反向也受限于相同的邏輯,并且這種邏輯也隨著每個新的修訂版和版本而演變和變化。
在 C 和 C++ 項目中,由于編譯模型涉及頭文件文本包含成為消費者二進制產物的一部分,以及原生產物鏈接模型,“產品”管道變得比其他語言更加必要和關鍵。
構建中間包的新二進制文件 (Building intermediate packages new binaries)
一個經常被問到的問題是,當消費者包針對新的依賴項版本構建時,它的版本會是什么。明確地說明我們的例子,我們定義需要重新構建 engine/1.0
包,因為它現在依賴于新的 ai/1.1.0
版本:
- 我們應該創建一個新的
engine/1.1
版本來構建以支持新的ai/1.1.0
嗎? - 或者我們應該保留
engine/1.0
版本?
答案在于二進制模型以及依賴項如何影響 package_id
。Conan 有一個二進制模型,它同時考慮了依賴項的版本、修訂版和 package_id
,以及不同的包類型(package_type
屬性)。
建議是將包版本與源代碼保持一致。如果 engine/1.0
是從其源代碼倉庫的特定提交/標簽構建的,并且該倉庫的源代碼根本沒有更改,那么擁有一個偏離源代碼的更改包版本會非常令人困惑。使用 Conan 二進制模型,我們將為 engine/1.0
擁有 2 個不同的二進制文件,具有 2 個不同的 package_id
。一個二進制文件將針對 ai/1.0
版本構建,另一個二進制文件將針對 ai/1.1.0
構建,類似于:
$ conan list engine:* -r=develop
engine/1.0
revisions
fba6659c9dd04a4bbdc7a375f22143cb (2024-08-22 09:46:24 UTC)
packages
2c5842e5aa3ed21b74ed7d8a0a637eb89068916e
info
settings
...
requires
ai/1.0.Z
graphics/1.0.Z
mathlib/1.0.Z
de738ff5d09f0359b81da17c58256c619814a765
info
settings
...
requires
ai/1.1.Z
graphics/1.0.Z
mathlib/1.0.Z
讓我們看看產品管道如何構建這樣的 engine/1.0
和 game/1.0
新二進制文件,使用新的依賴項版本。在以下小節中,我們將以增量方式呈現一個產品管道,與包管道相同。
產品管道:單一配置 (Products pipeline: single configuration)
在本節中,我們將實現一個非常基礎的產品管道,不進行分布式構建,不使用鎖定文件或構建多個配置。
主要思想是說明需要重新構建一些包,因為有一個新的 ai/1.1.0
版本可以被我們的主要產品集成。這個新的 ai
版本在 products
倉庫中,因為它已經被“包管道”成功構建。讓我們首先確保我們有一個干凈的環境,并定義了正確的倉庫:
# 首先清理本地 "build" 文件夾
$ pwd # 應該是 <path>/examples2/ci/game
$ rm -rf build # 清理臨時構建文件夾
$ mkdir build && cd build # 存放臨時文件
# 現在清理包并定義遠程倉庫
$ conan remove "*" -c # 確保沒有上次運行的包
# 注意:products 倉庫優先,具有更高優先級。
$ conan remote enable products
回想一下,products
倉庫比 develop
倉庫具有更高的優先級。這意味著 Conan 將首先在 products
倉庫中解析,如果它找到為定義的版本范圍有效的版本,它將停止在那里并返回該版本,而不會檢查 develop
倉庫(使用 --update
可以檢查所有倉庫,但速度較慢,并且使用正確的倉庫排序,這不是必需的)。
正如我們已經定義的,我們的主要產品是 game/1.0
和 mapviewer/1.0
,讓我們首先嘗試安裝和使用 mapviewer/1.0
:
$ conan install --requires=mapviewer/1.0
...
Requirements
graphics/1.0#24b395ba17da96288766cc83accc98f5 - Downloaded (develop)
mapviewer/1.0#c4660fde083a1d581ac554e8a026d4ea - Downloaded (develop)
mathlib/1.0#f2b05681ed843bf50d8b7b7bdb5163ea - Downloaded (develop)
...
Install finished successfully
# 激活環境并運行可執行文件
# 在 Windows 上使用 "conanbuild.bat && mapviewer"
$ source conanrun.sh && mapviewer
...
graphics/1.0: Checking if things collide (Release)!
mapviewer/1.0:serving the game (Release)!
如我們所見,mapviewer/1.0
根本不依賴于 ai
包,任何版本都不依賴。因此,如果我們安裝它,我們已經有一個預編譯的二進制文件,一切正常。
但如果我們現在對 game/1.0
嘗試同樣的操作:
$ conan install --requires=game/1.0
...
======== Computing necessary packages ========
...
ERROR: Missing binary: game/1.0:bac7cd2fe1592075ddc715563984bbe000059d4c
game/1.0: WARN: Cant find a game/1.0 package binary bac7cd2fe1592075ddc715563984bbe000059d4c for the configuration:
...
[requires]
ai/1.1.0#01a885b003190704f7617f8c13baa630
它會失敗,因為它將從 products
倉庫獲取 ai/1.1.0
,并且沒有針對這個新 ai
版本的 game/1.0
預編譯二進制文件。這是正確的,ai
是一個靜態庫,因此我們需要針對它重新構建 game/1.0
,讓我們使用 --build=missing
參數來執行:
$ conan install --requires=game/1.0 --build=missing
...
======== Computing necessary packages ========
Requirements
ai/1.1.0:8b108997a4947ec6a0487a0b6bcbc0d1072e95f3 - Download (products)
engine/1.0:de738ff5d09f0359b81da17c58256c619814a765 - Build
game/1.0:bac7cd2fe1592075ddc715563984bbe000059d4c - Build
graphics/1.0:8b108997a4947ec6a0487a0b6bcbc0d1072e95f3 - Download (develop)
mathlib/1.0:4d8ab52ebb49f51e63d5193ed580b5a7672e23d5 - Download (develop)
-------- Installing package engine/1.0 (4 of 5) --------
engine/1.0: Building from source
...
engine/1.0: Package de738ff5d09f0359b81da17c58256c619814a765 created
-------- Installing package game/1.0 (5 of 5) --------
game/1.0: Building from source
...
game/1.0: Package bac7cd2fe1592075ddc715563984bbe000059d4c created
Install finished successfully
注意 --build=missing
知道 engine/1.0
也需要一個新的二進制文件,這是因為它依賴于新的 ai/1.1.0
版本的結果。然后,Conan 以正確的順序繼續構建包,首先必須構建 engine/1.0
,因為 game/1.0
依賴于它。構建后,我們可以列出新構建的二進制文件,并查看它們如何依賴于新版本:
$ conan list engine:*
Local Cache
engine
engine/1.0
revisions
fba6659c9dd04a4bbdc7a375f22143cb (2024-09-30 12:19:54 UTC)
packages
de738ff5d09f0359b81da17c58256c619814a765
info
...
requires
ai/1.1.Z
graphics/1.0.Z
mathlib/1.0.Z$ conan list game:*
Local Cache
game
game/1.0
revisions
1715574045610faa2705017c71d0000e (2024-09-30 12:19:55 UTC)
packages
bac7cd2fe1592075ddc715563984bbe000059d4c
info
...
requires
ai/1.1.0#01a885b003190704f7617f8c13baa630:8b108997a4947ec6a0487a0b6bcbc0d1072e95f3
engine/1.0#fba6659c9dd04a4bbdc7a375f22143cb:de738ff5d09f0359b81da17c58256c619814a765
graphics/1.0#24b395ba17da96288766cc83accc98f5:8b108997a4947ec6a0487a0b6bcbc0d1072e95f3
mathlib/1.0#f2b05681ed843bf50d8b7b7bdb5163ea:4d8ab52ebb49f51e63d5193ed580b5a7672e23d5
新的 engine/1.0:de738ff5d09f0359b81da17c58256c619814a765
二進制文件依賴于 ai/1.1.Z
,因為它是一個靜態庫,它只需要為次要版本更改重新構建,而不需要為補丁版本重新構建。而新的 game/1.0
二進制文件將依賴于完整的精確 ai/1.1.0#revision:package_id
,并且也依賴于依賴于 ai/1.1.Z
的新的 engine/1.0:de738ff5d09f0359b81da17c58256c619814a765
新二進制文件。
現在可以運行游戲了:
# 激活環境并運行可執行文件
# 在 Windows 上使用 "conanbuild.bat && game"
$ source conanrun.sh && game
mathlib/1.0: mathlib maths (Release)!
ai/1.1.0: SUPER BETTER Artificial Intelligence for aliens (Release)!
ai/1.1.0: Intelligence level=50
graphics/1.0: Checking if things collide (Release)!
engine/1.0: Computing some game things (Release)!
game/1.0:fun game (Release)!
我們可以看到新的 game/1.0
二進制文件包含了 ai/1.1.0
的改進,并且正確地與 engine/1.0
的新二進制文件鏈接。
這是一個基本的“產品管道”,我們設法在必要時構建和測試我們的主要產品(回想一下 mapviewer
并沒有真正受到影響,因此根本不需要重建)。通常,生產“產品管道”會上傳構建的包到倉庫并運行新的升級到 develop
倉庫。但由于這是一個非常基礎和簡單的管道,讓我們稍等片刻,繼續更高級的場景。
產品管道:構建順序 (Products pipeline: the build-order)
上一節使用 --build=missing
在同一臺 CI 機器上構建所有必要的包。這并不總是可取的,甚至不可能,在許多情況下,更可取的是進行分布式構建,以實現更快的構建和更好地利用 CI 資源。最自然的構建負載分布是在不同的機器上構建不同的包。讓我們看看如何使用 conan graph build-order
命令實現這一點。
讓我們像往常一樣確保我們有一個定義了正確倉庫的干凈環境:
# 首先清理本地 "build" 文件夾
$ pwd # 應該是 <path>/examples2/ci/game
$ rm -rf build # 清理臨時構建文件夾
$ mkdir build && cd build # 存放臨時文件
$ conan remove "*" -c # 確保沒有上次運行的包
# 注意:products 倉庫優先,具有更高優先級。
$ conan remote enable products
我們將暫時忽略 mapviewer/1.0
產品,并專注于本小節中的 game/1.0
產品。第一步是計算“構建順序”,即需要構建的包列表以及順序。這是使用以下 conan graph build-order
命令完成的:
$ conan graph build-order --requires=game/1.0 --build=missing --order-by=recipe --reduce --format=json > game_build_order.json
注意幾個要點:
- 需要使用
--build=missing
,與上一節完全相同。未能提供預期的--build
策略和參數將導致不完整或錯誤的構建順序。 --reduce
參數會從結果順序中刪除所有沒有binary: Build
策略的元素。這意味著生成的“構建順序”不能與其他構建順序文件合并以聚合到單個文件中,這在存在多個配置和產品時很重要。--order-by
參數允許定義不同的順序,按“配方 (recipe)”或按“配置 (configuration)”。在本例中,我們使用--order-by=recipe
,旨在并行化每個配方的構建,這意味著對于給定包(如engine/1.0
)的所有可能的不同二進制文件都應該首先構建,然后才能構建engine/1.0
的任何消費者。
生成的 game_build_order.json
如下所示:
{"order_by": "recipe","reduced": true,"order": [[{"ref": "engine/1.0#fba6659c9dd04a4bbdc7a375f22143cb","packages": [[{"package_id": "de738ff5d09f0359b81da17c58256c619814a765","binary": "Build","build_args": "--requires=engine/1.0 --build=engine/1.0",}]]}],[{"ref": "game/1.0#1715574045610faa2705017c71d0000e","depends": ["engine/1.0#fba6659c9dd04a4bbdc7a375f22143cb"],"packages": [[{"package_id": "bac7cd2fe1592075ddc715563984bbe000059d4c","binary": "Build","build_args": "--requires=game/1.0 --build=game/1.0",}]]}]]
}
為方便起見,就像 conan graph info ... --format=html > graph.html
可以生成帶有 HTML 交互式依賴圖的文件一樣,conan graph build-order ... --format=html > build_order.html
可以生成上述 json 文件的 HTML 可視化表示。
生成的 json 包含一個 order
元素,它是一個列表的列表。這種安排很重要,頂層列表中的每個元素是一組可以并行構建的包,因為它們之間沒有任何關系。您可以將此列表視為“級別 (levels)”列表,在級別 0 中,有不依賴于任何正在構建的其他包的包,在級別 1 中有僅依賴于級別 0 中的元素的包,依此類推。
然后,最外層列表的順序很重要并且必須遵守。直到一個列表項中的所有包構建完成,才能開始下一個“級別”的構建。
使用 graph_build_order.json
文件中的信息,可以執行必要包的構建,就像上一節的 --build=missing
所做的那樣,但不是由我們直接管理。
從 json 中獲取參數,要執行的命令將是:
$ conan install --requires=engine/1.0 --build=engine/1.0
$ conan install --requires=game/1.0 --build=game/1.0
我們正在手動執行這些命令,但在實踐中,CI 中會有一個 for
循環來執行 json 輸出。此時我們想重點介紹 conan graph build-order
命令,但我們還沒有真正解釋構建是如何分布的。
另外請注意,在每個元素內部,有一個列表的列表,即 "packages"
部分,用于必須為特定配方針對不同配置構建的所有二進制文件。
現在讓我們看看如何計算多產品、多配置的構建順序。
產品管道:多產品多配置構建 (Products pipeline: multi-product multi-configuration builds)
在上一節中,我們計算了一個 conan graph build-order
,但有幾個簡化:我們沒有考慮 mapviewer
產品,并且我們只處理了 1 種配置。
在現實場景中,需要管理多個產品,最常見的情況是每個產品有多個配置。如果我們按順序構建這些不同的情況,速度會慢得多且效率低下,如果我們嘗試并行構建它們,很容易出現許多重復和不必要的相同包的構建,浪費資源甚至導致競爭條件或可追溯性問題。
為了避免這個問題,可以計算一個統一的“構建順序”,該順序聚合了為不同產品和配置計算的所有不同構建順序。
讓我們像往常一樣清理本地緩存并定義正確的倉庫:
# 首先清理本地 "build" 文件夾
$ pwd # 應該是 <path>/examples2/ci/game
$ rm -rf build # 清理臨時構建文件夾
$ mkdir build && cd build # 存放臨時文件
$ conan remove "*" -c # 確保沒有上次運行的包
# 注意:products 倉庫優先,具有更高優先級。
$ conan remote enable products
現在,我們將開始為 game/1.0
計算我們將在本教程中構建的 2 種不同配置(debug 和 release)的構建順序:
$ conan graph build-order --requires=game/1.0 --build=missing --order-by=recipe --format=json > game_release.json
$ conan graph build-order --requires=game/1.0 --build=missing --order-by=recipe -s build_type=Debug --format=json > game_debug.json
這些命令基本上與上一節相同,每個命令使用不同的配置并創建不同的輸出文件 game_release.json
和 game_debug.json
。這些文件將類似于之前的文件,但由于我們沒有使用 --reduce
參數(這很重要!),它們實際上將包含圖中所有元素的“構建順序”,即使只有一些包含 binary: Build
定義,而其他元素將包含其他 binary: Download|Cache|etc
。
現在,讓我們計算 mapviewer/1.0
的構建順序:
$ conan graph build-order --requires=mapviewer/1.0 --build=missing --order-by=recipe --format=json > mapviewer_release.json
$ conan graph build-order --requires=mapviewer/1.0 --build=missing --order-by=recipe -s build_type=Debug --format=json > mapviewer_debug.json
請注意,在生成的 mapviewer_xxx.json
構建順序文件中,mapviewer/1.0
只有一個元素包含 binary: Download
,因為實際上沒有其他包需要構建,并且由于 mapviewer
是一個靜態鏈接的應用程序,Conan 知道它可以“跳過”其依賴項的二進制文件。如果我們使用了 --reduce
參數,我們將得到一個空的順序。但這并不是問題,因為下一個最終步驟將真正計算需要構建的內容。
讓我們獲取所有 4 個不同的“構建順序”文件(2 個產品 x 每個產品 2 個配置),并將它們合并在一起:
$ conan graph build-order-merge --file=game_release.json --file=game_debug.json --file=mapviewer_release.json --file=mapviewer_debug.json --reduce --format=json > build_order.json
現在我們應用了 --reduce
參數來生成最終的 build_order.json
,該文件已準備好分發給構建代理,并且只包含那些需要構建的特定包:
{"order_by": "recipe","reduced": true,"order": [[{"ref": "engine/1.0#fba6659c9dd04a4bbdc7a375f22143cb","packages": [[{"package_id": "de738ff5d09f0359b81da17c58256c619814a765","filenames": ["game_release"],"build_args": "--requires=engine/1.0 --build=engine/1.0",},{"package_id": "cbeb3ac76e3d890c630dae5c068bc178e538b090","filenames": ["game_debug"],"build_args": "--requires=engine/1.0 --build=engine/1.0",}]]}],[{"ref": "game/1.0#1715574045610faa2705017c71d0000e","packages": [[{"package_id": "bac7cd2fe1592075ddc715563984bbe000059d4c","filenames": ["game_release"],"build_args": "--requires=game/1.0 --build=game/1.0",},{"package_id": "01fbc27d2c156886244dafd0804eef1fff13440b","filenames": ["game_debug"],"build_args": "--requires=game/1.0 --build=game/1.0",}]]}]],"profiles": {"game_release": {"args": ""},"game_debug": {"args": "-s:h=\"build_type=Debug\""},"mapviewer_release": {"args": ""},"mapviewer_debug": {"args": "-s:h=\"build_type=Debug\""}}
}
這個構建順序總結了必要的構建。首先需要為 engine/1.0
構建所有不同的二進制文件。這個配方包含 2 個不同的二進制文件,一個用于 Release,另一個用于 Debug。這些二進制文件屬于 packages
列表中的同一個元素,這意味著它們彼此不依賴,可以并行構建。每個二進制文件通過 "filenames": ["game_release"]
跟蹤其原始的構建順序文件,因此可以推斷出需要應用于它的配置文件。build_order.json
文件包含一個 profiles
部分,有助于恢復用于創建相應原始構建順序文件的配置文件和設置命令行參數。
然后,在 engine/1.0
的所有二進制文件構建完成后,就可以繼續為 game/1.0
構建不同的二進制文件。它也包含用于其 debug 和 release 配置的 2 個不同的二進制文件,可以并行構建。
在實踐中,這意味著類似以下內容:
# 這兩個可以并行執行
# (在不同的機器上,或不同的 Conan 緩存中)
$ conan install --requires=engine/1.0 --build=engine/1.0
$ conan install --requires=engine/1.0 --build=engine/1.0 -s build_type=Debug# 一旦 engine/1.0 構建完成,
# 就可以并行構建這兩個二進制文件(在不同的機器或緩存中)
$ conan install --requires=game/1.0 --build=game/1.0
$ conan install --requires=game/1.0 --build=game/1.0 -s build_type=Debug
在本節中,我們仍然忽略了一些重要的實現細節,這些細節將在接下來的章節中介紹。目標是專注于 conan graph build-order-merge
命令以及如何將不同的產品和配置合并到一個“構建順序”中。下一節將更詳細地展示如何使用鎖定文件保證依賴關系恒定,從而真正分發此構建順序。
產品管道:使用鎖定文件的分布式完整管道 (Products pipeline: distributed full pipeline with lockfiles)
本節將展示多產品、多配置分布式 CI 管道的完整實現。它將涵蓋重要的實現細節:
- 使用鎖定文件保證所有配置具有一致且固定的依賴關系集。
- 將構建的包上傳到
products
倉庫。 - 捕獲“包列表”并使用它們運行最終升級。
- 如何以編程方式迭代“構建順序”。
讓我們像往常一樣開始清理本地緩存并定義正確的倉庫:
# 首先清理本地 "build" 文件夾
$ pwd # 應該是 <path>/examples2/ci/game
$ rm -rf build # 清理臨時構建文件夾
$ mkdir build && cd build # 存放臨時文件
$ conan remove "*" -c # 確保沒有上次運行的包
# 注意:products 倉庫優先,具有更高優先級。
$ conan remote enable products
與我們在包管道中為確保在構建不同配置和產品時依賴關系完全相同類似,第一步是計算一個 conan.lock
鎖定文件,我們可以將其傳遞給不同的 CI 構建代理以強制執行相同的依賴關系集。這可以針對不同的產品和配置增量完成,最終聚合到單個 conan.lock
鎖定文件中。這種方法假設 game/1.0
和 mapviewer/1.0
將使用公共依賴項的相同版本和修訂版。
$ conan lock create --requires=game/1.0 --lockfile-out=conan.lock
$ conan lock create --requires=game/1.0 -s build_type=Debug --lockfile=conan.lock --lockfile-out=conan.lock
$ conan lock create --requires=mapviewer/1.0 --lockfile=conan.lock --lockfile-out=conan.lock
$ conan lock create --requires=mapviewer/1.0 -s build_type=Debug --lockfile=conan.lock --lockfile-out=conan.lock
注意: 回想一下,conan.lock
參數大多是可選的,因為那是默認的鎖定文件名。第一個命令可以輸入為 conan lock create --requires=game/1.0
。此外,所有命令,包括 conan install
,如果它們找到現有的 conan.lock
文件,它們將自動使用它,而無需顯式的 --lockfile=conan.lock
。本教程中的命令為了完整性和教學原因而完整顯示。
然后,我們可以為每個產品和配置計算構建順序。這些命令與上一節相同,唯一的區別是添加了 --lockfile=conan.lock
參數:
$ conan graph build-order --requires=game/1.0 --lockfile=conan.lock --build=missing --order-by=recipe --format=json > game_release.json
$ conan graph build-order --requires=game/1.0 --lockfile=conan.lock --build=missing -s build_type=Debug --order-by=recipe --format=json > game_debug.json
$ conan graph build-order --requires=mapviewer/1.0 --lockfile=conan.lock --build=missing --order-by=recipe --format=json > mapviewer_release.json
$ conan graph build-order --requires=mapviewer/1.0 --lockfile=conan.lock --build=missing -s build_type=Debug --order-by=recipe --format=json > mapviewer_debug.json
同樣,build-order-merge
命令將與之前的相同。在這種情況下,由于此命令并不真正計算依賴圖,因此不需要 conan.lock
參數,依賴關系不會被解析:
$ conan graph build-order-merge --file=game_release.json --file=game_debug.json --file=mapviewer_release.json --file=mapviewer_debug.json --reduce --format=json > build_order.json
到目前為止,這個過程幾乎與上一節相同,只是捕獲并使用了鎖定文件。現在,我們將解釋產品管道的“核心”:迭代構建順序、分發構建以及收集生成的構建包。
這將是一些 Python 代碼的示例,該代碼順序執行迭代(真實的 CI 系統會將構建分發到不同的代理并行執行):
build_order = open("build_order.json", "r").read()
build_order = json.loads(build_order)
to_build = build_order["order"]
pkg_lists = [] # 用于聚合上傳的包列表for level in to_build:for recipe in level: # 這可以并行執行ref = recipe["ref"]# 對于每個 ref,正在構建多個二進制包。# 這也可以并行完成。通常針對不同平臺,# 它們需要分發到不同的構建代理for packages_level in recipe["packages"]:# 這也可以并行執行for package in packages_level:build_args = package["build_args"]filenames = package["filenames"]build_type = "-s build_type=Debug" if any("debug" in f for f in filenames) else ""run(f"conan install {build_args} {build_type} --lockfile=conan.lock --format=json", file_stdout="graph.json")run("conan list --graph=graph.json --format=json", file_stdout="built.json")filename = f"uploaded{len(pkg_lists)}.json"run(f"conan upload -l=built.json -r=products -c --format=json", file_stdout=filename)pkg_lists.append(filename)
注意:
- 此代碼特定于
--order-by=recipe
構建順序。如果選擇--order-by=configuration
,json 會不同,需要不同的迭代方式。
上面的 Python 代碼正在執行以下任務:
- 對于構建順序中的每個包,發出一個
conan install --require=<pkg> --build=<pkg>
命令,并將此命令的結果存儲在graph.json
文件中。 conan list
命令將此graph.json
轉換為名為built.json
的包列表。請注意,此包列表實際上存儲了構建的包和必要的傳遞依賴項。這樣做是為了簡單起見,因為稍后這些包列表將用于運行升級,并且我們也希望升級在包管道中構建的依賴項,如ai/1.1.0
,而不是由該作業構建的。conan upload
命令將包列表上傳到products
倉庫。請注意,上傳首先檢查倉庫中已存在哪些包,如果它們已經存在,則避免代價高昂的傳輸。conan upload
命令的結果被捕獲到一個名為uploaded<index>.json
的新包列表中,我們將稍后累積該列表,該列表將用于最終升級。
在實踐中,這轉化為以下命令(您可以執行這些命令以繼續教程):
# engine/1.0 release
$ conan install --requires=engine/1.0 --build=engine/1.0 --lockfile=conan.lock --format=json > graph.json
$ conan list --graph=graph.json --format=json > built.json
$ conan upload -l=built.json -r=products -c --format=json > uploaded1.json# engine/1.0 debug
$ conan install --requires=engine/1.0 --build=engine/1.0 --lockfile=conan.lock -s build_type=Debug --format=json > graph.json
$ conan list --graph=graph.json --format=json > built.json
$ conan upload -l=built.json -r=products -c --format=json > uploaded2.json# game/1.0 release
$ conan install --requires=game/1.0 --build=game/1.0 --lockfile=conan.lock --format=json > graph.json
$ conan list --graph=graph.json --format=json > built.json
$ conan upload -l=built.json -r=products -c --format=json > uploaded3.json# game/1.0 debug
$ conan install --requires=game/1.0 --build=game/1.0 --lockfile=conan.lock -s build_type=Debug --format=json > graph.json
$ conan list --graph=graph.json --format=json > built.json
$ conan upload -l=built.json -r=products -c --format=json > uploaded4.json
此步驟之后,新構建的包將位于 products
倉庫中,我們將有 4 個 uploaded1.json
- uploaded4.json
文件。
簡化不同的 release 和 debug 配置,我們倉庫的狀態將類似于:
[圖示:倉庫狀態示意圖,顯示新構建的 engine 和 game 的 release/debug 二進制文件在 products 倉庫中]
我們現在可以將不同的 uploadedX.json
文件累積到一個包含所有內容的單個包列表 uploaded.json
中:
$ conan pkglist merge -l uploaded0.json -l uploaded1.json -l uploaded2.json -l uploaded3.json --format=json > uploaded.json
最后,如果一切順利,并且我們認為這套新版本和新包二進制文件已準備好供開發人員和其他 CI 作業使用,那么我們可以運行從 products
到 develop
倉庫的最終升級:
# 從 products 升級到 develop (Listing 8)
# 使用 Conan download/upload 命令進行升級
# (慢,可以使用 art:promote 自定義命令改進)
$ conan download --list=uploaded.json -r=products --format=json > promote.json
$ conan upload --list=promote.json -r=develop -c
我們最終的 develop
倉庫狀態將是:
[圖示:倉庫狀態示意圖,顯示所有包(包括新構建的 engine 和 game)都在 develop 倉庫中]
這個 develop
倉庫的狀態將具有以下行為:
- 安裝
game/1.0
或engine/1.0
的開發人員默認將解析到最新的ai/1.1.0
并使用它。他們將找到依賴項的預編譯二進制文件,并且可以繼續使用最新的依賴項集進行開發。 - 使用鎖定文件鎖定
ai/1.0
版本的開發人員和 CI 仍然能夠繼續工作,而不會破壞任何東西,因為新的版本和包二進制文件不會破壞或使先前存在的二進制文件失效。
此時,可能會提出如何處理 CI 中使用的鎖定文件的問題。請注意,conan.lock
現在包含鎖定的 ai/1.1.0
版本。可能有不同的策略,例如將此鎖定文件存儲在“產品”的 git 倉庫中,使開發人員在檢出這些倉庫時易于使用。然而,請注意,此鎖定文件匹配 develop
倉庫的最新狀態,因此檢出其中一個“產品”git 倉庫的開發人員對 develop
服務器倉庫執行 conan install
將自然解析到鎖定文件中存儲的相同依賴項。
至少在發布捆綁包中包含此鎖定文件是一個好主意,如果“產品”以某種方式捆綁(安裝程序、debian/rpm/choco/等包),則包含或附加到此捆綁發布以供最終用戶使用,使用的鎖定文件,這樣無論開發倉庫發生什么變化,以后都可以從發布信息中恢復這些鎖定文件。
最終說明 (Final remarks)
正如本 CI 教程介紹中所述,這并不打算成為您可以在組織中即插即用的“銀彈”CI 系統。到目前為止,本教程展示了一個開發人員的“快樂路徑”持續集成過程,以及他們作為更大產品一部分的包中的更改如何作為這些產品的一部分進行測試和驗證。
本 CI 教程的重點是介紹一些重要概念、良好實踐和工具,例如:
- 定義組織“產品”的重要性,即需要針對新創建的依賴項版本進行檢查和構建的主要可交付成果。
- 開發人員的新依賴項版本在驗證之前不應上傳到主開發倉庫,以免破壞其他開發人員和 CI 作業。
- 如何使用多個倉庫構建 CI 管道,以隔離未經驗證的更改和新版本。
- 如何使用
conan graph build-order
高效構建大型依賴圖,以及如何將不同配置和產品的構建順序合并在一起。 - 為什么在存在并發 CI 構建時需要鎖定文件。
- 版本控制的重要性,以及
package_id
在大型依賴圖中僅重建必要內容的作用。 - 不在 CI 管道中使用 user/channel 作為變量的動態限定符,而是使用不同的服務器倉庫。
- 當新包版本通過驗證時,跨服務器倉庫運行包升級(promotions)。
仍然有許多實現細節、策略、用例和錯誤場景尚未在本教程中涵蓋:
- 如何集成需要新的破壞性主版本的包的破壞性更改。
- 不同的版本控制策略,使用預發布版本,在特定情況下依賴版本或配方修訂版。
- 鎖定文件如何存儲和跨不同構建使用,是否適合持久化它們以及在哪里持久化。
- 不同的分支和合并策略、夜間構建、發布流程。
我們計劃擴展此 CI 教程,包括更多示例和用例。如果您有任何問題或反饋,請在 https://github.com/conan-io/conan/issues 創建工單。