- A+
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 "..wpfbinRelease" #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 运行一下这个脚本文件确保它正确运行(如果成功的话会在 InstallerOutput 目录下生成一个安装程序)。
3. 在 Azure Pipelines 上运行 Inno Setup
把 SetupScript.iss 推送到 Azure Repos 上,然后修改对应的 Pipeline。Pipeline 中需要添加两个任务:
- 一个负责使用 Chocolatey 下载并安装 Inno Setup 的任务
- 一个调用 Inno Setup 运行 SetupScript.iss 的任务
然后修改 CopyFiles 任务,将 Installeroutput 目录中的安装包复制到 $(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 等,这些操作都超出了这篇文章的范畴,如有需要可以参考下面这些链接: