Skip to content
This repository has been archived by the owner on Jan 15, 2024. It is now read-only.

Commit

Permalink
Improve handling of unmapped dependencies
Browse files Browse the repository at this point in the history
Previously, all unmapped dependencies were treated equally. With this
commit a distinction is now made between unmapped direct dependencies
and unmapped transitive dependencies. By default, the build will fail
in the event of one or more unmapped direct dependencies but unmapped
transitive dependencies have no effect on the build's outcome. This
behaviour can be overridden via the  springioPlatform extension's
failOnUnmappedDirectDependency and failOnUnmappedTransitiveDependency
properties respectively. For example, to only fail on unmapped
transitive dependencies:

springioPlatform {
	failOnUnmappedDirectDependency = false
	failOnUnmappedTransitiveDependency = true
}
  • Loading branch information
wilkinsona committed Apr 30, 2014
1 parent f0d75d4 commit 19ef0ba
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 32 deletions.
60 changes: 46 additions & 14 deletions springio-platform-plugin/README.asciidoc
Expand Up @@ -14,9 +14,8 @@ Provides additional checks to ensure springio platform compatibility

== Quick Start

See http://repo.spring.io/repo/org/springframework/build/gradle/springio-platform-plugin/[repo.spring.io] to determine the
latest available version. Then configure the plugin in your project as
follows:
See http://repo.spring.io/repo/org/springframework/build/gradle/springio-platform-plugin/[repo.spring.io] to determine
the latest available version. Then configure the plugin in your project as follows:

[source,groovy]
----
Expand All @@ -41,7 +40,9 @@ configure(allprojects) {
}
----

NOTE: It is unlikely you will want to apply the plugin or snapshot repository to all projects. This is particularly true if you have sample projects within your project. Instead, you should limit the plugin to modules that are part of the Spring IO platform.
NOTE: It is unlikely you will want to apply the plugin or snapshot repository to all projects. This is particularly
true if you have sample projects within your project. Instead, you should limit the plugin to modules that are part of
the Spring IO platform.

Now you can run the following:

Expand All @@ -52,11 +53,17 @@ $ ./gradlew clean springioCheck -PJDK8_HOME=<jdk8-home> -PJDK7_HOME=<jdk7-home>

This will:

* Assist with running tests against your compiled code using JDK7 and JDK8 and the SpringIO dependency versions. For more information refer to <<additional-tests>>
* Ensure that all dependency exclusions use both the group and the module. For more information refer to <<incompleteexcludestask>>
* Verify that certain dependencies are not used and suggest alternatives. For more information refer to <<alternativedependenciestask>>
* Check that Spring IO contains versions for all direct dependencies, all transitive dependencies, or both. For more
information refer to <<spring-io-dependencies>>
* Assist with running tests against your compiled code using JDK7 and JDK8 and the Spring IO dependency versions. For
more information refer to <<additional-tests>>
* Ensure that all dependency exclusions use both the group and the module. For more information refer to
<<incompleteexcludestask>>
* Verify that certain dependencies are not used and suggest alternatives. For more information refer to
<<alternativedependenciestask>>

If you would like springioCheck to be invoked when you run `./gradlew build`, then you can make the check task depend on springioCheck as shown below:
If you would like springioCheck to be invoked when you run `./gradlew build`, then you can make the check task depend
on springioCheck as shown below:

