Skip to content

Commit f5f3903

Browse files
committed
Resolve versionManagement configuration lazily and preserve exclusions
Previously, the versionManagement configuration was resolved as part of the Boot Gradle plugin being applied. This meant that no dependencies could be added to it and attempting to do so would result in a failure: “You can't change a configuration which is not in unresolved state”. This commit updates ApplyExcludeRules to wrap its processing in a before resolve action. This defers the resolution of the versionManagement configuration until one of the project’s other configurations is being resolved. Fixes #1077 In addition to the above, the transitive exclusions that the Gradle plugin provides were being lost if custom version management provided a version for the same dependency. This commit updates AbstractDependencies to preserve the exclusions from an existing dependency declaration while using the version from the newer dependency. This ensures that the exclusions remain while allowing versions to be overridden. Fixes #1079
1 parent 9e93719 commit f5f3903

File tree

8 files changed

+201
-20
lines changed

8 files changed

+201
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.gradle;
18+
19+
import java.io.IOException;
20+
21+
import org.gradle.tooling.ProjectConnection;
22+
import org.junit.BeforeClass;
23+
import org.junit.Test;
24+
import org.springframework.boot.dependency.tools.ManagedDependencies;
25+
26+
/**
27+
* Tests for using the Gradle plugin's support for custom version management
28+
*
29+
* @author Andy Wilkinson
30+
*/
31+
public class CustomVersionManagementTests {
32+
33+
private static final String BOOT_VERSION = ManagedDependencies.get()
34+
.find("spring-boot").getVersion();
35+
36+
private static ProjectConnection project;
37+
38+
@BeforeClass
39+
public static void createProject() throws IOException {
40+
project = new ProjectCreator().createProject("custom-version-management");
41+
}
42+
43+
@Test
44+
public void exclusionsAreStillInPlace() {
45+
project.newBuild().forTasks("checkExclusions")
46+
.withArguments("-PbootVersion=" + BOOT_VERSION).run();
47+
}
48+
49+
@Test
50+
public void customSpringVersionIsUsed() {
51+
project.newBuild().forTasks("checkSpringVersion")
52+
.withArguments("-PbootVersion=" + BOOT_VERSION).run();
53+
}
54+
55+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.gradle;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
22+
import org.gradle.tooling.GradleConnector;
23+
import org.gradle.tooling.ProjectConnection;
24+
import org.gradle.tooling.internal.consumer.DefaultGradleConnector;
25+
import org.springframework.util.FileCopyUtils;
26+
27+
/**
28+
* @author Andy Wilkinson
29+
*/
30+
public class ProjectCreator {
31+
32+
public ProjectConnection createProject(String name) throws IOException {
33+
File projectDirectory = new File("target/" + name);
34+
projectDirectory.mkdirs();
35+
36+
File gradleScript = new File(projectDirectory, "build.gradle");
37+
FileCopyUtils.copy(new File("src/test/resources/" + name + ".gradle"),
38+
gradleScript);
39+
40+
GradleConnector gradleConnector = GradleConnector.newConnector();
41+
((DefaultGradleConnector) gradleConnector).embedded(true);
42+
return gradleConnector.forProjectDirectory(projectDirectory).connect();
43+
}
44+
}

spring-boot-integration-tests/src/test/java/org/springframework/boot/starter/StarterDependenciesIntegrationTests.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,22 @@
2323
import java.util.List;
2424

2525
import org.gradle.tooling.BuildException;
26-
import org.gradle.tooling.GradleConnector;
2726
import org.gradle.tooling.ProjectConnection;
28-
import org.gradle.tooling.internal.consumer.DefaultGradleConnector;
2927
import org.junit.AfterClass;
3028
import org.junit.BeforeClass;
3129
import org.junit.Test;
3230
import org.junit.runner.RunWith;
3331
import org.junit.runners.Parameterized;
3432
import org.junit.runners.Parameterized.Parameters;
3533
import org.springframework.boot.dependency.tools.ManagedDependencies;
36-
import org.springframework.util.FileCopyUtils;
34+
import org.springframework.boot.gradle.ProjectCreator;
3735

3836
import static org.junit.Assert.fail;
3937

4038
/**
4139
* Tests for the various starter projects to check that they don't pull in unwanted
4240
* transitive dependencies when used with Gradle
43-
*
41+
*
4442
* @author Andy Wilkinson
4543
*/
4644
@RunWith(Parameterized.class)
@@ -76,15 +74,7 @@ public static List<String[]> getStarters() {
7674

7775
@BeforeClass
7876
public static void createProject() throws IOException {
79-
File projectDirectory = new File("target/starter-dependencies");
80-
projectDirectory.mkdirs();
81-
82-
File gradleScript = new File(projectDirectory, "build.gradle");
83-
FileCopyUtils.copy(new File("src/test/resources/build.gradle"), gradleScript);
84-
85-
GradleConnector gradleConnector = GradleConnector.newConnector();
86-
((DefaultGradleConnector) gradleConnector).embedded(true);
87-
project = gradleConnector.forProjectDirectory(projectDirectory).connect();
77+
project = new ProjectCreator().createProject("starter-dependencies");
8878
}
8979

9080
@BeforeClass
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import org.gradle.api.artifacts.result.UnresolvedDependencyResult;
2+
3+
buildscript {
4+
repositories {
5+
mavenLocal()
6+
mavenCentral()
7+
}
8+
dependencies {
9+
classpath("org.springframework.boot:spring-boot-gradle-plugin:${project.bootVersion}")
10+
}
11+
}
12+
13+
repositories {
14+
mavenLocal()
15+
mavenCentral()
16+
}
17+
18+
configurations {
19+
exclusions
20+
springVersion
21+
}
22+
23+
apply plugin: 'spring-boot'
24+
25+
dependencies {
26+
exclusions "org.springframework.boot:spring-boot-starter"
27+
springVersion "org.springframework:spring-core"
28+
versionManagement files('../../src/test/resources/custom-versions.properties')
29+
}
30+
31+
task checkExclusions {
32+
doFirst {
33+
def commonsLogging = getResolvedDependencies(configurations.exclusions)
34+
.find { it.selected.id.group == 'commons-logging' }
35+
if (commonsLogging) {
36+
throw new GradleException("commong-logging is a transitive dependency")
37+
}
38+
}
39+
}
40+
41+
task checkSpringVersion {
42+
doFirst {
43+
def springVersions = getResolvedDependencies(configurations.springVersion)
44+
.findAll{it.selected.id.group == 'org.springframework'}
45+
.collect {it.selected.id.version}
46+
if (springVersions.size() != 1 || !springVersions.contains("4.0.3.RELEASE")) {
47+
throw new GradleException("Spring version was not 4.0.3.RELEASE")
48+
}
49+
}
50+
}
51+
52+
def getResolvedDependencies(def configuration) {
53+
def allDependencies = configuration.incoming
54+
.resolutionResult.allDependencies
55+
.split { it instanceof UnresolvedDependencyResult }
56+
57+
def unresolved = allDependencies.first()
58+
def resolved = allDependencies.last()
59+
if (unresolved) {
60+
throw new GradleException("Resolution failed: ${unresolved}")
61+
}
62+
resolved
63+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework\:spring-core=4.0.3.RELEASE
2+
org.springframework.boot\:spring-boot-starter=1.1.0.RELEASE

spring-boot-tools/spring-boot-dependency-tools/src/main/java/org/springframework/boot/dependency/tools/AbstractDependencies.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,19 @@
1616

1717
package org.springframework.boot.dependency.tools;
1818

19+
import java.util.ArrayList;
1920
import java.util.Iterator;
2021
import java.util.LinkedHashMap;
22+
import java.util.List;
2123
import java.util.Map;
2224

25+
import org.springframework.boot.dependency.tools.Dependency.Exclusion;
26+
2327
/**
2428
* Abstract base implementation for {@link Dependencies}.
25-
*
29+
*
2630
* @author Phillip Webb
31+
* @author Andy Wilkinson
2732
* @since 1.1.0
2833
*/
2934
abstract class AbstractDependencies implements Dependencies {
@@ -53,10 +58,24 @@ public Iterator<Dependency> iterator() {
5358
}
5459

5560
protected void add(ArtifactAndGroupId artifactAndGroupId, Dependency dependency) {
61+
Dependency existing = this.byArtifactAndGroupId.get(artifactAndGroupId);
62+
if (existing != null) {
63+
dependency = mergeDependencies(existing, dependency);
64+
}
5665
this.byArtifactAndGroupId.put(artifactAndGroupId, dependency);
5766
this.byArtifactId.put(dependency.getArtifactId(), dependency);
5867
}
5968

69+
private Dependency mergeDependencies(Dependency existingDependency,
70+
Dependency newDependency) {
71+
List<Exclusion> combinedExclusions = new ArrayList<Exclusion>();
72+
combinedExclusions.addAll(existingDependency.getExclusions());
73+
combinedExclusions.addAll(newDependency.getExclusions());
74+
75+
return new Dependency(newDependency.getGroupId(), newDependency.getArtifactId(),
76+
newDependency.getVersion(), combinedExclusions);
77+
}
78+
6079
/**
6180
* Simple holder for an artifact+group ID.
6281
*/

spring-boot-tools/spring-boot-gradle-plugin/src/main/groovy/org/springframework/boot/gradle/exclude/ApplyExcludeRules.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.gradle.api.artifacts.Configuration;
2222
import org.gradle.api.artifacts.Dependency;
2323
import org.gradle.api.artifacts.ModuleDependency;
24+
import org.gradle.api.artifacts.ResolvableDependencies;
2425
import org.gradle.api.internal.artifacts.DefaultExcludeRule;
2526
import org.gradle.api.logging.Logger;
2627
import org.springframework.boot.dependency.tools.Dependency.Exclusion;
@@ -45,12 +46,19 @@ public ApplyExcludeRules(Project project) {
4546

4647
@Override
4748
public void execute(Configuration configuration) {
48-
configuration.getDependencies().all(new Action<Dependency>() {
49-
@Override
50-
public void execute(Dependency dependency) {
51-
applyExcludeRules(dependency);
52-
}
53-
});
49+
if (!VersionManagedDependencies.CONFIGURATION.equals(configuration.getName())) {
50+
configuration.getIncoming().beforeResolve(new Action<ResolvableDependencies>() {
51+
@Override
52+
public void execute(ResolvableDependencies resolvableDependencies) {
53+
resolvableDependencies.getDependencies().all(new Action<Dependency>() {
54+
@Override
55+
public void execute(Dependency dependency) {
56+
applyExcludeRules(dependency);
57+
}
58+
});
59+
}
60+
});
61+
}
5462
}
5563

5664
private void applyExcludeRules(Dependency dependency) {

0 commit comments

Comments
 (0)