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
pull/29762/head
Andy Wilkinson 3 years ago
parent 52f1799c20
commit 43ca2d2cb0

@ -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;
@ -105,7 +106,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<FileCollection> classpath = () -> mainSourceSet.getRuntimeClasspath()
.minus((developmentOnly.minus(productionRuntimeClasspath))).filter(new JarTypeFileSpec());
TaskProvider<ResolveMainClassName> resolveMainClassName = ResolveMainClassName
.registerForTask(SpringBootPlugin.BOOT_JAR_TASK_NAME, project, classpath);
@ -137,7 +138,7 @@ final class JavaPluginAction implements PluginApplicationAction {
}
private void configureBootRunTask(Project project) {
FileCollection classpath = javaPluginConvention(project).getSourceSets()
Callable<FileCollection> classpath = () -> javaPluginConvention(project).getSourceSets()
.findByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath().filter(new JarTypeFileSpec());
TaskProvider<ResolveMainClassName> resolveProvider = ResolveMainClassName.registerForTask("bootRun", project,
classpath);

@ -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;
@ -86,7 +87,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);
}
/**
@ -142,7 +153,7 @@ public class ResolveMainClassName extends DefaultTask {
}
static TaskProvider<ResolveMainClassName> registerForTask(String taskName, Project project,
FileCollection classpath) {
Callable<FileCollection> classpath) {
TaskProvider<ResolveMainClassName> resolveMainClassNameProvider = project.getTasks()
.register(taskName + "MainClassName", ResolveMainClassName.class, (resolveMainClassName) -> {
Convention convention = project.getConvention();

@ -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.getConvention().getByType(SourceSetContainer.class)
Callable<FileCollection> classpath = () -> project.getConvention().getByType(SourceSetContainer.class)
.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()
.minus(providedRuntimeConfiguration(project)).minus((developmentOnly.minus(productionRuntimeClasspath)))
.filter(new JarTypeFileSpec());

@ -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.
@ -462,6 +462,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<String> 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");
}

@ -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");
}

@ -0,0 +1,15 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
sourceSets {
secondary
main {
runtimeClasspath += secondary.output
}
}
bootJar {
mainClass = 'com.example.Application'
}

@ -0,0 +1,15 @@
plugins {
id 'war'
id 'org.springframework.boot' version '{version}'
}
sourceSets {
secondary
main {
runtimeClasspath += secondary.output
}
}
bootWar {
mainClass = 'com.example.Application'
}

@ -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'
}
Loading…
Cancel
Save