Hi Gradle community,
I’d like to share a solution I found for preventing included builds from rebuilding every time, as it was challenging to discover, and I believe others may benefit from it.
My Use Case: Building gtdsync
with Dependencies on Freeplane
I’m developing an add-on called GTD Sync for Freeplane, a mind-mapping application that has a complex plugin-based structure. Due to Freeplane’s architecture (an OSGi framework for plugins), it’s not suitable to package it as a (local) Maven repository dependency. Although Freeplane provides binary JARs for use through the Freeplane Gradle plugin, this doesn’t include the source code.
Since I need source-level access to Freeplane’s classes for code completion, source lookup, and documentation in the IDE, using Freeplane’s source directly as an included build has been the most effective approach. This allows me to access all of Freeplane’s dependencies directly within my IDE without needing to manually add them.
Project Structure
Here’s an overview of the directory structure:
git/
├── gtdsync/ # Root project directory for the GTD Sync add-on
│ ├── settings.gradle # Includes freeplane_root as an included build
│ ├── build.gradle # Adds compileOnly dependencies to Freeplane
│ └── (other GTD Sync files)
└── freeplane_root/ # Freeplane source code as an included build
├── settings.gradle
├── build.gradle
├── freeplane/ # Main Freeplane project
├── freeplane_api/ # Freeplane API
├── freeplane_framework/ # Freeplane framework subproject
└── (several subprojects for Freeplane plugins)
In gtdsync/settings.gradle
, I include freeplane_root
as an included build:
rootProject.name = 'gtdsync'
includeBuild('../freeplane_root')
And in gtdsync/build.gradle
, I add dependencies to the Freeplane source:
dependencies {
compileOnly 'freeplane_root:freeplane'
compileOnly 'freeplane_root:freeplane_api'
compileOnly 'freeplane_root:freeplane_framework'
// Other Freeplane plugins...
}
Problem: Slow Builds Due to Rebuilding the Included Build
With this setup, packageAddon
(part of the Freeplane Gradle plugin) and test
tasks can take a considerable amount of time. Every time I run these tasks, Gradle seems to trigger a rebuild of freeplane_root
, even though its source hasn’t changed. (I always work with the unchanged source code of a stable release and will rebuild manually when I have the need to switch to another stable release.) This creates a large task graph that slows down the build.
Solutions Tried
Initially, I tried various approaches to control or disable freeplane_root
task execution from gtdsync/build.gradle
:
- Setting
onlyIf
conditions for tasks infreeplane_root
to prevent them from running. - Conditional dependency configurations to isolate Freeplane dependencies.
- Custom configurations to separate
freeplane_root
dependencies from the main and test classpaths. - Gradle properties to conditionally disable tasks in
freeplane_root
based on a flag.
All of these attempts were based on the assumption that freeplane_root
tasks were part of gtdsync
’s task graph, but this wasn’t the case. I realized that the rebuild
of freeplane_root
is actually triggered by defining any dependency on freeplane_root
in gtdsync/build.gradle
. This dependency forces Gradle to check if freeplane_root
is up-to-date and, if necessary, to rebuild it.
Solution: Use --no-rebuild
(-a
)
The effective solution is to add the -a
or --no-rebuild
option on the command line:
./gradlew test -a
./gradlew packageAddon -a
According to the Gradle documentation, this flag stops buildSrc
from rebuilding, but in practice, it also stops included builds from rebuilding. This flag has been working well for me, as it keeps freeplane_root
from rebuilding, making test
and packageAddon
tasks much faster.
Feature Requests
To improve the experience with included builds, I have two requests:
-
Documentation Update: It would be very helpful to update the Gradle documentation to explicitly mention that
--no-rebuild
(-a
) also applies to included builds, not justbuildSrc
. -
Feature Request for
build.gradle
Control: A built-in method to prevent specific included builds from rebuilding would be incredibly useful. This could look something like:gradle.includedBuild('freeplane_root').noRebuild()
for a specific included build, or
gradle.includedBuilds.noRebuild()
to apply to all included builds.
These features would allow developers to better manage large projects with included builds, improving performance without needing command-line flags each time.
Thank you for considering these suggestions, and I hope this post helps others facing similar challenges!