Skip to content

Commit

Permalink
Merge branch 'gh-22036'
Browse files Browse the repository at this point in the history
Closes gh-22036
  • Loading branch information
wilkinsona committed Sep 10, 2020
2 parents 3ba7d98 + 143d197 commit fa1d4a4
Show file tree
Hide file tree
Showing 13 changed files with 364 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -713,30 +713,6 @@ With Maven the dependency should be declared as optional, as shown in the follow
</dependency>
----

If you have defined `@ConfigurationProperties` in your application, make sure to configure the `spring-boot-maven-plugin` to prevent the `repackage` goal from adding the dependency into the fat jar:

[source,xml,indent=0,subs="verbatim,quotes,attributes"]
----
<project>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
----

With Gradle 4.5 and earlier, the dependency should be declared in the `compileOnly` configuration, as shown in the following example:

[source,groovy,indent=0,subs="verbatim,quotes,attributes"]
Expand Down
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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ protected final FilterArtifacts getFilters(ArtifactsFilter... additionalFilters)
if (this.excludes != null && !this.excludes.isEmpty()) {
filters.addFilter(new ExcludeFilter(this.excludes));
}
filters.addFilter(new JarTypeFilter());
return filters;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.maven;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarFile;

import org.apache.maven.artifact.Artifact;

/**
* A {@link DependencyFilter} that filters dependencies based on the jar type declared in
* their manifest.
*
* @author Andy Wilkinson
*/
class JarTypeFilter extends DependencyFilter {

private static final Set<String> EXCLUDED_JAR_TYPES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList("annotation-processor", "dependencies-starter")));

JarTypeFilter() {
super(Collections.emptyList());
}

@Override
protected boolean filter(Artifact artifact) {
try (JarFile jarFile = new JarFile(artifact.getFile())) {
String jarType = jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Jar-Type");
return jarType != null && EXCLUDED_JAR_TYPES.contains(jarType);
}
catch (IOException ex) {
return false;
}
}

}

0 comments on commit fa1d4a4

Please sign in to comment.