def buildAgent = 'ec2-android-toolchain'
pipeline {
agent none
options{
timestamps()
timeout(time: 2, unit: 'HOURS')
parallelsAlwaysFailFast()
}
parameters {
string(
defaultValue: "",
description: 'If set, reruns the device test stages only,using artifact from specified build id, skipping all other stages',
name: "RERUN_DEVICETEST",
)
booleanParam(
defaultValue: false,
description: 'Publish release build to crashlytics beta',
name: "PUBLISH_BETA",
)
}
environment {
//Disable gradle daemon
GRADLE_OPTS = "-Dorg.gradle.daemon=false"
}
stages {
stage('Jenkins') {
parallel {
stage('Release: build') {
when {
beforeAgent true
equals expected: PipelineMode.RELEASE, actual: getPipelineMode()
expression { !isDeviceTestOnly() }
}
agent {
label buildAgent
}
environment {
// Number of build numbers to allocate when building the project
// (release builds only)
O2_BUILD_VERSION_BLOCK_SIZE = "64"
ORG_GRADLE_PROJECT_publishToBeta = "${params.PUBLISH_BETA ? '1' : '0'}"
}
steps {
installJenkinsSshKey()
checkoutRepo(steps)
sh "./other/jenkins/job_release_build.sh"
}
post {
always {
addArtifacts()
processJvmJunitResults()
}
}
}
stage('Buddy: build apks') {
when {
beforeAgent true
equals expected: PipelineMode.BUDDY, actual: getPipelineMode()
expression { !isDeviceTestOnly() }
}
agent {
label buildAgent
}
steps {
installJenkinsSshKey()
checkoutRepo(steps)
sh '''#!/bin/bash
. "other/jenkins/build_lib.sh"
"$ST_ROOT/other/jenkins/initialize_build.sh"
execGradle --stacktrace jenkinsBuddyStageBuild
"$ST_ROOT/other/jenkins/job_build_upload_sharedartifacts.sh"
'''
}
post {
always {
addArtifacts()
}
}
}
stage('Buddy: jvm verify') {
when {
beforeAgent true
equals expected: PipelineMode.BUDDY, actual: getPipelineMode()
expression { !isDeviceTestOnly() }
}
agent {
label buildAgent
}
steps {
installJenkinsSshKey()
checkoutRepo(steps)
sh '''#!/bin/bash
. "other/jenkins/build_lib.sh"
"$ST_ROOT/other/jenkins/initialize_build.sh"
execGradle --stacktrace jenkinsBuddyStageVerifyAndTest
'''
}
post {
always {
processJvmJunitResults()
}
}
}
}
}
}
post {
failure {
notifyUnhealthy()
}
fixed {
notifyFixed()
}
unstable {
notifyUnhealthy()
}
}
}
def checkoutRepo(steps) {
steps.sh '''#!/bin/bash
git submodule init
git submodule update
'''
}
/**
* This should only be used on non-ec2 agents. Roles are used to properly authorize
* agents running in ev2
*
* @param cl
* @return
*/
@SuppressWarnings("GroovyInfiniteRecursion") // arg list is different and doesn't recurse
def withAwsCredentials(Closure cl) {
withAwsCredentials('**********', cl)
}
/**
* Installs the jenkins ssh key managed by jenkins
*/
def installJenkinsSshKey() {
installSshKey('jenkins-ssh-key',
"${env.HOME}/.ssh/id_rsa")
}
/**
* Installs an ssh managed by jenkins to the specified path
*
* @param credentialId
* @param destPath
*/
def installSshKey(String credentialId, String destPath) {
withCredentials([sshUserPrivateKey(
keyFileVariable: 'THEKEY',
credentialsId: credentialId)]) {
sh """
cp "$THEKEY" "$destPath"
chmod 0600 "$destPath"
"""
}
}
enum PipelineMode {
BUDDY, // Buddy mode for pipeline
RELEASE, // Release mode for pipeline
UNKNOWN // Unknown/unsupported mode. All stages skipped.
}
/**
* Each jenkins pipeline execution runs in a single mode
*/
def getPipelineMode() {
if (env.CHANGE_ID != null) {
return PipelineMode.BUDDY
} else if (isMainlineBranch()) {
return PipelineMode.RELEASE
} else {
return PipelineMode.UNKNOWN
}
}
/**
* Returns true if any of the current branches are a mainline branch, otherwise false
*/
def isMainlineBranch() {
def mainlines = [ /^master$/, /^release\/.*$/, /^topic\/.*$/ ]
return null != scm.branches.find { branch ->
mainlines.find { mainlineRegEx ->
return branch ==~ mainlineRegEx
}
}
}
/**
* Return true if only the device test stages should run
* @return
*/
def isDeviceTestOnly() {
return !params.RERUN_DEVICETEST.isEmpty()
}
/**
* Returns true if the device test stage(s) should run
*/
def shouldRunDeviceTest() {
return getPipelineMode() != PipelineMode.UNKNOWN
}
/**
* Given a steps element, configures it to run the given shard indexes
*/
def defineStepsForDeviceTest(steps, int totalShards, int...shardIndexes) {
steps.with {
checkoutRepo(steps)
withAwsCredentials {
def url = getArtifactShareUrl("build", deviceTestInputBuildId())
sh "./other/jenkins/devicetest_ensure_ready.sh '$url'"
}
script {
// PR builds merge the PR HEAD into the target branch. In this case
// we just want to pass the PR branch to the manual job. If
// it isn't a PR build, the pass the commit.
def commitToBuild = env.CHANGE_BRANCH?.trim()
if (!commitToBuild) {
commitToBuild = env.GIT_COMMIT
}
build job: "/Manual Builds/speedtestnet-android/deviceTest",
parameters: [
string(name: 'O2_INPUT_URL', value:
getArtifactShareUrl("build", deviceTestInputBuildId())),
string(name: 'O2_OUTPUT_URL', value:
getArtifactShareUrl("deviceTest", deviceTestInputBuildId())),
string(name: 'O2_COMMIT', value: commitToBuild),
string(name: 'O2_BRANCH', value: env.GIT_BRANCH),
string(name: 'O2_SHARD_COUNT', value: "${totalShards}"),
string(name: 'O2_SHARD_INDEXES', value: shardIndexes.join(','))
],
// Don't fail the stage if downstream job fails. We want to process
// test results from unstable builds in our pipeline
propagate: false
}
}
}
/**
* Build id to use as input for device test stage
*/
def deviceTestInputBuildId() {
return params.RERUN_DEVICETEST.isEmpty()
? env.BUILD_ID
: params.RERUN_DEVICETEST
}
/**
* Get the artifact share path for the given stage.
* @param stage the stage with which the path is associated
* @param buildId (optional) build id to use, defaults to current build id
*/
def getArtifactShareUrl(String stage, String buildId = env.BUILD_ID) {
return sh(returnStdout: true, script: "./other/jenkins/share_artifacts.sh "
+ "--action getShareUrl "
+ "--jobName ${env.JOB_NAME} --jobBuild ${buildId} --jobStage ${stage}"
).trim()
}
/**
* Process junit results from the jvm tests
*/
def processJvmJunitResults() {
junit '**/build/test-results/**/*.xml'
}
/**
* Add the archived apk's as artifacts
*/
def addArtifacts() {
archiveArtifacts artifacts:"Mobile4/build/outputs/apk/**/*.apk", fingerprint:true
}
def notifyUnhealthy() {
if (getPipelineMode() != PipelineMode.RELEASE) {
return
}
slackSend(channel: '#team-android-dev',
color: 'danger',
message: "<${currentBuild.absoluteUrl}|${currentBuild.fullDisplayName}>"
+ " :jenkins_angry: is unhealthy.")
}
def notifyFixed() {
if (getPipelineMode() != PipelineMode.RELEASE) {
return
}
slackSend(channel: '#team-android-dev',
color: 'good',
message: "<${currentBuild.absoluteUrl}|${currentBuild.fullDisplayName}>"
+ ":jenkins: is healthy again.")
}