我最终实现了这个功能,但没有在步骤级别跟踪成功。我基本上回顾了最近的构建,对于任何成功的构建,如果它对“受影响的”文件进行了更改,并且 在构建之前也对受影响的文件进行了更改,那么构建将继续进行一组文件(例如 API)。
我还在管道中添加了参数,以便我们可以根据需要覆盖此规则。
设置变量
final String forceApiBuildParam = 'forceApiBuild'
final String skipApiBuildParam = 'skipApiBuild'
final String forceUiBuildParam = 'forceUiBuild'
final String skipUiBuildParam = 'skipUiBuild'
final String apiPathRegex = /^(api\/|build.gradle|checkstyle.xml|settings.gradle|Jenkinsfile).*/
final String uiPathRegex = /^(ui\/|Jenkinsfile).*/
Boolean apiBuildEnabled = params[forceApiBuildParam] == true
? true
: (params[skipApiBuildParam] == true
? false
: hasChangesInPathsSinceLastSuccessFulBuildWithChangesInPaths(verbose, apiPathRegex))
Boolean uiBuildEnabled = params[forceUiBuildParam] == true
? true
: (params[skipUiBuildParam] == true
? false
: hasChangesInPathsSinceLastSuccessFulBuildWithChangesInPaths(verbose, uiPathRegex))
设置参数
parameters {
booleanParam(name: forceApiBuildParam, defaultValue: false, description: 'Whether or not to force the API to build and deploy.')
booleanParam(name: skipApiBuildParam, defaultValue: false, description: 'Whether or not to skip the API build, even if there are changes.')
booleanParam(name: forceUiBuildParam, defaultValue: false, description: 'Whether or not to force the UI to build and deploy.')
booleanParam(name: skipUiBuildParam, defaultValue: false, description: 'Whether or not to skip the UI build, even if there are changes.')
}
检查更改的功能(可以简化)
/**
* Attempts to determine if, for a set of paths given in the form of a regex,
* any files have changed (which match those paths) since the last build which
* was successful and also had changes within those paths.
*
* @param verbose true to include additional logging
* @param pathRegex the regex of the paths to consider
* @return true if paths matching the provided regex have changed since the last
* successful build which also had changes to a file matching the
* regex; false if otherwise or if no successful build yet exist
*/
boolean hasChangesInPathsSinceLastSuccessFulBuildWithChangesInPaths(boolean verbose, String pathRegex) {
Collection<String> allChangedPaths = new HashSet<String>()
RunWrapper build = (RunWrapper) (Object) currentBuild
int buildCount = 0
echo "Checking for changed paths since last successful build for paths matching: ${pathRegex}"
while (build != null) {
if (verbose) echo "Checking build ${build.displayName}..."
WorkflowRun rawBuild = (WorkflowRun) build.rawBuild
String buildHash = rawBuild.getAction(BuildData.class).lastBuiltRevision.sha1String.substring(8)
boolean success = build.result == 'SUCCESS'
if (success) {
echo "Found successful build (${build.displayName}; #${buildHash}) -- ${buildCount} builds ago."
}
else if (verbose) {
echo "Found failed build (${build.displayName}; #${buildHash}) -- ${buildCount} builds ago."
}
for (GitChangeSetList changeLog in (build.changeSets as List<GitChangeSetList>)) {
Collection<String> changedChangeLogPaths = new HashSet<String>()
for (GitChangeSet entry in changeLog) {
if (verbose) echo "Checking commit ${entry.commitId}..."
// if this build is successful, check if the already-stored files match `pathRegex`
// if so, then there have been changes since this successful build
if (success) {
for (String thisBuildPath in entry.affectedPaths) {
if (verbose) echo "Checking path ${thisBuildPath} for commit ${entry.commitId}..."
// check if this file (changed in this build) matches
if (thisBuildPath.matches(pathRegex)) {
if (verbose) echo "Match found!"
// now look through all changed files *before* this build for matches
for (String allBuildsPath in allChangedPaths) {
if (verbose) echo "Checking changed path ${allBuildsPath}..."
// check if this file (which was changed before this build) matches
if (allBuildsPath.matches(pathRegex)) {
echo "Since build ${build.displayName}, path \"${allBuildsPath}\" was changed and matches: ${pathRegex}"
return true
}
}
// if we get here, then we found a successful build matching `pathRegex`, but since then
// there have been no files changed which match `pathRegex` -- so no need to build again
echo "No files changed since build ${build.displayName} matching: ${pathRegex}"
return false
}
}
}
// add the affected paths to the list of changes for the changeset
changedChangeLogPaths.addAll(entry.affectedPaths)
}
// if we get here then the build was not successful or had no files changed whose
// path matches `pathRegex` -- add the files to the list and continue
allChangedPaths.addAll(changedChangeLogPaths)
}
build = build.previousBuild
buildCount++
}
echo "Found no successful builds (checked ${buildCount} builds) with changes to paths matching: ${pathRegex}"
// return true to signify so we can do the initial build
return true;
}
这种方法有一个需要注意的地方,那就是它的构建频率可能比它需要的略多。假设有一个 API 更改破坏了构建 - 不是因为 API 代码更改中的问题,而是因为构建脚本中的问题,或 API 的部署(代码更改之外的东西)。然后,API 将在每个后续构建中重新构建,直到另一个 API 更改出现。
- 构建 #100 -- API 更改,构建损坏
- Build #101...#n -- 没有 API 更改;由于 #100 失败而重建 API - 现在成功
- Build #102...#n -- 没有 API 更改;由于 #100 失败,API 被重建——尽管从那时起 #101 成功了
- Build #n+1 -- API 更改,重建 API
- Build #n+2 -- 没有 API 更改; API 未重新构建,因为 #n+1 有 API 更改并且成功
在上面的示例中,API 从 #101 开始重新构建,直到下一次 API 更改出现并导致构建成功。我认为这个解决方案不可能解决这个问题,除非可以为每个构建标记,无论 API/UI 是否成功构建。如果我们能做到这一点,那么 #102+ 就不会被构建,因为我们知道 API 构建在 #101 上是成功的,即使在构建过程中没有任何 API 文件受到影响。