Merge pull request #333 from gradle/dd/build-scan-failure

Report failure to publish build scan in Job Summary
This commit is contained in:
Daz DeBoer 2022-06-19 10:51:24 -06:00 committed by GitHub
commit 67421db6bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 214 additions and 110 deletions

View file

@ -1,5 +1,6 @@
plugins { plugins {
id "com.gradle.enterprise" version "3.10.2" id "com.gradle.enterprise" version "3.10.2"
id "com.gradle.common-custom-user-data-gradle-plugin" version "1.7.2"
} }
gradleEnterprise { gradleEnterprise {

View file

@ -1,5 +1,6 @@
plugins { plugins {
id("com.gradle.enterprise") version "3.10.2" id("com.gradle.enterprise") version "3.10.2"
id("com.gradle.common-custom-user-data-gradle-plugin") version "1.7.2"
} }
gradleEnterprise { gradleEnterprise {

View file

@ -25,7 +25,10 @@ jobs:
run: ./gradlew assemble run: ./gradlew assemble
- name: Build kotlin-dsl project without build scan - name: Build kotlin-dsl project without build scan
working-directory: .github/workflow-samples/kotlin-dsl working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew check --no-scan run: ./gradlew assemble check --no-scan
- name: Build kotlin-dsl project with build scan publish failure
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew check -Dgradle.enterprise.url=https://not.valid.server
- name: Build groovy-dsl project - name: Build groovy-dsl project
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
run: ./gradlew assemble run: ./gradlew assemble

55
dist/main/index.js vendored
View file

@ -66034,28 +66034,43 @@ function loadBuildResults() {
exports.loadBuildResults = loadBuildResults; exports.loadBuildResults = loadBuildResults;
function writeSummaryTable(results) { function writeSummaryTable(results) {
core.summary.addHeading('Gradle Builds', 3); core.summary.addHeading('Gradle Builds', 3);
core.summary.addTable([ core.summary.addRaw(`
[ <table>
{ data: 'Root Project', header: true }, <tr>
{ data: 'Tasks', header: true }, <th>Root Project</th>
{ data: 'Gradle Version', header: true }, <th>Requested Tasks</th>
{ data: 'Outcome', header: true } <th>Gradle Version</th>
], <th>Build Outcome</th>
...results.map(result => [ <th>Build Scan</th>
result.rootProjectName, </tr>${results.map(result => renderBuildResultRow(result)).join('')}
result.requestedTasks, </table>
result.gradleVersion, `);
renderOutcome(result) }
]) function renderBuildResultRow(result) {
]); return `
core.summary.addRaw('\n'); <tr>
<td>${result.rootProjectName}</td>
<td>${result.requestedTasks}</td>
<td align='center'>${result.gradleVersion}</td>
<td align='center'>${renderOutcome(result)}</td>
<td>${renderBuildScan(result)}</td>
</tr>`;
} }
function renderOutcome(result) { function renderOutcome(result) {
const labelPart = result.buildScanUri ? 'Build%20Scan%E2%84%A2' : 'Build'; return result.buildFailed ? ':x:' : ':white_check_mark:';
const outcomePart = result.buildFailed ? 'FAILED-red' : 'SUCCESS-brightgreen'; }
const badgeUrl = `https://img.shields.io/badge/${labelPart}-${outcomePart}?logo=Gradle`; function renderBuildScan(result) {
const badgeHtml = `<img src="${badgeUrl}" alt="Gradle Build">`; if (result.buildScanFailed) {
const targetUrl = result.buildScanUri ? result.buildScanUri : '#'; return renderBuildScanBadge('PUBLISH_FAILED', 'orange', 'https://docs.gradle.com/enterprise/gradle-plugin/#troubleshooting');
}
if (result.buildScanUri) {
return renderBuildScanBadge('PUBLISHED', '06A0CE', result.buildScanUri);
}
return renderBuildScanBadge('NOT_PUBLISHED', 'lightgrey', 'https://scans.gradle.com');
}
function renderBuildScanBadge(outcomeText, outcomeColor, targetUrl) {
const badgeUrl = `https://img.shields.io/badge/Build%20Scan%E2%84%A2-${outcomeText}-${outcomeColor}?logo=Gradle`;
const badgeHtml = `<img src="${badgeUrl}" alt="Build Scan ${outcomeText}" />`;
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`; return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`;
} }

File diff suppressed because one or more lines are too long

55
dist/post/index.js vendored
View file

@ -64954,28 +64954,43 @@ function loadBuildResults() {
exports.loadBuildResults = loadBuildResults; exports.loadBuildResults = loadBuildResults;
function writeSummaryTable(results) { function writeSummaryTable(results) {
core.summary.addHeading('Gradle Builds', 3); core.summary.addHeading('Gradle Builds', 3);
core.summary.addTable([ core.summary.addRaw(`
[ <table>
{ data: 'Root Project', header: true }, <tr>
{ data: 'Tasks', header: true }, <th>Root Project</th>
{ data: 'Gradle Version', header: true }, <th>Requested Tasks</th>
{ data: 'Outcome', header: true } <th>Gradle Version</th>
], <th>Build Outcome</th>
...results.map(result => [ <th>Build Scan</th>
result.rootProjectName, </tr>${results.map(result => renderBuildResultRow(result)).join('')}
result.requestedTasks, </table>
result.gradleVersion, `);
renderOutcome(result) }
]) function renderBuildResultRow(result) {
]); return `
core.summary.addRaw('\n'); <tr>
<td>${result.rootProjectName}</td>
<td>${result.requestedTasks}</td>
<td align='center'>${result.gradleVersion}</td>
<td align='center'>${renderOutcome(result)}</td>
<td>${renderBuildScan(result)}</td>
</tr>`;
} }
function renderOutcome(result) { function renderOutcome(result) {
const labelPart = result.buildScanUri ? 'Build%20Scan%E2%84%A2' : 'Build'; return result.buildFailed ? ':x:' : ':white_check_mark:';
const outcomePart = result.buildFailed ? 'FAILED-red' : 'SUCCESS-brightgreen'; }
const badgeUrl = `https://img.shields.io/badge/${labelPart}-${outcomePart}?logo=Gradle`; function renderBuildScan(result) {
const badgeHtml = `<img src="${badgeUrl}" alt="Gradle Build">`; if (result.buildScanFailed) {
const targetUrl = result.buildScanUri ? result.buildScanUri : '#'; return renderBuildScanBadge('PUBLISH_FAILED', 'orange', 'https://docs.gradle.com/enterprise/gradle-plugin/#troubleshooting');
}
if (result.buildScanUri) {
return renderBuildScanBadge('PUBLISHED', '06A0CE', result.buildScanUri);
}
return renderBuildScanBadge('NOT_PUBLISHED', 'lightgrey', 'https://scans.gradle.com');
}
function renderBuildScanBadge(outcomeText, outcomeColor, targetUrl) {
const badgeUrl = `https://img.shields.io/badge/Build%20Scan%E2%84%A2-${outcomeText}-${outcomeColor}?logo=Gradle`;
const badgeHtml = `<img src="${badgeUrl}" alt="Build Scan ${outcomeText}" />`;
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`; return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`;
} }

File diff suppressed because one or more lines are too long

View file

@ -11,6 +11,7 @@ export interface BuildResult {
get gradleHomeDir(): string get gradleHomeDir(): string
get buildFailed(): boolean get buildFailed(): boolean
get buildScanUri(): string get buildScanUri(): string
get buildScanFailed(): boolean
} }
export async function writeJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): Promise<void> { export async function writeJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): Promise<void> {
@ -43,28 +44,51 @@ export function loadBuildResults(): BuildResult[] {
function writeSummaryTable(results: BuildResult[]): void { function writeSummaryTable(results: BuildResult[]): void {
core.summary.addHeading('Gradle Builds', 3) core.summary.addHeading('Gradle Builds', 3)
core.summary.addTable([
[ core.summary.addRaw(`
{data: 'Root Project', header: true}, <table>
{data: 'Tasks', header: true}, <tr>
{data: 'Gradle Version', header: true}, <th>Root Project</th>
{data: 'Outcome', header: true} <th>Requested Tasks</th>
], <th>Gradle Version</th>
...results.map(result => [ <th>Build Outcome</th>
result.rootProjectName, <th>Build Scan</th>
result.requestedTasks, </tr>${results.map(result => renderBuildResultRow(result)).join('')}
result.gradleVersion, </table>
renderOutcome(result) `)
]) }
])
core.summary.addRaw('\n') function renderBuildResultRow(result: BuildResult): string {
return `
<tr>
<td>${result.rootProjectName}</td>
<td>${result.requestedTasks}</td>
<td align='center'>${result.gradleVersion}</td>
<td align='center'>${renderOutcome(result)}</td>
<td>${renderBuildScan(result)}</td>
</tr>`
} }
function renderOutcome(result: BuildResult): string { function renderOutcome(result: BuildResult): string {
const labelPart = result.buildScanUri ? 'Build%20Scan%E2%84%A2' : 'Build' return result.buildFailed ? ':x:' : ':white_check_mark:'
const outcomePart = result.buildFailed ? 'FAILED-red' : 'SUCCESS-brightgreen' }
const badgeUrl = `https://img.shields.io/badge/${labelPart}-${outcomePart}?logo=Gradle`
const badgeHtml = `<img src="${badgeUrl}" alt="Gradle Build">` function renderBuildScan(result: BuildResult): string {
const targetUrl = result.buildScanUri ? result.buildScanUri : '#' if (result.buildScanFailed) {
return renderBuildScanBadge(
'PUBLISH_FAILED',
'orange',
'https://docs.gradle.com/enterprise/gradle-plugin/#troubleshooting'
)
}
if (result.buildScanUri) {
return renderBuildScanBadge('PUBLISHED', '06A0CE', result.buildScanUri)
}
return renderBuildScanBadge('NOT_PUBLISHED', 'lightgrey', 'https://scans.gradle.com')
}
function renderBuildScanBadge(outcomeText: string, outcomeColor: string, targetUrl: string): string {
const badgeUrl = `https://img.shields.io/badge/Build%20Scan%E2%84%A2-${outcomeText}-${outcomeColor}?logo=Gradle`
const badgeHtml = `<img src="${badgeUrl}" alt="Build Scan ${outcomeText}" />`
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>` return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`
} }

View file

@ -41,7 +41,8 @@ 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 buildScanUri: null,
buildScanFailed: false
] ]
def buildResultsDir = new File(System.getenv("RUNNER_TEMP"), ".build-results") def buildResultsDir = new File(System.getenv("RUNNER_TEMP"), ".build-results")

