Properties (Gradle)

Gradle Properties: Key Concepts and Usage

Gradle properties allow you to define and configure settings that can be used across your build scripts. These properties can be defined globally for the entire project or locally within tasks. They are commonly used to store configuration values, such as version numbers, file paths, and custom task settings.

Gradle Properties:

Project Properties (gradle.properties)

  • Project Properties are global to the project and can be defined:
    • In the gradle.properties file
    • OR passed via the command line.
      • Command line properties override gradle.properties.
  • Accessed in build scripts using project.propertyName.

Example (gradle.properties file):

version=1.0.0

Access in build script (build.gradle.kts):

tasks.register("go") {
    doLast {
        println("Project version: ${project.version}")
    }
}
Glass thought Sandbox Snapshot

Command to reproduce:

gt.sandbox.checkout.commit ac59f88 \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew go"

Recorded output of command:


> Task :lib:go
Project version: 2.3.1

BUILD SUCCESSFUL in 438ms
1 actionable task: 1 executed
Gradle command line project properties

To pass project properties via the command line in Gradle, you can use the -P flag followed by the property name and value. This allows you to override or define properties at runtime without modifying the gradle.properties file.

Example of Passing a Project Property via the Command Line:

You can pass the version property like this:

Glass thought Sandbox Snapshot

Command to reproduce:

gt.sandbox.checkout.commit d043a3a \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew go -Pversion=9.9"

Recorded output of command:


> Task :lib:go
Project version: 9.9

BUILD SUCCESSFUL in 360ms
1 actionable task: 1 executed

This will override any version property defined in the gradle.properties file and set the version for this specific build invocation.

Key Points:

  • Use -PpropertyName=value to pass properties via the command line.
  • Properties passed this way override those defined in the gradle.properties file.
  • Access the property in your build script using project.propertyName.

Task properties

Gradle allows you to define task-specific properties that can be configured dynamically. These properties can be set and modified during the Configuration phase and accessed during the Execution phase.

Example of Task Property:

tasks.register("myTask") {
    // Define a custom task property
    val greeting = project.objects.property(String::class.java)

    // Configure the property during the Configuration phase
    greeting.set("Hello, Gradle!")

    doLast {
        // Access and use the property during the Execution phase
        println(greeting.get())
    }
}
Glass thought Sandbox Snapshot

Command to reproduce:

gt.sandbox.checkout.commit 4beb72b \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew myTask"

Recorded output of command:


> Task :lib:myTask
Hello, Gradle!

BUILD SUCCESSFUL in 359ms
1 actionable task: 1 executed

In this example:

  • Task property greeting is defined and configured during the Configuration phase (greeting.set()).
  • The property value is accessed and used during the Execution phase (greeting.get()).

Why use task properties and not variables

Why use task properties and not variables

The use of task properties in Gradle, like project.objects.property(String::class.java), provides important advantages over using regular variables within a task block. Here are the key reasons to use task properties instead of simple variables:

1. Gradle’s Incremental Build Support:

  • Task properties allow Gradle to track inputs and outputs, enabling incremental builds. This means that if the property value hasn't changed, Gradle can avoid re-executing the task, improving build performance.
  • Regular variables like someVariable are not tracked by Gradle, so changes to these won't trigger task re-execution or up-to-date checks.

2. Deferred Configuration:

  • Task properties are part of Gradle’s configuration system and can be configured after the task is defined (even from the command line or other tasks). This makes them more flexible, allowing dynamic behavior depending on the project state.
  • Regular variables are evaluated immediately when the task is configured, and their values are fixed during the configuration phase.

3. Lazy Evaluation:

  • With task properties, you can benefit from lazy evaluation, meaning the property values are only evaluated when accessed in the execution phase. This can help with more complex tasks where you want values to be set dynamically based on other conditions in the build.
  • In contrast, regular variables are eagerly evaluated when the task is configured.

4. Task Serialization:

  • When you define task properties, Gradle can serialize these properties, which is essential when running builds in parallel or in distributed build environments (like using Gradle’s build cache).
  • Regular variables aren’t part of Gradle’s internal task model, so they can’t be tracked, serialized, or cached.

Example Comparison:

Using a Task Property:

tasks.register("myTask") {
    // Define a custom task property
    val greeting = project.objects.property(String::class.java)

    // Configure the property during the Configuration phase
    greeting.set("Hello, Gradle!")

    doLast {
        println(greeting.get())
    }
}
  • Advantages: greeting is tracked by Gradle, can be lazily configured, and can enable incremental build support.

Using a Regular Variable:

tasks.register("myTask") {
    val greeting = "Hello, Gradle!"

    doLast {
        println(greeting)
    }
}
  • Disadvantages: greeting is not tracked by Gradle, so if you change its value or input/output files, Gradle won't know and can't optimize the build. It's also evaluated immediately during configuration.

