Build / Gradle

Gradle Dependency Configurations

Android Gradle Dependency Configurations: 8 Key Types, Differences & Best Practices

Gradle is an essential build tool for Android development, managing dependencies and automating the build process. One of the critical aspects of Gradle is its dependency configurations, which control how dependencies are included in a project. In this article, we will explore different Gradle dependency configurations in Android, explaining their usage with Kotlin DSL (build.gradle.kts) examples.

What Are Gradle Dependency Configurations?

In Gradle, dependencies have specific scopes, meaning they are used in different phases of your project.

  • Some dependencies are needed during compilation to build your source code.
  • Others are required only at runtime when the application is running.
Kotlin
dependencies {
    implementation("androidx.core:core-ktx:1.12.0")   // Needed for compiling and running the app
    runtimeOnly("com.squareup.okio:okio:3.3.0")      // Needed only at runtime (e.g. for file I/O)
}

Gradle manages these scopes using Configurations, which define when and how dependencies are used in the build process. Dependency configurations help organize dependencies based on their purpose, such as:

  • Compiling source code
  • Running the application
  • Testing the project

These configurations ensure that dependencies are available at the right stage of development, helping Gradle resolve them efficiently.

Commonly Used Gradle Dependency Configurations inย Android

For Main Code (Application/Library Code)

  • api โ€“ Used for both compilation and runtime. Also included in the published API (for libraries).
  • implementation โ€“ Used for compilation and runtime, but not exposed in the published API.
  • compileOnly โ€“ Used only for compilation, not available at runtime.
  • compileOnlyApi โ€“ Like compileOnly, but included in the published API.
  • runtimeOnly โ€“ Used only at runtime, not needed for compilation.

For Tests

  • testImplementation โ€“ Used for compiling and running tests.
  • testCompileOnly โ€“ Used only for compiling tests, not available at runtime.
  • testRuntimeOnly โ€“ Used only at test runtime, not needed for compilation.

Have you noticed thisโ€Šโ€”โ€Šwhat does โ€˜published APIโ€™ mean?

When we say โ€˜published API,โ€™ we are talking about the public API of our module or libraryโ€Šโ€”โ€Šthe part that other modules or projects can see and use.

For example, if we are creating a library (myLibrary) that will be used by another project (otherApp), some dependencies need to be exposed to the users of our library, while others should remain internal.

Now, letโ€™s break down each configuration with explanations and see how we will use it in our code.

implementation

The implementation configuration is the most commonly used dependency type. It ensures that a module can access the dependency but does not expose it to other modules.

Kotlin
dependencies {
    implementation("androidx.core:core-ktx:1.9.0")
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
}

Here,

  • The implementation keyword ensures that the dependency is only available to the module where it is declared.
  • Other modules cannot access these dependencies, reducing compilation time and improving modularity.
  • Also, dependencies declared with implementation are available at compile time and runtime.

api

The api configuration behaves similarly to implementation, but it exposes the dependency to other modules that depend on that particular module. This means we should use api when we want to expose a dependency to other modules that depend on our module.

Kotlin
// Adding a dependency that should be exposed to other modules
dependencies {
    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
}
  • If Module A includes this dependency with api, then Module B (which depends on Module A) can also access lifecycle-viewmodel-ktx.
  • Use api for both compilation and runtime, but only when the dependency is part of your public API.
  • This is useful for creating libraries or shared modules.

compileOnly

The compileOnly configuration includes dependencies only during compilation but excludes them at runtime.

Kotlin
// Adding a dependency that is required only at compile time
// Lombok is a Java library, but we can also use it in Android to reduce boilerplate code by providing annotations
dependencies {
    compileOnly("org.projectlombok:lombok:1.18.36")
    annotationProcessor("org.projectlombok:lombok:1.18.36")
    kapt("org.projectlombok:lombok:1.18.36") // kotlin project(Kotlin KAPT plugin)
}
  • compileOnly is typically used for annotation processors or compile-time dependencies that are not needed when the application is running.
  • Useful for lightweight dependencies that assist in code generation.
  • The dependency will not be included in the final APK.

compileOnlyApi

The compileOnlyApi configuration behaves like compileOnly, but it also exposes the dependency to modules that depend on this module.

Kotlin
// Adding a compile-only dependency that should be exposed
dependencies {
    compileOnlyApi("org.jetbrains:annotations:20.1.0")
}
  • The module itself uses this dependency only during compilation.
  • Other modules that depend on this module can also access the dependency.
  • Useful when developing shared libraries where compile-time dependencies need to be propagated.

runtimeOnly

The runtimeOnly configuration ensures that the dependency is available only at runtime, not during compilation.

Kotlin
dependencies {
    implementation("com.jakewharton.timber:timber:5.0.1") // Available at compile-time
    runtimeOnly("com.github.tony19:logback-android:3.0.0") // Only used at runtime for logging, logback-android is a backend logging framework that processes logs at runtime.
}
  • The code is compiled without logback-android because it is not available during compilation.
  • runtimeOnly ensures that the dependency is available only at runtime and is not included in the compilation classpath.
  • Helps in keeping the compilation classpath clean.

testImplementation

testImplementation is used when you need a dependency for both compiling and running test cases. This is the most commonly used configuration for test dependencies.

Kotlin
// Adding a testing library
dependencies {
    testImplementation("junit:junit:4.13.2")
}
  • testImplementation ensures that the JUnit library is only available in the test environment.
  • It does not get bundled into the final application.

testCompileOnly

testCompileOnly is used when a dependency is required only at test compile-time but is not needed at runtime. This is useful when a dependency provides compile-time annotations or APIs but doesnโ€™t need to be included during test execution.

Kotlin
dependencies {
    testCompileOnly 'junit:junit:4.13.2'  // Available at compile-time but not included at runtime
}

This means,

  • The dependency is only available during compilation for unit tests.
  • It is not included in the test runtime classpath.
  • Use it when you need a library at compile time (e.g., compile-time annotations) but donโ€™t want it in the runtime environment.

testRuntimeOnly

testRuntimeOnly is used when a dependency is needed only at test runtime and is not required at compile-time.

Kotlin
dependencies {
    testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.9.1")
}

Here,

  • The junit-vintage-engine is used only for executing JUnit 4 tests under the JUnit 5 framework.
  • Means, junit-vintage-engine is used to bridge the gap when you are using the latest version, JUnit 5, but still need to test some functionality that primarily relies on JUnit 4 or JUnit 3. In such cases, it allows you to run these older JUnit 3 and JUnit 4 tests alongside your new JUnit 5 tests within the same JUnit 5 test runner.
  • It is not needed for compilation but must be available when running tests.

Bonus

androidTestImplementation

Similar to testImplementation, but specifically for Android instrumentation tests that run on a device or emulator.

Kotlin
dependencies {
    androidTestImplementation("androidx.test.ext:junit:1.1.3")
}
  • The JUnit extension for Android testing is only available for instrumented tests (which run on an actual device or emulator).
  • Helps in isolating dependencies specific to Android UI tests.

