はじめに
これまでRustのCIビルドはTravis CIやAppVeyorを使ってきましたが、Azure PipelinesにてLinux/Windows/macOSが全て賄えるとのことで試してみました。
すでに先行記事として「Azure Pipelines で Rust プロジェクトを CI する」があるためわざわざ記事にするほどでもないと思っていましたが、結構ハマりポイントがあったのでまとめておきます。
実際に適用したリポジトリは以下になります。
まだTravis CIと併用ですが安定してきたらAzure一本にするつもりです。
できること
- コミット毎にCIする
- タグを打ったらGitHubにリリースする
- ターゲットは以下
- Linux: x86_64-unknown-linux-musl
- Windows: x86_64-pc-windows-msvc
- macOS: x86_64-apple-darwin
スクリプト
Azure Pipelinesも他のCIと同様にYAML(azure-pipelines.yml
)で設定を書きます。まず全体を示して、そのあとポイントを解説していきます。また、各項目に関連する公式リファレンスへのリンクを張っておきます。
全体
trigger:
branches:
include:
- '*'
tags:
include:
- '*'
jobs:
- job: Linux
pool:
vmImage: 'ubuntu-16.04'
steps:
- script: |
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
echo '##vso[task.setvariable variable=PATH]$(PATH):$(HOME)/.cargo/bin'
displayName: Install rustup
- script: |
cargo install cargo2junit
rustup target add x86_64-unknown-linux-musl
sudo apt-get -qq install musl-tools
displayName: Install tools
- script: |
cargo test -- -Z unstable-options --format json | cargo2junit > results.xml
displayName: Run test
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'results.xml'
condition: succeededOrFailed()
- script: |
make release_lnx
cp *.zip $(Build.ArtifactStagingDirectory)
displayName: Build release binary
- task: PublishBuildArtifacts@1
inputs:
artifactName: 'Linux'
pathtoPublish: $(Build.ArtifactStagingDirectory)
- job: macOS
pool:
vmImage: 'macos-10.13'
steps:
- script: |
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
echo '##vso[task.setvariable variable=PATH]$(PATH):$(HOME)/.cargo/bin'
displayName: Install rustup
- script: |
cargo install cargo2junit
displayName: Install tools
- script: |
cargo test -- -Z unstable-options --format json | cargo2junit > results.xml
displayName: Run test
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'results.xml'
condition: succeededOrFailed()
- script: |
make release_mac
cp *.zip $(Build.ArtifactStagingDirectory)
displayName: Build release binary
- task: PublishBuildArtifacts@1
inputs:
artifactName: 'macOS'
pathtoPublish: $(Build.ArtifactStagingDirectory)
- job: Windows
pool:
vmImage: 'vs2017-win2016'
steps:
- script: |
curl -sSf -o rustup-init.exe https://win.rustup.rs
rustup-init.exe -y --default-toolchain stable
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
echo '##vso[task.setvariable variable=PATH]%PATH%;%USERPROFILE%\.cargo\bin'
displayName: Install rustup
- script: |
choco install -y zip
cargo install cargo2junit
displayName: Install tools
- script: |
cargo test -- -Z unstable-options --format json | cargo2junit > results.xml
displayName: Run test
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'results.xml'
condition: succeededOrFailed()
- script: |
make release_win
cp *.zip $(Build.ArtifactStagingDirectory)
displayName: Build release binary
- task: PublishBuildArtifacts@1
inputs:
artifactName: 'Windows'
pathtoPublish: $(Build.ArtifactStagingDirectory)
- job: Release
dependsOn:
- Linux
- macOS
- Windows
steps:
- task: DownloadBuildArtifacts@0
inputs:
downloadType: 'specific'
itemPattern: '**'
downloadPath: $(Build.ArtifactStagingDirectory)
- script: ls $(Build.ArtifactStagingDirectory)
- task: GitHubRelease@0
inputs:
gitHubConnection: 'dalance'
repositoryName: 'dalance/procs'
tagSource: 'auto'
releaseNotesSource: 'input'
releaseNotes: '[Changelog](https://github.com/dalance/procs/blob/master/CHANGELOG.md)'
assets: '$(Build.ArtifactStagingDirectory)/*/*'
assetUploadMode: 'replace'
isDraft: false
isPreRelease: false
addChangeLog: false
displayName: Release to GitHub
全体構成
全体的には4つのジョブ(Linux/macOS/Windows/Release)で構成されています。まず3つのビルドジョブが実行されてから最後にReleaseを実行したいので、dependsOn
を使って依存関係を指定します。
- job: Linux
- job: macOS
- job: Windows
- job: Release
dependsOn:
- Linux
- macOS
- Windows
trigger
triggerはCIの実行条件を指定します。
trigger:
branches:
include:
- '*'
tags:
include:
- '*'
ここでは全てのブランチ、全てのタグに対して実行するようにしています。
デフォルト(triggerを指定しなかった場合)ではタグに対して実行されません。
また、ここの設定をミスると自動で実行されなくなったりします。
最後に実行した際のtrigger設定で監視しているような挙動になっていて、
例えばtrigger条件を空にしてコミットしてしまうと、それを修正してコミットしても反応しなくなります。
そうなった場合は、Web UI上のQueueボタンから手動で実行すればよいです。
Rustインストール・PATH設定
Azure Pipelinesは標準ではRust環境がないので自分でインストールする必要があります。
コンテナも使えるのですがLinux限定なので、今回は全てrustupでインストールにしました。
ポイントは##vso...
のところで、こういった文字列をechoするとCIの実行環境に対してタスクを実行できます。
ここではPATHの追加を行っており、これ以降のscriptでは追加されたPATHが利用できます。
逆にこれを実行したscript内ではPATH追加の効果がないので、追加されたPATHを期待するscriptは分ける必要があります。
ちなみにPATHを追加するためには##vso[task.prependpath
というぴったりのタスクがあるようだったのですが、うまく動きませんでした。
- script: |
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable
echo '##vso[task.setvariable variable=PATH]$(PATH):$(HOME)/.cargo/bin'
displayName: Install rustup
また、Windows環境ではscriptはcmd.exeで実行されるので環境変数の指定に注意が必要です。以下のうちset PATH=...
は不要な気がしているのですが、ないとうまく動かないようです。
- script: |
curl -sSf -o rustup-init.exe https://win.rustup.rs
rustup-init.exe -y --default-toolchain stable
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
echo '##vso[task.setvariable variable=PATH]%PATH%;%USERPROFILE%\.cargo\bin'
displayName: Install rustup
https://docs.microsoft.com/ja-jp/azure/devops/pipelines/scripts/cross-platform-scripting
https://github.com/Microsoft/azure-pipelines-tasks/blob/master/docs/authoring/commands.md
テスト
Azure PipelinesではJUnitのテストレポートを取り込めるので対応してみました。
cargoのテスト結果をJUnit形式に変換するツールを探したところ、cargo2junit
が見つかったのでこれを使っています。
(他にもいくつかあったのですがうまく動くのはこれだけでした)
cargoのテスト結果を--format json
で出力してcargo2junit
でJUnitに変換、PublishTestResult
タスクで取り込みます。
タスクはscriptとは違ってAzure Pipelinesにデフォルトで用意されたタスクを実行するものです。
後でGitHubにリリースする時にも使います。
- script: |
cargo test -- -Z unstable-options --format json | cargo2junit > results.xml
displayName: Run test
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'results.xml'
condition: succeededOrFailed()
ビルドとアーティファクトの保存
make内でcargo build --release
とzipアーカイブの生成を行っているので適当に読み替えてください。
zipができたら後続のReleaseジョブで使うためにPublishBuildArtifacts
タスクで保存しておきます。
本当はジョブ間のファイル共有専用のPublishPipelineArtifact
というタスクがあるのですが、まだPreviewということかmacOSでのみエラーで実行できず、こちらに切り替えました。
また、artifactName
は他のジョブと被るとエラーになるのでユニークに指定する必要があります。
(指定しないとデフォルトのdrop
が使われて被ってしまいます)
pathtoPublish
は*.zip
などのワイルドカードが使えません。ファイル名はバージョン番号を含むのでここに直接書くことは出来ず、$(Build.ArtifactStagingDirectory)
ディレクトリに一旦コピーしてから渡しています。
- script: |
make release_lnx
cp *.zip $(Build.ArtifactStagingDirectory)
displayName: Build release binary
- task: PublishBuildArtifacts@1
inputs:
artifactName: 'Linux'
pathtoPublish: $(Build.ArtifactStagingDirectory)
https://docs.microsoft.com/en-us/azure/devops/pipelines/artifacts/build-artifacts
https://docs.microsoft.com/en-us/azure/devops/pipelines/artifacts/pipeline-artifacts
アーティファクトのダウンロード
DownloadBuildArtifacts
で各ジョブで保存したzipを取ってきます。
- task: DownloadBuildArtifacts@0
inputs:
downloadType: 'specific'
itemPattern: '**'
downloadPath: $(Build.ArtifactStagingDirectory)
GitHubへのリリース
GitHubへのリリースはGitHubRelease
タスクを使います。まずgitHubConnection
ですが、Web UIのプロジェクトページにある編集アイコンを押してService connectionsを選んだ中にあるものを使います。GitHubと連携した場合はGitHubのユーザ名になると思います。また、デフォルトでは使えないようになっていて、PoliciesのPipeline policiesにある"Allow all pipelines to use this endpoint"にチェックを入れる必要があるようです。チェックを入れないと認証エラーか何かが出ます。
- task: GitHubRelease@0
inputs:
gitHubConnection: 'dalance'
repositoryName: 'dalance/procs'
tagSource: 'auto'
releaseNotesSource: 'input'
releaseNotes: '[Changelog](https://github.com/dalance/procs/blob/master/CHANGELOG.md)'
assets: '$(Build.ArtifactStagingDirectory)/*/*'
assetUploadMode: 'replace'
isDraft: false
isPreRelease: false
addChangeLog: false
displayName: Release to GitHub
releaseNotesを指定しておくとGitHubのリリースノートに反映されます。また、addChangelog
はデフォルトでtrueになっていて、リリース間のコミットログ一覧をリリースノートに挿入してくれますが、個人的には不要だったので切りました。
tagSource
はGit tag
にするという情報もありましたが、auto
が正解のようです。
まとめ
まだ出来立てということか、結構大変でした。ドキュメントは整備されている感があるのですが、仕様変更に追従できていないためか矛盾する記述もあって、それなりに試行錯誤が必要です。とはいえこのあたりは時間が解決すると思いますし、3プラットフォームをまとめられるのは便利なのでこれから使っていきたいと思います。