Quelea is by far the "side project" that takes up the majority of my time. To aid with testing, I built in CI relatively early with a Jenkins server running on a custom VM. This was great - I could just push a change to the repo from anywhere, and then point the user to the CI release. They'd download it and be able to confirm whether the fix had worked (or not!)
I've since switched to Travis and retired said VM (it's one less thing to maintain, and now everything is on Github.) But both these setups had one main issue - the windows installer wouldn't get built as part of this process, since they were Linux boxes and innosetup doesn't have a linux distribution. Travis has added windows support, but it's in early release, and in any case I'd like the entire build process to be able to run on any Linux box - it makes it both quicker and more transferrable if we ever need to move elsewhere.
I therefore looked into using wine in the CI release to build the windows installer - and it's not too bad at all. There's a few non-obvious things to figure out though, so I'm describing the process here in case anyone else has the same requirement.
I'm making some assumptions here - I'm not walking through creating the innosetup script (the iss file), nor am I walking through integrating innosetup with your build environment of choice. Before following this guide, you should have a build process that produces a working installer exe, from your build script, in a windows environment. We'll focus on translating this to a Linux environment so it can be built by any common CI tool (Travis, CircleCI, etc.)
I'm using Travis and Gradle for the purposes of this walkthrough, but the concepts can be applied anywhere. So without further ado, you'll need to add the following to your .travis.yml:
sudo: required
(You'll need that.)
addons:
apt:
packages:
- wine
- xvfb
You'll need to specify both wine and xvfb as packages for your CI environment to install. Wine is needed for obvious reasons. XVFB is the X virtual framebuffer, and is needed to emulate a graphical environment in a headless setting. (It can do all sorts of fancy things such as taking screenshots of the emulated framebuffer and simulating input events, but we don't need to worry about any of that.)
before_install:
- wget http://files.jrsoftware.org/is/5/innosetup-5.6.1.exe
- wineboot --update
- Xvfb :0 -screen 0 1024x768x16 &
- DISPLAY=:0.0 wine innosetup-5.6.1.exe /VERYSILENT /SUPPRESSMSGBOXES
Without "wineboot --update" you'll get some strange errors and very long hangs when the installer runs - that's the key to making the process go smoothly. The next line spins up the virtual framebuffer, and the line after executes the innosetup installer that wget downloaded in the first step. The final line installs innosetup into the wine environment, and from that point onwards it's available to your build environment!
The next thing I'll create is a simple shell script to build the installer:
#!/bin/bash
wine "/home/travis/.wine/drive_c/Program Files (x86)/Inno Setup 5/iscc.exe" /dMyAppVersion=$1 quelea64.iss /O /F /q"Quelea"
...and then a Gradle task to call said shell script, and rename / move the output files when done:
task innosetup (type:Exec) {
doFirst {
workingDir = project.file('.')
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
commandLine = ['cmd', '/C', 'build-install.bat', project.queleaversion]
} else {
commandLine = ['sh', './build-install.sh', project.queleaversion]
}
}
doLast {
copy {from file("Output/setup.exe") into file(project.distdir + "/standalone") rename { String filename -> return WindowsInstallerName}}
copy {from file("Output/setup64.exe") into file(project.distdir + "/standalone") rename { String filename -> return WindowsInstaller64Name}}
delete (file("Output"))
}
}
Note here that I've still kept the windows batch file in place which enables me to build on either platform. Gradle will selectively call the correct script based on the platform.
And that's pretty much it!
If you'd like to see how we've integrated this process into Quelea, you can poke around in the repo. There's additional logic there to upload the CI artifacts to a Github release as well, which means I can always point people at a single Github release to grab the latest version (and don't have to maintain any off-platform infrastructure.)
I've since switched to Travis and retired said VM (it's one less thing to maintain, and now everything is on Github.) But both these setups had one main issue - the windows installer wouldn't get built as part of this process, since they were Linux boxes and innosetup doesn't have a linux distribution. Travis has added windows support, but it's in early release, and in any case I'd like the entire build process to be able to run on any Linux box - it makes it both quicker and more transferrable if we ever need to move elsewhere.
I therefore looked into using wine in the CI release to build the windows installer - and it's not too bad at all. There's a few non-obvious things to figure out though, so I'm describing the process here in case anyone else has the same requirement.
I'm making some assumptions here - I'm not walking through creating the innosetup script (the iss file), nor am I walking through integrating innosetup with your build environment of choice. Before following this guide, you should have a build process that produces a working installer exe, from your build script, in a windows environment. We'll focus on translating this to a Linux environment so it can be built by any common CI tool (Travis, CircleCI, etc.)
I'm using Travis and Gradle for the purposes of this walkthrough, but the concepts can be applied anywhere. So without further ado, you'll need to add the following to your .travis.yml:
sudo: required
(You'll need that.)
addons:
apt:
packages:
- wine
- xvfb
You'll need to specify both wine and xvfb as packages for your CI environment to install. Wine is needed for obvious reasons. XVFB is the X virtual framebuffer, and is needed to emulate a graphical environment in a headless setting. (It can do all sorts of fancy things such as taking screenshots of the emulated framebuffer and simulating input events, but we don't need to worry about any of that.)
before_install:
- wget http://files.jrsoftware.org/is/5/innosetup-5.6.1.exe
- wineboot --update
- Xvfb :0 -screen 0 1024x768x16 &
- DISPLAY=:0.0 wine innosetup-5.6.1.exe /VERYSILENT /SUPPRESSMSGBOXES
Without "wineboot --update" you'll get some strange errors and very long hangs when the installer runs - that's the key to making the process go smoothly. The next line spins up the virtual framebuffer, and the line after executes the innosetup installer that wget downloaded in the first step. The final line installs innosetup into the wine environment, and from that point onwards it's available to your build environment!
The next thing I'll create is a simple shell script to build the installer:
#!/bin/bash
wine "/home/travis/.wine/drive_c/Program Files (x86)/Inno Setup 5/iscc.exe" /dMyAppVersion=$1 quelea64.iss /O /F /q"Quelea"
...and then a Gradle task to call said shell script, and rename / move the output files when done:
task innosetup (type:Exec) {
doFirst {
workingDir = project.file('.')
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
commandLine = ['cmd', '/C', 'build-install.bat', project.queleaversion]
} else {
commandLine = ['sh', './build-install.sh', project.queleaversion]
}
}
doLast {
copy {from file("Output/setup.exe") into file(project.distdir + "/standalone") rename { String filename -> return WindowsInstallerName}}
copy {from file("Output/setup64.exe") into file(project.distdir + "/standalone") rename { String filename -> return WindowsInstaller64Name}}
delete (file("Output"))
}
}
Note here that I've still kept the windows batch file in place which enables me to build on either platform. Gradle will selectively call the correct script based on the platform.
And that's pretty much it!
If you'd like to see how we've integrated this process into Quelea, you can poke around in the repo. There's additional logic there to upload the CI artifacts to a Github release as well, which means I can always point people at a single Github release to grab the latest version (and don't have to maintain any off-platform infrastructure.)
Comments
Post a Comment