Build with Gradle 9.1.0

This commit is contained in:
Andy Wilkinson 2025-07-14 10:54:28 +01:00
parent 2440667aab
commit 44099d3d21
27 changed files with 152 additions and 119 deletions

View File

@ -80,7 +80,6 @@ tasks.register("integrationTest") {
ant.propertyref(name: "ivy.class.path")
}
plainlistener()
file(layout.buildDirectory.dir("test-results/integrationTest")).mkdirs()
xmllistener(toDir: resultsDir)
fileset(dir: layout.buildDirectory.dir("it").get().asFile.toString(), includes: "**/build.xml")
}

View File

@ -141,9 +141,13 @@ final class ApplicationPluginAction implements PluginApplicationAction {
}
}
@SuppressWarnings("deprecation")
private void configureFileMode(CopySpec copySpec, int mode) {
copySpec.setFileMode(mode);
try {
copySpec.getClass().getMethod("setFileMode", Integer.class).invoke(copySpec, Integer.valueOf(mode));
}
catch (Exception ex) {
throw new RuntimeException("Failed to set file mode on CopySpec", ex);
}
}
}

View File

@ -285,7 +285,6 @@ final class JavaPluginAction implements PluginApplicationAction {
private void configureProductionRuntimeClasspathConfiguration(Project project) {
Configuration productionRuntimeClasspath = project.getConfigurations()
.create(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME);
productionRuntimeClasspath.setVisible(false);
Configuration runtimeClasspath = project.getConfigurations()
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
productionRuntimeClasspath.attributes((attributes) -> {

View File

@ -104,7 +104,6 @@ class WarPluginAction implements PluginApplicationAction {
.set(project.provider(() -> javaPluginExtension(project).getTargetCompatibility()));
bootWar.resolvedArtifacts(runtimeClasspath.getIncoming().getArtifacts().getResolvedArtifacts());
});
bootWarProvider.map(War::getClasspath);
return bootWarProvider;
}

View File

@ -43,7 +43,7 @@ import org.springframework.util.Assert;
* @since 3.0.0
*/
@CacheableTask
public class ProcessTestAot extends AbstractAot {
public abstract class ProcessTestAot extends AbstractAot {
private @Nullable FileCollection classpathRoots;

View File

@ -22,25 +22,19 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import org.gradle.api.file.ConfigurableFilePermissions;
import org.gradle.api.file.CopySpec;
import org.gradle.api.file.FileCopyDetails;
import org.gradle.api.file.FileTreeElement;
import org.gradle.api.file.RelativePath;
import org.gradle.api.internal.file.copy.CopyAction;
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
import org.gradle.api.java.archives.Attributes;
import org.gradle.api.java.archives.Manifest;
import org.gradle.api.provider.Property;
import org.gradle.api.specs.Spec;
import org.gradle.api.specs.Specs;
import org.gradle.api.tasks.WorkResult;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.util.PatternSet;
import org.gradle.util.GradleVersion;
@ -142,7 +136,7 @@ class BootArchiveSupport {
CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirPermissions,
filePermissions, includeDefaultLoader, jarmodeToolsLocation, requiresUnpack, exclusions, launchScript,
librarySpec, compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver);
return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action;
return action;
}
private @Nullable Integer getUnixNumericDirPermissions(CopySpec copySpec) {
@ -159,14 +153,22 @@ class BootArchiveSupport {
return permissions.isPresent() ? permissions.get().toUnixNumeric() : null;
}
@SuppressWarnings("deprecation")
private @Nullable Integer getDirMode(CopySpec copySpec) {
return copySpec.getDirMode();
try {
return (Integer) copySpec.getClass().getMethod("getDirMode").invoke(copySpec);
}
catch (Exception ex) {
throw new RuntimeException("Failed to get dir mode from CopySpec", ex);
}
}
@SuppressWarnings("deprecation")
private @Nullable Integer getFileMode(CopySpec copySpec) {
return copySpec.getFileMode();
try {
return (Integer) copySpec.getClass().getMethod("getFileMode").invoke(copySpec);
}
catch (Exception ex) {
throw new RuntimeException("Failed to get file mode from CopySpec", ex);
}
}
private boolean isUsingDefaultLoader(Jar jar) {
@ -229,26 +231,4 @@ class BootArchiveSupport {
details.setRelativePath(details.getRelativeSourcePath());
}
/**
* {@link CopyAction} variant that sorts entries to ensure reproducible ordering.
*/
private static final class ReproducibleOrderingCopyAction implements CopyAction {
private final CopyAction delegate;
private ReproducibleOrderingCopyAction(CopyAction delegate) {
this.delegate = delegate;
}
@Override
public WorkResult execute(CopyActionProcessingStream stream) {
return this.delegate.execute((action) -> {
Map<RelativePath, FileCopyDetailsInternal> detailsByPath = new TreeMap<>();
stream.process((details) -> detailsByPath.put(details.getRelativePath(), details));
detailsByPath.values().forEach(action::processFile);
});
}
}
}

View File

@ -504,9 +504,13 @@ class BootZipCopyAction implements CopyAction {
? details.getPermissions().toUnixNumeric() : getMode(details);
}
@SuppressWarnings("deprecation")
private int getMode(FileCopyDetails details) {
return details.getMode();
try {
return (int) details.getClass().getMethod("getMode").invoke(details);
}
catch (Exception ex) {
throw new RuntimeException("Failed to get mode from FileCopyDetails", ex);
}
}
}

View File

@ -24,8 +24,6 @@ import java.time.format.DateTimeFormatter;
import java.util.Properties;
import org.gradle.api.Project;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.initialization.GradlePropertiesController;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@ -173,11 +171,7 @@ class BuildInfoTests {
private Project createProject(String projectName) {
File projectDir = new File(this.temp, projectName);
Project project = GradleProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build();
((ProjectInternal) project).getServices()
.get(GradlePropertiesController.class)
.loadGradlePropertiesFrom(projectDir, false);
return project;
return GradleProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build();
}
private BuildInfo createTask(Project project) {

View File

@ -233,14 +233,8 @@ abstract class AbstractBootArchiveIntegrationTests {
.filter((entry) -> !entry.isDirectory())
.map(JarEntry::getName)
.filter((name) -> name.startsWith(this.libPath));
if (this.gradleBuild.gradleVersionIsLessThan("9.0.0-rc-1")) {
assertThat(libEntryNames).containsExactly(this.libPath + "two-1.0.jar",
this.libPath + "commons-io-2.19.0.jar");
}
else {
assertThat(libEntryNames).containsExactly(this.libPath + "commons-io-2.19.0.jar",
this.libPath + "two-1.0.jar");
}
assertThat(libEntryNames).containsExactly(this.libPath + "two-1.0.jar",
this.libPath + "commons-io-2.19.0.jar");
}
}

View File

@ -27,6 +27,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
@ -35,6 +37,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
@ -407,23 +410,46 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
}
@Test
void reproducibleOrderingCanBeEnabled() throws IOException {
void archiveIsReproducibleByDefault() throws IOException {
this.task.getMainClass().set("com.example.Main");
this.task.from(newFile("bravo.txt"), newFile("alpha.txt"), newFile("charlie.txt"));
this.task.setReproducibleFileOrder(true);
this.task.from(newFiles("files/b/bravo.txt", "files/a/alpha.txt", "files/c/charlie.txt"));
executeTask();
assertThat(this.task.getArchiveFile().get().getAsFile()).exists();
List<String> textFiles = new ArrayList<>();
List<String> files = new ArrayList<>();
try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".txt")) {
textFiles.add(entry.getName());
OffsetDateTime lastModifiedTime = entry.getLastModifiedTime().toInstant().atOffset(ZoneOffset.UTC);
assertThat(lastModifiedTime).isEqualTo(OffsetDateTime.of(1980, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC));
if (entry.getName().startsWith("files/")) {
files.add(entry.getName());
}
}
}
assertThat(files).containsExactly("files/", "files/a/", "files/a/alpha.txt", "files/b/", "files/b/bravo.txt",
"files/c/", "files/c/charlie.txt");
}
@Test
void archiveReproducibilityCanBeDisabled() throws IOException {
this.task.getMainClass().set("com.example.Main");
this.task.from(newFiles("files/b/bravo.txt", "files/a/alpha.txt", "files/c/charlie.txt"));
this.task.setPreserveFileTimestamps(true);
this.task.setReproducibleFileOrder(false);
executeTask();
assertThat(this.task.getArchiveFile().get().getAsFile()).exists();
try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".txt") || entry.getName().startsWith("BOOT-INF/lib/")) {
OffsetDateTime lastModifiedTime = entry.getLastModifiedTime().toInstant().atOffset(ZoneOffset.UTC);
assertThat(lastModifiedTime)
.isNotEqualTo(OffsetDateTime.of(1980, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC));
}
}
}
assertThat(textFiles).containsExactly("alpha.txt", "bravo.txt", "charlie.txt");
}
@Test
@ -663,6 +689,19 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
return entryNames;
}
protected File newFiles(String... names) throws IOException {
File dir = new File(this.temp, UUID.randomUUID().toString());
dir.mkdir();
List<File> files = new ArrayList<>();
for (String name : names) {
File file = new File(dir, name);
file.getParentFile().mkdirs();
file.createNewFile();
files.add(file);
}
return dir;
}
protected File newFile(String name) throws IOException {
File file = new File(this.temp, name);
file.createNewFile();

View File

@ -66,18 +66,10 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
copyClasspathApplication();
BuildResult result = this.gradleBuild.build("launch");
String output = result.getOutput();
if (this.gradleBuild.gradleVersionIsLessThan("9.0.0-rc-1")) {
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar");
assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-tools.*.jar");
}
else {
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*commons-lang3-3.9.jar");
assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-tools.*.jar");
}
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar");
assertThat(output).containsPattern("4\\. .*spring-boot-jarmode-tools.*.jar");
assertThat(output).doesNotContain("5. ");
}
@ -86,18 +78,10 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
copyClasspathApplication();
BuildResult result = this.gradleBuild.build("launch");
String output = result.getOutput();
if (this.gradleBuild.gradleVersionIsLessThan("9.0.0-rc-1")) {
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-tools.*.jar");
assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("4\\. .*commons-lang3-3.9.jar");
}
else {
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-tools.*.jar");
assertThat(output).containsPattern("3\\. .*commons-lang3-3.9.jar");
assertThat(output).containsPattern("4\\. .*library-1.0-SNAPSHOT.jar");
}
assertThat(output).containsPattern("1\\. .*classes");
assertThat(output).containsPattern("2\\. .*spring-boot-jarmode-tools.*.jar");
assertThat(output).containsPattern("3\\. .*library-1.0-SNAPSHOT.jar");
assertThat(output).containsPattern("4\\. .*commons-lang3-3.9.jar");
assertThat(output).doesNotContain("5. ");
}

