commit
2c2b962204
|
|
@ -22,11 +22,9 @@ import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.HashSet;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
import org.apache.commons.compress.archivers.zip.UnixStat;
|
import org.apache.commons.compress.archivers.zip.UnixStat;
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
|
|
@ -34,12 +32,9 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||||
import org.gradle.api.GradleException;
|
import org.gradle.api.GradleException;
|
||||||
import org.gradle.api.file.FileCopyDetails;
|
import org.gradle.api.file.FileCopyDetails;
|
||||||
import org.gradle.api.file.FileTreeElement;
|
import org.gradle.api.file.FileTreeElement;
|
||||||
import org.gradle.api.internal.file.CopyActionProcessingStreamAction;
|
|
||||||
import org.gradle.api.internal.file.copy.CopyAction;
|
import org.gradle.api.internal.file.copy.CopyAction;
|
||||||
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
|
import org.gradle.api.internal.file.copy.CopyActionProcessingStream;
|
||||||
import org.gradle.api.internal.file.copy.FileCopyDetailsInternal;
|
|
||||||
import org.gradle.api.specs.Spec;
|
import org.gradle.api.specs.Spec;
|
||||||
import org.gradle.api.specs.Specs;
|
|
||||||
import org.gradle.api.tasks.WorkResult;
|
import org.gradle.api.tasks.WorkResult;
|
||||||
|
|
||||||
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
||||||
|
|
@ -50,6 +45,7 @@ import org.springframework.boot.loader.tools.FileUtils;
|
||||||
* Stores jar files without compression as required by Spring Boot's loader.
|
* Stores jar files without compression as required by Spring Boot's loader.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
class BootZipCopyAction implements CopyAction {
|
class BootZipCopyAction implements CopyAction {
|
||||||
|
|
||||||
|
|
@ -88,191 +84,155 @@ class BootZipCopyAction implements CopyAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WorkResult execute(CopyActionProcessingStream stream) {
|
public WorkResult execute(CopyActionProcessingStream stream) {
|
||||||
ZipArchiveOutputStream zipStream;
|
|
||||||
Spec<FileTreeElement> loaderEntries;
|
|
||||||
try {
|
try {
|
||||||
FileOutputStream fileStream = new FileOutputStream(this.output);
|
writeArchive(stream);
|
||||||
writeLaunchScriptIfNecessary(fileStream);
|
return () -> true;
|
||||||
zipStream = new ZipArchiveOutputStream(fileStream);
|
|
||||||
if (this.encoding != null) {
|
|
||||||
zipStream.setEncoding(this.encoding);
|
|
||||||
}
|
|
||||||
loaderEntries = writeLoaderClassesIfNecessary(zipStream);
|
|
||||||
}
|
}
|
||||||
catch (IOException ex) {
|
catch (IOException ex) {
|
||||||
throw new GradleException("Failed to create " + this.output, ex);
|
throw new GradleException("Failed to create " + this.output, ex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeArchive(CopyActionProcessingStream stream) throws IOException {
|
||||||
|
OutputStream outputStream = new FileOutputStream(this.output);
|
||||||
try {
|
try {
|
||||||
stream.process(new ZipStreamAction(zipStream, this.output, this.preserveFileTimestamps, this.requiresUnpack,
|
writeLaunchScriptIfNecessary(outputStream);
|
||||||
createExclusionSpec(loaderEntries), this.compressionResolver));
|
ZipArchiveOutputStream zipOutputStream = new ZipArchiveOutputStream(outputStream);
|
||||||
|
try {
|
||||||
|
if (this.encoding != null) {
|
||||||
|
zipOutputStream.setEncoding(this.encoding);
|
||||||
|
}
|
||||||
|
Processor processor = new Processor(zipOutputStream);
|
||||||
|
stream.process(processor::process);
|
||||||
|
processor.finish();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
closeQuietly(zipOutputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
closeQuietly(outputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLaunchScriptIfNecessary(OutputStream outputStream) {
|
||||||
|
if (this.launchScript == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
zipStream.close();
|
File file = this.launchScript.getScript();
|
||||||
}
|
Map<String, String> properties = this.launchScript.getProperties();
|
||||||
catch (IOException ex) {
|
outputStream.write(new DefaultLaunchScript(file, properties).toByteArray());
|
||||||
// Continue
|
outputStream.flush();
|
||||||
}
|
|
||||||
}
|
|
||||||
return () -> true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Spec<FileTreeElement> createExclusionSpec(Spec<FileTreeElement> loaderEntries) {
|
|
||||||
return Specs.union(loaderEntries, this.exclusions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Spec<FileTreeElement> writeLoaderClassesIfNecessary(ZipArchiveOutputStream out) {
|
|
||||||
if (!this.includeDefaultLoader) {
|
|
||||||
return Specs.satisfyNone();
|
|
||||||
}
|
|
||||||
return writeLoaderClasses(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Spec<FileTreeElement> writeLoaderClasses(ZipArchiveOutputStream out) {
|
|
||||||
try (ZipInputStream in = new ZipInputStream(
|
|
||||||
getClass().getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) {
|
|
||||||
Set<String> entries = new HashSet<>();
|
|
||||||
java.util.zip.ZipEntry entry;
|
|
||||||
while ((entry = in.getNextEntry()) != null) {
|
|
||||||
if (entry.isDirectory() && !entry.getName().startsWith("META-INF/")) {
|
|
||||||
writeDirectory(new ZipArchiveEntry(entry), out);
|
|
||||||
entries.add(entry.getName());
|
|
||||||
}
|
|
||||||
else if (entry.getName().endsWith(".class")) {
|
|
||||||
writeClass(new ZipArchiveEntry(entry), in, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (element) -> {
|
|
||||||
String path = element.getRelativePath().getPathString();
|
|
||||||
if (element.isDirectory() && !path.endsWith(("/"))) {
|
|
||||||
path += "/";
|
|
||||||
}
|
|
||||||
return entries.contains(path);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
throw new GradleException("Failed to write loader classes", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException {
|
|
||||||
prepareEntry(entry, UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM);
|
|
||||||
out.putArchiveEntry(entry);
|
|
||||||
out.closeArchiveEntry();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeClass(ZipArchiveEntry entry, ZipInputStream in, ZipArchiveOutputStream out) throws IOException {
|
|
||||||
prepareEntry(entry, UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM);
|
|
||||||
out.putArchiveEntry(entry);
|
|
||||||
byte[] buffer = new byte[4096];
|
|
||||||
int read;
|
|
||||||
while ((read = in.read(buffer)) > 0) {
|
|
||||||
out.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
out.closeArchiveEntry();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareEntry(ZipArchiveEntry entry, int unixMode) {
|
|
||||||
if (!this.preserveFileTimestamps) {
|
|
||||||
entry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES);
|
|
||||||
}
|
|
||||||
entry.setUnixMode(unixMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeLaunchScriptIfNecessary(FileOutputStream fileStream) {
|
|
||||||
try {
|
|
||||||
if (this.launchScript != null) {
|
|
||||||
fileStream
|
|
||||||
.write(new DefaultLaunchScript(this.launchScript.getScript(), this.launchScript.getProperties())
|
|
||||||
.toByteArray());
|
|
||||||
this.output.setExecutable(true);
|
this.output.setExecutable(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
catch (IOException ex) {
|
||||||
throw new GradleException("Failed to write launch script to " + this.output, ex);
|
throw new GradleException("Failed to write launch script to " + this.output, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ZipStreamAction implements CopyActionProcessingStreamAction {
|
private void closeQuietly(OutputStream outputStream) {
|
||||||
|
try {
|
||||||
private final ZipArchiveOutputStream zipStream;
|
outputStream.close();
|
||||||
|
}
|
||||||
private final File output;
|
catch (IOException ex) {
|
||||||
|
}
|
||||||
private final boolean preserveFileTimestamps;
|
|
||||||
|
|
||||||
private final Spec<FileTreeElement> requiresUnpack;
|
|
||||||
|
|
||||||
private final Spec<FileTreeElement> exclusions;
|
|
||||||
|
|
||||||
private final Function<FileCopyDetails, ZipCompression> compressionType;
|
|
||||||
|
|
||||||
private ZipStreamAction(ZipArchiveOutputStream zipStream, File output, boolean preserveFileTimestamps,
|
|
||||||
Spec<FileTreeElement> requiresUnpack, Spec<FileTreeElement> exclusions,
|
|
||||||
Function<FileCopyDetails, ZipCompression> compressionType) {
|
|
||||||
this.zipStream = zipStream;
|
|
||||||
this.output = output;
|
|
||||||
this.preserveFileTimestamps = preserveFileTimestamps;
|
|
||||||
this.requiresUnpack = requiresUnpack;
|
|
||||||
this.exclusions = exclusions;
|
|
||||||
this.compressionType = compressionType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void processFile(FileCopyDetailsInternal details) {
|
* Internal process used to copy {@link FileCopyDetails file details} to the zip file.
|
||||||
if (this.exclusions.isSatisfiedBy(details)) {
|
*/
|
||||||
|
private class Processor {
|
||||||
|
|
||||||
|
private ZipArchiveOutputStream outputStream;
|
||||||
|
|
||||||
|
private Spec<FileTreeElement> writtenLoaderEntries;
|
||||||
|
|
||||||
|
Processor(ZipArchiveOutputStream outputStream) {
|
||||||
|
this.outputStream = outputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void process(FileCopyDetails details) {
|
||||||
|
if (BootZipCopyAction.this.exclusions.isSatisfiedBy(details)
|
||||||
|
|| (this.writtenLoaderEntries != null && this.writtenLoaderEntries.isSatisfiedBy(details))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
writeLoaderEntriesIfNecessary(details);
|
||||||
if (details.isDirectory()) {
|
if (details.isDirectory()) {
|
||||||
createDirectory(details);
|
processDirectory(details);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
createFile(details);
|
processFile(details);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException ex) {
|
catch (IOException ex) {
|
||||||
throw new GradleException("Failed to add " + details + " to " + this.output, ex);
|
throw new GradleException("Failed to add " + details + " to " + BootZipCopyAction.this.output, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createDirectory(FileCopyDetailsInternal details) throws IOException {
|
public void finish() throws IOException {
|
||||||
|
writeLoaderEntriesIfNecessary(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLoaderEntriesIfNecessary(FileCopyDetails details) throws IOException {
|
||||||
|
if (!BootZipCopyAction.this.includeDefaultLoader || this.writtenLoaderEntries != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isInMetaInf(details)) {
|
||||||
|
// Don't write loader entries until after META-INF folder (see gh-16698)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LoaderZipEntries loaderEntries = new LoaderZipEntries(
|
||||||
|
BootZipCopyAction.this.preserveFileTimestamps ? null : CONSTANT_TIME_FOR_ZIP_ENTRIES);
|
||||||
|
this.writtenLoaderEntries = loaderEntries.writeTo(this.outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInMetaInf(FileCopyDetails details) {
|
||||||
|
if (details == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String[] segments = details.getRelativePath().getSegments();
|
||||||
|
return segments.length > 0 && "META-INF".equals(segments[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processDirectory(FileCopyDetails details) throws IOException {
|
||||||
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(details.getRelativePath().getPathString() + '/');
|
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(details.getRelativePath().getPathString() + '/');
|
||||||
archiveEntry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
|
archiveEntry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
|
||||||
archiveEntry.setTime(getTime(details));
|
archiveEntry.setTime(getTime(details));
|
||||||
this.zipStream.putArchiveEntry(archiveEntry);
|
this.outputStream.putArchiveEntry(archiveEntry);
|
||||||
this.zipStream.closeArchiveEntry();
|
this.outputStream.closeArchiveEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createFile(FileCopyDetailsInternal details) throws IOException {
|
private void processFile(FileCopyDetails details) throws IOException {
|
||||||
String relativePath = details.getRelativePath().getPathString();
|
String relativePath = details.getRelativePath().getPathString();
|
||||||
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(relativePath);
|
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(relativePath);
|
||||||
archiveEntry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
|
archiveEntry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
|
||||||
archiveEntry.setTime(getTime(details));
|
archiveEntry.setTime(getTime(details));
|
||||||
ZipCompression compression = this.compressionType.apply(details);
|
ZipCompression compression = BootZipCopyAction.this.compressionResolver.apply(details);
|
||||||
if (compression == ZipCompression.STORED) {
|
if (compression == ZipCompression.STORED) {
|
||||||
prepareStoredEntry(details, archiveEntry);
|
prepareStoredEntry(details, archiveEntry);
|
||||||
}
|
}
|
||||||
this.zipStream.putArchiveEntry(archiveEntry);
|
this.outputStream.putArchiveEntry(archiveEntry);
|
||||||
details.copyTo(this.zipStream);
|
details.copyTo(this.outputStream);
|
||||||
this.zipStream.closeArchiveEntry();
|
this.outputStream.closeArchiveEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareStoredEntry(FileCopyDetailsInternal details, ZipArchiveEntry archiveEntry)
|
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
|
||||||
throws IOException {
|
|
||||||
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
archiveEntry.setMethod(java.util.zip.ZipEntry.STORED);
|
||||||
archiveEntry.setSize(details.getSize());
|
archiveEntry.setSize(details.getSize());
|
||||||
archiveEntry.setCompressedSize(details.getSize());
|
archiveEntry.setCompressedSize(details.getSize());
|
||||||
Crc32OutputStream crcStream = new Crc32OutputStream();
|
Crc32OutputStream crcStream = new Crc32OutputStream();
|
||||||
details.copyTo(crcStream);
|
details.copyTo(crcStream);
|
||||||
archiveEntry.setCrc(crcStream.getCrc());
|
archiveEntry.setCrc(crcStream.getCrc());
|
||||||
if (this.requiresUnpack.isSatisfiedBy(details)) {
|
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
|
||||||
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
|
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getTime(FileCopyDetails details) {
|
private long getTime(FileCopyDetails details) {
|
||||||
return this.preserveFileTimestamps ? details.getLastModified() : CONSTANT_TIME_FOR_ZIP_ENTRIES;
|
return BootZipCopyAction.this.preserveFileTimestamps ? details.getLastModified()
|
||||||
|
: CONSTANT_TIME_FOR_ZIP_ENTRIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -282,25 +242,25 @@ class BootZipCopyAction implements CopyAction {
|
||||||
*/
|
*/
|
||||||
private static final class Crc32OutputStream extends OutputStream {
|
private static final class Crc32OutputStream extends OutputStream {
|
||||||
|
|
||||||
private final CRC32 crc32 = new CRC32();
|
private final CRC32 crc = new CRC32();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int b) throws IOException {
|
public void write(int b) throws IOException {
|
||||||
this.crc32.update(b);
|
this.crc.update(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] b) throws IOException {
|
public void write(byte[] b) throws IOException {
|
||||||
this.crc32.update(b);
|
this.crc.update(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] b, int off, int len) throws IOException {
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
this.crc32.update(b, off, len);
|
this.crc.update(b, off, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getCrc() {
|
private long getCrc() {
|
||||||
return this.crc32.getValue();
|
return this.crc.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.gradle.tasks.bundling;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
import org.apache.commons.compress.archivers.zip.UnixStat;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||||
|
import org.gradle.api.file.FileTreeElement;
|
||||||
|
import org.gradle.api.specs.Spec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal utility used to copy entries from the {@code spring-boot-loader.jar}.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
class LoaderZipEntries {
|
||||||
|
|
||||||
|
private Long entryTime;
|
||||||
|
|
||||||
|
LoaderZipEntries(Long entryTime) {
|
||||||
|
this.entryTime = entryTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Spec<FileTreeElement> writeTo(ZipArchiveOutputStream zipOutputStream) throws IOException {
|
||||||
|
WrittenDirectoriesSpec writtenDirectoriesSpec = new WrittenDirectoriesSpec();
|
||||||
|
try (ZipInputStream loaderJar = new ZipInputStream(
|
||||||
|
getClass().getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) {
|
||||||
|
java.util.zip.ZipEntry entry = loaderJar.getNextEntry();
|
||||||
|
while (entry != null) {
|
||||||
|
if (entry.isDirectory() && !entry.getName().equals("META-INF/")) {
|
||||||
|
writeDirectory(new ZipArchiveEntry(entry), zipOutputStream);
|
||||||
|
writtenDirectoriesSpec.add(entry);
|
||||||
|
}
|
||||||
|
else if (entry.getName().endsWith(".class")) {
|
||||||
|
writeClass(new ZipArchiveEntry(entry), loaderJar, zipOutputStream);
|
||||||
|
}
|
||||||
|
entry = loaderJar.getNextEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return writtenDirectoriesSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeDirectory(ZipArchiveEntry entry, ZipArchiveOutputStream out) throws IOException {
|
||||||
|
prepareEntry(entry, UnixStat.DIR_FLAG | UnixStat.DEFAULT_DIR_PERM);
|
||||||
|
out.putArchiveEntry(entry);
|
||||||
|
out.closeArchiveEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeClass(ZipArchiveEntry entry, ZipInputStream in, ZipArchiveOutputStream out) throws IOException {
|
||||||
|
prepareEntry(entry, UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM);
|
||||||
|
out.putArchiveEntry(entry);
|
||||||
|
copy(in, out);
|
||||||
|
out.closeArchiveEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareEntry(ZipArchiveEntry entry, int unixMode) {
|
||||||
|
if (this.entryTime != null) {
|
||||||
|
entry.setTime(this.entryTime);
|
||||||
|
}
|
||||||
|
entry.setUnixMode(unixMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(InputStream in, OutputStream out) throws IOException {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead = -1;
|
||||||
|
while ((bytesRead = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spec to track directories that have been written.
|
||||||
|
*/
|
||||||
|
private static class WrittenDirectoriesSpec implements Spec<FileTreeElement> {
|
||||||
|
|
||||||
|
private final Set<String> entries = new HashSet<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSatisfiedBy(FileTreeElement element) {
|
||||||
|
String path = element.getRelativePath().getPathString();
|
||||||
|
if (element.isDirectory() && !path.endsWith(("/"))) {
|
||||||
|
path += "/";
|
||||||
|
}
|
||||||
|
return this.entries.contains(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(ZipEntry entry) {
|
||||||
|
this.entries.add(entry.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package org.springframework.boot.gradle.tasks.bundling;
|
package org.springframework.boot.gradle.tasks.bundling;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
@ -34,6 +35,7 @@ import java.util.jar.JarFile;
|
||||||
import java.util.jar.JarOutputStream;
|
import java.util.jar.JarOutputStream;
|
||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||||
|
|
@ -188,13 +190,18 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void loaderIsWrittenToTheRootOfTheJar() throws IOException {
|
void loaderIsWrittenToTheRootOfTheJarAfterManifest() throws IOException {
|
||||||
this.task.setMainClassName("com.example.Main");
|
this.task.setMainClassName("com.example.Main");
|
||||||
executeTask();
|
executeTask();
|
||||||
try (JarFile jarFile = new JarFile(this.task.getArchivePath())) {
|
try (JarFile jarFile = new JarFile(this.task.getArchivePath())) {
|
||||||
assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull();
|
assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull();
|
||||||
assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull();
|
assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull();
|
||||||
}
|
}
|
||||||
|
// gh-16698
|
||||||
|
try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(this.task.getArchivePath()))) {
|
||||||
|
assertThat(zipInputStream.getNextEntry().getName()).isEqualTo("META-INF/");
|
||||||
|
assertThat(zipInputStream.getNextEntry().getName()).isEqualTo("META-INF/MANIFEST.MF");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue