Automate Ionic Builds With Gitlab CI: Walkthrough and Tutorial

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": ""
    }
  }
}

environment-variables

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!