1. 桌面應用程式的 CI/CD
桌面應用程式的 CI/CD 過程和網站有一些不同,畢竟桌面應用程式的“部署”只是將安裝包分發到目標位置,連應用商店都不用上,根據公司的管理流程可以很複雜,也可以很簡單。在簡單的情況下,Azure Pipelines 中一個桌面應用(WPF)的 CI/CD 過程如下:
- 觸發器啟動 Pipeline
- 構建 WPF 應用程式
- 啟動單元測試以確保構建質量
- 建立安裝包
- 將安裝包複製到目標位置
- 通知使用者新安裝包已經可以獲取
在使用 Azure Pipelines 實現 CI 這篇文章中,我講解了如何實現第 1、2、3、5 步。至於第 6 步,可以在 Project Settings 的 Notifications 頁面中設定使用郵件通知團隊成員,也可以參考 使用聯結器接收Azure DevOps的通知 這篇文章通過 Teams 傳送構建的結果。
現在我們還缺少第 4 步“建立安裝包”,這篇文章將講解如何在 Azure Pipelines 中使用 Inno Setup 建立安裝包。
2. 使用 Inno Setup 建立安裝包
假設我們已經根據 使用 Azure Pipelines 實現 CI 的做法釋出了一個 WPF 應用程式,釋出到 Artifacts 的檔案將會如上圖所示,可以以 Zip 的方式將所有輸出檔案下載到本地,基本相當於綠色版軟體。但我們不能將這個 Zip 包直接發給客戶,我們至少還要包括開始選單和修改登錄檔什麼的一大堆東西,所以需要將 Release 的檔案打包到一個安裝包中。我的公司通常使用 Inno Setup 製作安裝包,在 Azure Pipelines 中使用 Inno Setup 也十分簡單,於是這篇文章將使用 Inno Setup 作為製作安裝包的例子。
首先我們需要一個 iss 指令碼。在 install 目錄下建立一個簡單的名為 SetupScript.iss 的指令碼檔案,大部分保留了預設值(懶得修改公司名之類的了),它只是將 Release 目錄的內容全部打包起來,內容如下:
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "My Program"
#define MyAppPublisher "My Company, Inc."
#define MyAppURL "https://www.example.com/"
#define MyAppExeName "wpf.exe"
#define VersionSourceAssemblyName MyAppExeName
#define BuildOutputFolder "..\wpf\bin\Release\"
#define MyAppFileVersion GetFileVersion(AddBackslash(BuildOutputFolder) + VersionSourceAssemblyName)
#define MyAppCustomerVersion GetStringFileInfo(AddBackslash(BuildOutputFolder) + VersionSourceAssemblyName, "ProductVersion")
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{0B50DAF7-728E-48C7-984F-5E6FDB924490}
AppName={#MyAppName}
AppVersion={#MyAppCustomerVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
DisableProgramGroupPage=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputBaseFilename=mysetup {#MyAppCustomerVersion}
Compression=lzma
SolidCompression=yes
WizardStyle=modern
VersionInfoCompany={#MyAppPublisher}
VersionInfoVersion={#MyAppFileVersion}
VersionInfoProductName={#MyAppName}
VersionInfoProductTextVersion={#MyAppCustomerVersion}
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "{#BuildOutputFolder}{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#BuildOutputFolder}*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs;
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
用 Inno Setup 執行一下這個指令碼檔案確保它正確執行(如果成功的話會在 Installer\Output 目錄下生成一個安裝程式)。
3. 在 Azure Pipelines 上執行 Inno Setup
把 SetupScript.iss 推送到 Azure Repos 上,然後修改對應的 Pipeline。Pipeline 中需要新增兩個任務:
- 一個負責使用 Chocolatey 下載並安裝 Inno Setup 的任務
- 一個呼叫 Inno Setup 執行 SetupScript.iss 的任務
然後修改 CopyFiles 任務,將 Installer\output 目錄中的安裝包複製到 $(build.artifactstagingdirectory)
。修改後的 YAML 檔案如下(其中兩個 PowerShell 任務即為新增的兩個任務):
trigger:
- master
pool:
vmImage: 'windows-latest'
variables:
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
steps:
- task: NuGetToolInstaller@1
- task: PowerShell@2
displayName: 'Inno setup download'
inputs:
targetType: 'inline'
script: 'choco install innosetup'
- task: NuGetCommand@2
inputs:
restoreSolution: '$(solution)'
- task: VSBuild@1
inputs:
solution: '$(solution)'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: PowerShell@2
displayName: 'Execute Inno Setup script'
inputs:
targetType: 'inline'
script: 'iscc.exe Installer\\SetupScript.iss'
- task: CopyFiles@2
inputs:
SourceFolder: 'Installer\\output'
Contents: '*.exe'
TargetFolder: '$(build.artifactstagingdirectory)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
4. 最後
現在,一個桌面應用程式的 CI/CD 已經基本完成了。當然實際應用中 iss 指令碼和 PowerShell 都可以更復雜以便完成更多工,例如程式簽名、檢查並安裝 .Net Framework 等,這些操作都超出了這篇文章的範疇,如有需要可以參考下面這些連結:
Inno Setup - a free installer for Windows programs
Chocolatey Software - The package manager for Windows