CI Part 1 -- Tool Journey and Jenkins Intro
Earlier this week, while buliding a React Native Mobile app and corresponding testing suite, I played around with different Continuous Integration (CI) and automation tools. Here’s some take-aways from my journey:
Bash Scripting
My first stop on the automation/CI train was with Bash Scripting. After a few mind numbing repetitions of re-running a dozen shell commands to build my app, I had had enough. A single LLM prompt1 and a few tweeks later, I had a script that would build and test my app in a single command.
The beauty: there was literally zero configuation required2 and I could iterate very quickly by directly altering the script. I loved this solution!
That said, I would be remiss if I didn’t quickly highlight scripts limitations in the context of CI. Namely, they lack features needed for robust CI processes such as logging, error handling, history, environment specification, authorization, and integrations with other tools. So while I will use bash scripts for personal projects, I will turn elsewhere for CI tools to use in real-world applications.
GitHub Actions
GitHub Actions (GHA) were my next stop on the quest stock my toolbelt with CI essentials. As a longtime GitHub user I have heard a lot about GHA, but I’m embarrassed to say that I had never used them.
Long story short, I expected the tool to be userfriendly and easy to learn, and it was! Truly, it beat expectations and it’s usefulness for CI were abundantly apparent.
These properties and features stuck out to me:
- By definition, GHA are integrated into your project’s code repository and source control management. And seeing that CI workflows typically run when code is committed, placing CI here essentially compresses two steps in the development process.
- The syntax is simple and similar to bash scripting, making the learning curve very shallow.
- Literally zero config. Simply add a workflow file to your codebase and the action will run on your specified trigger.
- It has a suprising amount of flexibility. For example, you can run workflows on a wide variety of virtual machines and operating systems that include MacOS, which is necesary for building iOS apps.
- There is a generous free tier.
- Being cloud based allows for it to have different triggers and to integrate with virtually any other service. Additonally, configuring environment variables and secrets is a breeze.
Here is my GitHub Action workflow to build and test my app on iOS.
I’m fully bought in and will turn to GHA for all but the most complex and customized CI workflows. Which leads me to my next tool…
Jenkins
The final stop on my CI Toolkit Journey was Jenkins — A commercial-grade framework focused exclusively on CI and automation. While the config was more cumbersome than GitHub or Bash Scripts, it was still relatively straightforward and is very well documented. The payoff of having to authorize and configure Jenkins is that it is super versatile and runs a server locally with a great UI where you can access extensive logs, build histories, and every other metric you would want in your CI workflow.
While it was overkill for my project, it was clear why commercial teams would use Jenkins for CI.
Choosing Your CI Tool
After only a few days of use, I would feel extremely comfortable using any one of these tools in future projects. However, the question remains: when to use one as opposed to the its alternatives? My rule of thumb is quite simple: If you’re working alone and locally -> bash scripting. If you’re working on a team and have minimal custustimization, go with GitHub Actions. For full control, customization, and advanced CI features, Jenkins is your man.
Jenkins Pipeline File
Finally, lets take a look at a Jenkins Pipeline File3 as a way of understanding the basics of scripting this powerful CI tool.
The full file can be found here. Here’s a quick rundown of the basics:
Agents
pipeline {
agent any
...
At the beginning of your Jenkinsfile, you declare where the job will be executed by specifying an agent. This gives you a ton of flexibility as you can execute on your local machine, docker, or cloud VMs. Docs
Environment
pipeline {
agent any
environment {
// Base paths
PROJECT_ROOT = "${WORKSPACE}"
APK_PATH = "${PROJECT_ROOT}/android/app/build/outputs/apk/debug/app-debug.apk"
FLOW_PATH = "${PROJECT_ROOT}/.maestro/flow.yaml"
}
The environment section sets key-value pairs to be used within a job. A helpful tip is that the WORKSPACE keyword is a special environment variable that points to the current working directory where the pipeline is being executed.
Stages and Steps
pipeline {
agent any
environment {
...
}
stages {
stage('Checkout SCM') {
steps {
echo 'Checking out'
}
...
}
stage('Install Android Dependencies') {
...
}
stage('Build Android APK') {
...
}
Stages are contained in the stages
directive, take place sequentially, and are where discrete blocks of work take place. Steps are one or more commands to be executed in a stage and are the unit where work takes place.
Types of Steps
While there are a ton of predefined steps that make Jenkins versitile, here are four that found myself using often:
sh
- Used to run shell commands
checkout scm
- Used to locate and get the source code for the pipeline
dir
- Used to change the current working directory
script
- Useful for combining code4 and commands
In my next post, I will explore setting up a docker container as an agent, authentication and security, and more pipeline options.
-
Note: Be sure to specify the system architecture in your prompt as scripts vary based on your underlying machine and os. ↩︎
-
Other than running
chmod +x <script-name>.sh
to make the script executable. ↩︎ -
Note there are two styles of pipeline files: Declarative and Scripted. I used the Declarative type for this project. It is suggested for most use cases, easier to read, and less complex. ↩︎
-
Note: Groovy is the language used in Jenkinsfiles. ↩︎