mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-10-10 03:19:01 +00:00
Support provided-scope libraries when extracting an executable war
See gh-51372 Signed-off-by: Sumit Kumar Das <151006536+skdas20@users.noreply.github.com>
This commit is contained in:
+5
-2
@@ -86,14 +86,17 @@ class BootArchiveSupport {
|
||||
}
|
||||
|
||||
void configureManifest(Manifest manifest, String mainClass, String classes, String lib,
|
||||
@Nullable String classPathIndex, @Nullable String layersIndex, String jdkVersion,
|
||||
String implementationTitle, @Nullable Object implementationVersion) {
|
||||
@Nullable String libProvided, @Nullable String classPathIndex, @Nullable String layersIndex,
|
||||
String jdkVersion, String implementationTitle, @Nullable Object implementationVersion) {
|
||||
Attributes attributes = manifest.getAttributes();
|
||||
attributes.putIfAbsent("Main-Class", this.loaderMainClass);
|
||||
attributes.putIfAbsent("Start-Class", mainClass);
|
||||
attributes.computeIfAbsent("Spring-Boot-Version", (name) -> determineSpringBootVersion());
|
||||
attributes.putIfAbsent("Spring-Boot-Classes", classes);
|
||||
attributes.putIfAbsent("Spring-Boot-Lib", lib);
|
||||
if (libProvided != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Lib-Provided", libProvided);
|
||||
}
|
||||
if (classPathIndex != null) {
|
||||
attributes.putIfAbsent("Spring-Boot-Classpath-Index", classPathIndex);
|
||||
}
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ public abstract class BootJar extends Jar implements BootArchive {
|
||||
|
||||
@Override
|
||||
public void copy() {
|
||||
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY,
|
||||
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, null,
|
||||
CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX,
|
||||
this.getTargetJavaVersion().get().getMajorVersion(), this.projectName.get(), this.projectVersion.get());
|
||||
super.copy();
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ public abstract class BootWar extends War implements BootArchive {
|
||||
@Override
|
||||
public void copy() {
|
||||
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY,
|
||||
CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX,
|
||||
LIB_PROVIDED_DIRECTORY, CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX,
|
||||
this.getTargetJavaVersion().get().getMajorVersion(), this.projectName.get(), this.projectVersion.get());
|
||||
super.copy();
|
||||
}
|
||||
|
||||
+19
-3
@@ -49,7 +49,7 @@ import org.springframework.util.StringUtils;
|
||||
class IndexedJarStructure implements JarStructure {
|
||||
|
||||
private static final List<String> MANIFEST_DENY_LIST = List.of("Start-Class", "Spring-Boot-Classes",
|
||||
"Spring-Boot-Lib", "Spring-Boot-Classpath-Index", "Spring-Boot-Layers-Index");
|
||||
"Spring-Boot-Lib", "Spring-Boot-Lib-Provided", "Spring-Boot-Classpath-Index", "Spring-Boot-Layers-Index");
|
||||
|
||||
private static final Set<String> ENTRY_IGNORE_LIST = Set.of("META-INF/", "META-INF/MANIFEST.MF",
|
||||
"META-INF/services/java.nio.file.spi.FileSystemProvider");
|
||||
@@ -58,6 +58,8 @@ class IndexedJarStructure implements JarStructure {
|
||||
|
||||
private final String libLocation;
|
||||
|
||||
private final @Nullable String providedLibLocation;
|
||||
|
||||
private final String classesLocation;
|
||||
|
||||
private final Set<String> classpathEntries;
|
||||
@@ -65,6 +67,7 @@ class IndexedJarStructure implements JarStructure {
|
||||
IndexedJarStructure(Manifest originalManifest, String indexFile) {
|
||||
this.originalManifest = originalManifest;
|
||||
this.libLocation = getLocation(originalManifest, "Spring-Boot-Lib");
|
||||
this.providedLibLocation = getOptionalLocation(originalManifest, "Spring-Boot-Lib-Provided");
|
||||
this.classesLocation = getLocation(originalManifest, "Spring-Boot-Classes");
|
||||
this.classpathEntries = readIndexFile(indexFile);
|
||||
}
|
||||
@@ -74,6 +77,14 @@ class IndexedJarStructure implements JarStructure {
|
||||
return (!location.endsWith("/")) ? location + "/" : location;
|
||||
}
|
||||
|
||||
private static @Nullable String getOptionalLocation(Manifest manifest, String attribute) {
|
||||
String location = manifest.getMainAttributes().getValue(attribute);
|
||||
if (!StringUtils.hasLength(location)) {
|
||||
return null;
|
||||
}
|
||||
return (!location.endsWith("/")) ? location + "/" : location;
|
||||
}
|
||||
|
||||
private static Set<String> readIndexFile(String indexFile) {
|
||||
String[] lines = Arrays.stream(indexFile.split("\n"))
|
||||
.map((line) -> line.replace("\r", ""))
|
||||
@@ -130,8 +141,13 @@ class IndexedJarStructure implements JarStructure {
|
||||
}
|
||||
|
||||
private String toStructureDependency(String libEntryName) {
|
||||
Assert.state(libEntryName.startsWith(this.libLocation), () -> "Invalid library location " + libEntryName);
|
||||
return libEntryName.substring(this.libLocation.length());
|
||||
if (libEntryName.startsWith(this.libLocation)) {
|
||||
return libEntryName.substring(this.libLocation.length());
|
||||
}
|
||||
if (this.providedLibLocation != null && libEntryName.startsWith(this.providedLibLocation)) {
|
||||
return libEntryName.substring(this.providedLibLocation.length());
|
||||
}
|
||||
throw new IllegalStateException("Invalid library location " + libEntryName);
|
||||
}
|
||||
|
||||
private static String getMandatoryAttribute(Manifest manifest, String attribute) {
|
||||
|
||||
+71
@@ -35,6 +35,7 @@ import org.springframework.boot.jarmode.tools.JarStructure.Entry;
|
||||
import org.springframework.boot.jarmode.tools.JarStructure.Entry.Type;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link IndexedJarStructure}.
|
||||
@@ -53,6 +54,24 @@ class IndexedJarStructureTests {
|
||||
assertThat(entry.type()).isEqualTo(Type.LIBRARY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveLibraryEntryFromWarProvidedLocation() throws IOException {
|
||||
IndexedJarStructure structure = createWarStructure();
|
||||
Entry entry = structure.resolve("WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar");
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(entry.location()).isEqualTo("tomcat-embed-core-10.1.19.jar");
|
||||
assertThat(entry.originalLocation()).isEqualTo("WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar");
|
||||
assertThat(entry.type()).isEqualTo(Type.LIBRARY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateLauncherManifestForWarWithProvidedLibraries() throws IOException {
|
||||
IndexedJarStructure structure = createWarStructure();
|
||||
Manifest manifest = structure.createLauncherManifest(UnaryOperator.identity());
|
||||
assertThat(getAttributes(manifest)).containsEntry("Class-Path",
|
||||
"spring-webmvc-6.1.4.jar tomcat-embed-core-10.1.19.jar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveApplicationEntry() throws IOException {
|
||||
IndexedJarStructure structure = createStructure();
|
||||
@@ -119,10 +138,62 @@ class IndexedJarStructureTests {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailToResolveProvidedLibraryWhenAttributeIsMissing() throws IOException {
|
||||
// an archive whose index references lib-provided but whose manifest does not
|
||||
// record the location has nothing to flatten the entry against
|
||||
IndexedJarStructure structure = createWarStructureWithoutProvidedLibAttribute();
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> structure.resolve("WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar"))
|
||||
.withMessageContaining("Invalid library location");
|
||||
}
|
||||
|
||||
@Test
|
||||
void launcherManifestShouldNotContainProvidedLibAttribute() throws IOException {
|
||||
IndexedJarStructure structure = createWarStructure();
|
||||
Manifest manifest = structure.createLauncherManifest(UnaryOperator.identity());
|
||||
assertThat(getAttributes(manifest)).doesNotContainKey("Spring-Boot-Lib-Provided");
|
||||
}
|
||||
|
||||
private IndexedJarStructure createStructure() throws IOException {
|
||||
return new IndexedJarStructure(createManifest(), createIndexFile());
|
||||
}
|
||||
|
||||
private IndexedJarStructure createWarStructure() throws IOException {
|
||||
Manifest manifest = new Manifest(new ByteArrayInputStream("""
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: org.springframework.boot.loader.launch.WarLauncher
|
||||
Start-Class: org.springframework.boot.jarmode.tools.IndexedJarStructureTests
|
||||
Spring-Boot-Version: 3.3.0-SNAPSHOT
|
||||
Spring-Boot-Classes: WEB-INF/classes/
|
||||
Spring-Boot-Lib: WEB-INF/lib/
|
||||
Spring-Boot-Lib-Provided: WEB-INF/lib-provided/
|
||||
Spring-Boot-Classpath-Index: WEB-INF/classpath.idx
|
||||
""".getBytes(StandardCharsets.UTF_8)));
|
||||
String indexFile = """
|
||||
- "WEB-INF/lib/spring-webmvc-6.1.4.jar"
|
||||
- "WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar"
|
||||
""";
|
||||
return new IndexedJarStructure(manifest, indexFile);
|
||||
}
|
||||
|
||||
private IndexedJarStructure createWarStructureWithoutProvidedLibAttribute() throws IOException {
|
||||
Manifest manifest = new Manifest(new ByteArrayInputStream("""
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: org.springframework.boot.loader.launch.WarLauncher
|
||||
Start-Class: org.springframework.boot.jarmode.tools.IndexedJarStructureTests
|
||||
Spring-Boot-Version: 3.3.0-SNAPSHOT
|
||||
Spring-Boot-Classes: WEB-INF/classes/
|
||||
Spring-Boot-Lib: WEB-INF/lib/
|
||||
Spring-Boot-Classpath-Index: WEB-INF/classpath.idx
|
||||
""".getBytes(StandardCharsets.UTF_8)));
|
||||
String indexFile = """
|
||||
- "WEB-INF/lib/spring-webmvc-6.1.4.jar"
|
||||
- "WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar"
|
||||
""";
|
||||
return new IndexedJarStructure(manifest, indexFile);
|
||||
}
|
||||
|
||||
private String createIndexFile() {
|
||||
return """
|
||||
- "BOOT-INF/lib/spring-webmvc-6.1.4.jar"
|
||||
|
||||
+9
-1
@@ -45,6 +45,7 @@ import org.springframework.boot.loader.tools.AbstractJarWriter.EntryTransformer;
|
||||
import org.springframework.boot.loader.tools.AbstractJarWriter.UnpackHandler;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -73,6 +74,8 @@ public abstract class Packager {
|
||||
|
||||
private static final String BOOT_LIB_ATTRIBUTE = "Spring-Boot-Lib";
|
||||
|
||||
private static final String BOOT_LIB_PROVIDED_ATTRIBUTE = "Spring-Boot-Lib-Provided";
|
||||
|
||||
private static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index";
|
||||
|
||||
private static final String BOOT_LAYERS_INDEX_ATTRIBUTE = "Spring-Boot-Layers-Index";
|
||||
@@ -413,7 +416,12 @@ public abstract class Packager {
|
||||
else {
|
||||
attributes.putValue(BOOT_CLASSES_ATTRIBUTE, layout.getClassesLocation());
|
||||
}
|
||||
putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, getLayout().getLibraryLocation("", LibraryScope.COMPILE));
|
||||
String libraryLocation = getLayout().getLibraryLocation("", LibraryScope.COMPILE);
|
||||
putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, libraryLocation);
|
||||
String providedLibraryLocation = getLayout().getLibraryLocation("", LibraryScope.PROVIDED);
|
||||
if (!ObjectUtils.nullSafeEquals(providedLibraryLocation, libraryLocation)) {
|
||||
putIfHasLength(attributes, BOOT_LIB_PROVIDED_ATTRIBUTE, providedLibraryLocation);
|
||||
}
|
||||
putIfHasLength(attributes, BOOT_CLASSPATH_INDEX_ATTRIBUTE, layout.getClasspathIndexFileLocation());
|
||||
if (isLayered()) {
|
||||
putIfHasLength(attributes, BOOT_LAYERS_INDEX_ATTRIBUTE, layout.getLayersIndexFileLocation());
|
||||
|
||||
Reference in New Issue
Block a user