We have a monolithic multi-project build consisting of about 90 sub projects. They all reside under one root having the build.gradle/settings.gradle files. Now we want to start conquering the monolith by splitting it up into separate artifacts. Here is where I need some advice on how to proceed in the most correct Gradle-fashion. All the multi-project build examples I have come across in the guide have a structure like I described above, but what if you have a structure like this:
-library
build.gradle
settings.gradle
-application
build.gradle
settings.gradle
-directory
-subproject2
build.gradle
-subproject1
build.gradle
And then subproject2 at the moment has a SNAPSHOT-dependency on library (separate artifact). All projects are JARs, except application which is a WAR.
Our requirements are:
- If a team resource has only application checked out in IDEA, he/she should have an external module dependency on library (‘library dependency’ in IDEA).
- If the team resource has both application and library checked out in IDEA, he/she should have a project dependency on library (‘module dependency’ in IDEA) if it is a SNAPSHOT/changing module dependency, thus facilitating refactoring. If it is not a SNAPSHOT it should be a regular external module dependency.
- Both library and application are built by Jenkins as two separate jobs. When application is built, we want it to be an external module dependency on library to improve performance. If library is also built, there is little point in separating out the new module.
- The same goes for building application locally, it should also ideally behave the same way as building in the build system.
What I have tried is the following:
application/settings.gradle:
def libraryDirectory = new File(settingsDir, '../library')
if (libraryDirectory.exists()) {
include ':library'
project(':library').projectDir = new File(settingsDir, '../library')
}
application/build.gradle:
configurations.all {
resolutionStrategy {
cacheChangingModulesFor 0, 'seconds'
dependencySubstitution.all { DependencySubstitution dependency ->
if (dependencyIsForAChangingModule(dependency)) {
if (dependency.requested.group == "group.common") {
def targetProject = findProject(":${dependency.requested.module}")
if (targetProject != null) {
dependency.useTarget targetProject
}
}
}
}
}
}
def boolean dependencyIsForAChangingModule(DependencySubstitution dependency) {
return dependency.requested instanceof ModuleComponentSelector && dependency.requested.version.endsWith("SNAPSHOT");
}
Using this solution I achieve the first 2 bullet points in the list above, but my local build log reveals that building application will also compile library:
:library:compileJava (Thread[main,5,main]) started.
:library:compileJava
Executing task ':library:compileJava' (up-to-date check took 0.016 secs) due to:
Output file /Users/magnusskuland/IdeaProjects/test/library/build/classes/main has changed.
Output file /Users/magnusskuland/IdeaProjects/test/library/build/dependency-cache has changed.
Output file /Users/magnusskuland/IdeaProjects/test/library/build/classes/main/no/gradle has been removed.
All input files are considered out-of-date for incremental task ':library:compileJava'.
Compiling with JDK Java compiler API.
:library:compileJava (Thread[main,5,main]) completed. Took 0.578 secs.
The only solution I can think of is requiring all members of the team to define a certain property locally that will decide whether to replace external module dependencies with project dependencies. The absence of this property on Jenkins will make sure we always build with external module dependencies there. The only requirement not satisfied then is building locally, unless you switch the property back and forth.
Any suggestions/ideas on how to go about this situation?
Environment:
Gradle 2.9, Windows 7, IDEA 15
Source code for the example can be found here: