Gradle support for cyclic dependencies

New to gradle. I am trying to implement gradle for an existing multi project (java)
build. And there exists circular dependencies between some of the sub projects.
Does gradle support this kind of a scenario? If so, whats the best strategy?
Thanks in advance.

It depends on the nature of your cycles. Dependencies are grouped into configurations. As long as the configurations are cycle-free, you’ll be fine. For instance, the following will work:

project(':a') {
  apply plugin: 'java'
  dependencies {
    testCompile project(':b')
  }
}

project(':b') {
  apply plugin: 'java'
  dependencies {
    compile project(':a')
  }
}

Still, removing those cycles is probably a good idea :wink:

Thanks for your response. I tried the method suggested, still having issue with the build.
I created a simple project to mimic the issue I am trying to resolve.

2 Java Projects:

GradleCycliecDependencyTest, and GradleCycliecDependencyTest are 2 simple Java projects with the cyclic dependency between them.

Class from the first project:

public class TestA1 {

    private TestA2 a2;

}

class from the second project:

public class TestA2 {

    private TestA1 a1;

}

So, they have circular dependency.

from the CyclicBuild directory:

contents of build.gradle:

project(‘:GradleCyclicDependencyTest’) {
apply plugin: ‘java’
sourceSets {

main {
    java {
         srcDir "../../$project.name/src"
    }
    resources {
    }
}

}

dependencies {
compile project(‘:GradleCyclicDependencyTest2’)
}
}

project(‘:GradleCyclicDependencyTest2’) {
apply plugin: ‘java’
sourceSets {

main {
    java {
             srcDir "../../$project.name/src"
    }
    resources {
    }
}

}
dependencies {
testCompile project(‘:GradleCyclicDependencyTest’)
}
}

contents of settings.gradle;

include ‘GradleCyclicDependencyTest’, ‘GradleCyclicDependencyTest2’

When I run the gradle java --info from the CyclicBuild folder, this is the output:

$ gradle java --info
Starting Build
Settings evaluated using settings file ‘C:\LocalSysWorkspaces\RFID\Test\CyclicBuild\settings.gradle’.
Projects loaded. Root project using build file ‘C:\LocalSysWorkspaces\RFID\Test\CyclicBuild\build.gradle’.
Included projects: [root project ‘CyclicBuild’, project ‘:GradleCyclicDependencyTest’, project ‘:GradleCyclicDependencyTest2’]
Evaluating root project ‘CyclicBuild’ using build file ‘C:\LocalSysWorkspaces\RFID\Test\CyclicBuild\build.gradle’.
Evaluating project ‘:GradleCyclicDependencyTest’ using build file ‘C:\LocalSysWorkspaces\RFID\Test\CyclicBuild\GradleCyclicDependencyTest\build.gradle’.
Evaluating project ‘:GradleCyclicDependencyTest2’ using build file ‘C:\LocalSysWorkspaces\RFID\Test\CyclicBuild\GradleCyclicDependencyTest2\build.gradle’.
All projects evaluated.
Selected primary task ‘javadoc’ from project :
Tasks to be executed: [task ‘:GradleCyclicDependencyTest2:compileJava’, task ‘:GradleCyclicDependencyTest2:processResources’, task ‘:GradleCyclicDependencyTest2:classes’, task ‘:GradleCyclicDependencyTest2:jar’, task ‘:GradleCyclicDependencyTest:compileJava’, task ‘:GradleCyclicDependencyTest:processResources’, task ‘:GradleCyclicDependencyTest:classes’, task ‘:GradleCyclicDependencyTest2:javadoc’, task ‘:GradleCyclicDependencyTest:javadoc’]
:GradleCyclicDependencyTest2:compileJava (Thread[main,5,main]) started.
:GradleCyclicDependencyTest2:compileJava
file or directory ‘C:\LocalSysWorkspaces\RFID\Test\CyclicBuild\GradleCyclicDependencyTest2\src\main\java’, not found
file or directory ‘C:\LocalSysWorkspaces\RFID\Test\CyclicBuild\GradleCyclicDependencyTest2\src\main\java’, not found
Executing task ‘:GradleCyclicDependencyTest2:compileJava’ (up-to-date check took 0.043 secs) due to:
No history is available.
All input files are considered out-of-date for incremental task ‘:GradleCyclicDependencyTest2:compileJava’.
file or directory ‘C:\LocalSysWorkspaces\RFID\Test\CyclicBuild\GradleCyclicDependencyTest2\src\main\java’, not found
Compiling with JDK Java compiler API.
C:\LocalSysWorkspaces\RFID\Test\GradleCyclicDependencyTest2\src\TestA2.java:4: error: cannot find symbol
private TestA1 a1;
^
symbol: class TestA1
location: class TestA2
1 error
:GradleCyclicDependencyTest2:compileJava FAILED
:GradleCyclicDependencyTest2:compileJava (Thread[main,5,main]) completed. Took 0.277 secs.

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ‘:GradleCyclicDependencyTest2:compileJava’.

Compilation failed; see the compiler error output for details.

  • Try:
    Run with --stacktrace option to get the stack trace. Run with --debug option to get more log output.

BUILD FAILED

Not sure if the method you suggested handles the circular dependency shown here, or if I am doing something wrong. Thanks for for your assistance.

What I posted was only meant as an example. It would work if A only needed B for testing.

The example you posted has the implementation of A depending on the implementation of B and vice versa. That won’t work. How do you build that project currently?

What could work (but I don’t know if that is your case):

  • The implementation of A depends on the api of B
  • The implementation of B depends on the api of A
  • The apis of both are independent

In that case, you’ll need to model a seperate api jar for both projects, so they could reference each other through that.

We build it using eclipse currently. If implementation of A depends on implementation of B and viceversa, is there a way to do it in gradle? Not sure how eclipse does it.

No, there is not. Gradle builds based on source sets. You’ll have to break it up into parts that can be built independently.

Eclipse builds based on individual files, so it can in principle handle such cycles. But cycles are flagged as an error by Eclipse by default, for good reason. It’s a design smell that the projects should either be merged or refactored.

Still, removing those cycles is probably a good idea :wink:

For tests support code, what’s idiomatic way to use it for the tests while it being a separate artifact?

I.e.:

  • project library-x:
    • maven artifact ID library-x,
    • implementation:
      • no dependencies or some (non-cycled),
    • test:
      • depends on library-x-test-support:implementation,
  • project library-x-test-support:
    • maven artifact ID library-x-test-support
    • implementation:
      • depends on library-x (and various testing tools),
    • test:
      • etc.

I guess that’s probably fine.
Or you could instead consider using java-test-fixtures so that you have the test support code in a separate source set, so do not have a “project cycle”. :man_shrugging:

And, release that source-set as a separate artifact? I am used to tie sub-projects to artifacts.

It also gets a bit more tricky, as I’m using Kotlin Multiplatform for the particular project.

Test fixtures are by default published as additional artifacts under the same coordinates as separate feature variant, that can easily be consumed, especially by Gradle consumers.

But dunno how good that would work with KMP, especially if your test-support is also multi-platform.

In that case your current setup might be appropriate already I guess.