├── File to put in file server └── ci.secret.zip ├── README.md ├── app └── build.gradle ├── bitbucket-pipelines.yml ├── build.gradle ├── build.sh ├── keystore.properties └── setup_export.sh /File to put in file server/ci.secret.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChetanAshtivkar/CI-CD-in-android/c36f60663c679d43df9009d6057014dde49ab809/File to put in file server/ci.secret.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Continuous integration and delivery implementation in android using bitbucket pipelines 2 | “Continuous Integration doesn’t get rid of bugs, but it does make them dramatically easier to find and remove them.” 3 | — Martin Fowler, Chief Scientist, ThoughtWorks 4 | 5 | # Overview 6 | Continuous integration systems let you automatically build and test your app every time you check-in updates to your source control system. 7 | Whenever developers check-in code in a shared repository, it is verified by an automated build, allowing teams to detect problems early. By integrating regularly, you can detect errors quickly, and locate them more easily. 8 | 9 | # Goals 10 | 1. Create and sign a release build of UAT flavor upon a check-in in develop branch. 11 | 2. Create and sign a release builds of Production flavor upon closing a release branch and publish it on Google Play Store. 12 | 13 | # Prerequisite 14 | 1. Basic knowledge of [YAML](https://learn.getgrav.org/advanced/yaml). 15 | 2. Basic knowledge of [bash scripts](https://learn.getgrav.org/advanced/yaml). 16 | 17 | # Steps 18 | ## 1. Setup a repository in bitbucket 19 | Create a new repository in bitbucket and set up gitignore and other properties. 20 | 21 | ## 2. Create a Google Play Store listing for your project and upload a build alpha release. 22 | Create playstore listing for the project and deploy first release in alpha channel. 23 | 24 | ## 3. Create a service account to access Google Play Developer API. Create a cloud project and download the google play store credentials. 25 | We will need a service account to allow an application to deply release on behalf of us. 26 | Follow [this link](https://developers.google.com/android/management/service-account) to create a service account. 27 | 28 | ## 4. Create a properties file to save your keystore properties. Zip files and save them in your file server. 29 | 30 | ## 5. Write custom tasks to download and extract these files in build directory. 31 | 32 | 1. Create a blank keystore.properties file in the root directory. 33 | 2. Add download task. Add `undercouch` plugin in root gradle to download files from remote file server. 34 | ``` 35 | plugins { 36 | id 'de.undercouch.download' version '3.3.0' 37 | } 38 | ``` 39 | 3. Add extract task. 40 | 4. Add task to write keystore properties to local file. 41 | 42 | ## 6. Setup build types. (Release and debug) Debug builds won’t get signed. 43 | 44 | ## 7. Setup signin config. 45 | 1. Create a global variable for KeyStore Properties. 46 | 2. Load the keystore.properties file in variable. 47 | 3. Set the signing credentials 48 | ```groovy 49 | 50 | android { 51 | ... 52 | signingConfigs { 53 | release { 54 | keystoreProperties.load(new FileInputStream(file("${project.rootDir}/keystore.properties"))) 55 | storeFile file('build/MyKey.jks') 56 | keyAlias keystoreProperties['keyAlias'] 57 | keyPassword keystoreProperties['keyPassword'] 58 | storePassword keystoreProperties['storePassword'] 59 | } 60 | } 61 | ... 62 | } 63 | ``` 64 | 65 | ## 8. Enable bitbucket pipeline 66 | 1. Open your repository on bitbucket.org for which you want to implement CI. 67 | 2. Click on pipeline icon, choose language template as Java (Gradle) and add the following YAML script to it and commit changes. 68 | 69 | ``` 70 | image: uber/android-build-environment:latest 71 | 72 | pipelines: 73 | tags: 74 | ‘**’: 75 | - step: 76 | script: 77 | - ./build.sh 0 78 | - . ./setup_export.sh 79 | ########## UPLOAD TO BITBUCKET DOWNLOADS ########## 80 | # Instructions to setup the next line. https://confluence.atlassian.com/bitbucket/deploy-build-artifacts-to-bitbucket-downloads-872124574.html 81 | - curl -X POST --user "${BB_AUTH_STRING}" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/release/downloads" --form files=@"${LATEST_APK}" 82 | 83 | branches: 84 | develop: 85 | - step: 86 | script: 87 | - ./build.sh 1 88 | - . ./setup_export.sh 89 | ########## UPLOAD TO BITBUCKET DOWNLOADS ########## 90 | # Instructions to setup the next line. https://confluence.atlassian.com/bitbucket/deploy-build-artifacts-to-bitbucket-downloads-872124574.html 91 | - curl -X POST --user "${BB_AUTH_STRING}" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/develop/downloads" --form files=@"${LATEST_APK}" 92 | ``` 93 | 94 | 95 | `The first line is for declaring the build environment for building the apps. 96 | Uber is a predefined build environment by Docker which we are going to use for creating a virtual build system in bitbucket cloud.` 97 | 98 | ## 9. Add shell scripts to project and make them executable. 99 | 1. Add build.sh. 100 | 2. Add setup_export.sh. 101 | 3. Set both the shell script files and gradlew executable by running following command in terminal. 102 | 103 | ``git update-index --chmod=+x `` 104 | 105 | 4. Edit build.sh to perform tasks according to type of pipeline. 106 | 107 | ## 10. Setup Play Store deployment setup. 108 | 109 | 1. Add triplet library to root level gradle. 110 | ```groovy 111 | buildscript { 112 | 113 | repositories { 114 | mavenCentral() 115 | } 116 | 117 | dependencies { 118 | // ... 119 | classpath 'com.github.triplet.gradle:play-publisher:1.2.0' 120 | } 121 | } 122 | ``` 123 | 2. Add triplet plugin in app level gradle. 124 | ```groovy 125 | apply plugin:'com.android.application' 126 | apply plugin: 'com.github.triplet.play' 127 | ``` 128 | 3. Add playAccountConfigs and playAccountConfig in app level gradle. 129 | ```groovy 130 | android { 131 | 132 | playAccountConfigs { 133 | defaultAccountConfig { 134 | jsonFile = file('build/playstore_credentials.json') 135 | } 136 | } 137 | 138 | defaultConfig { 139 | ... 140 | playAccountConfig = playAccountConfigs.defaultAccountConfig 141 | } 142 | 143 | ... 144 | } 145 | ``` 146 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.github.triplet.play' 3 | import de.undercouch.gradle.tasks.download.Download 4 | 5 | def keystoreProperties = new Properties() 6 | 7 | android { 8 | 9 | playAccountConfigs { 10 | defaultAccountConfig { 11 | jsonFile = file('build/playstore_credentials.json') 12 | } 13 | } 14 | 15 | signingConfigs { 16 | release { 17 | keystoreProperties.load(new FileInputStream(file("${project.rootDir}/keystore.properties"))) 18 | storeFile file('build/MyKey.jks') 19 | keyAlias keystoreProperties['keyAlias'] 20 | keyPassword keystoreProperties['keyPassword'] 21 | storePassword keystoreProperties['storePassword'] 22 | } 23 | } 24 | 25 | compileSdkVersion 26 26 | buildToolsVersion "26.0.2" 27 | 28 | defaultConfig { 29 | applicationId "...." 30 | minSdkVersion 19 31 | targetSdkVersion 26 32 | versionCode 10 33 | versionName "10.0" 34 | playAccountConfig = playAccountConfigs.defaultAccountConfig 35 | } 36 | 37 | buildTypes { 38 | release { 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 41 | signingConfig signingConfigs.release 42 | } 43 | debug { 44 | minifyEnabled false 45 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 46 | } 47 | } 48 | 49 | flavorDimensions "release_channel" 50 | productFlavors { 51 | uat { 52 | dimension "release_channel" 53 | buildConfigField 'String', 'URL_ROOT', '"uat url"' 54 | } 55 | 56 | production { 57 | dimension "release_channel" 58 | buildConfigField 'String', 'URL_ROOT', '"production url"' 59 | } 60 | } 61 | 62 | task downloadZipFile(type: Download) { 63 | src 'downloadable_url_of_file_server' 64 | dest new File(buildDir, 'ci.secret.zip') 65 | } 66 | 67 | task downloadAndUnzipFile(dependsOn: downloadZipFile, type: Copy) { 68 | from zipTree(downloadZipFile.dest) 69 | into buildDir 70 | } 71 | 72 | task setupKeyStoreProperties(dependsOn: downloadAndUnzipFile) { 73 | doLast { 74 | def toConcatenate = file("build/keystore.properties") 75 | def output = file("${project.rootDir}/keystore.properties") 76 | output << toConcatenate.text 77 | } 78 | } 79 | } 80 | 81 | dependencies { 82 | implementation fileTree(include: ['*.jar'], dir: 'libs') 83 | implementation 'com.android.support:support-v4:26.1.0' 84 | implementation 'com.android.support:appcompat-v7:26.1.0' 85 | } -------------------------------------------------------------------------------- /bitbucket-pipelines.yml: -------------------------------------------------------------------------------- 1 | image: uber/android-build-environment:latest 2 | 3 | pipelines: 4 | tags: 5 | '**': 6 | - step: 7 | script: 8 | - unset ANDROID_NDK_HOME 9 | - ./build.sh 0 10 | - . ./setup_export.sh 11 | ########## UPLOAD TO BITBUCKET DOWNLOADS ########## 12 | # Instructions to setup the next line. https://confluence.atlassian.com/bitbucket/deploy-build-artifacts-to-bitbucket-downloads-872124574.html 13 | - curl -X POST --user "${BB_AUTH_STRING}" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads" --form files=@"${LATEST_APK}" 14 | 15 | branches: 16 | develop: 17 | - step: 18 | script: 19 | - unset ANDROID_NDK_HOME 20 | - ./build.sh 1 21 | - . ./setup_export.sh 22 | ########## UPLOAD TO BITBUCKET DOWNLOADS ########## 23 | # Instructions to setup the next line. https://confluence.atlassian.com/bitbucket/deploy-build-artifacts-to-bitbucket-downloads-872124574.html 24 | - curl -X POST --user "${BB_AUTH_STRING}" "https://api.bitbucket.org/2.0/repositories/${BITBUCKET_REPO_OWNER}/${BITBUCKET_REPO_SLUG}/downloads" --form files=@"${LATEST_APK}" 25 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | jcenter() 7 | mavenCentral() 8 | google() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.0.1' 12 | classpath 'com.github.triplet.gradle:play-publisher:1.2.0' 13 | //classpath 'com.google.gms:google-services:3.0.0' 14 | 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | plugins { 21 | id 'de.undercouch.download' version '3.3.0' 22 | } 23 | 24 | allprojects { 25 | repositories { 26 | jcenter() 27 | mavenCentral() 28 | google() 29 | } 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir "${ANDROID_HOME}/licenses" || true 4 | echo "8933bad161af4178b1185d1a37fbf41ea5269c55" > "${ANDROID_HOME}/licenses/android-sdk-license" 5 | echo "d56f5187479451eabf01fb78af6dfcb131a6481e" >> "$ANDROID_HOME/licenses/android-sdk-license" 6 | if [ $1 -eq 1 ] 7 | then 8 | echo "assemble debug" 9 | ./gradlew clean 10 | ./gradlew setupKeyStoreProperties 11 | ./gradlew assembleUatRelease 12 | else 13 | echo "assemble release" 14 | ./gradlew clean 15 | ./gradlew setupKeyStoreProperties 16 | ./gradlew assembleUatRelease 17 | ./gradlew publishApkUatRelease 18 | fi -------------------------------------------------------------------------------- /keystore.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /setup_export.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | LATEST_APK=$(ls -lrt ./app/build/outputs/apk/*.apk | tail -1 | awk -F" " '{ print $9 }') #Pick the latest build apk. 3 | FILE_NAME=$(basename $LATEST_APK .apk)".apk" 4 | BUILD_DATE=`date +%Y-%m-%d` #optional -- For changelog title. 5 | FILE_TITLE=$(basename $LATEST_APK .apk) #optional -- For changelog title. --------------------------------------------------------------------------------