skip to content

What problem does a BuildAction solve over repeated getModel calls, and how do you use BuildActionExecuter to aggregate models across a multi-project build?

level: seniorimportance: should knowfreq 28%

answer

  1. BuildAction.execute(BuildController) runs in daemon
  2. one round trip vs getModel-per-project
  3. controller.getModel(project, type) / getBuildModel()
  4. findModel for cross-version null-safe fetch
  5. connection.action(action).run(); PhasedBuildAction

basics

~10 s

A BuildAction runs your code inside the daemon with a BuildController, so you can fetch and combine many per-project models in one round trip instead of calling getModel repeatedly. You run it with connection.action(buildAction).run().

solid answer

~40 s

Calling `getModel` separately for every subproject means many client↔daemon round trips and re-configuration cost. A **`BuildAction<T>`** moves the logic into the daemon: its `execute(BuildController controller)` runs after configuration with a **`BuildController`** that can fetch models efficiently — `controller.getModel(type)` for the build, or `controller.getModel(project, type)` per `BasicGradleProject`, and `controller.getBuildModel()` to enumerate projects. You return any serializable aggregate (e.g. a `Map` or list of per-project results) and it's sent back once. Run via `connection.action(myAction).run()` (or async with a handler). This is the cross-version-friendly way to gather data and the basis for `PhasedBuildAction` (run actions at projects-loaded and build-finished phases). It dramatically cuts round trips for large builds.

code

java · 13 lines
java
public class FetchPerProject implements BuildAction<Map<String, EclipseProject>> {
    @Override
    public Map<String, EclipseProject> execute(BuildController controller) {
        Map<String, EclipseProject> out = new LinkedHashMap<>();
        for (BasicGradleProject p : controller.getBuildModel().getProjects()) {
            out.put(p.getPath(), controller.getModel(p, EclipseProject.class));
        }
        return out;
    }
}
// client:
Map<String, EclipseProject> all =
    connection.action(new FetchPerProject()).run();

go deeper

for a junior

Recognizing BuildAction runs code in the daemon is sufficient.

for a middle

Explain execute(BuildController) and that it returns a serializable aggregate in one round trip.

for a senior

Cover getBuildModel/getModel(project,type)/findModel and the cross-version null-safe pattern.

for a principal

Discuss PhasedBuildAction, IDE import strategy, and BuildAction as the scalable contract for multi-module/composite builds.

## Why getModel-in-a-loop is bad `connection.getModel(type)` for project A, then again for B, C... each call is a separate request to the daemon and Gradle must re-resolve the model each time. For a 200-module build that's slow and chatty. ## BuildAction: run code inside the daemon A **`BuildAction<T>`** is a small serializable object whose `execute` runs **inside the daemon** after the build is configured. Gradle hands it a **`BuildController`** — a privileged in-daemon API for fetching models cheaply: ```java public class FetchAllEclipseModels implements BuildAction<List<EclipseProject>> { @Override public List<EclipseProject> execute(BuildController controller) { GradleBuild build = controller.getBuildModel(); // enumerate projects List<EclipseProject> result = new ArrayList<>(); for (BasicGradleProject p : build.getProjects()) { result.add(controller.getModel(p, EclipseProject.class)); } return result; } } ``` Key `BuildController` methods: - `getModel(Class)` — the build-scoped model. - `getModel(Model target, Class)` — a model for a specific `BasicGradleProject` / target. - `getBuildModel()` — a `GradleBuild` you can walk to get every project (and included builds via `getEditableBuilds()` / `getIncludedBuilds()`). - `findModel(...)` variants that return null instead of throwing when a model isn't available — vital for **cross-version** code, since older Gradle may not supply a given model. ## Running it ```java List<EclipseProject> models = connection.action(new FetchAllEclipseModels()).run(); ``` The whole traversal happens in one round trip; only the final serializable aggregate crosses the wire. There's also an async `run(ResultHandler)`. ## Cross-version benefit Because the action runs against whatever Gradle version is in the daemon, you can use `findModel` to degrade gracefully: request a newer model, and if the daemon's Gradle is too old it returns null and you fall back. This is *the* sanctioned pattern for IDEs that must support many Gradle versions from one codebase. ## Phased actions `connection.action()` (no-arg) returns a `BuildActionExecuter.Builder` for a **`PhasedBuildAction`**: you can attach a `projectsLoadedAction` (runs early, before tasks) and a `buildFinishedAction`, plus `forTasks(...)` to run tasks between phases. IDEs use the projects-loaded phase to populate the UI quickly, then enrich after the build. ## Serialization, again The action object itself and its return value are serialized. Keep both made of serializable value types; don't capture non-serializable client state in the action.

  • What is the practical performance win of a BuildAction over a getModel loop?
    It collapses N client↔daemon round trips into one and reuses the already-configured build in the daemon, so large multi-project imports are far faster.
  • How do you write a BuildAction that works against many Gradle versions?
    Use controller.findModel(...) which returns null when a model isn't available in that Gradle version, then fall back, rather than getModel which throws.
  • What is a PhasedBuildAction used for?
    Running action logic at distinct build phases (projects-loaded and build-finished) so an IDE can show coarse data early and enrich it after the build completes.

getModel-per-project is like phoning the warehouse once per item; a BuildAction is sending one worker into the warehouse to collect everything and bring back a single box.

saying these in an interview costs you the question

  • Looping getModel per project for large builds (chatty, slow)
  • Capturing non-serializable client objects inside the BuildAction
  • Using getModel (throws) instead of findModel for optional cross-version models

context