Avoid packaging two versions of same dependency in Gradle repackaging

Previously, the Gradle plugin would include all of the dependencies
from both the compile and runtime configurations in the repackaged
jar. In the unlikely event that the compile and runtime configurations
contained different versions of the same dependency, this would lead
to both versions of the dependency being packaged in the jar file.

The runtime configuration extends the compile configuration so, in
normal circumstances, it will contain a superset of the compile
configuration's dependencies. In the situation described above where
the two configurations contain different versions of the same
dependency the runtime configuration will only contain whichever
version of the two dependencies has "won". By default, this will
be the dependency with the higher version.

This commit updates the Gradle plugin to only include the runtime
configuration's resolved dependencies during repackaging. As explained
above, the runtime configuration extends the compile configuration so
any compile dependencies will still be included, with the added
benefit that duplicate versions of the same dependency will have been
resolved to a single, preferred version.

Closes gh-5749
This commit is contained in:
Andy Wilkinson 2016-04-21 11:37:40 +01:00
parent a6bcc6f573
commit 3bfc6b1a4b
3 changed files with 113 additions and 4 deletions

View File

@ -0,0 +1,83 @@
/*
* Copyright 2012-2016 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
*
* http://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;
import java.io.File;
import java.io.IOException;
import java.util.jar.JarFile;
import org.assertj.core.api.Condition;
import org.assertj.core.description.TextDescription;
import org.gradle.tooling.ProjectConnection;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.not;
/**
* Integration tests for Gradle repackaging with two different versions of the same
* dependency.
*
* @author Andy Wilkinson
*/
public class MixedVersionRepackagingTests {
private static final String BOOT_VERSION = Versions.getBootVersion();
private static ProjectConnection project;
@BeforeClass
public static void createProject() throws IOException {
project = new ProjectCreator().createProject("mixed-version-repackaging");
}
@Test
public void singleVersionIsIncludedInJar() throws IOException {
project.newBuild().forTasks("clean", "build")
.withArguments("-PbootVersion=" + BOOT_VERSION, "-Prepackage=true",
"-PexcludeDevtools=false")
.run();
File buildLibs = new File("target/mixed-version-repackaging/build/libs");
File repackageFile = new File(buildLibs, "mixed-version-repackaging.jar");
assertThat(repackageFile.exists()).isTrue();
assertThat(new JarFile(repackageFile))
.has(entryNamed("BOOT-INF/lib/guava-18.0.jar"));
assertThat(new JarFile(repackageFile))
.has(not(entryNamed("BOOT-INF/lib/guava-16.0.jar")));
}
private Condition<JarFile> entryNamed(String name) {
return new JarFileEntryCondition(name);
}
private final class JarFileEntryCondition extends Condition<JarFile> {
private final String entryName;
private JarFileEntryCondition(String entryName) {
super(new TextDescription("entry named '%s'", entryName));
this.entryName = entryName;
}
@Override
public boolean matches(JarFile jarFile) {
return jarFile.getEntry(this.entryName) != null;
}
}
}

View File

@ -0,0 +1,30 @@
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${project.bootVersion}"
}
}
repositories {
mavenLocal()
mavenCentral()
}
apply plugin: 'spring-boot'
apply plugin: 'java'
dependencies {
compile 'com.google.guava:guava:16.0'
runtime 'com.google.guava:guava:18.0'
}
dependencyManagement {
overriddenByDependencies = false
}
springBoot {
mainClass = 'foo.bar.Baz'
}

View File

@ -87,16 +87,12 @@ class ProjectLibraries implements Libraries {
libraries(custom, callback);
}
else {
Set<GradleLibrary> compile = getLibraries("compile", LibraryScope.COMPILE);
Set<GradleLibrary> runtime = getLibraries("runtime", LibraryScope.RUNTIME);
runtime = minus(runtime, compile);
Set<GradleLibrary> provided = getLibraries(this.providedConfigurationName,
LibraryScope.PROVIDED);
if (provided != null) {
compile = minus(compile, provided);
runtime = minus(runtime, provided);
}
libraries(compile, callback);
libraries(runtime, callback);
libraries(provided, callback);
}