skip to content

When building IDE integration, when would you launch work with BuildLauncher.forTasks() versus running a BuildAction, and what trade-offs guide the choice?

level: seniorimportance: nice to knowfreq 22%

answer

  1. BuildLauncher.forTasks -> execute tasks (Void)
  2. action(BuildAction) -> compute value via BuildController
  3. action executer can forTasks too (one round-trip)
  4. BuildAction result must be serializable
  5. shared: args/output/cancellation/progress

basics

~10 s

Use BuildLauncher.forTasks() to execute named tasks (the build). Use connection.action(BuildAction) when you need to run logic inside the build and return a computed value, optionally also executing tasks via forTasks() on the action executer.

solid answer

~50 s

`BuildLauncher` is for *executing tasks* — you name them with `forTasks()` and call `run()`; the result is `Void`. A `BuildAction` (run via `connection.action(...)` returning a `BuildActionExecuter`) is for *computing a value* from inside the build: your action runs in the build process with access to the `BuildController`, can query several models efficiently in one connection round-trip, and returns a custom serializable object. For pure task execution, `BuildLauncher` is simpler and the right tool. When you need to gather IDE models (project structure, dependencies) — possibly *and* run tasks in the same invocation — the `BuildActionExecuter` lets you `forTasks(...)` alongside the action, avoiding multiple daemon round-trips. The trade-off is complexity and serialization constraints (the action and its result cross the process boundary, so they must be on the right classpath and serializable). For a Stop button, both honor a shared `CancellationToken`; for live output, both accept `setStandardOutput`. Choose `BuildLauncher` for 'just run these tasks', and a `BuildAction` when you must run custom in-build logic or fetch composite information.

code

kotlin · 8 lines
kotlin
// Plain task execution
connection.newBuild().forTasks("assemble").run()

// Run in-build logic AND tasks in a single round-trip
val model: MyModel = connection.action(MyBuildAction())
    .forTasks("generateSources")
    .withCancellationToken(source.token())
    .run()

go deeper

for a junior

Recall that BuildLauncher.forTasks() runs tasks; deeper action() trade-offs are above this level.

for a middle

Explain that BuildAction computes a value in the build and can also run tasks, unlike a plain BuildLauncher.

for a senior

Weigh round-trip economy vs serialization complexity and pick the right operation per IDE use case.

for a principal

Architect an IDE sync/run layer: minimize daemon round-trips, isolate classpath/serialization risk, and unify cancellation/output across operations.

## Two ways to drive the daemon The Tooling API offers distinct operations for distinct intents: - **BuildLauncher** (`connection.newBuild()`): *execute tasks*. You select tasks with `forTasks(...)`, configure I/O/args, and `run()`. Output: a successful/failed build (`Void`). - **BuildActionExecuter** (`connection.action(BuildAction<T>)`): *run code inside the build and return T*. Your `BuildAction` executes in the build process, receives a `BuildController`, and can call `controller.getModel(...)` for one or many models, then return a custom value. (There are also `ModelBuilder` for a single model and `TestLauncher` for tests — both sibling concerns.) ## Why a BuildAction can also run tasks The `BuildActionExecuter` itself exposes `forTasks(...)` and `forLaunchables(...)`. This lets an IDE, in a *single* connection/invocation, both **execute tasks** and **compute models** — avoiding several separate daemon round-trips. That round-trip economy is the main performance reason to reach for an action instead of issuing a model fetch and a `BuildLauncher.run()` separately. ## Trade-offs | Concern | BuildLauncher.forTasks() | BuildAction via action() | |---|---|---| | Intent | Run named tasks | Compute a value / gather models (optionally + tasks) | | Result | Void | Your serializable T | | Runs your code in build process? | No | Yes (BuildController) | | Complexity / classpath | Low | Higher — action + result must serialize across the process boundary | | Round-trips for models+tasks | Multiple | One | ## Cross-process constraints Because a `BuildAction` and its return value cross from the build daemon back to your client, both must be **serializable** and resolvable on the appropriate classpaths. This is a real source of bugs (ClassNotFound / serialization errors) that `BuildLauncher` simply doesn't have. ## Shared launcher capabilities Both are `LongRunningOperation`s, so the same knobs apply: `withArguments`, `setStandardOutput`/`setStandardError`, `withCancellationToken`, `addProgressListener`, sync `run()`/`run()` vs async `run(ResultHandler)`. ## Decision rule - Just need to run `clean build`? -> **BuildLauncher.forTasks()**. - Need custom in-build logic, or to fetch IDE models (and maybe run tasks too) in one shot? -> **BuildAction via action()**. ``` // execute tasks connection.newBuild().forTasks("assemble").run(); // compute a value (and optionally run tasks) in one round-trip MyModel m = connection.action(new MyBuildAction()) .forTasks("generateSources") .run(); ```

  • Why can running tasks via a BuildAction be more efficient than a separate model fetch plus BuildLauncher?
    The BuildActionExecuter's forTasks() runs tasks and computes models in one connection round-trip to the daemon, avoiding multiple invocations and connection setup costs.
  • What new failure mode does a BuildAction introduce versus BuildLauncher?
    Serialization/classpath issues: the action and its returned value cross the client/daemon process boundary and must be serializable and resolvable on the right classpaths.
  • Do BuildLauncher and BuildActionExecuter share configuration like cancellation and output?
    Yes — both are LongRunningOperations, so withCancellationToken, setStandardOutput/Error, withArguments, progress listeners, and sync/async run all apply.

saying these in an interview costs you the question

  • Using a BuildAction merely to run tasks when BuildLauncher.forTasks() suffices.
  • Forgetting that a BuildAction's result must be serializable across processes.
  • Believing only BuildLauncher (not action()) can execute tasks.

context