借助Jenkins + Ansible,辅助一系列的COC实现Inno Setup打包自动化。
1. 流程图
相关的交互流程图参见:CI - Inno Setup打包
为了方便之后的叙述和读者的理解,以下穷举涉及到的服务器:
- 安装了Jenkins的Linux服务器250。
- 安装了自动部署工具Ansible的Linux服务器254。
- 安装了Inno Setup的WIndows服务器3。
- 安装了FTP Server的Windows服务器3。(服务器资源紧张,所以我们将Inno Setup和FTP Server的安装合并在了同一台服务器上;不过按照我们的设计来说,这两者并没有隐含依赖关系)
2. 环境准备
Jenkins和Ansible的基本安装和配置不是本文的重点,以下列举其他的一些关键性的前期环境准备。
2.1 lftp工具安装
在本文的设计实现中,我们需要使用lftp来实现如下效果:
- 将用户上传的待打包软件推送到FTP服务器上,以供Inno Setup打包时使用。这一步操作发生在250服务器。
- 将上一步推送到FTP服务器的待打包软件拉取到本地,以执行Inno Setup打包工作。这一步操作发生在3服务器。
因此这里我们需要分别在250服务器,3服务器上安装lftp:
yum install lftp
1. 从 https://lftp.nwgat.ninja/lftp-4.4.15/ 下载对应版本的压缩包
2. 解压到任意目录
3. 将以上目录添加到Windows环境变量
4. 验证: lftp /?
2.2 svn命令行工具
践行DevOps"一切皆版本控制"的思想,我们将Inno Setup脚本进行了源代码版本控制,因此每次打包前我们都需要从svn拉取最新的代码。
关于svn命令行工具,得益于对于zentao的了解,我们可以直接从zentao的安装目录下获取免安装的svn命令行工具(这一步操作也发生在3服务器上)。
2.3 7zip工具安装
本工具的安装发生在3服务器上,目的是为了在使用Inno Setup生成打包之后,将打包过程中收集的一些必要元信息汇总为专门的元文件,然后使用7zip压缩工具将它们合并为一个压缩包,以便于之后的版本追溯。
2.4 Inno Setup安装
作为主角的 Inno Setup,其安装操作发生在3服务器上,我们将借助其命令行工具来实现打包操作。
作为一款打包工具,Inno Setup的安装过程不出意料地非常简单 —— 一路"下一步"即可。
安装完毕之后,将安装路径添加到环境变量Path中,并进行如下验证:
ISCC.exe /?
2.5 Jenkins File Params
现阶段我们暂时没有实现C++应用的集成编译,所以在本文介绍的自动化流程中,待打包软件的提供还是由用户自行上传的,因此我们需要Jenkins的File Params支持。
安装过程参见 Jenkins Pipeline使用File parameter,具体的使用参见本文下面的细节。
3. 各脚本
脚本涉及三大块:
- Inno Setup打包脚本
- Jenkins Pipeline脚本
- Ansible部署脚本
3.1 Inno Setup打包脚本
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "ZzCamera"
#define MyAppVersion "1.0.0"
#define MyAppPublisher "ZZZZ, Inc."
#define MyAppURL "http://www.ZZZZ.com/"
#define MyAppExeName "ZZZZ-camera.exe"
; // 以本脚本所在的目录为基础, 进行相对路径跳转
#define CompileBasePath ".\..\"
#define AppFolderNameFromOutside "ZZZZ-camera-" ; 由外部传入
[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={{33CD0F63-DE20-44E9-BF1D-8A82D45B97FD}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName=D:/{#MyAppName}
DisableProgramGroupPage=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputDir={#CompileBasePath}\compileOutput
OutputBaseFilename=ZZZZ-camera-installer-{#MyAppVersion}
SetupIconFile={#CompileBasePath}\{#AppFolderNameFromOutside}\ZZZZCamera.ico
Compression=lzma
SolidCompression=yes
WizardStyle=modern
DefaultGroupName={#MyAppName}
[Languages]
Name: "chinesesimp"; MessagesFile: "compiler:Default.isl"
; 写入注册表进行protocol注册,以实现在浏览器输入 ZZZZCamera:// 打开应用的效果
[Registry]
Root: HKCR; Subkey: "ZZZZCamera"; Flags: uninsdeletekeyifempty; ValueType: expandsz; ValueName: "URL Protocol"; ValueData: "{app}\{#MyAppExeName}"
Root: HKCR; Subkey: "ZZZZCamera"; Flags: uninsdeletekeyifempty; ValueType: expandsz; ValueName: ""; ValueData: "ZZZZCamera"
Root: HKCR; Subkey: "ZZZZCamera\shell"; Flags: uninsdeletekeyifempty
Root: HKCR; Subkey: "ZZZZCamera\shell\open"; Flags: uninsdeletekeyifempty
Root: HKCR; Subkey: "ZZZZCamera\shell\open\command"; Flags: uninsdeletekeyifempty; ValueType: expandsz; ValueName: ""; ValueData: "{app}\{#MyAppExeName} %1"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkablealone
;Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkablealone
[Files]
Source: "{#CompileBasePath}\{#AppFolderNameFromOutside}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#CompileBasePath}\{#AppFolderNameFromOutside}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "{#CompileBasePath}\code\image\*"; DestDir: "{app}\image"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "{#CompileBasePath}\code\bin\uninstall.bat"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#CompileBasePath}\code\bin\stop.bat"; DestDir: "{app}"; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
;Source: "{#CompileBasePath}\dll\ISTask.dll"; DestDir: {app}; Flags: ignoreversion; Attribs: hidden
;Source: "{#CompileBasePath}\dll\psvince.dll";DestDir: {app}; Flags: dontcopy noencryption
; Source: compiler:psvince.dll compile前缀为内置值,,,,
[Icons]
;Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}";IconFilename: "{app}\ZZZZCamera.ico";
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}";IconFilename: "{app}\ZZZZCamera.ico"; Tasks: desktopicon
Name: "{group}\{#MyAppName}-启动服务"; Filename: "{app}\{#MyAppExeName}";IconFilename: "{app}\ZZZZCamera.ico";
Name: "{group}\{#MyAppName}-停止服务"; Filename:"{app}\stop.bat";Tasks:desktopicon;IconFilename: "{app}\image\stop.ico";
Name: "{group}\{#MyAppName}-卸载"; Filename:"{app}\uninstall.bat";IconFilename: "{app}\image\uninstall.ico";
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
[UninstallRun]
Filename: "{app}\uninstall.bat"; RunOnceId: "DelService"
[UninstallDelete]
Type: files; Name: "{app}\uninstall.bat"
; 新版本安装时,先卸载旧有安装版本
[Code]
// https://blog.csdn.net/qq_36190858/article/details/84893661 卸载之后再安装
function InitializeSetup(): boolean;
var
ResultStr: String;
ResultCode: Integer;
begin
if RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{33CD0F63-DE20-44E9-BF1D-8A82D45B97FD}_is1', 'UninstallString', ResultStr) then
begin
if MsgBox('ZZZZCamera进程正在运行,需要先卸载它.', mbConfirmation, MB_OKCANCEL) = IDOK then
begin
ResultStr := RemoveQuotes(ResultStr);
//MsgBox(ResultStr, mbConfirmation, MB_OKCANCEL) ;
// ResultStr : D:\ZZZZCamera\uninstall000.exe
Exec('taskkill', '/f /t /fi "imagename eq ZZZZ-camera.exe"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Exec(ResultStr, '/silent', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
Result := true;
end
else
begin
Result:= false;
end;
end
else
begin
Result := true;
end;
end;
// https://blog.csdn.net/qq_36190858/article/details/84894346 【卸载前删除某些文件】
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usDone then
begin
DelTree(ExpandConstant('{app}'), True, True, True);
end;
end;
3.2 Jenkins Pipeline脚本
properties([
parameters([
choice(name: 'SERVER', choices: ['windows_3'], description: '编译服务器'),
file(name: 'FILE_TO_COMPILE', description: '需要被Inno Setup打包的压缩包,请将待打包的文件夹打包为zip,直接根目录打包,不要包含一个根文件夹')
])
])
library "jenkinsci-unstashParam-library"
@Library('global-shared-library') _
def msgTools = new org.devops.tools()
def file_upload
pipeline {
agent any
stages {
stage('Initialize') {
steps {
echo "清空工作空间"
cleanWs()
}
}
stage('validate file params') {
steps {
script{
if(!"${FILE_TO_COMPILE}".endsWith(".zip")){
error "请将待打包的文件夹打包为zip,直接根目录打包,不要包含一个根文件夹"
}
println "${FILE_TO_COMPILE}"
}
}
}
stage('接收上传的文件'){
steps {
script{
file_upload = unstashParam "FILE_TO_COMPILE"
sh "cd ${WORKSPACE} && mv ${file_upload} ZZZZ-camera-needComiple.zip"
file_upload = "ZZZZ-camera-needComiple.zip"
sh "pwd ${file_upload}"
}
}
}
stage('上传文件到Inno Setup编译服务器.3'){
steps {
sh """
lftp 172.16.3.3 -p 21 -u webadmin,webadmin -e "lcd ${WORKSPACE}/; cd /ZZZZCamera/;put ${file_upload}; quit"
"""
}
}
stage('inno-setup-compile') {
steps {
sshPublisher(
publishers: [
sshPublisherDesc(
configName: 'ansibleServerIn254',
transfers: [
sshTransfer(
excludes: '',
execCommand: '''
cd /etc/ansible/playbooks/common
ansible-playbook windows_inno_setup_common.yaml \
-e "TARGET=${SERVER}"
''',
)
],
verbose: true
)
]
)
}
}
stage('show artifact download url'){
steps {
script{
msgTools.PrintMes("ftp://xxx.xx.x.3/ZZZZCamera/ZZZZ-camera-installer-1.0.0.exe" , "red")
}
}
}
}
post{
success {
echo 'pipeline post success'
}
}
}
3.3 Ansible 部署脚本
---
- hosts: "{{ TARGET | default('windows_3') }}"
vars:
WORKPATH: "E:/_XXXXX/_compileTempOfInnoSetup"
ZIP_FILE_NAME_INNER: "{{ZIP_FILE_NAME | default('ZZZZ-camera-needComiple.zip')}}"
ZIP_FILE_NAME_FIRST_INNER: "{{ZIP_FILE_NAME_INNER.split('.')[0]}}"
vars_files:
- vars-common.json
tasks:
- name: remove old inno-setup-script in windows-server-compiler
win_file: path="{{WORKPATH}}/code/" state=absent
- name: download install.iss script of inno-setup from svn
win_shell: |
# sliksvn/ 是从 zentao的安装包中拷贝而来
cd E:/_devops/sliksvn/
./svn.exe checkout https://xxxx.xxx.xxx.xxx:888/svn/DEVOPS/ZZZZ-installer-camera {{WORKPATH}} --username ccccc --password cccc
- name: move package_will_compile to dest location {{WORKPATH}}
win_shell: |
mv D:\FTP\1WEB_SAVE\ZZZZCamera\{{ZIP_FILE_NAME_INNER}} {{WORKPATH}}\{{ZIP_FILE_NAME_INNER}} -Force
- name: rm folder folder {{WORKPATH}}/{{ZIP_FILE_NAME_FIRST_INNER}}/
win_file: path="{{WORKPATH}}/{{ZIP_FILE_NAME_FIRST_INNER}}/" state=absent
ignore_errors: yes
- name: unzip {{ZIP_FILE_NAME_INNER}} to {{WORKPATH}}
win_unzip: creates=yes src="{{WORKPATH}}\{{ZIP_FILE_NAME_INNER}}" dest="{{WORKPATH}}\{{ZIP_FILE_NAME_FIRST_INNER}}" delete_archive=yes
- name: call ISCC.exe to compile
win_shell: |
$env:path = $env:path + "; E:/_devops/Inno Setup 6"
cd {{WORKPATH}}/code/
echo "begin call iscc.exe"
ISCC.exe /DAppFolderNameFromOutside={{ZIP_FILE_NAME_FIRST_INNER}} .\install.iss
- name: push package to ftp server
win_command: |
lftp 172.16.3.3 -p 21 -u webadmin,webadmin -e "lcd {{WORKPATH}}/compileOutput/; cd /ZZZZCamera/;put ZZZZ-camera-installer-1.0.0.exe; quit"
4. Links
- Jenkins Pipeline使用File parameter
- lftp For Windows
- Inno Setup官网
|