Help with upgrading Task from 6.9 to 7.6

Hi, I am in the process of upgrading a Spring Boot Gradle project, I have gotten it from Java 8 to 11. Now I need to upgrade Gradle to get the project built and running on Java 17.

It contains 4 tasks, that performs text replacement of application version number for the build. When compiling it outputs the following message, one for each task:

Task :filter1
Execution optimizations have been disabled for task ':filter1' to ensure correctness due to the following reasons:

Gradle detected a problem with the following location: '/.../build/resources/main/templates'. Reason: Task ':bootJarMainClassName' uses this output of task ':filter1' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed. Please refer to 
 for more details about this problem

The tasks are defined as follows:

import org.apache.tools.ant.filters.ReplaceTokens

task filter1(type: Copy) {
    from 'src/main/resources/templates/layout.html'
    into "$buildDir/resources/main/templates"
    filter(ReplaceTokens, tokens: [VERSION: version])
    filteringCharset = 'UTF-8'
}
task filter2(type: Copy) {
    from 'src/main/resources/templates/layout2.html'
    into "$buildDir/resources/main/templates"
    filter(ReplaceTokens, tokens: [VERSION: version])
    filteringCharset = 'UTF-8'
}
task filter3(type: Copy) {
    from 'src/main/resources/templates/layout_small.html'
    into "$buildDir/resources/main/templates"
    filter(ReplaceTokens, tokens: [VERSION: version])
    filteringCharset = 'UTF-8'
}
task filter4(type: Copy) {
    from 'src/main/resources/banner.txt'
    into "$buildDir/resources/main"
    filter(ReplaceTokens, tokens: [VERSION: version])
    filteringCharset = 'UTF-8'
}

build.dependsOn ([filter1,filter2,filter3,filter4])
bootJar.dependsOn ([filter1,filter2,filter3,filter4])

I am not very familiar with build script programming, so I dont understand what the problem actually is. Other than the fact the syntax has changed between version 6 and 7.

Can anybody explain to me whats going on, and what needs to be done to fix it?

Since the tasks are tied to the Spring Boot Plugin the following might also be of relevance:


// located in build.gradle after the 'import' statement and before the task definitions:


buildscript {
    ext {
        bootVersion = '2.7.18'
    }

    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:$bootVersion"
        classpath 'org.apache.ant:ant:1.7.1'
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'groovy'




I am not very familiar with build script programming, so I dont understand what the problem actually is. Other than the fact the syntax has changed between version 6 and 7.

The syntax did not change.
And also the rules did not change.
Just Gradle got better in discovering that you did a bad setup. :slight_smile:

The problem is, that your 4 tasks all have overlapping outputs.
And also they have overlapping outputs with processResources as you pollute that tasks outputs with your tasks.
They all have the directory $buildDir/resources/main/templates as output directory, and so all files in that directory are output of all those 4 tasks.
And now if another task has one of the files in that directory as input, Gradle sees that those tasks have the file as output, but your consuming task does not have a dependency on those tasks, complaining because the outputs of the task will be stale or absent if the task did not run as they are outputs of those tasks.

No two tasks should ever have overlapping outputs, or you get major problems of various kinds.

Besides that, you explicit dependsOn calls are also all bad practice and also a side-effect of your other setup, also task <someName> should not be used as it does not leverage task-configuration avoidance, wasting your time when execute builds.

What you actually wanted to do is replacing your whole snippet simply by

tasks.processResources {
   filteringCharset = 'UTF-8'
   filesMatching([
         'templates/layout.html',
         'templates/layout2.html',
         'templates/layout_small.html',
         'banner.txt'
   ]) {
      filter(ReplaceTokens, tokens: [VERSION: version])
   }
}

PS: Also your apply plugin and manual classpath dependencies should not be done. Just apply your plugins in the plugins { ... } block. And Ant is a first-class citizen in Gradle anyway, so no need to add a buildscript-classpath-dependency either.

Thank you for your help. I have a couple of followup questions.

I understand that processresources would also copy the files from src’s resources to the builddir, so that is duplication. But how does the four tasks have overlapping outputs? they are processing different files. (even though it goes into the same directory.)

There is no copy command in your example, so I suppose this task is performed before the original processResources starts copying the files?

How is the above task linked into the right spot in the chain of tasks in the build, as there is no dependesOn or similar?

I understand that processresources would also copy the files from src’s resources to the builddir, so that is duplication. But how does the four tasks have overlapping outputs? they are processing different files. (even though it goes into the same directory.)

Five tasks.
filter1-4 + processResources

The output of a Copy task is not the copied file, but the whole target directory.
That means that filter1-3 have the same outputs (well, depends on what was executed in which oder, but at least the same directory),
filter4 and processResources have the parent directory of the others as outputs so also contain the templates directory.
So all 5 tasks have overlapping outputs.

If you would instead write

task filter1 {
   inputs.file('src/main/resources/templates/layout.html')
   inputs.property('version', version)
   outputs.file("$buildDir/resources/main/templates/layout.html")
   doLast {
      copy {
         from 'src/main/resources/templates/layout.html'
         into "$buildDir/resources/main/templates"
         filter(ReplaceTokens, tokens: [VERSION: version])
         filteringCharset = 'UTF-8'
      }
   }
}

and the other three tasks accordingly, then at least the filter1-4 tasks would not have overlapping outputs, as you do no longer have a task of type Copy but just use a copy { ... } as implementation detail and declared the one input file and the one output file as the sole input and output.

But even then, all would have overlapping outputs with processResources, as they still pollute the output directory of processResources.

Besides that the tasks are simply absolutely superfluous, as you already have a task that copies the resources there and just need to configure the replacing for it as shown.

There is no copy command in your example, so I suppose this task is performed before the original processResources starts copying the files?

I don’t get what you mean.
The processResources task is already syncing the files over.
Previously, you just copy the files again with doing the replacing.
Which also just accidentally worked sometimes anyway, as it depended on task execution order that was not defined, so it could have happened that the filter* tasks run first and the processResources later, overwriting the files again with the original versions.

What I gave you just configures the processResources task to do the replacing while syncing over the files, which is all you actually wanted to do or should have done.

How is the above task linked into the right spot in the chain of tasks in the build, as there is no dependesOn or similar?

I just configure an existing task that is already configured and linked properly.
I only tell it to do the replacing.

Besides that, any “dependesOn or similar” that does not have a lifecycle-task on the left-hand side is a code-smell and usually a sign that you did not properly wire task outputs to task inputs which comes with automatic implicit task dependencies where necessary. You should almost never wire tasks together with dependsOn manually, at least not if one task needs output files of another task as inputs.