Merge branch 'gradle-plugin-configuration-cache'

Closes gh-22922
pull/23886/head
Andy Wilkinson 4 years ago
commit 72361a7521

@ -43,6 +43,7 @@ The Spring Boot Gradle Plugin provides Spring Boot support in https://gradle.org
It allows you to package executable jar or war archives, run Spring Boot applications, and use the dependency management provided by `spring-boot-dependencies`.
Spring Boot's Gradle plugin requires Gradle 6 (6.3 or later).
Gradle 5.6 is also supported but this support is deprecated and will be removed in a future release.
Gradle's {gradle-userguide}/configuration_cache.html[configuration cache] is supported when using Gradle 6.7 or later.
In addition to this user guide, {api-documentation}[API documentation] is also available.

@ -10,8 +10,3 @@ application {
}
// end::main-class[]
task configuredMainClass {
doLast {
println bootRun.main
}
}

@ -11,9 +11,3 @@ application {
mainClass.set("com.example.ExampleApplication")
}
// end::main-class[]
task("configuredMainClass") {
doLast {
println(tasks.getByName<BootRun>("bootRun").main)
}
}

@ -9,9 +9,3 @@ springBoot {
mainClass = 'com.example.ExampleApplication'
}
// end::main-class[]
task configuredMainClass {
doLast {
println bootRun.main
}
}

@ -11,9 +11,3 @@ springBoot {
mainClass.set("com.example.ExampleApplication")
}
// end::main-class[]
task("configuredMainClass") {
doLast {
println(tasks.getByName<BootRun>("bootRun").main)
}
}

@ -21,7 +21,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
@ -39,6 +38,7 @@ import org.gradle.api.plugins.ApplicationPlugin;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.compile.JavaCompile;
@ -93,25 +93,25 @@ final class JavaPluginAction implements PluginApplicationAction {
}
private TaskProvider<BootJar> configureBootJarTask(Project project) {
SourceSet mainSourceSet = javaPluginConvention(project).getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
Configuration developmentOnly = project.getConfigurations()
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
Configuration productionRuntimeClasspath = project.getConfigurations()
.getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME);
FileCollection classpath = mainSourceSet.getRuntimeClasspath()
.minus((developmentOnly.minus(productionRuntimeClasspath))).filter(new JarTypeFileSpec());
TaskProvider<ResolveMainClassName> resolveMainClassName = ResolveMainClassName
.registerForTask(SpringBootPlugin.BOOT_JAR_TASK_NAME, project, classpath);
return project.getTasks().register(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class, (bootJar) -> {
bootJar.setDescription(
"Assembles an executable jar archive containing the main classes and their dependencies.");
bootJar.setGroup(BasePlugin.BUILD_GROUP);
SourceSet mainSourceSet = javaPluginConvention(project).getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
bootJar.classpath((Callable<FileCollection>) () -> {
Configuration developmentOnly = project.getConfigurations()
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
Configuration productionRuntimeClasspath = project.getConfigurations()
.getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME);
return mainSourceSet.getRuntimeClasspath().minus((developmentOnly.minus(productionRuntimeClasspath)))
.filter(new JarTypeFileSpec());
});
bootJar.getMainClass().convention(project.provider(() -> {
String manifestStartClass = (String) bootJar.getManifest().getAttributes().get("Start-Class");
return (manifestStartClass != null) ? manifestStartClass
: new MainClassConvention(project, bootJar::getClasspath).call();
}));
bootJar.classpath(classpath);
Provider<String> manifestStartClass = project
.provider(() -> (String) bootJar.getManifest().getAttributes().get("Start-Class"));
bootJar.getMainClass().convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent()
? manifestStartClass : resolveMainClassName.get().readMainClassName()));
});
}
@ -130,18 +130,28 @@ final class JavaPluginAction implements PluginApplicationAction {
}
private void configureBootRunTask(Project project) {
FileCollection classpath = javaPluginConvention(project).getSourceSets()
.findByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath().filter(new JarTypeFileSpec());
TaskProvider<ResolveMainClassName> resolveProvider = ResolveMainClassName.registerForTask("bootRun", project,
classpath);
project.getTasks().register("bootRun", BootRun.class, (run) -> {
run.setDescription("Runs this project as a Spring Boot application.");
run.setGroup(ApplicationPlugin.APPLICATION_GROUP);
run.classpath(javaPluginConvention(project).getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME)
.getRuntimeClasspath().filter(new JarTypeFileSpec()));
run.classpath(classpath);
run.getConventionMapping().map("jvmArgs", () -> {
if (project.hasProperty("applicationDefaultJvmArgs")) {
return project.property("applicationDefaultJvmArgs");
}
return Collections.emptyList();
});
run.conventionMapping("main", new MainClassConvention(project, run::getClasspath));
try {
run.getMainClass().convention(resolveProvider.flatMap(ResolveMainClassName::readMainClassName));
}
catch (NoSuchMethodError ex) {
run.getInputs().file(resolveProvider.map((task) -> task.getOutputFile()));
run.conventionMapping("main",
() -> resolveProvider.flatMap(ResolveMainClassName::readMainClassName).get());
}
});
}