5. External Configuration:

  • Task properties can be set or overridden via external configurations like the command line or other build scripts, adding to their flexibility.
  • Regular variables are static and cannot be reconfigured dynamically from outside the task definition.

When to Use Task Properties:

  • When tasks need incremental build support, such as for tasks that depend on external input or outputs.
  • When the task properties need to be configured dynamically, for example, based on user input, environment variables, or project states.
  • For tasks running in parallel or in distributed environments, where Gradle needs to serialize and track task state.

Conclusion:

While you can use regular variables for simple cases, task properties offer significant advantages in terms of incremental builds, lazy evaluation, external configuration, and task state tracking, making them crucial for complex, efficient Gradle builds.

Not tracked by gradle

These properties are not tracked by gradle and can cause tasks that should be-reran to be skipped when gradle runs its UP-TO-DATE Checks (Task Avoidance):

System Properties

System Properties:

  • System properties can be defined at runtime by passing them as JVM arguments. They can be accessed using System.getProperty(). Note: System Properties are #not-tracked-by-gradle .

    Example:

    gradle build -DmyProperty=value
    

    Access in build script:

    val myProperty = System.getProperty("myProperty")
    println("System property: $myProperty")
    

Not tracked by gradle

Not tracked by gradle

#not-tracked-by-gradle

Correct, system properties are not tracked by Gradle by default. Just like environment variables, changes to system properties won't automatically trigger Gradle tasks to be re-executed, and they aren’t included in Gradle’s up-to-date checking or incremental build system.

Why System Properties Aren’t Tracked:

  • System properties are typically set outside of the Gradle build process (e.g., through JVM arguments or command line options like -DmyProperty=value), so Gradle does not track them as part of its normal task input/output model.
  • If a task depends on system properties, but those properties change between builds, Gradle won't automatically detect this and re-execute the task unless you explicitly tell Gradle to treat those properties as inputs.

Workaround: Manually Declare System Properties as Task Inputs

To ensure Gradle considers system properties in its up-to-date checks and re-executes tasks when system properties change, you can declare them as task inputs manually, similar to environment variables.

Example: Tracking a System Property as a Task Input

tasks.register("printSysProp") {
    val sysProp = System.getProperty("myProperty")

    // Declare the system property as an input
    inputs.property("myProperty", sysProp)
    outputs.file("build/output.txt")    // Output file

    doLast {
        val outputFile = file("build/output.txt")
        outputFile.writeText("System property: $sysProp")

        println("doLast is running... System property: $sysProp")
    }
}
Glass thought Sandbox Snapshot

Command to reproduce:

gt.sandbox.checkout.commit 05d2b11 \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "gs"

Recorded output of command:

On branch sandbox-commit-d7ac1d1
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   lib/build.gradle.kts

no changes added to commit (use "git add" and/or "git commit -a")

In this example:

  • The system property myProperty is declared as a task input using inputs.property().
  • Gradle will now track this property, and if it changes, the task will be re-executed.

How to Pass a System Property via Command Line:

./gradlew printSysProp -DmyProperty=someValue

Key Points:

  • System properties are not tracked by default, meaning they won’t trigger task re-execution if they change.
  • To ensure a task is re-executed when a system property changes, you need to manually declare the system property as a task input using inputs.property().
  • Once declared as a task input, Gradle will include the system property in its up-to-date checks.

Conclusion:

Just like environment variables, system properties are not automatically tracked by Gradle. However, you can manually declare system properties as task inputs if they are critical to the task’s behavior and you want Gradle to re-run the task when they change.

Environment Variables

Environment Variables:

  • These are properties defined outside of Gradle (in the environment) and can be accessed using System.getenv(). Note: Environment Variables are #not-tracked-by-gradle .

Example:

export MY_ENV_VAR="some_value"
gradle build

Access in build script:

val envVar = System.getenv("MY_ENV_VAR")
println("Environment variable: $envVar")

Not tracked by gradle

Not tracked by gradle

#not-tracked-by-gradle

Environment variables are NOT automatically tracked by Gradle. This means that changes to environment variables won’t trigger tasks to be re-executed, and they won’t be considered in Gradle’s up-to-date checking or incremental build system.

Why Environment Variables Aren’t Tracked:

  • Gradle’s incremental build support is based on its ability to track task inputs and outputs, such as files or task properties explicitly defined within the build script.
  • Environment variables exist outside of Gradle’s scope, so it doesn’t automatically know about changes to them. As a result, tasks depending on environment variables can still be considered up-to-date even if the environment variables change.

Example: Environment Variable is not Tracked

Glass thought Sandbox Snapshot & Explanation

In this example we will run the following shell, which will vary the environment variable MY_ENV_VAR between two values and run the task saveEnvVarToFile which writes the environment variable to a file.

