PUBLIC OBJECT

Gradle’s includeBuild() is Awesome

I’ve worked on teams that use many repositories and teams that put everything into a giant monorepo. But no matter how much code we jam into git, there’s always something else on the outside.

Gradle’s includeBuild() feature smashes multiple projects together into one. Recently I used it to pull an open source project into my team’s internal repo. Here’s how...

Syntax

I’m going to add Wire to an internal project called newswriter. I put an includeBuild() block to the bottom of newswriter’s settings.gradle.kts. (This also works with settings.gradle Groovy syntax.)

// This is the regular stuff in settings.gradle.
include("service")
include("testing")
...

// This adds Wire to newswriter!
includeBuild("/Users/jwilson/Projects/wire/wire-library") {
  dependencySubstitution {
    substitute(module("com.squareup.wire:wire-gradle-plugin"))
      .with(project(":wire-gradle-plugin"))
    substitute(module("com.squareup.wire:wire-runtime"))
      .with(project(":wire-runtime"))
  }
}

The string argument to includeBuild() is the path to the Wire directory that contains its settings.gradle.kts file.

The rest is a list of dependency substitutions. The module argument is a Maven coordinate without its version. The project argument is the Wire subproject that produces that artifact.

Unified IntelliJ Project

After pressing Gradle’s ‘Sync Elephant’ button IntelliJ adds Wire to the projects sidebar.

Both Projects in IntelliJ

Navigating between the projects works perfectly. I can even refactor across projects and it works like a single project.

If I change some behavior in Wire, when I run newswriter’s tests it uses the changed code. This is super handy for debugging! It’s also nice to develop open source features in the application where they’re used.

Plugins Too

Investigating Gradle plugin problems isn’t fun. To make it easier, I use includeBuild() to include the plugin and then debug the build! With this I can step through the plugin code to figure out what’s going on.

Debugging a Plugin

If there’s a plugin bug? I can fix it, re-run the build, and confirm that my fix works!

Avoiding Git Mistakes

It’s annoying to have local edits to settings.gradle.kts that shouldn’t get checked in. To avoid that problem I add this to the bottom of my settings.gradle.kts files.

val localSettings = file("local.settings.gradle.kts")
if (localSettings.exists()) {
  apply(from = localSettings)
}

That goes with this line in my .gitignore:

local.settings.gradle.kts

With these changes I put my includeBuild() thing in a file that Git won’t track.

Custom Working Sets

In this example I combined an internal project with an open source one. It also works to combine multiple open source projects, multiple internal projects, or combinations thereof.

The gotcha? All the projects gotta use Gradle. That’s fine with me.