Skip to content

Commit

Permalink
Do not include empty starter jars in fat jars
Browse files Browse the repository at this point in the history
  • Loading branch information
dreis2211 committed Jul 2, 2020
1 parent ecbc8ea commit 03e045f
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package org.springframework.boot.build.starters;

import java.io.File;
import java.util.Map;
import java.util.TreeMap;

import org.gradle.api.Plugin;
import org.gradle.api.Project;
Expand All @@ -26,6 +28,7 @@
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.PluginContainer;
import org.gradle.api.tasks.bundling.Jar;

import org.springframework.boot.build.ConventionsPlugin;
import org.springframework.boot.build.DeployedPlugin;
Expand Down Expand Up @@ -57,6 +60,7 @@ public void apply(Project project) {
(artifact) -> artifact.builtBy(starterMetadata));
createClasspathConflictsCheck(runtimeClasspath, project);
createProhibitedDependenciesCheck(runtimeClasspath, project);
configureJarManifest(project);
}

private void createClasspathConflictsCheck(Configuration classpath, Project project) {
Expand All @@ -75,4 +79,14 @@ private void createProhibitedDependenciesCheck(Configuration classpath, Project
project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForProhibitedDependencies);
}

private void configureJarManifest(Project project) {
project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> {
jar.manifest((manifest) -> {
Map<String, Object> attributes = new TreeMap<>();
attributes.put("Spring-Boot-Starter", project.getName());
manifest.attributes(attributes);
});
}));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.CRC32;

Expand All @@ -49,6 +50,7 @@

