Skip to content
Jose S edited this page Nov 9, 2020 · 3 revisions

Setting up CI/CD using SemaphoreCI

Git

Super Lazy Minimalist Dingdong EZ Git Branching

main
dev
feature/ticket-number OR short-description [feature/ft-69 OR feature/nice]
chore/ticket-number OR short-description [chore/ch-69 OR chore/nice]

Flow?

  • Initialize project using main branch
  • Branch out and set default branch to dev
  • Branch out per feature/chore task
  • After completing the task(unit tests included), create a PR back to dev
  • Merge once passed
  • Profit?
  • For QA testing/internal builds, create a PR from dev to main
  • For Release builds, tag the main

Triggers

  • All pull requests to dev will automatically trigger CI(please write unit tests before creating a PR mkay?)
  • Commits to main will trigger CI + auto promote to staging/internal deploy (upload to firebase for testing)
  • Tags in main will trigger CI + auto promote to prod deploy (upload to google play)

Android

Gradle Setup

Create a file keystore.properties inside your project folder. Fill in the following values:

debugStorePassword=samplePassword
debugKeyAlias=sampleAlias
debugKeyPassword=sampleKeyPassword

storePassword=samplePassword
alias=sampleAlias
keyPassword=sampleKeyPassword

In you app/build.gradle: add the following code:

def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

...
android {
…
  signingConfigs {
    debug {
       storeFile file("../debug_key.jks")
       storePassword keystoreProperties.getProperty("debugStorePassword")
       keyAlias keystoreProperties.getProperty("debugKeyAlias")
       keyPassword keystoreProperties.getProperty("debugKeyPassword")
     }
     release {
       storeFile file("../release_key.jks")
       storePassword keystoreProperties.getProperty("storePassword")
       keyAlias keystoreProperties.getProperty("alias")
       keyPassword keystoreProperties.getProperty("keyPassword")
     }
  }
}

Firebase Setup

  1. Add the firebase dependency to your project level build.gradle
dependencies{
  classpath 'com.google.firebase:firebase-appdistribution-gradle:2.0.1'
}
  1. Use the plugin on your app level build.gradle
apply plugin: 'com.google.firebase.appdistribution'
  1. Create a service account via https://console.cloud.google.com/
  2. Download and reference the json key. Add the following to your app level build.gradle. Release notes and testers properties are optional.
 buildTypes {
        release {
            firebaseAppDistribution {
                releaseNotesFile="/path/to/releasenotes.txt" 
                serviceCredentialsFile="path/to/json/key.json"
                testers="ali@example.com, bri@example.com, cal@example.com"
            }
        }
    }

SemaphoreCI Setup

Avoid pushing release keystore, credentials, json configs, etc to the repo. Add them as a secret.

Setup Semaphore CI/CD on your project. Use the following configurations as semaphore.yml:

version: v1.0
name: Verification Pipeline

agent:
 machine:
   type: e1-standard-2
   os_image: ubuntu1804

 containers:
   - name: main
     image: 'semaphoreci/android:29'

auto_cancel:
 running:
   when: 'true'

global_job_config:
 env_vars:
   - name: ADB_INSTALL_TIMEOUT
     value: '30'
 secrets:
   - name: android-release-keys

 prologue:
   commands:
     - checkout
     - cp /home/semaphore/android-release-key.jks android-release-key.jks
     - cp /home/semaphore/android_release_credentials.properties keystore.properties
     - cp /home/semaphore/google_services.json app/google-services.json
     - cache restore gradle-wrapper
     - cache restore gradle-cache
     - cache restore android-build

blocks:
 - name: Build
   task:
     jobs:
       - name: Build Project
         commands:
           - ./gradlew assemble bundle
     epilogue:
       on_pass:
         commands:
           - cache clear
           - cache store gradle-wrapper ~/.gradle/wrapper
           - cache store gradle-cache ~/.gradle/caches
           - cache store android-build ~/.android/build-cache

 - name: Verification
   task:
     jobs:
       - name: Analyze Code
         commands:
           - ./gradlew lint
       - name: Unit Tests
         commands:
           - ./gradlew test
     epilogue:
       always:
         commands:
           - artifact push job --expire-in 2w --destination reports/ app/build/reports/


promotions:

 - name: Deploy to Firebase App Distribution
   pipeline_file: deploy_internal.yml
   auto_promote:
     when: result = 'passed' AND branch =~ 'release/*'

 - name: Deploy to Google Play
   pipeline_file: deploy_prod.yml
   auto_promote:
      when: result = 'passed' AND branch = 'master'

deploy_internal.yml

version: v1.0
name: Deployment Pipeline - Internal
agent:
  machine:
    type: e1-standard-2
    os_image: ubuntu1804
  containers:
    - name: main
      image: 'semaphoreci/android:29'
auto_cancel:
  running:
    when: 'true'
global_job_config:
  secrets:
    - name: android-release-key
  prologue:
    commands:
      - checkout
      - cp /home/semaphore/android_release_key.jks release_key.jks
      - cp /home/semaphore/android_release_credentials.properties keystore.properties
      - cp /home/semaphore/google_services.json app/google-services.json
      - export GOOGLE_APPLICATION_CREDENTIALS=/home/semaphore/firebase_key.json
      - cache restore gradle-wrapper
      - cache restore gradle-cache
      - cache restore android-build
blocks:
  - name: Internal Deployment
    task:
      jobs:
        - name: Deploy to Firebase
          commands:
            - ./gradlew assembleDebug appDistributionUploadDebug

TODO:

Deconstructing the SemaphoreCI config + gathering/sending test coverage data for multi module projects using jacoco and uploading them to codeclimate