Refine layer customization for Maven and Gradle
Simplify layer customization logic for both Maven and Gradle and refactor some internals of the Gradle plugin. Both Maven and Gradle now use a simpler customization format that consists of `application`, `dependencies` and `layer order` sections. The `application`, `dependencies` configurations support one or more `into` blocks that are used to select content for a specific layer. Closes gh-20526
This commit is contained in:
@@ -9,27 +9,17 @@ bootJar {
|
||||
|
||||
// tag::layered[]
|
||||
bootJar {
|
||||
layers {
|
||||
layersOrder "dependencies", "snapshot-dependencies", "application"
|
||||
libraries {
|
||||
layerContent("snapshot-dependencies") {
|
||||
coordinates {
|
||||
include "*:*:*SNAPSHOT"
|
||||
}
|
||||
}
|
||||
layerContent("dependencies") {
|
||||
coordinates {
|
||||
include "*:*"
|
||||
}
|
||||
}
|
||||
}
|
||||
layered {
|
||||
application {
|
||||
layerContent("application") {
|
||||
locations {
|
||||
include "**"
|
||||
}
|
||||
}
|
||||
intoLayer("application")
|
||||
}
|
||||
dependencies {
|
||||
intoLayer("snapshot-dependencies") {
|
||||
include "*:*:*SNAPSHOT"
|
||||
}
|
||||
intoLayer("dependencies")
|
||||
}
|
||||
layerOrder "dependencies", "snapshot-dependencies", "application"
|
||||
}
|
||||
}
|
||||
// end::layered[]
|
||||
|
||||
@@ -7,27 +7,17 @@ plugins {
|
||||
|
||||
// tag::layered[]
|
||||
tasks.getByName<BootJar>("bootJar") {
|
||||
layers {
|
||||
layersOrder("dependencies", "snapshot-dependencies", "application")
|
||||
libraries {
|
||||
layerContent("snapshot-dependencies") {
|
||||
coordinates {
|
||||
include("*:*:*SNAPSHOT")
|
||||
}
|
||||
}
|
||||
layerContent("dependencies") {
|
||||
coordinates {
|
||||
include("*:*")
|
||||
}
|
||||
}
|
||||
}
|
||||
layered {
|
||||
application {
|
||||
layerContent("application") {
|
||||
locations {
|
||||
include("**")
|
||||
}
|
||||
}
|
||||
intoLayer("application")
|
||||
}
|
||||
dependencies {
|
||||
intoLayer("snapshot-dependencies") {
|
||||
include("*:*:*SNAPSHOT")
|
||||
}
|
||||
intoLayer("dependencies") {
|
||||
}
|
||||
layersOrder("dependencies", "snapshot-dependencies", "application")
|
||||
}
|
||||
}
|
||||
// end::layered[]
|
||||
|
||||
@@ -9,6 +9,6 @@ bootJar {
|
||||
|
||||
// tag::layered[]
|
||||
bootJar {
|
||||
layers()
|
||||
layered()
|
||||
}
|
||||
// end::layered[]
|
||||
|
||||
@@ -11,6 +11,6 @@ tasks.getByName<BootJar>("bootJar") {
|
||||
|
||||
// tag::layered[]
|
||||
tasks.getByName<BootJar>("bootJar") {
|
||||
layers()
|
||||
layered()
|
||||
}
|
||||
// end::layered[]
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.gradle.api.Action;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact;
|
||||
import org.gradle.api.plugins.ApplicationPlugin;
|
||||
@@ -94,9 +93,6 @@ final class JavaPluginAction implements PluginApplicationAction {
|
||||
SourceSet mainSourceSet = javaPluginConvention(project).getSourceSets()
|
||||
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
|
||||
bootJar.classpath((Callable<FileCollection>) mainSourceSet::getRuntimeClasspath);
|
||||
Configuration runtimeClasspathConfiguration = project.getConfigurations()
|
||||
.getByName(mainSourceSet.getRuntimeClasspathConfigurationName());
|
||||
runtimeClasspathConfiguration.getIncoming().afterResolve(bootJar::resolvedDependencies);
|
||||
bootJar.conventionMapping("mainClassName", new MainClassConvention(project, bootJar::getClasspath));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.gradle.api.file.CopySpec;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.file.FileTreeElement;
|
||||
import org.gradle.api.file.RelativePath;
|
||||
@@ -34,6 +35,7 @@ import org.gradle.api.internal.file.copy.CopyAction;
|
||||
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
|
||||
import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
|
||||
import org.gradle.api.java.archives.Attributes;
|
||||
import org.gradle.api.java.archives.Manifest;
|
||||
import org.gradle.api.specs.Spec;
|
||||
import org.gradle.api.specs.Specs;
|
||||
import org.gradle.api.tasks.WorkResult;
|
||||
@@ -44,6 +46,9 @@ import org.gradle.api.tasks.util.PatternSet;
|
||||
* Support class for implementations of {@link BootArchive}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @see BootJar
|
||||
* @see BootWar
|
||||
*/
|
||||
class BootArchiveSupport {
|
||||
|
||||
@@ -61,46 +66,71 @@ class BootArchiveSupport {
|
||||
|
||||
private final PatternSet requiresUnpack = new PatternSet();
|
||||
|
||||
private final Function<FileCopyDetails, ZipCompression> compressionResolver;
|
||||
|
||||
private final PatternSet exclusions = new PatternSet();
|
||||
|
||||
private final String loaderMainClass;
|
||||
|
||||
private final Spec<FileCopyDetails> librarySpec;
|
||||
|
||||
private final Function<FileCopyDetails, ZipCompression> compressionResolver;
|
||||
|
||||
private LaunchScriptConfiguration launchScript;
|
||||
|
||||
private boolean excludeDevtools = true;
|
||||
|
||||
BootArchiveSupport(String loaderMainClass, Function<FileCopyDetails, ZipCompression> compressionResolver) {
|
||||
BootArchiveSupport(String loaderMainClass, Spec<FileCopyDetails> librarySpec,
|
||||
Function<FileCopyDetails, ZipCompression> compressionResolver) {
|
||||
this.loaderMainClass = loaderMainClass;
|
||||
this.librarySpec = librarySpec;
|
||||
this.compressionResolver = compressionResolver;
|
||||
this.requiresUnpack.include(Specs.satisfyNone());
|
||||
configureExclusions();
|
||||
}
|
||||
|
||||
void configureManifest(Jar jar, String mainClassName, String springBootClasses, String springBootLib) {
|
||||
Attributes attributes = jar.getManifest().getAttributes();
|
||||
void configureManifest(Manifest manifest, String mainClass, String classes, String lib, String classPathIndex,
|
||||
String layersIndex) {
|
||||
Attributes attributes = manifest.getAttributes();
|
||||
attributes.putIfAbsent("Main-Class", this.loaderMainClass);
|
||||
attributes.putIfAbsent("Start-Class", mainClassName);
|
||||
attributes.computeIfAbsent("Spring-Boot-Version", (key) -> determineSpringBootVersion());
|
||||
attributes.putIfAbsent("Spring-Boot-Classes", springBootClasses);
|
||||
attributes.putIfAbsent("Spring-Boot-Lib", springBootLib);
|
||||
attributes.putIfAbsent("Start-Class", mainClass);
|
||||
attributes.computeIfAbsent("Spring-Boot-Version", (name) -> determineSpringBootVersion());
|
||||
if (classes != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Classes", classes);
|
||||
}
|
||||
if (lib != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Lib", lib);
|
||||
}
|
||||
if (classPathIndex != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Classpath-Index", classPathIndex);
|
||||
}
|
||||
if (layersIndex != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Layers-Index", layersIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private String determineSpringBootVersion() {
|
||||
String implementationVersion = getClass().getPackage().getImplementationVersion();
|
||||
return (implementationVersion != null) ? implementationVersion : "unknown";
|
||||
String version = getClass().getPackage().getImplementationVersion();
|
||||
return (version != null) ? version : "unknown";
|
||||
}
|
||||
|
||||
CopyAction createCopyAction(Jar jar) {
|
||||
CopyAction copyAction = new BootZipCopyAction(jar.getArchiveFile().get().getAsFile(),
|
||||
jar.isPreserveFileTimestamps(), isUsingDefaultLoader(jar), this.requiresUnpack.getAsSpec(),
|
||||
this.exclusions.getAsExcludeSpec(), this.launchScript, this.compressionResolver,
|
||||
jar.getMetadataCharset());
|
||||
if (!jar.isReproducibleFileOrder()) {
|
||||
return copyAction;
|
||||
}
|
||||
return new ReproducibleOrderingCopyAction(copyAction);
|
||||
return createCopyAction(jar, null, false);
|
||||
}
|
||||
|
||||
CopyAction createCopyAction(Jar jar, LayerResolver layerResolver, boolean includeLayerTools) {
|
||||
File output = jar.getArchiveFile().get().getAsFile();
|
||||
Manifest manifest = jar.getManifest();
|
||||
boolean preserveFileTimestamps = jar.isPreserveFileTimestamps();
|
||||
boolean includeDefaultLoader = isUsingDefaultLoader(jar);
|
||||
Spec<FileTreeElement> requiresUnpack = this.requiresUnpack.getAsSpec();
|
||||
Spec<FileTreeElement> exclusions = this.exclusions.getAsExcludeSpec();
|
||||
LaunchScriptConfiguration launchScript = this.launchScript;
|
||||
Spec<FileCopyDetails> librarySpec = this.librarySpec;
|
||||
Function<FileCopyDetails, ZipCompression> compressionResolver = this.compressionResolver;
|
||||
String encoding = jar.getMetadataCharset();
|
||||
CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, includeDefaultLoader,
|
||||
includeLayerTools, requiresUnpack, exclusions, launchScript, librarySpec, compressionResolver, encoding,
|
||||
layerResolver);
|
||||
return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action;
|
||||
}
|
||||
|
||||
private boolean isUsingDefaultLoader(Jar jar) {
|
||||
@@ -132,7 +162,19 @@ class BootArchiveSupport {
|
||||
configureExclusions();
|
||||
}
|
||||
|
||||
boolean isZip(File file) {
|
||||
void excludeNonZipLibraryFiles(FileCopyDetails details) {
|
||||
if (this.librarySpec.isSatisfiedBy(details)) {
|
||||
excludeNonZipFiles(details);
|
||||
}
|
||||
}
|
||||
|
||||
void excludeNonZipFiles(FileCopyDetails details) {
|
||||
if (!isZip(details.getFile())) {
|
||||
details.exclude();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isZip(File file) {
|
||||
try {
|
||||
try (FileInputStream fileInputStream = new FileInputStream(file)) {
|
||||
return isZip(fileInputStream);
|
||||
@@ -160,6 +202,17 @@ class BootArchiveSupport {
|
||||
this.exclusions.setExcludes(excludes);
|
||||
}
|
||||
|
||||
void moveModuleInfoToRoot(CopySpec spec) {
|
||||
spec.filesMatching("module-info.class", BootArchiveSupport::moveToRoot);
|
||||
}
|
||||
|
||||
private static void moveToRoot(FileCopyDetails details) {
|
||||
details.setRelativePath(details.getRelativeSourcePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link CopyAction} variant that sorts entries to ensure reproducible ordering.
|
||||
*/
|
||||
private static final class ReproducibleOrderingCopyAction implements CopyAction {
|
||||
|
||||
private final CopyAction delegate;
|
||||
|
||||
@@ -16,45 +16,24 @@
|
||||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.artifacts.ArtifactCollection;
|
||||
import org.gradle.api.artifacts.ResolvableDependencies;
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.file.CopySpec;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.file.FileTreeElement;
|
||||
import org.gradle.api.internal.file.copy.CopyAction;
|
||||
import org.gradle.api.java.archives.Attributes;
|
||||
import org.gradle.api.specs.Spec;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Layers;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCoordinates;
|
||||
import org.springframework.boot.loader.tools.layer.CustomLayers;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.gradle.util.ConfigureUtil;
|
||||
|
||||
/**
|
||||
* A custom {@link Jar} task that produces a Spring Boot executable jar.
|
||||
@@ -62,82 +41,88 @@ import org.springframework.util.FileCopyUtils;
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class BootJar extends Jar implements BootArchive {
|
||||
|
||||
private final BootArchiveSupport support = new BootArchiveSupport("org.springframework.boot.loader.JarLauncher",
|
||||
this::resolveZipCompression);
|
||||
private static final String LAUNCHER = "org.springframework.boot.loader.JarLauncher";
|
||||
|
||||
private final CopySpec bootInf;
|
||||
private static final String CLASSES_FOLDER = "BOOT-INF/classes/";
|
||||
|
||||
private static final String LIB_FOLDER = "BOOT-INF/lib/";
|
||||
|
||||
private static final String LAYERS_INDEX = "BOOT-INF/layers.idx";
|
||||
|
||||
private static final String CLASSPATH_INDEX = "BOOT-INF/classpath.idx";
|
||||
|
||||
private final BootArchiveSupport support;
|
||||
|
||||
private final CopySpec bootInfSpec;
|
||||
|
||||
private String mainClassName;
|
||||
|
||||
private FileCollection classpath;
|
||||
|
||||
private Layers layers;
|
||||
|
||||
private LayerConfiguration layerConfiguration;
|
||||
|
||||
private static final String BOOT_INF_LAYERS = "BOOT-INF/layers";
|
||||
|
||||
private final List<String> dependencies = new ArrayList<>();
|
||||
|
||||
private final Map<String, String> coordinatesByFileName = new HashMap<>();
|
||||
private LayeredSpec layered;
|
||||
|
||||
/**
|
||||
* Creates a new {@code BootJar} task.
|
||||
*/
|
||||
public BootJar() {
|
||||
this.bootInf = getProject().copySpec().into("BOOT-INF");
|
||||
getMainSpec().with(this.bootInf);
|
||||
this.bootInf.into("classes", classpathFiles(File::isDirectory));
|
||||
this.bootInf.into("lib", classpathFiles(File::isFile))
|
||||
.eachFile((details) -> BootJar.this.dependencies.add(details.getPath()));
|
||||
this.bootInf.into("",
|
||||
(spec) -> spec.from((Callable<File>) () -> createClasspathIndex(BootJar.this.dependencies)));
|
||||
this.bootInf.filesMatching("module-info.class",
|
||||
(details) -> details.setRelativePath(details.getRelativeSourcePath()));
|
||||
getRootSpec().eachFile((details) -> {
|
||||
String pathString = details.getRelativePath().getPathString();
|
||||
if (pathString.startsWith("BOOT-INF/lib/") && !this.support.isZip(details.getFile())) {
|
||||
details.exclude();
|
||||
}
|
||||
});
|
||||
this.support = new BootArchiveSupport(LAUNCHER, this::isLibrary, this::resolveZipCompression);
|
||||
this.bootInfSpec = getProject().copySpec().into("BOOT-INF");
|
||||
configureBootInfSpec(this.bootInfSpec);
|
||||
getMainSpec().with(this.bootInfSpec);
|
||||
}
|
||||
|
||||
private Action<CopySpec> classpathFiles(Spec<File> filter) {
|
||||
return (copySpec) -> copySpec.from((Callable<Iterable<File>>) () -> (this.classpath != null)
|
||||
? this.classpath.filter(filter) : Collections.emptyList());
|
||||
private void configureBootInfSpec(CopySpec bootInfSpec) {
|
||||
bootInfSpec.into("classes", fromCallTo(this::classpathDirectories));
|
||||
bootInfSpec.into("lib", fromCallTo(this::classpathFiles)).eachFile(this.support::excludeNonZipFiles);
|
||||
bootInfSpec.filesMatching("module-info.class",
|
||||
(details) -> details.setRelativePath(details.getRelativeSourcePath()));
|
||||
}
|
||||
|
||||
private Iterable<File> classpathDirectories() {
|
||||
return classpathEntries(File::isDirectory);
|
||||
}
|
||||
|
||||
private Iterable<File> classpathFiles() {
|
||||
return classpathEntries(File::isFile);
|
||||
}
|
||||
|
||||
private Iterable<File> classpathEntries(Spec<File> filter) {
|
||||
return (this.classpath != null) ? this.classpath.filter(filter) : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy() {
|
||||
this.support.configureManifest(this, getMainClassName(), "BOOT-INF/classes/", "BOOT-INF/lib/");
|
||||
Attributes attributes = this.getManifest().getAttributes();
|
||||
if (this.layers != null) {
|
||||
attributes.remove("Spring-Boot-Classes");
|
||||
attributes.remove("Spring-Boot-Lib");
|
||||
attributes.putIfAbsent("Spring-Boot-Layers-Index", "BOOT-INF/layers.idx");
|
||||
if (this.layered != null) {
|
||||
this.support.configureManifest(getManifest(), getMainClassName(), null, null, CLASSPATH_INDEX,
|
||||
LAYERS_INDEX);
|
||||
}
|
||||
else {
|
||||
this.support.configureManifest(getManifest(), getMainClassName(), CLASSES_FOLDER, LIB_FOLDER,
|
||||
CLASSPATH_INDEX, null);
|
||||
}
|
||||
attributes.putIfAbsent("Spring-Boot-Classpath-Index", "BOOT-INF/classpath.idx");
|
||||
super.copy();
|
||||
}
|
||||
|
||||
private File createClasspathIndex(List<String> dependencies) {
|
||||
String content = dependencies.stream().map((name) -> name.substring(name.lastIndexOf('/') + 1))
|
||||
.collect(Collectors.joining("\n", "", "\n"));
|
||||
File source = getProject().getResources().getText().fromString(content).asFile();
|
||||
File indexFile = new File(source.getParentFile(), "classpath.idx");
|
||||
source.renameTo(indexFile);
|
||||
return indexFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CopyAction createCopyAction() {
|
||||
if (this.layered != null) {
|
||||
LayerResolver layerResolver = new LayerResolver(getConfigurations(), this.layered, this::isLibrary);
|
||||
boolean includeLayerTools = this.layered.isIncludeLayerTools();
|
||||
return this.support.createCopyAction(this, layerResolver, includeLayerTools);
|
||||
}
|
||||
return this.support.createCopyAction(this);
|
||||
}
|
||||
|
||||
@Internal
|
||||
protected Iterable<Configuration> getConfigurations() {
|
||||
return getProject().getConfigurations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMainClassName() {
|
||||
if (this.mainClassName == null) {
|
||||
@@ -179,110 +164,28 @@ public class BootJar extends Jar implements BootArchive {
|
||||
action.execute(enableLaunchScriptIfNecessary());
|
||||
}
|
||||
|
||||
@Optional
|
||||
@Nested
|
||||
public LayerConfiguration getLayerConfiguration() {
|
||||
return this.layerConfiguration;
|
||||
@Optional
|
||||
public LayeredSpec getLayered() {
|
||||
return this.layered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the archive to have layers.
|
||||
*/
|
||||
public void layers() {
|
||||
enableLayers();
|
||||
public void layered() {
|
||||
layered(true);
|
||||
}
|
||||
|
||||
public void layers(Action<LayerConfiguration> action) {
|
||||
action.execute(enableLayers());
|
||||
public void layered(boolean layered) {
|
||||
this.layered = layered ? new LayeredSpec() : null;
|
||||
}
|
||||
|
||||
private LayerConfiguration enableLayers() {
|
||||
if (this.layerConfiguration == null) {
|
||||
this.layerConfiguration = new LayerConfiguration();
|
||||
}
|
||||
|
||||
return this.layerConfiguration;
|
||||
public void layered(Closure<?> closure) {
|
||||
layered(ConfigureUtil.configureUsing(closure));
|
||||
}
|
||||
|
||||
private void applyLayers() {
|
||||
if (this.layerConfiguration == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.layerConfiguration.getLayersOrder() == null || this.layerConfiguration.getLayersOrder().isEmpty()) {
|
||||
this.layers = Layers.IMPLICIT;
|
||||
}
|
||||
else {
|
||||
List<Layer> customLayers = this.layerConfiguration.getLayersOrder().stream().map(Layer::new)
|
||||
.collect(Collectors.toList());
|
||||
this.layers = new CustomLayers(customLayers, this.layerConfiguration.getApplication(),
|
||||
this.layerConfiguration.getLibraries());
|
||||
}
|
||||
|
||||
if (this.layerConfiguration.isIncludeLayerTools()) {
|
||||
this.bootInf.into("lib", (spec) -> spec.from((Callable<File>) () -> {
|
||||
String jarName = "spring-boot-jarmode-layertools.jar";
|
||||
InputStream stream = getClass().getClassLoader().getResourceAsStream("META-INF/jarmode/" + jarName);
|
||||
File taskTmp = new File(getProject().getBuildDir(), "tmp/" + getName());
|
||||
taskTmp.mkdirs();
|
||||
File layerToolsJar = new File(taskTmp, jarName);
|
||||
FileCopyUtils.copy(stream, new FileOutputStream(layerToolsJar));
|
||||
return layerToolsJar;
|
||||
}));
|
||||
}
|
||||
this.bootInf.eachFile((details) -> {
|
||||
Layer layer = layerForFileDetails(details);
|
||||
if (layer != null) {
|
||||
String relativePath = details.getPath().substring("BOOT-INF/".length());
|
||||
details.setPath(BOOT_INF_LAYERS + "/" + layer + "/" + relativePath);
|
||||
}
|
||||
}).setIncludeEmptyDirs(false);
|
||||
this.bootInf.into("", (spec) -> spec.from(createLayersIndex()));
|
||||
}
|
||||
|
||||
private Layer layerForFileDetails(FileCopyDetails details) {
|
||||
String path = details.getPath();
|
||||
if (path.startsWith("BOOT-INF/lib/")) {
|
||||
String coordinates = this.coordinatesByFileName.get(details.getName());
|
||||
LibraryCoordinates libraryCoordinates = (coordinates != null) ? new LibraryCoordinates(coordinates)
|
||||
: new LibraryCoordinates("?:?:?");
|
||||
return this.layers.getLayer(new Library(null, details.getFile(), null, libraryCoordinates, false));
|
||||
}
|
||||
if (path.startsWith("BOOT-INF/classes/")) {
|
||||
return this.layers.getLayer(details.getSourcePath());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private File createLayersIndex() {
|
||||
try {
|
||||
StringWriter content = new StringWriter();
|
||||
BufferedWriter writer = new BufferedWriter(content);
|
||||
for (Layer layer : this.layers) {
|
||||
writer.write(layer.toString());
|
||||
writer.write("\n");
|
||||
}
|
||||
writer.flush();
|
||||
File source = getProject().getResources().getText().fromString(content.toString()).asFile();
|
||||
File indexFile = new File(source.getParentFile(), "layers.idx");
|
||||
source.renameTo(indexFile);
|
||||
return indexFile;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException("Failed to create layers.idx", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveCoordinatesForFiles(ResolvableDependencies resolvableDependencies) {
|
||||
ArtifactCollection resolvedArtifactResults = resolvableDependencies.getArtifacts();
|
||||
Set<ResolvedArtifactResult> artifacts = resolvedArtifactResults.getArtifacts();
|
||||
artifacts.forEach((artifact) -> this.coordinatesByFileName.put(artifact.getFile().getName(),
|
||||
artifact.getId().getComponentIdentifier().getDisplayName()));
|
||||
}
|
||||
|
||||
@Input
|
||||
boolean isLayered() {
|
||||
return this.layerConfiguration != null;
|
||||
public void layered(Action<LayeredSpec> action) {
|
||||
LayeredSpec layered = new LayeredSpec();
|
||||
action.execute(layered);
|
||||
this.layered = layered;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -317,13 +220,6 @@ public class BootJar extends Jar implements BootArchive {
|
||||
this.support.setExcludeDevtools(excludeDevtools);
|
||||
}
|
||||
|
||||
public void resolvedDependencies(ResolvableDependencies resolvableDependencies) {
|
||||
if (resolvableDependencies != null) {
|
||||
resolveCoordinatesForFiles(resolvableDependencies);
|
||||
}
|
||||
applyLayers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code CopySpec} that can be used to add content to the {@code BOOT-INF}
|
||||
* directory of the jar.
|
||||
@@ -333,7 +229,7 @@ public class BootJar extends Jar implements BootArchive {
|
||||
@Internal
|
||||
public CopySpec getBootInf() {
|
||||
CopySpec child = getProject().copySpec();
|
||||
this.bootInf.with(child);
|
||||
this.bootInfSpec.with(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
@@ -352,30 +248,26 @@ public class BootJar extends Jar implements BootArchive {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ZipCompression} that should be used when adding the file
|
||||
* represented by the given {@code details} to the jar.
|
||||
* <p>
|
||||
* By default, any file in {@code BOOT-INF/lib/} or
|
||||
* {@code BOOT-INF/layers/<layer>/lib} is stored and all other files are deflated.
|
||||
* @param details the details
|
||||
* Return the {@link ZipCompression} that should be used when adding the file
|
||||
* represented by the given {@code details} to the jar. By default, any
|
||||
* {@link #isLibrary(FileCopyDetails) library} is {@link ZipCompression#STORED stored}
|
||||
* and all other files are {@link ZipCompression#DEFLATED deflated}.
|
||||
* @param details the file copy details
|
||||
* @return the compression to use
|
||||
*/
|
||||
protected ZipCompression resolveZipCompression(FileCopyDetails details) {
|
||||
String path = details.getRelativePath().getPathString();
|
||||
for (String prefix : getLibPathPrefixes()) {
|
||||
if (path.startsWith(prefix)) {
|
||||
return ZipCompression.STORED;
|
||||
}
|
||||
}
|
||||
return ZipCompression.DEFLATED;
|
||||
return isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED;
|
||||
}
|
||||
|
||||
private Set<String> getLibPathPrefixes() {
|
||||
if (this.layers == null) {
|
||||
return Collections.singleton("BOOT-INF/lib/");
|
||||
}
|
||||
return StreamSupport.stream(this.layers.spliterator(), false)
|
||||
.map((layer) -> "BOOT-INF/layers/" + layer + "/lib/").collect(Collectors.toSet());
|
||||
/**
|
||||
* Return if the {@link FileCopyDetails} are for a library. By default any file in
|
||||
* {@code BOOT-INF/lib} is considered to be a library.
|
||||
* @param details the file copy details
|
||||
* @return {@code true} if the details are for a library
|
||||
*/
|
||||
protected boolean isLibrary(FileCopyDetails details) {
|
||||
String path = details.getRelativePath().getPathString();
|
||||
return path.startsWith(LIB_FOLDER);
|
||||
}
|
||||
|
||||
private LaunchScriptConfiguration enableLaunchScriptIfNecessary() {
|
||||
@@ -387,4 +279,24 @@ public class BootJar extends Jar implements BootArchive {
|
||||
return launchScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
* @param callable the callable
|
||||
* @return an action to add the callable to the spec
|
||||
*/
|
||||
private static <T> Action<CopySpec> fromCallTo(Callable<T> callable) {
|
||||
return (spec) -> spec.from(callTo(callable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#from} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
* @param callable the callable
|
||||
* @return the callable
|
||||
*/
|
||||
private static <T> Callable<T> callTo(Callable<T> callable) {
|
||||
return callable;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.CopySpec;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.file.FileTreeElement;
|
||||
@@ -35,12 +35,20 @@ import org.gradle.api.tasks.bundling.War;
|
||||
* A custom {@link War} task that produces a Spring Boot executable war.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class BootWar extends War implements BootArchive {
|
||||
|
||||
private final BootArchiveSupport support = new BootArchiveSupport("org.springframework.boot.loader.WarLauncher",
|
||||
this::resolveZipCompression);
|
||||
private static final String LAUNCHER = "org.springframework.boot.loader.WarLauncher";
|
||||
|
||||
private static final String CLASSES_FOLDER = "WEB-INF/classes/";
|
||||
|
||||
private static final String LIB_PROVIDED_FOLDER = "WEB-INF/lib-provided/";
|
||||
|
||||
private static final String LIB_FOLDER = "WEB-INF/lib/";
|
||||
|
||||
private final BootArchiveSupport support;
|
||||
|
||||
private String mainClassName;
|
||||
|
||||
@@ -50,23 +58,19 @@ public class BootWar extends War implements BootArchive {
|
||||
* Creates a new {@code BootWar} task.
|
||||
*/
|
||||
public BootWar() {
|
||||
getWebInf().into("lib-provided",
|
||||
(copySpec) -> copySpec.from((Callable<Iterable<File>>) () -> (this.providedClasspath != null)
|
||||
? this.providedClasspath : Collections.emptyList()));
|
||||
getRootSpec().filesMatching("module-info.class",
|
||||
(details) -> details.setRelativePath(details.getRelativeSourcePath()));
|
||||
getRootSpec().eachFile((details) -> {
|
||||
String pathString = details.getRelativePath().getPathString();
|
||||
if ((pathString.startsWith("WEB-INF/lib/") || pathString.startsWith("WEB-INF/lib-provided/"))
|
||||
&& !this.support.isZip(details.getFile())) {
|
||||
details.exclude();
|
||||
}
|
||||
});
|
||||
this.support = new BootArchiveSupport(LAUNCHER, this::isLibrary, this::resolveZipCompression);
|
||||
getWebInf().into("lib-provided", fromCallTo(this::getProvidedLibFiles));
|
||||
this.support.moveModuleInfoToRoot(getRootSpec());
|
||||
getRootSpec().eachFile(this.support::excludeNonZipLibraryFiles);
|
||||
}
|
||||
|
||||
private Object getProvidedLibFiles() {
|
||||
return (this.providedClasspath != null) ? this.providedClasspath : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy() {
|
||||
this.support.configureManifest(this, getMainClassName(), "WEB-INF/classes/", "WEB-INF/lib/");
|
||||
this.support.configureManifest(getManifest(), getMainClassName(), CLASSES_FOLDER, LIB_FOLDER, null, null);
|
||||
super.copy();
|
||||
}
|
||||
|
||||
@@ -171,20 +175,26 @@ public class BootWar extends War implements BootArchive {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ZipCompression} that should be used when adding the file
|
||||
* represented by the given {@code details} to the jar.
|
||||
* <p>
|
||||
* By default, any file in {@code WEB-INF/lib/} or {@code WEB-INF/lib-provided/} is
|
||||
* stored and all other files are deflated.
|
||||
* @param details the details
|
||||
* Return the {@link ZipCompression} that should be used when adding the file
|
||||
* represented by the given {@code details} to the jar. By default, any
|
||||
* {@link #isLibrary(FileCopyDetails) library} is {@link ZipCompression#STORED stored}
|
||||
* and all other files are {@link ZipCompression#DEFLATED deflated}.
|
||||
* @param details the file copy details
|
||||
* @return the compression to use
|
||||
*/
|
||||
protected ZipCompression resolveZipCompression(FileCopyDetails details) {
|
||||
String relativePath = details.getRelativePath().getPathString();
|
||||
if (relativePath.startsWith("WEB-INF/lib/") || relativePath.startsWith("WEB-INF/lib-provided/")) {
|
||||
return ZipCompression.STORED;
|
||||
}
|
||||
return ZipCompression.DEFLATED;
|
||||
return isLibrary(details) ? ZipCompression.STORED : ZipCompression.DEFLATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param details the file copy details
|
||||
* @return {@code true} if the details are for a library
|
||||
*/
|
||||
protected boolean isLibrary(FileCopyDetails details) {
|
||||
String path = details.getRelativePath().getPathString();
|
||||
return path.startsWith(LIB_FOLDER) || path.startsWith(LIB_PROVIDED_FOLDER);
|
||||
}
|
||||
|
||||
private LaunchScriptConfiguration enableLaunchScriptIfNecessary() {
|
||||
@@ -196,4 +206,24 @@ public class BootWar extends War implements BootArchive {
|
||||
return launchScript;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#into} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
* @param callable the callable
|
||||
* @return an action to add the callable to the spec
|
||||
*/
|
||||
private static <T> Action<CopySpec> fromCallTo(Callable<T> callable) {
|
||||
return (spec) -> spec.from(callTo(callable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntactic sugar that makes {@link CopySpec#from} calls a little easier to read.
|
||||
* @param <T> the result type
|
||||
* @param callable the callable
|
||||
* @return the callable
|
||||
*/
|
||||
private static <T> Callable<T> callTo(Callable<T> callable) {
|
||||
return callable;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,10 +19,15 @@ package org.springframework.boot.gradle.tasks.bundling;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.zip.CRC32;
|
||||
|
||||
@@ -34,11 +39,16 @@ import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.file.FileTreeElement;
|
||||
import org.gradle.api.internal.file.copy.CopyAction;
|
||||
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
|
||||
import org.gradle.api.java.archives.Attributes;
|
||||
import org.gradle.api.java.archives.Manifest;
|
||||
import org.gradle.api.specs.Spec;
|
||||
import org.gradle.api.tasks.WorkResult;
|
||||
import org.gradle.api.tasks.WorkResults;
|
||||
|
||||
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
||||
import org.springframework.boot.loader.tools.FileUtils;
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* A {@link CopyAction} for creating a Spring Boot zip archive (typically a jar or war).
|
||||
@@ -49,69 +59,84 @@ import org.springframework.boot.loader.tools.FileUtils;
|
||||
*/
|
||||
class BootZipCopyAction implements CopyAction {
|
||||
|
||||
static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = new GregorianCalendar(1980, Calendar.FEBRUARY, 1, 0, 0, 0)
|
||||
.getTimeInMillis();
|
||||
static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = OffsetDateTime.of(1980, 2, 1, 0, 0, 0, 0, ZoneOffset.UTC)
|
||||
.toInstant().toEpochMilli();
|
||||
|
||||
private final File output;
|
||||
|
||||
private final Manifest manifest;
|
||||
|
||||
private final boolean preserveFileTimestamps;
|
||||
|
||||
private final boolean includeDefaultLoader;
|
||||
|
||||
private final boolean includeLayerTools;
|
||||
|
||||
private final Spec<FileTreeElement> requiresUnpack;
|
||||
|
||||
private final Spec<FileTreeElement> exclusions;
|
||||
|
||||
private final LaunchScriptConfiguration launchScript;
|
||||
|
||||
private final Spec<FileCopyDetails> librarySpec;
|
||||
|
||||
private final Function<FileCopyDetails, ZipCompression> compressionResolver;
|
||||
|
||||
private final String encoding;
|
||||
|
||||
BootZipCopyAction(File output, boolean preserveFileTimestamps, boolean includeDefaultLoader,
|
||||
Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
|
||||
LaunchScriptConfiguration launchScript, Function<FileCopyDetails, ZipCompression> compressionResolver,
|
||||
String encoding) {
|
||||
private final LayerResolver layerResolver;
|
||||
|
||||
BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, boolean includeDefaultLoader,
|
||||
boolean includeLayerTools, Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
|
||||
LaunchScriptConfiguration launchScript, Spec<FileCopyDetails> librarySpec,
|
||||
Function<FileCopyDetails, ZipCompression> compressionResolver, String encoding,
|
||||
LayerResolver layerResolver) {
|
||||
this.output = output;
|
||||
this.manifest = manifest;
|
||||
this.preserveFileTimestamps = preserveFileTimestamps;
|
||||
this.includeDefaultLoader = includeDefaultLoader;
|
||||
this.includeLayerTools = includeLayerTools;
|
||||
this.requiresUnpack = requiresUnpack;
|
||||
this.exclusions = exclusions;
|
||||
this.launchScript = launchScript;
|
||||
this.librarySpec = librarySpec;
|
||||
this.compressionResolver = compressionResolver;
|
||||
this.encoding = encoding;
|
||||
this.layerResolver = layerResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorkResult execute(CopyActionProcessingStream stream) {
|
||||
public WorkResult execute(CopyActionProcessingStream copyActions) {
|
||||
try {
|
||||
writeArchive(stream);
|
||||
return () -> true;
|
||||
writeArchive(copyActions);
|
||||
return WorkResults.didWork(true);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new GradleException("Failed to create " + this.output, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeArchive(CopyActionProcessingStream stream) throws IOException {
|
||||
OutputStream outputStream = new FileOutputStream(this.output);
|
||||
private void writeArchive(CopyActionProcessingStream copyActions) throws IOException {
|
||||
OutputStream output = new FileOutputStream(this.output);
|
||||
try {
|
||||
writeLaunchScriptIfNecessary(outputStream);
|
||||
ZipArchiveOutputStream zipOutputStream = new ZipArchiveOutputStream(outputStream);
|
||||
try {
|
||||
if (this.encoding != null) {
|
||||
zipOutputStream.setEncoding(this.encoding);
|
||||
}
|
||||
Processor processor = new Processor(zipOutputStream);
|
||||
stream.process(processor::process);
|
||||
processor.finish();
|
||||
}
|
||||
finally {
|
||||
closeQuietly(zipOutputStream);
|
||||
}
|
||||
writeArchive(copyActions, output);
|
||||
}
|
||||
finally {
|
||||
closeQuietly(outputStream);
|
||||
closeQuietly(output);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeArchive(CopyActionProcessingStream copyActions, OutputStream output) throws IOException {
|
||||
writeLaunchScriptIfNecessary(output);
|
||||
ZipArchiveOutputStream zipOutput = new ZipArchiveOutputStream(output);
|
||||
try {
|
||||
setEncodingIfNecessary(zipOutput);
|
||||
Processor processor = new Processor(zipOutput);
|
||||
copyActions.process(processor::process);
|
||||
processor.finish();
|
||||
}
|
||||
finally {
|
||||
closeQuietly(zipOutput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +156,12 @@ class BootZipCopyAction implements CopyAction {
|
||||
}
|
||||
}
|
||||
|
||||
private void setEncodingIfNecessary(ZipArchiveOutputStream zipOutputStream) {
|
||||
if (this.encoding != null) {
|
||||
zipOutputStream.setEncoding(this.encoding);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeQuietly(OutputStream outputStream) {
|
||||
try {
|
||||
outputStream.close();
|
||||
@@ -144,17 +175,20 @@ class BootZipCopyAction implements CopyAction {
|
||||
*/
|
||||
private class Processor {
|
||||
|
||||
private ZipArchiveOutputStream outputStream;
|
||||
private ZipArchiveOutputStream out;
|
||||
|
||||
private Spec<FileTreeElement> writtenLoaderEntries;
|
||||
|
||||
Processor(ZipArchiveOutputStream outputStream) {
|
||||
this.outputStream = outputStream;
|
||||
private Set<String> writtenDirectories = new LinkedHashSet<>();
|
||||
|
||||
private Set<String> writtenLibraries = new LinkedHashSet<>();
|
||||
|
||||
Processor(ZipArchiveOutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
void process(FileCopyDetails details) {
|
||||
if (BootZipCopyAction.this.exclusions.isSatisfiedBy(details)
|
||||
|| (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isSatisfiedBy(details))) {
|
||||
if (skipProcessing(details)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -171,8 +205,90 @@ class BootZipCopyAction implements CopyAction {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean skipProcessing(FileCopyDetails details) {
|
||||
return BootZipCopyAction.this.exclusions.isSatisfiedBy(details)
|
||||
|| (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isSatisfiedBy(details));
|
||||
}
|
||||
|
||||
private void processDirectory(FileCopyDetails details) throws IOException {
|
||||
String name = getEntryName(details);
|
||||
long time = getTime(details);
|
||||
writeParentDirectoriesIfNecessary(name, time);
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(name + '/');
|
||||
entry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
|
||||
entry.setTime(time);
|
||||
this.out.putArchiveEntry(entry);
|
||||
this.out.closeArchiveEntry();
|
||||
this.writtenDirectories.add(name);
|
||||
}
|
||||
|
||||
private void processFile(FileCopyDetails details) throws IOException {
|
||||
String name = getEntryName(details);
|
||||
long time = getTime(details);
|
||||
writeParentDirectoriesIfNecessary(name, time);
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(name);
|
||||
entry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
|
||||
entry.setTime(time);
|
||||
ZipCompression compression = BootZipCopyAction.this.compressionResolver.apply(details);
|
||||
if (compression == ZipCompression.STORED) {
|
||||
prepareStoredEntry(details, entry);
|
||||
}
|
||||
this.out.putArchiveEntry(entry);
|
||||
details.copyTo(this.out);
|
||||
this.out.closeArchiveEntry();
|
||||
if (BootZipCopyAction.this.librarySpec.isSatisfiedBy(details)) {
|
||||
this.writtenLibraries.add(name.substring(name.lastIndexOf('/') + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeParentDirectoriesIfNecessary(String name, long time) throws IOException {
|
||||
String parentDirectory = getParentDirectory(name);
|
||||
if (parentDirectory != null && this.writtenDirectories.add(parentDirectory)) {
|
||||
writeParentDirectoriesIfNecessary(parentDirectory, time);
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(parentDirectory + '/');
|
||||
entry.setUnixMode(UnixStat.DIR_FLAG);
|
||||
entry.setTime(time);
|
||||
this.out.putArchiveEntry(entry);
|
||||
this.out.closeArchiveEntry();
|
||||
}
|
||||
}
|
||||
|
||||
private String getParentDirectory(String name) {
|
||||
int lastSlash = name.lastIndexOf('/');
|
||||
if (lastSlash == -1) {
|
||||
return null;
|
||||
}
|
||||
return name.substring(0, lastSlash);
|
||||
}
|
||||
|
||||
private String getEntryName(FileCopyDetails details) {
|
||||
if (BootZipCopyAction.this.layerResolver == null) {
|
||||
return details.getRelativePath().getPathString();
|
||||
}
|
||||
return BootZipCopyAction.this.layerResolver.getPath(details);
|
||||
}
|
||||
|
||||
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
|
||||
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
||||
archiveEntry.setSize(details.getSize());
|
||||
archiveEntry.setCompressedSize(details.getSize());
|
||||
archiveEntry.setCrc(getCrc(details));
|
||||
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
|
||||
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
|
||||
}
|
||||
}
|
||||
|
||||
private long getCrc(FileCopyDetails details) {
|
||||
Crc32OutputStream crcStream = new Crc32OutputStream();
|
||||
details.copyTo(crcStream);
|
||||
return crcStream.getCrc();
|
||||
}
|
||||
|
||||
void finish() throws IOException {
|
||||
writeLoaderEntriesIfNecessary(null);
|
||||
writeJarToolsIfNecessary();
|
||||
writeLayersIndexIfNecessary();
|
||||
writeClassPathIndexIfNecessary();
|
||||
}
|
||||
|
||||
private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOException {
|
||||
@@ -183,9 +299,9 @@ class BootZipCopyAction implements CopyAction {
|
||||
// Don't write loader entries until after META-INF folder (see gh-16698)
|
||||
return;
|
||||
}
|
||||
LoaderZipEntries loaderEntries = new LoaderZipEntries(
|
||||
LoaderZipEntries entries = new LoaderZipEntries(
|
||||
BootZipCopyAction.this.preserveFileTimestamps ? null : CONSTANT_TIME_FOR_ZIP_ENTRIES);
|
||||
this.writtenLoaderEntries = loaderEntries.writeTo(this.outputStream);
|
||||
this.writtenLoaderEntries = entries.writeTo(this.out);
|
||||
}
|
||||
|
||||
private boolean isInMetaInf(FileCopyDetails details) {
|
||||
@@ -196,40 +312,47 @@ class BootZipCopyAction implements CopyAction {
|
||||
return segments.length > 0 && "META-INF".equals(segments[0]);
|
||||
}
|
||||
|
||||
private void processDirectory(FileCopyDetails details) throws IOException {
|
||||
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(details.getRelativePath().getPathString() + '/');
|
||||
archiveEntry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
|
||||
archiveEntry.setTime(getTime(details));
|
||||
this.outputStream.putArchiveEntry(archiveEntry);
|
||||
this.outputStream.closeArchiveEntry();
|
||||
private void writeJarToolsIfNecessary() throws IOException {
|
||||
if (BootZipCopyAction.this.layerResolver == null || !BootZipCopyAction.this.includeLayerTools) {
|
||||
return;
|
||||
}
|
||||
writeJarModeLibrary(JarModeLibrary.LAYER_TOOLS);
|
||||
}
|
||||
|
||||
private void processFile(FileCopyDetails details) throws IOException {
|
||||
String relativePath = details.getRelativePath().getPathString();
|
||||
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(relativePath);
|
||||
archiveEntry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
|
||||
archiveEntry.setTime(getTime(details));
|
||||
ZipCompression compression = BootZipCopyAction.this.compressionResolver.apply(details);
|
||||
if (compression == ZipCompression.STORED) {
|
||||
prepareStoredEntry(details, archiveEntry);
|
||||
}
|
||||
this.outputStream.putArchiveEntry(archiveEntry);
|
||||
details.copyTo(this.outputStream);
|
||||
this.outputStream.closeArchiveEntry();
|
||||
private void writeJarModeLibrary(JarModeLibrary jarModeLibrary) throws IOException {
|
||||
String name = BootZipCopyAction.this.layerResolver.getPath(jarModeLibrary);
|
||||
writeFile(name, ZipEntryWriter.fromInputStream(jarModeLibrary.openStream()));
|
||||
}
|
||||
|
||||
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
|
||||
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
||||
archiveEntry.setSize(details.getSize());
|
||||
archiveEntry.setCompressedSize(details.getSize());
|
||||
Crc32OutputStream crcStream = new Crc32OutputStream();
|
||||
details.copyTo(crcStream);
|
||||
archiveEntry.setCrc(crcStream.getCrc());
|
||||
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
|
||||
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
|
||||
private void writeLayersIndexIfNecessary() throws IOException {
|
||||
Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes();
|
||||
String layersIndex = (String) manifestAttributes.get("Spring-Boot-Layers-Index");
|
||||
if (layersIndex != null && BootZipCopyAction.this.layerResolver != null) {
|
||||
writeFile(layersIndex, ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding,
|
||||
BootZipCopyAction.this.layerResolver.getLayerNames()));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeClassPathIndexIfNecessary() throws IOException {
|
||||
Attributes manifestAttributes = BootZipCopyAction.this.manifest.getAttributes();
|
||||
String classPathIndex = (String) manifestAttributes.get("Spring-Boot-Classpath-Index");
|
||||
if (classPathIndex != null) {
|
||||
writeFile(classPathIndex,
|
||||
ZipEntryWriter.fromLines(BootZipCopyAction.this.encoding, this.writtenLibraries));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFile(String name, ZipEntryWriter entryWriter) throws IOException {
|
||||
writeParentDirectoriesIfNecessary(name, CONSTANT_TIME_FOR_ZIP_ENTRIES);
|
||||
ZipArchiveEntry entry = new ZipArchiveEntry(name);
|
||||
entry.setUnixMode(UnixStat.FILE_FLAG);
|
||||
entry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES);
|
||||
this.out.putArchiveEntry(entry);
|
||||
entryWriter.writeTo(entry, this.out);
|
||||
this.out.closeArchiveEntry();
|
||||
|
||||
}
|
||||
|
||||
private long getTime(FileCopyDetails details) {
|
||||
return BootZipCopyAction.this.preserveFileTimestamps ? details.getLastModified()
|
||||
: CONSTANT_TIME_FOR_ZIP_ENTRIES;
|
||||
@@ -237,6 +360,52 @@ class BootZipCopyAction implements CopyAction {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to write a zip entry data.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
private interface ZipEntryWriter {
|
||||
|
||||
/**
|
||||
* Write the entry data.
|
||||
* @param entry the entry being written
|
||||
* @param out the output stream used to write the data
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
void writeTo(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException;
|
||||
|
||||
/**
|
||||
* Create a new {@link ZipEntryWriter} that will copy content from the given
|
||||
* {@link InputStream}.
|
||||
* @param in the source input stream
|
||||
* @return a new {@link ZipEntryWriter} instance
|
||||
*/
|
||||
static ZipEntryWriter fromInputStream(InputStream in) {
|
||||
return (entry, out) -> {
|
||||
StreamUtils.copy(in, out);
|
||||
in.close();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ZipEntryWriter} that will copy content from the given
|
||||
* lines.
|
||||
* @param encoding the required character encoding
|
||||
* @param lines the lines to write
|
||||
* @return a new {@link ZipEntryWriter} instance
|
||||
*/
|
||||
static ZipEntryWriter fromLines(String encoding, Collection<String> lines) {
|
||||
return (entry, out) -> {
|
||||
OutputStreamWriter writer = new OutputStreamWriter(out, encoding);
|
||||
for (String line : lines) {
|
||||
writer.append(line + "\n");
|
||||
}
|
||||
writer.flush();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@code OutputStream} that provides a CRC-32 of the data that is written to it.
|
||||
*/
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.tasks.Input;
|
||||
|
||||
import org.springframework.boot.loader.tools.layer.application.FilteredResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.application.LocationFilter;
|
||||
import org.springframework.boot.loader.tools.layer.application.ResourceFilter;
|
||||
import org.springframework.boot.loader.tools.layer.application.ResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.library.CoordinateFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.FilteredLibraryStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.library.LibraryFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.LibraryStrategy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Encapsulates the configuration for a layered jar.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class LayerConfiguration {
|
||||
|
||||
private boolean includeLayerTools = true;
|
||||
|
||||
private List<String> layersOrder = new ArrayList<>();
|
||||
|
||||
private List<ResourceStrategy> resourceStrategies = new ArrayList<>();
|
||||
|
||||
private List<LibraryStrategy> libraryStrategies = new ArrayList<>();
|
||||
|
||||
private StrategySpec strategySpec;
|
||||
|
||||
/**
|
||||
* Whether to include the layer tools jar.
|
||||
* @return true if layer tools is included
|
||||
*/
|
||||
@Input
|
||||
public boolean isIncludeLayerTools() {
|
||||
return this.includeLayerTools;
|
||||
}
|
||||
|
||||
public void setIncludeLayerTools(boolean includeLayerTools) {
|
||||
this.includeLayerTools = includeLayerTools;
|
||||
}
|
||||
|
||||
@Input
|
||||
public List<String> getLayersOrder() {
|
||||
return this.layersOrder;
|
||||
}
|
||||
|
||||
public void layersOrder(String... layers) {
|
||||
this.layersOrder = Arrays.asList(layers);
|
||||
}
|
||||
|
||||
public void layersOrder(List<String> layers) {
|
||||
this.layersOrder = layers;
|
||||
}
|
||||
|
||||
@Input
|
||||
public List<ResourceStrategy> getApplication() {
|
||||
return this.resourceStrategies;
|
||||
}
|
||||
|
||||
public void application(ResourceStrategy... resourceStrategies) {
|
||||
assertLayersOrderConfigured();
|
||||
this.resourceStrategies = Arrays.asList(resourceStrategies);
|
||||
}
|
||||
|
||||
public void application(Action<LayerConfiguration> config) {
|
||||
assertLayersOrderConfigured();
|
||||
this.strategySpec = StrategySpec.forResources();
|
||||
config.execute(this);
|
||||
}
|
||||
|
||||
@Input
|
||||
public List<LibraryStrategy> getLibraries() {
|
||||
return this.libraryStrategies;
|
||||
}
|
||||
|
||||
public void libraries(LibraryStrategy... strategies) {
|
||||
assertLayersOrderConfigured();
|
||||
this.libraryStrategies = Arrays.asList(strategies);
|
||||
}
|
||||
|
||||
public void libraries(Action<LayerConfiguration> configure) {
|
||||
assertLayersOrderConfigured();
|
||||
this.strategySpec = StrategySpec.forLibraries();
|
||||
configure.execute(this);
|
||||
}
|
||||
|
||||
private void assertLayersOrderConfigured() {
|
||||
Assert.state(!this.layersOrder.isEmpty(), "'layersOrder' must be configured before filters can be applied.");
|
||||
}
|
||||
|
||||
public void layerContent(String layerName, Action<LayerConfiguration> config) {
|
||||
this.strategySpec.newStrategy();
|
||||
config.execute(this);
|
||||
if (this.strategySpec.isLibrariesStrategy()) {
|
||||
this.libraryStrategies.add(new FilteredLibraryStrategy(layerName, this.strategySpec.libraryFilters()));
|
||||
}
|
||||
else {
|
||||
this.resourceStrategies.add(new FilteredResourceStrategy(layerName, this.strategySpec.resourceFilters()));
|
||||
}
|
||||
}
|
||||
|
||||
public void coordinates(Action<LayerConfiguration> config) {
|
||||
Assert.state(this.strategySpec.isLibrariesStrategy(),
|
||||
"The 'coordinates' filter must be used only with libraries");
|
||||
this.strategySpec.newFilter();
|
||||
config.execute(this);
|
||||
this.strategySpec
|
||||
.addLibraryFilter(new CoordinateFilter(this.strategySpec.includes(), this.strategySpec.excludes()));
|
||||
}
|
||||
|
||||
public void locations(Action<LayerConfiguration> config) {
|
||||
Assert.state(this.strategySpec.isResourcesStrategy(),
|
||||
"The 'locations' filter must be used only with application");
|
||||
this.strategySpec.newFilter();
|
||||
config.execute(this);
|
||||
this.strategySpec
|
||||
.addResourceFilter(new LocationFilter(this.strategySpec.includes(), this.strategySpec.excludes()));
|
||||
}
|
||||
|
||||
public void include(String... includes) {
|
||||
this.strategySpec.include(includes);
|
||||
}
|
||||
|
||||
public void exclude(String... excludes) {
|
||||
this.strategySpec.exclude(excludes);
|
||||
}
|
||||
|
||||
private static final class StrategySpec {
|
||||
|
||||
private final TYPE type;
|
||||
|
||||
private List<LibraryFilter> libraryFilters;
|
||||
|
||||
private List<ResourceFilter> resourceFilters;
|
||||
|
||||
private List<String> filterIncludes;
|
||||
|
||||
private List<String> filterExcludes;
|
||||
|
||||
private StrategySpec(TYPE type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private boolean isLibrariesStrategy() {
|
||||
return this.type == TYPE.LIBRARIES;
|
||||
}
|
||||
|
||||
private boolean isResourcesStrategy() {
|
||||
return this.type == TYPE.RESOURCES;
|
||||
}
|
||||
|
||||
private void newStrategy() {
|
||||
this.libraryFilters = new ArrayList<>();
|
||||
this.resourceFilters = new ArrayList<>();
|
||||
newFilter();
|
||||
}
|
||||
|
||||
private void newFilter() {
|
||||
this.filterIncludes = new ArrayList<>();
|
||||
this.filterExcludes = new ArrayList<>();
|
||||
}
|
||||
|
||||
private List<LibraryFilter> libraryFilters() {
|
||||
return this.libraryFilters;
|
||||
}
|
||||
|
||||
private void addLibraryFilter(LibraryFilter filter) {
|
||||
this.libraryFilters.add(filter);
|
||||
}
|
||||
|
||||
private List<ResourceFilter> resourceFilters() {
|
||||
return this.resourceFilters;
|
||||
}
|
||||
|
||||
private void addResourceFilter(ResourceFilter filter) {
|
||||
this.resourceFilters.add(filter);
|
||||
}
|
||||
|
||||
private List<String> includes() {
|
||||
return this.filterIncludes;
|
||||
}
|
||||
|
||||
private void include(String... includes) {
|
||||
this.filterIncludes.addAll(Arrays.asList(includes));
|
||||
}
|
||||
|
||||
private void exclude(String... excludes) {
|
||||
this.filterIncludes.addAll(Arrays.asList(excludes));
|
||||
}
|
||||
|
||||
private List<String> excludes() {
|
||||
return this.filterExcludes;
|
||||
}
|
||||
|
||||
private static StrategySpec forLibraries() {
|
||||
return new StrategySpec(TYPE.LIBRARIES);
|
||||
}
|
||||
|
||||
private static StrategySpec forResources() {
|
||||
return new StrategySpec(TYPE.RESOURCES);
|
||||
}
|
||||
|
||||
private enum TYPE {
|
||||
|
||||
LIBRARIES, RESOURCES;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.gradle.api.artifacts.ArtifactCollection;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.component.ComponentIdentifier;
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.specs.Spec;
|
||||
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Layers;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.LibraryCoordinates;
|
||||
|
||||
/**
|
||||
* Resolver backed by a {@link LayeredSpec} that provides the destination {@link Layer}
|
||||
* for each copied {@link FileCopyDetails}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
* @see BootZipCopyAction
|
||||
*/
|
||||
class LayerResolver {
|
||||
|
||||
private static final String BOOT_INF_FOLDER = "BOOT-INF/";
|
||||
|
||||
private final ResolvedDependencies resolvedDependencies;
|
||||
|
||||
private final LayeredSpec layeredConfiguration;
|
||||
|
||||
private final Spec<FileCopyDetails> librarySpec;
|
||||
|
||||
LayerResolver(Iterable<Configuration> configurations, LayeredSpec layeredConfiguration,
|
||||
Spec<FileCopyDetails> librarySpec) {
|
||||
this.resolvedDependencies = new ResolvedDependencies(configurations);
|
||||
this.layeredConfiguration = layeredConfiguration;
|
||||
this.librarySpec = librarySpec;
|
||||
}
|
||||
|
||||
String getPath(JarModeLibrary jarModeLibrary) {
|
||||
Layers layers = this.layeredConfiguration.asLayers();
|
||||
Layer layer = layers.getLayer(jarModeLibrary);
|
||||
if (layer != null) {
|
||||
return BOOT_INF_FOLDER + "layers/" + layer + "/lib/" + jarModeLibrary.getName();
|
||||
}
|
||||
return BOOT_INF_FOLDER + "lib/" + jarModeLibrary.getName();
|
||||
}
|
||||
|
||||
String getPath(FileCopyDetails details) {
|
||||
String path = details.getRelativePath().getPathString();
|
||||
Layer layer = getLayer(details);
|
||||
if (layer == null || !path.startsWith(BOOT_INF_FOLDER)) {
|
||||
return path;
|
||||
}
|
||||
path = path.substring(BOOT_INF_FOLDER.length());
|
||||
return BOOT_INF_FOLDER + "layers/" + layer + "/" + path;
|
||||
}
|
||||
|
||||
Layer getLayer(FileCopyDetails details) {
|
||||
Layers layers = this.layeredConfiguration.asLayers();
|
||||
try {
|
||||
if (this.librarySpec.isSatisfiedBy(details)) {
|
||||
return layers.getLayer(asLibrary(details));
|
||||
}
|
||||
return layers.getLayer(details.getSourcePath());
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> getLayerNames() {
|
||||
return this.layeredConfiguration.asLayers().stream().map(Layer::toString).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Library asLibrary(FileCopyDetails details) {
|
||||
File file = details.getFile();
|
||||
LibraryCoordinates coordinates = this.resolvedDependencies.find(file);
|
||||
return new Library(null, file, null, coordinates, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks and provides details of resolved dependencies in the project so we can find
|
||||
* {@link LibraryCoordinates}.
|
||||
*/
|
||||
private static class ResolvedDependencies {
|
||||
|
||||
private final Map<Configuration, ResolvedConfigurationDependencies> configurationDependencies = new LinkedHashMap<>();
|
||||
|
||||
ResolvedDependencies(Iterable<Configuration> configurations) {
|
||||
configurations.forEach(this::processConfiguration);
|
||||
}
|
||||
|
||||
private void processConfiguration(Configuration configuration) {
|
||||
if (configuration.isCanBeResolved()) {
|
||||
this.configurationDependencies.put(configuration,
|
||||
new ResolvedConfigurationDependencies(configuration.getIncoming().getArtifacts()));
|
||||
}
|
||||
}
|
||||
|
||||
LibraryCoordinates find(File file) {
|
||||
for (ResolvedConfigurationDependencies dependencies : this.configurationDependencies.values()) {
|
||||
LibraryCoordinates coordinates = dependencies.find(file);
|
||||
if (coordinates != null) {
|
||||
return coordinates;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores details of resolved configuration dependencies.
|
||||
*/
|
||||
private static class ResolvedConfigurationDependencies {
|
||||
|
||||
private final Map<File, LibraryCoordinates> artifactCoordinates = new LinkedHashMap<>();
|
||||
|
||||
ResolvedConfigurationDependencies(ArtifactCollection resolvedDependencies) {
|
||||
if (resolvedDependencies != null) {
|
||||
for (ResolvedArtifactResult resolvedArtifact : resolvedDependencies.getArtifacts()) {
|
||||
ComponentIdentifier identifier = resolvedArtifact.getId().getComponentIdentifier();
|
||||
if (identifier instanceof ModuleComponentIdentifier) {
|
||||
this.artifactCoordinates.put(resolvedArtifact.getFile(),
|
||||
new ModuleComponentIdentifierLibraryCoordinates(
|
||||
(ModuleComponentIdentifier) identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LibraryCoordinates find(File file) {
|
||||
return this.artifactCoordinates.get(file);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts a {@link ModuleComponentIdentifier} to {@link LibraryCoordinates}.
|
||||
*/
|
||||
private static class ModuleComponentIdentifierLibraryCoordinates implements LibraryCoordinates {
|
||||
|
||||
private final ModuleComponentIdentifier identifier;
|
||||
|
||||
ModuleComponentIdentifierLibraryCoordinates(ModuleComponentIdentifier identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupId() {
|
||||
return this.identifier.getGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArtifactId() {
|
||||
return this.identifier.getModule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return this.identifier.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.identifier.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.util.ConfigureUtil;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layer;
|
||||
import org.springframework.boot.loader.tools.Layers;
|
||||
import org.springframework.boot.loader.tools.Library;
|
||||
import org.springframework.boot.loader.tools.layer.ApplicationContentFilter;
|
||||
import org.springframework.boot.loader.tools.layer.ContentFilter;
|
||||
import org.springframework.boot.loader.tools.layer.ContentSelector;
|
||||
import org.springframework.boot.loader.tools.layer.CustomLayers;
|
||||
import org.springframework.boot.loader.tools.layer.IncludeExcludeContentSelector;
|
||||
import org.springframework.boot.loader.tools.layer.LibraryContentFilter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Encapsulates the configuration for a layered jar.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
* @author Phillip Webb
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class LayeredSpec {
|
||||
|
||||
private boolean includeLayerTools = true;
|
||||
|
||||
private ApplicationSpec application = new ApplicationSpec();
|
||||
|
||||
private DependenciesSpec dependencies = new DependenciesSpec();
|
||||
|
||||
@Optional
|
||||
private List<String> layerOrder;
|
||||
|
||||
private Layers layers;
|
||||
|
||||
@Input
|
||||
public boolean isIncludeLayerTools() {
|
||||
return this.includeLayerTools;
|
||||
}
|
||||
|
||||
public void setIncludeLayerTools(boolean includeLayerTools) {
|
||||
this.includeLayerTools = includeLayerTools;
|
||||
}
|
||||
|
||||
@Input
|
||||
public ApplicationSpec getApplication() {
|
||||
return this.application;
|
||||
}
|
||||
|
||||
public void application(ApplicationSpec spec) {
|
||||
this.application = spec;
|
||||
}
|
||||
|
||||
public void application(Closure<?> closure) {
|
||||
application(ConfigureUtil.configureUsing(closure));
|
||||
}
|
||||
|
||||
public void application(Action<ApplicationSpec> action) {
|
||||
action.execute(this.application);
|
||||
}
|
||||
|
||||
@Input
|
||||
public DependenciesSpec getDependencies() {
|
||||
return this.dependencies;
|
||||
}
|
||||
|
||||
public void dependencies(DependenciesSpec spec) {
|
||||
this.dependencies = spec;
|
||||
}
|
||||
|
||||
public void dependencies(Closure<?> closure) {
|
||||
dependencies(ConfigureUtil.configureUsing(closure));
|
||||
}
|
||||
|
||||
public void dependencies(Action<DependenciesSpec> action) {
|
||||
action.execute(this.dependencies);
|
||||
}
|
||||
|
||||
@Input
|
||||
public List<String> getLayerOrder() {
|
||||
return this.layerOrder;
|
||||
}
|
||||
|
||||
public void layerOrder(String... layerOrder) {
|
||||
this.layerOrder = Arrays.asList(layerOrder);
|
||||
}
|
||||
|
||||
public void layerOrder(List<String> layerOrder) {
|
||||
this.layerOrder = layerOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this configuration as a {@link Layers} instance. This method should only be
|
||||
* called when the configuration is complete and will no longer be changed.
|
||||
* @return the layers
|
||||
*/
|
||||
Layers asLayers() {
|
||||
Layers layers = this.layers;
|
||||
if (layers == null) {
|
||||
layers = createLayers();
|
||||
this.layers = layers;
|
||||
}
|
||||
return layers;
|
||||
}
|
||||
|
||||
private Layers createLayers() {
|
||||
if (this.layerOrder == null || this.layerOrder.isEmpty()) {
|
||||
Assert.state(this.application.isEmpty() && this.dependencies.isEmpty(),
|
||||
"The 'layerOrder' must be defined when using custom layering");
|
||||
return Layers.IMPLICIT;
|
||||
}
|
||||
List<Layer> layers = this.layerOrder.stream().map(Layer::new).collect(Collectors.toList());
|
||||
return new CustomLayers(layers, this.application.asSelectors(), this.dependencies.asSelectors());
|
||||
}
|
||||
|
||||
public abstract static class IntoLayersSpec implements Serializable {
|
||||
|
||||
private final List<IntoLayerSpec> intoLayers;
|
||||
|
||||
boolean isEmpty() {
|
||||
return this.intoLayers.isEmpty();
|
||||
}
|
||||
|
||||
IntoLayersSpec(IntoLayerSpec... spec) {
|
||||
this.intoLayers = new ArrayList<>(Arrays.asList(spec));
|
||||
}
|
||||
|
||||
public void intoLayer(String layer) {
|
||||
this.intoLayers.add(new IntoLayerSpec(layer));
|
||||
}
|
||||
|
||||
public void intoLayer(String layer, Closure<?> closure) {
|
||||
intoLayer(layer, ConfigureUtil.configureUsing(closure));
|
||||
}
|
||||
|
||||
public void intoLayer(String layer, Action<IntoLayerSpec> action) {
|
||||
IntoLayerSpec spec = new IntoLayerSpec(layer);
|
||||
action.execute(spec);
|
||||
this.intoLayers.add(spec);
|
||||
}
|
||||
|
||||
<T> List<ContentSelector<T>> asSelectors(Function<String, ContentFilter<T>> filterFactory) {
|
||||
return this.intoLayers.stream().map((content) -> content.asSelector(filterFactory))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class IntoLayerSpec implements Serializable {
|
||||
|
||||
private final String intoLayer;
|
||||
|
||||
private final List<String> includes = new ArrayList<>();
|
||||
|
||||
private final List<String> excludes = new ArrayList<>();
|
||||
|
||||
public IntoLayerSpec(String intoLayer) {
|
||||
this.intoLayer = intoLayer;
|
||||
}
|
||||
|
||||
public void include(String... patterns) {
|
||||
this.includes.addAll(Arrays.asList(patterns));
|
||||
}
|
||||
|
||||
public void exclude(String... patterns) {
|
||||
this.includes.addAll(Arrays.asList(patterns));
|
||||
}
|
||||
|
||||
<T> ContentSelector<T> asSelector(Function<String, ContentFilter<T>> filterFactory) {
|
||||
Layer layer = new Layer(this.intoLayer);
|
||||
return new IncludeExcludeContentSelector<>(layer, this.includes, this.excludes, filterFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ApplicationSpec extends IntoLayersSpec {
|
||||
|
||||
public ApplicationSpec(IntoLayerSpec... contents) {
|
||||
super(contents);
|
||||
}
|
||||
|
||||
List<ContentSelector<String>> asSelectors() {
|
||||
return asSelectors(ApplicationContentFilter::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class DependenciesSpec extends IntoLayersSpec {
|
||||
|
||||
public DependenciesSpec(IntoLayerSpec... contents) {
|
||||
super(contents);
|
||||
}
|
||||
|
||||
List<ContentSelector<Library>> asSelectors() {
|
||||
return asSelectors(LibraryContentFilter::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,18 +44,18 @@ class LoaderZipEntries {
|
||||
this.entryTime = entryTime;
|
||||
}
|
||||
|
||||
Spec<FileTreeElement> writeTo(ZipArchiveOutputStream zipOutputStream) throws IOException {
|
||||
Spec<FileTreeElement> writeTo(ZipArchiveOutputStream out) throws IOException {
|
||||
WrittenDirectoriesSpec writtenDirectoriesSpec = new WrittenDirectoriesSpec();
|
||||
try (ZipInputStream loaderJar = new ZipInputStream(
|
||||
getClass().getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) {
|
||||
java.util.zip.ZipEntry entry = loaderJar.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (entry.isDirectory() && !entry.getName().equals("META-INF/")) {
|
||||
writeDirectory(new ZipArchiveEntry(entry), zipOutputStream);
|
||||
writeDirectory(new ZipArchiveEntry(entry), out);
|
||||
writtenDirectoriesSpec.add(entry);
|
||||
}
|
||||
else if (entry.getName().endsWith(".class")) {
|
||||
writeClass(new ZipArchiveEntry(entry), loaderJar, zipOutputStream);
|
||||
writeClass(new ZipArchiveEntry(entry), loaderJar, out);
|
||||
}
|
||||
entry = loaderJar.getNextEntry();
|
||||
}
|
||||
|
||||
@@ -25,11 +25,14 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
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.loader.tools.JarModeLibrary;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
@@ -75,8 +78,7 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
||||
writeResource();
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar"))
|
||||
.isNotNull();
|
||||
assertThat(jarFile.getEntry(jarModeLayerTools())).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/dependencies/lib/commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/snapshot-dependencies/lib/commons-io-2.7-SNAPSHOT.jar"))
|
||||
.isNotNull();
|
||||
@@ -89,10 +91,11 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
||||
void customLayers() throws IOException {
|
||||
writeMainClass();
|
||||
writeResource();
|
||||
assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
BuildResult build = this.gradleBuild.build("bootJar");
|
||||
System.out.println(build.getOutput());
|
||||
assertThat(build.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar"))
|
||||
.isNotNull();
|
||||
assertThat(jarFile.getEntry(jarModeLayerTools())).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/commons-dependencies/lib/commons-lang3-3.9.jar")).isNotNull();
|
||||
assertThat(jarFile.getEntry("BOOT-INF/layers/snapshot-dependencies/lib/commons-io-2.7-SNAPSHOT.jar"))
|
||||
.isNotNull();
|
||||
@@ -101,6 +104,13 @@ class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
private String jarModeLayerTools() {
|
||||
JarModeLibrary library = JarModeLibrary.LAYER_TOOLS;
|
||||
String version = library.getCoordinates().getVersion();
|
||||
String layer = (version == null || !version.contains("SNAPSHOT")) ? "dependencies" : "snapshot-dependencies";
|
||||
return "BOOT-INF/layers/" + layer + "/lib/" + library.getName();
|
||||
}
|
||||
|
||||
private void writeMainClass() {
|
||||
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
||||
examplePackage.mkdirs();
|
||||
|
||||
@@ -20,9 +20,8 @@ import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarFile;
|
||||
@@ -31,16 +30,15 @@ import java.util.zip.ZipEntry;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.artifacts.ArtifactCollection;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ResolvableDependencies;
|
||||
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier;
|
||||
import org.gradle.api.artifacts.component.ComponentIdentifier;
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.loader.tools.layer.application.FilteredResourceStrategy;
|
||||
import org.springframework.boot.loader.tools.layer.application.LocationFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.CoordinateFilter;
|
||||
import org.springframework.boot.loader.tools.layer.library.FilteredLibraryStrategy;
|
||||
import org.springframework.boot.gradle.tasks.bundling.BootJarTests.TestBootJar;
|
||||
import org.springframework.boot.loader.tools.JarModeLibrary;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
@@ -53,10 +51,10 @@ import static org.mockito.Mockito.mock;
|
||||
* @author Madhura Bhave
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||
class BootJarTests extends AbstractBootArchiveTests<TestBootJar> {
|
||||
|
||||
BootJarTests() {
|
||||
super(BootJar.class, "org.springframework.boot.loader.JarLauncher", "BOOT-INF/lib/", "BOOT-INF/classes/");
|
||||
super(TestBootJar.class, "org.springframework.boot.loader.JarLauncher", "BOOT-INF/lib/", "BOOT-INF/classes/");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,13 +119,17 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredWithCustomStrategiesThenContentsAreMovedToLayerDirectories() throws IOException {
|
||||
File jar = createLayeredJar((configuration) -> {
|
||||
configuration.layersOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application");
|
||||
configuration.libraries(createLibraryStrategy("my-snapshot-deps", "com.example:*:*.SNAPSHOT"),
|
||||
createLibraryStrategy("my-internal-deps", "com.example:*:*"),
|
||||
createLibraryStrategy("my-deps", "*:*"));
|
||||
configuration.application(createResourceStrategy("resources", "static/**"),
|
||||
createResourceStrategy("application", "**"));
|
||||
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.layerOrder("my-deps", "my-internal-deps", "my-snapshot-deps", "resources", "application");
|
||||
});
|
||||
List<String> entryNames = getEntryNames(jar);
|
||||
assertThat(entryNames)
|
||||
@@ -139,16 +141,6 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||
.contains("BOOT-INF/layers/resources/classes/static/test.css");
|
||||
}
|
||||
|
||||
private FilteredLibraryStrategy createLibraryStrategy(String layerName, String... includes) {
|
||||
return new FilteredLibraryStrategy(layerName,
|
||||
Collections.singletonList(new CoordinateFilter(Arrays.asList(includes), Collections.emptyList())));
|
||||
}
|
||||
|
||||
private FilteredResourceStrategy createResourceStrategy(String layerName, String... includes) {
|
||||
return new FilteredResourceStrategy(layerName,
|
||||
Collections.singletonList(new LocationFilter(Arrays.asList(includes), Collections.emptyList())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenJarIsLayeredJarsInLibAreStored() throws IOException {
|
||||
try (JarFile jarFile = new JarFile(createLayeredJar())) {
|
||||
@@ -172,7 +164,14 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||
@Test
|
||||
void whenJarIsLayeredThenLayerToolsAreAddedToTheJar() throws IOException {
|
||||
List<String> entryNames = getEntryNames(createLayeredJar());
|
||||
assertThat(entryNames).contains("BOOT-INF/layers/dependencies/lib/spring-boot-jarmode-layertools.jar");
|
||||
assertThat(entryNames).contains(jarModeLayerTools());
|
||||
}
|
||||
|
||||
private String jarModeLayerTools() {
|
||||
JarModeLibrary library = JarModeLibrary.LAYER_TOOLS;
|
||||
String version = library.getCoordinates().getVersion();
|
||||
String layer = (version == null || !version.contains("SNAPSHOT")) ? "dependencies" : "snapshot-dependencies";
|
||||
return "BOOT-INF/layers/" + layer + "/lib/" + library.getName();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -198,24 +197,24 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||
return getTask().getArchiveFile().get().getAsFile();
|
||||
}
|
||||
|
||||
private File createLayeredJar(Action<LayerConfiguration> action) throws IOException {
|
||||
private File createLayeredJar() throws IOException {
|
||||
return createLayeredJar(null);
|
||||
}
|
||||
|
||||
private File createLayeredJar(Action<LayeredSpec> action) throws IOException {
|
||||
if (action != null) {
|
||||
getTask().layers(action);
|
||||
getTask().layered(action);
|
||||
}
|
||||
else {
|
||||
getTask().layers();
|
||||
getTask().layered();
|
||||
}
|
||||
addContent();
|
||||
executeTask();
|
||||
return getTask().getArchiveFile().get().getAsFile();
|
||||
}
|
||||
|
||||
private File createLayeredJar() throws IOException {
|
||||
return createLayeredJar(null);
|
||||
}
|
||||
|
||||
private void addContent() throws IOException {
|
||||
BootJar bootJar = getTask();
|
||||
TestBootJar bootJar = getTask();
|
||||
bootJar.setMainClassName("com.example.Main");
|
||||
File classesJavaMain = new File(this.temp, "classes/java/main");
|
||||
File applicationClass = new File(classesJavaMain, "com/example/Application.class");
|
||||
@@ -231,32 +230,33 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||
css.createNewFile();
|
||||
bootJar.classpath(classesJavaMain, resourcesMain, jarFile("first-library.jar"), jarFile("second-library.jar"),
|
||||
jarFile("third-library-SNAPSHOT.jar"));
|
||||
|
||||
Set<ResolvedArtifactResult> resolvedArtifacts = new HashSet<>();
|
||||
resolvedArtifacts.add(mockLibraryArtifact("first-library.jar", "com.example:first-library:1.0.0"));
|
||||
resolvedArtifacts.add(mockLibraryArtifact("second-library.jar", "com.example:second-library:1.0.0"));
|
||||
resolvedArtifacts
|
||||
.add(mockLibraryArtifact("third-library-SNAPSHOT.jar", "com.example:third-library:1.0.0.SNAPSHOT"));
|
||||
|
||||
ArtifactCollection artifacts = mock(ArtifactCollection.class);
|
||||
given(artifacts.getArtifacts()).willReturn(resolvedArtifacts);
|
||||
|
||||
ResolvableDependencies deps = mock(ResolvableDependencies.class);
|
||||
given(deps.getArtifacts()).willReturn(artifacts);
|
||||
bootJar.resolvedDependencies(deps);
|
||||
Set<ResolvedArtifactResult> 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"));
|
||||
ArtifactCollection resolvedDependencies = mock(ArtifactCollection.class);
|
||||
given(resolvedDependencies.getArtifacts()).willReturn(artifacts);
|
||||
ResolvableDependencies resolvableDependencies = mock(ResolvableDependencies.class);
|
||||
given(resolvableDependencies.getArtifacts()).willReturn(resolvedDependencies);
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
given(configuration.isCanBeResolved()).willReturn(true);
|
||||
given(configuration.getIncoming()).willReturn(resolvableDependencies);
|
||||
bootJar.setConfiguration(Collections.singleton(configuration));
|
||||
}
|
||||
|
||||
private ResolvedArtifactResult mockLibraryArtifact(String fileName, String coordinates) {
|
||||
ComponentIdentifier libraryId = mock(ComponentIdentifier.class);
|
||||
given(libraryId.getDisplayName()).willReturn(coordinates);
|
||||
|
||||
private ResolvedArtifactResult mockLibraryArtifact(String fileName, String group, String module, String version) {
|
||||
ModuleComponentIdentifier identifier = mock(ModuleComponentIdentifier.class);
|
||||
given(identifier.getGroup()).willReturn(group);
|
||||
given(identifier.getModule()).willReturn(module);
|
||||
given(identifier.getVersion()).willReturn(version);
|
||||
ComponentArtifactIdentifier libraryArtifactId = mock(ComponentArtifactIdentifier.class);
|
||||
given(libraryArtifactId.getComponentIdentifier()).willReturn(libraryId);
|
||||
|
||||
given(libraryArtifactId.getComponentIdentifier()).willReturn(identifier);
|
||||
ResolvedArtifactResult libraryArtifact = mock(ResolvedArtifactResult.class);
|
||||
given(libraryArtifact.getFile()).willReturn(new File(fileName));
|
||||
File file = new File(this.temp, fileName).getAbsoluteFile();
|
||||
System.out.println(file);
|
||||
given(libraryArtifact.getFile()).willReturn(file);
|
||||
given(libraryArtifact.getId()).willReturn(libraryArtifactId);
|
||||
|
||||
return libraryArtifact;
|
||||
}
|
||||
|
||||
@@ -272,4 +272,19 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||
getTask().copy();
|
||||
}
|
||||
|
||||
public static class TestBootJar extends BootJar {
|
||||
|
||||
private Iterable<Configuration> configurations = Collections.emptySet();
|
||||
|
||||
@Override
|
||||
protected Iterable<Configuration> getConfigurations() {
|
||||
return this.configurations;
|
||||
}
|
||||
|
||||
void setConfiguration(Iterable<Configuration> configurations) {
|
||||
this.configurations = configurations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,26 +5,23 @@ plugins {
|
||||
|
||||
bootJar {
|
||||
mainClassName = 'com.example.Application'
|
||||
layers {
|
||||
layersOrder "dependencies", "commons-dependencies", "snapshot-dependencies", "static", "app"
|
||||
libraries {
|
||||
layerContent("snapshot-dependencies") { coordinates { include "*:*:*SNAPSHOT" } }
|
||||
layerContent("commons-dependencies") { coordinates { include "org.apache.commons:*" } }
|
||||
layerContent("dependencies") { coordinates { include "*:*" } }
|
||||
}
|
||||
layered {
|
||||
application {
|
||||
layerContent("static") {
|
||||
locations {
|
||||
include "META-INF/resources/**", "resources/**"
|
||||
include "static/**", "public/**"
|
||||
}
|
||||
}
|
||||
layerContent("app") {
|
||||
locations {
|
||||
include "**"
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ plugins {
|
||||
|
||||
bootJar {
|
||||
mainClassName = 'com.example.Application'
|
||||
layers()
|
||||
layered()
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -11,7 +11,7 @@ bootJar {
|
||||
}
|
||||
}
|
||||
if (project.hasProperty('layered') && project.getProperty('layered')) {
|
||||
layers {
|
||||
layered {
|
||||
includeLayerTools = project.hasProperty('excludeTools') && project.getProperty('excludeTools') ? false : true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user