|  | #!/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", "notedb"] | 
|  | 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) { | 
|  | 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" | 
|  | } | 
|  | } else if(changedFiles.contains("WORKSPACE")) { | 
|  | println "WORKSPACE file changes detected, adding 'polygerrit' validation..." | 
|  | Builds.modes += "polygerrit" | 
|  | } | 
|  | } | 
|  |  | 
|  | def prepareBuildsForMode(buildName, mode="reviewdb", retryTimes = 1) { | 
|  | return { | 
|  | stage("${buildName}/${mode}") { | 
|  | def slaveBuild = null | 
|  | for (int i = 1; i <= retryTimes; i++) { | 
|  | try { | 
|  | 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: false | 
|  | } finally { | 
|  | if (buildName == "Gerrit-codestyle"){ | 
|  | Builds.codeStyle = new Build( | 
|  | slaveBuild.getAbsoluteUrl(), slaveBuild.getResult()) | 
|  | } else { | 
|  | Builds.verification[mode] = new Build( | 
|  | slaveBuild.getAbsoluteUrl(), slaveBuild.getResult()) | 
|  | } | 
|  | if (slaveBuild.getResult() == "SUCCESS") { | 
|  | break | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | def collectBuilds() { | 
|  | def builds = [:] | 
|  | if (env.GERRIT_CHANGE_NUMBER == "") { | 
|  | builds["java8"] = { -> build "Gerrit-bazel-${env.BRANCH_NAME}" } | 
|  |  | 
|  | if (env.BRANCH_NAME == "master") { | 
|  | builds["java11"] = { -> build "Gerrit-bazel-java11-${env.BRANCH_NAME}" } | 
|  | } | 
|  | } else { | 
|  | 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 = Builds.verification.findAll { it.key != 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') { | 
|  |  | 
|  | if (env.GERRIT_CHANGE_NUMBER != "") { | 
|  | stage('Preparing'){ | 
|  | gerritReview labels: ['Verified': 0, 'Code-Style': 0] | 
|  |  | 
|  | getChangeMetaData() | 
|  | collectBuildModes() | 
|  | } | 
|  | } | 
|  |  | 
|  | parallel(collectBuilds()) | 
|  |  | 
|  | if (env.GERRIT_CHANGE_NUMBER != "") { | 
|  | 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) | 
|  | } | 
|  | } | 
|  | } |