Commit 152698f2 authored by Madhura Bhave's avatar Madhura Bhave

Add support for creating layered war files with Maven

See gh-22821
parent fd27c26e
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -36,7 +36,7 @@ import org.springframework.util.Assert; ...@@ -36,7 +36,7 @@ import org.springframework.util.Assert;
*/ */
class Context { class Context {
private final File jarFile; private final File archiveFile;
private final File workingDir; private final File workingDir;
...@@ -46,23 +46,31 @@ class Context { ...@@ -46,23 +46,31 @@ class Context {
* Create a new {@link Context} instance. * Create a new {@link Context} instance.
*/ */
Context() { Context() {
this(getSourceJarFile(), Paths.get(".").toAbsolutePath().normalize().toFile()); this(getSourceArchiveFile(), Paths.get(".").toAbsolutePath().normalize().toFile());
} }
/** /**
* Create a new {@link Context} instance with the specified value. * Create a new {@link Context} instance with the specified value.
* @param jarFile the source jar file * @param archiveFile the source archive file
* @param workingDir the working directory * @param workingDir the working directory
*/ */
Context(File jarFile, File workingDir) { Context(File archiveFile, File workingDir) {
Assert.state(jarFile != null && jarFile.isFile() && jarFile.exists() Assert.state(isExistingFile(archiveFile) && isJarOrWar(archiveFile), "Unable to find source archive");
&& jarFile.getName().toLowerCase().endsWith(".jar"), "Unable to find source JAR"); this.archiveFile = archiveFile;
this.jarFile = jarFile;
this.workingDir = workingDir; this.workingDir = workingDir;
this.relativeDir = deduceRelativeDir(jarFile.getParentFile(), this.workingDir); this.relativeDir = deduceRelativeDir(archiveFile.getParentFile(), this.workingDir);
} }
private static File getSourceJarFile() { private boolean isExistingFile(File archiveFile) {
return archiveFile != null && archiveFile.isFile() && archiveFile.exists();
}
private boolean isJarOrWar(File jarFile) {
String name = jarFile.getName().toLowerCase();
return name.endsWith(".jar") || name.endsWith(".war");
}
private static File getSourceArchiveFile() {
try { try {
ProtectionDomain domain = Context.class.getProtectionDomain(); ProtectionDomain domain = Context.class.getProtectionDomain();
CodeSource codeSource = (domain != null) ? domain.getCodeSource() : null; CodeSource codeSource = (domain != null) ? domain.getCodeSource() : null;
...@@ -106,11 +114,11 @@ class Context { ...@@ -106,11 +114,11 @@ class Context {
} }
/** /**
* Return the source jar file that is running in tools mode. * Return the source archive file that is running in tools mode.
* @return the jar file * @return the archive file
*/ */
File getJarFile() { File getArchiveFile() {
return this.jarFile; return this.archiveFile;
} }
/** /**
...@@ -122,11 +130,11 @@ class Context { ...@@ -122,11 +130,11 @@ class Context {
} }
/** /**
* Return the directory relative to {@link #getWorkingDir()} that contains the jar or * Return the directory relative to {@link #getWorkingDir()} that contains the archive or
* {@code null} if none relative directory can be deduced. * {@code null} if none relative directory can be deduced.
* @return the relative dir ending in {@code /} or {@code null} * @return the relative dir ending in {@code /} or {@code null}
*/ */
String getRelativeJarDir() { String getRelativeArchiveDir() {
return this.relativeDir; return this.relativeDir;
} }
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -65,9 +65,9 @@ class ExtractCommand extends Command { ...@@ -65,9 +65,9 @@ class ExtractCommand extends Command {
mkDirs(new File(destination, layer)); mkDirs(new File(destination, layer));
} }
} }
try (ZipInputStream zip = new ZipInputStream(new FileInputStream(this.context.getJarFile()))) { try (ZipInputStream zip = new ZipInputStream(new FileInputStream(this.context.getArchiveFile()))) {
ZipEntry entry = zip.getNextEntry(); ZipEntry entry = zip.getNextEntry();
Assert.state(entry != null, "File '" + this.context.getJarFile().toString() Assert.state(entry != null, "File '" + this.context.getArchiveFile().toString()
+ "' is not compatible with layertools; ensure jar file is valid and launch script is not enabled"); + "' is not compatible with layertools; ensure jar file is valid and launch script is not enabled");
while (entry != null) { while (entry != null) {
if (!entry.isDirectory()) { if (!entry.isDirectory()) {
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -99,7 +99,7 @@ class HelpCommand extends Command { ...@@ -99,7 +99,7 @@ class HelpCommand extends Command {
} }
private String getJavaCommand() { private String getJavaCommand() {
return "java -Djarmode=layertools -jar " + this.context.getJarFile().getName(); return "java -Djarmode=layertools -jar " + this.context.getArchiveFile().getName();
} }
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -27,6 +27,7 @@ import java.util.LinkedHashMap; ...@@ -27,6 +27,7 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import org.springframework.util.Assert; import org.springframework.util.Assert;
...@@ -34,7 +35,7 @@ import org.springframework.util.StreamUtils; ...@@ -34,7 +35,7 @@ import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* {@link Layers} implementation backed by a {@code BOOT-INF/layers.idx} file. * {@link Layers} implementation backed by a {@code layers.idx} file.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Madhura Bhave * @author Madhura Bhave
...@@ -91,8 +92,10 @@ class IndexedLayers implements Layers { ...@@ -91,8 +92,10 @@ class IndexedLayers implements Layers {
*/ */
static IndexedLayers get(Context context) { static IndexedLayers get(Context context) {
try { try {
try (JarFile jarFile = new JarFile(context.getJarFile())) { try (JarFile jarFile = new JarFile(context.getArchiveFile())) {
ZipEntry entry = jarFile.getEntry("BOOT-INF/layers.idx"); Manifest manifest = jarFile.getManifest();
String location = manifest.getMainAttributes().getValue("Spring-Boot-Layers-Index");
ZipEntry entry = (location != null) ? jarFile.getEntry(location) : null;
if (entry != null) { if (entry != null) {
String indexFile = StreamUtils.copyToString(jarFile.getInputStream(entry), StandardCharsets.UTF_8); String indexFile = StreamUtils.copyToString(jarFile.getInputStream(entry), StandardCharsets.UTF_8);
return new IndexedLayers(indexFile); return new IndexedLayers(indexFile);
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -50,20 +50,12 @@ class ContextTests { ...@@ -50,20 +50,12 @@ class ContextTests {
.withMessage("Unable to find source JAR"); .withMessage("Unable to find source JAR");
} }
@Test
void createWhenSourceIsNotJarThrowsException() throws Exception {
File zip = new File(this.temp, "test.zip");
Files.createFile(zip.toPath());
assertThatIllegalStateException().isThrownBy(() -> new Context(zip, this.temp))
.withMessage("Unable to find source JAR");
}
@Test @Test
void getJarFileReturnsJar() throws Exception { void getJarFileReturnsJar() throws Exception {
File jar = new File(this.temp, "test.jar"); File jar = new File(this.temp, "test.jar");
Files.createFile(jar.toPath()); Files.createFile(jar.toPath());
Context context = new Context(jar, this.temp); Context context = new Context(jar, this.temp);
assertThat(context.getJarFile()).isEqualTo(jar); assertThat(context.getArchiveFile()).isEqualTo(jar);
} }
@Test @Test
...@@ -82,7 +74,7 @@ class ContextTests { ...@@ -82,7 +74,7 @@ class ContextTests {
File jar = new File(target, "test.jar"); File jar = new File(target, "test.jar");
Files.createFile(jar.toPath()); Files.createFile(jar.toPath());
Context context = new Context(jar, this.temp); Context context = new Context(jar, this.temp);
assertThat(context.getRelativeJarDir()).isEqualTo("target"); assertThat(context.getRelativeArchiveDir()).isEqualTo("target");
} }
@Test @Test
...@@ -90,7 +82,7 @@ class ContextTests { ...@@ -90,7 +82,7 @@ class ContextTests {
File jar = new File(this.temp, "test.jar"); File jar = new File(this.temp, "test.jar");
Files.createFile(jar.toPath()); Files.createFile(jar.toPath());
Context context = new Context(jar, this.temp); Context context = new Context(jar, this.temp);
assertThat(context.getRelativeJarDir()).isNull(); assertThat(context.getRelativeArchiveDir()).isNull();
} }
@Test @Test
...@@ -102,7 +94,7 @@ class ContextTests { ...@@ -102,7 +94,7 @@ class ContextTests {
File jar = new File(directory1, "test.jar"); File jar = new File(directory1, "test.jar");
Files.createFile(jar.toPath()); Files.createFile(jar.toPath());
Context context = new Context(jar, directory2); Context context = new Context(jar, directory2);
assertThat(context.getRelativeJarDir()).isNull(); assertThat(context.getRelativeArchiveDir()).isNull();
} }
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -69,7 +69,7 @@ class ExtractCommandTests { ...@@ -69,7 +69,7 @@ class ExtractCommandTests {
@Test @Test
void runExtractsLayers() throws Exception { void runExtractsLayers() throws Exception {
given(this.context.getJarFile()).willReturn(this.jarFile); given(this.context.getArchiveFile()).willReturn(this.jarFile);
given(this.context.getWorkingDir()).willReturn(this.extract); given(this.context.getWorkingDir()).willReturn(this.extract);
this.command.run(Collections.emptyMap(), Collections.emptyList()); this.command.run(Collections.emptyMap(), Collections.emptyList());
assertThat(this.extract.list()).containsOnly("a", "b", "c", "d"); assertThat(this.extract.list()).containsOnly("a", "b", "c", "d");
...@@ -81,7 +81,7 @@ class ExtractCommandTests { ...@@ -81,7 +81,7 @@ class ExtractCommandTests {
@Test @Test
void runWhenHasDestinationOptionExtractsLayers() { void runWhenHasDestinationOptionExtractsLayers() {
given(this.context.getJarFile()).willReturn(this.jarFile); given(this.context.getArchiveFile()).willReturn(this.jarFile);
File out = new File(this.extract, "out"); File out = new File(this.extract, "out");
this.command.run(Collections.singletonMap(ExtractCommand.DESTINATION_OPTION, out.getAbsolutePath()), this.command.run(Collections.singletonMap(ExtractCommand.DESTINATION_OPTION, out.getAbsolutePath()),
Collections.emptyList()); Collections.emptyList());
...@@ -93,7 +93,7 @@ class ExtractCommandTests { ...@@ -93,7 +93,7 @@ class ExtractCommandTests {
@Test @Test
void runWhenHasLayerParamsExtractsLimitedLayers() { void runWhenHasLayerParamsExtractsLimitedLayers() {
given(this.context.getJarFile()).willReturn(this.jarFile); given(this.context.getArchiveFile()).willReturn(this.jarFile);
given(this.context.getWorkingDir()).willReturn(this.extract); given(this.context.getWorkingDir()).willReturn(this.extract);
this.command.run(Collections.emptyMap(), Arrays.asList("a", "c")); this.command.run(Collections.emptyMap(), Arrays.asList("a", "c"));
assertThat(this.extract.list()).containsOnly("a", "c"); assertThat(this.extract.list()).containsOnly("a", "c");
...@@ -107,7 +107,7 @@ class ExtractCommandTests { ...@@ -107,7 +107,7 @@ class ExtractCommandTests {
try (FileWriter writer = new FileWriter(file)) { try (FileWriter writer = new FileWriter(file)) {
writer.write("text"); writer.write("text");
} }
given(this.context.getJarFile()).willReturn(file); given(this.context.getArchiveFile()).willReturn(file);
given(this.context.getWorkingDir()).willReturn(this.extract); given(this.context.getWorkingDir()).willReturn(this.extract);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> this.command.run(Collections.emptyMap(), Collections.emptyList())) .isThrownBy(() -> this.command.run(Collections.emptyMap(), Collections.emptyList()))
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -52,7 +52,7 @@ class HelpCommandTests { ...@@ -52,7 +52,7 @@ class HelpCommandTests {
@BeforeEach @BeforeEach
void setup() throws Exception { void setup() throws Exception {
Context context = mock(Context.class); Context context = mock(Context.class);
given(context.getJarFile()).willReturn(createJarFile("test.jar")); given(context.getArchiveFile()).willReturn(createJarFile("test.jar"));
this.command = new HelpCommand(context, LayerToolsJarMode.Runner.getCommands(context)); this.command = new HelpCommand(context, LayerToolsJarMode.Runner.getCommands(context));
this.out = new TestPrintStream(this); this.out = new TestPrintStream(this);
} }
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,10 +16,14 @@ ...@@ -16,10 +16,14 @@
package org.springframework.boot.jarmode.layertools; package org.springframework.boot.jarmode.layertools;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
...@@ -37,6 +41,9 @@ import static org.mockito.Mockito.mock; ...@@ -37,6 +41,9 @@ import static org.mockito.Mockito.mock;
*/ */
class IndexedLayersTests { class IndexedLayersTests {
@TempDir
File temp;
@Test @Test
void createWhenIndexFileIsEmptyThrowsException() { void createWhenIndexFileIsEmptyThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> new IndexedLayers(" \n ")) assertThatIllegalStateException().isThrownBy(() -> new IndexedLayers(" \n "))
...@@ -82,8 +89,20 @@ class IndexedLayersTests { ...@@ -82,8 +89,20 @@ class IndexedLayersTests {
assertThat(layers.getLayer(mockEntry("a b/c d"))).isEqualTo("application"); assertThat(layers.getLayer(mockEntry("a b/c d"))).isEqualTo("application");
} }
@Test
void getShouldReturnIndexedLayersFromContext() throws Exception {
Context context = mock(Context.class);
given(context.getArchiveFile()).willReturn(createWarFile("test.war"));
IndexedLayers layers = IndexedLayers.get(context);
assertThat(layers.getLayer(mockEntry("WEB-INF/lib/a.jar"))).isEqualTo("test");
}
private String getIndex() throws Exception { private String getIndex() throws Exception {
ClassPathResource resource = new ClassPathResource("test-layers.idx", getClass()); return getIndex("test-layers.idx");
}
private String getIndex(String fileName) throws Exception {
ClassPathResource resource = new ClassPathResource(fileName, getClass());
InputStreamReader reader = new InputStreamReader(resource.getInputStream()); InputStreamReader reader = new InputStreamReader(resource.getInputStream());
return FileCopyUtils.copyToString(reader); return FileCopyUtils.copyToString(reader);
} }
...@@ -94,4 +113,23 @@ class IndexedLayersTests { ...@@ -94,4 +113,23 @@ class IndexedLayersTests {
return entry; return entry;
} }
private File createWarFile(String name) throws Exception {
File file = new File(this.temp, name);
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file))) {
out.putNextEntry(new ZipEntry("WEB-INF/lib/a/"));
out.closeEntry();
out.putNextEntry(new ZipEntry("WEB-INF/lib/a/a.jar"));
out.closeEntry();
out.putNextEntry(new ZipEntry("WEB-INF/classes/Demo.class"));
out.closeEntry();
out.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
out.write(getIndex("test-manifest.MF").getBytes());
out.closeEntry();
out.putNextEntry(new ZipEntry("WEB-INF/layers.idx"));
out.write(getIndex("test-war-layers.idx").getBytes());
out.closeEntry();
}
return file;
}
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -55,7 +55,7 @@ class LayerToolsJarModeTests { ...@@ -55,7 +55,7 @@ class LayerToolsJarModeTests {
@BeforeEach @BeforeEach
void setup() throws Exception { void setup() throws Exception {
Context context = mock(Context.class); Context context = mock(Context.class);
given(context.getJarFile()).willReturn(createJarFile("test.jar")); given(context.getArchiveFile()).willReturn(createJarFile("test.jar"));
this.out = new TestPrintStream(this); this.out = new TestPrintStream(this);
this.systemOut = System.out; this.systemOut = System.out;
System.setOut(this.out); System.setOut(this.out);
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -60,7 +60,7 @@ class ListCommandTests { ...@@ -60,7 +60,7 @@ class ListCommandTests {
@BeforeEach @BeforeEach
void setup() throws Exception { void setup() throws Exception {
this.jarFile = createJarFile("test.jar"); this.jarFile = createJarFile("test.jar");
given(this.context.getJarFile()).willReturn(this.jarFile); given(this.context.getArchiveFile()).willReturn(this.jarFile);
this.command = new ListCommand(this.context); this.command = new ListCommand(this.context);
this.out = new TestPrintStream(this); this.out = new TestPrintStream(this);
} }
......
Manifest-Version: 1.0
Created-By: Maven WAR Plugin 3.3.1
Build-Jdk-Spec: 11
Implementation-Title: demo
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.WarLauncher
Start-Class: com.example.DemoApplication
Spring-Boot-Version: 2.5.0-SNAPSHOT
Spring-Boot-Classes: WEB-INF/classes/
Spring-Boot-Lib: WEB-INF/lib/
Spring-Boot-Classpath-Index: WEB-INF/classpath.idx
Spring-Boot-Layers-Index: WEB-INF/layers.idx
- "test":
- "WEB-INF/lib/a.jar"
- "WEB-INF/lib/b.jar"
- "empty":
- "application":
- "WEB-INF/classes/Demo.class"
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -30,6 +30,7 @@ import java.util.Collection; ...@@ -30,6 +30,7 @@ import java.util.Collection;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.JarInputStream; import java.util.jar.JarInputStream;
...@@ -90,11 +91,11 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter { ...@@ -90,11 +91,11 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
* @throws IOException if the entries cannot be written * @throws IOException if the entries cannot be written
*/ */
public void writeEntries(JarFile jarFile) throws IOException { public void writeEntries(JarFile jarFile) throws IOException {
writeEntries(jarFile, EntryTransformer.NONE, UnpackHandler.NEVER); writeEntries(jarFile, EntryTransformer.NONE, UnpackHandler.NEVER, (name) -> false);
} }
final void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler) final void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler,
throws IOException { Predicate<String> libraryPredicate) throws IOException {
Enumeration<JarEntry> entries = jarFile.entries(); Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) { while (entries.hasMoreElements()) {
JarArchiveEntry entry = new JarArchiveEntry(entries.nextElement()); JarArchiveEntry entry = new JarArchiveEntry(entries.nextElement());
...@@ -103,7 +104,8 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter { ...@@ -103,7 +104,8 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
EntryWriter entryWriter = new InputStreamEntryWriter(inputStream); EntryWriter entryWriter = new InputStreamEntryWriter(inputStream);
JarArchiveEntry transformedEntry = entryTransformer.transform(entry); JarArchiveEntry transformedEntry = entryTransformer.transform(entry);
if (transformedEntry != null) { if (transformedEntry != null) {
writeEntry(transformedEntry, entryWriter, unpackHandler, true); boolean updateLayerIndex = !libraryPredicate.test(entry.getName());
writeEntry(transformedEntry, entryWriter, unpackHandler, updateLayerIndex);
} }
} }
} }
......
...@@ -50,6 +50,28 @@ public interface Layout { ...@@ -50,6 +50,28 @@ public interface Layout {
*/ */
String getClassesLocation(); String getClassesLocation();
/**
* Returns the location of the classpath index file that should be written or
* {@code null} if not index is required. The result should include the filename and
* is relative to the root of the jar.
* @return the classpath index file location
* @since 2.5.0
*/
default String getClasspathIndexFileLocation() {
return null;
}
/**
* Returns the location of the layer index file that should be written or {@code null}
* if not index is required. The result should include the filename and is relative to
* the root of the jar.
* @return the layer index file location
* @since 2.5.0
*/
default String getLayersIndexFileLocation() {
return null;
}
/** /**
* Returns if loader classes should be included to make the archive executable. * Returns if loader classes should be included to make the archive executable.
* @return if the layout is executable * @return if the layout is executable
......
...@@ -160,6 +160,16 @@ public final class Layouts { ...@@ -160,6 +160,16 @@ public final class Layouts {
return "WEB-INF/classes/"; return "WEB-INF/classes/";
} }
@Override
public String getClasspathIndexFileLocation() {
return "WEB-INF/classpath.idx";
}
@Override
public String getLayersIndexFileLocation() {
return "WEB-INF/layers.idx";
}
@Override @Override
public boolean isExecutable() { public boolean isExecutable() {
return true; return true;
......
...@@ -172,7 +172,7 @@ public abstract class Packager { ...@@ -172,7 +172,7 @@ public abstract class Packager {
} }
writer.writeManifest(buildManifest(sourceJar)); writer.writeManifest(buildManifest(sourceJar));
writeLoaderClasses(writer); writeLoaderClasses(writer);
writer.writeEntries(sourceJar, getEntityTransformer(), writeableLibraries); writer.writeEntries(sourceJar, getEntityTransformer(), writeableLibraries, writeableLibraries::containsEntry);
writeableLibraries.write(writer); writeableLibraries.write(writer);
if (isLayered()) { if (isLayered()) {
writeLayerIndex(writer); writeLayerIndex(writer);
...@@ -190,7 +190,7 @@ public abstract class Packager { ...@@ -190,7 +190,7 @@ public abstract class Packager {
} }
private void writeLayerIndex(AbstractJarWriter writer) throws IOException { private void writeLayerIndex(AbstractJarWriter writer) throws IOException {
String name = ((RepackagingLayout) this.layout).getLayersIndexFileLocation(); String name = this.layout.getLayersIndexFileLocation();
if (StringUtils.hasLength(name)) { if (StringUtils.hasLength(name)) {
Layer layer = this.layers.getLayer(name); Layer layer = this.layers.getLayer(name);
this.layersIndex.add(layer, name); this.layersIndex.add(layer, name);
...@@ -318,17 +318,17 @@ public abstract class Packager { ...@@ -318,17 +318,17 @@ public abstract class Packager {
private void addBootAttributes(Attributes attributes) { private void addBootAttributes(Attributes attributes) {
attributes.putValue(BOOT_VERSION_ATTRIBUTE, getClass().getPackage().getImplementationVersion()); attributes.putValue(BOOT_VERSION_ATTRIBUTE, getClass().getPackage().getImplementationVersion());
addBootAttributesForLayout(attributes);
}
private void addBootAttributesForLayout(Attributes attributes) {
Layout layout = getLayout(); Layout layout = getLayout();
if (layout instanceof RepackagingLayout) { if (layout instanceof RepackagingLayout) {
addBootBootAttributesForRepackagingLayout(attributes, (RepackagingLayout) layout); attributes.putValue(BOOT_CLASSES_ATTRIBUTE, ((RepackagingLayout) layout).getRepackagedClassesLocation());
} }
else { else {
addBootBootAttributesForPlainLayout(attributes); attributes.putValue(BOOT_CLASSES_ATTRIBUTE, layout.getClassesLocation());
} }
}
private void addBootBootAttributesForRepackagingLayout(Attributes attributes, RepackagingLayout layout) {
attributes.putValue(BOOT_CLASSES_ATTRIBUTE, layout.getRepackagedClassesLocation());
putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, getLayout().getLibraryLocation("", LibraryScope.COMPILE)); putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, getLayout().getLibraryLocation("", LibraryScope.COMPILE));
putIfHasLength(attributes, BOOT_CLASSPATH_INDEX_ATTRIBUTE, layout.getClasspathIndexFileLocation()); putIfHasLength(attributes, BOOT_CLASSPATH_INDEX_ATTRIBUTE, layout.getClasspathIndexFileLocation());
if (isLayered()) { if (isLayered()) {
...@@ -336,11 +336,6 @@ public abstract class Packager { ...@@ -336,11 +336,6 @@ public abstract class Packager {
} }
} }
private void addBootBootAttributesForPlainLayout(Attributes attributes) {
attributes.putValue(BOOT_CLASSES_ATTRIBUTE, getLayout().getClassesLocation());
putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, getLayout().getLibraryLocation("", LibraryScope.COMPILE));
}
private void putIfHasLength(Attributes attributes, String name, String value) { private void putIfHasLength(Attributes attributes, String name, String value) {
if (StringUtils.hasLength(value)) { if (StringUtils.hasLength(value)) {
attributes.putValue(name, value); attributes.putValue(name, value);
...@@ -348,7 +343,7 @@ public abstract class Packager { ...@@ -348,7 +343,7 @@ public abstract class Packager {
} }
private boolean isLayered() { private boolean isLayered() {
return this.layers != null && getLayout() instanceof Layouts.Jar; return this.layers != null;
} }
/** /**
...@@ -466,6 +461,10 @@ public abstract class Packager { ...@@ -466,6 +461,10 @@ public abstract class Packager {
return Digest.sha1(library::openStream); return Digest.sha1(library::openStream);
} }
boolean containsEntry(String name) {
return this.libraries.containsKey(name);
}
private void write(AbstractJarWriter writer) throws IOException { private void write(AbstractJarWriter writer) throws IOException {
for (Entry<String, Library> entry : this.libraries.entrySet()) { for (Entry<String, Library> entry : this.libraries.entrySet()) {
String path = entry.getKey(); String path = entry.getKey();
...@@ -473,12 +472,12 @@ public abstract class Packager { ...@@ -473,12 +472,12 @@ public abstract class Packager {
String location = path.substring(0, path.lastIndexOf('/') + 1); String location = path.substring(0, path.lastIndexOf('/') + 1);
writer.writeNestedLibrary(location, library); writer.writeNestedLibrary(location, library);
} }
if (getLayout() instanceof RepackagingLayout) { if (Packager.this.layout instanceof RepackagingLayout) {
writeClasspathIndex((RepackagingLayout) getLayout(), writer); writeClasspathIndex(getLayout(), writer);
} }
} }
private void writeClasspathIndex(RepackagingLayout layout, AbstractJarWriter writer) throws IOException { private void writeClasspathIndex(Layout layout, AbstractJarWriter writer) throws IOException {
List<String> names = this.libraries.keySet().stream().map((path) -> "- \"" + path + "\"") List<String> names = this.libraries.keySet().stream().map((path) -> "- \"" + path + "\"")
.collect(Collectors.toList()); .collect(Collectors.toList());
writer.writeIndexFile(layout.getClasspathIndexFileLocation(), names); writer.writeIndexFile(layout.getClasspathIndexFileLocation(), names);
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -31,26 +31,4 @@ public interface RepackagingLayout extends Layout { ...@@ -31,26 +31,4 @@ public interface RepackagingLayout extends Layout {
*/ */
String getRepackagedClassesLocation(); String getRepackagedClassesLocation();
/**
* Returns the location of the classpath index file that should be written or
* {@code null} if not index is required. The result should include the filename and
* is relative to the root of the jar.
* @return the classpath index file location
* @since 2.3.0
*/
default String getClasspathIndexFileLocation() {
return null;
}
/**
* Returns the location of the layer index file that should be written or {@code null}
* if not index is required. The result should include the filename and is relative to
* the root of the jar.
* @return the layer index file location
* @since 2.3.0
*/
default String getLayersIndexFileLocation() {
return null;
}
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,15 +16,22 @@ ...@@ -16,15 +16,22 @@
package org.springframework.boot.maven; package org.springframework.boot.maven;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AssertProvider; import org.assertj.core.api.AssertProvider;
...@@ -60,6 +67,33 @@ abstract class AbstractArchiveIntegrationTests { ...@@ -60,6 +67,33 @@ abstract class AbstractArchiveIntegrationTests {
}; };
} }
protected Map<String, List<String>> readLayerIndex(JarFile jarFile) throws IOException {
if (getLayersIndexLocation() == null) {
return Collections.emptyMap();
}
Map<String, List<String>> index = new LinkedHashMap<>();
ZipEntry indexEntry = jarFile.getEntry(getLayersIndexLocation());
try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) {
String line = reader.readLine();
String layer = null;
while (line != null) {
if (line.startsWith("- ")) {
layer = line.substring(3, line.length() - 2);
index.put(layer, new ArrayList<>());
}
else if (line.startsWith(" - ")) {
index.computeIfAbsent(layer, (key) -> new ArrayList<>()).add(line.substring(5, line.length() - 1));
}
line = reader.readLine();
}
return index;
}
}
protected String getLayersIndexLocation() {
return null;
}
static final class JarAssert extends AbstractAssert<JarAssert, File> { static final class JarAssert extends AbstractAssert<JarAssert, File> {
private JarAssert(File actual) { private JarAssert(File actual) {
......
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,18 +16,13 @@ ...@@ -16,18 +16,13 @@
package org.springframework.boot.maven; package org.springframework.boot.maven;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
...@@ -47,6 +42,11 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -47,6 +42,11 @@ import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MavenBuildExtension.class) @ExtendWith(MavenBuildExtension.class)
class JarIntegrationTests extends AbstractArchiveIntegrationTests { class JarIntegrationTests extends AbstractArchiveIntegrationTests {
@Override
protected String getLayersIndexLocation() {
return "BOOT-INF/layers.idx";
}
@TestTemplate @TestTemplate
void whenJarIsRepackagedInPlaceOnlyRepackagedJarIsInstalled(MavenBuild mavenBuild) { void whenJarIsRepackagedInPlaceOnlyRepackagedJarIsInstalled(MavenBuild mavenBuild) {
mavenBuild.project("jar").goals("install").execute((project) -> { mavenBuild.project("jar").goals("install").execute((project) -> {
...@@ -394,24 +394,4 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests { ...@@ -394,24 +394,4 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
return jarHash.get(); return jarHash.get();
} }
private Map<String, List<String>> readLayerIndex(JarFile jarFile) throws IOException {
Map<String, List<String>> index = new LinkedHashMap<>();
ZipEntry indexEntry = jarFile.getEntry("BOOT-INF/layers.idx");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) {
String line = reader.readLine();
String layer = null;
while (line != null) {
if (line.startsWith("- ")) {
layer = line.substring(3, line.length() - 2);
index.put(layer, new ArrayList<>());
}
else if (line.startsWith(" - ")) {
index.computeIfAbsent(layer, (key) -> new ArrayList<>()).add(line.substring(5, line.length() - 1));
}
line = reader.readLine();
}
return index;
}
}
} }
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,10 +18,15 @@ package org.springframework.boot.maven; ...@@ -18,10 +18,15 @@ package org.springframework.boot.maven;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.jar.JarFile;
import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -34,6 +39,11 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -34,6 +39,11 @@ import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MavenBuildExtension.class) @ExtendWith(MavenBuildExtension.class)
class WarIntegrationTests extends AbstractArchiveIntegrationTests { class WarIntegrationTests extends AbstractArchiveIntegrationTests {
@Override
protected String getLayersIndexLocation() {
return "WEB-INF/layers.idx";
}
@TestTemplate @TestTemplate
void warRepackaging(MavenBuild mavenBuild) { void warRepackaging(MavenBuild mavenBuild) {
mavenBuild.project("war") mavenBuild.project("war")
...@@ -89,4 +99,72 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests { ...@@ -89,4 +99,72 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests {
}); });
} }
@TestTemplate
void repackagedWarContainsTheLayersIndexByDefault(MavenBuild mavenBuild) {
mavenBuild.project("war-layered").execute((project) -> {
File repackaged = new File(project, "war/target/war-layered-0.0.1.BUILD-SNAPSHOT.war");
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("WEB-INF/classes/")
.hasEntryWithNameStartingWith("WEB-INF/lib/jar-release")
.hasEntryWithNameStartingWith("WEB-INF/lib/jar-snapshot").hasEntryWithNameStartingWith(
"WEB-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getCoordinates().getArtifactId());
try (JarFile jarFile = new JarFile(repackaged)) {
Map<String, List<String>> layerIndex = readLayerIndex(jarFile);
assertThat(layerIndex.keySet()).containsExactly("dependencies", "spring-boot-loader",
"snapshot-dependencies", "application");
assertThat(layerIndex.get("application")).contains("WEB-INF/lib/jar-release-0.0.1.RELEASE.jar",
"WEB-INF/lib/jar-snapshot-0.0.1.BUILD-SNAPSHOT.jar");
assertThat(layerIndex.get("dependencies"))
.anyMatch((dependency) -> dependency.startsWith("WEB-INF/lib/spring-context"));
assertThat(layerIndex.get("dependencies"))
.anyMatch((dependency) -> dependency.startsWith("WEB-INF/lib-provided/"));
}
catch (IOException ex) {
}
});
}
@TestTemplate
void whenWarIsRepackagedWithTheLayersDisabledDoesNotContainLayersIndex(MavenBuild mavenBuild) {
mavenBuild.project("war-layered-disabled").execute((project) -> {
File repackaged = new File(project, "war/target/war-layered-0.0.1.BUILD-SNAPSHOT.war");
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("WEB-INF/classes/")
.hasEntryWithNameStartingWith("WEB-INF/lib/jar-release")
.hasEntryWithNameStartingWith("WEB-INF/lib/jar-snapshot")
.doesNotHaveEntryWithName("WEB-INF/layers.idx")
.doesNotHaveEntryWithNameStartingWith("WEB-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName());
});
}
@TestTemplate
void whenWarIsRepackagedWithTheLayersEnabledAndLayerToolsExcluded(MavenBuild mavenBuild) {
mavenBuild.project("war-layered-no-layer-tools").execute((project) -> {
File repackaged = new File(project, "war/target/war-layered-0.0.1.BUILD-SNAPSHOT.war");
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("WEB-INF/classes/")
.hasEntryWithNameStartingWith("WEB-INF/lib/jar-release")
.hasEntryWithNameStartingWith("WEB-INF/lib/jar-snapshot")
.hasEntryWithNameStartingWith("WEB-INF/layers.idx")
.doesNotHaveEntryWithNameStartingWith("WEB-INF/lib/" + JarModeLibrary.LAYER_TOOLS.getName());
});
}
@TestTemplate
void whenWarIsRepackagedWithTheCustomLayers(MavenBuild mavenBuild) {
mavenBuild.project("war-layered-custom").execute((project) -> {
File repackaged = new File(project, "war/target/war-layered-0.0.1.BUILD-SNAPSHOT.war");
assertThat(jar(repackaged)).hasEntryWithNameStartingWith("WEB-INF/classes/")
.hasEntryWithNameStartingWith("WEB-INF/lib/jar-release")
.hasEntryWithNameStartingWith("WEB-INF/lib/jar-snapshot");
try (JarFile jarFile = new JarFile(repackaged)) {
Map<String, List<String>> layerIndex = readLayerIndex(jarFile);
assertThat(layerIndex.keySet()).containsExactly("my-dependencies-name", "snapshot-dependencies",
"configuration", "application");
assertThat(layerIndex.get("application"))
.contains("WEB-INF/lib/jar-release-0.0.1.RELEASE.jar",
"WEB-INF/lib/jar-snapshot-0.0.1.BUILD-SNAPSHOT.jar",
"WEB-INF/lib/jar-classifier-0.0.1-bravo.jar")
.doesNotContain("WEB-INF/lib/jar-classifier-0.0.1-alpha.jar");
}
});
}
} }
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-classifier</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>jar</name>
<description>Classifier Jar dependency</description>
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>alpha</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>alpha</classifier>
</configuration>
</execution>
<execution>
<id>bravo</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>bravo</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-release</artifactId>
<version>0.0.1.RELEASE</version>
<packaging>jar</packaging>
<name>jar</name>
<description>Release Jar dependency</description>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-snapshot</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jar</name>
<description>Snapshot Jar dependency</description>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>aggregator</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<modules>
<module>jar-classifier</module>
<module>jar-release</module>
<module>jar-snapshot</module>
<module>war</module>
</modules>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>aggregator</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</parent>
<artifactId>war-layered</artifactId>
<packaging>war</packaging>
<name>war</name>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<layers>
<enabled>true</enabled>
<configuration>${project.basedir}/src/layers.xml</configuration>
</layers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>@maven-war-plugin.version@</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>@spring-framework.version@</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>@jakarta-servlet.version@</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-snapshot</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-classifier</artifactId>
<version>0.0.1</version>
<classifier>bravo</classifier>
</dependency>
<dependency>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-release</artifactId>
<version>0.0.1.RELEASE</version>
</dependency>
</dependencies>
</project>
<layers xmlns="http://www.springframework.org/schema/boot/layers"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
https://www.springframework.org/schema/layers/layers-2.4.xsd">
<application>
<into layer="configuration">
<include>**/application*.*</include>
</into>
<into layer="application" />
</application>
<dependencies>
<into layer="application">
<includeModuleDependencies />
</into>
<into layer="snapshot-dependencies">
<include>*:*:*-SNAPSHOT</include>
</into>
<into layer="my-dependencies-name" />
</dependencies>
<layerOrder>
<layer>my-dependencies-name</layer>
<layer>snapshot-dependencies</layer>
<layer>configuration</layer>
<layer>application</layer>
</layerOrder>
</layers>
/*
* 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.test;
public class SampleApplication {
public static void main(String[] args) {
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-release</artifactId>
<version>0.0.1.RELEASE</version>
<packaging>jar</packaging>
<name>jar</name>
<description>Release Jar dependency</description>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-snapshot</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jar</name>
<description>Snapshot Jar dependency</description>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>aggregator</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<modules>
<module>jar-snapshot</module>
<module>jar-release</module>
<module>war</module>
</modules>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>aggregator</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</parent>
<artifactId>war-layered</artifactId>
<packaging>war</packaging>
<name>war</name>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<layers>
<enabled>false</enabled>
</layers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>@maven-war-plugin.version@</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>@spring-framework.version@</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>@jakarta-servlet.version@</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-snapshot</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-release</artifactId>
<version>0.0.1.RELEASE</version>
</dependency>
</dependencies>
</project>
/*
* 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.test;
public class SampleApplication {
public static void main(String[] args) {
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-release</artifactId>
<version>0.0.1.RELEASE</version>
<packaging>jar</packaging>
<name>jar</name>
<description>Release Jar dependency</description>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-snapshot</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jar</name>
<description>Snapshot Jar dependency</description>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>aggregator</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<modules>
<module>jar-snapshot</module>
<module>jar-release</module>
<module>war</module>
</modules>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>aggregator</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</parent>
<artifactId>war-layered</artifactId>
<packaging>war</packaging>
<name>war</name>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<layers>
<includeLayerTools>false</includeLayerTools>
</layers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>@maven-war-plugin.version@</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>@spring-framework.version@</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>@jakarta-servlet.version@</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-snapshot</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-release</artifactId>
<version>0.0.1.RELEASE</version>
</dependency>
</dependencies>
</project>
/*
* 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.test;
public class SampleApplication {
public static void main(String[] args) {
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-release</artifactId>
<version>0.0.1.RELEASE</version>
<packaging>jar</packaging>
<name>jar</name>
<description>Release Jar dependency</description>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-snapshot</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>jar</packaging>
<name>jar</name>
<description>Snapshot Jar dependency</description>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>aggregator</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<modules>
<module>jar-snapshot</module>
<module>jar-release</module>
<module>war</module>
</modules>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>aggregator</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</parent>
<artifactId>war-layered</artifactId>
<packaging>war</packaging>
<name>war</name>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>@maven-war-plugin.version@</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>@spring-framework.version@</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>@jakarta-servlet.version@</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-snapshot</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-release</artifactId>
<version>0.0.1.RELEASE</version>
</dependency>
</dependencies>
</project>
/*
* 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.test;
public class SampleApplication {
public static void main(String[] args) {
}
}
...@@ -182,7 +182,10 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo ...@@ -182,7 +182,10 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
* @throws MojoExecutionException on execution error * @throws MojoExecutionException on execution error
*/ */
protected final Libraries getLibraries(Collection<Dependency> unpacks) throws MojoExecutionException { protected final Libraries getLibraries(Collection<Dependency> unpacks) throws MojoExecutionException {
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), getFilters(getAdditionalFilters())); String packaging = this.project.getPackaging();
Set<Artifact> projectArtifacts = this.project.getArtifacts();
Set<Artifact> artifacts = ("war".equals(packaging)) ? projectArtifacts
: filterDependencies(projectArtifacts, getFilters(getAdditionalFilters()));
return new ArtifactsLibraries(artifacts, this.session.getProjects(), unpacks, getLog()); return new ArtifactsLibraries(artifacts, this.session.getProjects(), unpacks, getLog());
} }
......
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