1

I have been using Gradle for some time now but have never delved deeper into it's many features and functionalities, but the other day I decided to split my Java project into two modules for easier testing and maintenance. After I was done I started reading about Authoring Multi-projects and one of the newer Gradle concepts called Composite builds. Then I proceeded to spend the better part of the day trying to apply (what I thought I understood) this knowledge on making my project modular. It's safe to assume that I was not successful and now here I am asking for help in understanding these concepts and their application.

Theoretical Workspace

Let us imagine that we have the following directory structure:

toolbox
|
├── first-tool
|   └── build.gradle
|
├── second-tool
|   └── build.gradle
|
├── build.gradle
└── settings.gradle

And these are the projects inside the root directory:

Root project 'toolbox'
+--- Project ':first-tool'
\--- Project ':second-tool'

Let's make project first-tool introduce an external dependency org.master-toolbox:some.other.tool on which the second toolbox is dependent on but does not implement itself:

default - Configuration for default artifacts.
+--- org.master-toolbox:some.other.tool:1.0
|    +--- ...
\--- io.toolbox:first-tool

These are the relevant Gradle files:

settings.gradle

rootProject.name = 'toolbox'

include 'first-tool', 'second-tool'

toolbox.build.gradle

allprojects {

    apply plugin: 'java-library'

    group = "io.toolbox"

    repositories {
        jcenter()
    }
} 

first-tool.build.gradle

dependencies {
    // This is an external dependency that is resolved through jcentral
    implements 'org.master-toolbox:some.other.tool:1.0'
}

second-tool.build.gradle

dependencies {
    // Try to implement first-tool build to inherit dependencies
    implementation 'io.toolbox:first-tool'
}

The above produces a nice project structure as shown above but would not fully work because second-tool would not find first-tool and something similar to the following error would appear in our console:

> Could not resolve all files for configuration ':second-tool:compileClasspath'.
   > Could not find io.toolbox:first-tool:.
     Required by:
         project :second-tool

Now as far as I understand it this is where composite builds are suppose to swoop-in and save the day by allowing us to include entire builds. Well that sounds great, let us just update out settings.gradle:

rootProject.name = 'toolbox'

includeBuild `first-tool`
include 'second-tool'

Now the project builds and compiles properly, everything is good right? Well not exactly, as by using the includeBuild keyword in our settings.gradle first-tool is no longer inheriting properties from toolbox and has essentially stopped being a sub-project of toolbox.

Project Goal

I am most probably misunderstanding the whole concept of composite builds or just making silly mistakes. Either way what I would like to know is if there is any way to have a hierarchical multi-project structure with sub-projects that are interdependent upon each-other?

As it stands I have to choose between the following:

  • a regular multi-project build where everything is neat and organized and all my sub-projects share the same definitions from the top project.
  • a composite build where I am free to create inter-project dependencies.

It would be great if it was possible to have a main build.gradle that resides at the top project's root directory and defines the common plugins, repositories, group etc. for the whole hierarchy. Other projects would then have their own build.gradle files that would give them unique properties but would execute their tasks from the top project.

I have already read the official documentation on this subject so I am looking for more concrete explanations and directions on how to accomplish this specific goal.

Matthew
  • 1,779
  • 3
  • 15
  • 23
  • 1
    This may help: https://stackoverflow.com/questions/44413952/gradle-implementation-vs-api-configuration – Henry Jul 04 '19 at 03:58
  • @Henry Thank you, I've already read that and am familiar with the difference between `api` and `implementation` keyword`. – Matthew Jul 04 '19 at 05:27

1 Answers1

1

With multi-module builds, a module can depend on another module. To let the second project depend on the first, edit second-tool/build.gradle as:

dependencies {
    implementation project(':first-tool')
}

Also, the first module should probably be:

dependencies {
    api 'org.master-toolbox:some.other.tool:1.0'
}

You can use a multi-module build to do what you describe; you can have inter-module dependencies, and modules can be configured from the top-level build by using either allprojects { ... } or subprojects{ ... }.

Composite builds are rarely used- if you are happy with a "monolitic" build, and don't need to split your code into different repos. etc., then go with a (plain) multi-module build.

Daniele
  • 2,442
  • 1
  • 11
  • 17
  • I've managed to solve this problem yesterday and the way I've done it is by making `second-tool` implement `first-tool` from `toolbox` (root project), but your way is actually better. Implementing it directly from the builds that actually need it makes more sense. – Matthew Jul 06 '19 at 14:03