annotationProcessor

Used for annotation processors that generate code at compile time.

Kotlin
dependencies {
    annotationProcessor("androidx.room:room-compiler:2.4.2")
}

// Note: Even though I defined it in a Kotlin file, it is actually in the Gradle file (build.gradle).  
// Also, annotationProcessor is mostly used for Java-based projects; its alternative in Kotlin is KAPT, 
// which we will see just after this.
  • Roomโ€™s compiler processes annotations at compile time and generates necessary database-related code.
  • Typically used with libraries like Dagger, Room, and ButterKnife.

kapt (Kotlin Annotation Processing Tool)

Since annotationProcessor does not work with Kotlin, we use kapt instead.

Kotlin
dependencies {
    kapt("androidx.room:room-compiler:2.4.2")
}
  • kapt handles annotation processing in Kotlin.
  • Required for libraries that rely on annotation processing.

Conclusion

Understanding Gradle dependency configurations is crucial for managing dependencies efficiently in Android development. By using the right configurations, you can improve build performance, maintain a modular project structure, and avoid unnecessary dependencies. By following best practices, you can make your Gradle build system cleaner and more maintainable.

With these insights and examples, you should now have a clear understanding of Gradle dependency configurations in Android and how to use them effectively!

testImplementation in Gradle

Mastering testImplementation in Gradle: 3 Key Differences & Best Practices

Gradle provides different dependency configurations to manage libraries and dependencies for building and testing applications. When working with tests in a Gradle-based project, three key dependency configurations are commonly used:

  • testImplementation โ€“ Used for compiling and running tests.
  • testCompileOnly โ€“ Used only for compiling tests, not available at runtime.
  • testRuntimeOnly โ€“ Used only at test runtime, not needed for compilation.

Letโ€™s explore each of these in detail with examples and understand when to use them.

testImplementation

testImplementation is used when you need a dependency for both compiling and running test cases. This is the most commonly used configuration for test dependencies.

Kotlin
// Adding a testing library
dependencies {
    testImplementation("junit:junit:4.13.2")
}
  • testImplementation ensures that the JUnit library is only available in the test environment.
  • It does not get bundled into the final application.

When to Use testImplementation

  • When the library is needed for both writing and executing tests.
  • Common for testing frameworks like JUnit, TestNG, and mocking libraries like Mockito.

testCompileOnly

testCompileOnly is used when a dependency is required only at test compile-time but is not needed at runtime. This is useful when a dependency provides compile-time annotations or APIs but doesnโ€™t need to be included during test execution.

Kotlin
dependencies {
    testCompileOnly 'junit:junit:4.13.2'  // Available at compile-time but not included at runtime
}

This means,

  • The dependency is only available during compilation for unit tests.
  • It is not included in the test runtime classpath.
  • Use it when you need a library at compile time (e.g., compile-time annotations) but donโ€™t want it in the runtime environment.

When to Use testCompileOnly

  • When a dependency provides compile-time features (such as annotation processing) but is not required at runtime.
  • To minimize the runtime classpath and avoid unnecessary dependencies.

testRuntimeOnly

testRuntimeOnly is used when a dependency is needed only at test runtime and is not required at compile-time.

Kotlin
dependencies {
    testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.9.1")
}

Here,

  • The junit-vintage-engine is used only for executing JUnit 4 tests under the JUnit 5 framework.
  • Means, junit-vintage-engine is used to bridge the gap when you are using the latest version, JUnit 5, but still need to test some functionality that primarily relies on JUnit 4 or JUnit 3. In such cases, it allows you to run these older JUnit 3 and JUnit 4 tests alongside your new JUnit 5 tests within the same JUnit 5 test runner.
  • It is not needed for compilation but must be available when running tests.

When to Use testRuntimeOnly

  • When a dependency is required only at runtime, such as database drivers, logging frameworks, or test execution engines.
  • When you want to keep the compile-time classpath clean and only include dependencies that are absolutely necessary for execution.

Conclusion

Understanding testImplementation, testCompileOnly, and testRuntimeOnly helps in optimizing test dependencies and ensuring efficient builds.

  • Use testImplementation for dependencies needed both at compile-time and runtime.
  • Use testCompileOnly for dependencies required only during compilation.
  • Use testRuntimeOnly for dependencies that are only needed when running tests.

By applying these configurations effectively, you can keep your test environment lightweight and efficient while avoiding unnecessary dependencies in the build process.

init scripts

Decoding Magic of Init Scripts in Gradle : A Comprehensive Guide to Mastering Init Scripts

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:

Kotlin
// Groovy init.gradle

allprojects {
    // Your configuration here
}

And in Kotlin:

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:
Kotlin
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

Kotlin
//build.gradle.kts

repositories {
    mavenCentral()
}
tasks.register("showRepos") {
    val repositoryNames = repositories.map { it.name }
    doLast {
        println("All repos:")
        println(repositoryNames)
    }
}
Kotlin
// init.gradle.kts

allprojects {
    repositories {
        mavenLocal()
    }
}

Output when applying the init script:

Kotlin
> 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

Kotlin
// 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

Kotlin
// 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))
Kotlin
// build.gradle.kts

tasks.register("doNothing")

Now, output when applying the init script

Kotlin
> 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:

Kotlin
// 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:

Kotlin
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:

Kotlin
// 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:

Kotlin
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

Kotlin
> 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 named EnterpriseRepositoryPlugin is applied. This plugin restricts the repositories used in the build to a specific URL (ENTERPRISE_REPOSITORY_URL).
  • The EnterpriseRepositoryPlugin class implements the Plugin<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 called showRepositories. 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 the init.gradle.kts file. This will apply the EnterpriseRepositoryPlugin plugin and configure the repositories. Once the init.gradle.kts file is finished executing, Gradle will then execute the build.gradle.kts file.
  • Finally the output of the gradle command shows that the STANDARD_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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.

gradle directories and files

Inside Gradleโ€™s Blueprint: Navigating Essential Directories and Files for Seamless Development

When it comes to building and managing projects, Gradle has become a popular choice among developers due to its flexibility, extensibility, and efficiency. One of the key aspects of Gradleโ€™s functionality lies in how it organizes and utilizes directories and files within a project. In this blog post, we will take an in-depth look at the directories and files Gradle uses, understanding their purposes and significance in the build process.

Project Structure

Before diving into the specifics of directories and files, letโ€™s briefly discuss the typical structure of a Gradle project. Gradle projects are structured in a way that allows for clear separation of source code, resources, configuration files, and build artifacts. The most common structure includes directories such as:

Kotlin
Project Root
โ”œโ”€โ”€ build.gradle.kts (build.gradle)
โ”œโ”€โ”€ settings.gradle.kts (settings.gradle)
โ”œโ”€โ”€ gradle.properties
โ”œโ”€โ”€ gradlew (Unix-like systems)
โ”œโ”€โ”€ gradlew.bat (Windows)
โ”œโ”€โ”€ gradle
โ”‚   โ””โ”€โ”€ wrapper
โ”‚       โ””โ”€โ”€ gradle-wrapper.properties
โ”œโ”€โ”€ src
โ”‚   โ”œโ”€โ”€ main
โ”‚   โ”‚   โ”œโ”€โ”€ java
โ”‚   โ”‚   โ”œโ”€โ”€ resources
โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ””โ”€โ”€ test
โ”‚       โ”œโ”€โ”€ java
โ”‚       โ”œโ”€โ”€ resources
โ”‚       โ””โ”€โ”€ ...
โ””โ”€โ”€ build
    โ”œโ”€โ”€ ...
    โ”œโ”€โ”€ outputs
    โ””โ”€โ”€ ...
  • src: This directory contains the source code and resources for your project. Itโ€™s usually divided into subdirectories like main and test, each containing corresponding code and resources. The main directory holds the main application code, while the test directory contains unit tests.
  • build: Gradle generates build artifacts in this directory. This includes compiled code, JARs, test reports, and other artifacts resulting from the build process. The build directory is typically temporary and gets regenerated each time you build the project.
  • gradle: This directory contains Gradle-specific files and configurations. It includes the wrapper subdirectory, which holds the Gradle Wrapper files. The Gradle Wrapper is a script that allows you to use a specific version of Gradle without installing it globally on your system.

Directories

Gradle relies on two main directories: the Gradle User Home directory and the Project root directory. Letโ€™s explore whatโ€™s inside each of them and how temporary files and directories are cleaned up.

Gradle User Home directory

The Gradle User Home (usually found at <home directory of the current user>/.gradle) is like a special storage area for Gradle. It keeps important settings, like configuration, initialization scripts as well as caches and logs, safe and organized.

Kotlin
โ”œโ”€โ”€ caches   // 1
โ”‚   โ”œโ”€โ”€ 4.8  // 2
โ”‚   โ”œโ”€โ”€ 4.9  // 2
โ”‚   โ”œโ”€โ”€ โ‹ฎ
โ”‚   โ”œโ”€โ”€ jars-3 // 3
โ”‚   โ””โ”€โ”€ modules-2 // 3
โ”œโ”€โ”€ daemon   // 4
โ”‚   โ”œโ”€โ”€ โ‹ฎ
โ”‚   โ”œโ”€โ”€ 4.8
โ”‚   โ””โ”€โ”€ 4.9
โ”œโ”€โ”€ init.d   // 5
โ”‚   โ””โ”€โ”€ my-setup.gradle
โ”œโ”€โ”€ jdks     // 6
โ”‚   โ”œโ”€โ”€ โ‹ฎ
โ”‚   โ””โ”€โ”€ jdk-14.0.2+12
โ”œโ”€โ”€ wrapper
โ”‚   โ””โ”€โ”€ dists   // 7
โ”‚       โ”œโ”€โ”€ โ‹ฎ
โ”‚       โ”œโ”€โ”€ gradle-4.8-bin
โ”‚       โ”œโ”€โ”€ gradle-4.9-all
โ”‚       โ””โ”€โ”€ gradle-4.9-bin
โ””โ”€โ”€ gradle.properties   // 8 

1. Global cache directory (for everything thatโ€™s not project-specific): This directory stores the results of tasks that are not specific to any particular project. This includes things like the results of downloading dependencies and the results of compiling code. The default location of this directory is $USER_HOME/.gradle/caches.

2. Version-specific caches (e.g. to support incremental builds): This directory stores the results of tasks that are specific to a particular version of Gradle. This includes things like the results of parsing the projectโ€™s build script and the results of configuring the projectโ€™s dependencies. The default location of this directory is $USER_HOME/.gradle/<gradle-version>/caches.

3. Shared caches (e.g. for artifacts of dependencies): This directory stores the results of tasks that are shared by multiple projects. This includes things like the results of downloading dependencies and the results of compiling code. The default location of this directory is $USER_HOME/.gradle/shared/caches.

4. Registry and logs of the Gradle Daemon (the daemon is a long-running process that can be used to speed up builds): This directory stores the registry of the Gradle Daemon and the logs of the Gradle Daemon. The default location of this directory is $USER_HOME/.gradle/daemon.

5. Global initialization scripts (scripts that are executed before any build starts): This directory stores the global initialization scripts. The default location of this directory is $USER_HOME/.gradle/init.d.

6. JDKs downloaded by the toolchain support: This directory stores the JDKs that are downloaded by the toolchain support. The toolchain support is used to compile code for different platforms. The default location of this directory is $USER_HOME/.gradle/toolchains.

7. Distributions downloaded by the Gradle Wrapper: This directory stores the distributions that are downloaded by the Gradle Wrapper. The Gradle Wrapper is a script that can be used to simplify the installation and execution of Gradle. The default location of this directory is $USER_HOME/.gradle/wrapper.

8. Global Gradle configuration properties (properties that are used by all Gradle builds): This directory stores the global Gradle configuration properties. The default location of this directory is $USER_HOME/.gradle/gradle.properties.

Cleaning Up Caches and Distributions

When you use Gradle for building projects, it creates temporary files and data in your computerโ€™s user home directory. Gradle automatically cleans up these files to free up space. Hereโ€™s how it works:

Background Cleanup

Gradle cleans up in the background when you stop the Gradle tool (daemon). If you donโ€™t use the background cleanup, it happens after each build with a progress bar.

For example, imagine youโ€™re working on a software project using Gradle for building. After you finish your work and close the Gradle tool, it automatically cleans up any temporary files it created. This ensures that your computer doesnโ€™t get cluttered with unnecessary files over time. Itโ€™s like cleaning up your workspace after youโ€™re done with a task.

Cleaning Strategies

In a software project, you often use different versions of Gradle. Gradle keeps some files specific to each version. If a version hasnโ€™t been used for a while, these files are removed to save space. This is similar to getting rid of old documents or files you no longer need. For instance, if youโ€™re not using a particular version of a library anymore, Gradle will clean up the related files.

Gradle has different ways to clean up:

  • Version-specific Caches: These are files for specific versions of Gradle. If theyโ€™re not used, Gradle deletes release version files after 30 days of inactivity and snapshot version files after 7 days of inactivity.
  • Shared Caches: These are files used by multiple versions of Gradle. If no Gradle version needs them, theyโ€™re deleted.
  • Files for Current Gradle Version: Files for the version of Gradle youโ€™re using are checked. Depending on if they can be made again or need to be downloaded, theyโ€™re deleted after 7 or 30 days of not being used.
  • Unused Distributions: If a distribution of Gradle isnโ€™t used, itโ€™s removed.

Configuring Cleanup

Think about a project where you frequently switch between different Gradle versions. You can decide how long Gradle keeps files before cleaning them up. For example, if you want to keep the files of the released versions for 45 days and the files of the snapshots (unstable versions) for 10 days, you can adjust these settings. Itโ€™s like deciding how long you want to keep your emails before they are automatically deleted.

You can set how long Gradle keeps these files:

  • Released Versions: 30 days for released versions.
  • Snapshot Versions: 7 days for snapshot versions.
  • Downloaded Resources: 30 days for resources from the internet.
  • Created Resources: 7 days for resources Gradle makes.

How to Configure

You can change these settings in a file called โ€œcache-settings.gradle.ktsโ€ in your Gradle User Home directory. Hereโ€™s an example of how you can do it:

Kotlin
beforeSettings {
    caches {
        releasedWrappers.setRemoveUnusedEntriesAfterDays(45)
        snapshotWrappers.setRemoveUnusedEntriesAfterDays(10)
        downloadedResources.setRemoveUnusedEntriesAfterDays(45)
        createdResources.setRemoveUnusedEntriesAfterDays(10)
    }
}

Here,

  1. beforeSettings: This is a Gradle lifecycle event that allows you to execute certain actions before the settings of your build script are applied.
  2. caches: This part refers to the caches configuration within the beforeSettings block.
  3. releasedWrappers.setRemoveUnusedEntriesAfterDays(45): This line sets the retention period for released versions and their related caches to 45 days. It means that if a released version of Gradle or its cache files havenโ€t been used for 45 days, they will be removed during cleanup.
  4. snapshotWrappers.setRemoveUnusedEntriesAfterDays(10): This line sets the retention period for snapshot versions (unstable, in-development versions) and their related caches to 10 days. If they havenโ€™t been used for 10 days, they will be removed during cleanup.
  5. downloadedResources.setRemoveUnusedEntriesAfterDays(45): This line sets the retention period for resources downloaded from remote repositories (e.g., cached dependencies) to 45 days. If these resources havenโ€™t been used for 45 days, they will be removed.
  6. createdResources.setRemoveUnusedEntriesAfterDays(10): This line sets the retention period for resources created by Gradle during the build process (e.g., artifact transformations) to 10 days. If these resources havenโ€™t been used for 10 days, they will be removed.

In essence, this code configures how long different types of files should be retained before Gradleโ€™s automatic cleanup process removes them. The numbers you see (45, 10) represent the number of days of inactivity after which the files will be considered for cleanup. You can adjust these numbers based on your projectโ€™s needs and your preferred cleanup frequency.

Cleaning Frequency

You can choose how often cleanup happens:

  • DEFAULT: Happens every 24 hours.
  • DISABLED: Never cleans up (useful for specific cases).
  • ALWAYS: Cleans up after each build (useful but can be slow).

Sometimes you might want to control when the cleanup happens. If you choose the โ€œDEFAULTโ€ option, It will automatically clean up every 24 hours in the background. However, if you have limited storage and need to manage space carefully, you might choose the โ€œALWAYSโ€ option. This way, cleanup occurs after each build, ensuring that space is cleared right away. This can be compared to deciding whether to clean your room every day (DEFAULT) or cleaning it immediately after a project (ALWAYS).

Disabling Cleanup

Hereโ€™s how you can disable cleanup:

Kotlin
beforeSettings {
    caches {
        cleanup.set(Cleanup.DISABLED)
    }
}

Above I mentioned โ€œuseful for specific cases,โ€ I meant that the option to disable cleanup (CLEANUP.DISABLED) might be helpful in certain situations where you have a specific reason to avoid cleaning up the temporary files and data created by it.

For example, imagine youโ€™re working on a project where you need to keep these temporary files for a longer time because you frequently switch between different builds or versions. In this scenario, you might want to delay the cleanup process until a later time when itโ€™s more convenient for you, rather than having Gradle automatically clean up these files.

So, โ€œuseful for specific casesโ€ means there are situations where you might want to keep the temporary files around for a longer duration due to your projectโ€™s requirements or your workflow.

Remember, you can only change these settings using specific files in your Gradle User Home directory. This helps prevent different projects from conflicting with each otherโ€™s settings.

Sharing a Gradle User Home Directory between Multiple Gradleย Versions

Sharing a single Gradle User Home among various Gradle versions is a common practice. In this shared home, there are caches that belong to specific versions of Gradle. Each Gradle version usually manages its own caches.

However, there are some caches that are used by multiple Gradle versions, like the cache for dependency artifacts or the artifact transform cache. Starting from version 8.0, you can adjust settings to control how long these caches are kept. But in older versions, the retention periods are fixed (either 7 or 30 days depending on the cache).

This situation can lead to a scenario where different versions might have different settings for how long cache artifacts are retained. As a result, shared caches could be accessed by various versions with different retention settings.

This means that:

  • If you donโ€™t customize the retention period, all versions of Gradle that do cleanup will follow the same retention periods. This means that sharing a Gradle User Home among multiple versions wonโ€™t cause any issues in this case. The cleanup behavior will be consistent across all versions.
  • If you set a custom retention period for Gradle versions equal to or greater than 8.0, making it shorter than the older fixed periods, it wonโ€™t cause any issues. The newer versions will clean up their artifacts sooner than the old fixed periods. However, the older versions wonโ€™t be aware of these custom settings, so they wonโ€™t participate in the cleanup of shared caches. This means the cleanup behavior might not be consistent across all versions.
  • If you set a custom retention period for Gradle versions equal to or greater than 8.0, now making it longer than the older fixed periods, there could be an issue. The older versions might clean the shared caches sooner than your custom settings. If you want the newer versions to keep the shared cache entries for a longer period, they canโ€™t share the same Gradle User Home with the older versions. Instead, they should use a separate directory to ensure the desired retention periods are maintained.

When sharing the Gradle User Home with Gradle versions before 8.0, thereโ€™s another thing to keep in mind. In older versions, the DSL elements used to set cache retention settings arenโ€™t available. So, if youโ€™re using a shared init script among different versions, you need to consider this.

Kotlin
//gradleUserHome/init.d/cache-settings.gradle.kts

if (GradleVersion.current() >= GradleVersion.version("8.0")) {
    apply(from = "gradle8/cache-settings.gradle.kts")
}
Kotlin
//gradleUserHome/init.d/gradle8/cache-settings.gradle.kts

beforeSettings {
    caches {
        releasedWrappers { setRemoveUnusedEntriesAfterDays(45) }
        snapshotWrappers { setRemoveUnusedEntriesAfterDays(10) }
        downloadedResources { setRemoveUnusedEntriesAfterDays(45) }
        createdResources { setRemoveUnusedEntriesAfterDays(10) }
    }
}

To handle this, you can apply a script that matches the version requirements. Make sure this version-specific script is stored outside the init.d directory, perhaps in a sub-directory. This way, it wonโ€™t be automatically applied, and you can ensure that the right settings are used for each Gradle version.

Cache marking

Starting from Gradle version 8.1, a new feature is available. Gradle now lets you mark caches using a file called CACHEDIR.TAG, following the format defined in the Cache Directory Tagging Specification. This file serves a specific purpose: it helps tools recognize directories that donโ€™t require searching or backing up.

By default, in the Gradle User Home, several directories are already marked with this file: caches, wrapper/dists, daemon, and jdks. This means these directories are identified as ones that donโ€™t need to be extensively searched or included in backups.

Here is a sample CACHEDIR.TAG file:

Kotlin
# This file is a cache tag file, created by Gradle version 8.1.
# It identifies the directory `caches` as a Gradle cache directory.

name = caches
version = 8.1
signature = sha256:<signature>

The name field specifies the name of the directory that is being tagged. In this case, the directory is caches.

The version field specifies the version of Gradle that created the tag. In this case, the version is 8.1.

The signature field is a signature that can be used to verify the authenticity of the tag. This signature is created using a cryptographic hash function.

The CACHEDIR.TAG file is a simple text file, so you can create it using any text editor. However, it is important to make sure that the file is created with the correct permissions. The file should have the following permissions:

-rw-r--r--          

This means that the file is readable by everyone, but only writable by the owner.

Configuring cacheย marking

The cache marking feature can be configured via an init script in the Gradle User Home:

Kotlin
//gradleUserHome/init.d/cache-settings.gradle.kts

beforeSettings {
    caches {
        // Disable cache marking for all caches
        markingStrategy.set(MarkingStrategy.NONE)
    }
}

Note that cache marking settings can only be configured via init scripts and should be placed under the init.d directory in the Gradle User Home. This is because the init.d directory is loaded before any other scripts, so the cache marking settings will be applied to all projects that use the Gradle User Home.

This also limits the possibility of different conflicting settings from different projects being applied to the same directory. If the cache marking settings were not coupled to the Gradle User Home, then it would be possible for different projects to apply different settings to the same directory. This could lead to confusion and errors.

Project Root Directory

The project root directory holds all the source files for your project. It also includes files and folders created by Gradle, likeย .gradle and build. While source files are typically added to version control, the ones created by Gradle are temporary and used to enable features like incremental builds. A typical project root directory structure looks something like this:

Kotlin
โ”œโ”€โ”€ .gradle    // 1     (Folder for caches)
โ”‚   โ”œโ”€โ”€ 4.8    // 2 
โ”‚   โ”œโ”€โ”€ 4.9    // 2
โ”‚   โ””โ”€โ”€ โ‹ฎ
โ”œโ”€โ”€ build      // 3     (Generated build files)
โ”œโ”€โ”€ gradle              // (Folder for Gradle tools)
โ”‚   โ””โ”€โ”€ wrapper   // 4     (Wrapper configuration)
โ”œโ”€โ”€ gradle.properties   // 5  (Project properties)
โ”œโ”€โ”€ gradlew   // 6          (Script to run Gradle on Unix-like systems)
โ”œโ”€โ”€ gradlew.bat   // 6      (Script to run Gradle on Windows)
โ”œโ”€โ”€ settings.gradle or settings.gradle.kts  // 7 (Project settings)
โ”œโ”€โ”€ subproject-one   // 8                     (Subproject folder)
|   โ””โ”€โ”€ build.gradle or build.gradle.kts   // 9 (Build script for subproject)
โ”œโ”€โ”€ subproject-two   // 8                       (Another subproject folder)
|   โ””โ”€โ”€ build.gradle or build.gradle.kts   // 9 (Build script for another subproject)
โ””โ”€โ”€ โ‹ฎ                                        // (And more subprojects)
  1. Project-specific cache directory generated by Gradle: This is a folder where Gradle stores temporary files and data that it uses to speed up building projects. Itโ€™s specific to your project and helps Gradle avoid redoing certain tasks each time you build, which can save time.
  2. Version-specific caches (e.g. to support incremental builds): These caches are used to remember previous build information, allowing Gradle to only rebuild parts of your project that have changed. This is especially helpful for โ€œincremental buildsโ€ where you make small changes and donโ€™t want to redo everything.
  3. The build directory of this project into which Gradle generates all build artifacts: When you build your project using Gradle, it generates various files and outputs. This โ€œbuild directoryโ€ is where Gradle puts all of those created files like compiled code, libraries, and other artifacts.
  4. Contains the JAR file and configuration of the Gradle Wrapper: The JAR file is a packaged software component. Here, it refers to the Gradle Wrapperโ€™s JAR file, which allows you to use Gradle without installing it separately. The configuration helps the Wrapper know how to work with Gradle.
  5. Project-specific Gradle configuration properties: These are settings that are specific to your project and control how Gradle behaves when building. For example, they might determine which plugins to use or how to package your project.
  6. Scripts for executing builds using the Gradle Wrapper: The gradlew and gradlew.bat scripts are used to execute builds using the Gradle Wrapper. These scripts are special commands that let you run Gradle tasks without needing to have Gradle installed globally on your system.
  7. The projectโ€™s settings file where the list of subprojects is defined: This file defines how your project is structured, including the list of smaller โ€œsubprojectsโ€ that make up the whole. It helps Gradle understand the layout of your project.
  8. Usually a project is organized into one or multiple subprojects: A project can be split into smaller pieces called subprojects. This is useful for organizing complex projects into manageable parts, each with its own set of tasks.
  9. Each subproject has its own Gradle build script: Each subproject within your project has its own build script. This script provides instructions to Gradle on how to build that specific part of your project. It can include tasks like compiling code, running tests, and generating outputs.

Project cacheย cleanup

From version 4.10 onwards, Gradle automatically cleans the project-specific cache directory. After building the project, version-specific cache directories inย .gradle/<gradle-version>/ are checked periodically (at most every 24 hours) for whether they are still in use. They are deleted if they havenโ€™t been used for 7 days.

This helps to keep the cache directories clean and free up disk space. It also helps to ensure that the build process is as efficient as possible.

Conclusion

In conclusion, delving into the directories and files that Gradle utilizes provides a valuable understanding of how this powerful build tool operates. Navigating through the cache directory, version-specific caches, build artifacts, Gradle Wrapper components, project configuration properties, and subproject structures sheds light on the intricate mechanisms that streamline the development process. With Gradleโ€™s continuous enhancements, such as automated cache cleaning from version 4.10 onwards, developers can harness an optimized environment for building projects efficiently. By comprehending the roles of these directories and files, developers are empowered to leverage Gradle to its fullest potential, ensuring smooth and effective project management.

Gradle Properties

A Clear Guide to Demystify Gradle Properties for Enhanced Project Control

In the realm of modern software development, efficiency and automation reign supreme. Enter Gradle, the powerful build automation tool that empowers developers to wield control over their build process through a plethora of configuration options. One such avenue of control is Gradle properties, a mechanism that allows you to mold your build environment to your exact specifications. In this guide, weโ€™ll navigate the terrain of Gradle properties, understand their purpose, explore various types, and decipher how to wield them effectively.

Configure Gradleย Behavior

Gradle provides multiple mechanisms for configuring the behavior of Gradle itself and specific projects. The following is a reference for using these mechanisms.

When configuring Gradle behavior you can use these methods, listed in order of highest to lowest precedence (the first one wins):

  1. Command-line flags: You can pass flags to the gradle command to configure Gradle behavior. For example, the --build-cache flag tells Gradle to cache the results of tasks, which can speed up subsequent builds.
  2. System properties: You can set system properties to configure Gradle behavior. For example, the systemProp.http.proxyHost property can be used to set the proxy host for HTTP requests.
  3. Gradle properties: You can set Gradle properties to configure Gradle behavior. Gradle properties are similar to system properties, but they are specific to Gradle. For example, the org.gradle.caching property can be used to enable or disable caching and that is typically stored in a gradle.properties file in a project directory or in the GRADLE_USER_HOME.
  4. Environment variables: You can set environment variables to configure Gradle behavior. Environment variables are similar to system properties, but they are not specific to Gradle. For example, GRADLE_OPTS is sourced by the environment that executes Gradle. This variable allows you to set Java options and other configuration options that affect how Gradle runs.

In short, If we talk about precedence, If you set a property using both a command-line flag and a system property, the value specified by the command-line flag will take precedence.

Gradle Properties

Gradle is a tool that helps you build and manage your Java, Kotlin, and Android projects. It lets you set up how your Java programs are run during the building process. You can configure these settings either on your own computer or for your whole team. To make things consistent for everyone on the team, you can save these settings in a special file called โ€œgradle.properties,โ€ which you keep in your projectโ€™s folder.

When Gradle figures out how to run your project, it looks at different places to find these settings. It checks:

  1. Any settings you give it when you run a command.
  2. Settings in a file called โ€œgradle.propertiesโ€ in your personal Gradle settings folder (userโ€™s home directory).
  3. Settings in โ€œgradle.propertiesโ€ files in your projectโ€™s folder, or even its parent folders up to the main project folder.
  4. Settings in the Gradle programโ€™s own folder (Gradle installation directory).

If a setting is in multiple places, Gradle uses the first one it finds in this order.

Here are some gradle properties you can use to set up your Gradle environment:

Build Cache

The build cache is a feature that allows Gradle to reuse the outputs of previous builds, which can significantly speed up the build process. By default, the build cache is not enabled.

  1. org.gradle.caching: This can be set to either โ€œtrueโ€ or โ€œfalseโ€. When itโ€™s set to โ€œtrueโ€, Gradle will try to use the results from previous builds for tasks, which makes the builds faster. This is called the build cache. By default, this is turned off.
  2. org.gradle.caching.debug: This property can also be set to either โ€œtrueโ€ or โ€œfalseโ€. When itโ€™s set to โ€œtrueโ€, Gradle will show information on the console about how itโ€™s using the build cache for each task. This can help you understand whatโ€™s happening. The default value is โ€œfalseโ€.

Here are some additional things to keep in mind about the build cache:

  • The build cache is enabled for all tasks by default. However, you can disable the build cache for individual tasks by setting the buildCache property to false for that task.
  • The build cache is stored in a local directory. The location of this directory can be configured using the org.gradle.caching.directory property.
  • The build cache can also be stored in a remote repository. This can be useful for teams that need to share the build cache across multiple machines.

Configuration Caching

Gradle configuration caching is a feature that allows Gradle to reuse the build configuration from previous builds. This can significantly speed up the build process, especially for projects with complex build configurations. By default, configuration caching is not enabled.

  1. org.gradle.configuration-cache: This can be set to either โ€œtrueโ€ or โ€œfalseโ€. When set to โ€œtrue,โ€ Gradle will try to remember how your project was set up in previous builds and reuse that information. By default, this is turned off.
  2. org.gradle.configuration-cache.problems: You can set this to โ€œfailโ€ or โ€œwarnโ€. If set to โ€œwarn,โ€ Gradle will tell you about any issues with the configuration cache, but it wonโ€™t stop the build. If set to โ€œfail,โ€ it will stop the build if there are any issues. The default is โ€œfail.โ€
  3. org.gradle.configuration-cache.max-problems: You can set the maximum number of configuration cache problems allowed as warnings before Gradle fails the build. It decides how many issues can be there before Gradle stops the build. The default is 512.
  4. org.gradle.configureondemand: This can be set to either โ€œtrueโ€ or โ€œfalseโ€. When set to โ€œtrue,โ€ Gradle will try to set up only the parts of your project that are needed. This can be useful for projects with large build configurations, as it can reduce the amount of time Gradle needs to spend configuring the project. By default, this is turned off.

Gradle Daemon

The daemon is a long-lived process that is used to run Gradle builds. The org.gradle.daemon property controls whether or not Gradle will use the daemon. By default, the daemon is enabled.

  1. org.gradle.daemon: This can be set to either โ€œtrueโ€ or โ€œfalseโ€. When set to โ€œtrue,โ€ Gradle uses something called the โ€œDaemonโ€ to run your projectโ€™s builds. The Daemon makes things faster. By default, this is turned on, so builds use the Daemon.
  2. org.gradle.daemon.idletimeout: This controls how long the daemon will remain idle before it terminates itself. You can set a number here. The Gradle Daemon will shut down by itself if itโ€™s not being used for the specified number of milliseconds. The default is 3 hours (10800000 milliseconds).

Here are some of the benefits of using the Gradle daemon:

  • Faster builds: The daemon can significantly improve the performance of Gradle builds by caching project information and avoiding the need to start a new JVM for each build.
  • Reduced memory usage: The daemon can reduce the amount of memory used by Gradle builds by reusing the same JVM for multiple builds.
  • Improved stability: The daemon can improve the stability of Gradle builds by avoiding the need to restart the JVM for each build.

If you are using Gradle for your builds, I recommend that you enable the daemon and configure it to terminate itself after a reasonable period of time. This will help to improve the performance, memory usage, and stability of your builds.

Remote Debugging

Remote debugging in Gradle allows you to debug a Gradle build that is running on a remote machine. This can be useful for debugging builds that are deployed to production servers or that are running on devices that are not easily accessible.

  1. org.gradle.debug: The org.gradle.debug property is a Gradle property that controls whether or not remote debugging is enabled for Gradle builds. When set to true, Gradle will run the build with remote debugging enabled, which means that a debugger can be attached to the Gradle process while it is running. The debugger will be listening on port 5005, which is the default port for remote debugging. The -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 JVM argument is used to enable remote debugging in the JVM. agentlib:jdwp tells the Java Virtual Machine (JVM) to load the JDWP (Java Debug Wire Protocol) agent library. The transport parameter specifies the transport that will be used for debugging, in this case dt_socket which means that the debugger will connect to the JVM via a socket. The server parameter specifies that the JVM will act as a server for the debugger, which means that it will listen for connections from the debugger. The suspend parameter specifies whether or not the JVM will suspend execution when the debugger attaches. In this case, the JVM will suspend execution, which means that the debugger will be able to step through the code line by line.
  2. org.gradle.debug.host: This property specifies the host address that the debugger should listen on or connect to when remote debugging is enabled. If you set it to a specific host address, the debugger will only listen on that address or connect to that address. If you set it to โ€œ*โ€, the debugger will listen on all network interfaces. By default, if this property is not specified, the behavior depends on the version of Java being used.
  3. org.gradle.debug.port: This property specifies the port number that the debugger should use when remote debugging is enabled. The default port number is 5005.
  4. org.gradle.debug.server: This property determines the mode in which the debugger operates. If set to true (which is the default), Gradle will run the build in socket-attach mode of the debugger. If set to false, Gradle will run the build in socket-listen mode of the debugger.
  5. org.gradle.debug.suspend: This property controls whether the JVM running the Gradle build process should be suspended until a debugger is attached. If set to true (which is the default), the JVM will wait for a debugger to attach before continuing the execution.

Logging inย Gradle

Configuration properties related to logging in Gradle. These properties allow you to control how logging and stack traces are displayed during the build process:

1. org.gradle.logging.level: This property sets the logging level for Gradleโ€™s output. The possible values are quiet, warn, lifecycle, info, and debug. The values are not case-sensitive. Hereโ€™s what each level means:

  • quiet: Only errors are logged.
  • warn: Warnings and errors are logged.
  • lifecycle: The lifecycle of the build is logged, including tasks that are executed and their results. This is the default level.
  • info: All information about the build is logged, including the inputs and outputs of tasks.
  • debug: All debug information about the build is logged, including the stack trace for any exceptions that occur.

2. org.gradle.logging.stacktrace: This property controls whether or not stack traces are displayed in the build output when an exception occurs. The possible values are:

  • internal: Stack traces are only displayed for internal exceptions.
  • all: Stack traces are displayed for all exceptions and build failures.
  • full: Stack traces are displayed for all exceptions and build failures, and they are not truncated. This can lead to a much more verbose output.

File Systemย Watching

File system watching is a feature in Gradle that lets Gradle notice when there are changes to the files in your project. If there are changes, Gradle can then decide to redo the project build. This is handy because it helps make builds fasterโ€Šโ€”โ€ŠGradle only has to rebuild the parts that changed since the last build.

1. org.gradle.vfs.verbose: This property controls whether or not Gradle logs more information about the file system changes that it detects when file system watching is enabled. When set to true, Gradle will log more information, such as the file path, the change type, and the timestamp of the change. This can be helpful for debugging problems with file system watching. The default value is false.

2. org.gradle.vfs.watch: This property controls whether or not Gradle watches the file system for changes. When set to true, Gradle will keep track of the files and directories that have changed since the last build. This information can be used to speed up subsequent builds by only rebuilding the files that have changed. The default value is true on operating systems where Gradle supports this feature.

Performance Options

  1. org.gradle.parallel: This option can be set to either true or false. When set to true, Gradle will divide its tasks among separate Java Virtual Machines (JVMs) called workers, which can run concurrently. This can improve build speed by utilizing multiple CPU cores effectively. The number of workers is controlled by the org.gradle.workers.max option. By default, this option is set to false, meaning no parallel execution.
  2. org.gradle.priority: This setting controls the scheduling priority of the Gradle daemon and its related processes. The daemon is a background process that helps speed up Gradle builds by keeping certain information cached. It can be set to either low or normal. Choosing low priority means the daemon will run with lower system priority, which can be helpful to avoid interfering with other critical tasks(means doesnโ€™t disturb or disrupt important tasks). The default is normal priority.
  3. org.gradle.workers.max: This option determines the maximum number of worker processes that Gradle can use when performing parallel tasks. Each worker is a separate JVM process that can handle tasks concurrently, potentially improving build performance. If this option is not set, Gradle will use the number of CPU processors available on your machine as the default. Setting this option allows you to control the balance between parallelism and resource consumption.

Console Loggingย Options

1. org.gradle.console: This setting offers various options for customizing the appearance and verbosity of console output when running Gradle tasks. You can choose from the following values:

  • auto: The default setting, which adapts the console output based on how Gradle is invoked(environment).
  • plain: Outputs simple, uncolored text without any additional formatting.
  • rich: Enhances console output with colors and formatting to make it more visually informative.
  • verbose: Provides detailed and comprehensive console output, useful for debugging and troubleshooting.

2. org.gradle.warning.mode: This option determines how Gradle displays warning messages during the build process. You have several choices:

  • all: Displays all warning messages.
  • fail: Treats warning messages as errors, causing the build to fail when warnings are encountered. This means gradle will fail the build if any warnings are emitted.
  • summary: Displays a summary of warning messages at the end of the build. The default behavior is to show a summary of warning messages.
  • none: Suppresses the display of warning messages entirely.

3. org.gradle.welcome: This setting controls whether Gradle should display a welcome message when you run Gradle commands. You can set it to:

  • never: Suppresses (never print) the welcome message completely.
  • once: Displays the welcome message once for each new version of Gradle. The default behavior is to show the welcome message once for each new version of Gradle.

Environment Options

  1. org.gradle.java.home: This option allows you to specify the location (path) of the Java Development Kit (JDK) or Java Runtime Environment (JRE) that Gradle should use for the build process. Itโ€™s recommended to use a JDK location because it provides a more complete set of tools for building projects. However, depending on your projectโ€™s requirements, a JRE location might suffice. If you donโ€™t set this option, Gradle will try to use a reasonable default based on your environment (using JAVA_HOME or the systemโ€™s java executable).
  2. org.gradle.jvmargs: This setting lets you provide additional arguments to the Java Virtual Machine (JVM) when running the Gradle Daemon. This option is useful for configuring JVM memory settings, which can significantly impact build performance. The default JVM arguments for the Gradle Daemon are -Xmx512m "-XX:MaxMetaspaceSize=384m"ย , which specifies that the daemon should be allocated 512MB of memory and that the maximum size of the metaspace should be 384MB.

Continuous Build

org.gradle.continuous.quietperiod: This setting is relevant when youโ€™re utilizing continuous build functionality in Gradle. Continuous build mode is designed to automatically rebuild your project whenever changes are detected. However, to avoid excessive rebuilds triggered by frequent changes, Gradle introduces a โ€œquiet period.โ€

A quiet period is a designated time interval in milliseconds that Gradle waits after the last detected change before initiating a new build. This allows time for multiple changes to accumulate before the build process starts. If additional changes occur during the quiet period, the timer restarts. This mechanism helps prevent unnecessary builds triggered by rapid or small changes.

The option org.gradle.continuous.quietperiod allows you to specify the duration of this quiet period. The default quiet period is 250 milliseconds. You can adjust this value based on the characteristics of your project and how frequently changes are made. Longer quiet periods might be suitable for projects with larger codebases or longer build times, while shorter periods might be useful for smaller projects.

Best Practices for Using Gradle Properties

  • Keep Properties Separate from Logic: Properties should store configuration, not logic.
  • Document Your Properties: Clearly document each propertyโ€™s purpose and expected values.
  • Use Consistent Naming Conventions: Follow naming conventions for properties to maintain consistency.

Conclusion

Gradle properties provide an elegant way to configure your project, adapt to different scenarios, and enhance maintainability. By leveraging the power of Gradle properties, you can streamline your development process and build more robust and flexible software projects. With the insights gained from this guide, youโ€™re well-equipped to harness the full potential of Gradle properties for your next project. Happy building!

