Should we break expensive modules into `-domain` modules

Hi, we are trying to reduce build times on an Android project with 9 devs and fresh build times at around 3-4 min. We already tried best practices mentioned in blog posts and documentation, so we wonder if our build dependency graph is the issue.

We never properly implemented closed architecture principles. My assumption is that this prevents paralellization. On a 16 core machine, there’s rarely more than 3-4 workers at the same time.

Looking at a build scan, I noticed that the 1st blocking task was bluetoothSdkKapt. Most of the modules have a dependency on bluetooth-sdk module, thus I thought that making the following change would have a significant impact on build times

Sadly, fresh build times remain unchanged. bluetoothSdkKapt is no longer a blocking task but, for some reason, total time didn’t improve.

I assume there’s improvement when touching Bluetooth SDK module because we won’t have to rebuild Feature 1 and Feature 2 modules. But I’m looking for fresh build times for now.

I assume part of my expectations came from lack of deep understanding of Android’s gradle build chain, but I haven’t found documentation to point us on the right direction

is it worth pursuing breakdown of modules into new -domain modules? I wonder if there’s a way

  1. To show critical path, so that we can focus on offending modules
  2. To warn when a PR breaks closed architecture principles

We are a team of 9 devs, so I dream of a way to keep build times from slowly increasing over time.

For context, I’m using ./gradlew assembleDebug --rerun-tasks

Thank you very much!

edit: removing kaptfrom modules is not an option, unfortunately. We are deeply invested into Dagger & Room

Hi Miguel,
Thanks for posting this.
In general the best way to measure whether a specific change will or will not improve your build speed is to benchmark it. I recommend using the Gradle Profiler to measure a specific scenario with and without the change.
As for your specific questions:

  1. Show critical path of modules: If you are using Gradle Enterprise Build Scans, you can open the timeline view and see which tasks are lined up with other tasks. You can also view the task dependencies in the timeline view by focusing on the tasks. You can also check why tasks ran and didn’t run for the course of a build.
  2. To warn when a PR breaks your architecture principles: I would recommend trying a lint analysis tool that codifies your architecture principles. That will give you the best way to analyze your code’s architecture on a deep level.

I hope that helps.

Thank you very much for taking the time to answer to my post

I’ve been doing that with Build scans, but it’s quite cumbersome to have to search for the task, then look for predecessors/successors.

Best,
Miguel

Have you considered having a remote build cache? Under this setup your CI build writes to the build cache and developer workstations read from the remote build cache (in addition to local workstation caching for local changes). This way even a fresh build can benefit from the remote cache.

Eg: settings.gradle

boolean isCiServer = System.getenv().containsKey("CI")

buildCache {
    remote(HttpBuildCache) {
        url = 'https://example.com:8123/cache/'
        push = isCiServer
    }
}

See Configure the Build Cache, HttpBuildCache and Speed up your Build with Gradle Remote Build Cache

Thank you very much Lance, I guess that’s the next logical step given our team size