@ -1,109 +0,0 @@
/*
* Copyright 2012-2020 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.boot.gradle.plugin;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.provider.Property;
import org.springframework.boot.gradle.dsl.SpringBootExtension;
import org.springframework.boot.loader.tools.MainClassFinder;
/**
* A {@link Callable} that provides a convention for the project's main class name.
*
* @author Andy Wilkinson
*/
final class MainClassConvention implements Callable<String> {
private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";
private final Project project;
private final Supplier<FileCollection> classpathSupplier;
MainClassConvention(Project project, Supplier<FileCollection> classpathSupplier) {
this.project = project;
this.classpathSupplier = classpathSupplier;
}
@Override
public String call() throws Exception {
SpringBootExtension springBootExtension = this.project.getExtensions().findByType(SpringBootExtension.class);
if (springBootExtension != null) {
String mainClass = springBootExtension.getMainClass().getOrNull();
if (mainClass != null) {
return mainClass;
}
}
String javaApplicationMainClass = getJavaApplicationMainClass();
return (javaApplicationMainClass != null) ? javaApplicationMainClass : resolveMainClass();
}
@SuppressWarnings({ "unchecked", "deprecation" })
private String getJavaApplicationMainClass() {
JavaApplication javaApplication = this.project.getConvention().findByType(JavaApplication.class);
if (javaApplication == null) {
return null;
}
Method getMainClass = findMethod(JavaApplication.class, "getMainClass");
if (getMainClass != null) {
try {
Property<String> mainClass = (Property<String>) getMainClass.invoke(javaApplication);
return mainClass.getOrElse(null);
}
catch (Exception ex) {
// Continue
}
}
return javaApplication.getMainClassName();
}
private static Method findMethod(Class<?> type, String name) {
for (Method candidate : type.getMethods()) {
if (candidate.getName().equals(name)) {
return candidate;
}
}
return null;
}
private String resolveMainClass() {
return this.classpathSupplier.get().filter(File::isDirectory).getFiles().stream().map(this::findMainClass)
.filter(Objects::nonNull).findFirst().orElseThrow(() -> new InvalidUserDataException(
"Main class name has not been configured and it could not be resolved"));
}
private String findMainClass(File file) {
try {
return MainClassFinder.findSingleMainClass(file, SPRING_BOOT_APPLICATION_CLASS_NAME);
}
catch (IOException ex) {
return null;
}
}
}