export MY_ENV_VAR=HI4 \
&& ./gradlew saveEnvVarToFile --console=plain \
&& cat ${GLASSTHOUGHT_SANDBOX:?}/lib/build/output.txt \
&& export MY_ENV_VAR=HI5 \
&& ./gradlew saveEnvVarToFile --console=plain \
&& cat ${GLASSTHOUGHT_SANDBOX:?}/lib/build/output.txt

This is the gradle task that writes the environment variable to a file:

tasks.register("saveEnvVarToFile") {
    val envVar = System.getenv("MY_ENV_VAR")

    // Without declaring the property as input and relying on env variable.
    // inputs.property("MY_ENV_VAR", envVar)
    outputs.file("build/output.txt")    // Output file

    doLast {
        val outputFile = file("build/output.txt")
        outputFile.writeText("Environment property: $envVar \n")

        println("Environment variable: $envVar")
    }
}

Notice that we have explicitly commented out the inputs.property("MY_ENV_VAR", envVar) line, which would have declared the environment variable as a task input. Now when we run the 2nd run even though the environment variable has changed, the task is still considered up-to-date.

Command to reproduce:

gt.sandbox.checkout.commit 5b726c5 \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "export MY_ENV_VAR=HI4 && ./gradlew saveEnvVarToFile --console=plain && cat ${GLASSTHOUGHT_SANDBOX:?}/lib/build/output.txt && export MY_ENV_VAR=HI5 && ./gradlew saveEnvVarToFile --console=plain && cat ${GLASSTHOUGHT_SANDBOX:?}/lib/build/output.txt"

Recorded output of command:

> Task :lib:saveEnvVarToFile UP-TO-DATE

BUILD SUCCESSFUL in 424ms
1 actionable task: 1 up-to-date
Environment property: HI4 
> Task :lib:saveEnvVarToFile UP-TO-DATE

BUILD SUCCESSFUL in 337ms
1 actionable task: 1 up-to-date
Environment property: HI4 

Workaround: Manually Declare Environment Variables as Inputs

If you want Gradle to consider environment variables when determining whether a task is up-to-date, you can declare them as task inputs. This way, Gradle will re-execute the task when the environment variable changes.

Example: Tracking an Environment Variable as a Task Input

tasks.register("saveEnvVarToFile") {
    val envVar = System.getenv("MY_ENV_VAR")

    // Declare the environment variable as input:
    inputs.property("MY_ENV_VAR", envVar)
    outputs.file("build/output.txt")    // Output file

    doLast {
        val outputFile = file("build/output.txt")
        outputFile.writeText("Environment variable saved in file: $envVar \n")

        println("Environment variable: $envVar")
    }
}
Glass thought Sandbox Snapshot & Explanation

Now when we run

export MY_ENV_VAR=HI4 \
&& ./gradlew saveEnvVarToFile --console=plain \
&& cat ${GLASSTHOUGHT_SANDBOX:?}/lib/build/output.txt \
&& export MY_ENV_VAR=HI5 \
&& ./gradlew saveEnvVarToFile --console=plain \
&& cat ${GLASSTHOUGHT_SANDBOX:?}/lib/build/output.txt

The task will re-execute when the environment variable changes.

Command to reproduce:

gt.sandbox.checkout.commit 18b8810 \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "export MY_ENV_VAR=HI4 && ./gradlew saveEnvVarToFile --console=plain && cat ${GLASSTHOUGHT_SANDBOX:?}/lib/build/output.txt && export MY_ENV_VAR=HI5 && ./gradlew saveEnvVarToFile --console=plain && cat ${GLASSTHOUGHT_SANDBOX:?}/lib/build/output.txt"

Recorded output of command:


> Task :lib:saveEnvVarToFile
Environment variable: HI4

BUILD SUCCESSFUL in 350ms
1 actionable task: 1 executed
Environment variable saved in file: HI4 

> Task :lib:saveEnvVarToFile
Environment variable: HI5

BUILD SUCCESSFUL in 342ms
1 actionable task: 1 executed
Environment variable saved in file: HI5 

Gotcha

The gotcha with this approach is that we need to know which tasks depend on which environment variables and declare them as inputs manually.

Key Points:

  • Environment variables are not tracked by default, so changes to them won’t trigger task re-execution.
  • To ensure a task re-executes when an environment variable changes, declare the environment variable as a task input using inputs.property().
  • By declaring it as an input, Gradle will treat the environment variable as part of the task’s state and include it in the up-to-date checks.

Conclusion:

While Gradle doesn’t automatically track environment variables, you can make Gradle aware of them by manually declaring them as task inputs. This ensures that tasks depending on environment variables will re-run when those variables change.

Key Points:

  • Global properties can be defined in gradle.properties, passed via command line, or set as environment variables.
  • Task properties are specific to individual tasks and can be dynamically configured.
  • System properties and Environment Variables are not tracked by Gradle and can cause tasks to be skipped unintentionally.

Children
  1. Environment Variables (In Gradle)
  2. Project Properties
  3. System Property (in Gradle)
  4. Task Properties

Backlinks