Skip to content

Commit 43ca2d2

Browse files
committed
Access classpath lazily to allow later changes to be picked up
Previously, the classpath of bootJar, bootWar, and bootRun was configured directly as a FileCollection derived from the main source set's runtime classpath. This direct configuration meant that subsequent changes to the main source set's runtime classpath may not have been picked up. This commit changes the configuration of the classpath to use a Callable. This indirection allows subsequent changes to the main source set's runtime classpath to be picked up as long as they occur before Gradle calls the callable. Closes gh-29672
1 parent 52f1799 commit 43ca2d2

File tree

8 files changed

+103
-10
lines changed

8 files changed

+103
-10
lines changed

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.util.Collections;
2121
import java.util.List;
2222
import java.util.Set;
23+
import java.util.concurrent.Callable;
2324

2425
import org.gradle.api.Action;
2526
import org.gradle.api.Plugin;
@@ -105,7 +106,7 @@ private TaskProvider<BootJar> configureBootJarTask(Project project) {
105106
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
106107
Configuration productionRuntimeClasspath = project.getConfigurations()
107108
.getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME);
108-
FileCollection classpath = mainSourceSet.getRuntimeClasspath()
109+
Callable<FileCollection> classpath = () -> mainSourceSet.getRuntimeClasspath()
109110
.minus((developmentOnly.minus(productionRuntimeClasspath))).filter(new JarTypeFileSpec());
110111
TaskProvider<ResolveMainClassName> resolveMainClassName = ResolveMainClassName
111112
.registerForTask(SpringBootPlugin.BOOT_JAR_TASK_NAME, project, classpath);
@@ -137,7 +138,7 @@ private void configureArtifactPublication(TaskProvider<BootJar> bootJar) {
137138
}
138139

