Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
My example Jenkins Pipeline setup for Android app project
#!/usr/bin/groovy
/*
* Copyright (c) 2016, Andrey Makeev <amaksoft@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and|or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Method for pushing build results to git repo via SSH. (SSH Agent Plugin required)
* To keep things going while we wait for official Git Publish support for pipelines (https://issues.jenkins-ci.org/browse/JENKINS-28335)
*
* Example call (Inline values):
* pushSSH(branch: "master", commitMsg: "Jenkins build #${env.BUILD_NUMBER}", tagName: "build-${env.BUILD_NUMBER}", files: ".", config: true, username: "Jenkins CI", email: "jenkins-ci@example.com");
*
* Example call (Environment variables):
* env.BRANCH_NAME = "mycoolbranch"// BRANCH_NAME is predefined in multibranch pipeline job
* env.J_GIT_CONFIG = "true"
* env.J_USERNAME = "Jenkins CI"
* env.J_EMAIL = "jenkins-ci@example.com"
* env.J_CREDS_IDS = '02aa92ec-593e-4a90-ac85-3f43a06cfae3' // Use credentials id from Jenkins (Does anyone know a way to reference them by name rather than by id?)
* ...
* pushSSH(commitMsg: "Jenkins build #${env.BUILD_NUMBER}", tagName: "build-${env.BUILD_NUMBER}", files: ".");
*
* @param args Map with followinf parameters:
* commitMsg : (String) commit message
* files : (String) list of files to push (space serparated) (Won't push files if not specified)
* tagName : (String) tag name (won't push tag if not specified)
* branch : (String) git branch (Will use env variable BRANCH_NAME if not specified)
* creds_ids : (List<String>) credentials ids (Will use env variable J_CREDS_IDS if not specified) (haven't figured out yet how to resolve credentials name)
* configure : (boolean) configure git publisher (username, email). (If not specified will check out env variable J_GIT_CONFIG)
* username : (String) committer name (If not specified will check out env variable J_USERNAME)
* email : (String) committer email (If not specified will check out env variable J_EMAIL)
*/
def pushSSH(Map args) {
String tagName = args.tagName
String commitMsg = args.commitMsg
String files = args.files
String branch = args.branch != null ? args.branch : env.BRANCH_NAME;
List<String> creds_ids = args.creds != null ? args.creds : env.J_CREDS_IDS.tokenize(" ");
boolean config; // Boolean.parseBoolean() is forbidden in this DSL
if(args.config != null)
config = args.config
else if (env.J_GIT_CONFIG.toLowerCase() == "true") {
config = true
}else {
echo "git config = ${config}, J_GIT_CONFIG = ${env.J_GIT_CONFIG}, assuming false"
config = false;
}
String username = args.username != null ? args.username : env.J_USERNAME;
String email = args.email != null ? args.email : env.J_EMAIL;
if (tagName == null && files == null) {
echo "Neither tag nor files to push specified. Ignoring.";
return;
}
if (branch == null)
error "Error. Invalid value: git branch = ${branch}";
if(config) {
if (username == null || email == null || creds_ids == null)
error "Error. Invalid value set: { username = ${username}, email = ${email}, credentials = ${creds_ids} }";
sh """ git config push.default simple
git config user.name \"${username}\"
git config user.email \"${email}\"
"""
}
sshagent(creds_ids) {
if (files != null) {
sh """ git add . && git commit -m \"${commitMsg}\" || true
git push origin HEAD:refs/heads/${branch} || true
"""
}
if (tagName != null) {
sh """ git tag -fa \"${tagName}\" -m \"${commitMsg}\"
git push -f origin refs/tags/${tagName}:refs/tags/${tagName}
"""
}
}
}
return this;
#!/usr/bin/groovy
/*
* Copyright (c) 2016, Andrey Makeev <amaksoft@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and|or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Env variables for git push
env.J_USERNAME = "Jenkins CI"
env.J_EMAIL = "jenkins-ci@example.com"
env.J_GIT_CONFIG = "true"
// Use credentials id from Jenkins (Does anyone know a way to reference them by name rather than by id?)
env.J_CREDS_IDS = '02aa92ec-593e-4a90-ac85-3f43a06cfae3'
def gitLib
timestampedNode ("AndroidBuilder") {
stage ("Checkout") {
checkout scm
sh "chmod a+x ./gradlew"
gitLib = load "git_push_ssh.groovy"
}
stage ("Build") {
// Check environment (We define ANDROID_HOME in node settings)
if (env.ANDROID_HOME == null || env.ANDROID_HOME == "") error "ANDROID_HOME not defined"
if (env.JAVA_HOME == null || env.JAVA_HOME == "") error "JAVA_HOME not defined"
// Default parameters (In case file is unreadable or missing)
def d = [versionName: 'unversioned', versionCode: '1']
// Read properties from file (Right now we only keep versionName and VersionCode there)
HashMap<String, Object> props = readProperties defaults: d, file: 'gradle.properties'
// Optional user input to override parameters
def userInput
try {
timeout(time: 60, unit: 'SECONDS') {
userInput = input( id:'userInput', message: 'Override build parameters?', parameters: [
string(defaultValue: props.versionName, description: 'App version (without build number)', name: 'versionName'),
string(defaultValue: props.versionCode, description: 'Version code (for GooglePlay Store)', name: 'versionCode')
])
logOverrides(userInput, props, "manual_override.log")
props.putAll(userInput)
echo("Parameters entered : ${userInput.toString()}")
}
} catch (Exception e) {
echo "User input timed out or cancelled, continue with default values"
}
// Change build name to current app version
currentBuild.displayName = "${props.versionName}.${env.BUILD_NUMBER}"
// Common build arguments
env.COMMON_BUILD_ARGS = " -PBUILD_NUMBER=${env.BUILD_NUMBER} -PBRANCH_NAME=${env.BRANCH_NAME}" +
" -PversionName=${props.versionName} -PversionCode=${props.versionCode}"
// Build the app
sh "./gradlew clean"
sh """./gradlew assembleDebug ${env.COMMON_BUILD_ARGS}
./gradlew assembleRelease ${env.COMMON_BUILD_ARGS}
"""
}
stage('Save artifacts and publish') {
// Save build results
step([$class: 'ArtifactArchiver', artifacts: "**/*.apk", excludes: "**/*unaligned.apk", fingerprint: true])
// Push changes and tag
gitLib.pushSSH(commitMsg: "Jenkins build #${env.BUILD_NUMBER} from ${env.BRANCH_NAME}",
tagName: "build/${env.BRANCH_NAME}/${env.BUILD_NUMBER}", files: ".", config: true);
sendEmails();
}
stage ('Crashlytics register') {
sh """./gradlew crashlyticsUploadDistributionDebug ${env.COMMON_BUILD_ARGS}
./gradlew crashlyticsUploadDistributionRelease ${env.COMMON_BUILD_ARGS}
"""
}
}
stage ('Release') {
try {
input 'Do we release this build?'
node {
echo "Push Release tag"
def date = sh(returnStdout: true, script: 'date -u +%Y%m%d').trim()// = new Date().format('yyyyMMdd') // apparently we can't use Date here, not a problem
gitLib.pushSSH(tagName: "release-${date}", commitMsg: "Jenkins promoted");
// Do your release stuff
}
} catch (Exception e) {
echo "Release cancelled"
}
}
// To send emails to everyone relevant to this build (Requires Email-ext plugin)
def sendEmails() {
emailext body: "See ${env.BUILD_URL}",
recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']],
subject: "Jenkins Build Successful",
to: "admin@example.com";
}
// To log manual overrides
@NonCPS
def logOverrides(def ov_map, def orig_map, def filename) {
def header = "# Build ${env.BUILD_NUMBER}-${env.BRANCH_NAME} manual parameters override: ";
def headWritten = false;
ov_map.each{ k, v ->
if( orig_map[k] != v ) {
if (!headWritten) {
sh "echo \"${header}\" >> ${filename}"; // apparently we are not allowed to use File.write() in this DSL
headWritten = true;
};
sh "echo \"${k}=${v}\" >> ${filename}"
}
}
}
// Taken from jenkinsci/jenkins project (https://github.com/jenkinsci/jenkins/blob/master/Jenkinsfile)
// to add timestamps to logs
def timestampedNode(String label = "master", Closure body) {
node(label) {
wrap([$class: 'TimestamperBuildWrapper']) {
body.call();
}
}
}
@aarsan

This comment has been minimized.

Copy link

@aarsan aarsan commented Oct 14, 2016

This is awesome. Thanks for putting this together. I'm curious why you need the SSH agent. Would like to use a Windows slave but SSH agent doesn't seem to work on Windows properly (rather, I can't get it to work).

@EduardoAC

This comment has been minimized.

Copy link

@EduardoAC EduardoAC commented Apr 4, 2017

This is brilliant, thanks for taking the time to share. I wonder if this code could be modularize to help to put another authentication system like withCredentials.

@ChrisWeissNike

This comment has been minimized.

Copy link

@ChrisWeissNike ChrisWeissNike commented Jan 16, 2019

https://gist.github.com/amaksoft/b17408303d69c71498eaa39ea2ee3b01#file-jenkinsfile-L34 - You can reference credentials by the ID and that doesn't actually have to be a GUID.

@sohailshaikh001

This comment has been minimized.

Copy link

@sohailshaikh001 sohailshaikh001 commented Nov 11, 2019

Can you include test cases as well? I'm not able to integrate Test cases with espresso.
Please help me out..! i'm running out of options.
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment