Reputation: 2745
I am migrating a Java project from Ant to Gradle. I think the best solution is to use Gradle's multi-project support, but I cannot find a way to get rid of a circular dependency.
The original project was setup to have this layout:
- project/
- common/
- product-a/
- product-b/
The relationship between common
, product-a
, and product-b
is tricky. The common
depends on product-a
or product-b
, depending on a configuration file. Likewise, product-a
and product-b
depend on common
, regardless of the configuration property. product-a
and product-b
will never be built at the same time.
I thought a quick solution would be to use something like this in the project/build.gradle
:
project(':product-a') {
dependencies {
compile project(':common')
}
}
project(':product-b') {
dependencies {
compile project(':common')
}
}
Next, I thought about getting a way to get this closer to working for just product-a
. That led me to this:
project(':common') {
dependencies {
compile project(':product-a')
}
}
This will throw an exception for having a circular dependency.
I've considered refactoring product-a
and product-b
by setting up interfaces of the classes expected by common
and product-a
/product-b
or by using polymorphism, but before I move forward with either of those, is there a better way to accomplish this with Gradle? I'm not ready to get rid of this technical debt yet.
Upvotes: 23
Views: 35684
Reputation: 11
project(':project-a') {
dependencies {
compile project(':project-b')
}
}
project(':project-b') {
dependencies {
//circular dependency to :project-a
compile project(':project-a')
}
compileJava {
doLast {
// NOTE: project-a needs :project-b classes to be included
// in :project-a jar file hence the copy, mostly done if we need to
// to support different version of the same library
// compile each version on a separate project
copy {
from "$buildDir/classes/java/main"
include '**/*.class'
into project(':project-a').file('build/classes/java/main')
}
}
}
}
product-a --> build.gradle
/**
* Do nothing during configuration stage by
* registering a GradleBuild task
* will be referenced in the task compileJava doLast{}
*/
tasks.register("copyProjectBClasses", GradleBuild) {
//we'll invoke this later
def taskList = new ArrayList<String>()
taskList.add(":product-b:compileJava")
logger.lifecycle "Task to execute $taskList..."
setTasks(taskList)
}
// make sure :project-b classes are compiled first and copied to this project before
// all classes are added to the jar, so we do it after :project-a compiled.
compileJava {
doLast {
synchronized(this) {
// create temp file to avoid circular dependency
def newFile = new File("$buildDir/ongoingcopy.tmp")
if (!newFile.exists()) {
newFile.createNewFile()
GradleBuild buildCopyProjectBClasses = tasks.getByName("copyProjectBClasses")
buildCopyProjectBClasses.build()
}
newFile.delete()
}
}
}
Upvotes: 1
Reputation: 15225
Removing a circular dependency cannot be resolved with build trickery. You're going to have to refactor your modules so there is no longer a circular dependency. From your module names, and with no other information, I would think you would want to extract the part of "common" that depends on "product-*" and put it into a new module.
Upvotes: 26