-
-
Save brunocrt/67eb2c96bda1a28d4583e31825235d0f to your computer and use it in GitHub Desktop.
My example Jenkins Pipeline setup for Android app project
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/groovy | |
/* | |
* Copyright (c) 2016, Andrey Makeev <[email protected]> | |
* 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: "[email protected]"); | |
* | |
* 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 = "[email protected]" | |
* 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/groovy | |
/* | |
* Copyright (c) 2016, Andrey Makeev <[email protected]> | |
* 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 = "[email protected]" | |
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: "[email protected]"; | |
} | |
// 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