import org.springframework.boot.loader.tools.DefaultLaunchScript;
import org.springframework.boot.loader.tools.FileUtils;
import org.springframework.boot.loader.tools.JarFileUtils;
import org.springframework.boot.loader.tools.JarModeLibrary;
import org.springframework.boot.loader.tools.Layer;
import org.springframework.boot.loader.tools.LayersIndex;
Expand All @@ -67,6 +69,8 @@
*/
class BootZipCopyAction implements CopyAction {

static final Pattern SPRING_BOOT_STARTER_JAR_PATTERN = Pattern.compile("spring-boot-starter-.*\\.jar");

static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = new GregorianCalendar(1980, Calendar.FEBRUARY, 1, 0, 0, 0)
.getTimeInMillis();

Expand Down Expand Up @@ -232,6 +236,9 @@ private void processDirectory(FileCopyDetails details) throws IOException {
}

private void processFile(FileCopyDetails details) throws IOException {
if (isSpringBootStarterJar(details)) {
return;
}
String name = details.getRelativePath().getPathString();
ZipArchiveEntry entry = new ZipArchiveEntry(name);
prepareEntry(entry, name, getTime(details), UnixStat.FILE_FLAG | details.getMode());
Expand All @@ -251,6 +258,13 @@ private void processFile(FileCopyDetails details) throws IOException {
}
}

private boolean isSpringBootStarterJar(FileCopyDetails details) {
if (SPRING_BOOT_STARTER_JAR_PATTERN.matcher(details.getSourceName()).matches()) {
return JarFileUtils.hasManifestAttribute(details.getFile(), "Spring-Boot-Starter");
}
return false;
}

private void writeParentDirectoriesIfNecessary(String name, Long time) throws IOException {
String parentDirectory = getParentDirectory(name);
if (parentDirectory != null && this.writtenDirectories.add(parentDirectory)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
Expand Down Expand Up @@ -369,6 +370,17 @@ void devtoolsJarIsExcludedByDefault() throws IOException {
}
}

@Test
void springBootStarterJarIsExcluded() throws IOException {
this.task.setMainClassName("com.example.Main");
this.task.classpath(springBootJarFile("spring-boot-starter-web-0.1.2.jar"));
executeTask();
assertThat(this.task.getArchiveFile().get().getAsFile()).exists();
try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) {
assertThat(jarFile.getEntry(this.libPath + "spring-boot-starter-web-0.1.2.jar")).isNull();
}
}

@Test
@Deprecated
void devtoolsJarCanBeIncluded() throws IOException {
Expand Down Expand Up @@ -430,6 +442,20 @@ protected File jarFile(String name) throws IOException {
return file;
}

protected File springBootJarFile(String name) throws IOException {
File file = newFile(name);
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) {
jar.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.putValue("Spring-Boot-Starter", name);
manifest.write(jar);
jar.closeEntry();
}
return file;
}

private T configure(T task) throws IOException {
AbstractArchiveTask archiveTask = task;
archiveTask.getArchiveBaseName().set("test");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.springframework.boot.loader.tools;

import java.io.File;
import java.util.jar.Attributes;
import java.util.jar.JarFile;

/**
* Utilities for working with {@link java.util.jar.JarFile}.
*
* @author Christoph Dreis
* @since 2.4.0
*/
public abstract class JarFileUtils {

/**
* Determines if a file has an attribute in the JARs manifest.
* @param file the jar file
* @param attribute name of the attribute
* @return {@code true} if attribute with the given name was found in the JAR
* manifest, {@code false} otherwise
*/
public static boolean hasManifestAttribute(File file, String attribute) {
try (JarFile jarFile = new JarFile(file)) {
return jarFile.getManifest().getMainAttributes().containsKey(new Attributes.Name(attribute));
}
catch (Exception ignore) {
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;

Expand Down Expand Up @@ -70,6 +71,8 @@ public abstract class Packager {

private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";

private static final Pattern SPRING_BOOT_STARTER_JAR_PATTERN = Pattern.compile("spring-boot-starter-.*\\.jar");

private List<MainClassTimeoutWarningListener> mainClassTimeoutListeners = new ArrayList<>();

private String mainClass;
Expand Down Expand Up @@ -461,7 +464,7 @@ private final class WritableLibraries implements UnpackHandler {

WritableLibraries(Libraries libraries) throws IOException {
libraries.doWithLibraries((library) -> {
if (isZip(library::openStream)) {
if (isZip(library::openStream) && !isSpringBootStarterJar(library)) {
addLibrary(library);
}
});
Expand All @@ -479,6 +482,13 @@ private void addLibrary(Library library) {
}
}

private boolean isSpringBootStarterJar(Library library) {
if (SPRING_BOOT_STARTER_JAR_PATTERN.matcher(library.getName()).matches()) {
return JarFileUtils.hasManifestAttribute(library.getFile(), "Spring-Boot-Starter");
}
return false;
}

@Override
public boolean requiresUnpack(String name) {
Library library = this.libraries.get(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.Random;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -583,12 +584,41 @@ void kotlinModuleMetadataMovesBeneathBootInfClassesWhenRepackaged() throws Excep
assertThat(getPackagedEntry("BOOT-INF/classes/META-INF/test.kotlin_module")).isNotNull();
}

@Test
void springBootStarterJarIsExcluded() throws IOException {
this.testJarFile.addClass("com/example/Application.class", ClassWithMainMethod.class);
File libraryOne = createSpringBootJarFile("spring-boot-starter-web-0.1.2.jar");
P packager = createPackager();
execute(packager, (callback) -> callback.library(new Library(libraryOne, LibraryScope.COMPILE, false)));
assertThat(getPackagedEntryNames()).doesNotContain("BOOT-INF/lib/" + libraryOne.getName());
}

private File createLibrary() throws IOException {
TestJarFile library = new TestJarFile(this.tempDir);
library.addClass("com/example/library/Library.class", ClassWithoutMainMethod.class);
return library.getFile();
}

protected File createSpringBootJarFile(String name) throws IOException {
File file = newFile(name);
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) {
jar.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.putValue("Spring-Boot-Starter", name);
manifest.write(jar);
jar.closeEntry();
}
return file;
}

protected File newFile(String name) throws IOException {
File file = new File(this.tempDir, name);
file.createNewFile();
return file;
}

protected final P createPackager() throws IOException {
return createPackager(this.testJarFile.getFile());
}
Expand Down

0 comments on commit 03e045f

Please sign in to comment.