From 91199e72024aa94a4fca324d13fffca50b99c27d Mon Sep 17 00:00:00 2001 From: Eric Haag Date: Mon, 23 Mar 2026 11:07:19 -0500 Subject: [PATCH] Gracefully handle detached HEAD in branch version check Previously, the `CheckExpectedBranchVersionPlugin` would crash the Gradle configuration phase if the project was in a detached HEAD state or not in a Git repository, e.g., downloaded as a ZIP. This commit refactors the plugin to be lazy and adopts several Gradle best practices: - Prevents build crashes on Git failures by gracefully catching non-zero exit codes, e.g., when checked out in a detached HEAD state. - Moves the branch validation out of the task's main execution action and into an `onlyIf` predicate, allowing Gradle to skip the task entirely instead of executing an early return. This makes the skip outcome and reason visible in a Build Scan, rather than making it appear as if it executed. - Defers the Git `exec` call to the execution phase using a lazy provider. - Makes the task configuration cache compatible by avoiding illegal `Project` access inside the execution-time `onlyIf` closure. - Improves user-facing logs and adds actionable bypass instructions when the project version doesn't match the branch version. Signed-off-by: Eric Haag --- .../CheckExpectedBranchVersionPlugin.java | 60 ++++++++++++++----- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java b/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java index 9a25dced4c..3a5f17f499 100644 --- a/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java +++ b/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java @@ -21,8 +21,12 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.specs.Spec; import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputFile; @@ -30,6 +34,7 @@ import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskExecutionException; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.VerificationException; +import org.gradle.process.ExecOutput; import java.io.IOException; import java.nio.file.Files; @@ -44,21 +49,53 @@ public class CheckExpectedBranchVersionPlugin implements Plugin { TaskProvider checkExpectedBranchVersionTask = project.getTasks().register("checkExpectedBranchVersion", CheckExpectedBranchVersionTask.class, (task) -> { task.setGroup("Build"); task.setDescription("Check if the project version matches the branch version"); - task.onlyIf("skipCheckExpectedBranchVersion property is false or not present", CheckExpectedBranchVersionPlugin::skipPropertyFalseOrNotPresent); + task.onlyIf("Property 'skipCheckExpectedBranchVersion' is false or not present", skipPropertyFalseOrNotPresent(project.getProviders())); + task.onlyIf("Branch name matches expected version pattern *.x", CheckExpectedBranchVersionPlugin::isVersionBranch); task.getVersion().convention(project.provider(() -> project.getVersion().toString())); - task.getBranchName().convention(project.getProviders().exec((execSpec) -> execSpec.setCommandLine("git", "symbolic-ref", "--short", "HEAD")).getStandardOutput().getAsText()); + task.getBranchName().convention(getBranchName(project.getProviders(), project.getLogger())); task.getOutputFile().convention(project.getLayout().getBuildDirectory().file("check-expected-branch-version")); }); project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(checkExpectedBranchVersionTask)); } - private static boolean skipPropertyFalseOrNotPresent(Task task) { - return task.getProject() - .getProviders() + private static Spec skipPropertyFalseOrNotPresent(ProviderFactory providers) { + Provider skipPropertyFalseOrNotPresent = providers .gradleProperty("skipCheckExpectedBranchVersion") .orElse("false") - .map("false"::equalsIgnoreCase) - .get(); + .map("false"::equalsIgnoreCase); + return (task) -> skipPropertyFalseOrNotPresent.get(); + } + + private static boolean isVersionBranch(Task task) { + return isVersionBranch((CheckExpectedBranchVersionTask) task); + } + + private static boolean isVersionBranch(CheckExpectedBranchVersionTask task) { + String branchName = task.getBranchName().getOrNull(); + if (branchName == null) { + return false; + } + return branchName.matches("^[0-9]+\\.[0-9]+\\.x$"); + } + + private static Provider getBranchName(ProviderFactory providers, Logger logger) { + ExecOutput execOutput = providers.exec((execSpec) -> { + execSpec.setCommandLine("git", "symbolic-ref", "--short", "HEAD"); + execSpec.setIgnoreExitValue(true); + }); + + return providers.provider(() -> { + int exitValue = execOutput.getResult().get().getExitValue(); + if (exitValue != 0) { + logger.warn("Unable to determine branch name. Received exit code '{}' from `git`.", exitValue); + logger.warn(execOutput.getStandardError().getAsText().getOrNull()); + return null; + } + + String branchName = execOutput.getStandardOutput().getAsText().get().trim(); + logger.info("Git branch name is '{}'.", branchName); + return branchName; + }); } @CacheableTask @@ -77,15 +114,10 @@ public class CheckExpectedBranchVersionPlugin implements Plugin { public void run() { String version = getVersion().get(); String branchVersion = getBranchName().map(String::trim).get(); - if (!branchVersion.matches("^[0-9]+\\.[0-9]+\\.x$")) { - String msg = String.format("Branch version [%s] does not match *.x, ignoring", branchVersion); - getLogger().warn(msg); - writeExpectedVersionOutput(msg); - return; - } if (!versionsMatch(version, branchVersion)) { String msg = String.format("Project version [%s] does not match branch version [%s]. " + - "Please verify that the branch contains the right version.", version, branchVersion); + "Please verify that the branch contains the right version. " + + "To bypass this check, run the build with -PskipCheckExpectedBranchVersion.", version, branchVersion); writeExpectedVersionOutput(msg); throw new VerificationException(msg); }