Azure ArtifactsでC++ライブラリのパッケージ管理
前回はパイプラインを使ってライブラリを複数ターゲットにビルドしました。今回は、ビルドしたライブラリをメインのプログラムで使用するにはどうしたらいいかを考えます。
KAGURAでは、たくさんの外部ライブラリが使用されていますが、中でも、boost、OpenCV、wxWidgetsといった大きなライブラリはビルドに10分以上かかることもあり、全部のライブラリをビルドすると大変なことになります。
ライブラリを毎回ビルドする必要は無いので、ビルドしたものをパッケージにしてどこかに置いておき、本体のビルドのときに利用することで、ビルド時間を短縮したいです。
ライブラリは数カ月に1回くらいの頻度でバージョンアップします。バージョン間で互換性が無いこともあるので、ライブラリを利用するプログラム側は、どのバージョンを利用するのかを明確に指定して、正しいバージョンを取得する必要があります。
Azure DevOpsでこのようなことを実現するのに使えるのが、Azure Artifactsです。
Artifactsは「Maven、npm、NuGetのパッケージを作成、共有可能」とあります。MavenはJava、npmはNode.js、NuGetは.NET用のパッケージ管理システムです。
C++はどうしたらいいの・・・?
よく見ると、Universal Packageという見慣れないものがありました。
Universal Packageはファイルの種類に関係なく、何でもパッケージにできるもののようです。C++のライブラリはUniversal Packageにすると良さそうです。
Universal Packageについては、Qiitaにもまとめてみました。
Artifactsにフィードを作成
適当にtestfeedという名前を付けました。
最初はフィードに何もない状態です。
Pipelines→Artifactsにアップロード
zlibをビルドするジョブにUniversalPackagesタスクを追加します。vstsFeedPublish
で作成したフィード名を指定します。
# build-zlib.yml (一部) - task: UniversalPackages@0 displayName: Universal Publish inputs: command: publish publishDirectory: '$(Build.ArtifactStagingDirectory)' vstsFeedPublish: 'testfeed' vstsFeedPackagePublish: 'zlib.$(PlatformName)' packagePublishDescription: 'zlib-1.2.11 for $(PlatformName)'
vstsFeedPackagePublish
はパッケージ名です。各環境用にパッケージの名前を変えたいので、PlatformNameという変数を作ってパッケージ名に使いました。テンプレートの呼び出し元で変数を定義しています。
# azure-pipelines.yml(一部) jobs: - job: 'ubuntu1604' variables: PlatformName: ubuntu pool: vmImage: 'ubuntu-16.04' steps: - template: build-zlib.yml - job: 'win2016_vs2017' variables: PlatformName: win pool: vmImage: 'vs2017-win2016' steps: - template: build-zlib.yml - job: 'macos1013' variables: PlatformName: mac pool: vmImage: 'macOS-10.13' steps: - template: build-zlib.yml
成功するとフィードに追加されます。 パッケージのバージョンを指定しなかったので、バージョン番号は自動で0.0.1とつけられました。ビルドのたびに自動でバージョン番号が上がっていくようです。
Artifacts→Pipelinesにダウンロード
次に、ビルドしたライブラリを使う側を考えます。
まずはローカルで動作確認。ArtifactsからローカルにUniversal PackageをダウンロードするにはVSTS CLIを使います。
> vsts package universal download ` --instance "https://dev.azure.com/{myorg}" ` --feed "testfeed" ` --name "zlib.ubuntu" ` --version "0.0.1" ` --path thirdparty
どこでもいいですが、ダウンロードしたライブラリはthirdpartyディレクトリに配置することにしました。
zlibを使用するプログラムのサンプルとして、次のようなzlibのバージョンを表示するだけのC++コードがあるとします。
// main.cpp #include <iostream> #include <zlib.h> int main() { std::cout << "Hello zlib: " << zlibVersion() << std::endl; return 0; }
これをビルドするCMakeの設定ファイルはこのようになります。
# CMakeLists.txt cmake_minimum_required (VERSION 3.0) project(hello) include_directories( ${CMAKE_CURRENT_LIST_DIR}/thirdparty/include ) if(MSVC) set(ZLIB_LIBRARY ${CMAKE_CURRENT_LIST_DIR}/thirdparty/lib/zlibstatic.lib) else() set(ZLIB_LIBRARY ${CMAKE_CURRENT_LIST_DIR}/thirdparty/lib/libz.a) endif() add_executable(hello main.cpp) target_link_libraries(hello ${ZLIB_LIBRARY} ) install(TARGETS hello RUNTIME DESTINATION bin )
以上のことをパイプラインで実行するには、CMakeのビルドの前に「Artifactsからライブラリをダウンロードしてthirdpartyディレクトリに配置する」というタスクを追加する必要があります。
設定ファイルはこのようになります。
# build-hello.yml steps: - task: UniversalPackages@0 displayName: 'Universal download' inputs: command: download downloadDirectory: '$(Build.SourcesDirectory)/thirdparty' vstsFeed: 'testfeed' vstsFeedPackage: 'zlib.$(PlatformName)' vstsPackageVersion: 0.0.1 - task: CMake@1 inputs: cmakeArgs: > -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory) $(Build.SourcesDirectory) - task: CMake@1 inputs: cmakeArgs: > --build ./ --config Release --target install - task: PublishBuildArtifacts@1 inputs: pathtoPublish: $(Build.ArtifactStagingDirectory) artifactName: hello_zlib_$(PlatformName)
# azure-pipelines.yml trigger: - master jobs: - job: 'ubuntu1604' variables: PlatformName: ubuntu pool: vmImage: 'ubuntu-16.04' steps: - template: build-hello.yml - job: 'win2016_vs2017' variables: PlatformName: win pool: vmImage: 'vs2017-win2016' steps: - template: build-hello.yml - job: 'macos1013' variables: PlatformName: mac pool: vmImage: 'macOS-10.13' steps: - template: build-hello.yml
ビルドを実行すると・・・
# 実行結果 > ./hello Hello zlib: 1.2.11
できました。
まとめ
- ライブラリのビルド生成物をArtifactsに保存し、別のパイプラインのビルドで利用することができました。
- C++のライブラリのパッケージ管理には、Universal Packageが使えます。
これでKAGURAのCI/CD化に向けて、どういう手順でやっていけばいいか大体分かりました。Azure DevOpsは特に難しいことはなく、KAGURAの自動ビルドに導入できそうです。
ただ、Azure DevOps自体は簡単ですが、今までローカルで構築してきた複雑なビルド手順を整理して自動化するのはちょっと気が遠くなるなあ。
次に新しいプロジェクトを起ち上げるときは、最初っからCI/CDでやるべきと思いました。