Improve dependency mapping to include coordinates

This commit improves the dependency mapping infrastructure to include the
groupId and artifactId attributes alongside the existing version. For
consistency, the attribute has been renamed from `versions` to
`mappings`.

This allows to better support the `redis` use case: the starter was
renamed from `spring-boot-starter-redis` to
`spring-boot-starter-data-redis` and previously we had two entries
excluding each other using version ranges.

Closes gh-219
This commit is contained in:
Stephane Nicoll 2016-04-24 05:20:59 +02:00
parent b0f1abed41
commit 2d9ac9bd5a
5 changed files with 83 additions and 34 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2015 the original author or authors. * Copyright 2012-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -62,10 +62,10 @@ class Dependency extends MetadataElement {
String version String version
/** /**
* Versions mapping if the version differs according to the Spring Boot * Dependency mapping if an attribute of the dependency differs according to the
* version. If no mapping matches, {@code version} is used. * Spring Boot version. If no mapping matches, default attributes are used.
*/ */
List<Mapping> versions = [] List<Mapping> mappings = []
String scope = SCOPE_COMPILE String scope = SCOPE_COMPILE
@ -154,7 +154,7 @@ class Dependency extends MetadataElement {
"dependency with id '$id'") "dependency with id '$id'")
} }
} }
versions.each { mappings.each {
try { try {
it.range = VersionRange.parse(it.versionRange) it.range = VersionRange.parse(it.versionRange)
} catch (InvalidVersionException ex) { } catch (InvalidVersionException ex) {
@ -168,11 +168,13 @@ class Dependency extends MetadataElement {
* a {@link Dependency} instance that has its state resolved against the specified version. * a {@link Dependency} instance that has its state resolved against the specified version.
*/ */
Dependency resolve(Version bootVersion) { Dependency resolve(Version bootVersion) {
for (Mapping mapping : versions) { for (Mapping mapping : mappings) {
if (mapping.range.match(bootVersion)) { if (mapping.range.match(bootVersion)) {
def dependency = new Dependency(this) def dependency = new Dependency(this)
dependency.groupId = mapping.groupId ? mapping.groupId : this.groupId
dependency.artifactId = mapping.artifactId ? mapping.artifactId : this.artifactId
dependency.version = mapping.version ? mapping.version : this.version dependency.version = mapping.version ? mapping.version : this.version
dependency.versions = null dependency.mappings = null
return dependency return dependency
} }
} }
@ -202,10 +204,29 @@ class Dependency extends MetadataElement {
id = sb.toString() id = sb.toString()
} }
/**
* Map several attribute of the dependency for a given version range.
*/
static class Mapping { static class Mapping {
/**
* The version range of this mapping.
*/
String versionRange String versionRange
/**
* The version to use for this mapping or {@code null} to use the default.
*/
String groupId;
/**
* The groupId to use for this mapping or {@code null} to use the default.
*/
String artifactId;
/**
* The artifactId to use for this mapping or {@code null} to use the default.
*/
String version String version
private VersionRange range private VersionRange range

View File

@ -158,9 +158,9 @@ class ProjectRequestTests {
@Test @Test
void resolveDependencyVersion() { void resolveDependencyVersion() {
def dependency = createDependency('org.foo', 'bar', '1.2.0.RELEASE') def dependency = createDependency('org.foo', 'bar', '1.2.0.RELEASE')
dependency.versions << new Dependency.Mapping( dependency.mappings << new Dependency.Mapping(
version: '0.1.0.RELEASE', versionRange: '[1.0.0.RELEASE, 1.1.0.RELEASE)') version: '0.1.0.RELEASE', versionRange: '[1.0.0.RELEASE, 1.1.0.RELEASE)')
dependency.versions << new Dependency.Mapping( dependency.mappings << new Dependency.Mapping(
version: '0.2.0.RELEASE', versionRange: '1.1.0.RELEASE') version: '0.2.0.RELEASE', versionRange: '1.1.0.RELEASE')
def metadata = InitializrMetadataTestBuilder.withDefaults() def metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('code', dependency).build() .addDependencyGroup('code', dependency).build()

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2015 the original author or authors. * Copyright 2012-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -148,7 +148,7 @@ class DependencyTests {
@Test @Test
void resolveInvalidMapping() { void resolveInvalidMapping() {
def dependency = new Dependency(id: 'web') def dependency = new Dependency(id: 'web')
dependency.versions << new Dependency.Mapping( dependency.mappings << new Dependency.Mapping(
versionRange: 'foo-bar', version: '0.1.0.RELEASE') versionRange: 'foo-bar', version: '0.1.0.RELEASE')
thrown.expect(InvalidInitializrMetadataException) thrown.expect(InvalidInitializrMetadataException)
thrown.expectMessage('foo-bar') thrown.expectMessage('foo-bar')
@ -156,25 +156,47 @@ class DependencyTests {
} }
@Test @Test
void resolveMatchingMapping() { void resolveMatchingVersionMapping() {
def dependency = new Dependency(id: 'web', description: 'A web dependency', version: '0.3.0.RELEASE', def dependency = new Dependency(id: 'web', description: 'A web dependency', version: '0.3.0.RELEASE',
keywords: ['foo', 'bar'], aliases: ['the-web'], facets: ['web'] ) keywords: ['foo', 'bar'], aliases: ['the-web'], facets: ['web'])
dependency.versions << new Dependency.Mapping( dependency.mappings << new Dependency.Mapping(
versionRange: '[1.1.0.RELEASE, 1.2.0.RELEASE)', version: '0.1.0.RELEASE') versionRange: '[1.1.0.RELEASE, 1.2.0.RELEASE)', version: '0.1.0.RELEASE')
dependency.versions << new Dependency.Mapping( dependency.mappings << new Dependency.Mapping(
versionRange: '[1.2.0.RELEASE, 1.3.0.RELEASE)', version: '0.2.0.RELEASE') versionRange: '[1.2.0.RELEASE, 1.3.0.RELEASE)', version: '0.2.0.RELEASE')
dependency.resolve() dependency.resolve()
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.5.RELEASE')), '0.1.0.RELEASE') validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.5.RELEASE')),
validateResolvedWebDependency(dependency.resolve(Version.parse('1.2.0.RELEASE')), '0.2.0.RELEASE') 'org.springframework.boot', 'spring-boot-starter-web', '0.1.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('2.1.3.M1')), '0.3.0.RELEASE') // default validateResolvedWebDependency(dependency.resolve(Version.parse('1.2.0.RELEASE')),
'org.springframework.boot', 'spring-boot-starter-web', '0.2.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('2.1.3.M1')),
'org.springframework.boot', 'spring-boot-starter-web', '0.3.0.RELEASE') // default
} }
static void validateResolvedWebDependency(def dependency, def expectedVersion) { @Test
void resolveMatchArtifactMapping() {
def dependency = new Dependency(id: 'web', description: 'A web dependency', version: '0.3.0.RELEASE',
keywords: ['foo', 'bar'], aliases: ['the-web'], facets: ['web'])
dependency.mappings << new Dependency.Mapping(
versionRange: '[1.1.0.RELEASE, 1.2.0.RELEASE)', groupId: 'org.spring.boot')
dependency.mappings << new Dependency.Mapping(
versionRange: '[1.2.0.RELEASE, 1.3.0.RELEASE)', artifactId: 'starter-web')
dependency.resolve()
validateResolvedWebDependency(dependency.resolve(Version.parse('1.1.5.RELEASE')),
'org.spring.boot', 'spring-boot-starter-web', '0.3.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('1.2.0.RELEASE')),
'org.springframework.boot', 'starter-web', '0.3.0.RELEASE')
validateResolvedWebDependency(dependency.resolve(Version.parse('2.1.3.M1')),
'org.springframework.boot', 'spring-boot-starter-web', '0.3.0.RELEASE') // default
}
static void validateResolvedWebDependency(
def dependency, def expectedGroupId, def expectedArtifactId, def expectedVersion) {
assertEquals expectedVersion, dependency.version assertEquals expectedVersion, dependency.version
assertEquals 'web', dependency.id assertEquals 'web', dependency.id
assertEquals 'org.springframework.boot', dependency.groupId assertEquals expectedGroupId, dependency.groupId
assertEquals 'spring-boot-starter-web', dependency.artifactId assertEquals expectedArtifactId, dependency.artifactId
assertEquals 2, dependency.keywords.size() assertEquals 2, dependency.keywords.size()
assertEquals 1, dependency.aliases.size() assertEquals 1, dependency.aliases.size()
assertEquals 1, dependency.facets.size() assertEquals 1, dependency.facets.size()

View File

@ -91,7 +91,7 @@ initializr:
groupId: org.projectlombok groupId: org.projectlombok
artifactId: lombok artifactId: lombok
description: Java annotation library which help to reduce boilerplate code and code faster description: Java annotation library which help to reduce boilerplate code and code faster
versions: mappings:
- versionRange: "[1.2.0.RELEASE,1.4.0.M1)" - versionRange: "[1.2.0.RELEASE,1.4.0.M1)"
version: 1.16.6 version: 1.16.6
starter: false starter: false
@ -153,7 +153,7 @@ initializr:
description: Document RESTful services by combining hand-written and auto-generated documentation description: Document RESTful services by combining hand-written and auto-generated documentation
groupId: org.springframework.restdocs groupId: org.springframework.restdocs
artifactId: spring-restdocs-mockmvc artifactId: spring-restdocs-mockmvc
versions: mappings:
- versionRange: "[1.2.0.RELEASE,1.3.0.RC1)" - versionRange: "[1.2.0.RELEASE,1.3.0.RC1)"
version: 1.0.1.RELEASE version: 1.0.1.RELEASE
scope: test scope: test
@ -243,7 +243,7 @@ initializr:
description: PostgreSQL jdbc driver description: PostgreSQL jdbc driver
groupId: org.postgresql groupId: org.postgresql
artifactId: postgresql artifactId: postgresql
versions: mappings:
- versionRange: "[1.2.0.RELEASE,1.3.0.M1)" - versionRange: "[1.2.0.RELEASE,1.3.0.M1)"
version: 9.4-1201-jdbc41 version: 9.4-1201-jdbc41
scope: runtime scope: runtime
@ -269,11 +269,13 @@ initializr:
- name: Redis - name: Redis
id: data-redis id: data-redis
description: REDIS key-value data store, including spring-redis description: REDIS key-value data store, including spring-redis
versionRange: 1.4.0.M1 aliases:
- name: Redis - redis
id: redis mappings:
description: REDIS key-value data store, including spring-redis - versionRange: 1.4.0.M1
versionRange: "[1.1.5.RELEASE,1.4.0.M1)" artifactId: spring-boot-starter-data-redis
- versionRange: "[1.1.5.RELEASE,1.4.0.M1)"
artifactId: spring-boot-starter-redis
- name: Gemfire - name: Gemfire
id: data-gemfire id: data-gemfire
description: GemFire distributed data store including spring-data-gemfire description: GemFire distributed data store including spring-data-gemfire

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2015 the original author or authors. * Copyright 2012-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -54,20 +54,24 @@ class DefaultDependencyMetadataProviderTests {
@Test @Test
void resolveDependencies() { void resolveDependencies() {
def first = new Dependency(id: 'first', groupId: 'org.foo', artifactId: 'first') def first = new Dependency(id: 'first', groupId: 'org.foo', artifactId: 'first')
first.versions << new Dependency.Mapping(versionRange: '[1.0.0.RELEASE, 1.1.0.RELEASE)', first.mappings << new Dependency.Mapping(versionRange: '[1.0.0.RELEASE, 1.1.0.RELEASE)',
version: '0.1.0.RELEASE') version: '0.1.0.RELEASE', groupId: 'org.bar', artifactId: 'second')
first.versions << new Dependency.Mapping(versionRange: '1.1.0.RELEASE', first.mappings << new Dependency.Mapping(versionRange: '1.1.0.RELEASE',
version: '0.2.0.RELEASE') version: '0.2.0.RELEASE', groupId: 'org.biz', artifactId: 'third')
def second = new Dependency(id: 'second', groupId: 'org.foo', artifactId: 'second') def second = new Dependency(id: 'second', groupId: 'org.foo', artifactId: 'second')
def metadata = InitializrMetadataTestBuilder.withDefaults() def metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('test', first, second).build() .addDependencyGroup('test', first, second).build()
def dependencyMetadata = provider.get(metadata, Version.parse('1.0.5.RELEASE')) def dependencyMetadata = provider.get(metadata, Version.parse('1.0.5.RELEASE'))
assertEquals 2, dependencyMetadata.dependencies.size() assertEquals 2, dependencyMetadata.dependencies.size()
assertEquals('org.bar', dependencyMetadata.dependencies['first'].groupId)
assertEquals('second', dependencyMetadata.dependencies['first'].artifactId)
assertEquals('0.1.0.RELEASE', dependencyMetadata.dependencies['first'].version) assertEquals('0.1.0.RELEASE', dependencyMetadata.dependencies['first'].version)
def anotherDependencyMetadata = provider.get(metadata, Version.parse('1.1.0.RELEASE')) def anotherDependencyMetadata = provider.get(metadata, Version.parse('1.1.0.RELEASE'))
assertEquals 2, anotherDependencyMetadata.dependencies.size() assertEquals 2, anotherDependencyMetadata.dependencies.size()
assertEquals('org.biz', anotherDependencyMetadata.dependencies['first'].groupId)
assertEquals('third', anotherDependencyMetadata.dependencies['first'].artifactId)
assertEquals('0.2.0.RELEASE', anotherDependencyMetadata.dependencies['first'].version) assertEquals('0.2.0.RELEASE', anotherDependencyMetadata.dependencies['first'].version)
} }