[source,groovy]
----
Expand All @@ -71,23 +78,45 @@ configure(allprojects) {

Typically users will keep the springioCheck task separate so as to only run the springioCheck task on the CI server.

== Spring IO dependencies

The plugin creates a new configuration, `springioTestRuntime`, that contains all of the project's dependencies with
their versions mapped to those that are in the platform. This configuration is used when running the additional
tests. By default, if a direct dependency is encountered that is not part of the platform the build will fail. This
can be configured using the `springIoPlatform` extension. For example:

[source,groovy]
springioPlatform {
failOnUnmappedDirectDependency = true
failOnUnmappedTransitiveDependency = true
}

`failOnUnmappedDirectDependency` controls whether or not the build will fail if a direct dependency is encountered that
is not part of the Spring IO plaform. The default is `true`. `failOnUnmappedTransitiveDependency` controls whether or
not the build will fail if a transitive dependency is encountered that is not part of the Spring IO platform. The
default is `false`.

== Additional Tests

One of the goals of the Spring IO platform is to ensure modules work with JDK7 and JDK8 and that they run with specific versions of dependencies. Applying the plugin will create tests that:
One of the goals of the Spring IO platform is to ensure modules work with JDK7 and JDK8 and that they run with specific
versions of dependencies. Applying the plugin will create tests that:

* Nothing changes for how your code is actually compiled or consumed by users (dependency changes and JDK changes only impact the additional tests)
* Nothing changes for how your code is actually compiled or consumed by users (dependency changes and JDK changes only
impact the additional tests)
* Ensure that the Spring IO versions of dependencies are used at runtime for the additional test tasks
* Tests are ran against the specified JDKs
* If JDK7_HOME and JDK8_HOME is omitted, then no additional test tasks will be created

For example, the following will compile the project with the declared dependency versions and JDK. It will then run all the tests against JDK7 and JDK8 with the Spring IO dependency versions.
For example, the following will compile the project with the declared dependency versions and JDK. It will then run all
the tests against JDK7 and JDK8 with the Spring IO dependency versions.

[source,bash]
----
$ ./gradlew springioCheck -PJDK7_HOME=/opt/java/jdk/Sun/7.0 -PJDK8_HOME=/opt/java/jdk/Sun/8.0
----

Where `JDK8_HOME` is the absolute path to the JDK8 Home and `JDK7_HOME` is the absolute path to the JDK7 Home. The example above works with the Spring Bamboo environment.
Where `JDK8_HOME` is the absolute path to the JDK8 Home and `JDK7_HOME` is the absolute path to the JDK7 Home. The
example above works with the Spring Bamboo environment.

[source,bash]
----
Expand All @@ -98,7 +127,9 @@ NOTE: You can also place JDK8_HOME and JDK7_HOME in your gradle.properties

== IncompleteExcludesTask

This task ensures that any dependency exclusions that are done use both the group and the module because otherwise the dependency will not be excluded in the generated pom.xml file. For example the following is not allowed because it only excludes the module:
This task ensures that any dependency exclusions that are done use both the group and the module because otherwise the
dependency will not be excluded in the generated pom.xml file. For example the following is not allowed because it only
excludes the module:

[source,groovy]
----
Expand Down Expand Up @@ -133,4 +164,5 @@ dependencies {

== AlternativeDependenciesTask

This task will ensure certain dependencies are not used and suggest alternatives. For example, intead of using asm:asm it is preferred to use spring-core's repackages asm dependencies.
This task will ensure certain dependencies are not used and suggest alternatives. For example, intead of using asm:asm
it is preferred to use spring-core's repackages asm dependencies.
@@ -1,8 +1,10 @@
package org.springframework.build.gradle.springio.platform;

import org.gradle.api.Action
import org.gradle.api.InvalidUserDataException
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.DependencyResolveDetails
import org.gradle.api.artifacts.ModuleVersionSelector
import org.gradle.api.artifacts.ResolvableDependencies
Expand All @@ -22,10 +24,28 @@ class PlatformDependenciesBeforeResolveAction implements Action<ResolvableDepend
public void execute(ResolvableDependencies resolvableDependencies) {
def action = project.springioPlatform.dependencyResolutionAction;
if (!action) {
action = createDefaultActionFromStream(project,getClass().getResourceAsStream('springio-dependencies'))
action = createDefaultActionFromStream(project, configuration, getClass().getResourceAsStream('springio-dependencies'))
}

configuration.resolutionStrategy.eachDependency action

configuration.incoming.afterResolve {
if (action instanceof MappingDependencyResolveDetailsAction) {
String message
if (project.springioPlatform.failOnUnmappedDirectDependency && action.unmappedDirectDependencies) {
message = "The following direct dependencies do not have Spring IO versions: " + action.unmappedDirectDependencies.collect { "$it.group:$it.name" }.join(", ")
}

if (project.springioPlatform.failOnUnmappedTransitiveDependency && action.unmappedTransitiveDependencies) {
message = message ? message + ". " : ""
message += "The following transitive dependencies do not have Spring IO versions: " + action.unmappedTransitiveDependencies.collect { "$it.group:$it.name" }.join(", ")
}

if (message) {
throw new InvalidUserDataException(message)
}
}
}
}

/**
Expand All @@ -34,7 +54,7 @@ class PlatformDependenciesBeforeResolveAction implements Action<ResolvableDepend
*
* @return
*/
private static MappingDependencyResolveDetailsAction createDefaultActionFromStream(Project project, InputStream stream) {
private static MappingDependencyResolveDetailsAction createDefaultActionFromStream(Project project, Configuration configuration, InputStream stream) {
Map<String,ModuleVersionSelector> depToSelector = [:]
stream.eachLine { line ->
if(line && !line.startsWith('#')) {
Expand All @@ -44,11 +64,19 @@ class PlatformDependenciesBeforeResolveAction implements Action<ResolvableDepend
}
}
Set<String> ignoredGroupAndNames = project.rootProject.allprojects.collect { "$it.group:$it.name" }
new MappingDependencyResolveDetailsAction(depToSelector : depToSelector, ignoredGroupAndNames : ignoredGroupAndNames)
new MappingDependencyResolveDetailsAction(depToSelector : depToSelector, ignoredGroupAndNames : ignoredGroupAndNames, configuration : configuration)
}

private static class MappingDependencyResolveDetailsAction implements Action<DependencyResolveDetails> {

Configuration configuration

Map<String,ModuleVersionSelector> depToSelector = [:]

List<ModuleVersionSelector> unmappedDirectDependencies = []

List<ModuleVersionSelector> unmappedTransitiveDependencies = []

/**
* In the format of group:name
*/
Expand All @@ -64,13 +92,27 @@ class PlatformDependenciesBeforeResolveAction implements Action<ResolvableDepend
ModuleVersionSelector mapping = getMapping("$requested.group:$requested.name") ?: (getMapping("$requested.group:") ?: getMapping(":$requested.name"))

if(!mapping) {
//logger.debug("Did NOT find mapping for $requested.group:$requested.name")
if (isDirectDependency(requested)) {
unmappedDirectDependencies << requested
} else {
unmappedTransitiveDependencies << requested
}

return
}

details.useTarget new DefaultModuleVersionSelector( mapping.group ?: requested.group, mapping.name ?: requested.name, mapping.version ?: requested.version)
}

private boolean isDirectDependency(ModuleVersionSelector selector) {
for (Dependency dependency: configuration.allDependencies) {
if (dependency.group == selector.group && dependency.name == selector.name) {
return true
}
}
return false
}

private ModuleVersionSelector getMapping(String id) {
depToSelector[id]
}
Expand Down
Expand Up @@ -16,4 +16,16 @@ class SpringioPlatformExtension {
* @see ResolutionStrategy#eachDependency
*/
def dependencyResolutionAction

/**
* Controls whether or not the build will fail when a direct dependency that does not
* have a Spring IO version mapping is encountered. Defaults to {@code true}.
*/
def failOnUnmappedDirectDependency = true

/**
* Controls whether or not the build will fail when a transitive dependency that does
* not have a Spring IO version mapping is encountered. Defaults to {@code false}.
*/
def failOnUnmappedTransitiveDependency = false
}
@@ -1,6 +1,7 @@
package org.springframework.build.gradle.springio.platform

import org.gradle.api.Action
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.DependencyResolveDetails
Expand Down Expand Up @@ -29,7 +30,7 @@ class PlatformDependenciesBeforeResolveActionTests extends Specification {
parent.version = 'nochange'

config = parent.configurations.create('configuration')
action = new PlatformDependenciesBeforeResolveAction(project:parent, configuration: config)
action = new PlatformDependenciesBeforeResolveAction(project: parent, configuration: config)

child = ProjectBuilder.builder().withName('child').withParent(parent).build()
child.group = parent.group
Expand Down Expand Up @@ -65,7 +66,7 @@ class PlatformDependenciesBeforeResolveActionTests extends Specification {
def "default dependency resolution action supports group only"() {
setup:
DependencyResolveDetails details = details('grouponly:notdefined:changeme')
configureDefaultDependencyResolutionAction(parent)
configureDefaultDependencyResolutionAction(parent, config)
when:
action.execute(Mock(ResolvableDependencies))
config.resolutionStrategy.dependencyResolveRule.execute(details)
Expand All @@ -78,7 +79,7 @@ class PlatformDependenciesBeforeResolveActionTests extends Specification {
def "default dependency resolution action supports name only"() {
setup:
DependencyResolveDetails details = details('notdefined:nameonly:changeme')
configureDefaultDependencyResolutionAction(parent)
configureDefaultDependencyResolutionAction(parent, config)
when:
action.execute(Mock(ResolvableDependencies))
config.resolutionStrategy.dependencyResolveRule.execute(details)
Expand All @@ -91,7 +92,7 @@ class PlatformDependenciesBeforeResolveActionTests extends Specification {
def "default dependency resolution action supports name and group"() {
setup:
DependencyResolveDetails details = details('standardgroup:standardname:changeme')
configureDefaultDependencyResolutionAction(parent)
configureDefaultDependencyResolutionAction(parent, config)
when:
action.execute(Mock(ResolvableDependencies))
config.resolutionStrategy.dependencyResolveRule.execute(details)
Expand All @@ -104,7 +105,7 @@ class PlatformDependenciesBeforeResolveActionTests extends Specification {
def "default dependency resolution action prioritizes both group and name highest"() {
setup:
DependencyResolveDetails details = details('prioritygroup:priorityname:changeme')
configureDefaultDependencyResolutionAction(parent)
configureDefaultDependencyResolutionAction(parent, config)
when:
action.execute(Mock(ResolvableDependencies))
config.resolutionStrategy.dependencyResolveRule.execute(details)
Expand All @@ -117,7 +118,7 @@ class PlatformDependenciesBeforeResolveActionTests extends Specification {
def "default dependency resolution action prioritizes group only second highest"() {
setup:
DependencyResolveDetails details = details('priority2group:notfound:changeme')
configureDefaultDependencyResolutionAction(parent)
configureDefaultDependencyResolutionAction(parent, config)
when:
action.execute(Mock(ResolvableDependencies))
config.resolutionStrategy.dependencyResolveRule.execute(details)
Expand All @@ -130,30 +131,72 @@ class PlatformDependenciesBeforeResolveActionTests extends Specification {
def "default dependency resolution action prioritizes name only second highest"() {
setup:
DependencyResolveDetails details = details('notfound:priority3name:changeme')
configureDefaultDependencyResolutionAction(parent)
configureDefaultDependencyResolutionAction(parent, config)
when:
action.execute(Mock(ResolvableDependencies))
config.resolutionStrategy.dependencyResolveRule.execute(details)
then:
details.target.version == 'priority3nameversion'
}

def "default dependency resolution action supports notfound"() {
def "default dependency resolution action fails with unmapped direct dependency"() {
setup:
parent.dependencies {
configuration "notfound:notfound:nochange"
}
configureDefaultDependencyResolutionAction(parent, config)
when:
action.execute(Mock(ResolvableDependencies))
config.resolvedConfiguration
then:
thrown InvalidUserDataException
}

def "default dependency resolution action succeeds with unmapped transitive dependency"() {
setup:
DependencyResolveDetails details = details('notfound:notfound:nochange')
configureDefaultDependencyResolutionAction(parent)
configureDefaultDependencyResolutionAction(parent, config)
when:
action.execute(Mock(ResolvableDependencies))
config.resolutionStrategy.dependencyResolveRule.execute(details)
then: 'resolution will succeed'
config.resolvedConfiguration
}

def "default dependency resolution action can be configured to fail with unmapped transitive dependency"() {
setup:
DependencyResolveDetails details = details('notfound:notfound:nochange')
configureDefaultDependencyResolutionAction(parent, config)
parent.springioPlatform {
failOnUnmappedTransitiveDependency = true
}
when:
action.execute(Mock(ResolvableDependencies))
config.resolutionStrategy.dependencyResolveRule.execute(details)
config.resolvedConfiguration
then:
details.target.version == 'nochange'
details.target.name == 'notfound'
details.target.group == 'notfound'
thrown InvalidUserDataException
}

def "default dependency resolution action can be configured to succeed with unmapped direct dependency"() {
setup:
parent.dependencies {
configuration "notfound:notfound:nochange"
}
parent.springioPlatform {
failOnUnmappedDirectDependency = false
}
configureDefaultDependencyResolutionAction(parent, config)
when:
action.execute(Mock(ResolvableDependencies))

then: 'resolution will succeeed'
config.resolvedConfiguration
}

void configureDefaultDependencyResolutionAction(Project project) {
void configureDefaultDependencyResolutionAction(Project project, Configuration configuration) {
project.springioPlatform {
dependencyResolutionAction = PlatformDependenciesBeforeResolveAction.createDefaultActionFromStream(parent, getClass().getResourceAsStream('test-springio-dependencies'))
dependencyResolutionAction = PlatformDependenciesBeforeResolveAction.createDefaultActionFromStream(parent, configuration, getClass().getResourceAsStream('test-springio-dependencies'))
}
}

Expand Down

0 comments on commit 19ef0ba

Please sign in to comment.