Skip to content

Commit

Permalink
Merge pull request #304 from gradle/dd/test-init-scripts
Browse files Browse the repository at this point in the history
Improve init scripts and add test coverage

The build-scan-capture init script will now capture results from builds that do not publish a build-scan, with and without the configuration-cache.

Fixes #292
  • Loading branch information
bigdaz committed Jun 5, 2022
2 parents 14c898b + b400dc5 commit 44e1837
Show file tree
Hide file tree
Showing 24 changed files with 982 additions and 44 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/ci-init-script-check.yml
@@ -0,0 +1,23 @@
name: CI-init-script-check

on:
push:

jobs:
test-init-scripts:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 8
- name: Setup Gradle
uses: ./
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
- name: Run integration tests
working-directory: test/test-init-scripts
run: ./gradlew check
5 changes: 4 additions & 1 deletion .github/workflows/demo-job-summary.yml
Expand Up @@ -23,10 +23,13 @@ jobs:
- name: Build kotlin-dsl project
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew assemble
- name: Build kotlin-dsl project without build scan
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew check --no-scan
- name: Build groovy-dsl project
working-directory: .github/workflow-samples/groovy-dsl
run: ./gradlew assemble
- name: Build kotlin-dsl project again
- name: Build kotlin-dsl project with multiple gradle invocations
working-directory: .github/workflow-samples/kotlin-dsl
run: |
./gradlew tasks --no-daemon
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/integ-test-execution.yml
Expand Up @@ -56,19 +56,26 @@ jobs:
gradle-versions:
strategy:
matrix:
gradle: [7.3, 6.9, 5.6.4, 4.10.3]
gradle: [7.3, 6.9, 5.6.4, 4.10.3, 3.5.1]
os: ${{fromJSON(inputs.runner-os)}}
include:
- gradle: 5.6.4
build-root-suffix: -gradle-5
- gradle: 4.10.3
build-root-suffix: -gradle-4
- gradle: 3.5.1
build-root-suffix: -gradle-4
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Download distribution if required
uses: ./.github/actions/download-dist
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 8
- name: Run Gradle build
uses: ./
id: gradle
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/integ-test-provision-gradle-versions.yml
Expand Up @@ -59,19 +59,26 @@ jobs:
gradle-versions:
strategy:
matrix:
gradle: [7.3, 6.9, 5.6.4, 4.10.3]
gradle: [7.3, 6.9, 5.6.4, 4.10.3, 3.5.1]
os: ${{fromJSON(inputs.runner-os)}}
include:
- gradle: 5.6.4
build-root-suffix: -gradle-5
- gradle: 4.10.3
build-root-suffix: -gradle-4
- gradle: 3.5.1
build-root-suffix: -gradle-4
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Download distribution if required
uses: ./.github/actions/download-dist
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 8
- name: Setup Gradle
uses: ./
with:
Expand Down
17 changes: 8 additions & 9 deletions dist/main/index.js
Expand Up @@ -64967,6 +64967,7 @@ class GradleStateCache {
fs_1.default.appendFileSync(propertiesFile, 'org.gradle.daemon=false');
const initScriptFilenames = [
'build-result-capture.init.gradle',
'build-result-capture-service.plugin.groovy',
'project-root-capture.init.gradle',
'project-root-capture.plugin.groovy'
];
Expand Down Expand Up @@ -65291,7 +65292,7 @@ class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
});
}
getProjectRoots() {
const projectList = path_1.default.resolve(this.gradleUserHome, cache_base_1.PROJECT_ROOTS_FILE);
const projectList = path_1.default.resolve(process.env['RUNNER_TEMP'], cache_base_1.PROJECT_ROOTS_FILE);
if (!fs_1.default.existsSync(projectList)) {
core.info(`Missing project list file ${projectList}`);
return [];
Expand Down Expand Up @@ -65559,7 +65560,7 @@ function isCacheDisabled() {
}
exports.isCacheDisabled = isCacheDisabled;
function isCacheReadOnly() {
return core.getBooleanInput(CACHE_READONLY_PARAMETER);
return !isCacheWriteOnly() && core.getBooleanInput(CACHE_READONLY_PARAMETER);
}
exports.isCacheReadOnly = isCacheReadOnly;
function isCacheWriteOnly() {
Expand Down Expand Up @@ -66031,14 +66032,12 @@ function writeSummaryTable(results) {
core.summary.addRaw('\n');
}
function renderOutcome(result) {
const badgeUrl = result.buildFailed
? 'https://img.shields.io/badge/Build%20Scan%E2%84%A2-FAILED-red?logo=Gradle'
: 'https://img.shields.io/badge/Build%20Scan%E2%84%A2-SUCCESS-brightgreen?logo=Gradle';
const labelPart = result.buildScanUri ? 'Build%20Scan%E2%84%A2' : 'Build';
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">`;
if (result.buildScanUri) {
return `<a href="${result.buildScanUri}" rel="nofollow">${badgeHtml}</a>`;
}
return badgeHtml;
const targetUrl = result.buildScanUri ? result.buildScanUri : '#';
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`;
}


Expand Down
2 changes: 1 addition & 1 deletion dist/main/index.js.map

Large diffs are not rendered by default.

17 changes: 8 additions & 9 deletions dist/post/index.js
Expand Up @@ -64018,6 +64018,7 @@ class GradleStateCache {
fs_1.default.appendFileSync(propertiesFile, 'org.gradle.daemon=false');
const initScriptFilenames = [
'build-result-capture.init.gradle',
'build-result-capture-service.plugin.groovy',
'project-root-capture.init.gradle',
'project-root-capture.plugin.groovy'
];
Expand Down Expand Up @@ -64342,7 +64343,7 @@ class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
});
}
getProjectRoots() {
const projectList = path_1.default.resolve(this.gradleUserHome, cache_base_1.PROJECT_ROOTS_FILE);
const projectList = path_1.default.resolve(process.env['RUNNER_TEMP'], cache_base_1.PROJECT_ROOTS_FILE);
if (!fs_1.default.existsSync(projectList)) {
core.info(`Missing project list file ${projectList}`);
return [];
Expand Down Expand Up @@ -64610,7 +64611,7 @@ function isCacheDisabled() {
}
exports.isCacheDisabled = isCacheDisabled;
function isCacheReadOnly() {
return core.getBooleanInput(CACHE_READONLY_PARAMETER);
return !isCacheWriteOnly() && core.getBooleanInput(CACHE_READONLY_PARAMETER);
}
exports.isCacheReadOnly = isCacheReadOnly;
function isCacheWriteOnly() {
Expand Down Expand Up @@ -64951,14 +64952,12 @@ function writeSummaryTable(results) {
core.summary.addRaw('\n');
}
function renderOutcome(result) {
const badgeUrl = result.buildFailed
? 'https://img.shields.io/badge/Build%20Scan%E2%84%A2-FAILED-red?logo=Gradle'
: 'https://img.shields.io/badge/Build%20Scan%E2%84%A2-SUCCESS-brightgreen?logo=Gradle';
const labelPart = result.buildScanUri ? 'Build%20Scan%E2%84%A2' : 'Build';
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">`;
if (result.buildScanUri) {
return `<a href="${result.buildScanUri}" rel="nofollow">${badgeHtml}</a>`;
}
return badgeHtml;
const targetUrl = result.buildScanUri ? result.buildScanUri : '#';
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`;
}


Expand Down
2 changes: 1 addition & 1 deletion dist/post/index.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/cache-base.ts
Expand Up @@ -172,6 +172,7 @@ export class GradleStateCache {

const initScriptFilenames = [
'build-result-capture.init.gradle',
'build-result-capture-service.plugin.groovy',
'project-root-capture.init.gradle',
'project-root-capture.plugin.groovy'
]
Expand Down
2 changes: 1 addition & 1 deletion src/cache-extract-entries.ts
Expand Up @@ -387,7 +387,7 @@ export class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
* set of project roots, to allow saving of configuration-cache entries for each.
*/
private getProjectRoots(): string[] {
const projectList = path.resolve(this.gradleUserHome, PROJECT_ROOTS_FILE)
const projectList = path.resolve(process.env['RUNNER_TEMP']!, PROJECT_ROOTS_FILE)
if (!fs.existsSync(projectList)) {
core.info(`Missing project list file ${projectList}`)
return []
Expand Down
13 changes: 5 additions & 8 deletions src/job-summary.ts
Expand Up @@ -60,13 +60,10 @@ function writeSummaryTable(results: BuildResult[]): void {
}

function renderOutcome(result: BuildResult): string {
const badgeUrl = result.buildFailed
? 'https://img.shields.io/badge/Build%20Scan%E2%84%A2-FAILED-red?logo=Gradle'
: 'https://img.shields.io/badge/Build%20Scan%E2%84%A2-SUCCESS-brightgreen?logo=Gradle'
const labelPart = result.buildScanUri ? 'Build%20Scan%E2%84%A2' : 'Build'
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">`

if (result.buildScanUri) {
return `<a href="${result.buildScanUri}" rel="nofollow">${badgeHtml}</a>`
}
return badgeHtml
const targetUrl = result.buildScanUri ? result.buildScanUri : '#'
return `<a href="${targetUrl}" rel="nofollow">${badgeHtml}</a>`
}
46 changes: 46 additions & 0 deletions src/resources/build-result-capture-service.plugin.groovy
@@ -0,0 +1,46 @@
import org.gradle.tooling.events.*
import org.gradle.tooling.events.task.*
import org.gradle.util.GradleVersion

// Can't use settingsEvaluated since this script is applied inside a settingsEvaluated handler
// But projectsEvaluated is good enough, since the build service won't catch configuration failures anyway
projectsEvaluated {
def projectTracker = gradle.sharedServices.registerIfAbsent("gradle-build-action-buildResultsRecorder", BuildResultsRecorder, { spec ->
spec.getParameters().getRootProject().set(gradle.rootProject.name)
spec.getParameters().getRequestedTasks().set(gradle.startParameter.taskNames.join(" "))
spec.getParameters().getInvocationId().set(gradle.ext.invocationId)
})

gradle.services.get(BuildEventsListenerRegistry).onTaskCompletion(projectTracker)
}

abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder.Params>, OperationCompletionListener, AutoCloseable {
private boolean buildFailed = false
interface Params extends BuildServiceParameters {
Property<String> getRootProject()
Property<String> getRequestedTasks()
Property<String> getInvocationId()
}

public void onFinish(FinishEvent finishEvent) {
if (finishEvent instanceof TaskFinishEvent && finishEvent.result instanceof TaskFailureResult) {
buildFailed = true
}
}

@Override
public void close() {
def buildResults = [
rootProject: getParameters().getRootProject().get(),
requestedTasks: getParameters().getRequestedTasks().get(),
gradleVersion: GradleVersion.current().version,
buildFailed: buildFailed,
buildScanUri: null
]

def buildResultsDir = new File(System.getenv("RUNNER_TEMP"), ".build-results")
buildResultsDir.mkdirs()
def buildResultsFile = new File(buildResultsDir, System.getenv("GITHUB_ACTION") + getParameters().getInvocationId().get() + ".json")
buildResultsFile << groovy.json.JsonOutput.toJson(buildResults)
}
}
63 changes: 52 additions & 11 deletions src/resources/build-result-capture.init.gradle
Expand Up @@ -7,25 +7,38 @@ import org.gradle.util.GradleVersion
def isTopLevelBuild = gradle.getParent() == null
if (isTopLevelBuild) {
def version = GradleVersion.current().baseVersion
def atLeastGradle4 = version >= GradleVersion.version("4.0")

def atLeastGradle3 = version >= GradleVersion.version("3.0")
def atLeastGradle6 = version >= GradleVersion.version("6.0")

def invocationId = "-${System.currentTimeMillis()}"

if (atLeastGradle6) {
def useBuildService = version >= GradleVersion.version("6.6")
settingsEvaluated { settings ->
// The `buildScanPublished` hook is the only way to capture the build scan URI.
if (settings.pluginManager.hasPlugin("com.gradle.enterprise")) {
registerCallbacks(settings.extensions["gradleEnterprise"].buildScan, settings.rootProject.name)
captureUsingBuildScanPublished(settings.extensions["gradleEnterprise"].buildScan, settings.rootProject.name, invocationId)
}
// We also need to add hooks in case the plugin is applied but no build scan is published
if (useBuildService) {
captureUsingBuildService(settings, invocationId)
} else {
captureUsingBuildFinished(gradle, invocationId)
}
}
} else if (atLeastGradle4) {
} else if (atLeastGradle3) {
projectsEvaluated { gradle ->
if (gradle.rootProject.pluginManager.hasPlugin("com.gradle.build-scan")) {
registerCallbacks(gradle.rootProject.extensions["buildScan"], gradle.rootProject.name)
captureUsingBuildScanPublished(gradle.rootProject.extensions["buildScan"], gradle.rootProject.name, invocationId)
}
// We need to capture in buildFinished in case the plugin is applied but no build scan is published
captureUsingBuildFinished(gradle, invocationId)
}
}
}

def registerCallbacks(buildScanExtension, rootProjectName) {
def captureUsingBuildScanPublished(buildScanExtension, rootProjectName, invocationId) {
buildScanExtension.with {
def requestedTasks = gradle.startParameter.taskNames.join(" ")
def gradleVersion = GradleVersion.current().version
Expand All @@ -36,10 +49,6 @@ def registerCallbacks(buildScanExtension, rootProjectName) {
}

buildScanPublished { buildScan ->
def buildResultsDir = new File(System.getenv("RUNNER_TEMP"), ".build-results")
buildResultsDir.mkdirs()

def buildResultsFile = new File(buildResultsDir, System.getenv("GITHUB_ACTION") + System.currentTimeMillis() + ".json")

def buildScanUri = buildScan.buildScanUri.toASCIIString()
def buildResults = [
Expand All @@ -49,9 +58,41 @@ def registerCallbacks(buildScanExtension, rootProjectName) {
buildFailed: buildFailed,
buildScanUri: buildScanUri
]
buildResultsFile << groovy.json.JsonOutput.toJson(buildResults)

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}")
}
}
}
}

def captureUsingBuildFinished(gradle, invocationId) {
gradle.buildFinished { result ->
def buildResults = [
rootProject: gradle.rootProject.name,
requestedTasks: gradle.startParameter.taskNames.join(" "),
gradleVersion: GradleVersion.current().version,
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")
buildResultsFile << groovy.json.JsonOutput.toJson(buildResults)
}
}

def captureUsingBuildService(settings, invocationId) {
gradle.ext.invocationId = invocationId
apply from: 'build-result-capture-service.plugin.groovy'
}
2 changes: 1 addition & 1 deletion src/resources/project-root-capture.plugin.groovy
Expand Up @@ -10,7 +10,7 @@ import org.gradle.tooling.events.*

settingsEvaluated { settings ->
def rootDir = settings.rootDir.absolutePath
def rootListLocation = new File(settings.gradle.gradleUserHomeDir, "project-roots.txt").absolutePath
def rootListLocation = new File(System.getenv("RUNNER_TEMP"), "project-roots.txt").absolutePath

def projectTracker = gradle.sharedServices.registerIfAbsent("gradle-build-action-projectRootTracker", ProjectTracker, { spec ->
spec.getParameters().getRootDir().set(rootDir);
Expand Down
2 changes: 2 additions & 0 deletions test/test-init-scripts/.gitignore
@@ -0,0 +1,2 @@
build
.gradle

0 comments on commit 44e1837

Please sign in to comment.