Created
November 8, 2018 16:17
-
-
Save AaronDMarasco-VSI/a1f3829869cbf3256ca52919eba1cd16 to your computer and use it in GitHub Desktop.
Jenkins detecting changes
This file contains hidden or 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
#!groovy | |
/* | |
* This file is protected by Copyright. Please refer to the COPYRIGHT file | |
* distributed with this source distribution. | |
* | |
* This file is part of OpenCPI <http://www.opencpi.org> | |
* | |
* OpenCPI is free software: you can redistribute it and/or modify it under the | |
* terms of the GNU Lesser General Public License as published by the Free | |
* Software Foundation, either version 3 of the License, or (at your option) any | |
* later version. | |
* | |
* OpenCPI is distributed in the hope that it will be useful, but WITHOUT ANY | |
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more | |
* details. | |
* | |
* You should have received a copy of the GNU Lesser General Public License | |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
import groovy.transform.Field | |
// This will return the build number of the last successful build and the changesets between then and now | |
// The returned array of maps is slightly complicated; try "JsonOutput.toJson()" on it. | |
// See also https://support.cloudbees.com/hc/en-us/articles/217630098-How-to-access-Changelogs-in-a-Pipeline-Job- | |
def usage_example = """ | |
checkout scm | |
def import_config = load("releng/jenkins/runtime/groovy/find_last_success.groovy") | |
def build_data = find_last_success() | |
""" | |
@Field changesets // stores array of maps of changeset information | |
@Field successBuild // keeps track of last Jenkins build success | |
def lastSuccessfulBuild(failedBuilds, abortedBuilds, build) { // https://devops.stackexchange.com/a/2319 | |
// echo "Debug: lastSuccessfulBuild(X,Y,${build.number}): in" | |
if ((build != null) && (build.result != 'SUCCESS')) { | |
// echo "find_last_success: Build ${build.number} was not SUCCESS." | |
failedBuilds.add(build) | |
if ('ABORTED' == build.result) | |
abortedBuilds.add(build.number) | |
// echo "Debug: lastSuccessfulBuild(X,Y,${build.number}): not a success; changeset size = ${build.changeSets.size()}" | |
for (int i = 0; i < build.changeSets.size(); i++) { | |
def entries = build.changeSets[i].items | |
for (int j = 0; j < entries.length; j++) { | |
def entry = entries[j] | |
// echo "find_last_success: Entry ${j+1}: ${entry.commitId} by ${entry.author} on ${new Date(entry.timestamp)}: ${entry.msg}" | |
def file_info = [] // Collect all files | |
def files = new ArrayList(entry.affectedFiles) | |
for (int k = 0; k < files.size(); k++) { | |
def file = files[k] | |
// echo "find_last_success: File (${k+1}/${files.size()}): ${file.editType.name} ${file.getPath()}" | |
file_info.push([action: file.editType.name, file: file.getPath()]) | |
} | |
def path_info = [] // Collect all paths | |
def paths = new ArrayList(entry.affectedPaths) | |
for (int k = 0; k < paths.size(); k++) { | |
// echo "find_last_success: Path (${k+1}/${paths.size()}): ${paths[k]}" | |
path_info.push(paths[k]) | |
} | |
changesets.push([ | |
build: build.number, // Can be 1:N relationship | |
commit: entry.commitId, | |
author: entry.author.getFullName(), // want string not User class | |
message: entry.msg, // Unfortunately, only first line | |
date: new Date(entry.timestamp), | |
files: file_info, | |
paths: path_info, | |
]) | |
} // j loop (entry in a changeset) | |
} // i loop (changeset in a build) | |
lastSuccessfulBuild(failedBuilds, abortedBuilds, build.getPreviousBuild()) | |
} | |
if ((build != null) && (build.result == 'SUCCESS')) | |
successBuild = build | |
} | |
def call() { | |
changesets = [] | |
failedBuilds = [] | |
abortedBuilds = [] as Set | |
successBuild = null | |
lastSuccessfulBuild(failedBuilds, abortedBuilds, currentBuild); | |
if (successBuild) | |
echo "find_last_success: Returning last success was ${successBuild.number} (${changesets.size()} changesets)" | |
else | |
echo "find_last_success: Could not find successful build." | |
return [ buildNumber: successBuild?successBuild.number:0, | |
changeSets: changesets, | |
failedBuilds: failedBuilds, | |
abortedBuilds: abortedBuilds, | |
] | |
} | |
return this |
This file contains hidden or 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
// Field ~~ global variable | |
import groovy.transform.Field | |
@Field previous_changesets | |
properties properties: [ // This is ugly https://stackoverflow.com/a/35471196 | |
// .... | |
parameters([ | |
// .... | |
booleanParam(defaultValue: false, description: 'Force Rebuild (Job normally aborts for various reasons, e.g. no applicable source changes)', name: 'Force Rebuild'), | |
]), | |
// .... | |
] | |
def find_last_success = load("releng/jenkins/runtime/groovy/find_last_success.groovy") | |
previous_changesets = find_last_success() | |
// .... | |
println "Job build causes: " + JsonOutput.prettyPrint(JsonOutput.toJson(currentBuild.getBuildCauses())) | |
if (params["Force Rebuild"]) // Workaround for JENKINS-43754 and JENKINS-41272 | |
return | |
def nojenkins_flags = 0 | |
def ignored_path_only_changes = 0 | |
for (int i = 0; i < previous_changesets.changeSets.size(); ++i) { | |
def this_change = previous_changesets.changeSets[i] | |
// We check each changeset for NoJenkins (only the first line is checked, unfortunately) | |
if (this_change.message.toLowerCase().contains("nojenkins")) | |
++nojenkins_flags | |
// And check if all paths were something we should ignore | |
def ign_paths = this_change.paths.findAll{ | |
it.contains('/doc/') || | |
it.contains('releng/jenkins/jenkins_backups') || | |
false | |
} | |
if (ign_paths.size() == this_change.paths.size()) | |
++ignored_path_only_changes | |
} | |
echo "After processing ${previous_changesets.changeSets.size()} changesets, found ${nojenkins_flags} flagged NoJenkins and ${ignored_path_only_changes} that only modified ignored paths" | |
if (!previous_changesets.changeSets.size()) { | |
currentBuild.result = 'ABORTED' | |
def errstr = "Self-aborted (no changes)" | |
error_email += "\n\n" + errstr | |
currentBuild.description = errstr | |
error("No changes since last successful build; use 'Force Rebuild' option") | |
} | |
if (nojenkins_flags == previous_changesets.changeSets.size()) { | |
currentBuild.result = 'ABORTED' | |
def errstr = "Self-aborted (NoJenkins flags)" | |
error_email += "\n\n" + errstr | |
currentBuild.description = errstr | |
error("All previous ${nojenkins_flags} changesets flagged with NoJenkins") | |
} | |
if (ignored_path_only_changes == previous_changesets.changeSets.size()) { | |
currentBuild.result = 'ABORTED' | |
def errstr = "Self-aborted (all paths ignored)" | |
error_email += "\n\n" + errstr | |
currentBuild.description = errstr | |
error("All previous ${ignored_path_only_changes} changesets only dealt with ignored paths") | |
} | |
if ((nojenkins_flags + ignored_path_only_changes) == previous_changesets.changeSets.size()) { | |
currentBuild.result = 'ABORTED' | |
def errstr = "Self-aborted (NoJenkins flags + paths)" | |
error_email += "\n\n" + errstr | |
currentBuild.description = errstr | |
error("All previous ${nojenkins_flags + ignored_path_only_changes} changesets indicated not to build") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment