Add classpath index support for exploded archives

Update the `Repackager` class so that an additional `classpath.idx` file
is written into the jar that provides the original order of the
classpath. The `JarLauncher` class now uses this file when running as
an exploded archive to ensure that the classpath order is the same as
when running from the far jar.

Closes gh-9128

Co-authored-by: Phillip Webb <pwebb@pivotal.io>
This commit is contained in:
Madhura Bhave
2020-01-15 21:44:59 -08:00
committed by Phillip Webb
parent ad72f86bdb
commit 45b1ab46c3
12 changed files with 442 additions and 3 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.boot.loader.tools;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -27,13 +28,16 @@ import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -52,6 +56,7 @@ import org.apache.commons.compress.archivers.zip.UnixStat;
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 1.0.0
*/
public class JarWriter implements LoaderClassesWriter, AutoCloseable {
@@ -190,6 +195,28 @@ public class JarWriter implements LoaderClassesWriter, AutoCloseable {
}
}
/**
* Write a simple index file containing the specified UTF-8 lines.
* @param location the location of the index file
* @param lines the lines to write
* @throws IOException if the write fails
* @since 2.3.0
*/
public void writeIndexFile(String location, List<String> lines) throws IOException {
if (location != null) {
JarArchiveEntry entry = new JarArchiveEntry(location);
writeEntry(entry, (outputStream) -> {
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
for (String line : lines) {
writer.write(line);
writer.write("\n");
}
writer.flush();
});
}
}
private long getNestedLibraryTime(File file) {
try {
try (JarFile jarFile = new JarFile(file)) {

View File

@@ -28,6 +28,7 @@ import java.util.Map;
* @author Phillip Webb
* @author Dave Syer
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 1.0.0
*/
public final class Layouts {
@@ -88,6 +89,11 @@ public final class Layouts {
return "BOOT-INF/classes/";
}
@Override
public String getClasspathIndexFileLocation() {
return "BOOT-INF/classpath.idx";
}
@Override
public boolean isExecutable() {
return true;

View File

@@ -45,6 +45,7 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Madhura Bhave
* @since 1.0.0
*/
public class Repackager {
@@ -59,6 +60,8 @@ public class Repackager {
private static final String BOOT_LIB_ATTRIBUTE = "Spring-Boot-Lib";
private static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index";
private static final byte[] ZIP_FILE_HEADER = new byte[] { 'P', 'K', 3, 4 };
private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
@@ -336,6 +339,7 @@ public class Repackager {
private void addBootBootAttributesForRepackagingLayout(Attributes attributes, RepackagingLayout layout) {
attributes.putValue(BOOT_CLASSES_ATTRIBUTE, layout.getRepackagedClassesLocation());
putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, this.layout.getLibraryLocation("", LibraryScope.COMPILE));
putIfHasLength(attributes, BOOT_CLASSPATH_INDEX_ATTRIBUTE, layout.getClasspathIndexFileLocation());
}
private void addBootBootAttributesForPlainLayout(Attributes attributes, Layout layout) {
@@ -473,6 +477,10 @@ public class Repackager {
writer.writeNestedLibrary(entry.getKey().substring(0, entry.getKey().lastIndexOf('/') + 1),
entry.getValue());
}
if (Repackager.this.layout instanceof RepackagingLayout) {
String location = ((RepackagingLayout) (Repackager.this.layout)).getClasspathIndexFileLocation();
writer.writeIndexFile(location, new ArrayList<>(this.libraryEntryNames.keySet()));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,4 +31,15 @@ public interface RepackagingLayout extends Layout {
*/
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;
}
}