@ -0,0 +1,192 @@
/*
* Copyright 2012-2020 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.boot.gradle.plugin;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import org.gradle.api.DefaultTask;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.Convention;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskProvider;
import org.springframework.boot.gradle.dsl.SpringBootExtension;
import org.springframework.boot.loader.tools.MainClassFinder;
/**
* {@link Task} for resolving the name of the application's main class.
*
* @author Andy Wilkinson
* @since 2.4
*/
public class ResolveMainClassName extends DefaultTask {
private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";
private final RegularFileProperty outputFile;
private final Property<String> configuredMainClass;
private FileCollection classpath;
/**
* Creates a new instance of the {@code ResolveMainClassName} task.
*/
public ResolveMainClassName() {
this.outputFile = getProject().getObjects().fileProperty();
this.configuredMainClass = getProject().getObjects().property(String.class);
}
/**
* Returns the classpath that the task will examine when resolving the main class
* name.
* @return the classpath
*/
@Classpath
public FileCollection getClasspath() {
return this.classpath;
}
/**
* Sets the classpath that the task will examine when resolving the main class name.
* @param classpath the classpath
*/
public void setClasspath(FileCollection classpath) {
this.classpath = classpath;
}
/**
* Returns the property for the task's output file that will contain the name of the
* main class.
* @return the output file
*/
@OutputFile
public RegularFileProperty getOutputFile() {
return this.outputFile;
}
/**
* Returns the property for the explicitly configured main class name that should be
* used in favour of resolving the main class name from the classpath.
* @return the configured main class name property
*/
@Input
@Optional
public Property<String> getConfiguredMainClassName() {
return this.configuredMainClass;
}
@TaskAction
void resolveAndStoreMainClassName() throws IOException {
File outputFile = this.outputFile.getAsFile().get();
outputFile.getParentFile().mkdirs();
String mainClassName = resolveMainClassName();
Files.write(outputFile.toPath(), mainClassName.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
private String resolveMainClassName() {
String configuredMainClass = this.configuredMainClass.getOrNull();
if (configuredMainClass != null) {
return configuredMainClass;
}
return getClasspath().filter(File::isDirectory).getFiles().stream().map(this::findMainClass)
.filter(Objects::nonNull).findFirst().orElse("");
}
private String findMainClass(File file) {
try {
return MainClassFinder.findSingleMainClass(file, SPRING_BOOT_APPLICATION_CLASS_NAME);
}
catch (IOException ex) {
return null;
}
}
Provider<String> readMainClassName() {
return this.outputFile.map((file) -> {
if (file.getAsFile().length() == 0) {
throw new InvalidUserDataException(
"Main class name has not been configured and it could not be resolved");
}
Path output = file.getAsFile().toPath();
try {
return new String(Files.readAllBytes(output), StandardCharsets.UTF_8);
}
catch (IOException ex) {
throw new RuntimeException("Failed to read main class name from '" + output + "'");
}
});
}
static TaskProvider<ResolveMainClassName> registerForTask(String taskName, Project project,
FileCollection classpath) {
TaskProvider<ResolveMainClassName> resolveMainClassNameProvider = project.getTasks()
.register(taskName + "MainClassName", ResolveMainClassName.class, (resolveMainClassName) -> {
Convention convention = project.getConvention();
resolveMainClassName.setDescription(
"Resolves the name of the application's main class for the " + taskName + " task.");
resolveMainClassName.setGroup(BasePlugin.BUILD_GROUP);
resolveMainClassName.setClasspath(classpath);
resolveMainClassName.getConfiguredMainClassName().convention(project.provider(() -> {
String javaApplicationMainClass = getJavaApplicationMainClass(convention);
if (javaApplicationMainClass != null) {
return javaApplicationMainClass;
}
SpringBootExtension springBootExtension = project.getExtensions()
.findByType(SpringBootExtension.class);
return springBootExtension.getMainClass().getOrNull();
}));
resolveMainClassName.getOutputFile()
.set(project.getLayout().getBuildDirectory().file(taskName + "MainClassName"));
});
return resolveMainClassNameProvider;
}
@SuppressWarnings("deprecation")
private static String getJavaApplicationMainClass(Convention convention) {
JavaApplication javaApplication = convention.findByType(JavaApplication.class);
if (javaApplication == null) {
return null;
}
try {
return javaApplication.getMainClass().getOrNull();
}
catch (NoSuchMethodError ex) {
return javaApplication.getMainClassName();
}
}
}

@ -25,6 +25,9 @@ import org.gradle.api.file.FileCollection;
import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.WarPlugin;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.springframework.boot.gradle.tasks.bundling.BootWar;
@ -60,23 +63,31 @@ class WarPluginAction implements PluginApplicationAction {
}
private TaskProvider<BootWar> configureBootWarTask(Project project) {
return project.getTasks().register(SpringBootPlugin.BOOT_WAR_TASK_NAME, BootWar.class, (bootWar) -> {
bootWar.setGroup(BasePlugin.BUILD_GROUP);
bootWar.setDescription("Assembles an executable war archive containing webapp"
+ " content, and the main classes and their dependencies.");
bootWar.providedClasspath(providedRuntimeConfiguration(project));
Configuration developmentOnly = project.getConfigurations()
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
Configuration productionRuntimeClasspath = project.getConfigurations()
.getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME);
bootWar.setClasspath(bootWar.getClasspath().minus((developmentOnly.minus(productionRuntimeClasspath)))
.filter(new JarTypeFileSpec()));
bootWar.getMainClass().convention(project.provider(() -> {
String manifestStartClass = (String) bootWar.getManifest().getAttributes().get("Start-Class");
return (manifestStartClass != null) ? manifestStartClass
: new MainClassConvention(project, bootWar::getClasspath).call();
}));
});
Configuration developmentOnly = project.getConfigurations()
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
Configuration productionRuntimeClasspath = project.getConfigurations()
.getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME);
FileCollection classpath = project.getConvention().getByType(SourceSetContainer.class)
.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()
.minus(providedRuntimeConfiguration(project)).minus((developmentOnly.minus(productionRuntimeClasspath)))
.filter(new JarTypeFileSpec());
TaskProvider<ResolveMainClassName> resolveMainClassName = ResolveMainClassName
.registerForTask(SpringBootPlugin.BOOT_WAR_TASK_NAME, project, classpath);
TaskProvider<BootWar> bootWarProvider = project.getTasks().register(SpringBootPlugin.BOOT_WAR_TASK_NAME,
BootWar.class, (bootWar) -> {
bootWar.setGroup(BasePlugin.BUILD_GROUP);
bootWar.setDescription("Assembles an executable war archive containing webapp"
+ " content, and the main classes and their dependencies.");
bootWar.providedClasspath(providedRuntimeConfiguration(project));
bootWar.setClasspath(classpath);
Provider<String> manifestStartClass = project
.provider(() -> (String) bootWar.getManifest().getAttributes().get("Start-Class"));
bootWar.getMainClass()
.convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent()
? manifestStartClass : resolveMainClassName.get().readMainClassName()));
});
bootWarProvider.map((bootWar) -> bootWar.getClasspath());
return bootWarProvider;
}
private FileCollection providedRuntimeConfiguration(Project project) {

@ -24,6 +24,7 @@ import java.util.Map;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.internal.ConventionTask;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputDirectory;
@ -44,7 +45,12 @@ public class BuildInfo extends ConventionTask {
private final BuildInfoProperties properties = new BuildInfoProperties(getProject());
private File destinationDir;
private final DirectoryProperty destinationDir;
public BuildInfo() {
this.destinationDir = getProject().getObjects().directoryProperty()
.convention(getProject().getLayout().getBuildDirectory());
}
/**
* Generates the {@code build-info.properties} file in the configured
@ -73,7 +79,7 @@ public class BuildInfo extends ConventionTask {
*/
@OutputDirectory
public File getDestinationDir() {
return (this.destinationDir != null) ? this.destinationDir : getProject().getBuildDir();
return this.destinationDir.getAsFile().get();
}
/**
@ -81,7 +87,7 @@ public class BuildInfo extends ConventionTask {
* @param destinationDir the destination directory
*/
public void setDestinationDir(File destinationDir) {
this.destinationDir = destinationDir;
this.destinationDir.set(destinationDir);
}
/**

@ -16,6 +16,8 @@
package org.springframework.boot.gradle.tasks.buildinfo;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.time.Instant;
import java.util.HashMap;
@ -23,6 +25,7 @@ import java.util.Map;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
@ -35,6 +38,8 @@ import org.gradle.api.tasks.Optional;
@SuppressWarnings("serial")
public class BuildInfoProperties implements Serializable {
private transient Instant creationTime = Instant.now();
private final Property<String> group;
private final Property<String> artifact;
@ -43,22 +48,35 @@ public class BuildInfoProperties implements Serializable {
private final Property<String> name;
private final Property<Instant> time;
private final Property<Long> time;
private boolean timeConfigured = false;
private Map<String, Object> additionalProperties = new HashMap<>();
BuildInfoProperties(Project project) {
this.time = project.getObjects().property(Instant.class);
this.time.set(Instant.now());
this.time = project.getObjects().property(Long.class);
this.group = project.getObjects().property(String.class);
this.group.set(project.provider(() -> project.getGroup().toString()));
this.artifact = project.getObjects().property(String.class);
this.version = project.getObjects().property(String.class);
this.version.set(project.provider(() -> project.getVersion().toString()));
this.version.set(projectVersion(project));
this.name = project.getObjects().property(String.class);
this.name.set(project.provider(project::getName));
}
private Provider<String> projectVersion(Project project) {
try {
Provider<String> externalVersionProperty = project.getProviders().gradleProperty("version")
.forUseAtConfigurationTime();
externalVersionProperty.getOrNull();
}
catch (NoSuchMethodError ex) {
// Gradle < 6.5
}
return project.provider(() -> project.getVersion().toString());
}
/**
* Returns the value used for the {@code build.group} property. Defaults to the
* {@link Project#getGroup() Project's group}.
@ -142,7 +160,14 @@ public class BuildInfoProperties implements Serializable {
@Input
@Optional
public Instant getTime() {
return this.time.getOrNull();
Long epochMillis = this.time.getOrNull();
if (epochMillis != null) {
return Instant.ofEpochMilli(epochMillis);
}
if (this.timeConfigured) {
return null;
}
return this.creationTime;
}
/**
@ -150,7 +175,8 @@ public class BuildInfoProperties implements Serializable {
* @param time the build time
*/
public void setTime(Instant time) {
this.time.set(time);
this.timeConfigured = true;
this.time.set((time != null) ? time.toEpochMilli() : null);
}
/**
@ -173,4 +199,9 @@ public class BuildInfoProperties implements Serializable {
this.additionalProperties = additionalProperties;
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
input.defaultReadObject();
this.creationTime = Instant.now();
}
}

@ -59,6 +59,10 @@ public class BootBuildImage extends DefaultTask {
private static final String BUILDPACK_JVM_VERSION_KEY = "BP_JVM_VERSION";
private final String projectName;
private final Property<String> projectVersion;
private RegularFileProperty jar;
private Property<JavaVersion> targetJavaVersion;
@ -84,6 +88,9 @@ public class BootBuildImage extends DefaultTask {
public BootBuildImage() {
this.jar = getProject().getObjects().fileProperty();
this.targetJavaVersion = getProject().getObjects().property(JavaVersion.class);
this.projectName = getProject().getName();
this.projectVersion = getProject().getObjects().property(String.class);
this.projectVersion.set(getProject().provider(() -> getProject().getVersion().toString()));
}
/**
@ -318,12 +325,11 @@ public class BootBuildImage extends DefaultTask {
if (StringUtils.hasText(this.imageName)) {
return ImageReference.of(this.imageName);
}
ImageName imageName = ImageName.of(getProject().getName());
String version = getProject().getVersion().toString();
if ("unspecified".equals(version)) {
ImageName imageName = ImageName.of(this.projectName);
if ("unspecified".equals(this.projectVersion.get())) {
return ImageReference.of(imageName);
}
return ImageReference.of(imageName, version);
return ImageReference.of(imageName, this.projectVersion.get());
}
private BuildRequest customize(BuildRequest request) {

@ -19,6 +19,7 @@ package org.springframework.boot.gradle.tasks.bundling;
import java.io.File;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.function.Function;
import org.gradle.api.Action;
import org.gradle.api.Project;
@ -72,7 +73,7 @@ public class BootJar extends Jar implements BootArchive {
* Creates a new {@code BootJar} task.
*/
public BootJar() {
this.support = new BootArchiveSupport(LAUNCHER, this::isLibrary, this::resolveZipCompression);
this.support = new BootArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver());
this.bootInfSpec = getProject().copySpec().into("BOOT-INF");
this.mainClass = getProject().getObjects().property(String.class);
configureBootInfSpec(this.bootInfSpec);
@ -325,4 +326,22 @@ public class BootJar extends Jar implements BootArchive {
return this.resolvedDependencies;
}
private final class LibrarySpec implements Spec<FileCopyDetails> {
@Override
public boolean isSatisfiedBy(FileCopyDetails details) {
return isLibrary(details);
}
}
private final class ZipCompressionResolver implements Function<FileCopyDetails, ZipCompression> {
@Override
public ZipCompression apply(FileCopyDetails details) {
return resolveZipCompression(details);
}
}
}

@ -18,6 +18,7 @@ package org.springframework.boot.gradle.tasks.bundling;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.function.Function;
import org.gradle.api.Action;
import org.gradle.api.Project;
@ -59,7 +60,7 @@ public class BootWar extends War implements BootArchive {
* Creates a new {@code BootWar} task.
*/
public BootWar() {
this.support = new BootArchiveSupport(LAUNCHER, this::isLibrary, this::resolveZipCompression);
this.support = new BootArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver());
this.mainClass = getProject().getObjects().property(String.class);
getWebInf().into("lib-provided", fromCallTo(this::getProvidedLibFiles));
this.support.moveModuleInfoToRoot(getRootSpec());
@ -232,4 +233,22 @@ public class BootWar extends War implements BootArchive {
return callable;
}
private final class LibrarySpec implements Spec<FileCopyDetails> {
@Override
public boolean isSatisfiedBy(FileCopyDetails details) {
return isLibrary(details);
}
}
private final class ZipCompressionResolver implements Function<FileCopyDetails, ZipCompression> {
@Override
public ZipCompression apply(FileCopyDetails details) {
return resolveZipCompression(details);
}
}
}

@ -16,7 +16,9 @@
package org.springframework.boot.gradle.tasks.run;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Set;
import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.tasks.Input;
@ -63,8 +65,9 @@ public class BootRun extends JavaExec {
* @param sourceSet the source set
*/
public void sourceResources(SourceSet sourceSet) {
setClasspath(getProject().files(sourceSet.getResources().getSrcDirs(), getClasspath())
.filter((file) -> !file.equals(sourceSet.getOutput().getResourcesDir())));
File resourcesDir = sourceSet.getOutput().getResourcesDir();
Set<File> srcDirs = sourceSet.getResources().getSrcDirs();
setClasspath(getProject().files(srcDirs, getClasspath()).filter((file) -> !file.equals(resourcesDir)));
}
@Override

@ -0,0 +1,34 @@
/*
* Copyright 2012-2020 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 com.example.main;
/**
* Application used for testing {@code BootRun}'s main class configuration.
*
* @author Andy Wilkinson
*/
public class CustomMainClass {
protected CustomMainClass() {
}
public static void main(String[] args) {
System.out.println(CustomMainClass.class.getName());
}
}

@ -17,7 +17,9 @@
package org.springframework.boot.gradle.docs;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.condition.DisabledForJreRange;
@ -43,20 +45,23 @@ class RunningDocumentationTests {
@TestTemplate
@DisabledForJreRange(min = JRE.JAVA_13)
void bootRunMain() throws IOException {
assertThat(this.gradleBuild.script("src/docs/gradle/running/boot-run-main").build("configuredMainClass")
.getOutput()).contains("com.example.ExampleApplication");
writeMainClass();
assertThat(this.gradleBuild.script("src/docs/gradle/running/boot-run-main").build("bootRun").getOutput())
.contains("com.example.ExampleApplication");
}
@TestTemplate
void applicationPluginMainClassName() {
void applicationPluginMainClassName() throws IOException {
writeMainClass();
assertThat(this.gradleBuild.script("src/docs/gradle/running/application-plugin-main-class-name")
.build("configuredMainClass").getOutput()).contains("com.example.ExampleApplication");
.build("bootRun").getOutput()).contains("com.example.ExampleApplication");
}
@TestTemplate
void springBootDslMainClassName() throws IOException {
assertThat(this.gradleBuild.script("src/docs/gradle/running/spring-boot-dsl-main-class-name")
.build("configuredMainClass").getOutput()).contains("com.example.ExampleApplication");
writeMainClass();
assertThat(this.gradleBuild.script("src/docs/gradle/running/spring-boot-dsl-main-class-name").build("bootRun")
.getOutput()).contains("com.example.ExampleApplication");
}
@TestTemplate
@ -84,4 +89,18 @@ class RunningDocumentationTests {
.contains("com.example.property = custom");
}
private void writeMainClass() throws IOException {
File exampleApplication = new File(this.gradleBuild.getProjectDir(),
"src/main/java/com/example/ExampleApplication.java");
exampleApplication.getParentFile().mkdirs();
try (PrintWriter writer = new PrintWriter(new FileWriter(exampleApplication))) {
writer.println("package com.example;");
writer.println("public class ExampleApplication {");
writer.println(" public static void main(String[] args) {");
writer.println(" System.out.println(ExampleApplication.class.getName());");
writer.println(" }");
writer.println("}");
}
}
}

@ -41,4 +41,11 @@ import org.springframework.boot.gradle.testkit.GradleBuild;
@ExtendWith(GradleCompatibilityExtension.class)
public @interface GradleCompatibility {
/**
* Whether to include running Gradle with {@code --cache-configuration} cache in the
* compatibility matrix.
* @return {@code true} to enable the configuration cache, {@code false} otherwise
*/
boolean configurationCache() default false;
}

@ -16,16 +16,19 @@
package org.springframework.boot.gradle.junit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.gradle.api.JavaVersion;
import org.gradle.util.GradleVersion;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import org.junit.platform.commons.util.AnnotationUtils;
import org.springframework.boot.gradle.testkit.GradleBuild;
import org.springframework.boot.gradle.testkit.GradleBuildExtension;
@ -45,16 +48,29 @@ final class GradleCompatibilityExtension implements TestTemplateInvocationContex
JavaVersion javaVersion = JavaVersion.current();
if (javaVersion.isCompatibleWith(JavaVersion.VERSION_14)
|| javaVersion.isCompatibleWith(JavaVersion.VERSION_13)) {
GRADLE_VERSIONS = Arrays.asList("6.3", "6.4.1", "6.5.1", "6.6.1", "default");
GRADLE_VERSIONS = Arrays.asList("6.3", "6.4.1", "6.5.1", "6.6.1", "current");
}
else {
GRADLE_VERSIONS = Arrays.asList("5.6.4", "6.3", "6.4.1", "6.5.1", "6.6.1", "default");
GRADLE_VERSIONS = Arrays.asList("5.6.4", "6.3", "6.4.1", "6.5.1", "6.6.1", "current");
}
}
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
return GRADLE_VERSIONS.stream().map(GradleVersionTestTemplateInvocationContext::new);
return GRADLE_VERSIONS.stream().flatMap((version) -> {
if (version.equals("current")) {
version = GradleVersion.current().getVersion();
}
List<TestTemplateInvocationContext> invocationContexts = new ArrayList<>();
invocationContexts.add(new GradleVersionTestTemplateInvocationContext(version, false));
boolean configurationCache = AnnotationUtils
.findAnnotation(context.getRequiredTestClass(), GradleCompatibility.class).get()
.configurationCache();
if (configurationCache && GradleVersion.version(version).compareTo(GradleVersion.version("6.7")) >= 0) {
invocationContexts.add(new GradleVersionTestTemplateInvocationContext(version, true));
}
return invocationContexts.stream();
});
}
@Override
@ -66,20 +82,23 @@ final class GradleCompatibilityExtension implements TestTemplateInvocationContex
private final String gradleVersion;
GradleVersionTestTemplateInvocationContext(String gradleVersion) {
private final boolean configurationCache;
GradleVersionTestTemplateInvocationContext(String gradleVersion, boolean configurationCache) {
this.gradleVersion = gradleVersion;
this.configurationCache = configurationCache;
}
@Override
public String getDisplayName(int invocationIndex) {
return "Gradle " + this.gradleVersion;
return "Gradle " + this.gradleVersion + ((this.configurationCache) ? " --configuration-cache" : "");
}
@Override
public List<Extension> getAdditionalExtensions() {
GradleBuild gradleBuild = new GradleBuild();
if (!this.gradleVersion.equals("default")) {
gradleBuild.gradleVersion(this.gradleVersion);
GradleBuild gradleBuild = new GradleBuild().gradleVersion(this.gradleVersion);
if (this.configurationCache) {
gradleBuild.configurationCache();
}
return Arrays.asList(new GradleBuildFieldSetter(gradleBuild), new GradleBuildExtension());
}

@ -1,81 +0,0 @@
/*
* Copyright 2012-2020 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.boot.gradle.plugin;
import java.io.File;
import java.io.IOException;
import org.gradle.api.Project;
import org.gradle.api.plugins.ApplicationPlugin;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.gradle.dsl.SpringBootExtension;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link MainClassConvention}.
*
* @author Andy Wilkinson
*/
class MainClassConventionTests {
@TempDir
File temp;
private Project project;
private MainClassConvention convention;
@BeforeEach
void createConvention() throws IOException {
this.project = ProjectBuilder.builder().withProjectDir(this.temp).build();
this.convention = new MainClassConvention(this.project, () -> null);
}
@Test
void javaApplicationExtensionMainClassNameIsUsed() throws Exception {
this.project.getPlugins().apply(ApplicationPlugin.class);
JavaApplication extension = this.project.getExtensions().findByType(JavaApplication.class);
extension.getMainClass().set("com.example.MainClass");
assertThat(this.convention.call()).isEqualTo("com.example.MainClass");
}
@Test
void springBootExtensionMainClassNameIsUsed() throws Exception {
SpringBootExtension extension = this.project.getExtensions().create("springBoot", SpringBootExtension.class,
this.project);
extension.getMainClass().set("com.example.MainClass");
assertThat(this.convention.call()).isEqualTo("com.example.MainClass");
}
@Test
void springBootExtensionMainClassNameIsUsedInPreferenceToJavaApplicationExtensionMainClassName() throws Exception {
this.project.getPlugins().apply(ApplicationPlugin.class);
JavaApplication javaApplication = this.project.getExtensions().findByType(JavaApplication.class);
javaApplication.getMainClass().set("com.example.JavaApplicationMainClass");
SpringBootExtension extension = this.project.getExtensions().create("springBoot", SpringBootExtension.class,
this.project);
extension.getMainClass().set("com.example.SpringBootExtensionMainClass");
assertThat(this.convention.call()).isEqualTo("com.example.SpringBootExtensionMainClass");
}
}

@ -19,9 +19,13 @@ package org.springframework.boot.gradle.tasks.buildinfo;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.Properties;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.InvalidRunnerConfigurationException;
import org.gradle.testkit.runner.TaskOutcome;
import org.gradle.testkit.runner.UnexpectedBuildFailure;
@ -38,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
@GradleCompatibility
@GradleCompatibility(configurationCache = true)
class BuildInfoIntegrationTests {
GradleBuild gradleBuild;
@ -69,7 +73,14 @@ class BuildInfoIntegrationTests {
@TestTemplate
void notUpToDateWhenExecutedTwiceAsTimeChanges() {
assertThat(this.gradleBuild.build("buildInfo").task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Properties first = buildInfoProperties();
String firstBuildTime = first.getProperty("build.time");
assertThat(firstBuildTime).isNotNull();
assertThat(this.gradleBuild.build("buildInfo").task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Properties second = buildInfoProperties();
String secondBuildTime = second.getProperty("build.time");
assertThat(secondBuildTime).isNotNull();
assertThat(Instant.parse(firstBuildTime).isBefore(Instant.parse(secondBuildTime)));
}
@TestTemplate
@ -81,11 +92,22 @@ class BuildInfoIntegrationTests {
}
@TestTemplate
void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion() {
assertThat(this.gradleBuild.build("buildInfo", "-PnullTime").task(":buildInfo").getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
BuildResult result = this.gradleBuild.build("buildInfo", "-PnullTime", "-PprojectVersion=0.2.0");
assertThat(result.task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion() throws IOException {
assertThat(this.gradleBuild.scriptProperty("projectVersion", "0.1.0").build("buildInfo").task(":buildInfo")
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.scriptProperty("projectVersion", "0.2.0").build("buildInfo").task(":buildInfo")
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
@TestTemplate
void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedGradlePropertiesProjectVersion() throws IOException {
Path gradleProperties = new File(this.gradleBuild.getProjectDir(), "gradle.properties").toPath();
Files.write(gradleProperties, "version=0.1.0".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE,
StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
assertThat(this.gradleBuild.build("buildInfo").task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
Files.write(gradleProperties, "version=0.2.0".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE,
StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
assertThat(this.gradleBuild.build("buildInfo").task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
@TestTemplate

@ -24,6 +24,8 @@ 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.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@ -125,7 +127,10 @@ class BuildInfoTests {
private Project createProject(String projectName) {
File projectDir = new File(this.temp, projectName);
return ProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build();
Project project = ProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build();
((ProjectInternal) project).getServices().get(GradlePropertiesController.class)
.loadGradlePropertiesFrom(projectDir);
return project;
}
private BuildInfo createTask(Project project) {

@ -32,9 +32,9 @@ import org.gradle.testkit.runner.TaskOutcome;
import org.gradle.testkit.runner.UnexpectedBuildFailure;
import org.junit.jupiter.api.TestTemplate;
import org.springframework.boot.gradle.junit.GradleCompatibility;
import org.springframework.boot.gradle.testkit.GradleBuild;
import org.springframework.boot.loader.tools.FileUtils;
import org.springframework.util.FileSystemUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -43,7 +43,6 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
@GradleCompatibility
abstract class AbstractBootArchiveIntegrationTests {
private final String taskName;
@ -99,25 +98,25 @@ abstract class AbstractBootArchiveIntegrationTests {
@TestTemplate
void notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded() {
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.build("-PincludeLaunchScript=true", this.taskName).task(":" + this.taskName)
assertThat(this.gradleBuild.scriptProperty("launchScript", "").build(this.taskName).task(":" + this.taskName)
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.scriptProperty("launchScript", "launchScript()").build(this.taskName)
.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
@TestTemplate
void notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded() {
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.build("-PincludeLaunchScript=true", this.taskName).task(":" + this.taskName)
assertThat(this.gradleBuild.scriptProperty("launchScript", "launchScript()").build(this.taskName)
.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.scriptProperty("launchScript", "").build(this.taskName).task(":" + this.taskName)
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
@TestTemplate
void notUpToDateWhenLaunchScriptPropertyChanges() {
assertThat(this.gradleBuild.build("-PincludeLaunchScript=true", "-PlaunchScriptProperty=foo", this.taskName)
assertThat(this.gradleBuild.scriptProperty("launchScriptProperty", "alpha").build(this.taskName)
.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.build("-PincludeLaunchScript=true", "-PlaunchScriptProperty=bar", this.taskName)
assertThat(this.gradleBuild.scriptProperty("launchScriptProperty", "bravo").build(this.taskName)
.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
@ -190,6 +189,27 @@ abstract class AbstractBootArchiveIntegrationTests {
}
}
@TestTemplate
void startClassIsSetByResolvingTheMainClass() throws IOException {
copyMainClassApplication();
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
Attributes mainAttributes = jarFile.getManifest().getMainAttributes();
assertThat(mainAttributes.getValue("Start-Class")).isEqualTo("com.example.main.CustomMainClass");
}
}
private void copyMainClassApplication() throws IOException {
copyApplication("main");
}
private void copyApplication(String name) throws IOException {
File output = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/" + name);
output.mkdirs();
FileSystemUtils.copyRecursively(new File("src/test/java/com/example/" + name), output);
}
private void createStandardJar(File location) throws IOException {
createJar(location, (attributes) -> {
});

@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Andy Wilkinson
* @author Scott Frederick
*/
@GradleCompatibility
@GradleCompatibility(configurationCache = true)
@DisabledIfDockerUnavailable
class BootBuildImageIntegrationTests {

@ -43,6 +43,7 @@ import org.gradle.testkit.runner.TaskOutcome;
import org.gradle.testkit.runner.UnexpectedBuildFailure;
import org.junit.jupiter.api.TestTemplate;
import org.springframework.boot.gradle.junit.GradleCompatibility;
import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.util.StringUtils;
@ -55,43 +56,39 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Madhura Bhave
* @author Paddy Drury
*/
@GradleCompatibility(configurationCache = true)
class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
BootJarIntegrationTests() {
super("bootJar", "BOOT-INF/lib/", "BOOT-INF/classes/");
}
@TestTemplate
void upToDateWhenBuiltTwiceWithLayers() throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome())
.isEqualTo(TaskOutcome.UP_TO_DATE);
}
@TestTemplate
void upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered()
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome())
.isEqualTo(TaskOutcome.UP_TO_DATE);
assertThat(this.gradleBuild.scriptProperty("layered", "").build("bootJar").task(":bootJar").getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
assertThat(
this.gradleBuild.scriptProperty("layered", "layered {}").build("bootJar").task(":bootJar").getOutcome())
.isEqualTo(TaskOutcome.UP_TO_DATE);
}
@TestTemplate
void notUpToDateWhenBuiltWithoutLayersAndThenWithLayers()
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "-PdisableLayers=true", "bootJar").task(":bootJar")
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.scriptProperty("layerEnablement", "enabled = false").build("bootJar")
.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.scriptProperty("layerEnablement", "enabled = true").build("bootJar")
.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
@TestTemplate
void notUpToDateWhenBuiltWithLayerToolsAndThenWithoutLayerTools()
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "-PexcludeTools=true", "bootJar").task(":bootJar")
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.scriptProperty("layerTools", "").build("bootJar").task(":bootJar").getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
assertThat(this.gradleBuild.scriptProperty("layerTools", "includeLayerTools = false").build("bootJar")
.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
}
@TestTemplate

@ -16,11 +16,14 @@
package org.springframework.boot.gradle.tasks.bundling;
import org.springframework.boot.gradle.junit.GradleCompatibility;
/**
* Integration tests for {@link BootJar}.
*
* @author Andy Wilkinson
*/
@GradleCompatibility(configurationCache = true)
class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests {
BootWarIntegrationTests() {

@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
*/
@GradleCompatibility
@GradleCompatibility(configurationCache = true)
class BootRunIntegrationTests {
GradleBuild gradleBuild;
@ -68,23 +68,25 @@ class BootRunIntegrationTests {
@TestTemplate
void springBootExtensionMainClassNameIsUsed() throws IOException {
BuildResult result = this.gradleBuild.build("echoMainClassName");
assertThat(result.task(":echoMainClassName").getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE);
assertThat(result.getOutput()).contains("Main class name = com.example.CustomMainClass");
copyMainClassApplication();
BuildResult result = this.gradleBuild.build("bootRun");
assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("com.example.main.CustomMainClass");
}
@TestTemplate
void applicationPluginMainClassNameIsUsed() throws IOException {
BuildResult result = this.gradleBuild.build("echoMainClassName");
assertThat(result.task(":echoMainClassName").getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE);
assertThat(result.getOutput()).contains("Main class name = com.example.CustomMainClass");
copyMainClassApplication();
BuildResult result = this.gradleBuild.build("bootRun");
assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("com.example.main.CustomMainClass");
}
@TestTemplate
void applicationPluginMainClassNameIsNotUsedWhenItIsNull() throws IOException {
copyClasspathApplication();
BuildResult result = this.gradleBuild.build("echoMainClassName");
assertThat(result.task(":echoMainClassName").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
BuildResult result = this.gradleBuild.build("bootRun");
assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Main class name = com.example.classpath.BootRunClasspathApplication");
}
@ -135,6 +137,10 @@ class BootRunIntegrationTests {
assertThat(result.getOutput()).contains("standard.jar").doesNotContain("starter.jar");
}
private void copyMainClassApplication() throws IOException {
copyApplication("main");
}
private void copyClasspathApplication() throws IOException {
copyApplication("classpath");
}

@ -24,7 +24,10 @@ import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.JarFile;
import com.fasterxml.jackson.annotation.JsonView;
@ -72,8 +75,14 @@ public class GradleBuild {
private GradleVersion expectDeprecationWarnings;
private boolean configurationCache = false;
private Map<String, String> scriptProperties = new HashMap<>();
public GradleBuild() {
this(Dsl.GROOVY);
this.scriptProperties.put("bootVersion", getBootVersion());
this.scriptProperties.put("dependencyManagementPluginVersion", getDependencyManagementPluginVersion());
}
public GradleBuild(Dsl dsl) {
@ -124,6 +133,16 @@ public class GradleBuild {
return this;
}
public GradleBuild configurationCache() {
this.configurationCache = true;
return this;
}
public GradleBuild scriptProperty(String key, String value) {
this.scriptProperties.put(key, value);
return this;
}
public BuildResult build(String... arguments) {
try {
BuildResult result = prepareRunner(arguments).build();
@ -148,15 +167,16 @@ public class GradleBuild {
}
public GradleRunner prepareRunner(String... arguments) throws IOException {
String scriptContent = FileCopyUtils.copyToString(new FileReader(this.script))
.replace("{version}", getBootVersion())
.replace("{dependency-management-plugin-version}", getDependencyManagementPluginVersion());
String scriptContent = FileCopyUtils.copyToString(new FileReader(this.script));
for (Entry<String, String> property : this.scriptProperties.entrySet()) {
scriptContent = scriptContent.replace("{" + property.getKey() + "}", property.getValue());
}
FileCopyUtils.copy(scriptContent, new FileWriter(new File(this.projectDir, "build" + this.dsl.getExtension())));
FileSystemUtils.copyRecursively(new File("src/test/resources/repository"),
new File(this.projectDir, "repository"));
GradleRunner gradleRunner = GradleRunner.create().withProjectDir(this.projectDir)
.withPluginClasspath(pluginClasspath());
if (this.dsl != Dsl.KOTLIN) {
if (this.dsl != Dsl.KOTLIN && !this.configurationCache) {
// see https://github.com/gradle/gradle/issues/6862
gradleRunner.withDebug(true);
}
@ -169,6 +189,9 @@ public class GradleBuild {
allArguments.addAll(Arrays.asList(arguments));
allArguments.add("--warning-mode");
allArguments.add("all");
if (this.configurationCache) {
allArguments.add("--configuration-cache");
}
return gradleRunner.withArguments(allArguments);
}

@ -0,0 +1,15 @@
plugins {
id 'org.springframework.boot' version '{version}' apply false
}
version = '0.1.0'
task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) {
destinationDir project.buildDir
properties {
artifact = 'foo'
group = 'foo'
name = 'foo'
additional = ['additional': 'foo']
}
}

@ -2,8 +2,4 @@ plugins {
id 'org.springframework.boot' version '{version}' apply false
}
def property(String name, Object defaultValue) {
project.hasProperty(name) ? project.getProperty(name) : defaultValue
}
task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo)

@ -0,0 +1,5 @@
plugins {
id 'org.springframework.boot' version '{version}' apply false
}
task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo)

@ -0,0 +1,13 @@
plugins {
id 'org.springframework.boot' version '{version}' apply false
}
task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) {
properties {
artifact = 'example'
group = 'com.example'
name = 'example'
additional = ['additional': 'alpha']
time = null
}
}

@ -0,0 +1,15 @@
plugins {
id 'org.springframework.boot' version '{version}' apply false
}
version = '{projectVersion}'
task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) {
properties {
artifact = 'example'
group = 'com.example'
name = 'example'
additional = ['additional': 'alpha']
time = null
}
}

@ -0,0 +1,9 @@
plugins {
id 'org.springframework.boot' version '{version}' apply false
}
task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) {
properties {
time = null
}
}

@ -0,0 +1,9 @@
plugins {
id 'org.springframework.boot' version '{version}' apply false
}
task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) {
properties {
time = null
}
}

@ -1,22 +0,0 @@
plugins {
id 'org.springframework.boot' version '{version}' apply false
}
def property(String name, Object defaultValue) {
project.hasProperty(name) ? project.getProperty(name) : defaultValue
}
version = property('projectVersion', '0.1.0')
task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) {
destinationDir file(property('buildInfoDestinationDir', project.buildDir))
properties {
artifact = property('buildInfoArtifact', 'foo')
group = property('buildInfoGroup', 'foo')
name = property('buildInfoName', 'foo')
additional = ['additional': property('buildInfoAdditional', 'foo')]
if (project.hasProperty('nullTime')) {
time = null
}
}
}

@ -0,0 +1,8 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
bootJar {
mainClass = 'com.example.Application'
}

@ -0,0 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
bootJar {
mainClass = 'com.example.Application'
layered {
{layerTools}
}
}

@ -0,0 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
bootJar {
mainClass = 'com.example.Application'
layered {
{layerEnablement}
}
}

@ -0,0 +1,11 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
bootJar {
mainClass = 'com.example.Application'
launchScript {
properties 'prop' : '{launchScriptProperty}'
}
}

@ -0,0 +1,8 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
bootJar {
mainClass = 'com.example.Application'
}

@ -0,0 +1,9 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
bootJar {
mainClassName = 'com.example.Application'
launchScript()
}

@ -1,19 +0,0 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
bootJar {
mainClass = 'com.example.Application'
if (project.hasProperty('includeLaunchScript') ? includeLaunchScript : false) {
launchScript {
properties 'prop' : project.hasProperty('launchScriptProperty') ? launchScriptProperty : 'default'
}
}
if (project.hasProperty('customizeLayered') && project.getProperty('customizeLayered')) {
layered {
includeLayerTools = project.hasProperty('excludeTools') && project.getProperty('excludeTools') ? false : true
enabled = project.hasProperty('disableLayers') && project.getProperty('disableLayers') ? false : true
}
}
}

@ -0,0 +1,8 @@
plugins {
id 'war'
id 'org.springframework.boot' version '{version}'
}
bootWar {
mainClass = 'com.example.Application'
}

@ -0,0 +1,27 @@
plugins {
id 'war'
id 'org.springframework.boot' version '{version}'
}
bootWar {
mainClass = 'com.example.CustomMain'
duplicatesStrategy = "exclude"
}
configurations {
provided
}
sourceSets.all {
compileClasspath += configurations.provided
runtimeClasspath += configurations.provided
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.apache.commons:commons-lang3:3.6")
provided "org.apache.commons:commons-lang3:3.6"
}

@ -0,0 +1,11 @@
plugins {
id 'war'
id 'org.springframework.boot' version '{version}'
}
bootWar {
mainClass = 'com.example.Application'
launchScript {
properties 'prop' : '{launchScriptProperty}'
}
}

@ -0,0 +1,8 @@
plugins {
id 'war'
id 'org.springframework.boot' version '{version}'
}
bootWar {
mainClass = 'com.example.Application'
}

@ -0,0 +1,9 @@
plugins {
id 'war'
id 'org.springframework.boot' version '{version}'
}
bootWar {
mainClass = 'com.example.Application'
launchScript()
}

@ -1,13 +0,0 @@
plugins {
id 'war'
id 'org.springframework.boot' version '{version}'
}
bootWar {
mainClass = 'com.example.Application'
if (project.hasProperty('includeLaunchScript') ? includeLaunchScript : false) {
launchScript {
properties 'prop' : project.hasProperty('launchScriptProperty') ? launchScriptProperty : 'default'
}
}
}

@ -3,9 +3,8 @@ plugins {
id 'org.springframework.boot' version '{version}'
}
task echoMainClassName {
dependsOn compileJava
doLast {
println 'Main class name = ' + bootRun.main
bootRun {
doFirst {
println "Main class name = ${bootRun.main}"
}
}
}

@ -3,8 +3,4 @@ plugins {
id 'org.springframework.boot' version '{version}'
}
mainClassName = 'com.example.CustomMainClass'
task echoMainClassName {
println 'Main class name = ' + bootRun.main
}
mainClassName = 'com.example.main.CustomMainClass'

@ -1,12 +1,8 @@
plugins {
id 'application'
id 'java'
id 'org.springframework.boot' version '{version}'
}
springBoot {
mainClass = 'com.example.CustomMainClass'
}
task echoMainClassName {
println 'Main class name = ' + bootRun.main
mainClass = 'com.example.main.CustomMainClass'
}

Loading…
Cancel
Save