View file

@ -40,65 +40,33 @@ if (isTopLevelBuild) {
def captureUsingBuildScanPublished(buildScanExtension, rootProject, invocationId) { def captureUsingBuildScanPublished(buildScanExtension, rootProject, invocationId) {
buildScanExtension.with { buildScanExtension.with {
def requestedTasks = gradle.startParameter.taskNames.join(" ") def buildResults = new BuildResults(invocationId, gradle, rootProject)
def rootProjectName = rootProject.name
def rootProjectDir = rootProject.projectDir.absolutePath
def gradleVersion = GradleVersion.current().version
def gradleHomeDir = gradle.gradleHomeDir.absolutePath
def buildFailed = false
buildFinished { result -> buildFinished { result ->
buildFailed = (result.failure != null) buildResults.setBuildResult(result)
} }
buildScanPublished { buildScan -> buildScanPublished { buildScan ->
buildResults.setBuildScanUri(buildScan.buildScanUri.toASCIIString())
def buildScanUri = buildScan.buildScanUri.toASCIIString() buildResults.writeToResultsFile(true)
def buildResults = [
rootProjectName: rootProjectName,
rootProjectDir: rootProjectDir,
requestedTasks: requestedTasks,
gradleVersion: gradleVersion,
gradleHomeDir: gradleHomeDir,
buildFailed: buildFailed,
buildScanUri: buildScanUri
]
def buildResultsDir = new File(System.getenv("RUNNER_TEMP"), ".build-results")
buildResultsDir.mkdirs()
def buildResultsFile = new File(buildResultsDir, System.getenv("GITHUB_ACTION") + invocationId + ".json")
// Overwrite any contents written by buildFinished or build service, since this result is a superset.
if (buildResultsFile.exists()) {
buildResultsFile.text = groovy.json.JsonOutput.toJson(buildResults)
} else {
buildResultsFile << groovy.json.JsonOutput.toJson(buildResults)
}
println("::set-output name=build-scan-url::${buildScan.buildScanUri}") println("::set-output name=build-scan-url::${buildScan.buildScanUri}")
} }
onError { error ->
buildResults.setBuildScanFailed()
buildResults.writeToResultsFile(true)
}
} }
} }
def captureUsingBuildFinished(gradle, invocationId) { def captureUsingBuildFinished(gradle, invocationId) {
gradle.buildFinished { result -> gradle.buildFinished { result ->
def buildResults = [ def buildResults = new BuildResults(invocationId, gradle, gradle.rootProject)
rootProjectName: gradle.rootProject.name, buildResults.setBuildResult(result)
rootProjectDir: gradle.rootProject.rootDir.absolutePath,
requestedTasks: gradle.startParameter.taskNames.join(" "), buildResults.writeToResultsFile(false)
gradleVersion: GradleVersion.current().version,
gradleHomeDir: gradle.gradleHomeDir.absolutePath,
buildFailed: result.failure != null,
buildScanUri: null
]
def buildResultsDir = new File(System.getenv("RUNNER_TEMP"), ".build-results")
buildResultsDir.mkdirs()
def buildResultsFile = new File(buildResultsDir, System.getenv("GITHUB_ACTION") + invocationId + ".json")
// Don't overwrite file generated by build-scan plugin if present (which has build-scan-uri)
if (!buildResultsFile.exists()) {
buildResultsFile << groovy.json.JsonOutput.toJson(buildResults)
}
} }
} }
@ -106,3 +74,49 @@ def captureUsingBuildService(settings, invocationId) {
gradle.ext.invocationId = invocationId gradle.ext.invocationId = invocationId
apply from: 'build-result-capture-service.plugin.groovy' apply from: '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) {
buildResults['buildFailed'] = result.failure != null
}
def setBuildScanUri(def buildScanUrl) {
buildResults['buildScanUri'] = buildScanUrl
}
def setBuildScanFailed() {
buildResults['buildScanFailed'] = true
}
def writeToResultsFile(boolean overwrite) {
def buildResultsDir = new File(System.getenv("RUNNER_TEMP"), ".build-results")
buildResultsDir.mkdirs()
def buildResultsFile = new File(buildResultsDir, System.getenv("GITHUB_ACTION") + invocationId + ".json")
// Overwrite any contents written by buildFinished or build service, since this result is a superset.
if (buildResultsFile.exists()) {
if (overwrite) {
buildResultsFile.text = groovy.json.JsonOutput.toJson(buildResults)
}
} else {
buildResultsFile << groovy.json.JsonOutput.toJson(buildResults)
}
}
}

