diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java index e7773d3cf11..7e3ff75958b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -20,6 +20,7 @@ import java.io.File; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.Callable; import org.gradle.api.Action; import org.gradle.api.Plugin; @@ -104,7 +105,7 @@ final class JavaPluginAction implements PluginApplicationAction { .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); Configuration productionRuntimeClasspath = project.getConfigurations() .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); - FileCollection classpath = mainSourceSet.getRuntimeClasspath() + Callable classpath = () -> mainSourceSet.getRuntimeClasspath() .minus((developmentOnly.minus(productionRuntimeClasspath))).filter(new JarTypeFileSpec()); TaskProvider resolveMainClassName = ResolveMainClassName .registerForTask(SpringBootPlugin.BOOT_JAR_TASK_NAME, project, classpath); @@ -136,7 +137,7 @@ final class JavaPluginAction implements PluginApplicationAction { } private void configureBootRunTask(Project project) { - FileCollection classpath = javaPluginExtension(project).getSourceSets() + Callable classpath = () -> javaPluginExtension(project).getSourceSets() .findByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath().filter(new JarTypeFileSpec()); TaskProvider resolveProvider = ResolveMainClassName.registerForTask("bootRun", project, classpath); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java index c63afe42701..2219ce5271e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -23,6 +23,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Objects; +import java.util.concurrent.Callable; import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; @@ -88,7 +89,17 @@ public class ResolveMainClassName extends DefaultTask { * @param classpath the classpath */ public void setClasspath(FileCollection classpath) { - this.classpath = classpath; + setClasspath((Object) classpath); + } + + /** + * Sets the classpath to include in the archive. The given {@code classpath} is + * evaluated as per {@link Project#files(Object...)}. + * @param classpath the classpath + * @since 2.5.9 + */ + public void setClasspath(Object classpath) { + this.classpath = getProject().files(classpath); } /** @@ -144,7 +155,7 @@ public class ResolveMainClassName extends DefaultTask { } static TaskProvider registerForTask(String taskName, Project project, - FileCollection classpath) { + Callable classpath) { TaskProvider resolveMainClassNameProvider = project.getTasks() .register(taskName + "MainClassName", ResolveMainClassName.class, (resolveMainClassName) -> { ExtensionContainer extensions = project.getExtensions(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java index 60a132811f2..c1b6762e7f2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -16,6 +16,8 @@ package org.springframework.boot.gradle.plugin; +import java.util.concurrent.Callable; + import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; @@ -71,7 +73,7 @@ class WarPluginAction implements PluginApplicationAction { .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); Configuration productionRuntimeClasspath = project.getConfigurations() .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME); - FileCollection classpath = project.getExtensions().getByType(SourceSetContainer.class) + Callable classpath = () -> project.getExtensions().getByType(SourceSetContainer.class) .getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath() .minus(providedRuntimeConfiguration(project)).minus((developmentOnly.minus(productionRuntimeClasspath))) .filter(new JarTypeFileSpec()); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java index aa733804e7e..b7dc7e11225 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -455,6 +455,30 @@ abstract class AbstractBootArchiveIntegrationTests { assertExtractedLayers(layerNames, indexedLayers); } + @TestTemplate + void classesFromASecondarySourceSetCanBeIncludedInTheArchive() throws IOException { + writeMainClass(); + File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/secondary/java/example"); + examplePackage.mkdirs(); + File main = new File(examplePackage, "Secondary.java"); + try (PrintWriter writer = new PrintWriter(new FileWriter(main))) { + writer.println("package example;"); + writer.println(); + writer.println("public class Secondary {}"); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + BuildResult build = this.gradleBuild.build(this.taskName); + assertThat(build.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { + Stream classesEntryNames = jarFile.stream().filter((entry) -> !entry.isDirectory()) + .map(JarEntry::getName).filter((name) -> name.startsWith(this.classesPath)); + assertThat(classesEntryNames).containsExactly(this.classesPath + "example/Main.class", + this.classesPath + "example/Secondary.class"); + } + } + private void copyMainClassApplication() throws IOException { copyApplication("main"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java index c9953c3e41e..85a0865d8d2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -138,6 +138,16 @@ class BootRunIntegrationTests { assertThat(result.getOutput()).contains("standard.jar").doesNotContain("starter.jar"); } + @TestTemplate + void classesFromASecondarySourceSetCanBeOnTheClasspath() throws IOException { + File output = new File(this.gradleBuild.getProjectDir(), "src/secondary/java/com/example/bootrun/main"); + output.mkdirs(); + FileSystemUtils.copyRecursively(new File("src/test/java/com/example/bootrun/main"), output); + BuildResult result = this.gradleBuild.build("bootRun"); + assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("com.example.bootrun.main.CustomMainClass"); + } + private void copyMainClassApplication() throws IOException { copyApplication("main"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle new file mode 100644 index 00000000000..eeca788d36f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle @@ -0,0 +1,15 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +sourceSets { + secondary + main { + runtimeClasspath += secondary.output + } +} + +bootJar { + mainClass = 'com.example.Application' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle new file mode 100644 index 00000000000..e107dfd956f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classesFromASecondarySourceSetCanBeIncludedInTheArchive.gradle @@ -0,0 +1,15 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +sourceSets { + secondary + main { + runtimeClasspath += secondary.output + } +} + +bootWar { + mainClass = 'com.example.Application' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-classesFromASecondarySourceSetCanBeOnTheClasspath.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-classesFromASecondarySourceSetCanBeOnTheClasspath.gradle new file mode 100644 index 00000000000..315eb8a0b85 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-classesFromASecondarySourceSetCanBeOnTheClasspath.gradle @@ -0,0 +1,15 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +sourceSets { + secondary + main { + runtimeClasspath += secondary.output + } +} + +springBoot { + mainClass = 'com.example.bootrun.main.CustomMainClass' +}