Skip to content

Commit 5d885bf

Browse files
authored
Merge pull request #15532 from ribafish/fix-overlapping-task-outputs
Fix overlapping task outputs preventing build cache usage
2 parents 8cff3a5 + a5b18b2 commit 5d885bf

4 files changed

Lines changed: 86 additions & 18 deletions

File tree

build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,19 @@ class GrailsCodeStylePlugin implements Plugin<Project> {
148148
it.toolVersion = project.findProperty('checkstyleVersion')
149149
}
150150

151-
project.tasks.withType(Checkstyle).configureEach {
152-
it.group = 'verification'
153-
it.onlyIf { !project.hasProperty('skipCodeStyle') }
151+
project.tasks.withType(Checkstyle).configureEach { Checkstyle task ->
152+
task.group = 'verification'
153+
task.onlyIf { !project.hasProperty('skipCodeStyle') }
154154

155155
// Redirect XML report output to a single directory to consolidate
156-
// reports across all subprojects into one known location
157-
it.reports.xml.outputLocation.set(
156+
// reports across all subprojects into one known location.
157+
// Include the task name to avoid overlapping outputs when a project has
158+
// multiple source sets (e.g. grails-cache has ast + main).
159+
task.reports.xml.outputLocation.set(
158160
project.extensions.getByType(GrailsCodeStyleExtension)
159161
.reportsDirectory.get()
160162
.dir('checkstyle')
161-
.file(project.name + '.xml')
163+
.file("${project.name}-${task.name}.xml")
162164
)
163165
}
164166
}
@@ -172,18 +174,20 @@ class GrailsCodeStylePlugin implements Plugin<Project> {
172174
it.toolVersion = project.findProperty('codenarcVersion')
173175
}
174176

175-
project.tasks.withType(CodeNarc).configureEach {
176-
it.group = 'verification'
177-
it.onlyIf { !project.hasProperty('skipCodeStyle') }
177+
project.tasks.withType(CodeNarc).configureEach { CodeNarc task ->
178+
task.group = 'verification'
179+
task.onlyIf { !project.hasProperty('skipCodeStyle') }
178180

179181
// Redirect XML report output to a single directory to consolidate
180-
// reports across all subprojects into one known location
181-
it.reports.xml.required.set(true)
182-
it.reports.xml.outputLocation.set(
182+
// reports across all subprojects into one known location.
183+
// Include the task name to avoid overlapping outputs when a project has
184+
// multiple source sets.
185+
task.reports.xml.required.set(true)
186+
task.reports.xml.outputLocation.set(
183187
project.extensions.getByType(GrailsCodeStyleExtension)
184188
.reportsDirectory.get()
185189
.dir('codenarc')
186-
.file(project.name + '.xml')
190+
.file("${project.name}-${task.name}.xml")
187191
)
188192
}
189193
}

build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/SbomPlugin.groovy

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import org.gradle.api.provider.Provider
4343
import org.gradle.api.tasks.bundling.Jar
4444

4545
import java.nio.charset.StandardCharsets
46+
import java.time.Instant
47+
import java.time.ZoneOffset
4648
import java.time.ZonedDateTime
4749
import java.time.format.DateTimeFormatter
4850
import java.time.temporal.ChronoUnit
@@ -212,8 +214,14 @@ class SbomPlugin implements Plugin<Project> {
212214
def bom = new JsonSlurper().parse(f)
213215

214216
// timestamp is not reproducible: https://github.com/CycloneDX/cyclonedx-gradle-plugin/issues/292
215-
ZonedDateTime buildDate = lookupProperty(project, 'buildDate')
216-
bom['metadata']['timestamp'] = DateTimeFormatter.ISO_INSTANT.format(buildDate.truncatedTo(ChronoUnit.SECONDS))
217+
// Use a fixed epoch when SOURCE_DATE_EPOCH is not set so the SBOM is identical between
218+
// builds. This prevents the non-reproducible timestamp from changing the jar checksum
219+
// and cascading cache misses through the compile classpath of downstream projects.
220+
boolean isReproducibleBuild = lookupProperty(project, 'isReproducibleBuild')
221+
ZonedDateTime sbomTimestamp = isReproducibleBuild ?
222+
lookupProperty(project, 'buildDate') as ZonedDateTime :
223+
Instant.EPOCH.atZone(ZoneOffset.UTC)
224+
bom['metadata']['timestamp'] = DateTimeFormatter.ISO_INSTANT.format(sbomTimestamp.truncatedTo(ChronoUnit.SECONDS))
217225

218226
// components[*]
219227
def comps = (bom instanceof Map && bom.components instanceof List) ? bom.components : []
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.gradle.plugin.core
20+
21+
import groovy.transform.CompileStatic
22+
23+
import org.gradle.api.tasks.Internal
24+
import org.gradle.process.CommandLineArgumentProvider
25+
26+
import grails.util.BuildSettings
27+
28+
/**
29+
* Provides the {@code -Dgrails.build.base.dir} system property to forked JVM tasks.
30+
*
31+
* The directory is marked {@link Internal} rather than {@code @InputDirectory} because
32+
* the project directory encompasses task output directories (e.g. {@code build/}).
33+
* Declaring it as {@code @InputDirectory} causes Gradle to report implicit dependency
34+
* violations between the consuming task (e.g. {@code test}) and every task that writes
35+
* into the project directory (e.g. {@code compileIntegrationTestGroovy}, {@code jar}).
36+
* The actual inputs that matter for caching (classpath, source sets) are already tracked
37+
* by their respective tasks.
38+
*/
39+
@CompileStatic
40+
class GrailsAppBaseDirProvider implements CommandLineArgumentProvider {
41+
42+
@Internal
43+
final File appBaseDir
44+
45+
GrailsAppBaseDirProvider(File appBaseDir) {
46+
this.appBaseDir = appBaseDir
47+
}
48+
49+
@Override
50+
Iterable<String> asArguments() {
51+
["-D${BuildSettings.APP_BASE_DIR}=${appBaseDir.absolutePath}".toString()]
52+
}
53+
}

grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsGradlePlugin.groovy

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,10 @@ class GrailsGradlePlugin extends GroovyPlugin {
188188
}
189189

190190
private void configureGroovyCompiler(Project project) {
191-
Provider<RegularFile> groovyCompilerConfigFile = project.layout.buildDirectory.file('grailsGroovyCompilerConfig.groovy')
192-
193191
project.tasks.withType(GroovyCompile).configureEach { GroovyCompile c ->
192+
// Use a task-specific config file to avoid overlapping outputs when multiple
193+
// GroovyCompile tasks exist in the same project (e.g. compileGroovy, compileTestGroovy).
194+
Provider<RegularFile> groovyCompilerConfigFile = project.layout.buildDirectory.file("grailsGroovyCompilerConfig-${c.name}.groovy")
194195
c.outputs.file(groovyCompilerConfigFile)
195196

196197
Closure<String> userScriptGenerator = getGroovyCompilerScript(c, project)
@@ -537,7 +538,9 @@ class GrailsGradlePlugin extends GroovyPlugin {
537538
task.systemProperty(Metadata.APPLICATION_NAME, project.name)
538539
task.systemProperty(Metadata.APPLICATION_VERSION, (project.version instanceof Serializable ? project.version : project.version.toString()))
539540
task.systemProperty(Metadata.APPLICATION_GRAILS_VERSION, grailsVersion)
540-
task.systemProperty(BuildSettings.APP_BASE_DIR, project.projectDir.absolutePath)
541+
// Use a CommandLineArgumentProvider so that the absolute project directory path
542+
// is normalized for build cache relocatability (PathSensitivity.RELATIVE).
543+
task.jvmArgumentProviders.add(new GrailsAppBaseDirProvider(project.projectDir))
541544
task.systemProperty(BuildSettings.PROJECT_TARGET_DIR, project.layout.buildDirectory.get().asFile.name)
542545
task.systemProperty(Environment.KEY, defaultGrailsEnv)
543546
task.systemProperty(Environment.FULL_STACKTRACE, System.getProperty(Environment.FULL_STACKTRACE) ?: '')

0 commit comments

Comments
 (0)