Understanding Services and Service Injection
Gradle provides a number of useful services that can be used by custom Gradle types. For example, the WorkerExecutor service can be used by a task to run work in parallel, as seen in the worker API section. The services are made available through service injection.
Available services
The following services are available for injection:
-
ObjectFactory
- Allows model objects to be created. -
ProjectLayout
- Provides access to key project locations. -
BuildLayout
- Provides access to important locations for a Gradle build. -
ProviderFactory
- CreatesProvider
instances. -
WorkerExecutor
- Allows a task to run work in parallel. -
FileSystemOperations
- Allows a task to run operations on the filesystem such as deleting files, copying files or syncing directories. -
ArchiveOperations
- Allows a task to run operations on archive files such as ZIP or TAR files. -
ExecOperations
- Allows a task to run external processes with dedicated support for running externaljava
programs. -
ToolingModelBuilderRegistry
- Allows a plugin to registers a Gradle tooling API model.
Out of the above, ProjectLayout
and WorkerExecutor
services are only available for injection in project plugins.
BuildLayout
is only available in settings plugins and settings files.
ProjectLayout
is unavailable in Worker API actions.
1. ObjectFactory
ObjectFactory
is a service for creating custom Gradle types, allowing you to define nested objects and DSLs in your build logic.
It provides methods for creating instances of different types, such as properties (Property<T>
), collections (ListProperty<T>
, SetProperty<T>
, MapProperty<K, V>
), file-related objects (RegularFileProperty
, DirectoryProperty
, ConfigurableFileCollection
, ConfigurableFileTree
), and more.
You can obtain an instance of ObjectFactory
using the project.objects
property.
Here’s a simple example demonstrating how to use ObjectFactory
to create a property and set its value:
tasks.register("myObjectFactoryTask") {
doLast {
val objectFactory = project.objects
val myProperty = objectFactory.property(String::class)
myProperty.set("Hello, Gradle!")
println(myProperty.get())
}
}
tasks.register("myObjectFactoryTask") {
doLast {
def objectFactory = project.objects
def myProperty = objectFactory.property(String)
myProperty.set("Hello, Gradle!")
println myProperty.get()
}
}
It is preferable to let Gradle create objects automatically by using managed properties. |
Using ObjectFactory
to create these objects ensures that they are properly managed by Gradle, especially in terms of configuration avoidance and lazy evaluation.
This means that the values of these objects are only calculated when needed, which can improve build performance.
In the following example, a project extension called DownloadExtension
receives an ObjectFactory
instance through its constructor.
The constructor uses this to create a nested Resource
object (also a custom Gradle type) and makes this object available through the resource
property:
public class DownloadExtension {
// A nested instance
private final Resource resource;
@Inject
public DownloadExtension(ObjectFactory objectFactory) {
// Use an injected ObjectFactory to create a Resource object
resource = objectFactory.newInstance(Resource.class);
}
public Resource getResource() {
return resource;
}
}
public interface Resource {
Property<URI> getUri();
}
Here is another example using javax.inject.Inject
:
abstract class MyObjectFactoryTask
@Inject constructor(private var objectFactory: ObjectFactory) : DefaultTask() {
@TaskAction
fun doTaskAction() {
val outputDirectory = objectFactory.directoryProperty()
outputDirectory.convention(project.layout.projectDirectory)
println(outputDirectory.get())
}
}
tasks.register("myInjectedObjectFactoryTask", MyObjectFactoryTask::class) {}
abstract class MyObjectFactoryTask extends DefaultTask {
private ObjectFactory objectFactory
@Inject //@javax.inject.Inject
MyObjectFactoryTask(ObjectFactory objectFactory) {
this.objectFactory = objectFactory
}
@TaskAction
void doTaskAction() {
var outputDirectory = objectFactory.directoryProperty()
outputDirectory.convention(project.layout.projectDirectory)
println(outputDirectory.get())
}
}
tasks.register("myInjectedObjectFactoryTask",MyObjectFactoryTask) {}
The MyObjectFactoryTask
task uses an ObjectFactory
instance, which is injected into the task’s constructor using the @Inject
annotation.
2. ProjectLayout
ProjectLayout
is a service that provides access to the layout of a Gradle project’s directories and files.
It’s part of the org.gradle.api.file
package and allows you to query the project’s layout to get information about source sets, build directories, and other file-related aspects of the project.
You can obtain a ProjectLayout
instance from a Project
object using the project.layout
property.
Here’s a simple example:
tasks.register("showLayout") {
doLast {
val layout = project.layout
println("Project Directory: ${layout.projectDirectory}")
println("Build Directory: ${layout.buildDirectory.get()}")
}
}
tasks.register('showLayout') {
doLast {
def layout = project.layout
println "Project Directory: ${layout.projectDirectory}"
println "Build Directory: ${layout.buildDirectory.get()}"
}
}
Here is an example using javax.inject.Inject
:
abstract class MyProjectLayoutTask
@Inject constructor(private var projectLayout: ProjectLayout) : DefaultTask() {
@TaskAction
fun doTaskAction() {
val outputDirectory = projectLayout.projectDirectory
println(outputDirectory)
}
}
tasks.register("myInjectedProjectLayoutTask", MyProjectLayoutTask::class) {}
abstract class MyProjectLayoutTask extends DefaultTask {
private ProjectLayout projectLayout
@Inject //@javax.inject.Inject
MyProjectLayoutTask(ProjectLayout projectLayout) {
this.projectLayout = projectLayout
}
@TaskAction
void doTaskAction() {
var outputDirectory = projectLayout.projectDirectory
println(outputDirectory)
}
}
tasks.register("myInjectedProjectLayoutTask",MyProjectLayoutTask) {}
The MyProjectLayoutTask
task uses a ProjectLayout
instance, which is injected into the task’s constructor using the @Inject
annotation.
3. BuildLayout
BuildLayout
is a service that provides access to the root and settings directory in a Settings plugin or a Settings script, it is analogous to ProjectLayout
.
It’s part of the org.gradle.api.file
package to access standard build-wide file system locations as lazily computed value.
These APIs are currently incubating but eventually should replace existing accessors in Settings, which return eagerly computed locations:Settings.rootDir → Settings.layout.rootDirectory Settings.settingsDir → Settings.layout.settingsDirectory
|
You can obtain a BuildLayout
instance from a Settings
object using the settings.layout
property.
Here’s a simple example:
println("Root Directory: ${settings.layout.rootDirectory}")
println("Settings Directory: ${settings.layout.settingsDirectory}")
println "Root Directory: ${settings.getLayout().rootDirectory}"
println "Settings Directory: ${settings.getLayout().settingsDirectory}"
Here is an example using javax.inject.Inject
:
abstract class MyBuildLayoutPlugin @Inject constructor(private val buildLayout: BuildLayout) : Plugin<Settings> {
override fun apply(settings: Settings) {
println(buildLayout.rootDirectory)
}
}
apply<MyBuildLayoutPlugin>()
abstract class MyBuildLayoutPlugin implements Plugin<Settings> {
private BuildLayout buildLayout
@Inject //@javax.inject.Inject
MyBuildLayoutPlugin(BuildLayout buildLayout) {
this.buildLayout = buildLayout
}
@Override void apply(Settings settings) {
// the meat and potatoes of the plugin
println buildLayout.rootDirectory
}
}
apply plugin: MyBuildLayoutPlugin
This code defines a MyBuildLayoutPlugin
plugin that implements the Plugin
interface for the Settings
type.
The plugin expects a BuildLayout
instance to be injected into its constructor using the @Inject
annotation.
4. ProviderFactory
ProviderFactory
is a service that provides methods for creating different types of providers.
Providers are used to model values that may be computed lazily in your build scripts.
The ProviderFactory
interface provides methods for creating various types of providers, including:
-
provider(Callable<T> value)
to create a provider with a value that is lazily computed based on aCallable
. -
provider(Provider<T> value)
to create a provider that simply wraps an existing provider. -
property(Class<T> type)
to create a property provider for a specific type. -
gradleProperty(Class<T> type)
to create a property provider that reads its value from a Gradle project property.
Here’s a simple example demonstrating the use of ProviderFactory
using project.providers
:
tasks.register("printMessage") {
doLast {
val providerFactory = project.providers
val messageProvider = providerFactory.provider { "Hello, Gradle!" }
println(messageProvider.get())
}
}
tasks.register('printMessage') {
doLast {
def providerFactory = project.providers
def messageProvider = providerFactory.provider { "Hello, Gradle!" }
println messageProvider.get()
}
}
The task named printMessage
uses the ProviderFactory
to create a provider
that supplies the message string.
Here is an example using javax.inject.Inject
:
abstract class MyProviderFactoryTask
@Inject constructor(private var providerFactory: ProviderFactory) : DefaultTask() {
@TaskAction
fun doTaskAction() {
val outputDirectory = providerFactory.provider { "build/my-file.txt" }
println(outputDirectory.get())
}
}
tasks.register("myInjectedProviderFactoryTask", MyProviderFactoryTask::class) {}
abstract class MyProviderFactoryTask extends DefaultTask {
private ProviderFactory providerFactory
@Inject //@javax.inject.Inject
MyProviderFactoryTask(ProviderFactory providerFactory) {
this.providerFactory = providerFactory
}
@TaskAction
void doTaskAction() {
var outputDirectory = providerFactory.provider { "build/my-file.txt" }
println(outputDirectory.get())
}
}
tasks.register("myInjectedProviderFactoryTask",MyProviderFactoryTask) {}
The ProviderFactory
service is injected into the MyProviderFactoryTask
task’s constructor using the @Inject
annotation.
5. WorkerExecutor
WorkerExecutor
is a service that allows you to perform parallel execution of tasks using worker processes.
This is particularly useful for tasks that perform CPU-intensive or long-running operations, as it allows them to be executed in parallel, improving build performance.
Using WorkerExecutor
, you can submit units of work (called actions) to be executed in separate worker processes.
This helps isolate the work from the main Gradle process, providing better reliability and performance.
Here’s a basic example of how you might use WorkerExecutor
in a build script:
abstract class MyWorkAction : WorkAction<WorkParameters.None> {
private val greeting: String = "Hello from a Worker!"
override fun execute() {
println(greeting)
}
}
abstract class MyWorkerTask
@Inject constructor(private var workerExecutor: WorkerExecutor) : DefaultTask() {
@get:Input
abstract val booleanFlag: Property<Boolean>
@TaskAction
fun doThings() {
workerExecutor.noIsolation().submit(MyWorkAction::class.java) {}
}
}
tasks.register("myWorkTask", MyWorkerTask::class) {}
abstract class MyWorkAction implements WorkAction<WorkParameters.None> {
private final String greeting;
@Inject
public MyWorkAction() {
this.greeting = "Hello from a Worker!";
}
@Override
public void execute() {
System.out.println(greeting);
}
}
abstract class MyWorkerTask extends DefaultTask {
@Input
abstract Property<Boolean> getBooleanFlag()
@Inject
abstract WorkerExecutor getWorkerExecutor()
@TaskAction
void doThings() {
workerExecutor.noIsolation().submit(MyWorkAction) {}
}
}
tasks.register("myWorkTask", MyWorkerTask) {}
See the worker API for more details.
6. FileSystemOperations
FileSystemOperations
is a service that provides methods for performing file system operations such as copying, deleting, and syncing.
It is part of the org.gradle.api.file
package and is typically used in custom tasks or plugins to interact with the file system.
Here is an example using javax.inject.Inject
:
abstract class MyFileSystemOperationsTask
@Inject constructor(private var fileSystemOperations: FileSystemOperations) : DefaultTask() {
@TaskAction
fun doTaskAction() {
fileSystemOperations.sync {
from("src")
into("dest")
}
}
}
tasks.register("myInjectedFileSystemOperationsTask", MyFileSystemOperationsTask::class)
abstract class MyFileSystemOperationsTask extends DefaultTask {
private FileSystemOperations fileSystemOperations
@Inject //@javax.inject.Inject
MyFileSystemOperationsTask(FileSystemOperations fileSystemOperations) {
this.fileSystemOperations = fileSystemOperations
}
@TaskAction
void doTaskAction() {
fileSystemOperations.sync {
from 'src'
into 'dest'
}
}
}
tasks.register("myInjectedFileSystemOperationsTask", MyFileSystemOperationsTask)
The FileSystemOperations
service is injected into the MyFileSystemOperationsTask
task’s constructor using the @Inject
annotation.
With some ceremony, it is possible to use FileSystemOperations
in an ad-hoc task defined in a build script:
interface InjectedFsOps {
@get:Inject val fs: FileSystemOperations
}
tasks.register("myAdHocFileSystemOperationsTask") {
val injected = project.objects.newInstance<InjectedFsOps>()
doLast {
injected.fs.copy {
from("src")
into("dest")
}
}
}
interface InjectedFsOps {
@Inject //@javax.inject.Inject
FileSystemOperations getFs()
}
tasks.register('myAdHocFileSystemOperationsTask') {
def injected = project.objects.newInstance(InjectedFsOps)
doLast {
injected.fs.copy {
from 'source'
into 'destination'
}
}
}
First, you need to declare an interface with a property of type FileSystemOperations
, here named InjectedFsOps
, to serve as an injection point.
Then call the method ObjectFactory.newInstance
to generate an implementation of the interface that holds an injected service.
This is a good time to consider extracting the ad-hoc task into a proper class. |
7. ArchiveOperations
ArchiveOperations
is a service that provides methods for accessing the contents of archives, such as ZIP and TAR files.
It is part of the org.gradle.api.file
package and is typically used in custom tasks or plugins to unpack archive files.
Here is an example using javax.inject.Inject
:
abstract class MyArchiveOperationsTask
@Inject constructor(
private val archiveOperations: ArchiveOperations,
private val layout: ProjectLayout,
private val fs: FileSystemOperations
) : DefaultTask() {
@TaskAction
fun doTaskAction() {
fs.sync {
from(archiveOperations.zipTree(layout.projectDirectory.file("sources.jar")))
into(layout.buildDirectory.dir("unpacked-sources"))
}
}
}
tasks.register("myInjectedArchiveOperationsTask", MyArchiveOperationsTask::class)
abstract class MyArchiveOperationsTask extends DefaultTask {
private ArchiveOperations archiveOperations
private ProjectLayout layout
private FileSystemOperations fs
@Inject
MyArchiveOperationsTask(ArchiveOperations archiveOperations, ProjectLayout layout, FileSystemOperations fs) {
this.archiveOperations = archiveOperations
this.layout = layout
this.fs = fs
}
@TaskAction
void doTaskAction() {
fs.sync {
from(archiveOperations.zipTree(layout.projectDirectory.file("sources.jar")))
into(layout.buildDirectory.dir("unpacked-sources"))
}
}
}
tasks.register("myInjectedArchiveOperationsTask", MyArchiveOperationsTask)
The ArchiveOperations
service is injected into the MyArchiveOperationsTask
task’s constructor using the @Inject
annotation.
With some ceremony, it is possible to use ArchiveOperations
in an ad-hoc task defined in a build script:
interface InjectedArcOps {
@get:Inject val arcOps: ArchiveOperations
}
tasks.register("myAdHocArchiveOperationsTask") {
val injected = project.objects.newInstance<InjectedArcOps>()
val archiveFile = "${project.projectDir}/sources.jar"
doLast {
injected.arcOps.zipTree(archiveFile)
}
}
interface InjectedArcOps {
@Inject //@javax.inject.Inject
ArchiveOperations getArcOps()
}
tasks.register('myAdHocArchiveOperationsTask') {
def injected = project.objects.newInstance(InjectedArcOps)
def archiveFile = "${projectDir}/sources.jar"
doLast {
injected.arcOps.zipTree(archiveFile)
}
}
First, you need to declare an interface with a property of type ArchiveOperations
, here named InjectedArcOps
, to serve as an injection point.
Then call the method ObjectFactory.newInstance
to generate an implementation of the interface that holds an injected service.
This is a good time to consider extracting the ad-hoc task into a proper class. |
8. ExecOperations
ExecOperations
is a service that provides methods for executing external processes (commands) from within a build script.
It is part of the org.gradle.process
package and is typically used in custom tasks or plugins to run command-line tools or scripts as part of the build process.
Here is an example using javax.inject.Inject
:
abstract class MyExecOperationsTask
@Inject constructor(private var execOperations: ExecOperations) : DefaultTask() {
@TaskAction
fun doTaskAction() {
execOperations.exec {
commandLine("ls", "-la")
}
}
}
tasks.register("myInjectedExecOperationsTask", MyExecOperationsTask::class)
abstract class MyExecOperationsTask extends DefaultTask {
private ExecOperations execOperations
@Inject //@javax.inject.Inject
MyExecOperationsTask(ExecOperations execOperations) {
this.execOperations = execOperations
}
@TaskAction
void doTaskAction() {
execOperations.exec {
commandLine 'ls', '-la'
}
}
}
tasks.register("myInjectedExecOperationsTask", MyExecOperationsTask)
The ExecOperations
is injected into the MyExecOperationsTask
task’s constructor using the @Inject
annotation.
With some ceremony, it is possible to use ExecOperations
in an ad-hoc task defined in a build script:
interface InjectedExecOps {
@get:Inject val execOps: ExecOperations
}
tasks.register("myAdHocExecOperationsTask") {
val injected = project.objects.newInstance<InjectedExecOps>()
doLast {
injected.execOps.exec {
commandLine("ls", "-la")
}
}
}
interface InjectedExecOps {
@Inject //@javax.inject.Inject
ExecOperations getExecOps()
}
tasks.register('myAdHocExecOperationsTask') {
def injected = project.objects.newInstance(InjectedExecOps)
doLast {
injected.execOps.exec {
commandLine 'ls', '-la'
}
}
}
First, you need to declare an interface with a property of type ExecOperations
, here named InjectedExecOps
, to serve as an injection point.
Then call the method ObjectFactory.newInstance
to generate an implementation of the interface that holds an injected service.
This is a good time to consider extracting the ad-hoc task into a proper class. |
9. ToolingModelBuilderRegistry
ToolingModelBuilderRegistry
is a service that allows you to register custom tooling model builders.
Tooling models are used to provide rich IDE integration for Gradle projects, allowing IDEs to understand and work with the project’s structure, dependencies, and other aspects.
The ToolingModelBuilderRegistry
interface is part of the org.gradle.tooling.provider.model
package and is typically used in custom Gradle plugins that provide enhanced IDE support.
Here’s a simplified example:
// Implements the ToolingModelBuilder interface.
// This interface is used in Gradle to define custom tooling models that can
// be accessed by IDEs or other tools through the Gradle tooling API.
class OrtModelBuilder : ToolingModelBuilder {
private val repositories: MutableMap<String, String> = mutableMapOf()
private val platformCategories: Set<String> = setOf("platform", "enforced-platform")
private val visitedDependencies: MutableSet<ModuleComponentIdentifier> = mutableSetOf()
private val visitedProjects: MutableSet<ModuleVersionIdentifier> = mutableSetOf()
private val logger = Logging.getLogger(OrtModelBuilder::class.java)
private val errors: MutableList<String> = mutableListOf()
private val warnings: MutableList<String> = mutableListOf()
override fun canBuild(modelName: String): Boolean {
return false
}
override fun buildAll(modelName: String, project: Project): Any? {
return null
}
}
// Plugin is responsible for registering a custom tooling model builder
// (OrtModelBuilder) with the ToolingModelBuilderRegistry, which allows
// IDEs and other tools to access the custom tooling model.
class OrtModelPlugin(private val registry: ToolingModelBuilderRegistry) : Plugin<Project> {
override fun apply(project: Project) {
registry.register(OrtModelBuilder())
}
}
// Implements the ToolingModelBuilder interface.
// This interface is used in Gradle to define custom tooling models that can
// be accessed by IDEs or other tools through the Gradle tooling API.
class OrtModelBuilder implements ToolingModelBuilder {
private Map<String, String> repositories = [:]
private Set<String> platformCategories = ["platform", "enforced-platform"]
private Set<ModuleComponentIdentifier> visitedDependencies = []
private Set<ModuleVersionIdentifier> visitedProjects = []
private static final logger = Logging.getLogger(OrtModelBuilder.class)
private List<String> errors = []
private List<String> warnings = []
@Override
boolean canBuild(String modelName) {
return false
}
@Override
Object buildAll(String modelName, Project project) {
return null
}
}
// Plugin is responsible for registering a custom tooling model builder
// (OrtModelBuilder) with the ToolingModelBuilderRegistry, which allows
// IDEs and other tools to access the custom tooling model.
class OrtModelPlugin implements Plugin<Project> {
ToolingModelBuilderRegistry registry
OrtModelPlugin(ToolingModelBuilderRegistry registry) {
this.registry = registry
}
void apply(Project project) {
registry.register(new OrtModelBuilder())
}
}
Constructor injection
There are 2 ways that an object can receive the services that it needs. The first option is to add the service as a parameter of the class constructor.
The constructor must be annotated with the javax.inject.Inject
annotation.
Gradle uses the declared type of each constructor parameter to determine the services that the object requires.
The order of the constructor parameters and their names are not significant and can be whatever you like.
Here is an example that shows a task type that receives an ObjectFactory
via its constructor:
public class Download extends DefaultTask {
private final DirectoryProperty outputDirectory;
// Inject an ObjectFactory into the constructor
@Inject
public Download(ObjectFactory objectFactory) {
// Use the factory
outputDirectory = objectFactory.directoryProperty();
}
@OutputDirectory
public DirectoryProperty getOutputDirectory() {
return outputDirectory;
}
@TaskAction
void run() {
// ...
}
}
Property injection
Alternatively, a service can be injected by adding a property getter method annotated with the javax.inject.Inject
annotation to the class.
This can be useful, for example, when you cannot change the constructor of the class due to backwards compatibility constraints.
This pattern also allows Gradle to defer creation of the service until the getter method is called, rather than when the instance is created. This can help with performance.
Gradle uses the declared return type of the getter method to determine the service to make available. The name of the property is not significant and can be whatever you like.
The property getter method must be public
or protected
. The method can be abstract
or, in cases where this isn’t possible, can have a dummy method body.
The method body is discarded.
Here is an example that shows a task type that receives a two services via property getter methods:
public abstract class Download extends DefaultTask {
// Use an abstract getter method
@Inject
protected abstract ObjectFactory getObjectFactory();
// Alternatively, use a getter method with a dummy implementation
@Inject
protected WorkerExecutor getWorkerExecutor() {
// Method body is ignored
throw new UnsupportedOperationException();
}
@TaskAction
void run() {
WorkerExecutor workerExecutor = getWorkerExecutor();
ObjectFactory objectFactory = getObjectFactory();
// Use the executor and factory ...
}
}