Merge branch '2.4.x' into 2.5.x

Closes gh-27056
pull/27157/head
Phillip Webb 3 years ago
commit af37f59136

@ -198,7 +198,7 @@ abstract class ArchiveCommand extends OptionParsingCommand {
List<Library> libraries = new ArrayList<>();
for (URL dependency : dependencies) {
File file = new File(dependency.toURI());
libraries.add(new Library(file, getLibraryScope(file)));
libraries.add(new Library(null, file, getLibraryScope(file), null, false, false, true));
}
return libraries;
}
@ -256,7 +256,7 @@ abstract class ArchiveCommand extends OptionParsingCommand {
List<Library> libraries = new ArrayList<>();
for (MatchedResource entry : entries) {
if (entry.isRoot()) {
libraries.add(new Library(entry.getFile(), LibraryScope.COMPILE));
libraries.add(new Library(null, entry.getFile(), LibraryScope.COMPILE, null, false, false, true));
}
else {
writeClasspathEntry(writer, entry);

@ -37,7 +37,7 @@ public class MyBuildTool {
private void getLibraries(LibraryCallback callback) throws IOException {
// Build system specific implementation, callback for each dependency
for (File nestedJar : getCompileScopeJars()) {
callback.library(new Library(nestedJar, LibraryScope.COMPILE));
callback.library(new Library(null, nestedJar, LibraryScope.COMPILE, null, false, false, true));
}
// ...
}

@ -24,6 +24,7 @@ import org.gradle.api.specs.Spec;
import org.springframework.boot.gradle.tasks.bundling.ResolvedDependencies.DependencyDescriptor;
import org.springframework.boot.loader.tools.Layer;
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}
@ -77,9 +78,12 @@ class LayerResolver {
private Library asLibrary(FileCopyDetails details) {
File file = details.getFile();
DependencyDescriptor dependency = this.resolvedDependencies.find(file);
return (dependency != null)
? new Library(null, file, null, dependency.getCoordinates(), false, dependency.isProjectDependency())
: new Library(file, null);
if (dependency == null) {
return new Library(null, file, null, null, false, false, true);
}
LibraryCoordinates coordinates = dependency.getCoordinates();
boolean projectDependency = dependency.isProjectDependency();
return new Library(null, file, null, coordinates, false, projectDependency, true);
}
}

@ -30,7 +30,7 @@ import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
@ -89,24 +89,33 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
* Write all entries from the specified jar file.
* @param jarFile the source jar file
* @throws IOException if the entries cannot be written
* @deprecated since 2.4.8 for removal in 2.6.0
*/
@Deprecated
public void writeEntries(JarFile jarFile) throws IOException {
writeEntries(jarFile, EntryTransformer.NONE, UnpackHandler.NEVER, (name) -> false);
writeEntries(jarFile, EntryTransformer.NONE, UnpackHandler.NEVER, (entry) -> null);
}
final void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler,
Predicate<String> libraryPredicate) throws IOException {
Function<JarEntry, Library> libraryLookup) throws IOException {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarArchiveEntry entry = new JarArchiveEntry(entries.nextElement());
setUpEntry(jarFile, entry);
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
EntryWriter entryWriter = new InputStreamEntryWriter(inputStream);
JarArchiveEntry transformedEntry = entryTransformer.transform(entry);
if (transformedEntry != null) {
boolean updateLayerIndex = !libraryPredicate.test(entry.getName());
writeEntry(transformedEntry, entryWriter, unpackHandler, updateLayerIndex);
}
JarEntry entry = entries.nextElement();
Library library = libraryLookup.apply(entry);
if (library == null || library.isIncluded()) {
writeEntry(jarFile, entryTransformer, unpackHandler, new JarArchiveEntry(entry), library);
}
}
}
private void writeEntry(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler,
JarArchiveEntry entry, Library library) throws IOException {
setUpEntry(jarFile, entry);
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
EntryWriter entryWriter = new InputStreamEntryWriter(inputStream);
JarArchiveEntry transformedEntry = entryTransformer.transform(entry);
if (transformedEntry != null) {
writeEntry(transformedEntry, library, entryWriter, unpackHandler);
}
}
}
@ -160,15 +169,7 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
entry.setTime(getNestedLibraryTime(library));
new CrcAndSize(library::openStream).setupStoredEntry(entry);
try (InputStream inputStream = library.openStream()) {
writeEntry(entry, new InputStreamEntryWriter(inputStream), new LibraryUnpackHandler(library), false);
updateLayerIndex(entry.getName(), library);
}
}
private void updateLayerIndex(String name, Library library) {
if (this.layers != null) {
Layer layer = this.layers.getLayer(library);
this.layersIndex.add(layer, name);
writeEntry(entry, library, new InputStreamEntryWriter(inputStream), new LibraryUnpackHandler(library));
}
}
@ -249,20 +250,20 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
}
private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter) throws IOException {
writeEntry(entry, entryWriter, UnpackHandler.NEVER, true);
writeEntry(entry, null, entryWriter, UnpackHandler.NEVER);
}
/**
* Perform the actual write of a {@link JarEntry}. All other write methods delegate to
* this one.
* @param entry the entry to write
* @param library the library for the entry or {@code null}
* @param entryWriter the entry writer or {@code null} if there is no content
* @param unpackHandler handles possible unpacking for the entry
* @param updateLayerIndex if the layer index should be updated
* @throws IOException in case of I/O errors
*/
private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter, UnpackHandler unpackHandler,
boolean updateLayerIndex) throws IOException {
private void writeEntry(JarArchiveEntry entry, Library library, EntryWriter entryWriter,
UnpackHandler unpackHandler) throws IOException {
String name = entry.getName();
writeParentDirectoryEntries(name);
if (this.writtenEntries.add(name)) {
@ -273,16 +274,14 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
entry.setSize(entryWriter.size());
}
entryWriter = addUnpackCommentIfNecessary(entry, entryWriter, unpackHandler);
if (updateLayerIndex) {
updateLayerIndex(entry);
}
updateLayerIndex(entry, library);
writeToArchive(entry, entryWriter);
}
}
private void updateLayerIndex(JarArchiveEntry entry) {
private void updateLayerIndex(JarArchiveEntry entry, Library library) {
if (this.layers != null && !entry.getName().endsWith("/")) {
Layer layer = this.layers.getLayer(entry.getName());
Layer layer = (library != null) ? this.layers.getLayer(library) : this.layers.getLayer(entry.getName());
this.layersIndex.add(layer, entry.getName());
}
}
@ -294,7 +293,7 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter {
while (parent.lastIndexOf('/') != -1) {
parent = parent.substring(0, parent.lastIndexOf('/'));
if (!parent.isEmpty()) {
writeEntry(new JarArchiveEntry(parent + "/"), null, UnpackHandler.NEVER, false);
writeEntry(new JarArchiveEntry(parent + "/"), null, null, UnpackHandler.NEVER);
}
}
}

@ -42,7 +42,7 @@ public class JarModeLibrary extends Library {
}
public JarModeLibrary(LibraryCoordinates coordinates) {
super(getJarName(coordinates), null, LibraryScope.RUNTIME, coordinates, false);
super(getJarName(coordinates), null, LibraryScope.RUNTIME, coordinates, false, false, true);
}
private static LibraryCoordinates createCoordinates(String artifactId) {

@ -43,11 +43,16 @@ public class Library {
private final boolean local;
private final boolean included;
/**
* Create a new {@link Library}.
* @param file the source file
* @param scope the scope of the library
* @deprecated since 2.4.8 for removal in 2.6.0 in favor of
* {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)}
*/
@Deprecated
public Library(File file, LibraryScope scope) {
this(file, scope, false);
}
@ -57,7 +62,10 @@ public class Library {
* @param file the source file
* @param scope the scope of the library
* @param unpackRequired if the library needs to be unpacked before it can be used
* @deprecated since 2.4.8 for removal in 2.6.0 in favor of
* {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)}
*/
@Deprecated
public Library(File file, LibraryScope scope, boolean unpackRequired) {
this(null, file, scope, unpackRequired);
}
@ -69,7 +77,10 @@ public class Library {
* @param file the source file
* @param scope the scope of the library
* @param unpackRequired if the library needs to be unpacked before it can be used
* @deprecated since 2.4.8 for removal in 2.6.0 in favor of
* {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)}
*/
@Deprecated
public Library(String name, File file, LibraryScope scope, boolean unpackRequired) {
this(name, file, scope, null, unpackRequired);
}
@ -82,7 +93,10 @@ public class Library {
* @param scope the scope of the library
* @param coordinates the library coordinates or {@code null}
* @param unpackRequired if the library needs to be unpacked before it can be used
* @deprecated since 2.4.8 for removal in 2.6.0 in favor of
* {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)}
*/
@Deprecated
public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired) {
this(name, file, scope, coordinates, unpackRequired, false);
}
@ -98,15 +112,37 @@ public class Library {
* @param local if the library is local (part of the same build) to the application
* that is being packaged
* @since 2.4.0
* @deprecated since 2.4.8 for removal in 2.6.0 in favor of
* {@link #Library(String, File, LibraryScope, LibraryCoordinates, boolean, boolean, boolean)}
*/
@Deprecated
public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired,
boolean local) {
this(name, file, scope, coordinates, unpackRequired, local, true);
}
/**
* Create a new {@link Library}.
* @param name the name of the library as it should be written or {@code null} to use
* the file name
* @param file the source file
* @param scope the scope of the library
* @param coordinates the library coordinates or {@code null}
* @param unpackRequired if the library needs to be unpacked before it can be used
* @param local if the library is local (part of the same build) to the application
* that is being packaged
* @param included if the library is included in the fat jar
* @since 2.4.8
*/
public Library(String name, File file, LibraryScope scope, LibraryCoordinates coordinates, boolean unpackRequired,
boolean local, boolean included) {
this.name = (name != null) ? name : file.getName();
this.file = file;
this.scope = scope;
this.coordinates = coordinates;
this.unpackRequired = unpackRequired;
this.local = local;
this.included = included;
}
/**
@ -172,4 +208,12 @@ public class Library {
return this.local;
}
/**
* Return if the library is included in the fat jar.
* @return if the library is included
*/
public boolean isIncluded() {
return this.included;
}
}

@ -25,7 +25,9 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
@ -191,14 +193,18 @@ public abstract class Packager {
protected final void write(JarFile sourceJar, Libraries libraries, AbstractJarWriter writer) throws IOException {
Assert.notNull(libraries, "Libraries must not be null");
WritableLibraries writeableLibraries = new WritableLibraries(libraries);
write(sourceJar, writer, new PackagedLibraries(libraries));
}
private void write(JarFile sourceJar, AbstractJarWriter writer, PackagedLibraries libraries) throws IOException {
if (isLayered()) {
writer.useLayers(this.layers, this.layersIndex);
}
writer.writeManifest(buildManifest(sourceJar));
writeLoaderClasses(writer);
writer.writeEntries(sourceJar, getEntityTransformer(), writeableLibraries, writeableLibraries::containsEntry);
writeableLibraries.write(writer);
writer.writeEntries(sourceJar, getEntityTransformer(), libraries.getUnpackHandler(),
libraries.getLibraryLookup());
libraries.write(writer);
if (isLayered()) {
writeLayerIndex(writer);
}
@ -451,11 +457,15 @@ public abstract class Packager {
* An {@link UnpackHandler} that determines that an entry needs to be unpacked if a
* library that requires unpacking has a matching entry name.
*/
private final class WritableLibraries implements UnpackHandler {
private final class PackagedLibraries {
private final Map<String, Library> libraries = new LinkedHashMap<>();
WritableLibraries(Libraries libraries) throws IOException {
private final UnpackHandler unpackHandler;
private final Function<JarEntry, Library> libraryLookup;
PackagedLibraries(Libraries libraries) throws IOException {
libraries.doWithLibraries((library) -> {
if (isZip(library::openStream)) {
addLibrary(library);
@ -464,6 +474,8 @@ public abstract class Packager {
if (isLayered() && Packager.this.includeRelevantJarModeJars) {
addLibrary(JarModeLibrary.LAYER_TOOLS);
}
this.unpackHandler = new PackagedLibrariesUnpackHandler();
this.libraryLookup = this::lookup;
}
private void addLibrary(Library library) {
@ -475,41 +487,57 @@ public abstract class Packager {
}
}
@Override
public boolean requiresUnpack(String name) {
Library library = this.libraries.get(name);
return library != null && library.isUnpackRequired();
private Library lookup(JarEntry entry) {
return this.libraries.get(entry.getName());
}
@Override
public String sha1Hash(String name) throws IOException {
Library library = this.libraries.get(name);
Assert.notNull(library, () -> "No library found for entry name '" + name + "'");
return Digest.sha1(library::openStream);
UnpackHandler getUnpackHandler() {
return this.unpackHandler;
}
boolean containsEntry(String name) {
return this.libraries.containsKey(name);
Function<JarEntry, Library> getLibraryLookup() {
return this.libraryLookup;
}
private void write(AbstractJarWriter writer) throws IOException {
void write(AbstractJarWriter writer) throws IOException {
List<String> writtenPaths = new ArrayList<>();
for (Entry<String, Library> entry : this.libraries.entrySet()) {
String path = entry.getKey();
Library library = entry.getValue();
String location = path.substring(0, path.lastIndexOf('/') + 1);
writer.writeNestedLibrary(location, library);
if (library.isIncluded()) {
String location = path.substring(0, path.lastIndexOf('/') + 1);
writer.writeNestedLibrary(location, library);
writtenPaths.add(path);
}
}
if (Packager.this.layout instanceof RepackagingLayout) {
writeClasspathIndex(getLayout(), writer);
if (getLayout() instanceof RepackagingLayout) {
writeClasspathIndex(writtenPaths, (RepackagingLayout) getLayout(), writer);
}
}
private void writeClasspathIndex(Layout layout, AbstractJarWriter writer) throws IOException {
List<String> names = this.libraries.keySet().stream().map((path) -> "- \"" + path + "\"")
.collect(Collectors.toList());
private void writeClasspathIndex(List<String> paths, RepackagingLayout layout, AbstractJarWriter writer)
throws IOException {
List<String> names = paths.stream().map((path) -> "- \"" + path + "\"").collect(Collectors.toList());
writer.writeIndexFile(layout.getClasspathIndexFileLocation(), names);
}
private class PackagedLibrariesUnpackHandler implements UnpackHandler {
@Override
public boolean requiresUnpack(String name) {
Library library = PackagedLibraries.this.libraries.get(name);
return library != null && library.isUnpackRequired();
}
@Override
public String sha1Hash(String name) throws IOException {
Library library = PackagedLibraries.this.libraries.get(name);
Assert.notNull(library, () -> "No library found for entry name '" + name + "'");
return Digest.sha1(library::openStream);
}
}
}
}

@ -197,9 +197,9 @@ abstract class AbstractPackagerTests<P extends Packager> {
libJarFile.setLastModified(JAN_1_1980);
P packager = createPackager();
execute(packager, (callback) -> {
callback.library(new Library(libJarFile, LibraryScope.COMPILE));
callback.library(new Library(libJarFileToUnpack, LibraryScope.COMPILE, true));
callback.library(new Library(libNonJarFile, LibraryScope.COMPILE));
callback.library(newLibrary(libJarFile, LibraryScope.COMPILE, false));
callback.library(newLibrary(libJarFileToUnpack, LibraryScope.COMPILE, true));
callback.library(newLibrary(libNonJarFile, LibraryScope.COMPILE, false));
});
assertThat(hasPackagedEntry("BOOT-INF/lib/" + libJarFile.getName())).isTrue();
assertThat(hasPackagedEntry("BOOT-INF/lib/" + libJarFileToUnpack.getName())).isTrue();
@ -226,9 +226,9 @@ abstract class AbstractPackagerTests<P extends Packager> {
File file = this.testJarFile.getFile();
P packager = createPackager(file);
execute(packager, (callback) -> {
callback.library(new Library(libJarFile1, LibraryScope.COMPILE));
callback.library(new Library(libJarFile2, LibraryScope.COMPILE));
callback.library(new Library(libJarFile3, LibraryScope.COMPILE));
callback.library(newLibrary(libJarFile1, LibraryScope.COMPILE, false));
callback.library(newLibrary(libJarFile2, LibraryScope.COMPILE, false));
callback.library(newLibrary(libJarFile3, LibraryScope.COMPILE, false));
});
assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue();
String index = getPackagedEntryContent("BOOT-INF/classpath.idx");
@ -258,9 +258,9 @@ abstract class AbstractPackagerTests<P extends Packager> {
packager.setLayers(layers);
packager.setIncludeRelevantJarModeJars(false);
execute(packager, (callback) -> {
callback.library(new Library(libJarFile1, LibraryScope.COMPILE));
callback.library(new Library(libJarFile2, LibraryScope.COMPILE));
callback.library(new Library(libJarFile3, LibraryScope.COMPILE));
callback.library(newLibrary(libJarFile1, LibraryScope.COMPILE, false));
callback.library(newLibrary(libJarFile2, LibraryScope.COMPILE, false));
callback.library(newLibrary(libJarFile3, LibraryScope.COMPILE, false));
});
assertThat(hasPackagedEntry("BOOT-INF/classpath.idx")).isTrue();
String classpathIndex = getPackagedEntryContent("BOOT-INF/classpath.idx");
@ -316,8 +316,8 @@ abstract class AbstractPackagerTests<P extends Packager> {
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
P packager = createPackager();
assertThatIllegalStateException().isThrownBy(() -> execute(packager, (callback) -> {
callback.library(new Library(libJarFile, LibraryScope.COMPILE, false));
callback.library(new Library(libJarFile, LibraryScope.COMPILE, false));
callback.library(newLibrary(libJarFile, LibraryScope.COMPILE, false));
callback.library(newLibrary(libJarFile, LibraryScope.COMPILE, false));
})).withMessageContaining("Duplicate library");
}
@ -334,7 +334,7 @@ abstract class AbstractPackagerTests<P extends Packager> {
given(layout.getLibraryLocation(anyString(), eq(scope))).willReturn("test/");
given(layout.getLibraryLocation(anyString(), eq(LibraryScope.COMPILE))).willReturn("test-lib/");
packager.setLayout(layout);
execute(packager, (callback) -> callback.library(new Library(libJarFile, scope)));
execute(packager, (callback) -> callback.library(newLibrary(libJarFile, scope, false)));
assertThat(hasPackagedEntry("test/" + libJarFile.getName())).isTrue();
assertThat(getPackagedManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isEqualTo("test-lib/");
assertThat(getPackagedManifest().getMainAttributes().getValue("Main-Class")).isEqualTo("testLauncher");
@ -351,7 +351,7 @@ abstract class AbstractPackagerTests<P extends Packager> {
LibraryScope scope = mock(LibraryScope.class);
given(layout.getLauncherClassName()).willReturn("testLauncher");
packager.setLayout(layout);
execute(packager, (callback) -> callback.library(new Library(libJarFile, scope)));
execute(packager, (callback) -> callback.library(newLibrary(libJarFile, scope, false)));
assertThat(getPackagedManifest().getMainAttributes().getValue("Spring-Boot-Lib")).isNull();
assertThat(getPackagedManifest().getMainAttributes().getValue("Main-Class")).isEqualTo("testLauncher");
}
@ -405,7 +405,7 @@ abstract class AbstractPackagerTests<P extends Packager> {
this.testJarFile.addFile("test/nested.jar", nestedFile);
this.testJarFile.addClass("A.class", ClassWithMainMethod.class);
P packager = createPackager();
execute(packager, (callback) -> callback.library(new Library(nestedFile, LibraryScope.COMPILE)));
execute(packager, (callback) -> callback.library(newLibrary(nestedFile, LibraryScope.COMPILE, false)));
assertThat(getPackagedEntry("BOOT-INF/lib/" + nestedFile.getName()).getMethod()).isEqualTo(ZipEntry.STORED);
assertThat(getPackagedEntry("BOOT-INF/classes/test/nested.jar").getMethod()).isEqualTo(ZipEntry.STORED);
}
@ -419,7 +419,7 @@ abstract class AbstractPackagerTests<P extends Packager> {
this.testJarFile.addFile(name, nested.getFile());
this.testJarFile.addClass("A.class", ClassWithMainMethod.class);
P packager = createPackager();
execute(packager, (callback) -> callback.library(new Library(nestedFile, LibraryScope.COMPILE, true)));
execute(packager, (callback) -> callback.library(newLibrary(nestedFile, LibraryScope.COMPILE, true)));
assertThat(getPackagedEntry(name).getComment()).startsWith("UNPACK:");
}
@ -437,7 +437,7 @@ abstract class AbstractPackagerTests<P extends Packager> {
File toZip = new File(this.tempDir, "to-zip");
toZip.createNewFile();
ZipUtil.packEntry(toZip, nestedFile);
callback.library(new Library(nestedFile, LibraryScope.COMPILE));
callback.library(newLibrary(nestedFile, LibraryScope.COMPILE, false));
});
assertThat(getPackagedEntry("BOOT-INF/lib/" + nestedFile.getName()).getSize()).isEqualTo(sourceLength);
}
@ -498,14 +498,14 @@ abstract class AbstractPackagerTests<P extends Packager> {
@Test
void loaderIsWrittenFirstThenApplicationClassesThenLibraries() throws IOException {
this.testJarFile.addClass("com/example/Application.class", ClassWithMainMethod.class);
File libraryOne = createLibrary();
File libraryTwo = createLibrary();
File libraryThree = createLibrary();
File libraryOne = createLibraryJar();
File libraryTwo = createLibraryJar();
File libraryThree = createLibraryJar();
P packager = createPackager();
execute(packager, (callback) -> {
callback.library(new Library(libraryOne, LibraryScope.COMPILE, false));
callback.library(new Library(libraryTwo, LibraryScope.COMPILE, true));
callback.library(new Library(libraryThree, LibraryScope.COMPILE, false));
callback.library(newLibrary(libraryOne, LibraryScope.COMPILE, false));
callback.library(newLibrary(libraryTwo, LibraryScope.COMPILE, true));
callback.library(newLibrary(libraryThree, LibraryScope.COMPILE, false));
});
assertThat(getPackagedEntryNames()).containsSubsequence("org/springframework/boot/loader/",
"BOOT-INF/classes/com/example/Application.class", "BOOT-INF/lib/" + libraryOne.getName(),
@ -514,12 +514,12 @@ abstract class AbstractPackagerTests<P extends Packager> {
@Test
void existingEntryThatMatchesUnpackLibraryIsMarkedForUnpack() throws IOException {
File library = createLibrary();
File library = createLibraryJar();
this.testJarFile.addClass("WEB-INF/classes/com/example/Application.class", ClassWithMainMethod.class);
this.testJarFile.addFile("WEB-INF/lib/" + library.getName(), library);
P packager = createPackager(this.testJarFile.getFile("war"));
packager.setLayout(new Layouts.War());
execute(packager, (callback) -> callback.library(new Library(library, LibraryScope.COMPILE, true)));
execute(packager, (callback) -> callback.library(newLibrary(library, LibraryScope.COMPILE, true)));
assertThat(getPackagedEntryNames()).containsSubsequence("org/springframework/boot/loader/",
"WEB-INF/classes/com/example/Application.class", "WEB-INF/lib/" + library.getName());
ZipEntry unpackLibrary = getPackagedEntry("WEB-INF/lib/" + library.getName());
@ -536,7 +536,7 @@ abstract class AbstractPackagerTests<P extends Packager> {
Layout layout = mock(Layout.class);
LibraryScope scope = mock(LibraryScope.class);
packager.setLayout(layout);
execute(packager, (callback) -> callback.library(new Library(libJarFile, scope)));
execute(packager, (callback) -> callback.library(newLibrary(libJarFile, scope, false)));
assertThat(getPackagedEntryNames()).containsExactly("META-INF/", "META-INF/MANIFEST.MF", "a/", "a/b/",
"a/b/C.class");
}
@ -583,12 +583,39 @@ abstract class AbstractPackagerTests<P extends Packager> {
assertThat(getPackagedEntry("BOOT-INF/classes/META-INF/test.kotlin_module")).isNotNull();
}
private File createLibrary() throws IOException {
@Test
void entryFiltering() throws Exception {
File webLibrary = createLibraryJar();
File libraryOne = createLibraryJar();
File libraryTwo = createLibraryJar();
this.testJarFile.addClass("WEB-INF/classes/com/example/Application.class", ClassWithMainMethod.class);
this.testJarFile.addFile("WEB-INF/lib/" + webLibrary.getName(), webLibrary);
P packager = createPackager(this.testJarFile.getFile("war"));
packager.setLayout(new Layouts.War());
execute(packager, (callback) -> {
callback.library(newLibrary(webLibrary, LibraryScope.COMPILE, false, false));
callback.library(newLibrary(libraryOne, LibraryScope.COMPILE, false, false));
callback.library(newLibrary(libraryTwo, LibraryScope.COMPILE, false, true));
});
Collection<String> packagedEntryNames = getPackagedEntryNames();
packagedEntryNames.removeIf((name) -> !name.endsWith(".jar"));
assertThat(packagedEntryNames).containsExactly("WEB-INF/lib/" + libraryTwo.getName());
}
private File createLibraryJar() throws IOException {
TestJarFile library = new TestJarFile(this.tempDir);
library.addClass("com/example/library/Library.class", ClassWithoutMainMethod.class);
return library.getFile();
}
private Library newLibrary(File file, LibraryScope scope, boolean unpackRequired) {
return new Library(null, file, scope, null, unpackRequired, false, true);
}
private Library newLibrary(File file, LibraryScope scope, boolean unpackRequired, boolean included) {
return new Library(null, file, scope, null, unpackRequired, false, included);
}
protected final P createPackager() throws IOException {
return createPackager(this.testJarFile.getFile());
}

@ -190,4 +190,13 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests {
});
}
@TestTemplate
void whenEntryIsExcludedItShouldNotBePresentInTheRepackagedWar(MavenBuild mavenBuild) {
mavenBuild.project("war-exclude-entry").execute((project) -> {
File war = new File(project, "target/war-exclude-entry-0.0.1.BUILD-SNAPSHOT.war");
assertThat(jar(war)).hasEntryWithNameStartingWith("WEB-INF/lib/spring-context")
.doesNotHaveEntryWithNameStartingWith("WEB-INF/lib/spring-core");
});
}
}

@ -0,0 +1,63 @@
<?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>war-exclude-entry</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<packaging>war</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>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclude>
</excludes>
</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>
</dependencies>
</project>

@ -0,0 +1,24 @@
/*
* 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) {
}
}

@ -76,7 +76,7 @@ public abstract class AbstractDependencyFilterMojo extends AbstractMojo {
this.excludeGroupIds = excludeGroupIds;
}
protected Set<Artifact> filterDependencies(Set<Artifact> dependencies, FilterArtifacts filters)
protected final Set<Artifact> filterDependencies(Set<Artifact> dependencies, FilterArtifacts filters)
throws MojoExecutionException {
try {
Set<Artifact> filtered = new LinkedHashSet<>(dependencies);

@ -182,11 +182,9 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo
* @throws MojoExecutionException on execution error
*/
protected final Libraries getLibraries(Collection<Dependency> unpacks) throws MojoExecutionException {
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());
Set<Artifact> artifacts = this.project.getArtifacts();
Set<Artifact> includedArtifacts = filterDependencies(artifacts, getFilters(getAdditionalFilters()));
return new ArtifactsLibraries(artifacts, includedArtifacts, this.session.getProjects(), unpacks, getLog());
}
private ArtifactsFilter[] getAdditionalFilters() {

@ -16,6 +16,7 @@
package org.springframework.boot.maven;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
@ -59,6 +60,8 @@ public class ArtifactsLibraries implements Libraries {
private final Set<Artifact> artifacts;
private final Set<Artifact> includedArtifacts;
private final Collection<MavenProject> localProjects;
private final Collection<Dependency> unpacks;
@ -89,7 +92,23 @@ public class ArtifactsLibraries implements Libraries {
*/
public ArtifactsLibraries(Set<Artifact> artifacts, Collection<MavenProject> localProjects,
Collection<Dependency> unpacks, Log log) {
this(artifacts, artifacts, localProjects, unpacks, log);
}
/**
* Creates a new {@code ArtifactsLibraries} from the given {@code artifacts}.
* @param artifacts all artifacts that can be represented as libraries
* @param includedArtifacts the actual artifacts to include in the fat jar
* @param localProjects projects for which {@link Library#isLocal() local} libraries
* should be created
* @param unpacks artifacts that should be unpacked on launch
* @param log the log
* @since 2.4.8
*/
public ArtifactsLibraries(Set<Artifact> artifacts, Set<Artifact> includedArtifacts,
Collection<MavenProject> localProjects, Collection<Dependency> unpacks, Log log) {
this.artifacts = artifacts;
this.includedArtifacts = includedArtifacts;
this.localProjects = localProjects;
this.unpacks = unpacks;
this.log = log;
@ -99,18 +118,22 @@ public class ArtifactsLibraries implements Libraries {
public void doWithLibraries(LibraryCallback callback) throws IOException {
Set<String> duplicates = getDuplicates(this.artifacts);
for (Artifact artifact : this.artifacts) {
String name = getFileName(artifact);
File file = artifact.getFile();
LibraryScope scope = SCOPES.get(artifact.getScope());
if (scope != null && artifact.getFile() != null) {
String name = getFileName(artifact);
if (duplicates.contains(name)) {
this.log.debug("Duplicate found: " + name);
name = artifact.getGroupId() + "-" + name;
this.log.debug("Renamed to: " + name);
}
LibraryCoordinates coordinates = new ArtifactLibraryCoordinates(artifact);
callback.library(new Library(name, artifact.getFile(), scope, coordinates, isUnpackRequired(artifact),
isLocal(artifact)));
if (scope == null || file == null) {
continue;
}
if (duplicates.contains(name)) {
this.log.debug("Duplicate found: " + name);
name = artifact.getGroupId() + "-" + name;
this.log.debug("Renamed to: " + name);
}
LibraryCoordinates coordinates = new ArtifactLibraryCoordinates(artifact);
boolean unpackRequired = isUnpackRequired(artifact);
boolean local = isLocal(artifact);
boolean included = this.includedArtifacts.contains(artifact);
callback.library(new Library(name, file, scope, coordinates, unpackRequired, local, included));
}
}

@ -189,8 +189,8 @@ public class BuildImageMojo extends AbstractPackagerMojo {
}
/**
* Return the layout factory that will be used to determine the {@link LayoutType} if
* no explicit layout is set.
* Return the layout factory that will be used to determine the
* {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set.
* @return the value of the {@code layoutFactory} parameter, or {@code null} if the
* parameter is not provided
*/

@ -183,8 +183,8 @@ public class RepackageMojo extends AbstractPackagerMojo {
}
/**
* Return the layout factory that will be used to determine the {@link LayoutType} if
* no explicit layout is set.
* Return the layout factory that will be used to determine the
* {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set.
* @return the value of the {@code layoutFactory} parameter, or {@code null} if the
* parameter is not provided
*/

@ -144,6 +144,7 @@ class ArtifactsLibrariesTests {
this.artifacts = Collections.singleton(snapshotArtifact);
new ArtifactsLibraries(this.artifacts, Collections.emptyList(), null, mock(Log.class))
.doWithLibraries((library) -> {
assertThat(library.isIncluded()).isTrue();
assertThat(library.isLocal()).isFalse();
assertThat(library.getCoordinates().getVersion()).isEqualTo("1.0-SNAPSHOT");
});
@ -181,4 +182,19 @@ class ArtifactsLibrariesTests {
.doWithLibraries((library) -> assertThat(library.isLocal()).isTrue());
}
@Test
void nonIncludedArtifact() throws IOException {
Artifact artifact = mock(Artifact.class);
given(artifact.getScope()).willReturn("compile");
given(artifact.getArtifactId()).willReturn("artifact");
given(artifact.getBaseVersion()).willReturn("1.0-SNAPSHOT");
given(artifact.getFile()).willReturn(new File("a"));
given(artifact.getArtifactHandler()).willReturn(this.artifactHandler);
MavenProject mavenProject = mock(MavenProject.class);
given(mavenProject.getArtifact()).willReturn(artifact);
this.artifacts = Collections.singleton(artifact);
new ArtifactsLibraries(this.artifacts, Collections.emptySet(), Collections.singleton(mavenProject), null,
mock(Log.class)).doWithLibraries((library) -> assertThat(library.isIncluded()).isFalse());
}
}

Loading…
Cancel
Save