Shared Build Services
Sometimes, is it useful for several tasks to share some state or resource. For example, tasks might share a cache of pre-computed values in order to do their work faster. Or tasks might do their work using a web service or database instance.
Gradle allows you to declare build services to represent this state. A build service is simply an object that holds the state for tasks to use. Gradle takes care of the service lifecycle, and will create the service instance only when it is required and clean it up once it is no longer required. Gradle can also optionally take care of coordinating access to the build service, so that no more than a specified number of tasks can use the service concurrently.
Implementing a build service
To implement a build service, create an abstract class that extends BuildService and define whichever methods on this type that you’d like tasks to use. A build service implementation is treated as a custom Gradle type and can use any of the features available to custom Gradle types.
A build service can also optionally take parameters, which Gradle injects into the service instance when creating it. To provide parameters, you define an abstract class or an interface that
holds the parameters. The parameters type must extend BuildServiceParameters.
The service implementation can access the parameters using this.getParameters()
.
The parameters type is also a custom Gradle type.
When the build service does not require any parameters, you can use BuildServiceParameters.None as the parameters type.
A build service implementation can also optionally implement AutoCloseable
, in which case Gradle will call the build service instance’s close()
method when it discards the service instance.
This happens some time between completion of the last task that uses the build service and the end of the build.
Here is an example of a service that takes parameters and is closeable:
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import java.net.URI;
import java.net.URISyntaxException;
public abstract class WebServer implements BuildService<WebServer.Params>, AutoCloseable {
// Some parameters for the web server
interface Params extends BuildServiceParameters {
Property<Integer> getPort();
DirectoryProperty getResources();
}
private final URI uri;
public WebServer() throws URISyntaxException {
// Use the parameters
int port = getParameters().getPort().get();
uri = new URI(String.format("https://localhost:%d/", port));
// Start the server ...
System.out.println(String.format("Server is running at %s", uri));
}
// A public method for tasks to use
public URI getUri() {
return uri;
}
@Override
public void close() {
// Stop the server ...
}
}
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.api.provider.Property
import org.gradle.api.file.DirectoryProperty
import java.net.URI
abstract class WebServer : BuildService<WebServer.Params>, AutoCloseable {
// Some parameters for the web server
interface Params : BuildServiceParameters {
val port: Property<Int>
val resources: DirectoryProperty
}
// A public property for tasks to use
val uri: URI
init {
// Use the parameters
val port = parameters.port.get()
uri = URI("https://localhost:$port/")
// Start the server ...
println("Server is running at $uri")
}
override fun close() {
// Stop the server ...
}
}
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.api.provider.Property
import org.gradle.api.file.DirectoryProperty
abstract class WebServer implements BuildService<Params>, AutoCloseable {
// Some parameters for the web server
interface Params extends BuildServiceParameters {
Property<Integer> getPort()
DirectoryProperty getResources()
}
// A public property for tasks to use
final URI uri
WebServer() {
// Use the parameters
def port = parameters.port.get()
uri = new URI("https://localhost:$port/")
// Start the server ...
System.out.println("Server is running at $uri")
}
@Override
void close() {
// Stop the server ...
}
}
Note that you should not implement the BuildService.getParameters() method, as Gradle will provide an implementation of this.
A build service implementation must be thread-safe, as it will potentially be used by multiple tasks concurrently.
Using a build service from a task
To use a build service from a task, add a property to the task of type Property<MyServiceType>
and mark the property as @Internal
.
Using a service with any other annotation is currently not supported. For example, it is currently not possible
to mark a service as an input to a task.
Here is an example of a task that uses the previous service:
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import java.net.URI;
public abstract class Download extends DefaultTask {
// This property provides access to the service instance
@Internal
abstract Property<WebServer> getServer();
@OutputFile
abstract RegularFileProperty getOutputFile();
@TaskAction
public void download() {
// Use the server to download a file
WebServer server = getServer().get();
URI uri = server.getUri().resolve("somefile.zip");
System.out.println(String.format("Downloading %s", uri));
}
}
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class Download : DefaultTask() {
// This property provides access to the service instance
@get:Internal
abstract val server: Property<WebServer>
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun download() {
// Use the server to download a file
val server = server.get()
val uri = server.uri.resolve("somefile.zip")
println("Downloading $uri")
}
}
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class Download extends DefaultTask {
// This property provides access to the service instance
@Internal
abstract Property<WebServer> getServer()
@OutputFile
abstract RegularFileProperty getOutputFile()
@TaskAction
void download() {
// Use the server to download a file
def server = server.get()
def uri = server.uri.resolve("somefile.zip")
println "Downloading $uri"
}
}
Registering a build service
To create a build service, you register the service instance using the BuildServiceRegistry.registerIfAbsent() method. Registering the service does not create the service instance. This happens on demand when a task first uses the service. If no task uses the service during a build, the service instance will not be created.
Currently, build services are scoped to a build, rather than to a project, and these services are available to be shared by the tasks of all projects.
You can access the registry of shared build services via Project.getGradle().getSharedServices()
.
Here is an example of a plugin that registers the previous service:
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;
public class DownloadPlugin implements Plugin<Project> {
public void apply(Project project) {
// Register the service
Provider<WebServer> serviceProvider = project.getGradle().getSharedServices().registerIfAbsent("web", WebServer.class, spec -> {
// Provide some parameters
spec.getParameters().getPort().set(5005);
});
project.getTasks().register("download", Download.class, task -> {
// Connect the service provider to the task
task.getServer().set(serviceProvider);
task.getOutputFile().set(project.getLayout().getBuildDirectory().file("result.zip"));
});
}
}
import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.provider.Provider
class DownloadPlugin : Plugin<Project> {
override fun apply(project: Project) {
// Register the service
val serviceProvider = project.gradle.sharedServices.registerIfAbsent("web", WebServer::class.java) {
// Provide some parameters
it.parameters.port.set(5005)
}
project.tasks.register("download", Download::class.java) {
// Connect the service provider to the task
it.server.set(serviceProvider)
it.outputFile.set(project.layout.buildDirectory.file("result.zip"))
}
}
}
import org.gradle.api.Plugin
import org.gradle.api.Project
class DownloadPlugin implements Plugin<Project> {
void apply(Project project) {
// Register the service
def serviceProvider = project.gradle.sharedServices.registerIfAbsent("web", WebServer) {
// Provide some parameters
parameters.port = 5005
}
project.tasks.register("download", Download) {
// Connect the service provider to the task
server = serviceProvider
outputFile = project.layout.buildDirectory.file('result.zip')
}
}
}
The plugin registers the service and receives a Provider<WebService>
back. This provider can be connected to task properties to pass the service to the task.
Generally, build services are intended to be used by tasks, as they usually represent some state that is potentially expensive to create, and you should avoid using
them at configuration time. However, sometimes it can make sense to use the service at configuration time. This is possible, simply call get()
on the provider.
Concurrent access to the service
You can constrain concurrent execution when you register the service, by using the Property
object returned from BuildServiceSpec.getMaxParallelUsages().
When this property has no value, which is the default, Gradle does not constrain access to the service. When this property has a value > 0, Gradle will allow no more than the specified number of tasks to use the service concurrently.
Receiving information about task execution
A build service can be used to receive events as tasks are executed. To do this, create and register a build service that implements OperationCompletionListener. Then, you can use the methods on the BuildEventsListenerRegistry service to start receiving events.