summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuca Milanesio <luca.milanesio@gmail.com>2019-10-17 19:29:48 +0100
committerLuca Milanesio <luca.milanesio@gmail.com>2019-10-17 19:30:00 +0100
commit2737147eb707c810a0a72875804c5cea30f83a9e (patch)
treefd52e5fd0e60b68ce51859c84134d251e073cb7c
parent3bdaac6ce170c8ff6a0d1fd8e1f54dc2a03fd61c (diff)
parent92ef8948fa8bea9b82e901bfbe36578ffc6d5601 (diff)
Merge branch 'stable-2.14' into stable-2.15
* stable-2.14: Don't sort messages returned by Jenkins Fix string formatting in Jenkinsfile Fix RejectedAccessException during posting checks in Jenkinsfile Remove unnecessary SCM checkout from Jenkinsfile Fix expected response code when posting checks in Jenkinsfile Use basic auth for posting checks Add Jenkinsfile for verification pipeline Change-Id: I7f2859e888fd8f6a4287fa98468e3723c4932af6
-rw-r--r--Jenkinsfile310
1 files changed, 310 insertions, 0 deletions
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000000..7495540247
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,310 @@
+#!/usr/bin/env groovy
+
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import groovy.json.JsonSlurper
+import groovy.json.JsonOutput
+
+class Globals {
+ static final String gerritUrl = "https://gerrit-review.googlesource.com/"
+ static final String gerritCredentialsId = "gerrit-review.googlesource.com"
+ static final long curlTimeout = 10000
+ static final int waitForResultTimeout = 10000
+ static final String gerritRepositoryNameSha1Suffix = "-a6a0e4682515f3521897c5f950d1394f4619d928"
+ static final resTicks = [ 'ABORTED':'\u26aa', 'SUCCESS':'\u2705', 'FAILURE':'\u274c' ]
+}
+
+class Change {
+ static String sha1 = ""
+ static String number = ""
+ static String branch = ""
+ static String ref = ""
+ static String patchNum = ""
+ static String url = ""
+}
+
+class Build {
+ String url
+ String result
+
+ Build(url, result) {
+ this.url = url
+ this.result = result
+ }
+}
+
+class Builds {
+ static Set<String> modes = []
+ static Build codeStyle = null
+ static Map verification = [:]
+}
+
+class GerritCheck {
+ String uuid
+ String changeNum
+ String sha1
+ Build build
+
+ GerritCheck(name, changeNum, sha1, build) {
+ this.uuid = "gerritforge:" + name.replaceAll("(bazel/)", "") +
+ Globals.gerritRepositoryNameSha1Suffix
+ this.changeNum = changeNum
+ this.sha1 = sha1
+ this.build = build
+ }
+
+ def getCheckResultFromBuild() {
+ switch(build.result) {
+ case 'SUCCESS':
+ return "SUCCESSFUL"
+ case 'NOT_BUILT':
+ case 'ABORTED':
+ return "NOT_STARTED"
+ case 'FAILURE':
+ case 'UNSTABLE':
+ default:
+ return "FAILED"
+ }
+ }
+
+ def createCheckPayload() {
+ return JsonOutput.toJson([
+ checker_uuid: uuid,
+ state: getCheckResultFromBuild(),
+ url: "${build.url}consoleText"
+ ])
+ }
+}
+
+def postCheck(check) {
+ def gerritPostUrl = Globals.gerritUrl +
+ "a/changes/${check.changeNum}/revisions/${check.sha1}/checks"
+
+ try {
+ def json = check.createCheckPayload()
+ httpRequest(httpMode: 'POST', authentication: Globals.gerritCredentialsId,
+ contentType: 'APPLICATION_JSON', requestBody: json,
+ validResponseCodes: '200', url: gerritPostUrl)
+ echo "----------------------------------------------------------------------------"
+ echo "Gerrit Check: ${check.uuid}=" + check.build.result + " to change " +
+ check.changeNum + "/" + check.sha1
+ echo "----------------------------------------------------------------------------"
+ } catch(Exception e) {
+ echo "ERROR> Failed to post check results to Gerrit: ${e}"
+ }
+}
+
+def queryChangedFiles(url, changeNum, sha1) {
+ def queryUrl = "${url}changes/${Change.number}/revisions/${Change.sha1}/files/"
+ def response = httpRequest queryUrl
+ def files = response.getContent().substring(5)
+ def filesJson = new JsonSlurper().parseText(files)
+ return filesJson.keySet().findAll { it != "/COMMIT_MSG" }
+}
+
+def queryChange(){
+ def requestedChangeId = env.BRANCH_NAME.split('/')[1]
+ def queryUrl = "${Globals.gerritUrl}changes/${requestedChangeId}/?pp=0&O=3"
+ def response = httpRequest queryUrl
+ def jsonSlurper = new JsonSlurper()
+ return jsonSlurper.parseText(response.getContent().substring(5))
+}
+
+def getChangeMetaData(){
+ def changeJson = queryChange()
+ Change.sha1 = changeJson.current_revision
+ Change.number = changeJson._number
+ Change.branch = changeJson.branch
+ def revision = changeJson.revisions.get(Change.sha1)
+ Change.ref = revision.ref
+ Change.patchNum = revision._number
+ Change.url = Globals.gerritUrl + "#/c/" + Change.number + "/" + Change.patchNum
+}
+
+def collectBuildModes() {
+ Builds.modes = ["reviewdb"]
+ def changedFiles = queryChangedFiles(Globals.gerritUrl, Change.number, Change.sha1)
+ def polygerritFiles = changedFiles.findAll { it.startsWith("polygerrit-ui") ||
+ it.startsWith("lib/js") }
+
+ if(polygerritFiles.size() > 0 || changedFiles.contains("WORKSPACE")) {
+ if(changedFiles.size() == polygerritFiles.size()) {
+ println "Only PolyGerrit UI changes detected, skipping other test modes..."
+ Builds.modes = ["polygerrit"]
+ } else {
+ println "PolyGerrit UI changes detected, adding 'polygerrit' validation..."
+ Builds.modes += "polygerrit"
+ }
+ }
+}
+
+def prepareBuildsForMode(buildName, mode="reviewdb", retryTimes = 1) {
+ def propagate = retryTimes == 1 ? false : true
+ return {
+ stage("${buildName}/${mode}") {
+ catchError{
+ retry(retryTimes){
+ def slaveBuild = build job: "${buildName}", parameters: [
+ string(name: 'REFSPEC', value: Change.ref),
+ string(name: 'BRANCH', value: Change.sha1),
+ string(name: 'CHANGE_URL', value: Change.url),
+ string(name: 'MODE', value: mode),
+ string(name: 'TARGET_BRANCH', value: Change.branch)
+ ], propagate: propagate
+ if (buildName == "Gerrit-codestyle"){
+ Builds.codeStyle = new Build(
+ slaveBuild.getAbsoluteUrl(), slaveBuild.getResult())
+ } else {
+ Builds.verification[mode] = new Build(
+ slaveBuild.getAbsoluteUrl(), slaveBuild.getResult())
+ }
+ }
+ }
+ }
+ }
+}
+
+def collectBuilds() {
+ def builds = [:]
+ builds["Gerrit-codestyle"] = prepareBuildsForMode("Gerrit-codestyle")
+ Builds.modes.each {
+ builds["Gerrit-verification(${it})"] = prepareBuildsForMode("Gerrit-verifier-bazel", it)
+ }
+ return builds
+}
+
+def findFlakyBuilds() {
+ def flaky = Builds.verification.findAll { it.value.result == null ||
+ it.value.result != 'SUCCESS' }
+
+ if(flaky.size() == Builds.verification.size()) {
+ return []
+ }
+
+ def retryBuilds = []
+ flaky.each {
+ def mode = it.key
+ Builds.verification.remove(mode)
+ retryBuilds += mode
+ }
+
+ return retryBuilds
+}
+
+def getLabelValue(acc, res) {
+ if(res == null || res == 'ABORTED') {
+ return 0
+ }
+ switch(acc) {
+ case 0: return 0
+ case 1:
+ if(res == null) {
+ return 0;
+ }
+ switch(res) {
+ case 'SUCCESS': return +1;
+ case 'FAILURE': return -1;
+ default: return 0;
+ }
+ case -1: return -1
+ }
+}
+
+def setResult(resultVerify, resultCodeStyle) {
+ if (resultVerify == 0 || resultCodeStyle == 0) {
+ currentBuild.result = 'ABORTED'
+ } else if (resultVerify == -1 || resultCodeStyle == -1) {
+ currentBuild.result = 'FAILURE'
+ } else {
+ currentBuild.result = 'SUCCESS'
+ }
+}
+
+def findCodestyleFilesInLog(build) {
+ def codeStyleFiles = []
+ def needsFormatting = false
+ def response = httpRequest "${build.url}consoleText"
+ response.content.eachLine {
+ needsFormatting = needsFormatting || (it ==~ /.*Need Formatting.*/)
+ if(needsFormatting && it ==~ /\[.*\]/) {
+ codeStyleFiles += it.substring(1,it.length()-1)
+ }
+ }
+
+ return codeStyleFiles
+}
+
+def createCodeStyleMsgBody(build, label) {
+ def codeStyleFiles = findCodestyleFilesInLog(build)
+ def formattingMsg = label < 0 ? ('The following files need formatting:\n ' +
+ codeStyleFiles.join('\n ')) : 'All files are correctly formatted'
+ def url = build.url + "consoleText"
+
+ return "${Globals.resTicks[build.result]} $formattingMsg\n (${url})"
+}
+
+def createVerifyMsgBody(builds) {
+ def msgList = builds.collect { type, build -> [
+ 'type': type, 'res': build.result, 'url': build.url + "consoleText" ]
+ }
+
+ return msgList.collect {
+ "${Globals.resTicks[it.res]} ${it.type} : ${it.res}\n (${it.url})"
+ } .join('\n')
+}
+
+node ('master') {
+
+ stage('Preparing'){
+ gerritReview labels: ['Verified': 0, 'Code-Style': 0]
+
+ getChangeMetaData()
+ collectBuildModes()
+ }
+
+ parallel(collectBuilds())
+
+ stage('Retry Flaky Builds'){
+ def flakyBuildsModes = findFlakyBuilds()
+ if (flakyBuildsModes.size() > 0){
+ parallel flakyBuildsModes.collectEntries {
+ ["Gerrit-verification(${it})" :
+ prepareBuildsForMode("Gerrit-verifier-bazel", it, 3)]
+ }
+ }
+ }
+
+ stage('Report to Gerrit'){
+ resCodeStyle = getLabelValue(1, Builds.codeStyle.result)
+ gerritReview(
+ labels: ['Code-Style': resCodeStyle],
+ message: createCodeStyleMsgBody(Builds.codeStyle, resCodeStyle))
+ postCheck(new GerritCheck("codestyle", Change.number, Change.sha1, Builds.codeStyle))
+
+ def verificationResults = Builds.verification.collect { k, v -> v }
+ def resVerify = verificationResults.inject(1) {
+ acc, build -> getLabelValue(acc, build.result)
+ }
+ gerritReview(
+ labels: ['Verified': resVerify],
+ message: createVerifyMsgBody(Builds.verification))
+
+ Builds.verification.each { type, build -> postCheck(
+ new GerritCheck(type, Change.number, Change.sha1, build)
+ )}
+
+ setResult(resVerify, resCodeStyle)
+ }
+}