Add support for creating layered war files with Gradle
See gh-22195
This commit is contained in:
parent
7f8ea33359
commit
1245e5eec9
|
@ -22,6 +22,7 @@ import java.util.function.Function;
|
|||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.ResolvableDependencies;
|
||||
import org.gradle.api.file.CopySpec;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
|
@ -30,6 +31,8 @@ import org.gradle.api.internal.file.copy.CopyAction;
|
|||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.specs.Spec;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.bundling.War;
|
||||
|
||||
|
@ -50,12 +53,18 @@ public class BootWar extends War implements BootArchive {
|
|||
|
||||
private static final String LIB_DIRECTORY = "WEB-INF/lib/";
|
||||
|
||||
private static final String LAYERS_INDEX = "WEB-INF/layers.idx";
|
||||
|
||||
private final BootArchiveSupport support;
|
||||
|
||||
private final Property<String> mainClass;
|
||||
|
||||
private FileCollection providedClasspath;
|
||||
|
||||
private final ResolvedDependencies resolvedDependencies = new ResolvedDependencies();
|
||||
|
||||
private LayeredSpec layered = new LayeredSpec();
|
||||
|
||||
/**
|
||||
* Creates a new {@code BootWar} task.
|
||||
*/
|
||||
|
@ -65,6 +74,14 @@ public class BootWar extends War implements BootArchive {
|
|||
getWebInf().into("lib-provided", fromCallTo(this::getProvidedLibFiles));
|
||||
this.support.moveModuleInfoToRoot(getRootSpec());
|
||||
getRootSpec().eachFile(this.support::excludeNonZipLibraryFiles);
|
||||
getProject().getConfigurations().all((configuration) -> {
|
||||
ResolvableDependencies incoming = configuration.getIncoming();
|
||||
incoming.afterResolve((resolvableDependencies) -> {
|
||||
if (resolvableDependencies == incoming) {
|
||||
this.resolvedDependencies.processConfiguration(configuration);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Object getProvidedLibFiles() {
|
||||
|
@ -74,12 +91,21 @@ public class BootWar extends War implements BootArchive {
|
|||
@Override
|
||||
public void copy() {
|
||||
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, null,
|
||||
null);
|
||||
(isLayeredDisabled()) ? null : LAYERS_INDEX);
|
||||
super.copy();
|
||||
}
|
||||
|
||||
private boolean isLayeredDisabled() {
|
||||
return this.layered != null && !this.layered.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CopyAction createCopyAction() {
|
||||
if (!isLayeredDisabled()) {
|
||||
LayerResolver layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary);
|
||||
String layerToolsLocation = this.layered.isIncludeLayerTools() ? LIB_DIRECTORY : null;
|
||||
return this.support.createCopyAction(this, layerResolver, layerToolsLocation);
|
||||
}
|
||||
return this.support.createCopyAction(this);
|
||||
}
|
||||
|
||||
|
@ -181,6 +207,25 @@ public class BootWar extends War implements BootArchive {
|
|||
return isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the spec that describes the layers in a layered jar.
|
||||
* @return the spec for the layers
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Nested
|
||||
public LayeredSpec getLayered() {
|
||||
return this.layered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the war's layering using the given {@code action}.
|
||||
* @param action the action to apply
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public void layered(Action<LayeredSpec> action) {
|
||||
action.execute(this.layered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the {@link FileCopyDetails} are for a library. By default any file in
|
||||
* {@code WEB-INF/lib} or {@code WEB-INF/lib-provided} is considered to be a library.
|
||||
|
@ -201,6 +246,11 @@ public class BootWar extends War implements BootArchive {
|
|||
return launchScript;
|
||||
}
|
||||
|
||||
@Internal
|
||||
ResolvedDependencies getResolvedDependencies() {
|
||||
return this.resolvedDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
|
|
|
@ -41,7 +41,7 @@ import org.springframework.boot.loader.tools.layer.LibraryContentFilter;
|
|||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Encapsulates the configuration for a layered jar.
|
||||
* Encapsulates the configuration for a layered archive.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
|
@ -65,7 +65,7 @@ public class LayeredSpec {
|
|||
|
||||
/**
|
||||
* Returns whether the layer tools should be included as a dependency in the layered
|
||||
* jar.
|
||||
* archive.
|
||||
* @return whether the layer tools should be included
|
||||
*/
|
||||
@Input
|
||||
|
@ -74,7 +74,8 @@ public class LayeredSpec {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets whether the layer tools should be included as a dependency in the layered jar.
|
||||
* Sets whether the layer tools should be included as a dependency in the layered
|
||||
* archive.
|
||||
* @param includeLayerTools {@code true} if the layer tools should be included,
|
||||
* otherwise {@code false}
|
||||
*/
|
||||
|
@ -83,7 +84,7 @@ public class LayeredSpec {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns whether the layers.idx should be included in the jar.
|
||||
* Returns whether the layers.idx should be included in the archive.
|
||||
* @return whether the layers.idx should be included
|
||||
*/
|
||||
@Input
|
||||
|
@ -92,8 +93,8 @@ public class LayeredSpec {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets whether the layers.idx should be included in the jar.
|
||||
* @param enabled {@code true} layers.idx should be included in the jar, otherwise
|
||||
* Sets whether the layers.idx should be included in the archive.
|
||||
* @param enabled {@code true} layers.idx should be included in the archive, otherwise
|
||||
* {@code false}
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
|
@ -171,7 +172,8 @@ public class LayeredSpec {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the order of the layers in the jar from least to most frequently changing.
|
||||
* Returns the order of the layers in the archive from least to most frequently
|
||||
* changing.
|
||||
* @return the layer order
|
||||
*/
|
||||
@Input
|
||||
|
@ -180,7 +182,7 @@ public class LayeredSpec {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets to order of the layers in the jar from least to most frequently changing.
|
||||
* Sets the order of the layers in the archive from least to most frequently changing.
|
||||
* @param layerOrder the layer order
|
||||
*/
|
||||
public void setLayerOrder(String... layerOrder) {
|
||||
|
@ -188,7 +190,7 @@ public class LayeredSpec {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets to order of the layers in the jar from least to most frequently changing.
|
||||
* Sets the order of the layers in the archive from least to most frequently changing.
|
||||
* @param layerOrder the layer order
|
||||
*/
|
||||
public void setLayerOrder(List<String> layerOrder) {
|
||||
|
|
|
@ -16,18 +16,36 @@
|
|||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.InvalidRunnerConfigurationException;
|
||||
import org.gradle.testkit.runner.TaskOutcome;
|
||||
import org.gradle.testkit.runner.UnexpectedBuildFailure;
|
||||
|
@ -35,14 +53,17 @@ import org.junit.jupiter.api.TestTemplate;
|
|||
|
||||
import org.springframework.boot.gradle.testkit.GradleBuild;
|
||||
import org.springframework.boot.loader.tools.FileUtils;
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link BootJar}.
|
||||
* Integration tests for {@link BootJar} and {@link BootWar}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
abstract class AbstractBootArchiveIntegrationTests {
|
||||
|
||||
|
@ -52,12 +73,16 @@ abstract class AbstractBootArchiveIntegrationTests {
|
|||
|
||||
private final String classesPath;
|
||||
|
||||
private final String indexPath;
|
||||
|
||||
GradleBuild gradleBuild;
|
||||
|
||||
protected AbstractBootArchiveIntegrationTests(String taskName, String libPath, String classesPath) {
|
||||
protected AbstractBootArchiveIntegrationTests(String taskName, String libPath, String classesPath,
|
||||
String indexPath) {
|
||||
this.taskName = taskName;
|
||||
this.libPath = libPath;
|
||||
this.classesPath = classesPath;
|
||||
this.indexPath = indexPath;
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
|
@ -204,6 +229,235 @@ abstract class AbstractBootArchiveIntegrationTests {
|
|||
.isEqualTo(TaskOutcome.UP_TO_DATE);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered()
|
||||
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
|
||||
assertThat(this.gradleBuild.scriptProperty("layered", "").build("" + this.taskName).task(":" + this.taskName)
|
||||
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(this.gradleBuild.scriptProperty("layered", "layered {}").build("" + this.taskName)
|
||||
.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void notUpToDateWhenBuiltWithoutLayersAndThenWithLayers()
|
||||
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
|
||||
assertThat(this.gradleBuild.scriptProperty("layerEnablement", "enabled = false").build(this.taskName)
|
||||
.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(this.gradleBuild.scriptProperty("layerEnablement", "enabled = true").build(this.taskName)
|
||||
.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void notUpToDateWhenBuiltWithLayerToolsAndThenWithoutLayerTools()
|
||||
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
|
||||
assertThat(this.gradleBuild.scriptProperty("layerTools", "").build(this.taskName).task(":" + this.taskName)
|
||||
.getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(this.gradleBuild.scriptProperty("layerTools", "includeLayerTools = false").build(this.taskName)
|
||||
.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void layersWithCustomSourceSet() throws IOException {
|
||||
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
|
||||
.isEqualTo(TaskOutcome.SUCCESS);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void implicitLayers() throws IOException {
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
|
||||
.isEqualTo(TaskOutcome.SUCCESS);
|
||||
Map<String, List<String>> indexedLayers;
|
||||
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "spring-core-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "library-1.0-SNAPSHOT.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.classesPath + "example/Main.class")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.classesPath + "static/file.txt")).isNotNull();
|
||||
indexedLayers = readLayerIndex(jarFile);
|
||||
}
|
||||
List<String> layerNames = Arrays.asList("dependencies", "spring-boot-loader", "snapshot-dependencies",
|
||||
"application");
|
||||
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
|
||||
Set<String> expectedDependencies = new TreeSet<>();
|
||||
expectedDependencies.add(this.libPath + "commons-lang3-3.9.jar");
|
||||
expectedDependencies.add(this.libPath + "spring-core-5.2.5.RELEASE.jar");
|
||||
expectedDependencies.add(this.libPath + "spring-jcl-5.2.5.RELEASE.jar");
|
||||
expectedDependencies.add(this.libPath + "jul-to-slf4j-1.7.28.jar");
|
||||
expectedDependencies.add(this.libPath + "log4j-api-2.12.1.jar");
|
||||
expectedDependencies.add(this.libPath + "log4j-to-slf4j-2.12.1.jar");
|
||||
expectedDependencies.add(this.libPath + "logback-classic-1.2.3.jar");
|
||||
expectedDependencies.add(this.libPath + "logback-core-1.2.3.jar");
|
||||
expectedDependencies.add(this.libPath + "slf4j-api-1.7.28.jar");
|
||||
expectedDependencies.add(this.libPath + "spring-boot-starter-logging-2.2.0.RELEASE.jar");
|
||||
Set<String> expectedSnapshotDependencies = new TreeSet<>();
|
||||
expectedSnapshotDependencies.add(this.libPath + "library-1.0-SNAPSHOT.jar");
|
||||
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
|
||||
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
|
||||
assertThat(indexedLayers.get("spring-boot-loader")).containsExactly("org/");
|
||||
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
|
||||
assertThat(indexedLayers.get("application"))
|
||||
.containsExactly(getExpectedApplicationLayerContents(this.classesPath));
|
||||
BuildResult listLayers = this.gradleBuild.build("listLayers");
|
||||
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
String listLayersOutput = listLayers.getOutput();
|
||||
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
|
||||
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
|
||||
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertExtractedLayers(layerNames, indexedLayers);
|
||||
}
|
||||
|
||||
abstract String[] getExpectedApplicationLayerContents(String... additionalFiles);
|
||||
|
||||
@TestTemplate
|
||||
void multiModuleImplicitLayers() throws IOException {
|
||||
writeSettingsGradle();
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
|
||||
.isEqualTo(TaskOutcome.SUCCESS);
|
||||
Map<String, List<String>> indexedLayers;
|
||||
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "alpha-1.2.3.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "bravo-1.2.3.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "spring-core-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "library-1.0-SNAPSHOT.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.classesPath + "example/Main.class")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.classesPath + "static/file.txt")).isNotNull();
|
||||
indexedLayers = readLayerIndex(jarFile);
|
||||
}
|
||||
List<String> layerNames = Arrays.asList("dependencies", "spring-boot-loader", "snapshot-dependencies",
|
||||
"application");
|
||||
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
|
||||
Set<String> expectedDependencies = new TreeSet<>();
|
||||
expectedDependencies.add(this.libPath + "commons-lang3-3.9.jar");
|
||||
expectedDependencies.add(this.libPath + "spring-core-5.2.5.RELEASE.jar");
|
||||
expectedDependencies.add(this.libPath + "spring-jcl-5.2.5.RELEASE.jar");
|
||||
Set<String> expectedSnapshotDependencies = new TreeSet<>();
|
||||
expectedSnapshotDependencies.add(this.libPath + "library-1.0-SNAPSHOT.jar");
|
||||
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
|
||||
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
|
||||
assertThat(indexedLayers.get("spring-boot-loader")).containsExactly("org/");
|
||||
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
|
||||
assertThat(indexedLayers.get("application")).containsExactly(getExpectedApplicationLayerContents(
|
||||
this.classesPath, this.libPath + "alpha-1.2.3.jar", this.libPath + "bravo-1.2.3.jar"));
|
||||
BuildResult listLayers = this.gradleBuild.build("listLayers");
|
||||
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
String listLayersOutput = listLayers.getOutput();
|
||||
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
|
||||
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
|
||||
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertExtractedLayers(layerNames, indexedLayers);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void customLayers() throws IOException {
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
BuildResult build = this.gradleBuild.build(this.taskName);
|
||||
assertThat(build.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
Map<String, List<String>> indexedLayers;
|
||||
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "spring-core-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "library-1.0-SNAPSHOT.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.classesPath + "example/Main.class")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.classesPath + "static/file.txt")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.indexPath + "layers.idx")).isNotNull();
|
||||
indexedLayers = readLayerIndex(jarFile);
|
||||
}
|
||||
List<String> layerNames = Arrays.asList("dependencies", "commons-dependencies", "snapshot-dependencies",
|
||||
"static", "app");
|
||||
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
|
||||
Set<String> expectedDependencies = new TreeSet<>();
|
||||
expectedDependencies.add(this.libPath + "spring-core-5.2.5.RELEASE.jar");
|
||||
expectedDependencies.add(this.libPath + "spring-jcl-5.2.5.RELEASE.jar");
|
||||
List<String> expectedSnapshotDependencies = new ArrayList<>();
|
||||
expectedSnapshotDependencies.add(this.libPath + "library-1.0-SNAPSHOT.jar");
|
||||
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
|
||||
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
|
||||
assertThat(indexedLayers.get("commons-dependencies")).containsExactly(this.libPath + "commons-lang3-3.9.jar");
|
||||
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
|
||||
assertThat(indexedLayers.get("static")).containsExactly(this.classesPath + "static/");
|
||||
List<String> appLayer = new ArrayList<>(indexedLayers.get("app"));
|
||||
String[] appLayerContents = getExpectedApplicationLayerContents(this.classesPath + "example/");
|
||||
assertThat(appLayer).containsSubsequence(appLayerContents);
|
||||
appLayer.removeAll(Arrays.asList(appLayerContents));
|
||||
assertThat(appLayer).containsExactly("org/");
|
||||
BuildResult listLayers = this.gradleBuild.build("listLayers");
|
||||
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
String listLayersOutput = listLayers.getOutput();
|
||||
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
|
||||
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
|
||||
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertExtractedLayers(layerNames, indexedLayers);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void multiModuleCustomLayers() throws IOException {
|
||||
writeSettingsGradle();
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
BuildResult build = this.gradleBuild.build(this.taskName);
|
||||
assertThat(build.task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
Map<String, List<String>> indexedLayers;
|
||||
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "alpha-1.2.3.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "bravo-1.2.3.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "spring-core-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.libPath + "library-1.0-SNAPSHOT.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.classesPath + "example/Main.class")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.classesPath + "static/file.txt")).isNotNull();
|
||||
assertThat(jarFile.getEntry(this.indexPath + "layers.idx")).isNotNull();
|
||||
indexedLayers = readLayerIndex(jarFile);
|
||||
}
|
||||
List<String> layerNames = Arrays.asList("dependencies", "commons-dependencies", "snapshot-dependencies",
|
||||
"subproject-dependencies", "static", "app");
|
||||
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
|
||||
Set<String> expectedSubprojectDependencies = new TreeSet<>();
|
||||
expectedSubprojectDependencies.add(this.libPath + "alpha-1.2.3.jar");
|
||||
expectedSubprojectDependencies.add(this.libPath + "bravo-1.2.3.jar");
|
||||
Set<String> expectedDependencies = new TreeSet<>();
|
||||
expectedDependencies.add(this.libPath + "spring-core-5.2.5.RELEASE.jar");
|
||||
expectedDependencies.add(this.libPath + "spring-jcl-5.2.5.RELEASE.jar");
|
||||
List<String> expectedSnapshotDependencies = new ArrayList<>();
|
||||
expectedSnapshotDependencies.add(this.libPath + "library-1.0-SNAPSHOT.jar");
|
||||
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
|
||||
assertThat(indexedLayers.get("subproject-dependencies"))
|
||||
.containsExactlyElementsOf(expectedSubprojectDependencies);
|
||||
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
|
||||
assertThat(indexedLayers.get("commons-dependencies")).containsExactly(this.libPath + "commons-lang3-3.9.jar");
|
||||
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
|
||||
assertThat(indexedLayers.get("static")).containsExactly(this.classesPath + "static/");
|
||||
List<String> appLayer = new ArrayList<>(indexedLayers.get("app"));
|
||||
String[] appLayerContents = getExpectedApplicationLayerContents(this.classesPath + "example/");
|
||||
assertThat(appLayer).containsSubsequence(appLayerContents);
|
||||
appLayer.removeAll(Arrays.asList(appLayerContents));
|
||||
assertThat(appLayer).containsExactly("org/");
|
||||
BuildResult listLayers = this.gradleBuild.build("listLayers");
|
||||
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
String listLayersOutput = listLayers.getOutput();
|
||||
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
|
||||
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
|
||||
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertExtractedLayers(layerNames, indexedLayers);
|
||||
}
|
||||
|
||||
private void copyMainClassApplication() throws IOException {
|
||||
copyApplication("main");
|
||||
}
|
||||
|
@ -235,4 +489,103 @@ abstract class AbstractBootArchiveIntegrationTests {
|
|||
new JarOutputStream(new FileOutputStream(location), manifest).close();
|
||||
}
|
||||
|
||||
private void writeSettingsGradle() {
|
||||
try (PrintWriter writer = new PrintWriter(
|
||||
new FileWriter(new File(this.gradleBuild.getProjectDir(), "settings.gradle")))) {
|
||||
writer.println("include 'alpha', 'bravo'");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMainClass() {
|
||||
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
||||
examplePackage.mkdirs();
|
||||
File main = new File(examplePackage, "Main.java");
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter(main))) {
|
||||
writer.println("package example;");
|
||||
writer.println();
|
||||
writer.println("import java.io.IOException;");
|
||||
writer.println();
|
||||
writer.println("public class Main {");
|
||||
writer.println();
|
||||
writer.println(" public static void main(String[] args) {");
|
||||
writer.println(" }");
|
||||
writer.println();
|
||||
writer.println("}");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResource() {
|
||||
try {
|
||||
Path path = this.gradleBuild.getProjectDir().toPath()
|
||||
.resolve(Paths.get("src", "main", "resources", "static", "file.txt"));
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.createFile(path);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, List<String>> readLayerIndex(JarFile jarFile) throws IOException {
|
||||
Map<String, List<String>> index = new LinkedHashMap<>();
|
||||
ZipEntry indexEntry = jarFile.getEntry(this.indexPath + "layers.idx");
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) {
|
||||
String line = reader.readLine();
|
||||
String layer = null;
|
||||
while (line != null) {
|
||||
if (line.startsWith("- ")) {
|
||||
layer = line.substring(3, line.length() - 2);
|
||||
}
|
||||
else if (line.startsWith(" - ")) {
|
||||
index.computeIfAbsent(layer, (key) -> new ArrayList<>()).add(line.substring(5, line.length() - 1));
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, List<String>> readExtractedLayers(File root, List<String> layerNames) throws IOException {
|
||||
Map<String, List<String>> extractedLayers = new LinkedHashMap<>();
|
||||
for (String layerName : layerNames) {
|
||||
File layer = new File(root, layerName);
|
||||
assertThat(layer).isDirectory();
|
||||
extractedLayers.put(layerName,
|
||||
Files.walk(layer.toPath()).filter((path) -> path.toFile().isFile()).map(layer.toPath()::relativize)
|
||||
.map(Path::toString).map(StringUtils::cleanPath).collect(Collectors.toList()));
|
||||
}
|
||||
return extractedLayers;
|
||||
}
|
||||
|
||||
private void assertExtractedLayers(List<String> layerNames, Map<String, List<String>> indexedLayers)
|
||||
throws IOException {
|
||||
Map<String, List<String>> extractedLayers = readExtractedLayers(this.gradleBuild.getProjectDir(), layerNames);
|
||||
assertThat(extractedLayers.keySet()).isEqualTo(indexedLayers.keySet());
|
||||
extractedLayers.forEach((name, contents) -> {
|
||||
List<String> index = indexedLayers.get(name);
|
||||
List<String> unexpected = new ArrayList<>();
|
||||
for (String file : contents) {
|
||||
if (!isInIndex(index, file)) {
|
||||
unexpected.add(name);
|
||||
}
|
||||
}
|
||||
assertThat(unexpected).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isInIndex(List<String> index, String file) {
|
||||
for (String candidate : index) {
|
||||
if (file.equals(candidate) || candidate.endsWith("/") && file.startsWith(candidate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
|
@ -27,19 +29,35 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DomainObjectSet;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.DependencySet;
|
||||
import org.gradle.api.artifacts.ModuleVersionIdentifier;
|
||||
import org.gradle.api.artifacts.ProjectDependency;
|
||||
import org.gradle.api.artifacts.ResolvableDependencies;
|
||||
import org.gradle.api.artifacts.ResolvedArtifact;
|
||||
import org.gradle.api.artifacts.ResolvedConfiguration;
|
||||
import org.gradle.api.artifacts.ResolvedModuleVersion;
|
||||
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier;
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
|
||||
import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
|
||||
import org.gradle.api.internal.file.archive.ZipCopyAction;
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
|
@ -49,8 +67,13 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Abstract base class for testing {@link BootArchive} implementations.
|
||||
|
@ -72,15 +95,19 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
|
|||
|
||||
private final String classesPath;
|
||||
|
||||
private final String indexPath;
|
||||
|
||||
private Project project;
|
||||
|
||||
private T task;
|
||||
|
||||
protected AbstractBootArchiveTests(Class<T> taskClass, String launcherClass, String libPath, String classesPath) {
|
||||
protected AbstractBootArchiveTests(Class<T> taskClass, String launcherClass, String libPath, String classesPath,
|
||||
String indexPath) {
|
||||
this.taskClass = taskClass;
|
||||
this.launcherClass = launcherClass;
|
||||
this.libPath = libPath;
|
||||
this.classesPath = classesPath;
|
||||
this.indexPath = indexPath;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
|
@ -407,6 +434,145 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
|
|||
this.libPath + "third-library.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void archiveShouldBeLayeredByDefault() throws IOException {
|
||||
addContent();
|
||||
executeTask();
|
||||
try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) {
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classes"))
|
||||
.isEqualTo(this.classesPath);
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo(this.libPath);
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Layers-Index"))
|
||||
.isEqualTo(this.indexPath + "layers.idx");
|
||||
assertThat(getEntryNames(jarFile)).contains(this.libPath + JarModeLibrary.LAYER_TOOLS.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarWhenLayersDisabledShouldNotContainLayersIndex() throws IOException {
|
||||
List<String> entryNames = getEntryNames(createLayeredJar((configuration) -> configuration.setEnabled(false)));
|
||||
assertThat(entryNames).doesNotContain(this.indexPath + "layers.idx");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredThenManifestContainsEntryForLayersIndexInPlaceOfClassesAndLib() throws IOException {
|
||||
try (JarFile jarFile = new JarFile(createLayeredJar())) {
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classes"))
|
||||
.isEqualTo(this.classesPath);
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo(this.libPath);
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Layers-Index"))
|
||||
.isEqualTo(this.indexPath + "layers.idx");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredThenLayersIndexIsPresentAndCorrect() throws IOException {
|
||||
try (JarFile jarFile = new JarFile(createLayeredJar())) {
|
||||
List<String> entryNames = getEntryNames(jarFile);
|
||||
assertThat(entryNames).contains(this.libPath + "first-library.jar", this.libPath + "second-library.jar",
|
||||
this.libPath + "third-library-SNAPSHOT.jar", this.libPath + "first-project-library.jar",
|
||||
this.libPath + "second-project-library-SNAPSHOT.jar",
|
||||
this.classesPath + "com/example/Application.class", this.classesPath + "application.properties",
|
||||
this.classesPath + "static/test.css");
|
||||
List<String> index = entryLines(jarFile, this.indexPath + "layers.idx");
|
||||
assertThat(getLayerNames(index)).containsExactly("dependencies", "spring-boot-loader",
|
||||
"snapshot-dependencies", "application");
|
||||
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
List<String> expected = new ArrayList<>();
|
||||
expected.add("- \"dependencies\":");
|
||||
expected.add(" - \"" + this.libPath + "first-library.jar\"");
|
||||
expected.add(" - \"" + this.libPath + "first-project-library.jar\"");
|
||||
expected.add(" - \"" + this.libPath + "second-library.jar\"");
|
||||
if (!layerToolsJar.contains("SNAPSHOT")) {
|
||||
expected.add(" - \"" + layerToolsJar + "\"");
|
||||
}
|
||||
expected.add("- \"spring-boot-loader\":");
|
||||
expected.add(" - \"org/\"");
|
||||
expected.add("- \"snapshot-dependencies\":");
|
||||
expected.add(" - \"" + this.libPath + "second-project-library-SNAPSHOT.jar\"");
|
||||
if (layerToolsJar.contains("SNAPSHOT")) {
|
||||
expected.add(" - \"" + layerToolsJar + "\"");
|
||||
}
|
||||
expected.add(" - \"" + this.libPath + "third-library-SNAPSHOT.jar\"");
|
||||
expected.add("- \"application\":");
|
||||
Set<String> applicationContents = new TreeSet<>();
|
||||
applicationContents.add(" - \"" + this.classesPath + "\"");
|
||||
if (archiveHasClasspathIndex()) {
|
||||
applicationContents.add(" - \"" + this.indexPath + "classpath.idx\"");
|
||||
}
|
||||
applicationContents.add(" - \"" + this.indexPath + "layers.idx\"");
|
||||
applicationContents.add(" - \"META-INF/\"");
|
||||
expected.addAll(applicationContents);
|
||||
assertThat(index).containsExactlyElementsOf(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredWithCustomStrategiesThenLayersIndexIsPresentAndCorrect() throws IOException {
|
||||
File jar = createLayeredJar((layered) -> {
|
||||
layered.application((application) -> {
|
||||
application.intoLayer("resources", (spec) -> spec.include("static/**"));
|
||||
application.intoLayer("application");
|
||||
});
|
||||
layered.dependencies((dependencies) -> {
|
||||
dependencies.intoLayer("my-snapshot-deps", (spec) -> spec.include("com.example:*:*.SNAPSHOT"));
|
||||
dependencies.intoLayer("my-internal-deps", (spec) -> spec.include("com.example:*:*"));
|
||||
dependencies.intoLayer("my-deps");
|
||||
});
|
||||
layered.setLayerOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application");
|
||||
});
|
||||
try (JarFile jarFile = new JarFile(jar)) {
|
||||
List<String> entryNames = getEntryNames(jar);
|
||||
assertThat(entryNames).contains(this.libPath + "first-library.jar", this.libPath + "second-library.jar",
|
||||
this.libPath + "third-library-SNAPSHOT.jar", this.libPath + "first-project-library.jar",
|
||||
this.libPath + "second-project-library-SNAPSHOT.jar",
|
||||
this.classesPath + "com/example/Application.class", this.classesPath + "application.properties",
|
||||
this.classesPath + "static/test.css");
|
||||
List<String> index = entryLines(jarFile, this.indexPath + "layers.idx");
|
||||
assertThat(getLayerNames(index)).containsExactly("my-deps", "my-internal-deps", "my-snapshot-deps",
|
||||
"resources", "application");
|
||||
String layerToolsJar = this.libPath + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
List<String> expected = new ArrayList<>();
|
||||
expected.add("- \"my-deps\":");
|
||||
expected.add(" - \"" + layerToolsJar + "\"");
|
||||
expected.add("- \"my-internal-deps\":");
|
||||
expected.add(" - \"" + this.libPath + "first-library.jar\"");
|
||||
expected.add(" - \"" + this.libPath + "first-project-library.jar\"");
|
||||
expected.add(" - \"" + this.libPath + "second-library.jar\"");
|
||||
expected.add("- \"my-snapshot-deps\":");
|
||||
expected.add(" - \"" + this.libPath + "second-project-library-SNAPSHOT.jar\"");
|
||||
expected.add(" - \"" + this.libPath + "third-library-SNAPSHOT.jar\"");
|
||||
expected.add("- \"resources\":");
|
||||
expected.add(" - \"" + this.classesPath + "static/\"");
|
||||
expected.add("- \"application\":");
|
||||
Set<String> applicationContents = new TreeSet<>();
|
||||
applicationContents.add(" - \"" + this.classesPath + "application.properties\"");
|
||||
applicationContents.add(" - \"" + this.classesPath + "com/\"");
|
||||
if (archiveHasClasspathIndex()) {
|
||||
applicationContents.add(" - \"" + this.indexPath + "classpath.idx\"");
|
||||
}
|
||||
applicationContents.add(" - \"" + this.indexPath + "layers.idx\"");
|
||||
applicationContents.add(" - \"META-INF/\"");
|
||||
applicationContents.add(" - \"org/\"");
|
||||
expected.addAll(applicationContents);
|
||||
assertThat(index).containsExactlyElementsOf(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenArchiveIsLayeredThenLayerToolsAreAddedToTheJar() throws IOException {
|
||||
List<String> entryNames = getEntryNames(createLayeredJar());
|
||||
assertThat(entryNames).contains(this.libPath + JarModeLibrary.LAYER_TOOLS.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenArchiveIsLayeredAndIncludeLayerToolsIsFalseThenLayerToolsAreNotAddedToTheJar() throws IOException {
|
||||
List<String> entryNames = getEntryNames(
|
||||
createLayeredJar((configuration) -> configuration.setIncludeLayerTools(false)));
|
||||
assertThat(entryNames)
|
||||
.doesNotContain(this.indexPath + "layers/dependencies/lib/spring-boot-jarmode-layertools.jar");
|
||||
}
|
||||
|
||||
protected File jarFile(String name) throws IOException {
|
||||
File file = newFile(name);
|
||||
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) {
|
||||
|
@ -453,4 +619,118 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
|
|||
return file;
|
||||
}
|
||||
|
||||
File createLayeredJar() throws IOException {
|
||||
return createLayeredJar((spec) -> {
|
||||
});
|
||||
}
|
||||
|
||||
File createLayeredJar(Action<LayeredSpec> action) throws IOException {
|
||||
applyLayered(action);
|
||||
addContent();
|
||||
executeTask();
|
||||
return getTask().getArchiveFile().get().getAsFile();
|
||||
}
|
||||
|
||||
abstract void applyLayered(Action<LayeredSpec> action);
|
||||
|
||||
boolean archiveHasClasspathIndex() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void addContent() throws IOException {
|
||||
this.task.getMainClass().set("com.example.Main");
|
||||
File classesJavaMain = new File(this.temp, "classes/java/main");
|
||||
File applicationClass = new File(classesJavaMain, "com/example/Application.class");
|
||||
applicationClass.getParentFile().mkdirs();
|
||||
applicationClass.createNewFile();
|
||||
File resourcesMain = new File(this.temp, "resources/main");
|
||||
File applicationProperties = new File(resourcesMain, "application.properties");
|
||||
applicationProperties.getParentFile().mkdirs();
|
||||
applicationProperties.createNewFile();
|
||||
File staticResources = new File(resourcesMain, "static");
|
||||
staticResources.mkdir();
|
||||
File css = new File(staticResources, "test.css");
|
||||
css.createNewFile();
|
||||
this.task.classpath(classesJavaMain, resourcesMain, jarFile("first-library.jar"), jarFile("second-library.jar"),
|
||||
jarFile("third-library-SNAPSHOT.jar"), jarFile("first-project-library.jar"),
|
||||
jarFile("second-project-library-SNAPSHOT.jar"));
|
||||
Set<ResolvedArtifact> artifacts = new LinkedHashSet<>();
|
||||
artifacts.add(mockLibraryArtifact("first-library.jar", "com.example", "first-library", "1.0.0"));
|
||||
artifacts.add(mockLibraryArtifact("second-library.jar", "com.example", "second-library", "1.0.0"));
|
||||
artifacts.add(
|
||||
mockLibraryArtifact("third-library-SNAPSHOT.jar", "com.example", "third-library", "1.0.0.SNAPSHOT"));
|
||||
artifacts
|
||||
.add(mockProjectArtifact("first-project-library.jar", "com.example", "first-project-library", "1.0.0"));
|
||||
artifacts.add(mockProjectArtifact("second-project-library-SNAPSHOT.jar", "com.example",
|
||||
"second-project-library", "1.0.0.SNAPSHOT"));
|
||||
ResolvedConfiguration resolvedConfiguration = mock(ResolvedConfiguration.class);
|
||||
given(resolvedConfiguration.getResolvedArtifacts()).willReturn(artifacts);
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
given(configuration.getResolvedConfiguration()).willReturn(resolvedConfiguration);
|
||||
ResolvableDependencies resolvableDependencies = mock(ResolvableDependencies.class);
|
||||
given(configuration.getIncoming()).willReturn(resolvableDependencies);
|
||||
DependencySet dependencies = mock(DependencySet.class);
|
||||
DomainObjectSet<ProjectDependency> projectDependencies = mock(DomainObjectSet.class);
|
||||
given(dependencies.withType(ProjectDependency.class)).willReturn(projectDependencies);
|
||||
given(configuration.getAllDependencies()).willReturn(dependencies);
|
||||
willAnswer((invocation) -> {
|
||||
invocation.getArgument(0, Action.class).execute(resolvableDependencies);
|
||||
return null;
|
||||
}).given(resolvableDependencies).afterResolve(any(Action.class));
|
||||
given(configuration.getIncoming()).willReturn(resolvableDependencies);
|
||||
populateResolvedDependencies(configuration);
|
||||
}
|
||||
|
||||
abstract void populateResolvedDependencies(Configuration configuration);
|
||||
|
||||
private ResolvedArtifact mockLibraryArtifact(String fileName, String group, String module, String version) {
|
||||
ModuleComponentIdentifier moduleComponentIdentifier = mock(ModuleComponentIdentifier.class);
|
||||
ComponentArtifactIdentifier libraryArtifactId = mock(ComponentArtifactIdentifier.class);
|
||||
given(libraryArtifactId.getComponentIdentifier()).willReturn(moduleComponentIdentifier);
|
||||
ResolvedArtifact libraryArtifact = mockArtifact(fileName, group, module, version);
|
||||
given(libraryArtifact.getId()).willReturn(libraryArtifactId);
|
||||
return libraryArtifact;
|
||||
}
|
||||
|
||||
private ResolvedArtifact mockProjectArtifact(String fileName, String group, String module, String version) {
|
||||
ProjectComponentIdentifier projectComponentIdentifier = mock(ProjectComponentIdentifier.class);
|
||||
ComponentArtifactIdentifier projectArtifactId = mock(ComponentArtifactIdentifier.class);
|
||||
given(projectArtifactId.getComponentIdentifier()).willReturn(projectComponentIdentifier);
|
||||
ResolvedArtifact projectArtifact = mockArtifact(fileName, group, module, version);
|
||||
given(projectArtifact.getId()).willReturn(projectArtifactId);
|
||||
return projectArtifact;
|
||||
}
|
||||
|
||||
private ResolvedArtifact mockArtifact(String fileName, String group, String module, String version) {
|
||||
ModuleVersionIdentifier moduleVersionIdentifier = mock(ModuleVersionIdentifier.class);
|
||||
given(moduleVersionIdentifier.getGroup()).willReturn(group);
|
||||
given(moduleVersionIdentifier.getName()).willReturn(module);
|
||||
given(moduleVersionIdentifier.getVersion()).willReturn(version);
|
||||
ResolvedModuleVersion moduleVersion = mock(ResolvedModuleVersion.class);
|
||||
given(moduleVersion.getId()).willReturn(moduleVersionIdentifier);
|
||||
ResolvedArtifact libraryArtifact = mock(ResolvedArtifact.class);
|
||||
File file = new File(this.temp, fileName).getAbsoluteFile();
|
||||
given(libraryArtifact.getFile()).willReturn(file);
|
||||
given(libraryArtifact.getModuleVersion()).willReturn(moduleVersion);
|
||||
return libraryArtifact;
|
||||
}
|
||||
|
||||
List<String> entryLines(JarFile jarFile, String entryName) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(jarFile.getInputStream(jarFile.getEntry(entryName))))) {
|
||||
return reader.lines().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getLayerNames(List<String> index) {
|
||||
Set<String> layerNames = new LinkedHashSet<>();
|
||||
for (String line : index) {
|
||||
if (line.startsWith("- ")) {
|
||||
layerNames.add(line.substring(3, line.length() - 2));
|
||||
}
|
||||
}
|
||||
return layerNames;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,36 +16,16 @@
|
|||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.InvalidRunnerConfigurationException;
|
||||
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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
@ -60,233 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
||||
|
||||
BootJarIntegrationTests() {
|
||||
super("bootJar", "BOOT-INF/lib/", "BOOT-INF/classes/");
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered()
|
||||
throws InvalidRunnerConfigurationException, UnexpectedBuildFailure {
|
||||
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.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.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
|
||||
void layersWithCustomSourceSet() throws IOException {
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void implicitLayers() throws IOException {
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
Map<String, List<String>> indexedLayers;
|
||||
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/library-1.0-SNAPSHOT.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/classes/example/Main.class")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/classes/static/file.txt")).isNotNull();
|
||||
indexedLayers = readLayerIndex(jarFile);
|
||||
}
|
||||
List<String> layerNames = Arrays.asList("dependencies", "spring-boot-loader", "snapshot-dependencies",
|
||||
"application");
|
||||
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
|
||||
Set<String> expectedDependencies = new TreeSet<>();
|
||||
expectedDependencies.add("BOOT-INF/lib/commons-lang3-3.9.jar");
|
||||
expectedDependencies.add("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar");
|
||||
expectedDependencies.add("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar");
|
||||
Set<String> expectedSnapshotDependencies = new TreeSet<>();
|
||||
expectedSnapshotDependencies.add("BOOT-INF/lib/library-1.0-SNAPSHOT.jar");
|
||||
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
|
||||
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
|
||||
assertThat(indexedLayers.get("spring-boot-loader")).containsExactly("org/");
|
||||
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
|
||||
assertThat(indexedLayers.get("application")).containsExactly("BOOT-INF/classes/", "BOOT-INF/classpath.idx",
|
||||
"BOOT-INF/layers.idx", "META-INF/");
|
||||
BuildResult listLayers = this.gradleBuild.build("listLayers");
|
||||
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
String listLayersOutput = listLayers.getOutput();
|
||||
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
|
||||
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
|
||||
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertExtractedLayers(layerNames, indexedLayers);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void multiModuleImplicitLayers() throws IOException {
|
||||
writeSettingsGradle();
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
Map<String, List<String>> indexedLayers;
|
||||
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/alpha-1.2.3.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/bravo-1.2.3.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/library-1.0-SNAPSHOT.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/classes/example/Main.class")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/classes/static/file.txt")).isNotNull();
|
||||
indexedLayers = readLayerIndex(jarFile);
|
||||
}
|
||||
List<String> layerNames = Arrays.asList("dependencies", "spring-boot-loader", "snapshot-dependencies",
|
||||
"application");
|
||||
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
|
||||
Set<String> expectedDependencies = new TreeSet<>();
|
||||
expectedDependencies.add("BOOT-INF/lib/commons-lang3-3.9.jar");
|
||||
expectedDependencies.add("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar");
|
||||
expectedDependencies.add("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar");
|
||||
Set<String> expectedSnapshotDependencies = new TreeSet<>();
|
||||
expectedSnapshotDependencies.add("BOOT-INF/lib/library-1.0-SNAPSHOT.jar");
|
||||
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
|
||||
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
|
||||
assertThat(indexedLayers.get("spring-boot-loader")).containsExactly("org/");
|
||||
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
|
||||
assertThat(indexedLayers.get("application")).containsExactly("BOOT-INF/classes/", "BOOT-INF/classpath.idx",
|
||||
"BOOT-INF/layers.idx", "BOOT-INF/lib/alpha-1.2.3.jar", "BOOT-INF/lib/bravo-1.2.3.jar", "META-INF/");
|
||||
BuildResult listLayers = this.gradleBuild.build("listLayers");
|
||||
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
String listLayersOutput = listLayers.getOutput();
|
||||
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
|
||||
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
|
||||
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertExtractedLayers(layerNames, indexedLayers);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void customLayers() throws IOException {
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
BuildResult build = this.gradleBuild.build("bootJar");
|
||||
assertThat(build.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
Map<String, List<String>> indexedLayers;
|
||||
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/library-1.0-SNAPSHOT.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/classes/example/Main.class")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/classes/static/file.txt")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers.idx")).isNotNull();
|
||||
indexedLayers = readLayerIndex(jarFile);
|
||||
}
|
||||
List<String> layerNames = Arrays.asList("dependencies", "commons-dependencies", "snapshot-dependencies",
|
||||
"static", "app");
|
||||
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
|
||||
Set<String> expectedDependencies = new TreeSet<>();
|
||||
expectedDependencies.add("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar");
|
||||
expectedDependencies.add("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar");
|
||||
List<String> expectedSnapshotDependencies = new ArrayList<>();
|
||||
expectedSnapshotDependencies.add("BOOT-INF/lib/library-1.0-SNAPSHOT.jar");
|
||||
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
|
||||
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
|
||||
assertThat(indexedLayers.get("commons-dependencies")).containsExactly("BOOT-INF/lib/commons-lang3-3.9.jar");
|
||||
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
|
||||
assertThat(indexedLayers.get("static")).containsExactly("BOOT-INF/classes/static/");
|
||||
List<String> appLayer = new ArrayList<>(indexedLayers.get("app"));
|
||||
Set<String> nonLoaderEntries = new TreeSet<>();
|
||||
nonLoaderEntries.add("BOOT-INF/classes/example/");
|
||||
nonLoaderEntries.add("BOOT-INF/classpath.idx");
|
||||
nonLoaderEntries.add("BOOT-INF/layers.idx");
|
||||
nonLoaderEntries.add("META-INF/");
|
||||
assertThat(appLayer).containsSubsequence(nonLoaderEntries);
|
||||
appLayer.removeAll(nonLoaderEntries);
|
||||
assertThat(appLayer).containsExactly("org/");
|
||||
BuildResult listLayers = this.gradleBuild.build("listLayers");
|
||||
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
String listLayersOutput = listLayers.getOutput();
|
||||
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
|
||||
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
|
||||
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertExtractedLayers(layerNames, indexedLayers);
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void multiModuleCustomLayers() throws IOException {
|
||||
writeSettingsGradle();
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
BuildResult build = this.gradleBuild.build("bootJar");
|
||||
assertThat(build.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
Map<String, List<String>> indexedLayers;
|
||||
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry(layerToolsJar)).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/alpha-1.2.3.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/bravo-1.2.3.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/lib/library-1.0-SNAPSHOT.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/classes/example/Main.class")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/classes/static/file.txt")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers.idx")).isNotNull();
|
||||
indexedLayers = readLayerIndex(jarFile);
|
||||
}
|
||||
List<String> layerNames = Arrays.asList("dependencies", "commons-dependencies", "snapshot-dependencies",
|
||||
"subproject-dependencies", "static", "app");
|
||||
assertThat(indexedLayers.keySet()).containsExactlyElementsOf(layerNames);
|
||||
Set<String> expectedSubprojectDependencies = new TreeSet<>();
|
||||
expectedSubprojectDependencies.add("BOOT-INF/lib/alpha-1.2.3.jar");
|
||||
expectedSubprojectDependencies.add("BOOT-INF/lib/bravo-1.2.3.jar");
|
||||
Set<String> expectedDependencies = new TreeSet<>();
|
||||
expectedDependencies.add("BOOT-INF/lib/spring-core-5.2.5.RELEASE.jar");
|
||||
expectedDependencies.add("BOOT-INF/lib/spring-jcl-5.2.5.RELEASE.jar");
|
||||
List<String> expectedSnapshotDependencies = new ArrayList<>();
|
||||
expectedSnapshotDependencies.add("BOOT-INF/lib/library-1.0-SNAPSHOT.jar");
|
||||
(layerToolsJar.contains("SNAPSHOT") ? expectedSnapshotDependencies : expectedDependencies).add(layerToolsJar);
|
||||
assertThat(indexedLayers.get("subproject-dependencies"))
|
||||
.containsExactlyElementsOf(expectedSubprojectDependencies);
|
||||
assertThat(indexedLayers.get("dependencies")).containsExactlyElementsOf(expectedDependencies);
|
||||
assertThat(indexedLayers.get("commons-dependencies")).containsExactly("BOOT-INF/lib/commons-lang3-3.9.jar");
|
||||
assertThat(indexedLayers.get("snapshot-dependencies")).containsExactlyElementsOf(expectedSnapshotDependencies);
|
||||
assertThat(indexedLayers.get("static")).containsExactly("BOOT-INF/classes/static/");
|
||||
List<String> appLayer = new ArrayList<>(indexedLayers.get("app"));
|
||||
Set<String> nonLoaderEntries = new TreeSet<>();
|
||||
nonLoaderEntries.add("BOOT-INF/classes/example/");
|
||||
nonLoaderEntries.add("BOOT-INF/classpath.idx");
|
||||
nonLoaderEntries.add("BOOT-INF/layers.idx");
|
||||
nonLoaderEntries.add("META-INF/");
|
||||
assertThat(appLayer).containsSubsequence(nonLoaderEntries);
|
||||
appLayer.removeAll(nonLoaderEntries);
|
||||
assertThat(appLayer).containsExactly("org/");
|
||||
BuildResult listLayers = this.gradleBuild.build("listLayers");
|
||||
assertThat(listLayers.task(":listLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
String listLayersOutput = listLayers.getOutput();
|
||||
assertThat(new BufferedReader(new StringReader(listLayersOutput)).lines()).containsSequence(layerNames);
|
||||
BuildResult extractLayers = this.gradleBuild.build("extractLayers");
|
||||
assertThat(extractLayers.task(":extractLayers").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertExtractedLayers(layerNames, indexedLayers);
|
||||
super("bootJar", "BOOT-INF/lib/", "BOOT-INF/classes/", "BOOT-INF/");
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
|
@ -320,107 +74,15 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
|||
assertThat(output).doesNotContain("5. ");
|
||||
}
|
||||
|
||||
private void assertExtractedLayers(List<String> layerNames, Map<String, List<String>> indexedLayers)
|
||||
throws IOException {
|
||||
Map<String, List<String>> extractedLayers = readExtractedLayers(this.gradleBuild.getProjectDir(), layerNames);
|
||||
assertThat(extractedLayers.keySet()).isEqualTo(indexedLayers.keySet());
|
||||
extractedLayers.forEach((name, contents) -> {
|
||||
List<String> index = indexedLayers.get(name);
|
||||
List<String> unexpected = new ArrayList<>();
|
||||
for (String file : contents) {
|
||||
if (!isInIndex(index, file)) {
|
||||
unexpected.add(name);
|
||||
}
|
||||
}
|
||||
assertThat(unexpected).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isInIndex(List<String> index, String file) {
|
||||
for (String candidate : index) {
|
||||
if (file.equals(candidate) || candidate.endsWith("/") && file.startsWith(candidate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void writeSettingsGradle() {
|
||||
try (PrintWriter writer = new PrintWriter(
|
||||
new FileWriter(new File(this.gradleBuild.getProjectDir(), "settings.gradle")))) {
|
||||
writer.println("include 'alpha', 'bravo'");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMainClass() {
|
||||
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
||||
examplePackage.mkdirs();
|
||||
File main = new File(examplePackage, "Main.java");
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter(main))) {
|
||||
writer.println("package example;");
|
||||
writer.println();
|
||||
writer.println("import java.io.IOException;");
|
||||
writer.println();
|
||||
writer.println("public class Main {");
|
||||
writer.println();
|
||||
writer.println(" public static void main(String[] args) {");
|
||||
writer.println(" }");
|
||||
writer.println();
|
||||
writer.println("}");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResource() {
|
||||
try {
|
||||
Path path = this.gradleBuild.getProjectDir().toPath()
|
||||
.resolve(Paths.get("src", "main", "resources", "static", "file.txt"));
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.createFile(path);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, List<String>> readLayerIndex(JarFile jarFile) throws IOException {
|
||||
Map<String, List<String>> index = new LinkedHashMap<>();
|
||||
ZipEntry indexEntry = jarFile.getEntry("BOOT-INF/layers.idx");
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) {
|
||||
String line = reader.readLine();
|
||||
String layer = null;
|
||||
while (line != null) {
|
||||
if (line.startsWith("- ")) {
|
||||
layer = line.substring(3, line.length() - 2);
|
||||
}
|
||||
else if (line.startsWith(" - ")) {
|
||||
index.computeIfAbsent(layer, (key) -> new ArrayList<>()).add(line.substring(5, line.length() - 1));
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, List<String>> readExtractedLayers(File root, List<String> layerNames) throws IOException {
|
||||
Map<String, List<String>> extractedLayers = new LinkedHashMap<>();
|
||||
for (String layerName : layerNames) {
|
||||
File layer = new File(root, layerName);
|
||||
assertThat(layer).isDirectory();
|
||||
extractedLayers.put(layerName,
|
||||
Files.walk(layer.toPath()).filter((path) -> path.toFile().isFile()).map(layer.toPath()::relativize)
|
||||
.map(Path::toString).map(StringUtils::cleanPath).collect(Collectors.toList()));
|
||||
}
|
||||
return extractedLayers;
|
||||
}
|
||||
|
||||
private void copyClasspathApplication() throws IOException {
|
||||
copyApplication("classpath");
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getExpectedApplicationLayerContents(String... additionalFiles) {
|
||||
Set<String> contents = new TreeSet<>(Arrays.asList(additionalFiles));
|
||||
contents.addAll(Arrays.asList("BOOT-INF/classpath.idx", "BOOT-INF/layers.idx", "META-INF/"));
|
||||
return contents.toArray(new String[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,41 +16,18 @@
|
|||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DomainObjectSet;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.DependencySet;
|
||||
import org.gradle.api.artifacts.ModuleVersionIdentifier;
|
||||
import org.gradle.api.artifacts.ProjectDependency;
|
||||
import org.gradle.api.artifacts.ResolvableDependencies;
|
||||
import org.gradle.api.artifacts.ResolvedArtifact;
|
||||
import org.gradle.api.artifacts.ResolvedConfiguration;
|
||||
import org.gradle.api.artifacts.ResolvedModuleVersion;
|
||||
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier;
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
|
||||
import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link BootJar}.
|
||||
|
@ -64,7 +41,8 @@ import static org.mockito.Mockito.mock;
|
|||
class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||
|
||||
BootJarTests() {
|
||||
super(BootJar.class, "org.springframework.boot.loader.JarLauncher", "BOOT-INF/lib/", "BOOT-INF/classes/");
|
||||
super(BootJar.class, "org.springframework.boot.loader.JarLauncher", "BOOT-INF/lib/", "BOOT-INF/classes/",
|
||||
"BOOT-INF/");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -89,130 +67,6 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarShouldBeLayeredByDefault() throws IOException {
|
||||
addContent();
|
||||
executeTask();
|
||||
BootJar bootJar = getTask();
|
||||
try (JarFile jarFile = new JarFile(bootJar.getArchiveFile().get().getAsFile())) {
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classes"))
|
||||
.isEqualTo("BOOT-INF/classes/");
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib"))
|
||||
.isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classpath-Index"))
|
||||
.isEqualTo("BOOT-INF/classpath.idx");
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Layers-Index"))
|
||||
.isEqualTo("BOOT-INF/layers.idx");
|
||||
assertThat(getEntryNames(jarFile)).contains("BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarWhenLayersDisabledShouldNotContainLayersIndex() throws IOException {
|
||||
List<String> entryNames = getEntryNames(createLayeredJar((configuration) -> configuration.setEnabled(false)));
|
||||
assertThat(entryNames).doesNotContain("BOOT-INF/layers.idx");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredThenManifestContainsEntryForLayersIndexInPlaceOfClassesAndLib() throws IOException {
|
||||
try (JarFile jarFile = new JarFile(createLayeredJar())) {
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classes"))
|
||||
.isEqualTo("BOOT-INF/classes/");
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Lib"))
|
||||
.isEqualTo("BOOT-INF/lib/");
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classpath-Index"))
|
||||
.isEqualTo("BOOT-INF/classpath.idx");
|
||||
assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Layers-Index"))
|
||||
.isEqualTo("BOOT-INF/layers.idx");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredThenLayersIndexIsPresentAndCorrect() throws IOException {
|
||||
try (JarFile jarFile = new JarFile(createLayeredJar())) {
|
||||
List<String> entryNames = getEntryNames(jarFile);
|
||||
assertThat(entryNames).contains("BOOT-INF/lib/first-library.jar", "BOOT-INF/lib/second-library.jar",
|
||||
"BOOT-INF/lib/third-library-SNAPSHOT.jar", "BOOT-INF/lib/first-project-library.jar",
|
||||
"BOOT-INF/lib/second-project-library-SNAPSHOT.jar",
|
||||
"BOOT-INF/classes/com/example/Application.class", "BOOT-INF/classes/application.properties",
|
||||
"BOOT-INF/classes/static/test.css");
|
||||
List<String> index = entryLines(jarFile, "BOOT-INF/layers.idx");
|
||||
assertThat(getLayerNames(index)).containsExactly("dependencies", "spring-boot-loader",
|
||||
"snapshot-dependencies", "application");
|
||||
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
List<String> expected = new ArrayList<>();
|
||||
expected.add("- \"dependencies\":");
|
||||
expected.add(" - \"BOOT-INF/lib/first-library.jar\"");
|
||||
expected.add(" - \"BOOT-INF/lib/first-project-library.jar\"");
|
||||
expected.add(" - \"BOOT-INF/lib/second-library.jar\"");
|
||||
if (!layerToolsJar.contains("SNAPSHOT")) {
|
||||
expected.add(" - \"" + layerToolsJar + "\"");
|
||||
}
|
||||
expected.add("- \"spring-boot-loader\":");
|
||||
expected.add(" - \"org/\"");
|
||||
expected.add("- \"snapshot-dependencies\":");
|
||||
expected.add(" - \"BOOT-INF/lib/second-project-library-SNAPSHOT.jar\"");
|
||||
if (layerToolsJar.contains("SNAPSHOT")) {
|
||||
expected.add(" - \"" + layerToolsJar + "\"");
|
||||
}
|
||||
expected.add(" - \"BOOT-INF/lib/third-library-SNAPSHOT.jar\"");
|
||||
expected.add("- \"application\":");
|
||||
expected.add(" - \"BOOT-INF/classes/\"");
|
||||
expected.add(" - \"BOOT-INF/classpath.idx\"");
|
||||
expected.add(" - \"BOOT-INF/layers.idx\"");
|
||||
expected.add(" - \"META-INF/\"");
|
||||
assertThat(index).containsExactlyElementsOf(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredWithCustomStrategiesThenLayersIndexIsPresentAndCorrect() throws IOException {
|
||||
File jar = createLayeredJar((layered) -> {
|
||||
layered.application((application) -> {
|
||||
application.intoLayer("resources", (spec) -> spec.include("static/**"));
|
||||
application.intoLayer("application");
|
||||
});
|
||||
layered.dependencies((dependencies) -> {
|
||||
dependencies.intoLayer("my-snapshot-deps", (spec) -> spec.include("com.example:*:*.SNAPSHOT"));
|
||||
dependencies.intoLayer("my-internal-deps", (spec) -> spec.include("com.example:*:*"));
|
||||
dependencies.intoLayer("my-deps");
|
||||
});
|
||||
layered.setLayerOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application");
|
||||
});
|
||||
try (JarFile jarFile = new JarFile(jar)) {
|
||||
List<String> entryNames = getEntryNames(jar);
|
||||
assertThat(entryNames).contains("BOOT-INF/lib/first-library.jar", "BOOT-INF/lib/second-library.jar",
|
||||
"BOOT-INF/lib/third-library-SNAPSHOT.jar", "BOOT-INF/lib/first-project-library.jar",
|
||||
"BOOT-INF/lib/second-project-library-SNAPSHOT.jar",
|
||||
"BOOT-INF/classes/com/example/Application.class", "BOOT-INF/classes/application.properties",
|
||||
"BOOT-INF/classes/static/test.css");
|
||||
List<String> index = entryLines(jarFile, "BOOT-INF/layers.idx");
|
||||
assertThat(getLayerNames(index)).containsExactly("my-deps", "my-internal-deps", "my-snapshot-deps",
|
||||
"resources", "application");
|
||||
String layerToolsJar = "BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName();
|
||||
List<String> expected = new ArrayList<>();
|
||||
expected.add("- \"my-deps\":");
|
||||
expected.add(" - \"" + layerToolsJar + "\"");
|
||||
expected.add("- \"my-internal-deps\":");
|
||||
expected.add(" - \"BOOT-INF/lib/first-library.jar\"");
|
||||
expected.add(" - \"BOOT-INF/lib/first-project-library.jar\"");
|
||||
expected.add(" - \"BOOT-INF/lib/second-library.jar\"");
|
||||
expected.add("- \"my-snapshot-deps\":");
|
||||
expected.add(" - \"BOOT-INF/lib/second-project-library-SNAPSHOT.jar\"");
|
||||
expected.add(" - \"BOOT-INF/lib/third-library-SNAPSHOT.jar\"");
|
||||
expected.add("- \"resources\":");
|
||||
expected.add(" - \"BOOT-INF/classes/static/\"");
|
||||
expected.add("- \"application\":");
|
||||
expected.add(" - \"BOOT-INF/classes/application.properties\"");
|
||||
expected.add(" - \"BOOT-INF/classes/com/\"");
|
||||
expected.add(" - \"BOOT-INF/classpath.idx\"");
|
||||
expected.add(" - \"BOOT-INF/layers.idx\"");
|
||||
expected.add(" - \"META-INF/\"");
|
||||
expected.add(" - \"org/\"");
|
||||
assertThat(index).containsExactlyElementsOf(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarsInLibAreStored() throws IOException {
|
||||
try (JarFile jarFile = new JarFile(createLayeredJar())) {
|
||||
|
@ -237,19 +91,6 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredThenLayerToolsAreAddedToTheJar() throws IOException {
|
||||
List<String> entryNames = getEntryNames(createLayeredJar());
|
||||
assertThat(entryNames).contains("BOOT-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredAndIncludeLayerToolsIsFalseThenLayerToolsAreNotAddedToTheJar() throws IOException {
|
||||
List<String> entryNames = getEntryNames(
|
||||
createLayeredJar((configuration) -> configuration.setIncludeLayerTools(false)));
|
||||
assertThat(entryNames).doesNotContain("BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void classpathIndexPointsToBootInfLibs() throws IOException {
|
||||
try (JarFile jarFile = new JarFile(createPopulatedJar())) {
|
||||
|
@ -268,111 +109,14 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
|||
return getTask().getArchiveFile().get().getAsFile();
|
||||
}
|
||||
|
||||
private File createLayeredJar() throws IOException {
|
||||
return createLayeredJar((spec) -> {
|
||||
});
|
||||
}
|
||||
|
||||
private File createLayeredJar(Action<LayeredSpec> action) throws IOException {
|
||||
@Override
|
||||
void applyLayered(Action<LayeredSpec> action) {
|
||||
getTask().layered(action);
|
||||
addContent();
|
||||
executeTask();
|
||||
return getTask().getArchiveFile().get().getAsFile();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addContent() throws IOException {
|
||||
BootJar bootJar = getTask();
|
||||
bootJar.getMainClass().set("com.example.Main");
|
||||
File classesJavaMain = new File(this.temp, "classes/java/main");
|
||||
File applicationClass = new File(classesJavaMain, "com/example/Application.class");
|
||||
applicationClass.getParentFile().mkdirs();
|
||||
applicationClass.createNewFile();
|
||||
File resourcesMain = new File(this.temp, "resources/main");
|
||||
File applicationProperties = new File(resourcesMain, "application.properties");
|
||||
applicationProperties.getParentFile().mkdirs();
|
||||
applicationProperties.createNewFile();
|
||||
File staticResources = new File(resourcesMain, "static");
|
||||
staticResources.mkdir();
|
||||
File css = new File(staticResources, "test.css");
|
||||
css.createNewFile();
|
||||
bootJar.classpath(classesJavaMain, resourcesMain, jarFile("first-library.jar"), jarFile("second-library.jar"),
|
||||
jarFile("third-library-SNAPSHOT.jar"), jarFile("first-project-library.jar"),
|
||||
jarFile("second-project-library-SNAPSHOT.jar"));
|
||||
Set<ResolvedArtifact> artifacts = new LinkedHashSet<>();
|
||||
artifacts.add(mockLibraryArtifact("first-library.jar", "com.example", "first-library", "1.0.0"));
|
||||
artifacts.add(mockLibraryArtifact("second-library.jar", "com.example", "second-library", "1.0.0"));
|
||||
artifacts.add(
|
||||
mockLibraryArtifact("third-library-SNAPSHOT.jar", "com.example", "third-library", "1.0.0.SNAPSHOT"));
|
||||
artifacts
|
||||
.add(mockProjectArtifact("first-project-library.jar", "com.example", "first-project-library", "1.0.0"));
|
||||
artifacts.add(mockProjectArtifact("second-project-library-SNAPSHOT.jar", "com.example",
|
||||
"second-project-library", "1.0.0.SNAPSHOT"));
|
||||
ResolvedConfiguration resolvedConfiguration = mock(ResolvedConfiguration.class);
|
||||
given(resolvedConfiguration.getResolvedArtifacts()).willReturn(artifacts);
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
given(configuration.getResolvedConfiguration()).willReturn(resolvedConfiguration);
|
||||
ResolvableDependencies resolvableDependencies = mock(ResolvableDependencies.class);
|
||||
given(configuration.getIncoming()).willReturn(resolvableDependencies);
|
||||
DependencySet dependencies = mock(DependencySet.class);
|
||||
DomainObjectSet<ProjectDependency> projectDependencies = mock(DomainObjectSet.class);
|
||||
given(dependencies.withType(ProjectDependency.class)).willReturn(projectDependencies);
|
||||
given(configuration.getAllDependencies()).willReturn(dependencies);
|
||||
willAnswer((invocation) -> {
|
||||
invocation.getArgument(0, Action.class).execute(resolvableDependencies);
|
||||
return null;
|
||||
}).given(resolvableDependencies).afterResolve(any(Action.class));
|
||||
given(configuration.getIncoming()).willReturn(resolvableDependencies);
|
||||
bootJar.getResolvedDependencies().processConfiguration(configuration);
|
||||
}
|
||||
|
||||
private ResolvedArtifact mockLibraryArtifact(String fileName, String group, String module, String version) {
|
||||
ModuleComponentIdentifier moduleComponentIdentifier = mock(ModuleComponentIdentifier.class);
|
||||
ComponentArtifactIdentifier libraryArtifactId = mock(ComponentArtifactIdentifier.class);
|
||||
given(libraryArtifactId.getComponentIdentifier()).willReturn(moduleComponentIdentifier);
|
||||
ResolvedArtifact libraryArtifact = mockArtifact(fileName, group, module, version);
|
||||
given(libraryArtifact.getId()).willReturn(libraryArtifactId);
|
||||
return libraryArtifact;
|
||||
}
|
||||
|
||||
private ResolvedArtifact mockProjectArtifact(String fileName, String group, String module, String version) {
|
||||
ProjectComponentIdentifier projectComponentIdentifier = mock(ProjectComponentIdentifier.class);
|
||||
ComponentArtifactIdentifier projectArtifactId = mock(ComponentArtifactIdentifier.class);
|
||||
given(projectArtifactId.getComponentIdentifier()).willReturn(projectComponentIdentifier);
|
||||
ResolvedArtifact projectArtifact = mockArtifact(fileName, group, module, version);
|
||||
given(projectArtifact.getId()).willReturn(projectArtifactId);
|
||||
return projectArtifact;
|
||||
}
|
||||
|
||||
private ResolvedArtifact mockArtifact(String fileName, String group, String module, String version) {
|
||||
ModuleVersionIdentifier moduleVersionIdentifier = mock(ModuleVersionIdentifier.class);
|
||||
given(moduleVersionIdentifier.getGroup()).willReturn(group);
|
||||
given(moduleVersionIdentifier.getName()).willReturn(module);
|
||||
given(moduleVersionIdentifier.getVersion()).willReturn(version);
|
||||
ResolvedModuleVersion moduleVersion = mock(ResolvedModuleVersion.class);
|
||||
given(moduleVersion.getId()).willReturn(moduleVersionIdentifier);
|
||||
ResolvedArtifact libraryArtifact = mock(ResolvedArtifact.class);
|
||||
File file = new File(this.temp, fileName).getAbsoluteFile();
|
||||
given(libraryArtifact.getFile()).willReturn(file);
|
||||
given(libraryArtifact.getModuleVersion()).willReturn(moduleVersion);
|
||||
return libraryArtifact;
|
||||
}
|
||||
|
||||
private List<String> entryLines(JarFile jarFile, String entryName) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(jarFile.getInputStream(jarFile.getEntry(entryName))))) {
|
||||
return reader.lines().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getLayerNames(List<String> index) {
|
||||
Set<String> layerNames = new LinkedHashSet<>();
|
||||
for (String line : index) {
|
||||
if (line.startsWith("- ")) {
|
||||
layerNames.add(line.substring(3, line.length() - 2));
|
||||
}
|
||||
}
|
||||
return layerNames;
|
||||
@Override
|
||||
void populateResolvedDependencies(Configuration configuration) {
|
||||
getTask().getResolvedDependencies().processConfiguration(configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,10 +16,14 @@
|
|||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.springframework.boot.gradle.junit.GradleCompatibility;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link BootJar}.
|
||||
* Integration tests for {@link BootWar}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
|
@ -27,7 +31,14 @@ import org.springframework.boot.gradle.junit.GradleCompatibility;
|
|||
class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
||||
|
||||
BootWarIntegrationTests() {
|
||||
super("bootWar", "WEB-INF/lib/", "WEB-INF/classes/");
|
||||
super("bootWar", "WEB-INF/lib/", "WEB-INF/classes/", "WEB-INF/");
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getExpectedApplicationLayerContents(String... additionalFiles) {
|
||||
Set<String> contents = new TreeSet<>(Arrays.asList(additionalFiles));
|
||||
contents.addAll(Arrays.asList("WEB-INF/layers.idx", "META-INF/"));
|
||||
return contents.toArray(new String[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -32,7 +34,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
class BootWarTests extends AbstractBootArchiveTests<BootWar> {
|
||||
|
||||
BootWarTests() {
|
||||
super(BootWar.class, "org.springframework.boot.loader.WarLauncher", "WEB-INF/lib/", "WEB-INF/classes/");
|
||||
super(BootWar.class, "org.springframework.boot.loader.WarLauncher", "WEB-INF/lib/", "WEB-INF/classes/",
|
||||
"WEB-INF/");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -111,4 +114,19 @@ class BootWarTests extends AbstractBootArchiveTests<BootWar> {
|
|||
getTask().copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
void populateResolvedDependencies(Configuration configuration) {
|
||||
getTask().getResolvedDependencies().processConfiguration(configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
void applyLayered(Action<LayeredSpec> action) {
|
||||
getTask().layered(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean archiveHasClasspathIndex() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ dependencies {
|
|||
implementation("com.example:library:1.0-SNAPSHOT")
|
||||
implementation("org.apache.commons:commons-lang3:3.9")
|
||||
implementation("org.springframework:spring-core:5.2.5.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-logging:2.2.0.RELEASE")
|
||||
}
|
||||
|
||||
task listLayers(type: JavaExec) {
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
layered {
|
||||
application {
|
||||
intoLayer("static") {
|
||||
include "META-INF/resources/**", "resources/**", "static/**", "public/**"
|
||||
}
|
||||
intoLayer("app")
|
||||
}
|
||||
dependencies {
|
||||
intoLayer("snapshot-dependencies") {
|
||||
include "*:*:*SNAPSHOT"
|
||||
}
|
||||
intoLayer("commons-dependencies") {
|
||||
include "org.apache.commons:*"
|
||||
}
|
||||
intoLayer("dependencies")
|
||||
}
|
||||
layerOrder = ["dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"]
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "file:repository" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.example:library:1.0-SNAPSHOT")
|
||||
implementation("org.apache.commons:commons-lang3:3.9")
|
||||
implementation("org.springframework:spring-core:5.2.5.RELEASE")
|
||||
}
|
||||
|
||||
task listLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "list"
|
||||
}
|
||||
|
||||
task extractLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "extract"
|
||||
}
|
|
@ -16,3 +16,9 @@ dependencies {
|
|||
developmentOnly("commons-io:commons-io:2.6")
|
||||
implementation("commons-io:commons-io:2.6")
|
||||
}
|
||||
|
||||
bootWar {
|
||||
layered {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,3 +19,9 @@ dependencies {
|
|||
bootWar {
|
||||
classpath configurations.developmentOnly
|
||||
}
|
||||
|
||||
bootWar {
|
||||
layered {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "file:repository" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.example:library:1.0-SNAPSHOT")
|
||||
implementation("org.apache.commons:commons-lang3:3.9")
|
||||
implementation("org.springframework:spring-core:5.2.5.RELEASE")
|
||||
implementation("org.springframework.boot:spring-boot-starter-logging:2.2.0.RELEASE")
|
||||
}
|
||||
|
||||
task listLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "list"
|
||||
}
|
||||
|
||||
task extractLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "extract"
|
||||
}
|
|
@ -17,3 +17,9 @@ dependencies {
|
|||
implementation(name: "standard")
|
||||
implementation(name: "starter")
|
||||
}
|
||||
|
||||
bootWar {
|
||||
layered {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
custom
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "file:repository" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.example:library:1.0-SNAPSHOT")
|
||||
implementation("org.apache.commons:commons-lang3:3.9")
|
||||
implementation("org.springframework:spring-core:5.2.5.RELEASE")
|
||||
}
|
||||
|
||||
task listLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "list"
|
||||
}
|
||||
|
||||
task extractLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "extract"
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
group = 'org.example.projects'
|
||||
version = '1.2.3'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
layered {
|
||||
application {
|
||||
intoLayer("static") {
|
||||
include "META-INF/resources/**", "resources/**", "static/**", "public/**"
|
||||
}
|
||||
intoLayer("app")
|
||||
}
|
||||
dependencies {
|
||||
intoLayer("snapshot-dependencies") {
|
||||
include "*:*:*SNAPSHOT"
|
||||
excludeProjectDependencies()
|
||||
}
|
||||
intoLayer("subproject-dependencies") {
|
||||
includeProjectDependencies()
|
||||
}
|
||||
intoLayer("commons-dependencies") {
|
||||
include "org.apache.commons:*"
|
||||
}
|
||||
intoLayer("dependencies")
|
||||
}
|
||||
layerOrder = ["dependencies", "commons-dependencies", "snapshot-dependencies", "subproject-dependencies", "static", "app"]
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "file:repository" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':alpha'))
|
||||
implementation(project(':bravo'))
|
||||
implementation("com.example:library:1.0-SNAPSHOT")
|
||||
implementation("org.apache.commons:commons-lang3:3.9")
|
||||
implementation("org.springframework:spring-core:5.2.5.RELEASE")
|
||||
}
|
||||
|
||||
task listLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "list"
|
||||
}
|
||||
|
||||
task extractLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "extract"
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
group = 'org.example.projects'
|
||||
version = '1.2.3'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "file:repository" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(':alpha'))
|
||||
implementation(project(':bravo'))
|
||||
implementation("com.example:library:1.0-SNAPSHOT")
|
||||
implementation("org.apache.commons:commons-lang3:3.9")
|
||||
implementation("org.springframework:spring-core:5.2.5.RELEASE")
|
||||
}
|
||||
|
||||
task listLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "list"
|
||||
}
|
||||
|
||||
task extractLayers(type: JavaExec) {
|
||||
classpath = bootWar.outputs.files
|
||||
systemProperties = [ "jarmode": "layertools" ]
|
||||
args "extract"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
layered {
|
||||
{layerTools}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
layered {
|
||||
{layerEnablement}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '{version}'
|
||||
id 'war'
|
||||
}
|
||||
|
||||
bootWar {
|
||||
mainClass = 'com.example.Application'
|
||||
{layered}
|
||||
}
|
Loading…
Reference in New Issue