小能豆

Gradle continuous build behaves odd

javascript

Gradle is a magic box for me. I often don’t get it and always do try and error. I use gradle 8.1 and groovy scripts. I have this small task

import java.time.Instant

tasks.register('continuous-build') {
    dependsOn tasks.compileJava
    onlyIf { !compileJava.state.upToDate }
    logger.lifecycle("Job executed at " + Instant.now())
    doLast {
        def file = new File(projectDir, "build/classes/java/main/.reloadTrigger");
        file.createNewFile();
        file.setLastModified(Instant.now().toEpochMilli())
        logger.lifecycle("This message is never seen")
    }
}

I want this task to run with –continuous-build and just touch a file if it’s done compiling.

this is what I do:

Run ./gradlew continuous-build –continuous

$ ./gradlew continuous-build --continuous
Reload triggered at 2023-11-10T11:48:50.446501720Z   
BUILD SUCCESSFUL in 1s
6 actionable tasks: 1 executed, 5 up-to-date
Waiting for changes to input files... (ctrl-d to exit)

Make a small change to Main.java which compiles fine

modified: [...]Main.java
Change detected, executing build...
Reload triggered at 2023-11-10T11:49:02.870526619Z
BUILD SUCCESSFUL in 1s
6 actionable tasks: 1 executed, 2 from cache, 3 up-to-date
Waiting for changes to input files... (ctrl-d to exit)

After this the file timestamp is changed. That is what I want.

Make a small change to Main.java which compiles with error

modified: [...]Main.java
Change detected, executing build...   
Reload triggered at 2023-11-10T11:49:09.303034432Z    
> Task :compileJava FAILED
[...]Main.java:22: error: <identifier> expected
    publi static final String ANSWER = "42";
         ^
1 error
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileJava'.
> Compilation failed; see the compiler error output for details.   
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.    
* Get more help at https://help.gradle.org    
BUILD FAILED in 1s
3 actionable tasks: 2 executed, 1 up-to-date    
Waiting for changes to input files... (ctrl-d to exit)

After this the file timestamp is NOT changed. That is what I want as it compiled with an error.

Make a small change to Main.java which compiles fine again

modified: [...]Main.java
Change detected, executing build...
Reload triggered at 2023-11-10T11:49:14.192599979Z
BUILD SUCCESSFUL in 1s
6 actionable tasks: 1 executed, 5 up-to-date
Waiting for changes to input files... (ctrl-d to exit)

After this the file timestamp is NOT changed anymore even the compilation succeeded. How can I achieve a file touch cmd if compileJava was successful.

And why is the second logging message never seen?

PS I have shortened the output a little bit to make it more readable


阅读 84

收藏
2023-11-20

共1个答案

小能豆

The reason why the file timestamp is not changed after the second successful compilation is that Gradle’s upToDate check considers both the inputs and outputs of the task. If the inputs and outputs of a task have not changed since the last successful execution, Gradle considers the task up-to-date and skips its execution.

In your case, the compileJava task has not changed (since you’re using --continuous and it’s watching for changes in source files), and its outputs are still valid, so Gradle skips the task execution, and consequently, your continuous-build task is not executed.

To work around this, you can introduce a custom input property that changes when you want to force the compileJava task to execute. Here’s an updated version of your script:

import java.time.Instant

tasks.register('continuous-build') {
    dependsOn tasks.compileJava
    onlyIf { !compileJava.state.upToDate }
    inputs.property("forceCompile", Instant.now().toEpochMilli())
    logger.lifecycle("Reload triggered at " + Instant.now())
    doLast {
        def file = new File(projectDir, "build/classes/java/main/.reloadTrigger")
        file.createNewFile()
        file.setLastModified(Instant.now().toEpochMilli())
        logger.lifecycle("This message is now seen")
    }
}

By introducing the forceCompile input property, you ensure that the continuous-build task is considered out-of-date whenever you want to force the compileJava task to execute. This should address the issue you’re facing.

Regarding the second logging message not being seen, it’s because the doLast block is only executed if the task’s work action executes. In your case, since the task is considered up-to-date, the doLast block is skipped. If you want the message to be seen regardless, you can move it outside the doLast block:

tasks.register('continuous-build') {
    dependsOn tasks.compileJava
    onlyIf { !compileJava.state.upToDate }
    inputs.property("forceCompile", Instant.now().toEpochMilli())
    logger.lifecycle("Reload triggered at " + Instant.now())
}

// Move this outside the task definition
tasks.register('continuous-build-message') {
    doLast {
        logger.lifecycle("This message is now seen")
    }
}

// Add a dependency to 'continuous-build-message' in 'continuous-build'
tasks.named('continuous-build') {
    dependsOn 'continuous-build-message'
}

Now, the message will be logged even if the continuous-build task is considered up-to-date.

2023-11-20