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; ...@@ -22,6 +22,7 @@ import java.util.function.Function;
import org.gradle.api.Action; import org.gradle.api.Action;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.artifacts.ResolvableDependencies;
import org.gradle.api.file.CopySpec; import org.gradle.api.file.CopySpec;
import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCollection;
import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileCopyDetails;
...@@ -30,6 +31,8 @@ import org.gradle.api.internal.file.copy.CopyAction; ...@@ -30,6 +31,8 @@ import org.gradle.api.internal.file.copy.CopyAction;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.specs.Spec; import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.Classpath; 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.Optional;
import org.gradle.api.tasks.bundling.War; import org.gradle.api.tasks.bundling.War;
...@@ -50,12 +53,18 @@ public class BootWar extends War implements BootArchive { ...@@ -50,12 +53,18 @@ public class BootWar extends War implements BootArchive {
private static final String LIB_DIRECTORY = "WEB-INF/lib/"; 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 BootArchiveSupport support;
private final Property<String> mainClass; private final Property<String> mainClass;
private FileCollection providedClasspath; private FileCollection providedClasspath;
private final ResolvedDependencies resolvedDependencies = new ResolvedDependencies();
private LayeredSpec layered = new LayeredSpec();
/** /**
* Creates a new {@code BootWar} task. * Creates a new {@code BootWar} task.
*/ */
...@@ -65,6 +74,14 @@ public class BootWar extends War implements BootArchive { ...@@ -65,6 +74,14 @@ public class BootWar extends War implements BootArchive {
getWebInf().into("lib-provided", fromCallTo(this::getProvidedLibFiles)); getWebInf().into("lib-provided", fromCallTo(this::getProvidedLibFiles));
this.support.moveModuleInfoToRoot(getRootSpec()); this.support.moveModuleInfoToRoot(getRootSpec());
getRootSpec().eachFile(this.support::excludeNonZipLibraryFiles); 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() { private Object getProvidedLibFiles() {
...@@ -74,12 +91,21 @@ public class BootWar extends War implements BootArchive { ...@@ -74,12 +91,21 @@ public class BootWar extends War implements BootArchive {
@Override @Override
public void copy() { public void copy() {
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, null, this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, null,
null); (isLayeredDisabled()) ? null : LAYERS_INDEX);
super.copy(); super.copy();
} }
private boolean isLayeredDisabled() {
return this.layered != null && !this.layered.isEnabled();
}
@Override @Override
protected CopyAction createCopyAction() { 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); return this.support.createCopyAction(this);
} }
...@@ -181,6 +207,25 @@ public class BootWar extends War implements BootArchive { ...@@ -181,6 +207,25 @@ public class BootWar extends War implements BootArchive {
return isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED; 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 * 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. * {@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 { ...@@ -201,6 +246,11 @@ public class BootWar extends War implements BootArchive {
return launchScript; return launchScript;
} }
@Internal
ResolvedDependencies getResolvedDependencies() {
return this.resolvedDependencies;
}
/** /**
* Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read. * Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read.
* @param <T> the result type * @param <T> the result type
......
...@@ -41,7 +41,7 @@ import org.springframework.boot.loader.tools.layer.LibraryContentFilter; ...@@ -41,7 +41,7 @@ import org.springframework.boot.loader.tools.layer.LibraryContentFilter;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Encapsulates the configuration for a layered jar. * Encapsulates the configuration for a layered archive.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Scott Frederick * @author Scott Frederick
...@@ -65,7 +65,7 @@ public class LayeredSpec { ...@@ -65,7 +65,7 @@ public class LayeredSpec {
/** /**
* Returns whether the layer tools should be included as a dependency in the layered * Returns whether the layer tools should be included as a dependency in the layered
* jar. * archive.
* @return whether the layer tools should be included * @return whether the layer tools should be included
*/ */
@Input @Input
...@@ -74,7 +74,8 @@ public class LayeredSpec { ...@@ -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, * @param includeLayerTools {@code true} if the layer tools should be included,
* otherwise {@code false} * otherwise {@code false}
*/ */
...@@ -83,7 +84,7 @@ public class LayeredSpec { ...@@ -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 * @return whether the layers.idx should be included
*/ */
@Input @Input
...@@ -92,8 +93,8 @@ public class LayeredSpec { ...@@ -92,8 +93,8 @@ public class LayeredSpec {
} }
/** /**
* Sets whether the layers.idx should be included in the jar. * Sets whether the layers.idx should be included in the archive.
* @param enabled {@code true} layers.idx should be included in the jar, otherwise * @param enabled {@code true} layers.idx should be included in the archive, otherwise
* {@code false} * {@code false}
*/ */
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
...@@ -171,7 +172,8 @@ public class LayeredSpec { ...@@ -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 * @return the layer order
*/ */
@Input @Input
...@@ -180,7 +182,7 @@ public class LayeredSpec { ...@@ -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 * @param layerOrder the layer order
*/ */
public void setLayerOrder(String... layerOrder) { public void setLayerOrder(String... layerOrder) {
...@@ -188,7 +190,7 @@ public class LayeredSpec { ...@@ -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 * @param layerOrder the layer order
*/ */
public void setLayerOrder(List<String> layerOrder) { public void setLayerOrder(List<String> layerOrder) {
......
...@@ -16,10 +16,14 @@ ...@@ -16,10 +16,14 @@
package org.springframework.boot.gradle.tasks.bundling; 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; import org.springframework.boot.gradle.junit.GradleCompatibility;
/** /**
* Integration tests for {@link BootJar}. * Integration tests for {@link BootWar}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
...@@ -27,7 +31,14 @@ import org.springframework.boot.gradle.junit.GradleCompatibility; ...@@ -27,7 +31,14 @@ import org.springframework.boot.gradle.junit.GradleCompatibility;
class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests { class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests {
BootWarIntegrationTests() { 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; ...@@ -20,6 +20,8 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import org.gradle.api.Action;
import org.gradle.api.artifacts.Configuration;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -32,7 +34,8 @@ 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> { class BootWarTests extends AbstractBootArchiveTests<BootWar> {
BootWarTests() { 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 @Test
...@@ -111,4 +114,19 @@ class BootWarTests extends AbstractBootArchiveTests<BootWar> { ...@@ -111,4 +114,19 @@ class BootWarTests extends AbstractBootArchiveTests<BootWar> {
getTask().copy(); 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 { ...@@ -16,6 +16,7 @@ dependencies {
implementation("com.example:library:1.0-SNAPSHOT") implementation("com.example:library:1.0-SNAPSHOT")
implementation("org.apache.commons:commons-lang3:3.9") implementation("org.apache.commons:commons-lang3:3.9")
implementation("org.springframework:spring-core:5.2.5.RELEASE") 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) { 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 { ...@@ -16,3 +16,9 @@ dependencies {
developmentOnly("commons-io:commons-io:2.6") developmentOnly("commons-io:commons-io:2.6")
implementation("commons-io:commons-io:2.6") implementation("commons-io:commons-io:2.6")
} }
bootWar {
layered {
enabled = false
}
}
...@@ -19,3 +19,9 @@ dependencies { ...@@ -19,3 +19,9 @@ dependencies {
bootWar { bootWar {
classpath configurations.developmentOnly classpath configurations.developmentOnly
} }
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 { ...@@ -17,3 +17,9 @@ dependencies {
implementation(name: "standard") implementation(name: "standard")
implementation(name: "starter") 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