Disable cache-cleanup on config-cache hit (#295)

Fixes #19
This commit is contained in:
Daz DeBoer 2024-07-18 22:32:30 -06:00 committed by GitHub
commit 7a630bc41b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 216 additions and 136 deletions

View File

@ -43,6 +43,7 @@ jobs:
uses: ./setup-gradle uses: ./setup-gradle
with: with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-write-only: true # Ensure we start with a clean cache entry
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6 gradle-version: 8.6
- name: Groovy build with configuration-cache enabled - name: Groovy build with configuration-cache enabled
@ -52,6 +53,7 @@ jobs:
verify-build-groovy: verify-build-groovy:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_1
needs: seed-build-groovy needs: seed-build-groovy
strategy: strategy:
fail-fast: false fail-fast: false
@ -64,6 +66,47 @@ jobs:
- name: Initialize integ-test - name: Initialize integ-test
uses: ./.github/actions/init-integ-test uses: ./.github/actions/init-integ-test
- name: Setup Java to ensure consistency
uses: actions/setup-java@v4
with:
distribution: 'liberica'
java-version: 17
- name: Setup Gradle
uses: ./setup-gradle
with:
cache-read-only: false
cache-cleanup: on-success
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6
- name: Groovy build with configuration-cache enabled
id: execute
working-directory: .github/workflow-samples/groovy-dsl
run: gradle test --configuration-cache
- name: Verify configuration-cache hit
shell: bash
run: |
if [ -e ".github/workflow-samples/groovy-dsl/task-configured.txt" ]; then
echo "Configuration cache was not used - task was configured unexpectedly"
exit 1
fi
# Ensure that cache-cleanup doesn't remove all necessary files
verify-no-cache-cleanup-groovy:
env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_2
needs: verify-build-groovy
strategy:
fail-fast: false
matrix:
os: ${{fromJSON(inputs.runner-os)}}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Setup Java to ensure consistency - name: Setup Java to ensure consistency
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
@ -79,19 +122,19 @@ jobs:
id: execute id: execute
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: gradle test --configuration-cache run: gradle test --configuration-cache
- name: Check that configuration-cache was used - name: Verify configuration-cache hit
uses: actions/github-script@v7 shell: bash
with: run: |
script: | if [ -e ".github/workflow-samples/groovy-dsl/task-configured.txt" ]; then
const fs = require('fs') echo "Configuration cache was not used - task was configured unexpectedly"
if (fs.existsSync('.github/workflow-samples/groovy-dsl/task-configured.txt')) { exit 1
core.setFailed('Configuration cache was not used - task was configured unexpectedly') fi
}
# Check that the build can run when no extracted cache entries are restored # Check that the build can run when no extracted cache entries are restored
gradle-user-home-not-fully-restored: gradle-user-home-not-fully-restored:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-groovy
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_x
needs: seed-build-groovy needs: seed-build-groovy
strategy: strategy:
fail-fast: false fail-fast: false
@ -144,6 +187,7 @@ jobs:
uses: ./setup-gradle uses: ./setup-gradle
with: with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches cache-read-only: false # For testing, allow writing cache entries on non-default branches
cache-write-only: true # Ensure we start with a clean cache entry
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
gradle-version: 8.6 gradle-version: 8.6
- name: Execute 'help' with configuration-cache enabled - name: Execute 'help' with configuration-cache enabled
@ -152,7 +196,8 @@ jobs:
modify-build-kotlin: modify-build-kotlin:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-kotlin-modified GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-kotlin
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_1
needs: seed-build-kotlin needs: seed-build-kotlin
strategy: strategy:
fail-fast: false fail-fast: false
@ -183,7 +228,8 @@ jobs:
# Test restore configuration-cache from the third build invocation # Test restore configuration-cache from the third build invocation
verify-build-kotlin: verify-build-kotlin:
env: env:
GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-kotlin-modified GRADLE_BUILD_ACTION_CACHE_KEY_JOB: restore-cc-kotlin
GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION: ${{github.sha}}_2
needs: modify-build-kotlin needs: modify-build-kotlin
strategy: strategy:
fail-fast: false fail-fast: false
@ -211,12 +257,10 @@ jobs:
id: execute id: execute
working-directory: .github/workflow-samples/kotlin-dsl working-directory: .github/workflow-samples/kotlin-dsl
run: gradle test --configuration-cache run: gradle test --configuration-cache
- name: Check that configuration-cache was used - name: Verify configuration-cache hit
uses: actions/github-script@v7 shell: bash
with: run: |
script: | if [ -e ".github/workflow-samples/kotlin-dsl/task-configured.txt" ]; then
const fs = require('fs') echo "Configuration cache was not used - task was configured unexpectedly"
if (fs.existsSync('.github/workflow-samples/kotlin-dsl/task-configured.txt')) { exit 1
core.setFailed('Configuration cache was not used - task was configured unexpectedly') fi
}

View File

@ -8,6 +8,7 @@ export interface BuildResult {
get gradleVersion(): string get gradleVersion(): string
get gradleHomeDir(): string get gradleHomeDir(): string
get buildFailed(): boolean get buildFailed(): boolean
get configCacheHit(): boolean
get buildScanUri(): string get buildScanUri(): string
get buildScanFailed(): boolean get buildScanFailed(): boolean
} }
@ -23,6 +24,10 @@ export class BuildResults {
return this.results.some(result => result.buildFailed) return this.results.some(result => result.buildFailed)
} }
anyConfigCacheHit(): boolean {
return this.results.some(result => result.configCacheHit)
}
uniqueGradleHomes(): string[] { uniqueGradleHomes(): string[] {
const allHomes = this.results.map(buildResult => buildResult.gradleHomeDir) const allHomes = this.results.map(buildResult => buildResult.gradleHomeDir)
return Array.from(new Set(allHomes)) return Array.from(new Set(allHomes))
@ -32,7 +37,9 @@ export class BuildResults {
export function loadBuildResults(): BuildResults { export function loadBuildResults(): BuildResults {
const results = getUnprocessedResults().map(filePath => { const results = getUnprocessedResults().map(filePath => {
const content = fs.readFileSync(filePath, 'utf8') const content = fs.readFileSync(filePath, 'utf8')
return JSON.parse(content) as BuildResult const buildResult = JSON.parse(content) as BuildResult
addScanResults(filePath, buildResult)
return buildResult
}) })
return new BuildResults(results) return new BuildResults(results)
} }
@ -42,7 +49,7 @@ export function markBuildResultsProcessed(): void {
} }
function getUnprocessedResults(): string[] { function getUnprocessedResults(): string[] {
const buildResultsDir = path.resolve(process.env['RUNNER_TEMP']!, '.build-results') const buildResultsDir = path.resolve(process.env['RUNNER_TEMP']!, '.gradle-actions', 'build-results')
if (!fs.existsSync(buildResultsDir)) { if (!fs.existsSync(buildResultsDir)) {
return [] return []
} }
@ -57,6 +64,22 @@ function getUnprocessedResults(): string[] {
}) })
} }
function addScanResults(buildResultsFile: string, buildResult: BuildResult): void {
const buildScansDir = path.resolve(process.env['RUNNER_TEMP']!, '.gradle-actions', 'build-scans')
if (!fs.existsSync(buildScansDir)) {
return
}
const buildScanResults = path.resolve(buildScansDir, path.basename(buildResultsFile))
if (fs.existsSync(buildScanResults)) {
const content = fs.readFileSync(buildScanResults, 'utf8')
const scanResults = JSON.parse(content)
Object.assign(buildResult, scanResults)
}
return
}
function isProcessed(resultFile: string): boolean { function isProcessed(resultFile: string): boolean {
const markerFile = `${resultFile}.processed` const markerFile = `${resultFile}.processed`
return fs.existsSync(markerFile) return fs.existsSync(markerFile)

View File

@ -19,6 +19,9 @@ export const DEFAULT_CLEANUP_ENABLED_REASON = `[Cache cleanup](https://github.co
export const CLEANUP_DISABLED_DUE_TO_FAILURE = export const CLEANUP_DISABLED_DUE_TO_FAILURE =
'[Cache cleanup was disabled due to build failure](https://github.com/gradle/actions/blob/v3/docs/setup-gradle.md#enabling-cache-cleanup). Use `cache-cleanup: always` to override this behavior.' '[Cache cleanup was disabled due to build failure](https://github.com/gradle/actions/blob/v3/docs/setup-gradle.md#enabling-cache-cleanup). Use `cache-cleanup: always` to override this behavior.'
export const CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT =
'[Cache cleanup was disabled due to configuration-cache reuse](https://github.com/gradle/actions/blob/v3/docs/setup-gradle.md#enabling-cache-cleanup). This is expected.'
/** /**
* Collects information on what entries were saved and restored during the action. * Collects information on what entries were saved and restored during the action.
* This information is used to generate a summary of the cache usage. * This information is used to generate a summary of the cache usage.

View File

@ -1,5 +1,10 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {CacheListener, EXISTING_GRADLE_HOME, CLEANUP_DISABLED_DUE_TO_FAILURE} from './cache-reporting' import {
CacheListener,
EXISTING_GRADLE_HOME,
CLEANUP_DISABLED_DUE_TO_FAILURE,
CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT
} from './cache-reporting'
import {GradleUserHomeCache} from './gradle-user-home-cache' import {GradleUserHomeCache} from './gradle-user-home-cache'
import {CacheCleaner} from './cache-cleaner' import {CacheCleaner} from './cache-cleaner'
import {DaemonController} from '../daemon-controller' import {DaemonController} from '../daemon-controller'
@ -90,7 +95,10 @@ export async function save(
await daemonController.stopAllDaemons() await daemonController.stopAllDaemons()
if (cacheConfig.isCacheCleanupEnabled()) { if (cacheConfig.isCacheCleanupEnabled()) {
if (cacheConfig.shouldPerformCacheCleanup(buildResults.anyFailed())) { if (buildResults.anyConfigCacheHit()) {
core.info('Not performing cache-cleanup due to config-cache reuse')
cacheListener.setCacheCleanupDisabled(CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT)
} else if (cacheConfig.shouldPerformCacheCleanup(buildResults.anyFailed())) {
cacheListener.setCacheCleanupEnabled() cacheListener.setCacheCleanupEnabled()
await performCacheCleanup(gradleUserHome) await performCacheCleanup(gradleUserHome)
} else { } else {

View File

@ -1,23 +1,27 @@
import org.gradle.tooling.events.* import org.gradle.tooling.events.*
import org.gradle.tooling.events.task.* import org.gradle.tooling.events.task.*
import org.gradle.internal.operations.*
import org.gradle.initialization.*
import org.gradle.api.internal.tasks.execution.*
import org.gradle.execution.*
import org.gradle.internal.build.event.BuildEventListenerRegistryInternal
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
// Can't use settingsEvaluated since this script is applied inside a settingsEvaluated handler settingsEvaluated { settings ->
// But projectsEvaluated is good enough, since the build service won't catch configuration failures anyway
projectsEvaluated {
def projectTracker = gradle.sharedServices.registerIfAbsent("gradle-action-buildResultsRecorder", BuildResultsRecorder, { spec -> def projectTracker = gradle.sharedServices.registerIfAbsent("gradle-action-buildResultsRecorder", BuildResultsRecorder, { spec ->
spec.getParameters().getRootProjectName().set(gradle.rootProject.name) spec.getParameters().getRootProjectName().set(settings.rootProject.name)
spec.getParameters().getRootProjectDir().set(gradle.rootProject.rootDir.absolutePath) spec.getParameters().getRootProjectDir().set(settings.rootDir.absolutePath)
spec.getParameters().getRequestedTasks().set(gradle.startParameter.taskNames.join(" ")) spec.getParameters().getRequestedTasks().set(gradle.startParameter.taskNames.join(" "))
spec.getParameters().getGradleHomeDir().set(gradle.gradleHomeDir.absolutePath) spec.getParameters().getGradleHomeDir().set(gradle.gradleHomeDir.absolutePath)
spec.getParameters().getInvocationId().set(gradle.ext.invocationId) spec.getParameters().getInvocationId().set(gradle.ext.invocationId)
}) })
gradle.services.get(BuildEventsListenerRegistry).onTaskCompletion(projectTracker) gradle.services.get(BuildEventListenerRegistryInternal).onOperationCompletion(projectTracker)
} }
abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder.Params>, OperationCompletionListener, AutoCloseable { abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder.Params>, BuildOperationListener, AutoCloseable {
private boolean buildFailed = false private boolean buildFailed = false
private boolean configCacheHit = true
interface Params extends BuildServiceParameters { interface Params extends BuildServiceParameters {
Property<String> getRootProjectName() Property<String> getRootProjectName()
Property<String> getRootProjectDir() Property<String> getRootProjectDir()
@ -26,9 +30,19 @@ abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder
Property<String> getInvocationId() Property<String> getInvocationId()
} }
public void onFinish(FinishEvent finishEvent) { void started(BuildOperationDescriptor buildOperation, OperationStartEvent startEvent) {}
if (finishEvent instanceof TaskFinishEvent && finishEvent.result instanceof TaskFailureResult) {
buildFailed = true void progress(OperationIdentifier operationIdentifier, OperationProgressEvent progressEvent) {}
void finished(BuildOperationDescriptor buildOperation, OperationFinishEvent finishEvent) {
if (buildOperation.details in EvaluateSettingsBuildOperationType.Details) {
// Got EVALUATE SETTINGS event: not a config-cache hit"
configCacheHit = false
}
if (buildOperation.details in RunRootBuildWorkBuildOperationType.Details) {
if (finishEvent.failure != null) {
buildFailed = true
}
} }
} }
@ -41,8 +55,7 @@ abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder
gradleVersion: GradleVersion.current().version, gradleVersion: GradleVersion.current().version,
gradleHomeDir: getParameters().getGradleHomeDir().get(), gradleHomeDir: getParameters().getGradleHomeDir().get(),
buildFailed: buildFailed, buildFailed: buildFailed,
buildScanUri: null, configCacheHit: configCacheHit
buildScanFailed: false
] ]
def runnerTempDir = System.getProperty("RUNNER_TEMP") ?: System.getenv("RUNNER_TEMP") def runnerTempDir = System.getProperty("RUNNER_TEMP") ?: System.getenv("RUNNER_TEMP")
@ -52,7 +65,7 @@ abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder
} }
try { try {
def buildResultsDir = new File(runnerTempDir, ".build-results") def buildResultsDir = new File(runnerTempDir, ".gradle-actions/build-results")
buildResultsDir.mkdirs() buildResultsDir.mkdirs()
def buildResultsFile = new File(buildResultsDir, githubActionStep + getParameters().getInvocationId().get() + ".json") def buildResultsFile = new File(buildResultsDir, githubActionStep + getParameters().getInvocationId().get() + ".json")
if (!buildResultsFile.exists()) { if (!buildResultsFile.exists()) {

View File

@ -13,6 +13,7 @@ def GE_EXTENSION = "gradleEnterprise"
// Only run against root build. Do not run against included builds. // Only run against root build. Do not run against included builds.
def isTopLevelBuild = gradle.getParent() == null def isTopLevelBuild = gradle.getParent() == null
if (isTopLevelBuild) { if (isTopLevelBuild) {
def resultsWriter = new ResultsWriter()
def version = GradleVersion.current().baseVersion def version = GradleVersion.current().baseVersion
def atLeastGradle3 = version >= GradleVersion.version("3.0") def atLeastGradle3 = version >= GradleVersion.version("3.0")
@ -21,59 +22,77 @@ if (isTopLevelBuild) {
def invocationId = "-${System.currentTimeMillis()}" def invocationId = "-${System.currentTimeMillis()}"
if (atLeastGradle6) { if (atLeastGradle6) {
// By default, use standard mechanisms to capture build results
def useBuildService = version >= GradleVersion.version("6.6") def useBuildService = version >= GradleVersion.version("6.6")
if (useBuildService) {
captureUsingBuildService(invocationId)
} else {
captureUsingBuildFinished(gradle, invocationId, resultsWriter)
}
// Use the Develocity plugin to also capture build scan links, when available
settingsEvaluated { settings -> settingsEvaluated { settings ->
// By default, use standard mechanisms to capture build results
if (useBuildService) {
captureUsingBuildService(settings, invocationId)
} else {
captureUsingBuildFinished(gradle, invocationId)
}
settings.pluginManager.withPlugin(GE_PLUGIN_ID) { settings.pluginManager.withPlugin(GE_PLUGIN_ID) {
// Only execute if develocity plugin isn't applied. // Only execute if develocity plugin isn't applied.
if (!settings.extensions.findByName(DEVELOCITY_EXTENSION)) { if (!settings.extensions.findByName(DEVELOCITY_EXTENSION)) {
captureUsingBuildScanPublished(settings.extensions[GE_EXTENSION].buildScan, settings.rootProject, invocationId) captureUsingBuildScanPublished(settings.extensions[GE_EXTENSION].buildScan, invocationId, resultsWriter)
} }
} }
settings.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) { settings.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) {
captureUsingBuildScanPublished(settings.extensions[DEVELOCITY_EXTENSION].buildScan, settings.rootProject, invocationId) captureUsingBuildScanPublished(settings.extensions[DEVELOCITY_EXTENSION].buildScan, invocationId, resultsWriter)
} }
} }
} else if (atLeastGradle3) { } else if (atLeastGradle3) {
projectsEvaluated { gradle -> projectsEvaluated { gradle ->
// By default, use 'buildFinished' to capture build results // By default, use 'buildFinished' to capture build results
captureUsingBuildFinished(gradle, invocationId) captureUsingBuildFinished(gradle, invocationId, resultsWriter)
gradle.rootProject.pluginManager.withPlugin(BUILD_SCAN_PLUGIN_ID) { gradle.rootProject.pluginManager.withPlugin(BUILD_SCAN_PLUGIN_ID) {
// Only execute if develocity plugin isn't applied. // Only execute if develocity plugin isn't applied.
if (!gradle.rootProject.extensions.findByName(DEVELOCITY_EXTENSION)) { if (!gradle.rootProject.extensions.findByName(DEVELOCITY_EXTENSION)) {
captureUsingBuildScanPublished(gradle.rootProject.extensions[BUILD_SCAN_EXTENSION], gradle.rootProject, invocationId) captureUsingBuildScanPublished(gradle.rootProject.extensions[BUILD_SCAN_EXTENSION], invocationId, resultsWriter)
} }
} }
gradle.rootProject.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) { gradle.rootProject.pluginManager.withPlugin(DEVELOCITY_PLUGIN_ID) {
captureUsingBuildScanPublished(gradle.rootProject.extensions[DEVELOCITY_EXTENSION].buildScan, gradle.rootProject, invocationId) captureUsingBuildScanPublished(gradle.rootProject.extensions[DEVELOCITY_EXTENSION].buildScan, invocationId, resultsWriter)
} }
} }
} }
} }
def captureUsingBuildService(invocationId) {
gradle.ext.invocationId = invocationId
apply from: 'gradle-actions.build-result-capture-service.plugin.groovy'
}
void captureUsingBuildFinished(gradle, String invocationId, ResultsWriter resultsWriter) {
gradle.buildFinished { result ->
println "Got buildFinished: ${result}"
def buildResults = [
rootProjectName: rootProject.name,
rootProjectDir: rootProject.projectDir.absolutePath,
requestedTasks: gradle.startParameter.taskNames.join(" "),
gradleVersion: GradleVersion.current().version,
gradleHomeDir: gradle.gradleHomeDir.absolutePath,
buildFailed: result.failure != null,
configCacheHit: false
]
resultsWriter.writeToResultsFile("build-results", invocationId, buildResults)
}
}
// The `buildScanPublished` hook allows the capture of the Build Scan URI. // The `buildScanPublished` hook allows the capture of the Build Scan URI.
// Results captured this way will overwrite any results from 'buildFinished'. // Results captured this way will overwrite any results from 'buildFinished'.
def captureUsingBuildScanPublished(buildScanExtension, rootProject, invocationId) { void captureUsingBuildScanPublished(buildScanExtension, String invocationId, ResultsWriter resultsWriter) {
buildScanExtension.with { buildScanExtension.with {
def buildResults = new BuildResults(invocationId, gradle, rootProject)
buildFinished { result ->
buildResults.setBuildResult(result)
}
buildScanPublished { buildScan -> buildScanPublished { buildScan ->
buildResults.setBuildScanUri(buildScan.buildScanUri.toASCIIString()) def scanResults = [
buildResults.writeToResultsFile(true) buildScanUri: buildScan.buildScanUri.toASCIIString(),
buildScanFailed: false
]
resultsWriter.writeToResultsFile("build-scans", invocationId, scanResults)
def githubOutput = System.getenv("GITHUB_OUTPUT") def githubOutput = System.getenv("GITHUB_OUTPUT")
if (githubOutput) { if (githubOutput) {
@ -85,63 +104,17 @@ def captureUsingBuildScanPublished(buildScanExtension, rootProject, invocationId
} }
onError { error -> onError { error ->
buildResults.setBuildScanFailed() def scanResults = [
buildResults.writeToResultsFile(true) buildScanUri: null,
buildScanFailed: true
]
resultsWriter.writeToResultsFile("build-scans", invocationId, scanResults)
} }
} }
} }
def captureUsingBuildFinished(gradle, invocationId) { class ResultsWriter {
gradle.buildFinished { result -> void writeToResultsFile(String subDir, String invocationId, def content) {
println "Got buildFinished: ${result}"
def buildResults = new BuildResults(invocationId, gradle, gradle.rootProject)
buildResults.setBuildResult(result)
buildResults.writeToResultsFile(false)
}
}
def captureUsingBuildService(settings, invocationId) {
gradle.ext.invocationId = invocationId
apply from: 'gradle-actions.build-result-capture-service.plugin.groovy'
}
class BuildResults {
def invocationId
def buildResults
BuildResults(String invocationId, def gradle, def rootProject) {
this.invocationId = invocationId
buildResults = [
rootProjectName: rootProject.name,
rootProjectDir: rootProject.projectDir.absolutePath,
requestedTasks: gradle.startParameter.taskNames.join(" "),
gradleVersion: GradleVersion.current().version,
gradleHomeDir: gradle.gradleHomeDir.absolutePath,
buildFailed: false,
buildScanUri: null,
buildScanFailed: false
]
}
def setBuildResult(def result) {
try {
// Gradle and old Build Scan/Gradle Enterprise plugins report a single optional failure in the build result
buildResults['buildFailed'] = result.failure != null
} catch (Exception e) {
// Develocity plugin unwraps all build failures and reports them as a mandatory array
buildResults['buildFailed'] = !result.failures.empty
}
}
def setBuildScanUri(def buildScanUrl) {
buildResults['buildScanUri'] = buildScanUrl
}
def setBuildScanFailed() {
buildResults['buildScanFailed'] = true
}
def writeToResultsFile(boolean overwrite) {
def runnerTempDir = System.getProperty("RUNNER_TEMP") ?: System.getenv("RUNNER_TEMP") def runnerTempDir = System.getProperty("RUNNER_TEMP") ?: System.getenv("RUNNER_TEMP")
def githubActionStep = System.getProperty("GITHUB_ACTION") ?: System.getenv("GITHUB_ACTION") def githubActionStep = System.getProperty("GITHUB_ACTION") ?: System.getenv("GITHUB_ACTION")
if (!runnerTempDir || !githubActionStep) { if (!runnerTempDir || !githubActionStep) {
@ -149,19 +122,12 @@ class BuildResults {
} }
try { try {
def buildResultsDir = new File(runnerTempDir, ".build-results") def buildResultsDir = new File(runnerTempDir, ".gradle-actions/${subDir}")
buildResultsDir.mkdirs() buildResultsDir.mkdirs()
def buildResultsFile = new File(buildResultsDir, githubActionStep + invocationId + ".json") def buildResultsFile = new File(buildResultsDir, githubActionStep + invocationId + ".json")
if (!buildResultsFile.exists()) {
// Overwrite any contents written by buildFinished or build service, since this result is a superset. buildResultsFile << groovy.json.JsonOutput.toJson(content)
if (buildResultsFile.exists()) {
if (overwrite) {
buildResultsFile.text = groovy.json.JsonOutput.toJson(buildResults)
}
} else {
buildResultsFile << groovy.json.JsonOutput.toJson(buildResults)
} }
} catch (Exception e) { } catch (Exception e) {
println "\ngradle action failed to write build-results file. Will continue.\n> ${e.getLocalizedMessage()}" println "\ngradle action failed to write build-results file. Will continue.\n> ${e.getLocalizedMessage()}"
} }

View File

@ -1,7 +1,6 @@
package com.gradle.gradlebuildaction package com.gradle.gradlebuildaction
import groovy.json.JsonSlurper import groovy.json.JsonSlurper
import org.gradle.util.GradleVersion
import static org.junit.Assume.assumeTrue import static org.junit.Assume.assumeTrue
@ -15,7 +14,7 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
run(testGradleVersion.gradleVersion) run(testGradleVersion.gradleVersion)
then: then:
assertResults('help', testGradleVersion, false, false) assertResults('help', testGradleVersion, false)
where: where:
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
@ -29,7 +28,7 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
runAndFail(testGradleVersion.gradleVersion) runAndFail(testGradleVersion.gradleVersion)
then: then:
assertResults('expectFailure', testGradleVersion, true, false) assertResults('expectFailure', testGradleVersion, true)
where: where:
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
@ -49,7 +48,7 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
run(['help', '--configuration-cache'], testGradleVersion.gradleVersion) run(['help', '--configuration-cache'], testGradleVersion.gradleVersion)
then: then:
assertResults('help', testGradleVersion, false, false) assertResults('help', testGradleVersion, false, true)
where: where:
testGradleVersion << CONFIGURATION_CACHE_VERSIONS testGradleVersion << CONFIGURATION_CACHE_VERSIONS
@ -63,7 +62,8 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
run(testGradleVersion.gradleVersion) run(testGradleVersion.gradleVersion)
then: then:
assertResults('help', testGradleVersion, false, true) assertResults('help', testGradleVersion, false)
assertScanResults()
where: where:
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
@ -77,7 +77,8 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
run(testGradleVersion.gradleVersion) run(testGradleVersion.gradleVersion)
then: then:
assertResults('help', testGradleVersion, false, true) assertResults('help', testGradleVersion, false)
assertScanResults()
where: where:
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
@ -91,7 +92,7 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
run(['help', '--no-scan'], testGradleVersion.gradleVersion) run(['help', '--no-scan'], testGradleVersion.gradleVersion)
then: then:
assertResults('help', testGradleVersion, false, false) assertResults('help', testGradleVersion, false)
where: where:
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
@ -106,7 +107,8 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
runAndFail(testGradleVersion.gradleVersion) runAndFail(testGradleVersion.gradleVersion)
then: then:
assertResults('expectFailure', testGradleVersion, true, true) assertResults('expectFailure', testGradleVersion, true)
assertScanResults()
where: where:
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
@ -120,14 +122,17 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
run(['help', '--configuration-cache'], testGradleVersion.gradleVersion) run(['help', '--configuration-cache'], testGradleVersion.gradleVersion)
then: then:
assertResults('help', testGradleVersion, false, true) assertResults('help', testGradleVersion, false, false)
assertScanResults()
assert buildResultFile.delete() assert buildResultFile.delete()
assert scanResultFile.delete()
when: when:
run(['help', '--configuration-cache'], testGradleVersion.gradleVersion) run(['help', '--configuration-cache'], testGradleVersion.gradleVersion)
then: then:
assertResults('help', testGradleVersion, false, true) assertResults('help', testGradleVersion, false, true)
assertScanResults()
where: where:
testGradleVersion << CONFIGURATION_CACHE_VERSIONS testGradleVersion << CONFIGURATION_CACHE_VERSIONS
@ -143,7 +148,8 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
runAndFail(testGradleVersion.gradleVersion) runAndFail(testGradleVersion.gradleVersion)
then: then:
assertResults('expectFailure', testGradleVersion, true, false, true) assertResults('expectFailure', testGradleVersion, true)
assertScanResults(true)
where: where:
testGradleVersion << ALL_VERSIONS testGradleVersion << ALL_VERSIONS
@ -204,7 +210,8 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
run(testGradleVersion.gradleVersion) run(testGradleVersion.gradleVersion)
then: then:
assertResults('help', testGradleVersion, false, true) assertResults('help', testGradleVersion, false)
assertScanResults()
where: where:
testGradleVersion << SETTINGS_PLUGIN_VERSIONS testGradleVersion << SETTINGS_PLUGIN_VERSIONS
@ -232,7 +239,7 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
] ]
} }
void assertResults(String task, TestGradleVersion testGradleVersion, boolean hasFailure, boolean hasBuildScan, boolean scanUploadFailed = false) { void assertResults(String task, TestGradleVersion testGradleVersion, boolean hasFailure, boolean configCacheHit = false) {
def results = new JsonSlurper().parse(buildResultFile) def results = new JsonSlurper().parse(buildResultFile)
assert results['rootProjectName'] == ROOT_PROJECT_NAME assert results['rootProjectName'] == ROOT_PROJECT_NAME
assert results['rootProjectDir'] == testProjectDir.canonicalPath assert results['rootProjectDir'] == testProjectDir.canonicalPath
@ -240,12 +247,17 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
assert results['gradleVersion'] == testGradleVersion.gradleVersion.version assert results['gradleVersion'] == testGradleVersion.gradleVersion.version
assert results['gradleHomeDir'] != null assert results['gradleHomeDir'] != null
assert results['buildFailed'] == hasFailure assert results['buildFailed'] == hasFailure
assert results['buildScanUri'] == (hasBuildScan ? "${mockScansServer.address}s/${PUBLIC_BUILD_SCAN_ID}" : null) assert results['configCacheHit'] == configCacheHit
assert results['buildScanFailed'] == scanUploadFailed }
void assertScanResults(boolean scanUploadFailed = false) {
def scanResults = new JsonSlurper().parse(scanResultFile)
assert scanResults['buildScanUri'] == (scanUploadFailed ? null : "${mockScansServer.address}s/${PUBLIC_BUILD_SCAN_ID}")
assert scanResults['buildScanFailed'] == scanUploadFailed
} }
private File getBuildResultFile() { private File getBuildResultFile() {
def buildResultsDir = new File(testProjectDir, '.build-results') def buildResultsDir = new File(testProjectDir, '.gradle-actions/build-results')
assert buildResultsDir.directory assert buildResultsDir.directory
assert buildResultsDir.listFiles().size() == 1 assert buildResultsDir.listFiles().size() == 1
def resultsFile = buildResultsDir.listFiles()[0] def resultsFile = buildResultsDir.listFiles()[0]
@ -253,4 +265,14 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
assert resultsFile.text.count('rootProjectName') == 1 assert resultsFile.text.count('rootProjectName') == 1
return resultsFile return resultsFile
} }
private File getScanResultFile() {
def buildResultsDir = new File(testProjectDir, '.gradle-actions/build-scans')
assert buildResultsDir.directory
assert buildResultsDir.listFiles().size() == 1
def resultsFile = buildResultsDir.listFiles()[0]
assert resultsFile.name.startsWith('github-step-id')
assert resultsFile.text.count('buildScanUri') == 1
return resultsFile
}
} }

View File

@ -10,6 +10,7 @@ const successfulHelpBuild: BuildResult = {
gradleVersion: '8.0', gradleVersion: '8.0',
gradleHomeDir: '/opt/gradle', gradleHomeDir: '/opt/gradle',
buildFailed: false, buildFailed: false,
configCacheHit: false,
buildScanUri: 'https://scans.gradle.com/s/abc123', buildScanUri: 'https://scans.gradle.com/s/abc123',
buildScanFailed: false buildScanFailed: false
} }