Skip to content

Commit

Permalink
Exclude starter jars when running and packaging with Gradle
Browse files Browse the repository at this point in the history
This commit updates the Gradle Plugin to filter dependencies based on
the Spring-Boot-Jar-Type entry in their manifest. Jars with a
Spring-Boot-Jar-Type of dependencies-starter are excluded. Unlike the
Maven plugin, jars with a type of annotation-processor are not
excluded. It is not necessary with Gradle as use of the
annotationProcessor configuration for such dependencies already ensures
that they are not included.

See gh-22036
  • Loading branch information
wilkinsona committed Sep 10, 2020
1 parent e743d5f commit 143d197
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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.gradle.plugin;

import java.io.File;
import java.util.Collections;
import java.util.Set;
import java.util.jar.JarFile;

import org.gradle.api.file.FileCollection;
import org.gradle.api.specs.Spec;

/**
* A {@link Spec} for {@link FileCollection#filter(Spec) filtering} {@code FileCollection}
* to remove jar files based on their {@code Spring-Boot-Jar-Type} as defined in the
* manifest. Jars of type {@code dependencies-starter} are excluded.
*
* @author Andy Wilkinson
*/
class JarTypeFileSpec implements Spec<File> {

private static final Set<String> EXCLUDED_JAR_TYPES = Collections.singleton("dependencies-starter");

@Override
public boolean isSatisfiedBy(File file) {
try (JarFile jar = new JarFile(file)) {
String jarType = jar.getManifest().getMainAttributes().getValue("Spring-Boot-Jar-Type");
if (jarType != null && EXCLUDED_JAR_TYPES.contains(jarType)) {
return false;
}
}
catch (Exception ex) {
// Continue
}
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ private TaskProvider<BootJar> configureBootJarTask(Project project) {
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
Configuration productionRuntimeClasspath = project.getConfigurations()
.getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME);
return mainSourceSet.getRuntimeClasspath().minus((developmentOnly.minus(productionRuntimeClasspath)));
return mainSourceSet.getRuntimeClasspath().minus((developmentOnly.minus(productionRuntimeClasspath)))
.filter(new JarTypeFileSpec());
});
bootJar.conventionMapping("mainClassName", new MainClassConvention(project, bootJar::getClasspath));
});
Expand All @@ -129,7 +130,7 @@ private void configureBootRunTask(Project project) {
run.setDescription("Runs this project as a Spring Boot application.");
run.setGroup(ApplicationPlugin.APPLICATION_GROUP);
run.classpath(javaPluginConvention(project).getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME)
.getRuntimeClasspath());
.getRuntimeClasspath().filter(new JarTypeFileSpec()));
run.getConventionMapping().map("jvmArgs", () -> {
if (project.hasProperty("applicationDefaultJvmArgs")) {
return project.property("applicationDefaultJvmArgs");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ private TaskProvider<BootWar> configureBootWarTask(Project project) {
.getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
Configuration productionRuntimeClasspath = project.getConfigurations()
.getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME);
bootWar.setClasspath(bootWar.getClasspath().minus((developmentOnly.minus(productionRuntimeClasspath))));
bootWar.setClasspath(bootWar.getClasspath().minus((developmentOnly.minus(productionRuntimeClasspath)))
.filter(new JarTypeFileSpec()));
bootWar.conventionMapping("mainClassName", new MainClassConvention(project, bootWar::getClasspath));
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@
package org.springframework.boot.gradle.tasks.bundling;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.function.Consumer;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.stream.Stream;

import org.gradle.testkit.runner.InvalidRunnerConfigurationException;
Expand Down Expand Up @@ -172,4 +177,36 @@ void developmentOnlyDependenciesCanBeIncludedInTheArchive() throws IOException {
}
}

@TestTemplate
void jarTypeFilteringIsApplied() throws IOException {
File flatDirRepository = new File(this.gradleBuild.getProjectDir(), "repository");
createDependenciesStarterJar(new File(flatDirRepository, "starter.jar"));
createStandardJar(new File(flatDirRepository, "standard.jar"));
assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome())
.isEqualTo(TaskOutcome.SUCCESS);
try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) {
Stream<String> libEntryNames = jarFile.stream().filter((entry) -> !entry.isDirectory())
.map(JarEntry::getName).filter((name) -> name.startsWith(this.libPath));
assertThat(libEntryNames).containsExactly(this.libPath + "standard.jar");
}
}

private void createStandardJar(File location) throws IOException {
createJar(location, (attributes) -> {
});
}

private void createDependenciesStarterJar(File location) throws IOException {
createJar(location, (attributes) -> attributes.putValue("Spring-Boot-Jar-Type", "dependencies-starter"));
}

private void createJar(File location, Consumer<Attributes> attributesConfigurer) throws IOException {
location.getParentFile().mkdirs();
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributesConfigurer.accept(attributes);
new JarOutputStream(new FileOutputStream(location), manifest).close();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
package org.springframework.boot.gradle.tasks.run;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.function.Consumer;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

import org.gradle.api.JavaVersion;
import org.gradle.testkit.runner.BuildResult;
Expand Down Expand Up @@ -120,6 +125,17 @@ void applicationPluginJvmArgumentsAreUsed() throws IOException {
}
}

@TestTemplate
void jarTypeFilteringIsAppliedToTheClasspath() throws IOException {
copyClasspathApplication();
File flatDirRepository = new File(this.gradleBuild.getProjectDir(), "repository");
createDependenciesStarterJar(new File(flatDirRepository, "starter.jar"));
createStandardJar(new File(flatDirRepository, "standard.jar"));
BuildResult result = this.gradleBuild.build("bootRun");
assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("standard.jar").doesNotContain("starter.jar");
}

private void copyClasspathApplication() throws IOException {
copyApplication("classpath");
}
Expand All @@ -138,4 +154,22 @@ private String canonicalPathOf(String path) throws IOException {
return new File(this.gradleBuild.getProjectDir(), path).getCanonicalPath();
}

private void createStandardJar(File location) throws IOException {
createJar(location, (attributes) -> {
});
}

private void createDependenciesStarterJar(File location) throws IOException {
createJar(location, (attributes) -> attributes.putValue("Spring-Boot-Jar-Type", "dependencies-starter"));
}

private void createJar(File location, Consumer<Attributes> attributesConfigurer) throws IOException {
location.getParentFile().mkdirs();
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributesConfigurer.accept(attributes);
new JarOutputStream(new FileOutputStream(location), manifest).close();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}

bootJar {
mainClassName = 'com.example.Application'
}

repositories {
flatDir {
dirs 'repository'
}
}

dependencies {
implementation(name: "standard")
implementation(name: "starter")
}

bootJar {
layered {
enabled = false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
id 'war'
id 'org.springframework.boot' version '{version}'
}

bootWar {
mainClassName = 'com.example.Application'
}

repositories {
flatDir {
dirs 'repository'
}
}

dependencies {
implementation(name: "standard")
implementation(name: "starter")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}

repositories {
flatDir {
dirs 'repository'
}
}

dependencies {
implementation(name: "standard")
implementation(name: "starter")
}

0 comments on commit 143d197

Please sign in to comment.