What is the best way to manage sub-projects in Gradle?

I’m migrating a large project from Maven to Gradle. This project has many sub-projects with a complex directory hierarchy.

By looking at other open-source projects that use Gradle, it seems that Gradle prefers to contain all subprojects in a flat directory rather than a directory hierarchy.

Here is a brief summary of the differences of how I believe Maven and Gradle view subprojects:

  • Gradle subprojects are created explicitly by the include statement in the settings.gradle file; Maven subprojects are created explicitly by the module element in the pom.xml file. * Gradle subprojects are referenced by their fully qualified name within a project hierarchy. Maven subprojects are referenced by their GAV. * A Gradle subproject’s parent is implied by its fully qualified name; A Maven subproject’s parent is explicitly declared by the parent element in the pom.xml. * A Gradle subproject’s name and root directory have a direct correlation by convention. A Maven subproject’s GAV and root directory have no correlation.

As an example:

±- app

±- business-logic

|

±- api

|

– impl

±- infrastructure

|

±- domainmodel

|

|

±- api

|

|

– impl

|

±- persistence

|

|

±- api

|

|

– impl

Needs to be referenced in the settings.gradle file this way:

include ‘app:business-logic:api’

include ‘app:business-logic:impl’

include ‘app:domainmodel:api

include ‘app:domainmodel:impl’

include ‘app:persistence:api

include ‘app:persistence:impl’

Problems arise when:

  1. The directory hierarchies are rather deep—the fully qualified name of subprojects becomes rather long and difficult to reference. 2. The project.name and project.projectDir have no direct correlation-- although this can be resolved by finding the project descriptor and modifying the projectDir, it is rather messy when there is no set pattern between subprojects.

Questions:

  1. Is the Gradle convention to have a flat directory structure for sub-projects? 2. Is there a way other than Settings.include to create project descriptors? 3. What is the best way to create a project when there is no correlation between the name and the directory? 4. What are the disadvantages to rooting all sub-projects off the rootProject? 5. What good open source examples are available that use Gradle and have a deep subproject hierarchy?

ad 1) ‘include’ gives a hierarchical layout, ‘includeFlat’ gives a flat layout. Choose whichever you prefer (most people use ‘include’). Gradle doesn’t care about the layout (logically a multi-project build is always a hierarchy).

ad 2) No there isn’t.

ad 3) Configure the project directory(s) in ‘settings.gradle’.

ad 4) Directory layout: none really (but I prefer to have logical layout match physical layout). Logical layout: There are no intermediate projects which can be used to operate on (configure, execute) all their children.

ad 5) I haven’t seen a project that went deeper than 3 (including root).

(What does “ad” mean?)

On the answer to question 4, by “directory” and “logical”, did you mean “hierarchical” and “flat”?

No I didn’t. It’s important to distinguish between the logical hierarchy of projects and the physical directory layout. When using ‘include’, the latter defaults to match the former, but it can changed at will to anything you like.

I believe the recommendation is to do something like this when the preferred names and directory hierarchy don’t match:

include 'xx-app:xx-business-logic:xx-business-logic-api'
    include 'xx-app:xx-business-logic:xx-business-logic-impl'
    include 'xx-app:xx-infrastructure:xx-domainmodel:xx-domainmodel-api'
    include 'xx-app:xx-infrastructure:xx-domainmodel:xx-domainmodel-impl'
    include 'xx-app:xx-infrastructure:xx-persistence:xx-persistence-api'
    include 'xx-app:xx-infrastructure:xx-persistence:xx-persistence-impl'
      project('xx-app:xx-business-logic:xx-business-logic-api').projectDir = "$rootDir/app/business-logic/api" as File
    project('xx-app:xx-business-logic:xx-business-logic-impl').projectDir = "$rootDir/app/business-logic/impl" as File
        project('xx-app:xx-infrastructure:xx-domainmodel:xx-domainmodel-api').projectDir = "$rootDir/app/infrastructure/domainmodel/api" as File
    project('xx-app:xx-infrastructure:xx-domainmodel:xx-domainmodel-impl').projectDir = "$rootDir/app/infrastructure/domainmodel/impl" as File
        project('xx-app:xx-infrastructure:xx-persistence:xx-persistence-api').projectDir = "$rootDir/app/infrastructure/persistence/api" as File
    project('xx-app:xx-infrastructure:xx-persistence:xx-persistence-impl').projectDir = "$rootDir/app/infrastructure/persistence/impl" as File

However, this is rather ugly and error prone since it requires the path id is specified multiple times. One could argue that my names are arbitrary and unnecessary, and I would half to agree. However, I’m converting a project from Maven that does just that (i.e., the artifactId does not match the directory).

I’d like to get your opinion on this approach in settings.gradle:

include name: ‘xx-app’,

path: ‘app’

include name: ‘xx-business-logic’,

path: ‘app/business-logic’

include name: ‘xx-business-logic-api’ path: ‘app/business-logic/api’

include name: ‘xx-business-logic-impl’ path: ‘app/business-logic/impl’

include name: ‘xx-infrastructure’,

path: ‘app/infrastructure’

include name: ‘xx-domainmodel’,

path: ‘app/infrastructure/domainmodel’

include name: ‘xx-domainmodel-api’,

path: ‘app/infrastructure/domainmodel/api’

include name: ‘xx-domainmodel-impl’,

path: ‘app/infrastructure/domainmodel/impl’

include name: ‘xx-persistence’,

path: ‘app/infrastructure/persistence’

include name: ‘xx-persistence-api’,

path: ‘app/infrastructure/persistence/api’

include name: ‘xx-persistence-impl’,

path: ‘app/infrastructure/persistence/impl’

def include(Map projectSpec) {

String subPath = “”;

String projectPath = projectSpec[‘path’]

String[] pathElements = projectPath.split("/");

def parentProjectDescriptor = rootProject;

for (String pathElement : pathElements) {

subPath = subPath + “/” + pathElement;

def projectDescriptor = projectDescriptorRegistry.getProject(new File(rootProject.getProjectDir(), subPath));

def projectName = subPath ==~ “/$projectPath” ? projectSpec[‘name’] : pathElement

if (projectDescriptor == null) {

parentProjectDescriptor = createProjectDescriptor(parentProjectDescriptor, projectName, new File(parentProjectDescriptor.getProjectDir(), pathElement));

} else {

parentProjectDescriptor = projectDescriptor;

}

}

}

This is something similar to what is done in Settings.include, but naturally allows for the project name and directory to differ. I’m trying to challenge the reason we need to maintain the old paradigm from Maven.