| #!/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 = ["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="notedb", 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 = [:] |
| 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') { |
| |
| 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) |
| } |
| } |