Skip to content

Instantly share code, notes, and snippets.

@mseroczynski
Forked from amaksoft/Jenkinsfile
Created September 12, 2017 14:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mseroczynski/fa61c314f390056062748bbd7717ad99 to your computer and use it in GitHub Desktop.
Save mseroczynski/fa61c314f390056062748bbd7717ad99 to your computer and use it in GitHub Desktop.
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();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment