Gradle is a powerful build automation tool used in many software development projects. One of the lesser-known but incredibly useful features of Gradle is its support for init scripts. Init scripts provide a way to configure Gradle before any build scripts are executed. In this blog post, we will delve into the world of init scripts in Gradle, discussing what they are, why you might need them, and how to use them effectively.
What are Init Scripts?
Init scripts in Gradle are scripts written in Groovy or Kotlin that are executed before any build script in a Gradle project. They allow you to customize Gradle’s behavior on a project-wide or even system-wide basis. These scripts can be used to define custom tasks, apply plugins, configure repositories, and perform various other initialization tasks.
Init scripts are particularly useful when you need to enforce consistent build configurations across multiple projects or when you want to set up global settings that should apply to all Gradle builds on a machine.
Why Use Init Scripts?
Init scripts offer several advantages that make them an essential part of Gradle’s flexibility:
Centralized Configuration
With init scripts, you can centralize your configuration settings and plugins, reducing redundancy across your project’s build scripts. This ensures that all your builds follow the same guidelines, making maintenance easier.
Code Reusability
Init scripts allow you to reuse code snippets across multiple projects. This can include custom tasks, custom plugin configurations, or even logic to set up environment variables.
Isolation of Configuration
Init scripts run independently of your project’s build scripts. This isolation ensures that the build scripts focus solely on the tasks related to building your project, while the init scripts handle setup and configuration.
System-wide Configuration
You can use init scripts to configure Gradle globally, affecting all projects on a machine. This is especially useful when you want to enforce certain conventions or settings across your organization.
Creating an Init Script
Now, let’s dive into creating and using init scripts in Gradle:
Location
Init scripts can be placed in one of two locations:
- Project-specific location: You can place an init script in the
init.d
directory located at the root of your project. This script will apply only to the specific project in which it resides. - Global location: You can also create a global init script that applies to all Gradle builds on your machine. These scripts are typically placed in the
USER_HOME/.gradle/init.d
directory.
Script Language
Init scripts can be written in either Groovy or Kotlin. Gradle supports both languages, so choose the one you are more comfortable with.
Basic Structure
Here’s a basic structure for an init script in Groovy:
// Groovy init.gradle
allprojects {
// Your configuration here
}
And in Kotlin:
// Kotlin init.gradle.kts
allprojects {
// Your configuration here
}
Configuration
In your init script, you can configure various aspects of Gradle, such as:
- Applying plugins
- Defining custom tasks
- Modifying repository settings
- Setting up environment variables
- Specifying project-level properties
Applying the Init Script
To apply an init script to your project, you have a few options:
- Project-specific init script: Place the init script in the
init.d
directory of your project, and it will automatically apply to that project when you run Gradle tasks. - Global init script: If you want the init script to apply to all projects on your machine, place it in the
USER_HOME/.gradle/init.d
directory. - Command-line application: You can apply an init script to a single invocation of Gradle using the
-I
or--init-script
command-line option, followed by the path to your script:
gradle -I /path/to/init.gradle <task>
Use Cases : Configuring Projects with an Init Script
As we know now, an init script is a Groovy or Kotlin script, just like a Gradle build script. Each init script is linked to a Gradle instance, meaning any properties or methods you use in the script relate to that specific Gradle instance.
Init scripts implement the Script interface, which is how they interact with Gradle’s internals and perform various tasks.
When writing or creating init scripts, it’s crucial to be mindful of the scope of the references you’re using. For instance, properties defined in a gradle.properties
file are available for use in Settings or Project instances but not directly in the top-level Gradle instance.
You can use an init script to set up and adjust the projects in your Gradle build. It’s similar to how you configure projects in a multi-project setup. Let’s take a look at an example where we use an init script to add an additional repository for specific environments.
Example 1. Using init script to perform extra configuration before projects are evaluated
//build.gradle.kts
repositories {
mavenCentral()
}
tasks.register("showRepos") {
val repositoryNames = repositories.map { it.name }
doLast {
println("All repos:")
println(repositoryNames)
}
}
// init.gradle.kts
allprojects {
repositories {
mavenLocal()
}
}
Output when applying the init script:
> gradle --init-script init.gradle.kts -q showRepos
All repos:
[MavenLocal, MavenRepo]
External dependencies for the init script
In your Gradle init script, you can declare external dependencies just like you do in a regular Gradle build script. This allows you to bring in additional libraries or resources needed for your init script to work correctly.
Example 2. Declaring external dependencies for an init script
// init.gradle.kts
initscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.apache.commons:commons-math:2.0")
}
}
The initscript()
method takes closure as an argument. This closure is used to configure the ScriptHandler instance for the init script. The ScriptHandler instance is responsible for loading and executing the init script.
You declare the init script’s classpath by adding dependencies to the classpath configuration. This is similar to declaring dependencies for tasks like Java compilation. The classpath
property of the closure can be used to specify the classpath for the init script. The classpath can be a list of directories or JAR files. You can use any of the dependency types described in Gradle’s dependency management, except for project dependencies.
Using Classes from Init Script Classpath
Once you’ve defined external dependencies in your Gradle init script, you can use the classes from those dependencies just like any other classes available on the classpath. This allows you to leverage external libraries and resources in your init script for various tasks.
For example, let’s consider a previous init script configuration:
Example 3. An init script with external dependencies
// init.gradle.kts
// Import a class from an external dependency
import org.apache.commons.math.fraction.Fraction
initscript {
repositories {
// Define where to find dependencies
mavenCentral()
}
dependencies {
// Declare an external dependency
classpath("org.apache.commons:commons-math:2.0")
}
}
// Use the imported class from the external dependency
println(Fraction.ONE_FIFTH.multiply(2))
// build.gradle.kts
tasks.register("doNothing")
Now, output when applying the init script
> gradle --init-script init.gradle.kts -q doNothing
2 / 5
In this case :
In the init.gradle.kts
file:
- We import a class
Fraction
from an external dependency, Apache Commons Math. - We configure the init script to fetch dependencies from the Maven Central repository.
- We declare the external dependency on the “commons-math” library with version “2.0.”
- We use the imported
Fraction
class to perform a calculation and print the result.
In the build.gradle.kts
file (for reference):
- We define a task named “doNothing” in the build script.
When you apply this init script using Gradle, it fetches the required dependency, and you can use classes from that dependency, as demonstrated by the calculation in the println
statement.
For instance, running gradle --init-script init.gradle.kts -q doNothing
will produce an output of 2 / 5
.
Init script plugins
Plugins can be applied to init scripts in the same way that they can be applied to build scripts or settings files.
To apply a plugin to an init script, you can use the apply()
method. The apply()
method takes a single argument, which is the name of the plugin.
In Gradle, plugins are used to add specific functionality or features to your build. You can apply plugins within your init script to extend or customize the behavior of your Gradle initialization.
For example, in an init script, you can apply a plugin like this:
// init.gradle.kts
// Apply a Gradle plugin
apply(plugin = "java")
// Rest of your init script
In this case, we’re applying the “java” plugin within the init script. This plugin brings in Java-related functionality for your build.
Plugins can also be applied to init scripts from the command line. To do this, you can use the -P
or --project-prop
option. The -P
or --project-prop
option takes a key-value pair, where the key is the name of the plugin and the value is the version of the plugin.
For example, the following command applies the java
plugin to an init script with version 1.0:
gradle -Pplugin=java -Pversion=1.0
This command tells Gradle to apply the java
plugin to the init script with the version 1.0.
Example 4. Using plugins in init scripts
In this example, we’re demonstrating how to use plugins in Gradle init scripts:
init.gradle.kts:
// Apply a custom EnterpriseRepositoryPlugin
apply<EnterpriseRepositoryPlugin>()
class EnterpriseRepositoryPlugin : Plugin<Gradle> {
companion object {
const val ENTERPRISE_REPOSITORY_URL = "https://repo.gradle.org/gradle/repo"
}
override fun apply(gradle: Gradle) {
gradle.allprojects {
repositories {
all {
// Remove repositories not pointing to the specified enterprise repository URL
if (this !is MavenArtifactRepository || url.toString() != ENTERPRISE_REPOSITORY_URL) {
project.logger.lifecycle("Repository ${(this as? MavenArtifactRepository)?.url ?: name} removed. Only $ENTERPRISE_REPOSITORY_URL is allowed")
remove(this)
}
}
// Add the enterprise repository
add(maven {
name = "STANDARD_ENTERPRISE_REPO"
url = uri(ENTERPRISE_REPOSITORY_URL)
})
}
}
}
}
build.gradle.kts:
repositories {
mavenCentral()
}
data class RepositoryData(val name: String, val url: URI)
tasks.register("showRepositories") {
val repositoryData = repositories.withType<MavenArtifactRepository>().map { RepositoryData(it.name, it.url) }
doLast {
repositoryData.forEach {
println("repository: ${it.name} ('${it.url}')")
}
}
}
Output, when applying the init script
> gradle --init-script init.gradle.kts -q showRepositories
repository: STANDARD_ENTERPRISE_REPO ('https://repo.gradle.org/gradle/repo')
Explanation:
- In the
init.gradle.kts
file, a custom plugin namedEnterpriseRepositoryPlugin
is applied. This plugin restricts the repositories used in the build to a specific URL (ENTERPRISE_REPOSITORY_URL
). - The
EnterpriseRepositoryPlugin
class implements thePlugin<Gradle>
marker interface, which allows it to configure the build process. - Inside the
apply
method of the plugin, it removes repositories that do not match the specified enterprise repository URL and adds the enterprise repository to the project. - The
build.gradle.kts
file defines a task calledshowRepositories
. This task prints the list of repositories that are used by the build. - When you run the
gradle
command with the-I
or--init-script
option, Gradle will first execute theinit.gradle.kts
file. This will apply theEnterpriseRepositoryPlugin
plugin and configure the repositories. Once theinit.gradle.kts
file is finished executing, Gradle will then execute thebuild.gradle.kts
file. - Finally the output of the
gradle
command shows that theSTANDARD_ENTERPRISE_REPO
repository is the only repository that is used by the build.
The plugin in the init script ensures that only a specified repository is used when running the build.
When applying plugins within the init script, Gradle instantiates the plugin and calls the plugin instance’s apply(gradle: Gradle)
method. The gradle
object is passed as a parameter, which can be used to configure all aspects of a build. Of course, the applied plugin can be resolved as an external dependency as described above in External dependencies for the init script.
In short, applying plugins in init scripts allows you to configure and customize your Gradle environment right from the start, tailoring it to your specific project’s needs.
Best Practices
Here are some best practices for working with init scripts in Gradle:
- Version Control: If your init script contains project-independent configurations that should be shared across your team, consider version-controlling it alongside your project’s codebase.
- Documentation: Include clear comments in your init scripts to explain their purpose and the configurations they apply. This helps maintainers and collaborators understand the script’s intentions.
- Testing: Test your init scripts in different project environments to ensure they behave as expected. Gradle’s flexibility can lead to unexpected interactions, so thorough testing is crucial.
- Regular Review: Init scripts can evolve over time, so periodically review them to ensure they remain relevant and effective.
Conclusion
Init scripts in Gradle provide a powerful way to configure and customize your Gradle builds at a project or system level. They offer the flexibility to enforce conventions, share common configurations, and simplify project maintenance. Understanding when and how to use init scripts can greatly improve your Gradle build process and help you maintain a consistent and efficient development environment.
So, the next time you find yourself duplicating build configurations or wishing to enforce global settings across your Gradle projects, consider harnessing the power of init scripts to streamline your development workflow.