Implementing a Flutter CI/CD Pipeline in Jenkins: Part 3 (Jenkinsfile Setup)

This step assumes you have a Jenkins server initiated and that you have gone through the initial setup steps in parts 1 and 2. Additionally, this step assumes that you are on the MacOS and have correctly installed Flutter, the Android SDK, and all necessary Xcode tools, including command-line tools.

Welcome to Part 3 of a 4 part series on CI/CD with Jenkins and Flutter. In Part 3, we will go over the Jenkinsfile and explain what each part does. Below is the full file and below that I’ll segment out each part and explain what it does. Your Jenkinsfile should be in your Flutter project’s base directory.

def appname = "Runner" //DON'T CHANGE THIS. This refers to the flutter 'Runner' target.
def xcarchive = "${appname}.xcarchive"

pipeline {
    agent { label 'Flutter_v2020_05' } //Change this to whatever your flutter jenkins nodes are labeled.
    environment {
        DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer/"  //This is necessary for Fastlane to access iOS Build things.
        PATH = "/Users/jenkins/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/jenkins/Documents/flutter/bin:/usr/local/Caskroom/android-sdk/4333796//tools:/usr/local/Caskroom/android-sdk/4333796//platform-tools:/Applications/Xcode.app/Contents/Developer"
    }
    stages {
        stage ('Checkout') {
            steps {
                step([$class: 'WsCleanup'])
                checkout scm
                sh "rm -rf brbuild_ios" //This removes the previous checkout of brbuild_ios if it exists.
                sh "rm -rf ios/fastlane/brbuild_ios" //This removes the brbuild_ios from the fastlane directory if it somehow still exists
                sh "GIT_SSH_COMMAND='ssh -i ~/.ssh/ios_dependencies' git clone --depth 1 git@bitbucket.org:BottleRocket/brbuild_ios.git" //This checks out the brbuild_ios library from BottleRocket's Bitbucket
                sh "mv brbuild_ios ios/fastlane" //This moves the just checked out brbuild_ios to the fastlane directory for easier importing
            }
        }
        stage ('Flutter Doctor') {
            steps {
                sh "flutter doctor -v"
            }
        }
        stage ('Run Flutter Tests') {
            steps {
                sh "flutter test --coverage test/logic_tests.dart"
            }
        }
        stage ('Flutter Build APK') {
            steps {
                sh "flutter build apk --split-per-abi"
            }
        }
        stage('Distribute Android APK') {
            steps {
                appCenter apiToken: 'API_TOKEN_HERE',
                        ownerName: 'OWNER_NAME',
                        appName: 'APP_NAME',
                        pathToApp: 'build/app/outputs/apk/release/app-arm64-v8a-release.apk',
                        distributionGroups: 'DISTRIBUTION_GROUP'
            }
        }
        stage('Flutter Build iOS') {
            steps {
                sh "flutter build ios --release --no-codesign"
            }
        }
        stage('Make iOS IPA And Distribute') {
                steps {
                    dir('ios'){
                            sh "bundle install"
                            sh "bundle exec fastlane buildAdHoc --verbose" 
                    }
                }
        }
        stage('Cleanup') {
            steps {
                sh "flutter clean"
            }
        }
    }
}

These first two lines are defined constants for use in the rest of the file. In Flutter, the iOS project is called ‘Runner’. This name must not change and must be used in building and archiving of the iOS file.

def appname = "Runner" //DON'T CHANGE THIS. This refers to the flutter 'Runner' target
def xcarchive = "${appname}.xcarchive"

Next, the pipeline block begins, and ‘agent’ is defined. The agent section specifies where the entire Pipeline, or a specific stage, will execute in the Jenkins environment depending on where the agent section is placed. The section must be defined at the top-level inside the pipeline block, but stage-level usage is optional. For our purposes, I use a Flutter specific ‘node’ with the label, ‘Flutter_v2020_05’.

pipeline {
    agent { label 'JENKINS_NODE_NAME_HERE' } //Change this to whatever your flutter jenkins nodes are labeled.
...
}

The ‘environment’ block is very, very important. This lets you add things to the ‘path’ of Jenkins, allowing it to know where Flutter, Android, and Xcode command-line tools are. All of which are necessary for our pipeline to run successfully. An additional part of this block is the ‘DEVELOPER_DIR’ line. That line defines a folder that Fastlane looks for to build for iOS.

environment {
    DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer/"  //This is necessary for Fastlane to access iOS Build things.
    PATH = "/Users/jenkins/.rbenv/shims:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/jenkins/Documents/flutter/bin:/usr/local/Caskroom/android-sdk/4333796//tools:/usr/local/Caskroom/android-sdk/4333796//platform-tools:/Applications/Xcode.app/Contents/Developer"
}

The ‘stages’ block defines sections of the pipeline to run. If one stage fails, the entire pipeline build is marked as a failure. The first stage in this pipeline is where the bitbucket project is checked out and set up for use in future stages. This stage also contains steps that clear out the workspace so each time the pipeline runs, it has a fresh/empty folder. Next, there are 4 lines that clear out previously checked out copies of brbuild_ios, then check out a new copy and put it into the fastlane for easier importing. The brbuild_ios repo is very important to this pipeline, as it handles custom keychain generation and usage.

stages {
    stage ('Checkout') {
        steps {
            step([$class: 'WsCleanup'])
            checkout scm
            sh "rm -rf brbuild_ios" //This removes the previous checkout of brbuild_ios if it exists.
            sh "rm -rf ios/fastlane/brbuild_ios" //This removes the brbuild_ios from the fastlane directory if it somehow still exists
            sh "GIT_SSH_COMMAND='ssh -i ~/.ssh/ios_dependencies' git clone --depth 1 git@bitbucket.org:BottleRocket/brbuild_ios.git" //This checks out the brbuild_ios library from BottleRocket's Bitbucket
            sh "mv brbuild_ios ios/fastlane" //This moves the just checked out brbuild_ios to the fastlane directory for easier importing

        }
    }
...
}

The second stage executes the ‘flutter doctor’ command, which ensures the Flutter environment on your machine is fully installed and working for both iOS and Android

stage ('Flutter Doctor') {
    steps {
        sh "flutter doctor"
    }
}

Stage 3 is where the Flutter tests, written in dart, are executed. This single line also generates a coverage report.

stage ('Run Flutter Tests') {
    steps {
        sh "flutter test --coverage test/logic_tests.dart"
    }
}

In the next part(Part 4) of this series, I will explain the Android and iOS parts of the Jenkinsfile and all that is necessary for that part of the Jenkins pipeline to build/complete successfully.

Published by Steven Foskett in Healthcare, Technology, COVID, Digital Front Door, Patient Experience