System Properties in Gradle

A Comprehensive Guide to Demystifying System Properties in Gradle for Streamlined Development

Gradle, a powerful build automation tool, offers a plethora of features that help streamline the development and deployment process. One of these features is system properties, which allow you to pass configuration values to your Gradle build scripts from the command line or other external sources. In this blog, weโ€™ll delve into the concept of system properties in Gradle, understand their significance, and provide practical examples to ensure a crystal-clear understanding.

Understanding System Properties

System properties are a way to provide external configuration to your Gradle build scripts. They enable you to pass key-value pairs to your build scripts when invoking Gradle tasks. These properties can be utilized within the build script to modify its behavior, adapt to different environments, or customize the build process according to your needs.

The syntax for passing system properties to a Gradle task is as follows:

Kotlin
gradle <taskName> -P<propertyName>=<propertyValue>

Here, <taskName> represents the name of the task you want to execute, <propertyName> is the name of the property you want to set, and <propertyValue> is the value you want to assign to the property.

The -P flag is used to pass project properties to a Gradle task when invoking it from the command line.

Kotlin
gradle build -Penvironment=staging

Here, the command is invoking the build task, and itโ€™s passing a project property named environment with the value staging. Inside your build.gradle script, you can access this propertyโ€™s value using project.property('environment').

So, What are system properties inย Gradle?

System properties are key-value pairs that can be used to control the behavior of Gradle. They can be set in a variety of ways, including:

  • On the command line using the -D option
  • In a gradle.properties file
  • In an environment variable

When Gradle starts, it will look for system properties in the following order:

  1. The command line
  2. The gradle.properties file in the userโ€™s home directory
  3. The gradle.properties file in the current project directory
  4. Environment variables

If a system property is defined in multiple places, the value from the first place (command line) it is defined will be used.

How to set system properties inย Gradle

There are three ways to set system properties in Gradle:

Using the -Dย option

You can set system properties on the command line using the -D option. For example, to set the db.url system property to localhost:3306, you would run the following command:

Kotlin
gradle -Ddb.url=localhost:3306

Using a gradle.properties file

You can also set system properties in a gradle.properties file. This file is located in the userโ€™s home directory. To set the db.url system property in a gradle.properties file, you would add the following line to the file:

Kotlin
db.url=localhost:3306

Using an environment variable

You can also set system properties using environment variables. To set the db.url system property using an environment variable, you would set the DB_URL environment variable to localhost:3306.

How to access system properties inย Gradle

Once you have set a system property, you can access it in Gradle using the System.getProperty() method. For example, to get the value of the db.url system property, you would use the following code:

Kotlin
String dbUrl = System.getProperty("db.url");

Difference between project properties and system properties inย Gradle

Project properties and system properties are both key-value pairs that can be used to control the behavior of Gradle. However, there are some important differences between the two:

  • Project properties are specific to a particular project, while system properties are global and can be used by all projects.
  • Project properties are defined in the gradle.properties file in the project directory, while system properties can be defined in a variety of ways, including on the command line, in an environment variable, or in a gradle.properties file in the userโ€™s home directory.
  • Project properties are accessed using the project.getProperty() method, while system properties are accessed using the System.getProperty() method.

Use Cases for System Properties

System properties can be immensely valuable in various scenarios:

  1. Environment-Specific Configurations: You might have different configurations for development, testing, and production environments. System properties allow you to adjust your build process accordingly.
  2. Build Customization: Depending on the requirements of a particular build, you can tweak various parameters through system properties, such as enabling/disabling certain features or modules.
  3. Versioning: You can pass the version number as a system property to ensure that the build uses the correct version throughout the process.
  4. Integration with External Tools: If your build process requires integration with external tools or services, you can provide the necessary connection details or credentials as system properties.

Implementation withย Examples

Letโ€™s explore system properties in action with some examples:

Example 1: Environment-Specific URL

Imagine youโ€™re working on a project where the backend API URL differs for different environments. You can use a system property to specify the API URL when invoking the build.

In your Goovy build.gradle:

Groovy
task printApiUrl {
    doLast {
        def apiUrl = project.property('apiUrl') ?: 'https://default-api-url.com'
        println "API URL: $apiUrl"
    }
}

In your Kotlin DSLbuild.gradle.kts:

Kotlin
tasks.register("printApiUrl") {
    doLast {
        val apiUrl = project.findProperty("apiUrl") as String? ?: "https://default-api-url.com"
        println("API URL: $apiUrl")
    }
}

In the Kotlin DSL, the register function is used to define tasks, and the doLast block is used to specify the taskโ€™s action. The project.findProperty function is used to retrieve the value of a project property, and the as String? cast is used to ensure that the property value is treated as a nullable string. The Elvis operator (?:) is used to provide a default value if the property is not set.

Run the task with a custom API URL:

Kotlin
gradle printApiUrl -PapiUrl=https://staging-api-url.com

Example 2: Build Versioning

Maintaining consistent versioning across different components of your project is crucial. System properties can help you manage this efficiently.

Groovy build.gradle:

Groovy
def versionNumber = project.property('version') ?: '1.0.0'

android {
    defaultConfig {
        versionCode = versionNumber.toInteger()
        versionName = versionNumber
        // Other configurations...
    }
}

Kotlin DSL build.gradle.kts:

Kotlin
val versionNumber: String? = project.findProperty("version") as String? ?: "1.0.0"

android {
    defaultConfig {
        versionCode = versionNumber?.toInt() ?: 1
        versionName = versionNumber
        // Other configurations...
    }
}

Run the build with a specific version:

Kotlin
gradle assembleDebug -Pversion=2.0.1

Example 3: Integration with Credentials

If your project requires access to a remote service during the build process, you can pass the necessary credentials through system properties.

Groovy build.gradle:

Kotlin
task deployToServer {
    doLast {
        def username = project.property('username')
        def password = project.property('password')

        // Deploy logic using the provided credentials...
    }
}

Kotlin DSL build.gradle.kts:

Kotlin
tasks.register("deployToServer") {
    doLast {
        val username: String? = project.findProperty("username") as String?
        val password: String? = project.findProperty("password") as String?

        // Deploy logic using the provided credentials...
    }
}

Run the task with the credentials:

Kotlin
gradle deployToServer -Pusername=myuser -Ppassword=mypassword

Handling Defaultย Values

In the above examples, you might have noticed the use of the project.property or project.findProperty method with a null coalescing operator (?:) to provide default values if the property isnโ€™t passed. This is important to ensure that your build script doesnโ€™t break when a property isnโ€™t provided.

Conclusion

System properties in Gradle offer a versatile mechanism to inject external configuration into your build scripts, promoting flexibility and reusability. By utilizing system properties, you can easily adapt your build process to various environments, customize build parameters, and integrate with external services without modifying the actual build script. This results in a more efficient and maintainable build automation process for your projects.

error: Content is protected !!