How do you run a Gradle build programmatically with the Tooling API using BuildLauncher, and how do you tell it which tasks to execute?
answer
- connection.newBuild() -> BuildLauncher
- forTasks("clean","build")
- run() synchronous, blocks + throws
- fluent one-shot launcher
- close ProjectConnection (try-with-resources)
basics
~10 sGet a ProjectConnection, call newBuild() to get a BuildLauncher, then forTasks("build") to select tasks, and run() to execute it synchronously.
solid answer
~40 sThe Tooling API runs builds through a `BuildLauncher`. From an open `ProjectConnection` you call `connection.newBuild()` to obtain the launcher, configure it (which tasks, arguments, I/O streams), then call `run()` to execute synchronously. Tasks are selected with `forTasks("clean", "build")` — these are the same task names you'd pass on the CLI. `run()` blocks until the build finishes and throws on failure (e.g. `BuildException`, `GradleConnectionException`). Always close the `ProjectConnection` when done, typically in a try-with-resources block, since it holds a connection to a (possibly reused) Gradle daemon. The launcher is a one-shot, fluent configuration object: you set it up fully before invoking `run()` (or `run(ResultHandler)` for async).
code
kotlin · 8 linesGradleConnector.newConnector()
.forProjectDirectory(projectDir)
.connect()
.use { connection ->
connection.newBuild()
.forTasks("clean", "build")
.run() // synchronous; throws BuildException on failure
}go deeper
Recall the chain: newBuild() -> forTasks() -> run(), and that run() blocks.
Explain the fluent one-shot nature, default-task behavior, and that the ProjectConnection (not the launcher) is the resource to close.
Discuss sync vs async run, exception types thrown, and why a daemon-backed connection must be closed deterministically.
Frame BuildLauncher within an IDE/CI integration: connection pooling, daemon reuse strategy, and isolating user builds from the host process.
## What the Tooling API is The **Gradle Tooling API** is a client library that lets an external process (an IDE, a CI plugin, a custom tool) drive a Gradle build *without* spawning the `gradle` CLI. It connects to a Gradle **daemon** and exchanges typed objects over that connection. IntelliJ IDEA, Android Studio, Eclipse Buildship and NetBeans all import and run Gradle builds through it. ## The execution entry point: BuildLauncher Once you have an open `ProjectConnection`, the object that *executes* a build is a `BuildLauncher`: ``` ProjectConnection connection -> connection.newBuild() -> BuildLauncher ``` `newBuild()` returns a fresh `BuildLauncher` each time. It is a **fluent, single-use configuration object**: you call setter-style methods that return `this`, then trigger the build. ## Selecting work - `forTasks(String... tasks)` — names the tasks to run, exactly as on the CLI (`forTasks("clean", "test")`). - `forTasks(Task... tasks)` — an overload that accepts `Task` model objects you previously queried from the build model, useful when you already hold task references. - If you call `forTasks()` with no arguments (or never call it), Gradle runs the **default tasks** declared in the build, mirroring a bare `gradle` invocation. ## Running it - `run()` — executes **synchronously**, blocking the calling thread until the build completes, and throws if it fails. - `run(ResultHandler)` — executes **asynchronously**, returning immediately and delivering success/failure via callback. ## Lifecycle and cleanup The `ProjectConnection` is the expensive, daemon-backed resource — *not* the launcher. Open it once, run as many builds as you like, and close it deterministically: ``` try (ProjectConnection c = connector.connect()) { c.newBuild().forTasks("build").run(); } ``` Failing to close leaks daemon connections. A single `BuildLauncher` should be configured then run once; create a new one per build.
- What happens if you never call forTasks()?Gradle runs the project's default tasks, just like invoking `gradle` with no task arguments on the CLI.
- Is the BuildLauncher reusable for multiple builds?No — treat it as one-shot. Call newBuild() again to get a fresh launcher for each build; the ProjectConnection is what you reuse.
saying these in an interview costs you the question
- Thinking the Tooling API shells out to the `gradle` CLI — it connects to a daemon over a typed API.
- Forgetting to close the ProjectConnection, leaking daemon connections.
- Believing run() returns a result object; it returns void and throws on failure.