Skip to content

Commit 21c271a

Browse files
committed
Merge pull request #11 from rspieldenner/fixes_ommitted_versions_cycles
Fix issues with cycles and omitted versions
2 parents 97ef015 + e8baf1b commit 21c271a

File tree

5 files changed

+323
-18
lines changed

5 files changed

+323
-18
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
1.1.5 / 2016/03/31
2+
==================
3+
- Fix interaction bug with nebula.dependency-recommender (omitted versions causing issues)
4+
- Fix interaction bug with spring-boot plugin (omitted versions causing issues)
5+
- Fix handling of dependency graphs with cycles in them
6+
17
1.1.4 / 2016/03/22
28
==================
39
- Remove dependency on jackson libraries

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The [Blacklist Plugin](https://github.com/nebula-plugins/gradle-blacklist-plugin
2121
}
2222
2323
dependencies {
24-
classpath 'com.netflix.nebula:gradle-resolution-rules-plugin:1.1.4'
24+
classpath 'com.netflix.nebula:gradle-resolution-rules-plugin:1.1.5'
2525
}
2626
}
2727
@@ -32,7 +32,7 @@ Or using the Gradle plugin portal:
3232

3333
```groovy
3434
plugins {
35-
id 'nebula.resolution-rules' version '1.1.4'
35+
id 'nebula.resolution-rules' version '1.1.5'
3636
}
3737
```
3838

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2016 Netflix, Inc.
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+
18+
package nebula.plugin.resolutionrules
19+
20+
import nebula.test.IntegrationSpec
21+
import nebula.test.dependencies.DependencyGraphBuilder
22+
import nebula.test.dependencies.GradleDependencyGenerator
23+
24+
class AlignRulesPluginInteractionSpec extends IntegrationSpec {
25+
def 'alignment interaction with dependency-recommender'() {
26+
def graph = new DependencyGraphBuilder()
27+
.addModule('test.a:a:1.42.2')
28+
.build()
29+
def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
30+
mavenrepo.generateTestMavenRepo()
31+
32+
def rulesJsonFile = new File(projectDir, 'rules.json')
33+
34+
rulesJsonFile << '''\
35+
{
36+
"deny": [], "reject": [], "substitute": [], "replace": [],
37+
"align": [
38+
{
39+
"group": "test.nebula",
40+
"reason": "Align test.nebula dependencies",
41+
"author": "Example Person <person@example.org>",
42+
"date": "2016-03-17T20:21:20.368Z"
43+
}
44+
]
45+
}
46+
'''.stripIndent()
47+
48+
buildFile << """\
49+
buildscript {
50+
repositories { jcenter() }
51+
52+
dependencies {
53+
classpath 'com.netflix.nebula:nebula-dependency-recommender:3.1.0'
54+
}
55+
}
56+
57+
${applyPlugin(ResolutionRulesPlugin)}
58+
apply plugin: 'java'
59+
apply plugin: 'nebula.dependency-recommender'
60+
61+
repositories {
62+
${mavenrepo.mavenRepositoryBlock}
63+
}
64+
65+
dependencyRecommendations {
66+
map recommendations: ['test.a:a': '1.42.2']
67+
}
68+
69+
dependencies {
70+
resolutionRules files('$rulesJsonFile')
71+
compile 'test.a:a'
72+
}
73+
""".stripIndent()
74+
75+
when:
76+
def result = runTasksSuccessfully('dependencies', '--configuration', 'compile')
77+
78+
then:
79+
result.standardOutput.contains '\\--- test.a:a: -> 1.42.2\n'
80+
}
81+
82+
def 'alignment interaction with dependency-recommender reverse order of application'() {
83+
def graph = new DependencyGraphBuilder()
84+
.addModule('test.a:a:1.42.2')
85+
.build()
86+
def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
87+
mavenrepo.generateTestMavenRepo()
88+
89+
def rulesJsonFile = new File(projectDir, 'rules.json')
90+
91+
rulesJsonFile << '''\
92+
{
93+
"deny": [], "reject": [], "substitute": [], "replace": [],
94+
"align": [
95+
{
96+
"group": "test.nebula",
97+
"reason": "Align test.nebula dependencies",
98+
"author": "Example Person <person@example.org>",
99+
"date": "2016-03-17T20:21:20.368Z"
100+
}
101+
]
102+
}
103+
'''.stripIndent()
104+
105+
buildFile << """\
106+
buildscript {
107+
repositories { jcenter() }
108+
109+
dependencies {
110+
classpath 'com.netflix.nebula:nebula-dependency-recommender:3.1.0'
111+
}
112+
}
113+
114+
apply plugin: 'nebula.dependency-recommender'
115+
${applyPlugin(ResolutionRulesPlugin)}
116+
apply plugin: 'java'
117+
118+
119+
repositories {
120+
${mavenrepo.mavenRepositoryBlock}
121+
}
122+
123+
dependencyRecommendations {
124+
map recommendations: ['test.a:a': '1.42.2']
125+
}
126+
127+
dependencies {
128+
resolutionRules files('$rulesJsonFile')
129+
compile 'test.a:a'
130+
}
131+
""".stripIndent()
132+
133+
when:
134+
def result = runTasksSuccessfully('dependencies', '--configuration', 'compile')
135+
136+
then:
137+
result.standardOutput.contains '\\--- test.a:a: -> 1.42.2\n'
138+
}
139+
140+
def 'align rules work with spring-boot'() {
141+
def rulesJsonFile = new File(projectDir, 'rules.json')
142+
rulesJsonFile << '''\
143+
{
144+
"deny": [], "reject": [], "substitute": [], "replace": [],
145+
"align": [
146+
{
147+
"group": "test.nebula",
148+
"reason": "Align test.nebula dependencies",
149+
"author": "Example Person <person@example.org>",
150+
"date": "2016-03-17T20:21:20.368Z"
151+
}
152+
]
153+
}
154+
'''.stripIndent()
155+
156+
buildFile << """\
157+
buildscript {
158+
repositories { jcenter() }
159+
dependencies {
160+
classpath('org.springframework.boot:spring-boot-gradle-plugin:1.3.3.RELEASE')
161+
}
162+
}
163+
apply plugin: 'spring-boot'
164+
${applyPlugin(ResolutionRulesPlugin)}
165+
166+
repositories { jcenter() }
167+
168+
dependencies {
169+
resolutionRules files('$rulesJsonFile')
170+
compile('org.springframework.boot:spring-boot-starter-web')
171+
}
172+
""".stripIndent()
173+
174+
when:
175+
def result = runTasksSuccessfully('dependencies', '--configuration', 'compile')
176+
177+
then:
178+
noExceptionThrown()
179+
}
180+
}

