Commit 1245e5ee authored by Madhura Bhave's avatar Madhura Bhave

Add support for creating layered war files with Gradle

See gh-22195
parent 7f8ea333
......@@ -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,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) {
......
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
}
}
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
}
}
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"
}
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"
}
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"
}
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
id 'war'
}
bootWar {
mainClass = 'com.example.Application'
layered {
{layerTools}
}
}
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
id 'war'
}
bootWar {
mainClass = 'com.example.Application'
layered {
{layerEnablement}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment