diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java index 2e09aae254..0e8c4ed43e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -16,6 +16,10 @@ package org.springframework.boot.gradle.tasks.bundling; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.Collections; import java.util.HashSet; import java.util.Map; @@ -43,6 +47,8 @@ import org.gradle.api.tasks.util.PatternSet; */ class BootArchiveSupport { + private static final byte[] ZIP_FILE_HEADER = new byte[] { 'P', 'K', 3, 4 }; + private static final Set DEFAULT_LAUNCHER_CLASSES; static { @@ -120,6 +126,26 @@ class BootArchiveSupport { configureExclusions(); } + boolean isZip(File file) { + try { + try (FileInputStream fileInputStream = new FileInputStream(file)) { + return isZip(fileInputStream); + } + } + catch (IOException ex) { + return false; + } + } + + private boolean isZip(InputStream inputStream) throws IOException { + for (int i = 0; i < ZIP_FILE_HEADER.length; i++) { + if (inputStream.read() != ZIP_FILE_HEADER[i]) { + return false; + } + } + return true; + } + private void configureExclusions() { Set excludes = new HashSet<>(); if (this.excludeDevtools) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java index 43916a3c2e..d2e2a3d49c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java @@ -58,6 +58,13 @@ public class BootJar extends Jar implements BootArchive { this.bootInf.filesMatching("module-info.class", (details) -> { details.setRelativePath(details.getRelativeSourcePath()); }); + getRootSpec().eachFile((details) -> { + String pathString = details.getRelativePath().getPathString(); + if (pathString.startsWith("BOOT-INF/lib/") + && !this.support.isZip(details.getFile())) { + details.exclude(); + } + }); } private Action classpathFiles(Spec filter) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java index 96760ba93e..9378dc3860 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java @@ -57,6 +57,14 @@ public class BootWar extends War implements BootArchive { getRootSpec().filesMatching("module-info.class", (details) -> { details.setRelativePath(details.getRelativeSourcePath()); }); + getRootSpec().eachFile((details) -> { + String pathString = details.getRelativePath().getPathString(); + if ((pathString.startsWith("WEB-INF/lib/") + || pathString.startsWith("WEB-INF/lib-provided/")) + && !this.support.isZip(details.getFile())) { + details.exclude(); + } + }); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java index 5a14062d24..021d200387 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java @@ -17,11 +17,15 @@ package org.springframework.boot.gradle.docs; import java.io.File; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; import org.junit.Rule; import org.junit.Test; @@ -108,8 +112,8 @@ public class PackagingDocumentationTests { @Test public void bootWarIncludeDevtools() throws IOException { - new File(this.gradleBuild.getProjectDir(), - "spring-boot-devtools-1.2.3.RELEASE.jar").createNewFile(); + jarFile(new File(this.gradleBuild.getProjectDir(), + "spring-boot-devtools-1.2.3.RELEASE.jar")); this.gradleBuild.script("src/main/gradle/packaging/boot-war-include-devtools") .build("bootWar"); File file = new File(this.gradleBuild.getProjectDir(), @@ -196,7 +200,14 @@ public class PackagingDocumentationTests { File bootJar = new File(this.gradleBuild.getProjectDir(), "build/libs/" + this.gradleBuild.getProjectDir().getName() + "-boot.jar"); assertThat(bootJar).isFile(); + } + protected void jarFile(File file) throws IOException { + try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) { + jar.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + new Manifest().write(jar); + jar.closeEntry(); + } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java index 85483a6d69..fc304708db 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.gradle.tasks.bundling; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardOpenOption; @@ -30,6 +31,9 @@ import java.util.Map; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; @@ -108,7 +112,7 @@ public abstract class AbstractBootArchiveTests { @Test public void classpathJarsArePackagedBeneathLibPath() throws IOException { this.task.setMainClassName("com.example.Main"); - this.task.classpath(this.temp.newFile("one.jar"), this.temp.newFile("two.jar")); + this.task.classpath(jarFile("one.jar"), jarFile("two.jar")); this.task.execute(); try (JarFile jarFile = new JarFile(this.task.getArchivePath())) { assertThat(jarFile.getEntry(this.libPath + "/one.jar")).isNotNull(); @@ -160,9 +164,8 @@ public abstract class AbstractBootArchiveTests { @Test public void classpathCanBeSetUsingAFileCollection() throws IOException { this.task.setMainClassName("com.example.Main"); - this.task.classpath(this.temp.newFile("one.jar")); - this.task - .setClasspath(this.task.getProject().files(this.temp.newFile("two.jar"))); + this.task.classpath(jarFile("one.jar")); + this.task.setClasspath(this.task.getProject().files(jarFile("two.jar"))); this.task.execute(); try (JarFile jarFile = new JarFile(this.task.getArchivePath())) { assertThat(jarFile.getEntry(this.libPath + "/one.jar")).isNull(); @@ -173,8 +176,8 @@ public abstract class AbstractBootArchiveTests { @Test public void classpathCanBeSetUsingAnObject() throws IOException { this.task.setMainClassName("com.example.Main"); - this.task.classpath(this.temp.newFile("one.jar")); - this.task.setClasspath(this.temp.newFile("two.jar")); + this.task.classpath(jarFile("one.jar")); + this.task.setClasspath(jarFile("two.jar")); this.task.execute(); try (JarFile jarFile = new JarFile(this.task.getArchivePath())) { assertThat(jarFile.getEntry(this.libPath + "/one.jar")).isNull(); @@ -182,6 +185,16 @@ public abstract class AbstractBootArchiveTests { } } + @Test + public void filesOnTheClasspathThatAreNotZipFilesAreSkipped() throws IOException { + this.task.setMainClassName("com.example.Main"); + this.task.classpath(this.temp.newFile("test.pom")); + this.task.execute(); + try (JarFile jarFile = new JarFile(this.task.getArchivePath())) { + assertThat(jarFile.getEntry(this.libPath + "/test.pom")).isNull(); + } + } + @Test public void loaderIsWrittenToTheRootOfTheJar() throws IOException { this.task.setMainClassName("com.example.Main"); @@ -212,7 +225,7 @@ public abstract class AbstractBootArchiveTests { @Test public void unpackCommentIsAddedToEntryIdentifiedByAPattern() throws IOException { this.task.setMainClassName("com.example.Main"); - this.task.classpath(this.temp.newFile("one.jar"), this.temp.newFile("two.jar")); + this.task.classpath(jarFile("one.jar"), jarFile("two.jar")); this.task.requiresUnpack("**/one.jar"); this.task.execute(); try (JarFile jarFile = new JarFile(this.task.getArchivePath())) { @@ -225,7 +238,7 @@ public abstract class AbstractBootArchiveTests { @Test public void unpackCommentIsAddedToEntryIdentifiedByASpec() throws IOException { this.task.setMainClassName("com.example.Main"); - this.task.classpath(this.temp.newFile("one.jar"), this.temp.newFile("two.jar")); + this.task.classpath(jarFile("one.jar"), jarFile("two.jar")); this.task.requiresUnpack((element) -> element.getName().endsWith("two.jar")); this.task.execute(); try (JarFile jarFile = new JarFile(this.task.getArchivePath())) { @@ -370,7 +383,7 @@ public abstract class AbstractBootArchiveTests { @Test public void devtoolsJarCanBeIncluded() throws IOException { this.task.setMainClassName("com.example.Main"); - this.task.classpath(this.temp.newFile("spring-boot-devtools-0.1.2.jar")); + this.task.classpath(jarFile("spring-boot-devtools-0.1.2.jar")); this.task.setExcludeDevtools(false); this.task.execute(); assertThat(this.task.getArchivePath()).exists(); @@ -410,9 +423,8 @@ public abstract class AbstractBootArchiveTests { "com/example/Application.class"); applicationClass.getParentFile().mkdirs(); applicationClass.createNewFile(); - this.task.classpath(classpathFolder, this.temp.newFile("first-library.jar"), - this.temp.newFile("second-library.jar"), - this.temp.newFile("third-library.jar")); + this.task.classpath(classpathFolder, jarFile("first-library.jar"), + jarFile("second-library.jar"), jarFile("third-library.jar")); this.task.requiresUnpack("second-library.jar"); this.task.execute(); assertThat(getEntryNames(this.task.getArchivePath())).containsSubsequence( @@ -422,6 +434,16 @@ public abstract class AbstractBootArchiveTests { this.libPath + "/third-library.jar"); } + protected File jarFile(String name) throws IOException { + File file = this.temp.newFile(name); + try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) { + jar.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + new Manifest().write(jar); + jar.closeEntry(); + } + return file; + } + private T configure(T task) throws IOException { AbstractArchiveTask archiveTask = task; archiveTask.setBaseName("test"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java index bafd444084..fbe08adb59 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -39,8 +39,7 @@ public class BootWarTests extends AbstractBootArchiveTests { @Test public void providedClasspathJarsArePackagedInWebInfLibProvided() throws IOException { getTask().setMainClassName("com.example.Main"); - getTask().providedClasspath(this.temp.newFile("one.jar"), - this.temp.newFile("two.jar")); + getTask().providedClasspath(jarFile("one.jar"), jarFile("two.jar")); getTask().execute(); try (JarFile jarFile = new JarFile(getTask().getArchivePath())) { assertThat(jarFile.getEntry("WEB-INF/lib-provided/one.jar")).isNotNull(); @@ -51,9 +50,8 @@ public class BootWarTests extends AbstractBootArchiveTests { @Test public void providedClasspathCanBeSetUsingAFileCollection() throws IOException { getTask().setMainClassName("com.example.Main"); - getTask().providedClasspath(this.temp.newFile("one.jar")); - getTask().setProvidedClasspath( - getTask().getProject().files(this.temp.newFile("two.jar"))); + getTask().providedClasspath(jarFile("one.jar")); + getTask().setProvidedClasspath(getTask().getProject().files(jarFile("two.jar"))); getTask().execute(); try (JarFile jarFile = new JarFile(getTask().getArchivePath())) { assertThat(jarFile.getEntry("WEB-INF/lib-provided/one.jar")).isNull(); @@ -64,8 +62,8 @@ public class BootWarTests extends AbstractBootArchiveTests { @Test public void providedClasspathCanBeSetUsingAnObject() throws IOException { getTask().setMainClassName("com.example.Main"); - getTask().providedClasspath(this.temp.newFile("one.jar")); - getTask().setProvidedClasspath(this.temp.newFile("two.jar")); + getTask().providedClasspath(jarFile("one.jar")); + getTask().setProvidedClasspath(jarFile("two.jar")); getTask().execute(); try (JarFile jarFile = new JarFile(getTask().getArchivePath())) { assertThat(jarFile.getEntry("WEB-INF/lib-provided/one.jar")).isNull(); @@ -91,7 +89,7 @@ public class BootWarTests extends AbstractBootArchiveTests { public void devtoolsJarCanBeIncludedWhenItsOnTheProvidedClasspath() throws IOException { getTask().setMainClassName("com.example.Main"); - getTask().providedClasspath(this.temp.newFile("spring-boot-devtools-0.1.2.jar")); + getTask().providedClasspath(jarFile("spring-boot-devtools-0.1.2.jar")); getTask().setExcludeDevtools(false); getTask().execute(); assertThat(getTask().getArchivePath()).exists(); @@ -122,8 +120,8 @@ public class BootWarTests extends AbstractBootArchiveTests { @Test public void libProvidedEntriesAreWrittenAfterLibEntries() throws IOException { getTask().setMainClassName("com.example.Main"); - getTask().classpath(this.temp.newFile("library.jar")); - getTask().providedClasspath(this.temp.newFile("provided-library.jar")); + getTask().classpath(jarFile("library.jar")); + getTask().providedClasspath(jarFile("provided-library.jar")); getTask().execute(); assertThat(getEntryNames(getTask().getArchivePath())).containsSubsequence( "WEB-INF/lib/library.jar", "WEB-INF/lib-provided/provided-library.jar");