日々 Windows デスクトップアプリの開発に勤しんでいます💦
『Windows アプリは自分のパソコンでビルドするモンだ!』
『ビルド環境の構築は一苦労』
という固定観念から抜けてみたく、試行錯誤していました。
結論的には
- Azure Pipelines (Azure DevOps) を利用すれば、.NET Framework デスクトップアプリの自動ビルドは可能
- GitHub の Releases にリリースを作成可能 (執筆時点では GitHub Release は PREVIEW 扱い)
- ビルドごとに Arfifacts を残すことが可能
- signtool で署名することも可能 (signtool は持ち込みしました)
- CodeSign.pfx ファイルを格納する手立ても Azure DevOps にあります
- CodeSign.pfx ファイルのパスワードを格納する手立ても Azure DevOps にあります
- PowerShell, NSIS, MinGW などが使えます
- VM の細かいスペックは Azure Pipelines Hosted VS2017 image のページが詳しいです
では大雑把に見ていきます
GitHub の Releases にリリースを作成可能
Note: つぎのトピック ビルドごとに Arfifacts を残すことが可能
で Artifacts が生成されていることをあてにしています。
対象プロジェクトを選択
-
Pipelines
クリック -
Releases
クリック -
+ New
クリック -
New release pipeline
クリック
さらに、
-
Empty job
で
さらに
-
Stage 1
をクリック -
Tasks
タブをクリック
さらに、
-
Agent job
の+
をクリック -
Utility
タブをクリック -
GitHub Release
を捜してきて、クリックして、Add
をクリック
さらに、
-
GitHub release (create)
をクリック。右側にプロパティ一覧らしきブレードが出てくるので埋めていきます -
GitHub connection (OAuth or PAT)*
は、~ 埋めてください。 -
Repository*
も、~ 埋めてください。 -
Action*
はCreate
のままで -
Target*
は$(Build.SourceVersion)
のままで -
Tag source*
は$(Release.ReleaseName)
にすると、Release-1
のような名前が付きます -
Release title
は$(Release.ReleaseName)
にしました -
Release notes source
はInline release notes
にしました -
Release notes
はby DevOps $(Release.ReleaseWebURL)
にしました -
Assets
は$(System.DefaultWorkingDirectory)/_COMPANYNAME.PROJECTNAME/drop/*
のようにしました。 -
_COMPANYNAME.PROJECTNAME
を何にすべきかは、2 つ下の画像の (1)Source alias *
を参考に -
drop
は、のちほど指定するartifactName
です -
Draft release
はオフ (これは好みで) -
Pre-release
はオン (これも好みで) -
Add changelog
はオフ (これも好みで) - 上部の
Save
で保存
続きはまた今度
ビルドごとに Arfifacts を残すことが可能
azure-pipelines.yml
を編集します。
- task: CopyFiles@2
inputs:
contents: 'Setup_*.exe'
targetFolder: $(Build.ArtifactStagingDirectory)
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: 'drop'
成果物を CopyFiles@2
タスクで $(Build.ArtifactStagingDirectory)
へコピーして、
PublishBuildArtifacts@1
タスクを実行します。
artifactName: 'drop'
は、ビルド結果画面右上の Artifacts
をプルダウンした時にリストされる名称です。下図参照
signtool で署名することも可能 (signtool は持ち込みしました)
MySign というツール
わたしは MySign
という実行ファイル署名用のコマンドラインツールを自作しています。
使用感 MySign a.exe b.dll
署名しない環境では空の MySign.bat
を作成します。
自分のパソコンでは、パスの通っている場所に GUI ツールの MySign.exe
を設置しています。
バックグラウンドタスクで署名が必要な環境では、つぎのような内容で MySign.bat
を書くと良いかもしれません。
%~dp0\signtool.exe sign /tr "http://rfc3161-url" /fd sha256 /n "COMPANY NAME" %*
フォルダ構成:
ソースフォルダ
- BuildTools フォルダ
- signtool フォルダ
- MySign.bat
- signtool.exe
NSIS でも活用可能
わたしは NSIS を多用をするので MySign
が必須です。
!system 'MySign "bin\DEBUG\${APP}.exe"'
!finalize 'MySign "%1"'
!system
でビルド後のファイルに署名を施し、
!finalize
でセットアップファイルに署名を施します。
デプロイしたい
azure-pipelines.yml
を編集します。
CopyFiles@2
で、MySign.bat
と signtool.exe
をソースフォルダにコピーしてきます。
- task: CopyFiles@2
inputs:
contents: 'BuildTools/signtool/*'
targetFolder: '.'
overWrite: true
flattenFolders: true
CodeSign.pfx ファイルを格納する手立て
Note: pfx ファイルはエクスポートする際に、暗号化を TripleDES-SHA1
に設定しておいた方が良いでしょう。 AES256-SHA256
を選択すると、証明書をインポートできない、という問題が発生しました。この選択欄が存在しない場合は無視で問題ありません。
Azure DevOps でプロジェクトを開きます。
- 左端の
Pipelines
を選択 -
Library
をクリック -
Secure files
タブをクリック -
+ Secure file
をクリックします - CodeSign.pfx などをアップロードします
操作手順です
ひとまずわたしは Authorize for use in all pipelines
にチェックを入れておきました。
CodeSign.pfx ファイルのパスワードを格納する手立て
Azure DevOps でプロジェクトを開きます。
- 左端の
Pipelines
を選択 -
Library
をクリック -
Variable groups
タブをクリック -
+ Variable group
をクリックします
さらに、
-
Variable group name
に適当な名称を設定。 -
+ Add
をクリック -
Name
にvariable name
を入力。PFX-PASSWD
にすると$(PFX-PASSWD)
のように参照できます。 -
Value
にパスワードを入力。 - 南京錠をクリックして、
Value
を保護します。保護というのはわたしの認識では、 - Azure Pipelines 上では表示できなくする
- ビルドログについても
***
などで隠蔽される -
Save
で保存
Builds で CodeSign.pfx を使用する場合の準備
Builds の場合の Variable group
割り当て方法
- 左端の
Pipelines
を選択 -
Builds
をクリック - 対象のパイプラインをクリック
-
Edit
をクリック
さらに、
- 右上の
...
をクリック -
Variables
をクリック
さらに、
-
Variable groups
をクリック -
Link variable group
をクリック
さらに、
-
Sign.pfx.passwd
選択 -
Link
クリック
さらに
-
Save & queue
クリック -
Save
クリック -
Save
クリック
これで Sign.pfx.passwd
を使用する準備が整ったと思います。
azure-pipelines.yml
を編集
steps:
- task: DownloadSecureFile@1
inputs:
secureFile: CodeSign.pfx
- powershell: |
$secureString = ConvertTo-SecureString $env:MySecret -AsPlainText -Force
Import-PfxCertificate -Exportable -FilePath $env:DOWNLOADSECUREFILE_SECUREFILEPATH -CertStoreLocation 'Cert:\CurrentUser\My' -Password $secureString
displayName: Import pfx
env:
MySecret: $(PFX-PASSWD)
secureFile: CodeSign.pfx
が、Library
の Secure Files
でアップロードしたファイル名、
$(PFX-PASSWD)
は、 Library
の Variable groups
で定義した Variables
PFX-PASSWD
の Value
が反映されます。
DownloadSecureFile@1
で取得したファイルは環境変数 DOWNLOADSECUREFILE_SECUREFILEPATH
に格納されることになっているようです。
PowerShell での参照方法は $env:DOWNLOADSECUREFILE_SECUREFILEPATH
です。
これで証明書を VM にインストールするので、あとから signtool.exe で参照できるようになるはずです。
signtool の動作チェックをする
パイプラインの最後の方でミスが発覚すると、消費した時間がむなしくなります…
先に動作チェックを走らせた方が良いでしょう。
- script: MySign EmptyExe.exe
displayName: Sign test
EmptyExe.exe は自分用に作ったものですが、共有いたします。 https://github.com/HiraokaHyperTools/EmptyExe/releases
chcp 65001 で動作
VM のコンソールはコードページ 65001 (UTF-8) で動作しているようです。
しかしながら Unicode 対応ではないプログラムの言語
の設定は日本語ではありませんでした。
つまり、bat ファイルに CP932 (Shift_JIS) を使用すると、文字化けなどの問題が出ます。
bat ファイルと nsi ファイルについては CP932 → UTF-8 に文字コード変換するタスクを書きました。
- script: |
for %%f in (*.bat) do (
echo ##[debug]Convert: %%f
iconv -f cp932 -t utf-8 %%f > %%f.new
move /y %%f.new %%f
)
for %%f in (*.nsi) do (
echo ##[debug]Convert: %%f
iconv -f cp932 -t utf-8 %%f > %%f.new
move /y %%f.new %%f
)
displayName: Needs to be UTF-8
UTF-8 に変換した bat nsi ファイルは、ビルド後に破棄されます。使い捨てです。
これらが Git レポジトリに書き戻ってくることは通常ありません。
makensis
NSIS セットアップを作成したい。
1 つの exe に対して多数のセットアップを作成するプロジェクトなので、こうしています。
- script: |
for %%f in (*.bat) do (
echo ##[debug]Run: %%f
%%f || exit /b 1
)
displayName: Build NSIS installers
env:
AZURE_PIPELINES_NSIS_OPTIONS: /INPUTCHARSET UTF8
command || exit /b 1
は短絡評価の応用ですね…
command
が成功 (ERRORLEVEL が 0) であれば、つぎの行へ。
command
が失敗 (ERRORLEVEL が 1 以上) であれば… ||
の後の exit /b 1
を実行。ERRORLEVEL 1 を返してビルド失敗を報告します。
AZURE_PIPELINES_NSIS_OPTIONS: /INPUTCHARSET UTF8
は、先ほどの文字コード変換の結果、必要になった記述です。
その NSIS ビルドに使う bat ファイルは、Git レポジトリには CP932 で保存しておきます。通常の Windows 環境でも使いたいので…
"C:\Program Files (x86)\NSIS\Bin\makensis.exe" /DSPEC="顧客名" %AZURE_PIPELINES_NSIS_OPTIONS% Setup_ABC.nsi