src/functionalTest/groovy/nebula/plugin/resolutionrules/AlignRulesSpec.groovy

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,49 @@ class AlignRulesSpec extends IntegrationSpec {
7575
when:
7676
def result = runTasksSuccessfully('dependencies', '--configuration', 'compile')
7777

78+
then:
79+
println result.standardOutput
80+
result.standardOutput.contains '+--- test.nebula:a:1.0.0\n'
81+
result.standardOutput.contains '\\--- test.nebula:b:0.15.0 -> 1.0.0\n'
82+
}
83+
84+
def 'can align direct dependencies from ivy repositories'() {
85+
def graph = new DependencyGraphBuilder()
86+
.addModule('test.nebula:a:1.0.0')
87+
.addModule('test.nebula:a:0.15.0')
88+
.addModule('test.nebula:b:1.0.0')
89+
.addModule('test.nebula:b:0.15.0')
90+
.build()
91+
GradleDependencyGenerator ivyrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
92+
ivyrepo.generateTestIvyRepo()
93+
94+
rulesJsonFile << '''\
95+
{
96+
"deny": [], "reject": [], "substitute": [], "replace": [],
97+
"align": [
98+
{
99+
"group": "test.nebula",
100+
"reason": "Align test.nebula dependencies",
101+
"author": "Example Person <person@example.org>",
102+
"date": "2016-03-17T20:21:20.368Z"
103+
}
104+
]
105+
}
106+
'''.stripIndent()
107+
108+
buildFile << """\
109+
repositories {
110+
${ivyrepo.ivyRepositoryBlock}
111+
}
112+
dependencies {
113+
compile 'test.nebula:a:1.0.0'
114+
compile 'test.nebula:b:0.15.0'
115+
}
116+
""".stripIndent()
117+
118+
when:
119+
def result = runTasksSuccessfully('dependencies', '--configuration', 'compile')
120+
78121
then:
79122
result.standardOutput.contains '+--- test.nebula:a:1.0.0\n'
80123
result.standardOutput.contains '\\--- test.nebula:b:0.15.0 -> 1.0.0\n'
@@ -339,4 +382,89 @@ class AlignRulesSpec extends IntegrationSpec {
339382
result.standardOutput.contains '+--- test.nebula:b:1.1.0\n'
340383
result.standardOutput.contains '\\--- test.nebula:c:0.42.0 -> 1.1.0'
341384
}
385+
386+
def 'dependencies with cycles do not lead to infinite loops'() {
387+
def graph = new DependencyGraphBuilder()
388+
.addModule(new ModuleBuilder('test.nebula:a:1.0.0').addDependency('test.other:b:1.0.0').build())
389+
.addModule(new ModuleBuilder('test.other:b:1.0.0').addDependency('test.nebula:b:1.0.0').build())
390+
.addModule(new ModuleBuilder('test.nebula:a:1.1.0').addDependency('test.other:b:1.0.0').build())
391+
.addModule('test.nebula:b:1.0.0')
392+
.addModule(new ModuleBuilder('test.nebula:b:1.1.0').addDependency('test.other:b:1.0.0').build())
393+
.build()
394+
File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
395+
396+
rulesJsonFile << '''\
397+
{
398+
"deny": [], "reject": [], "substitute": [], "replace": [],
399+
"align": [
400+
{
401+
"group": "test.nebula",
402+
"reason": "Align test.nebula dependencies",
403+
"author": "Example Person <person@example.org>",
404+
"date": "2016-03-17T20:21:20.368Z"
405+
}
406+
]
407+
}
408+
'''.stripIndent()
409+
410+
buildFile << """\
411+
repositories {
412+
maven { url '${mavenrepo.absolutePath}' }
413+
}
414+
dependencies {
415+
compile 'test.nebula:a:1.1.0'
416+
compile 'test.nebula:b:1.0.0'
417+
}
418+
""".stripIndent()
419+
420+
when:
421+
def result = runTasksSuccessfully('dependencies', '--configuration', 'compile')
422+
423+
then:
424+
result.standardOutput.contains '+--- test.nebula:a:1.1.0\n'
425+
result.standardOutput.contains '| \\--- test.other:b:1.0.0\n'
426+
result.standardOutput.contains '| \\--- test.nebula:b:1.0.0 -> 1.1.0\n'
427+
result.standardOutput.contains '| \\--- test.other:b:1.0.0 (*)\n'
428+
result.standardOutput.contains '\\--- test.nebula:b:1.0.0 -> 1.1.0 (*)\n'
429+
}
430+
431+
def 'able to omit dependency versions to take what is given transitively'() {
432+
def graph = new DependencyGraphBuilder()
433+
.addModule(new ModuleBuilder('test.nebula:a:1.0.0').addDependency('test.nebula:b:1.0.0').build())
434+
.addModule('test.nebula:b:1.0.0')
435+
.build()
436+
File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
437+
438+
rulesJsonFile << '''\
439+
{
440+
"deny": [], "reject": [], "substitute": [], "replace": [],
441+
"align": [
442+
{
443+
"group": "test.nebula",
444+
"reason": "Align test.nebula dependencies",
445+
"author": "Example Person <person@example.org>",
446+
"date": "2016-03-17T20:21:20.368Z"
447+
}
448+
]
449+
}
450+
'''.stripIndent()
451+
452+
buildFile << """\
453+
repositories {
454+
maven { url '${mavenrepo.absolutePath}' }
455+
}
456+
dependencies {
457+
compile 'test.nebula:a:1.0.0'
458+
compile 'test.nebula:b'
459+
}
460+
""".stripIndent()
461+
462+
when:
463+
def result = runTasksSuccessfully('dependencies', '--configuration', 'compile')
464+
465+
then:
466+
result.standardOutput.contains '+--- test.nebula:a:1.0.0\n'
467+
result.standardOutput.contains '| \\--- test.nebula:b:1.0.0\n'
468+
result.standardOutput.contains '\\--- test.nebula:b: -> 1.0.0\n'
469+
}
342470
}