View File

@ -21,6 +21,8 @@ plugins {
bootJar {
mainClass = 'com.example.Application'
preserveFileTimestamps = false
reproducibleFileOrder = true
if (GradleVersion.current().compareTo(GradleVersion.version("9.0.0-rc-1")) < 0) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
}

View File

@ -21,6 +21,8 @@ plugins {
bootWar {
mainClass = 'com.example.Application'
preserveFileTimestamps = false
reproducibleFileOrder = true
if (GradleVersion.current().compareTo(GradleVersion.version("9.0.0-rc-1")) < 0) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
}

View File

@ -41,7 +41,7 @@ public class OptionalDependenciesPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
Configuration optional = project.getConfigurations().create("optional");
optional.setCanBeConsumed(false);
optional.setCanBeConsumed(true);
optional.setCanBeResolved(false);
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
SourceSetContainer sourceSets = project.getExtensions()

View File

@ -166,6 +166,7 @@ class TestFailuresPluginIntegrationTests {
writer.println("dependencies {");
writer.println(" testImplementation 'org.junit.jupiter:junit-jupiter:5.6.0'");
writer.println(" testImplementation 'org.assertj:assertj-core:3.11.1'");
writer.println(" testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.6.0'");
writer.println("}");
writer.println();
writer.println("test {");

View File

@ -18,6 +18,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.springframework.boot.build.docs.ConfigureJavadocLinks
import org.springframework.boot.build.optional.OptionalDependenciesPlugin
plugins {
id "dev.adamko.dokkatoo-html"
@ -237,9 +238,48 @@ dokkatoo {
moduleName.set("Spring Boot Kotlin API")
}
configurations {
javadoc {
canBeConsumed = true
canBeResolved = false
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EMBEDDED))
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, DocsType.JAVADOC))
}
}
javadocSource {
transitive = false
canBeConsumed = false
canBeResolved = true
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.VERIFICATION))
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, DocsType.SOURCES))
attribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE, objects.named(VerificationType.class, VerificationType.MAIN_SOURCES))
}
extendsFrom configurations.javadoc
}
javadocClasspath {
canBeConsumed = false
canBeResolved = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
}
extendsFrom configurations.javadoc
resolutionStrategy.eachDependency {
if (it.requested.group == 'org.opensaml') {
it.useVersion '4.0.1'
}
}
}
}
def aggregatedJavadoc = tasks.register('aggregatedJavadoc', Javadoc) {
dependsOn configurations.resolvedBom
destinationDir = project.file(project.layout.buildDirectory.dir("docs/javadoc"))
source = configurations.javadocSource
classpath = configurations.javadocClasspath
include("**/*.java")
options {
author = true
docTitle = "Spring Boot ${project.version} API"
@ -253,7 +293,7 @@ def aggregatedJavadoc = tasks.register('aggregatedJavadoc', Javadoc) {
}
project.rootProject.gradle.projectsEvaluated {
Set<Project> publishedProjects = rootProject.subprojects
rootProject.subprojects
.findAll { it != project }
.findAll { it.plugins.hasPlugin(JavaPlugin) && it.plugins.hasPlugin(MavenPublishPlugin) }
.findAll { !it.path.contains(":build-plugin:") }
@ -262,11 +302,14 @@ project.rootProject.gradle.projectsEvaluated {
.findAll { !it.path.contains(":core:spring-boot-properties-migrator") }
.findAll { !it.path.contains(":loader:spring-boot-jarmode-tools") }
.findAll { !it.name.startsWith('spring-boot-starter') }
aggregatedJavadoc.configure {
dependsOn publishedProjects.javadoc
source publishedProjects.javadoc.source
classpath = project.files(publishedProjects.javadoc.classpath)
}
.each { javadocProject ->
dependencies {
javadoc(project(javadocProject.path))
if (javadocProject.plugins.hasPlugin(OptionalDependenciesPlugin)) {
javadoc(project(path: javadocProject.path, configuration: 'optional'))
}
}
}
}
aggregates {

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

5
gradlew vendored
View File

@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"

3
gradlew.bat vendored
View File

@ -70,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@ -43,7 +43,7 @@ tasks.register("bootJarUnpack", BootJar.class) {
classpath = bootJar.classpath
requiresUnpack '**/bcprov-jdk18on-*.jar'
archiveClassifier.set("unpack")
targetJavaVersion = targetCompatibility
targetJavaVersion = java.targetCompatibility
}
build.dependsOn bootJarUnpack

View File

@ -86,7 +86,7 @@ static boolean isWindows() {
task.mainClass = "com.example.ResourceHandlingApplication"
task.classpath = configurations.getByName(webServer)
task.archiveClassifier = webServer
task.targetJavaVersion = project.getTargetCompatibility()
task.targetJavaVersion = java.targetCompatibility
}
tasks.register("${webServer}BootJar", BootJar, configurer)
tasks.register("${webServer}BootWar", BootWar, configurer)

View File

@ -62,7 +62,7 @@ dependencies {
task.mainClass = "org.springframework.boot.sni.server.SniServerApplication"
task.classpath = configurations.getByName(webServer)
task.archiveClassifier = webServer
task.targetJavaVersion = project.getTargetCompatibility()
task.targetJavaVersion = project.java.targetCompatibility
}
tasks.register("${webServer}ServerApp", BootJar, configurer)
}

View File

@ -62,7 +62,7 @@ dependencies {
task.mainClass = "org.springframework.boot.sni.server.SniServerApplication"
task.classpath = configurations.getByName(webServer)
task.archiveClassifier = webServer
task.targetJavaVersion = project.getTargetCompatibility()
task.targetJavaVersion = project.java.targetCompatibility
}
tasks.register("${webServer}ServerApp", BootJar, configurer)
}

View File

@ -30,8 +30,3 @@ dependencies {
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter")
}
jar {
reproducibleFileOrder = true
preserveFileTimestamps = false
}

View File

@ -50,7 +50,7 @@ dependencies {
testImplementation("org.zeroturnaround:zt-zip:1.13")
}
tasks.register("reproducibleLoaderJar", Jar) {
tasks.register("loaderJar", Jar) {
dependsOn configurations.loader
from {
zipTree(configurations.loader.incoming.files.singleFile).matching {
@ -59,8 +59,6 @@ tasks.register("reproducibleLoaderJar", Jar) {
exclude "META-INF/spring-boot.properties"
}
}
reproducibleFileOrder = true
preserveFileTimestamps = false
archiveFileName = "spring-boot-loader.jar"
destinationDirectory = file(generatedResources.map {it.dir("META-INF/loader") })
}
@ -76,7 +74,7 @@ tasks.register("toolsJar", Sync) {
sourceSets {
main {
output.dir(generatedResources, builtBy: [toolsJar, reproducibleLoaderJar])
output.dir(generatedResources, builtBy: [toolsJar, loaderJar])
}
}

View File

@ -36,7 +36,7 @@ public final class GradleVersions {
if (isJavaVersion(JavaVersion.VERSION_25)) {
return Arrays.asList("9.0.0", "9.1.0");
}
return Arrays.asList(GradleVersion.current().getVersion(), "9.0.0", "9.1.0");
return Arrays.asList("8.14.3", "9.0.0", GradleVersion.current().getVersion());
}
public static String minimumCompatible() {