139140
private void configureBootRunTask(Project project) {
140-
FileCollection classpath = javaPluginConvention(project).getSourceSets()
141+
Callable<FileCollection> classpath = () -> javaPluginConvention(project).getSourceSets()
141142
.findByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath().filter(new JarTypeFileSpec());
142143
TaskProvider<ResolveMainClassName> resolveProvider = ResolveMainClassName.registerForTask("bootRun", project,
143144
classpath);

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import java.nio.file.Path;
2424
import java.nio.file.StandardOpenOption;
2525
import java.util.Objects;
26+
import java.util.concurrent.Callable;
2627

2728
import org.gradle.api.DefaultTask;
2829
import org.gradle.api.InvalidUserDataException;
@@ -86,7 +87,17 @@ public FileCollection getClasspath() {
8687
* @param classpath the classpath
8788
*/
8889
public void setClasspath(FileCollection classpath) {
89-
this.classpath = classpath;
90+
setClasspath((Object) classpath);
91+
}
92+
93+
/**
94+
* Sets the classpath to include in the archive. The given {@code classpath} is
95+
* evaluated as per {@link Project#files(Object...)}.
96+
* @param classpath the classpath
97+
* @since 2.5.9
98+
*/
99+
public void setClasspath(Object classpath) {
100+
this.classpath = getProject().files(classpath);
90101
}
91102

92103
/**
@@ -142,7 +153,7 @@ Provider<String> readMainClassName() {
142153
}
143154

144155
static TaskProvider<ResolveMainClassName> registerForTask(String taskName, Project project,
145-
FileCollection classpath) {
156+
Callable<FileCollection> classpath) {
146157
TaskProvider<ResolveMainClassName> resolveMainClassNameProvider = project.getTasks()
147158
.register(taskName + "MainClassName", ResolveMainClassName.class, (resolveMainClassName) -> {
148159
Convention convention = project.getConvention();

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.boot.gradle.plugin;
1818

19+
import java.util.concurrent.Callable;
20+
1921
import org.gradle.api.Action;
2022
import org.gradle.api.Plugin;
2123
import org.gradle.api.Project;
@@ -71,7 +73,7 @@ private TaskProvider<BootWar> configureBootWarTask(Project project) {
7173
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
7274
Configuration productionRuntimeClasspath = project.getConfigurations()
7375
.getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME);
74-
FileCollection classpath = project.getConvention().getByType(SourceSetContainer.class)
76+
Callable<FileCollection> classpath = () -> project.getConvention().getByType(SourceSetContainer.class)
7577
.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()
7678
.minus(providedRuntimeConfiguration(project)).minus((developmentOnly.minus(productionRuntimeClasspath)))
7779
.filter(new JarTypeFileSpec());

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -462,6 +462,30 @@ void multiModuleCustomLayers() throws IOException {
462462
assertExtractedLayers(layerNames, indexedLayers);
463463
}
464464

465+
@TestTemplate
466+
void classesFromASecondarySourceSetCanBeIncludedInTheArchive() throws IOException {
467+
writeMainClass();
468+
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/secondary/java/example");
469+
examplePackage.mkdirs();
470+
File main = new File(examplePackage, "Secondary.java");
471+
try (PrintWriter writer = new PrintWriter(new FileWriter(main))) {
472+
writer.println("package example;");
473+
writer.println();
474+
writer.println("public class Secondary {}");
475+
}
476+
catch (IOException ex) {
477+
throw new RuntimeException(ex);
478+
}
479+
BuildResult build = this.gradleBuild.build(this.taskName);
480+
assertThat(build.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
481+
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
482+
Stream<String> classesEntryNames = jarFile.stream().filter((entry) -> !entry.isDirectory())
483+
.map(JarEntry::getName).filter((name) -> name.startsWith(this.classesPath));
484+
assertThat(classesEntryNames).containsExactly(this.classesPath + "example/Main.class",
485+
this.classesPath + "example/Secondary.class");
486+
}
487+
}
488+
465489
private void copyMainClassApplication() throws IOException {
466490
copyApplication("main");
467491
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -138,6 +138,16 @@ void jarTypeFilteringIsAppliedToTheClasspath() throws IOException {
138138
assertThat(result.getOutput()).contains("standard.jar").doesNotContain("starter.jar");
139139
}
140140

141+
@TestTemplate
142+
void classesFromASecondarySourceSetCanBeOnTheClasspath() throws IOException {
143+
File output = new File(this.gradleBuild.getProjectDir(), "src/secondary/java/com/example/bootrun/main");
144+
output.mkdirs();
145+
FileSystemUtils.copyRecursively(new File("src/test/java/com/example/bootrun/main"), output);
146+
BuildResult result = this.gradleBuild.build("bootRun");
147+
assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
148+
assertThat(result.getOutput()).contains("com.example.bootrun.main.CustomMainClass");
149+
}
150+
141151
private void copyMainClassApplication() throws IOException {
142152
copyApplication("main");
143153
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
plugins {
2+
id 'java'
3+
id 'org.springframework.boot' version '{version}'
4+
}
5+
6+
sourceSets {
7+
secondary
8+
main {
9+
runtimeClasspath += secondary.output
10+
}
11+
}
12+
13+
bootJar {
14+
mainClass = 'com.example.Application'
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
plugins {
2+
id 'war'
3+
id 'org.springframework.boot' version '{version}'
4+
}
5+
6+
sourceSets {
7+
secondary
8+
main {
9+
runtimeClasspath += secondary.output
10+
}
11+
}
12+
13+
bootWar {
14+
mainClass = 'com.example.Application'
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
plugins {
2+
id 'java'
3+
id 'org.springframework.boot' version '{version}'
4+
}
5+
6+
sourceSets {
7+
secondary
8+
main {
9+
runtimeClasspath += secondary.output
10+
}
11+
}
12+
13+
springBoot {
14+
mainClass = 'com.example.bootrun.main.CustomMainClass'
15+
}

0 commit comments

Comments
 (0)