src/main/groovy/nebula/plugin/resolutionrules/Rules.groovy

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ class AlignRule extends BaseRule implements ProjectConfigurationRule {
171171
excludes = map.excludes ?: []
172172
}
173173

174-
boolean resolvedMatches(ResolvedDependency dep) {
175-
ruleMatches(dep.moduleGroup, dep.moduleName)
174+
boolean resolvedMatches(ResolvedModuleVersion dep) {
175+
ruleMatches(dep.id.group, dep.id.name)
176176
}
177177

178178
boolean dependencyMatches(DependencyResolveDetails details) {
@@ -187,21 +187,12 @@ class AlignRule extends BaseRule implements ProjectConfigurationRule {
187187

188188
@Override
189189
void apply(Project project, Configuration configuration) {
190-
def detached = project.configurations.detachedConfiguration(configuration.dependencies.toArray(new Dependency[configuration.dependencies.size()]))
191-
def matches = []
192-
def recurseDeps
193-
recurseDeps = { Collection<ResolvedDependency> col ->
194-
if (!col.isEmpty()) {
195-
col.each { ResolvedDependency dep ->
196-
if (resolvedMatches(dep))
197-
matches.add(dep)
198-
}
199-
recurseDeps(col*.children.flatten())
200-
}
201-
}
190+
def detached = configuration.copy()
191+
192+
def artifacts = detached.resolvedConfiguration.resolvedArtifacts.collect { it.moduleVersion }
193+
def matches = artifacts.findAll { resolvedMatches(it) }
194+
def versions = matches.collect { it.id.version }.toUnique().collect { new VersionInfo(it) }
202195

203-
recurseDeps(detached.resolvedConfiguration.firstLevelModuleDependencies)
204-
def versions = matches.collect { it.moduleVersion }.toUnique().collect { new VersionInfo(it) }
205196
def comparator = new DefaultVersionComparator()
206197
def alignedVersion = versions.max { a, b -> comparator.compare(a, b)}
207198

0 commit comments

Comments
 (0)