View file

@ -45,6 +45,7 @@ class BaseInitScriptTest extends Specification {
static final String PUBLIC_BUILD_SCAN_ID = 'i2wepy2gr7ovw' static final String PUBLIC_BUILD_SCAN_ID = 'i2wepy2gr7ovw'
static final String DEFAULT_SCAN_UPLOAD_TOKEN = 'scan-upload-token' static final String DEFAULT_SCAN_UPLOAD_TOKEN = 'scan-upload-token'
static final String ROOT_PROJECT_NAME = 'test-init-script' static final String ROOT_PROJECT_NAME = 'test-init-script'
boolean failScanUpload = false
File settingsFile File settingsFile
File buildFile File buildFile
@ -59,6 +60,10 @@ class BaseInitScriptTest extends Specification {
handlers { handlers {
post('in/:gradleVersion/:pluginVersion') { post('in/:gradleVersion/:pluginVersion') {
if (failScanUpload) {
context.response.status(401).send()
return
}
def scanUrlString = "${mockScansServer.address}s/$PUBLIC_BUILD_SCAN_ID" def scanUrlString = "${mockScansServer.address}s/$PUBLIC_BUILD_SCAN_ID"
def body = [ def body = [
id : PUBLIC_BUILD_SCAN_ID, id : PUBLIC_BUILD_SCAN_ID,
@ -72,6 +77,10 @@ class BaseInitScriptTest extends Specification {
} }
prefix('scans/publish') { prefix('scans/publish') {
post('gradle/:pluginVersion/token') { post('gradle/:pluginVersion/token') {
if (failScanUpload) {
context.response.status(401).send()
return
}
def pluginVersion = context.pathTokens.pluginVersion def pluginVersion = context.pathTokens.pluginVersion
def scanUrlString = "${mockScansServer.address}s/$PUBLIC_BUILD_SCAN_ID" def scanUrlString = "${mockScansServer.address}s/$PUBLIC_BUILD_SCAN_ID"
def body = [ def body = [
@ -85,6 +94,10 @@ class BaseInitScriptTest extends Specification {
.send(jsonWriter.writeValueAsBytes(body)) .send(jsonWriter.writeValueAsBytes(body))
} }
post('gradle/:pluginVersion/upload') { post('gradle/:pluginVersion/upload') {
if (failScanUpload) {
context.response.status(401).send()
return
}
context.request.getBody(1024 * 1024 * 10).then { context.request.getBody(1024 * 1024 * 10).then {
context.response context.response
.contentType('application/vnd.gradle.scan-upload-ack+json') .contentType('application/vnd.gradle.scan-upload-ack+json')

View file

@ -118,7 +118,23 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
testGradleVersion << CONFIGURATION_CACHE_VERSIONS testGradleVersion << CONFIGURATION_CACHE_VERSIONS
} }
void assertResults(String task, TestGradleVersion testGradleVersion, boolean hasFailure, boolean hasBuildScan) { def "produces build results file for failing build on #testGradleVersion when build scan publish fails"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
when:
declareGePluginApplication(testGradleVersion.gradleVersion)
addFailingTaskToBuild()
failScanUpload = true
runAndFail(['expectFailure'], initScript, testGradleVersion.gradleVersion)
then:
assertResults('expectFailure', testGradleVersion, true, false, true)
where:
testGradleVersion << ALL_VERSIONS
}
void assertResults(String task, TestGradleVersion testGradleVersion, boolean hasFailure, boolean hasBuildScan, boolean scanUploadFailed = 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
@ -127,6 +143,7 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
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['buildScanUri'] == (hasBuildScan ? "${mockScansServer.address}s/${PUBLIC_BUILD_SCAN_ID}" : null)
assert results['buildScanFailed'] == scanUploadFailed
} }
private File getBuildResultFile() { private File getBuildResultFile() {