Search code examples
gradlegradle-kotlin-dsl

Is it possible for 2 submodules to have different paths but identical names?


example project (written in Gradle 8.1.1 with kotlin DSL):

https://github.com/tribbloid/scaffold-gradle-kts/tree/16bf3acffca7b83a7e64dbb248a9512caba87e71

This project has 3+ submodules:

include(":lightweight-dependency:core")
include(":lightweight-dependency:extra") // depends on :lightweight-dependency:core
include(":core") // depends on lightweight-dependency:extra
...

When being compiled, it gave the following error:

FAILURE: Build failed with an exception.

* What went wrong:
Circular dependency between the following tasks:
:core:compileJava
+--- :core:compileJava (*)
+--- :core:compileKotlin
|    +--- :core:compileJava (*)
|    +--- :core:compileKotlin (*)
|    \--- :core:compileScala
|         +--- :core:compileJava (*)
|         +--- :core:compileKotlin (*)
|         \--- :core:compileScala (*)
\--- :core:compileScala (*)

(*) - details omitted (listed previously)

This is problematic as :lightweight-dependency:core and :core should be different projects. How to ensure that Gradle can differentiate them?


Solution

  • Gradle has issues resolving dependencies when two subproject names are the same, even if the paths are different.

    This is because Gradle differentiates between inter-subproject dependencies using the group and artifact ID of the subprojects.

    There are three options:

    1. Update the subproject paths to be distinct. This is my preferred option.

      .
      └── my-project/
          ├── core/
          │   └── build.gradle.kts
          ├── lightweight-dependency/
          │   └── lightweight-dependency-core/
          │       └── build.gradle.kts
          ├── build.gradle.kts
          └── settings.gradle.kts
      
      // settings.gradle.kts
      
      include(
        ":core",
        ":lightweight-dependency:lightweight-dependency-core",
      )
      

      The result is more verbose, but it's more explicit, and I think it follows the principal of least astonishment most closely. It's nice that the subproject file directory matches the artifact ID, which helps with understanding the structure even without loading any Gradle config.

    2. Use a different group per subproject.

      // ./core/build.gradle.kts
      
      group = "my.project"
      
      // ./lightweight-dependency/core/build.gradle.kts
      
      group = "my.project.lightweight-dependency"
      

      Now the coordinates of the two subprojects are different.

      This is also a convenient solution, although it requires some care to make sure that subproject groups don't clash, or to be aware of the requirement if the subprojects IDs or groups are ever updated.

    3. Update the path of the project when they are included settings.gradle.kts by using the ProjectDescriptor

      // settings.gradle.kts
      
      include(":core")
      include(":lightweight-dependency:core")
      
      project(":core").name = "core"
      project(":lightweight-dependency:core").name = "lightweight-dependency-core"
      

      I do not recommend this approach. I think updating the project names in this way is confusing, and unconventional. It will likely lead to unexpected issues, because although it is valid, other Gradle plugins might not realise it is possible and will expect that the subproject location matches the subproject name.

      This approach is also discouraged in the Gradle documentation:

      Keep default project names for subprojects: It is possible to configure custom project names in the settings file. However, it’s an unnecessary extra effort for the developers to keep track of which project belongs to what folders.