Automate Ionic Builds With Gitlab CI: Walkthrough and Tutorial
As a lot of our articles can attest, we at Hybrid Heroes are big fans of Continuous Integration. Not only does it help to catch bugs and exceptions early on, it also accelerates our development and delivery workflows and help our team be more efficient and productive.
In this article we will demonstrate how to implement CI with Gitlab on a cross-platform Ionic project.
Runner and environment variables
Using Gitlab CI for continuous integration requires setting up an environment for the build process to be run on. This environment can be encapsulated in a docker image, or simply be the shell of a targeted machine. We will be installing our runner on a macOS machine so we can make iOS builds.
We first need to install Gitlab Runner on our environment of choice, and register it with the unique token provided by Gitlab.
The next logical step is to provide environment variables to feed the runner jobs. A typical set up in a Cordova project would be to specify the build configuration in a build.json file.
A good practice here is to put this configuration in a BUILD_CONFIG environment variable. For example, our variable might look like this:
{
"ios": {
"debug": {
"codeSignIdentity": "iPhone Developer",
"developmentTeam": "XXXXXXXXXX",
"packageType": "development",
"automaticProvisioning": true,
"buildFlag": ["-UseModernBuildSystem=0", "-allowProvisioningUpdates"]
},
"release": {
"codeSignIdentity": "iPhone Developer",
"developmentTeam": "XXXXXXXXXX",
"packageType": "app-store",
"automaticProvisioning": true,
"buildFlag": ["-UseModernBuildSystem=0", "-allowProvisioningUpdates"]
}
},
"android": {
"debug": {
"keystore": "./development.jks",
"storePassword": "12345abcde",
"alias": "my-app",
"password": "12345abcde",
"keystoreType": ""
},
"release": {
"keystore": "./development.jks",
"storePassword": "12345abcde",
"alias": "my-app",
"password": "12345abcde",
"keystoreType": ""
}
}
}
Configure .gitlab-ci.yml
Jobs within a CI configuration can belong to three different default stages: build, test and deploy. In order to keep this article focused, we will only pay attention to the deploy stage, and work our script around that. The CI script uses the YAML markup language, make sure you get familiar with the syntax before diving head on in the code.
We will create a deploy template that will hold the common configuration for all types of builds. Note how we pass our build configuration from the environment variable $BUILD_CONFIG to the project folder.
.deploy: &deploy
stage: deploy
cache:
# speed up packaging by sharing cache amongst multiple deploy jobs of the same commit
key: "deploy-$CI_COMMIT_SHORT_SHA"
paths:
- platforms/
- plugins/
before_script:
# workaround Xcode not downloading updated profiles
- rm -fr ~/Library/MobileDevice/Provisioning\ Profiles/
- echo "$BUILD_CONFIG" | tr -d '\r' > build.json
after_script:
- rm build.json
- cp $OUTPUT_PATH.$FILE_TYPE $CI_PROJECT_NAME-$CI_COMMIT_REF_SLUG.$FILE_TYPE
artifacts:
name:
paths:
- $CI_PROJECT_NAME-$CI_COMMIT_REF_SLUG.$FILE_TYPE
expire_in: 7 days
tags:
- macOS
when: manual
Using this template as the basis for all types of build, it becomes easy to define our different build jobs. Make sure you create build scripts for each cases.
Android
deploy:android:debug:
<<: *deploy
environment:
name: production
variables:
PLATFORM: android
FILE_TYPE: apk
OUTPUT_PATH: platforms/android/app/build/outputs/apk/debug/app-debug
script:
- npm ci
- ionic cordova build android --debug
deploy:android:release:
<<: *deploy
environment:
name: production
variables:
PLATFORM: android
FILE_TYPE: apk
OUTPUT_PATH: platforms/android/app/build/outputs/apk/release/app-release
script:
- npm ci
- ionic cordova build android --release
iOS
deploy:ios:debug:
<<: *deploy
environment:
name: production
variables:
PLATFORM: iOS
FILE_TYPE: ipa
OUTPUT_PATH: platforms/ios/build/device/my-app
script:
- npm ci
- ionic cordova build ios --debug
deploy:ios:release:
<<: *deploy
environment:
name: production
variables:
PLATFORM: iOS
FILE_TYPE: ipa
OUTPUT_PATH: platforms/ios/build/device/my-app
script:
- npm ci
- ionic cordova build ios --release
From then on, it's easy to generate the types of build we need at any given time. Having a generic template gives us the flexibility to add/remove new features for to build system. Want to hook up your Slack bot? Piece of cake!
- 'curl -X POST -H ''Content-type: application/json'' --data '' ''''{"text":"🚀 ''${CI_PROJECT_NAME}'' ''${PLATFORM}'' *''${CI_COMMIT_REF_NAME}''* is now available for download: <https://gitlab.com/<my-workspace>/<my-app>/-/jobs/''${CI_JOB_ID}''/artifacts/download>" }'''' '' ${SLACK_HOOK}'
Simply trigger the build process from the Gitlab interface to have your files delivered directly to the Gitlab server.
Wrapping up
Manually building your branches on devices and simulators with Ionic can sometimes take a tremendous amount of time, especially when different developers work on the same project, or when a non-technical QA has to go through the process every time.
Automating builds is the best and sufire way to provide constant delivery of your software builds and will help you optimize turnaround times and focus more on what matters in your own Ionic development lifecycle. Happy continuous integration!