Configure gradle-versions-plugin

Closes gh-1594
This commit is contained in:
Stéphane Nicoll
2025-05-26 10:37:47 +02:00
parent 6a6d6f6f7e
commit 12ad317f2b
10 changed files with 656 additions and 0 deletions

View File

@@ -3,5 +3,7 @@ version=4.0.15-SNAPSHOT
org.gradle.caching=true
org.gradle.parallel=true
versionUpgradePolicy=same_minor_version
compatibilityTestPluginVersion=0.0.3
springFrameworkVersion=6.1.20

View File

@@ -5,12 +5,18 @@ plugins {
}
repositories {
gradlePluginPortal()
mavenCentral()
}
dependencies {
api(platform("org.junit:junit-bom:${junitVersion}"))
checkstyle("com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}")
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.assertj:assertj-core:${assertJVersion}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
gradlePlugin {
@@ -23,5 +29,10 @@ gradlePlugin {
}
dependencies {
implementation("com.github.ben-manes:gradle-versions-plugin:$gradleVersionsPluginVersion")
implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:$javaFormatVersion")
}
tasks.named('test') {
useJUnitPlatform()
}

View File

@@ -50,6 +50,7 @@ class JavaPluginConventions {
enableSourceAndJavadocJars(java);
configureSourceAndTargetCompatibility(java);
configureDependencyManagement(project);
configureVersionUpgradePolicy(project);
configureJarManifest(project);
configureToolchain(project, java);
configureJavadocClasspath(project, java);
@@ -97,6 +98,10 @@ class JavaPluginConventions {
.add(dependencies.enforcedPlatform(dependencies.project(Map.of("path", ":spring-ws-platform"))));
}
private void configureVersionUpgradePolicy(Project project) {
new VersionUpgradePluginConventions().apply(project);
}
private void configureJarManifest(Project project) {
project.getTasks()
.named("jar", Jar.class,

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2005-2025 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.ws.gradle.conventions;
import java.util.Locale;
import com.github.benmanes.gradle.versions.VersionsPlugin;
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask;
import org.gradle.api.Project;
import org.springframework.ws.gradle.conventions.support.Version;
import org.springframework.ws.gradle.conventions.support.VersionUpgradePolicy;
/**
* Conventions for {@code gradle-versions-plugin}.
*
* @author Stephane Nicoll
*/
class VersionUpgradePluginConventions {
void apply(Project project) {
project.getPlugins().apply(VersionsPlugin.class);
project.getTasks().withType(DependencyUpdatesTask.class, (updateTask) -> {
updateTask.setFilterConfigurations((configuration) -> !(configuration.getName().contains("_")
&& configuration.getName().endsWith("+")));
VersionUpgradePolicy upgradePolicy = getUpgradePolicy(project);
updateTask.rejectVersionIf((candidate) -> {
Version currentVersion = Version.from(candidate.getCurrentVersion());
Version candidateVersion = Version.from(candidate.getCandidate().getVersion());
return !upgradePolicy.isCandidate(currentVersion, candidateVersion);
});
});
}
private VersionUpgradePolicy getUpgradePolicy(Project project) {
Object versionUpgradePolicy = project.findProperty("versionUpgradePolicy");
if (versionUpgradePolicy == null) {
return VersionUpgradePolicy.SAME_MINOR_VERSION;
}
else if (versionUpgradePolicy instanceof String value) {
return VersionUpgradePolicy.valueOf(value.toUpperCase(Locale.ROOT));
}
throw new IllegalArgumentException("Unsupported version upgrade policy: " + versionUpgradePolicy);
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.ws.gradle.conventions.support;
import java.io.Serializable;
import java.util.Objects;
/**
* ë A version representation that provides a major and minor identifier. For instance,
* {@code 1.2.5} would have a {@code major} of "1" and {@code minor} of "1.2".
*
* @author Stephane Nicoll
*/
public class Version {
private final String id;
private final String major;
private final String minor;
private final Qualifier qualifier;
private final Parts parts;
Version(String id, String major, String minor, Qualifier qualifer, Parts parts) {
this.id = id;
this.major = major;
this.minor = minor;
this.qualifier = qualifer;
this.parts = parts;
}
public static Version from(String version) {
return VersionParser.safeParse(version);
}
/**
* Return the version.
* @return the version
*/
public String getId() {
return this.id;
}
/**
* Return the major qualifier or {@code null}.
* @return the major
*/
public String getMajor() {
return this.major;
}
/**
* Return the minor qualifier or {@code null}.
* @return the minor
*/
public String getMinor() {
return this.minor;
}
/**
* Return tue {@link Qualifier} or {@code null}.
* @return the qualifier
*/
public Qualifier getQualifier() {
return this.qualifier;
}
/**
* Return the elements of the version, if any. Does not apply for non-numeric version
* such as a release train.
* @return the parts
*/
public Parts getParts() {
return this.parts;
}
/**
* Returns whether this version has the same major and minor versions as the
* {@code other} version.
* @param other the version to test
* @return {@code true} if this version has the same major and minor, otherwise
* {@code false}
*/
public boolean isSameMinor(Version other) {
return isSameMajor(other) && Objects.equals(this.parts.minor, other.parts.minor);
}
/**
* Returns whether this version has the same major version as the {@code other}
* version.
* @param other the version to test
* @return {@code true} if this version has the same major, otherwise {@code false}
*/
public boolean isSameMajor(Version other) {
return (this.parts != null && other.parts != null && Objects.equals(this.parts.major, other.parts.major));
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Version version)) {
return false;
}
return Objects.equals(this.id, version.id);
}
@Override
public int hashCode() {
return Objects.hashCode(this.id);
}
@Override
public String toString() {
return this.id;
}
public record Parts(Integer major, Integer minor, Integer patch, Integer hotPatch) {
/**
* Return a unique number for this instance that allows to compare two versions.
* @return a comparable number
*/
public long toNumber() {
String paddedValue = paddedNumber(this.major) + paddedNumber(this.minor) + paddedNumber(this.patch)
+ paddedNumber(this.hotPatch);
return Long.parseLong(paddedValue);
}
private String paddedNumber(Integer number) {
if (number != null) {
return String.format("%02d", number);
}
return "00";
}
}
/**
* A version qualifier.
*
* @param id the identifier of the qualifier
* @param version the version or {@code null}
* @param separator the separator
*/
public record Qualifier(String id, Integer version, String separator) implements Serializable {
public Qualifier(String id) {
this(id, null, ".");
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.ws.gradle.conventions.support;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.ws.gradle.conventions.support.Version.Parts;
import org.springframework.ws.gradle.conventions.support.Version.Qualifier;
/**
* Parse version text to a {@link Version}.
*
* @author Stephane Nicoll
*/
abstract class VersionParser {
private static final Pattern VERSION_REGEX = Pattern
.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(\\.\\d+)?(?:([.|-])([^0-9]+)(\\d+)?)?$");
private static final Pattern RELEASE_TRAIN_REGEX = Pattern.compile("([A-Za-z]*)(_|-|.)([A-Za-z0-9_-]*)");
private static final Pattern NON_STANDARD_VERSION_REGEX = Pattern
.compile("^(\\d+)\\.(\\d+)(?:[.|-]([^0-9]+)(\\d+)?)?$");
/**
* Safely parse the specified {@code text} into a version.
* @param text a version text
* @return a {@link Version} (never {@code null})
*/
static Version safeParse(String text) {
String versionText = cleanVersion(text);
Version standardVersion = parseStandardVersion(versionText);
if (standardVersion != null) {
return standardVersion;
}
Version releaseTrainVersion = parseReleaseTrain(versionText);
if (releaseTrainVersion != null) {
return releaseTrainVersion;
}
Version nonStandardVersion = parseNonStandardVersion(versionText);
if (nonStandardVersion != null) {
return nonStandardVersion;
}
return new Version(versionText, null, null, null, null);
}
private static Version parseStandardVersion(String text) {
Matcher matcher = VERSION_REGEX.matcher(text);
if (!matcher.matches()) {
return null;
}
String major = matcher.group(1);
String minor = matcher.group(2);
if (major != null) {
// This can be calVer, semVer or our legacy format
if (minor != null && major.length() == 4 && Integer.parseInt(major) > 1970) {
String releaseTrainName = String.format("%s.%s", major, minor);
return new Version(text, null, releaseTrainName, parseQualifier(matcher),
new Parts(safeInteger(major), safeInteger(minor), safeInteger(matcher.group(3)), null));
}
String patch = matcher.group(3);
String hotPatch = matcher.group(4);
if (hotPatch != null) {
hotPatch = hotPatch.substring(1); // Remove .
}
return createVersion(text, major, minor, parseQualifier(matcher),
new Parts(safeInteger(major), safeInteger(minor), safeInteger(patch), safeInteger(hotPatch)));
}
return null;
}
private static Version parseReleaseTrain(String text) {
Matcher matcher = RELEASE_TRAIN_REGEX.matcher(text);
if (!matcher.matches()) {
return null;
}
String name = matcher.group(1);
return new Version(text, null, name, null, null);
}
private static Version parseNonStandardVersion(String text) {
Matcher matcher = NON_STANDARD_VERSION_REGEX.matcher(text.trim());
if (!matcher.matches()) {
return null;
}
String major = matcher.group(1);
String minor = matcher.group(2);
return createVersion(text, major, minor, null, new Parts(safeInteger(major), safeInteger(minor), null, null));
}
private static Qualifier parseQualifier(Matcher matcher) {
String qualifierSeparator = matcher.group(5);
String qualifierId = matcher.group(6);
if (hasText(qualifierSeparator) && hasText(qualifierId)) {
String versionString = matcher.group(7);
return new Qualifier(qualifierId, (versionString != null) ? Integer.valueOf(versionString) : null,
qualifierSeparator);
}
return null;
}
private static Version createVersion(String text, String major, String minor, Qualifier qualifier, Parts parts) {
String minorText = (minor != null) ? String.format("%s.%s", major, minor) : null;
return new Version(text, major, minorText, qualifier, parts);
}
private static String cleanVersion(String version) {
try {
String cleanVersion = URLDecoder.decode(version, StandardCharsets.UTF_8).trim();
int i = cleanVersion.lastIndexOf("?");
return (i != -1) ? cleanVersion.substring(i + 1) : cleanVersion;
}
catch (Exception ex) {
return version;
}
}
private static Integer safeInteger(String text) {
try {
if (text != null) {
return Integer.valueOf(text);
}
}
catch (NumberFormatException ex) {
// ignore
}
return null;
}
private static boolean hasText(String value) {
return value != null && !value.isEmpty();
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2005-2025 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.ws.gradle.conventions.support;
import java.util.function.BiPredicate;
/**
* Policies used to decide which versions are considered as possible upgrades.
*
* @author Stephane Nicoll
*/
public enum VersionUpgradePolicy {
/**
* Any version.
*/
ANY((current, candidate) -> true),
/**
* Minor versions of the current major version.
*/
SAME_MAJOR_VERSION(Version::isSameMajor),
/**
* Patch versions of the current minor version.
*/
SAME_MINOR_VERSION(Version::isSameMinor);
private final BiPredicate<Version, Version> delegate;
VersionUpgradePolicy(BiPredicate<Version, Version> delegate) {
this.delegate = delegate;
}
/**
* Specify if the {@code candidate} version is a valid upgrade according to this
* policy.
* @param current the current version
* @param candidate the candidate version
* @return {@code true} if the dependency can be upgraded to the {@code candidate}
* version
*/
public boolean isCandidate(Version current, Version candidate) {
return this.delegate.test(current, candidate);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2005-2025 the original author or authors.
*
* 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
*
* https://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.
*/
/**
* Support classes for conventions plugin.
*/
package org.springframework.ws.gradle.conventions.support;

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.ws.gradle.conventions.support;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.ws.gradle.conventions.support.Version.Parts;
import org.springframework.ws.gradle.conventions.support.Version.Qualifier;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link VersionParser}.
*
* @author Stephane Nicoll
*/
class VersionParserTests {
@Test
void parseSimpleVersion() {
assertVersion("1.2.3.RELEASE", "1", "1.2", new Qualifier("RELEASE"), new Parts(1, 2, 3, null));
}
@Test
void parseVersionWithDashedQualifier() {
assertVersion("2.0.0-M1", "2", "2.0", new Qualifier("M", 1, "-"), new Parts(2, 0, 0, null));
}
@Test
void parseNoQualifier() {
assertVersion("2.1.4", "2", "2.1", null, new Parts(2, 1, 4, null));
}
@Test
void parseCommercialRevision() {
assertVersion("2.7.19.1", "2", "2.7", null, new Parts(2, 7, 19, 1));
}
@Test
void parseWithQuestionMarkPrefix() {
assertVersion("?2.1.4", "2.1.4", "2", "2.1", null, new Parts(2, 1, 4, null));
}
@Test
void parseWithEncodedVersion() {
assertVersion("4%2E1%2E6%2ERELEASE", "4.1.6.RELEASE", "4", "4.1", new Qualifier("RELEASE", null, "."),
new Parts(4, 1, 6, null));
}
@Test
void parseWithMajorMinorOnly() {
assertVersion("2.0", "2", "2.0", null, new Parts(2, 0, null, null));
}
@Test
void parseReleaseTrain() {
assertVersion("Gosling-SR1", null, "Gosling", null);
}
@Test
void parseCalVer() {
assertVersion("2000.0.1", null, "2000.0", null, new Parts(2000, 0, 1, null));
}
@Test
void parseCalVerWithQualifier() {
assertVersion("2020.1.0-RC1", null, "2020.1", new Qualifier("RC", 1, "-"), new Parts(2020, 1, 0, null));
}
@Test
void parseVersionProperty() {
assertVersion("${spring.version}", null, null, null);
}
@Test
void versionNumberWithMajorMinor() {
assertThat(new Parts(2, 12, null, null).toNumber()).isEqualTo(2120000);
}
@Test
void versionNumberWithMajorMinorPatch() {
assertThat(new Parts(2, 12, 5, null).toNumber()).isEqualTo(2120500);
}
@Test
void versionNumberWithMajorMinorPatchHotPatch() {
assertThat(new Parts(2, 12, 5, 1).toNumber()).isEqualTo(2120501);
}
@ParameterizedTest
@CsvSource(textBlock = """
3.4.5,3.6.0
3.4.0-M1,3.5.0-M2
2020.1.0,2020.2.0
3.4.5,3.4.5
""")
void isSameMajorTrue(String left, String right) {
assertThat(Version.from(left).isSameMajor(Version.from(right))).isTrue();
}
@ParameterizedTest
@CsvSource(textBlock = """
3.4.5,2.4.5
3.4.0-M1,2.4.0-M1
2020.1.0,2021.1.0
""")
void isSameMajorFalse(String left, String right) {
assertThat(Version.from(left).isSameMajor(Version.from(right))).isFalse();
}
@ParameterizedTest
@CsvSource(textBlock = """
3.4.5,3.4.9
3.4.0-M1,3.4.0-M2
2020.1.0,2020.1.9
3.4.5,3.4.5
""")
void isSameMinorTrue(String left, String right) {
assertThat(Version.from(left).isSameMinor(Version.from(right))).isTrue();
}
@ParameterizedTest
@CsvSource(textBlock = """
3.4.5,3.3.9
3.4.0-M1,3.5.0-M1
2020.1.0,2020.2.0
""")
void isSameMinorFalse(String left, String right) {
assertThat(Version.from(left).isSameMinor(Version.from(right))).isFalse();
}
private void assertVersion(String text, String major, String minor, Qualifier qualifier) {
assertVersion(text, major, minor, qualifier, null);
}
private void assertVersion(String text, String major, String minor, Qualifier qualifier, Parts parts) {
assertVersion(text, text, major, minor, qualifier, parts);
}
private void assertVersion(String text, String id, String major, String minor, Qualifier qualifier, Parts parts) {
assertThat(VersionParser.safeParse(text)).satisfies(isVersion(id, major, minor, qualifier, parts));
}
private Consumer<Version> isVersion(String id, String major, String minor, Qualifier qualifier, Parts parts) {
return (version) -> {
assertThat(version.getId()).isEqualTo(id);
assertThat(version.getMajor()).isEqualTo(major);
assertThat(version.getMinor()).isEqualTo(minor);
assertThat(version.getQualifier()).isEqualTo(qualifier);
assertThat(version.getParts()).isEqualTo(parts);
};
}
}

View File

@@ -1 +1,4 @@
assertJVersion=3.25.3
gradleVersionsPluginVersion=0.52.0
javaFormatVersion=0.0.45
junitVersion=5.11.0