Refactor merge-artifacts script to a plugin

Refactor the existing merge-artifacts script to a full Gradle plugin.
The new plugin uses a 'merge' extension in favor of Gradle 'ext'
configuration and can be applied to all projects in a multi-module
build.

Any project that defines a 'merge.into' project will now implicitly
receive a dependency to that project. Furthermore other projects
with a dependency on 'merge.into' will receive a direct project
link within the IDE. For example: 'spring-webmvc-tiles3'
is merged into 'spring-webmvc' and 'spring-test-mvc' depends
on 'spring-webmvc'. Within the IDE 'spring-test-mvc' will show
dependencies to both 'spring-webmvc' and 'spring-webmvc-tiles'.

Issue: SPR-10042
This commit is contained in:
Phillip Webb 2012-12-08 18:46:24 -08:00
parent 4551cfcdfc
commit 87f7ecf907
4 changed files with 137 additions and 79 deletions

View File

@ -46,6 +46,7 @@ configure(allprojects) {
}
configure(subprojects) { subproject ->
apply plugin: MergePlugin
apply from: "${gradleScriptDir}/publish-maven.gradle"
jar {
@ -432,10 +433,8 @@ project("spring-orm") {
project("spring-orm-hibernate4") {
description = "Spring Object/Relational Mapping - Hibernate 4 support"
ext.mergeIntoProject = project(":spring-orm")
apply from: "${gradleScriptDir}/merge-artifacts.gradle"
merge.into = project(":spring-orm")
dependencies {
compile(project(":spring-orm").sourceSets.main.output)
compile(project(":spring-tx"))
compile(project(":spring-jdbc"))
optional("org.hibernate:hibernate-core:4.1.0.Final")
@ -506,11 +505,9 @@ project("spring-webmvc") {
project("spring-webmvc-tiles3") {
description = "Spring Framework Tiles3 Integration"
ext.mergeIntoProject = project(":spring-webmvc")
apply from: "${gradleScriptDir}/merge-artifacts.gradle"
merge.into = project(":spring-webmvc")
dependencies {
compile(project(":spring-context"))
compile(project(":spring-webmvc").sourceSets.main.output)
provided("javax.el:el-api:1.0")
provided("javax.servlet:jstl:1.2")
provided("javax.servlet.jsp:jsp-api:2.1")
@ -581,13 +578,10 @@ project("spring-test") {
project("spring-test-mvc") {
description = "Spring Test MVC Framework"
ext.mergeIntoProject = project(":spring-test")
apply from: "${gradleScriptDir}/merge-artifacts.gradle"
apply from: "ide.gradle"
merge.into = project(":spring-test")
dependencies {
optional(project(":spring-context"))
compile(project(":spring-webmvc"))
compile(project(":spring-test").sourceSets.main.output)
provided("javax.servlet:javax.servlet-api:3.0.1")
optional("org.hamcrest:hamcrest-core:1.3")
optional("com.jayway.jsonpath:json-path:0.8.1")
@ -663,6 +657,7 @@ configure(rootProject) {
description = "Spring Framework"
apply plugin: "docbook-reference"
apply plugin: "groovy"
apply from: "${gradleScriptDir}/jdiff.gradle"
reference {
@ -674,6 +669,8 @@ configure(rootProject) {
configurations.archives.artifacts.clear()
dependencies { // for integration tests
compile gradleApi()
groovy localGroovy()
testCompile(project(":spring-core"))
testCompile(project(":spring-beans"))
testCompile(project(":spring-aop"))

View File

@ -0,0 +1,130 @@
import org.gradle.api.*
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ProjectDependency;
import org.gradle.api.artifacts.maven.Conf2ScopeMapping
import org.gradle.api.plugins.MavenPlugin
import org.gradle.api.tasks.*
import org.gradle.plugins.ide.eclipse.EclipsePlugin
import org.gradle.plugins.ide.eclipse.model.EclipseClasspath;
import org.gradle.plugins.ide.idea.IdeaPlugin
import org.gradle.api.invocation.*
/**
* Gradle plugin that allows projects to merged together. Primarily developed to
* allow Spring to support multiple multiple incompatible versions of third-party
* dependencies (for example Hibernate v3 and v4).
* <p>
* The 'merge' extension should be used to define how projects are merged, for example:
* <pre>
* configure(subprojects) {
* apply plugin: MergePlugin
* }
*
* project("myproject") {
* }
*
* project("myproject-extra") {
* merge.into = project("myproject")
* }
* </pre>
* <p>
* This plugin adds two new configurations:
* <ul>
* <li>merging - Contains the projects being merged into this project<li>
* <li>runtimeMerge - Contains all dependencies that are merge projects. These are used
* to allow an IDE to reference merge projects.</li>
* <ul>
*
* @author Rob Winch
* @author Phillip Webb
*/
class MergePlugin implements Plugin<Project> {
private static boolean attachedProjectsEvaluated;
public void apply(Project project) {
project.plugins.apply(MavenPlugin)
project.plugins.apply(EclipsePlugin)
project.plugins.apply(IdeaPlugin)
MergeModel model = project.extensions.create("merge", MergeModel)
project.configurations.add("merging")
Configuration runtimeMerge = project.configurations.add("runtimeMerge")
// Ensure the IDE can reference merged projects
project.eclipse.classpath.plusConfigurations += [runtimeMerge]
project.idea.module.scopes.PROVIDED.plus += runtimeMerge
// Hook to perform the actual merge logic
project.afterEvaluate{
if(it.merge.into != null) {
setup(it)
}
}
// Hook to build runtimeMerge dependencies
if(!attachedProjectsEvaluated) {
project.gradle.projectsEvaluated{
postProcessProjects(it)
}
attachedProjectsEvaluated = true;
}
}
private void setup(Project project) {
project.merge.into.dependencies.add("merging", project)
project.dependencies.add("provided", project.merge.into.sourceSets.main.output)
project.dependencies.add("runtimeMerge", project.merge.into)
setupTaskDependencies(project)
setupMaven(project)
}
private void setupTaskDependencies(Project project) {
// invoking a task will invoke the task with the same name on 'into' project
["sourcesJar", "jar", "javadocJar", "javadoc", "install", "artifactoryPublish"].each {
def task = project.tasks.findByPath(it)
if(task) {
task.enabled = false
task.dependsOn(project.merge.into.tasks.findByPath(it))
}
}
// update 'into' project artifacts to contain the source artifact contents
project.merge.into.sourcesJar.from(project.sourcesJar.source)
project.merge.into.jar.from(project.jar.source)
project.merge.into.javadoc {
source += project.javadoc.source
classpath += project.javadoc.classpath
}
}
private void setupMaven(Project project) {
project.configurations.each { configuration ->
Conf2ScopeMapping mapping = project.conf2ScopeMappings.getMapping([configuration])
if(mapping.scope) {
Configuration intoConfiguration = project.merge.into.configurations.add(
project.name + "-" + configuration.name)
intoConfiguration.dependencies.addAll(configuration.dependencies)
project.merge.into.install.repositories.mavenInstaller.pom.scopeMappings.addMapping(
mapping.priority + 100, intoConfiguration, mapping.scope)
}
}
}
private postProcessProjects(Gradle gradle) {
gradle.allprojects(new Action<Project>() {
public void execute(Project project) {
project.configurations.getByName("runtime").allDependencies.withType(ProjectDependency).each{
Configuration dependsOnMergedFrom = it.dependencyProject.configurations.getByName("merging");
dependsOnMergedFrom.dependencies.each{ dep ->
project.dependencies.add("runtimeMerge", dep.dependencyProject)
}
}
}
});
}
}
class MergeModel {
Project into;
}

View File

@ -1,62 +0,0 @@
/**
* Will merge the artifacts of the current project into mergeIntoProject. For example, to
* bundle spring-test-mvc in spring-test"s jars. This script will perform the following
* steps:
*
* - Ensure that jar tasks of the project being merged from will execute the tasks of the
* project being merged into
*
* - Add the project being merged into to the classpath of the project being merged from
*
* - Update the pom.xml of the project being merged into to contain the entries from the
* project being merged from
*
* Example Usage:
*
* ext.mergeIntoProject = project(":spring-test")
* apply from: "${rootProject.projectDir}/gradle/merge-artifacts.gradle"
*/
def mergeFromProject = project
// invoking a task on mergeFromProject will invoke the task with the same name on mergeIntoProject
def taskNamesToMerge = ["sourcesJar","jar","javadocJar","javadoc","install","artifactoryPublish"]
taskNamesToMerge.each { taskName ->
def taskToRemove = mergeFromProject.tasks.findByPath(taskName)
if(taskToRemove) {
taskToRemove.enabled = false
taskToRemove.dependsOn mergeIntoProject."$taskName"
}
}
// update mergeIntoProject artifacts to contain the mergeFromProject artifact contents
mergeIntoProject."sourcesJar" {
from mergeFromProject.sourcesJar.source
}
mergeIntoProject."jar" {
from mergeFromProject.jar.source
}
mergeIntoProject."javadoc" {
source += mergeFromProject.javadoc.source
classpath += mergeFromProject.javadoc.classpath
}
// Update mergeIntoProject to contain additional configurations that contains all the dependencies from mergeFromProject
// so that Maven pom generation works
gradle.taskGraph.whenReady {
mergeFromProject.configurations.archives.artifacts.clear()
mergeFromProject.configurations.each { config->
def mapping = mergeFromProject.conf2ScopeMappings.getMapping([config])
if(mapping.scope) {
def newConfigName = mergeFromProject.name + "-"+ config.name
mergeIntoProject.configurations.add(newConfigName)
config.dependencies.each { dependency ->
mergeIntoProject.dependencies.add(newConfigName, dependency)
}
configure(mergeIntoProject.install.repositories.mavenInstaller.pom.scopeMappings) {
addMapping(mapping.priority + 100, mergeIntoProject.configurations."$newConfigName", mapping.scope)
}
}
}
}

View File

@ -1,7 +0,0 @@
import org.gradle.plugins.ide.eclipse.model.ProjectDependency
// SPR-10042
eclipse.classpath.file.whenMerged { classpath ->
def projectName = "spring-webmvc-tiles3"
classpath.entries.add(0, new ProjectDependency("/${projectName}", project(":${projectName}").path))
}