Skip to content

Instantly share code, notes, and snippets.

@AaronDMarasco-VSI
Created November 8, 2018 16:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AaronDMarasco-VSI/a1f3829869cbf3256ca52919eba1cd16 to your computer and use it in GitHub Desktop.
Save AaronDMarasco-VSI/a1f3829869cbf3256ca52919eba1cd16 to your computer and use it in GitHub Desktop.
Jenkins detecting changes
#!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
// 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