Merge pull request #305 from gradle/dd/stop-gradle-daemons

Allow daemons to run across workflow steps
This commit is contained in:
Daz DeBoer 2022-06-05 18:07:56 -06:00 committed by GitHub
commit a4a9a30e86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 160 additions and 223 deletions

View file

@ -15,9 +15,7 @@ jobs:
distribution: temurin
java-version: 8
- name: Setup Gradle
uses: ./
with:
cache-read-only: false # For testing, allow writing cache entries on non-default branches
uses: gradle/gradle-build-action@v2 # Use a released version to avoid breakages
- name: Run integration tests
working-directory: test/test-init-scripts
run: ./gradlew check

61
dist/main/index.js vendored
View file

@ -64843,7 +64843,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.GradleStateCache = exports.PROJECT_ROOTS_FILE = exports.META_FILE_DIR = void 0;
exports.GradleStateCache = exports.META_FILE_DIR = void 0;
const core = __importStar(__nccwpck_require__(2186));
const exec = __importStar(__nccwpck_require__(1514));
const path_1 = __importDefault(__nccwpck_require__(1017));
@ -64852,7 +64852,6 @@ const cache_utils_1 = __nccwpck_require__(1678);
const cache_extract_entries_1 = __nccwpck_require__(6161);
const RESTORED_CACHE_KEY_KEY = 'restored-cache-key';
exports.META_FILE_DIR = '.gradle-build-action';
exports.PROJECT_ROOTS_FILE = 'project-roots.txt';
const INCLUDE_PATHS_PARAMETER = 'gradle-home-cache-includes';
const EXCLUDE_PATHS_PARAMETER = 'gradle-home-cache-excludes';
class GradleStateCache {
@ -64963,14 +64962,7 @@ class GradleStateCache {
return path_1.default.resolve(this.gradleUserHome, rawPath);
}
initializeGradleUserHome(gradleUserHome, initScriptsDir) {
const propertiesFile = path_1.default.resolve(gradleUserHome, 'gradle.properties');
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'
];
const initScriptFilenames = ['build-result-capture.init.gradle', 'build-result-capture-service.plugin.groovy'];
for (const initScriptFilename of initScriptFilenames) {
const initScriptContent = this.readResourceAsString(initScriptFilename);
const initScriptPath = path_1.default.resolve(initScriptsDir, initScriptFilename);
@ -65060,6 +65052,7 @@ const core = __importStar(__nccwpck_require__(2186));
const glob = __importStar(__nccwpck_require__(8090));
const cache_base_1 = __nccwpck_require__(7591);
const cache_utils_1 = __nccwpck_require__(1678);
const job_summary_1 = __nccwpck_require__(7345);
const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE';
class ExtractedCacheEntry {
constructor(artifactType, pattern, cacheKey) {
@ -65292,14 +65285,9 @@ class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
});
}
getProjectRoots() {
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 [];
}
const projectRoots = fs_1.default.readFileSync(projectList, 'utf-8');
core.info(`Found project roots '${projectRoots}' in ${projectList}`);
return projectRoots.trim().split('\n');
const buildResults = (0, job_summary_1.loadBuildResults)();
const projectRootDirs = buildResults.map(x => x.rootProjectDir);
return [...new Set(projectRootDirs)];
}
}
exports.ConfigurationCacheEntryExtractor = ConfigurationCacheEntryExtractor;
@ -65984,14 +65972,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.writeJobSummary = void 0;
exports.loadBuildResults = exports.writeJobSummary = void 0;
const core = __importStar(__nccwpck_require__(2186));
const fs_1 = __importDefault(__nccwpck_require__(7147));
const path_1 = __importDefault(__nccwpck_require__(1017));
const cache_reporting_1 = __nccwpck_require__(6674);
function writeJobSummary(cacheListener) {
function writeJobSummary(buildResults, cacheListener) {
core.info('Writing job summary');
const buildResults = loadBuildResults();
if (buildResults.length === 0) {
core.debug('No Gradle build results found. Summary table will not be generated.');
}
@ -66013,6 +66000,7 @@ function loadBuildResults() {
return JSON.parse(content);
});
}
exports.loadBuildResults = loadBuildResults;
function writeSummaryTable(results) {
core.summary.addHeading('Gradle Builds', 3);
core.summary.addTable([
@ -66023,7 +66011,7 @@ function writeSummaryTable(results) {
{ data: 'Outcome', header: true }
],
...results.map(result => [
result.rootProject,
result.rootProjectName,
result.requestedTasks,
result.gradleVersion,
renderOutcome(result)
@ -66383,6 +66371,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.complete = exports.setup = void 0;
const core = __importStar(__nccwpck_require__(2186));
const exec = __importStar(__nccwpck_require__(1514));
const fs = __importStar(__nccwpck_require__(7147));
const path = __importStar(__nccwpck_require__(1017));
const os = __importStar(__nccwpck_require__(2037));
const caches = __importStar(__nccwpck_require__(3800));
@ -66414,11 +66404,14 @@ function complete() {
core.info('Gradle setup post-action only performed for first gradle-build-action step in workflow.');
return;
}
const buildResults = (0, job_summary_1.loadBuildResults)();
core.info('Stopping all Gradle daemons');
yield stopAllDaemons(getUniqueGradleHomes(buildResults));
core.info('In final post-action step, saving state and writing summary');
const cacheListener = cache_reporting_1.CacheListener.rehydrate(core.getState(CACHE_LISTENER));
const gradleUserHome = core.getState(GRADLE_USER_HOME);
yield caches.save(gradleUserHome, cacheListener);
(0, job_summary_1.writeJobSummary)(cacheListener);
(0, job_summary_1.writeJobSummary)(buildResults, cacheListener);
});
}
exports.complete = complete;
@ -66429,6 +66422,28 @@ function determineGradleUserHome(rootDir) {
}
return path.resolve(os.homedir(), '.gradle');
}
function getUniqueGradleHomes(buildResults) {
const gradleHomes = buildResults.map(buildResult => buildResult.gradleHomeDir);
return Array.from(new Set(gradleHomes));
}
function stopAllDaemons(gradleHomes) {
return __awaiter(this, void 0, void 0, function* () {
const executions = [];
const args = ['--stop'];
for (const gradleHome of gradleHomes) {
const executable = path.resolve(gradleHome, 'bin', 'gradle');
if (!fs.existsSync(executable)) {
core.warning(`Gradle executable not found at ${executable}. Could not stop Gradle daemons.`);
continue;
}
core.info(`Stopping Gradle daemons in ${gradleHome}`);
executions.push(exec.exec(executable, args, {
ignoreReturnCode: true
}));
}
yield Promise.all(executions);
});
}
/***/ }),

File diff suppressed because one or more lines are too long

61
dist/post/index.js vendored
View file

@ -63894,7 +63894,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.GradleStateCache = exports.PROJECT_ROOTS_FILE = exports.META_FILE_DIR = void 0;
exports.GradleStateCache = exports.META_FILE_DIR = void 0;
const core = __importStar(__nccwpck_require__(2186));
const exec = __importStar(__nccwpck_require__(1514));
const path_1 = __importDefault(__nccwpck_require__(1017));
@ -63903,7 +63903,6 @@ const cache_utils_1 = __nccwpck_require__(1678);
const cache_extract_entries_1 = __nccwpck_require__(6161);
const RESTORED_CACHE_KEY_KEY = 'restored-cache-key';
exports.META_FILE_DIR = '.gradle-build-action';
exports.PROJECT_ROOTS_FILE = 'project-roots.txt';
const INCLUDE_PATHS_PARAMETER = 'gradle-home-cache-includes';
const EXCLUDE_PATHS_PARAMETER = 'gradle-home-cache-excludes';
class GradleStateCache {
@ -64014,14 +64013,7 @@ class GradleStateCache {
return path_1.default.resolve(this.gradleUserHome, rawPath);
}
initializeGradleUserHome(gradleUserHome, initScriptsDir) {
const propertiesFile = path_1.default.resolve(gradleUserHome, 'gradle.properties');
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'
];
const initScriptFilenames = ['build-result-capture.init.gradle', 'build-result-capture-service.plugin.groovy'];
for (const initScriptFilename of initScriptFilenames) {
const initScriptContent = this.readResourceAsString(initScriptFilename);
const initScriptPath = path_1.default.resolve(initScriptsDir, initScriptFilename);
@ -64111,6 +64103,7 @@ const core = __importStar(__nccwpck_require__(2186));
const glob = __importStar(__nccwpck_require__(8090));
const cache_base_1 = __nccwpck_require__(7591);
const cache_utils_1 = __nccwpck_require__(1678);
const job_summary_1 = __nccwpck_require__(7345);
const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE';
class ExtractedCacheEntry {
constructor(artifactType, pattern, cacheKey) {
@ -64343,14 +64336,9 @@ class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
});
}
getProjectRoots() {
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 [];
}
const projectRoots = fs_1.default.readFileSync(projectList, 'utf-8');
core.info(`Found project roots '${projectRoots}' in ${projectList}`);
return projectRoots.trim().split('\n');
const buildResults = (0, job_summary_1.loadBuildResults)();
const projectRootDirs = buildResults.map(x => x.rootProjectDir);
return [...new Set(projectRootDirs)];
}
}
exports.ConfigurationCacheEntryExtractor = ConfigurationCacheEntryExtractor;
@ -64904,14 +64892,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.writeJobSummary = void 0;
exports.loadBuildResults = exports.writeJobSummary = void 0;
const core = __importStar(__nccwpck_require__(2186));
const fs_1 = __importDefault(__nccwpck_require__(7147));
const path_1 = __importDefault(__nccwpck_require__(1017));
const cache_reporting_1 = __nccwpck_require__(6674);
function writeJobSummary(cacheListener) {
function writeJobSummary(buildResults, cacheListener) {
core.info('Writing job summary');
const buildResults = loadBuildResults();
if (buildResults.length === 0) {
core.debug('No Gradle build results found. Summary table will not be generated.');
}
@ -64933,6 +64920,7 @@ function loadBuildResults() {
return JSON.parse(content);
});
}
exports.loadBuildResults = loadBuildResults;
function writeSummaryTable(results) {
core.summary.addHeading('Gradle Builds', 3);
core.summary.addTable([
@ -64943,7 +64931,7 @@ function writeSummaryTable(results) {
{ data: 'Outcome', header: true }
],
...results.map(result => [
result.rootProject,
result.rootProjectName,
result.requestedTasks,
result.gradleVersion,
renderOutcome(result)
@ -65067,6 +65055,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.complete = exports.setup = void 0;
const core = __importStar(__nccwpck_require__(2186));
const exec = __importStar(__nccwpck_require__(1514));
const fs = __importStar(__nccwpck_require__(7147));
const path = __importStar(__nccwpck_require__(1017));
const os = __importStar(__nccwpck_require__(2037));
const caches = __importStar(__nccwpck_require__(3800));
@ -65098,11 +65088,14 @@ function complete() {
core.info('Gradle setup post-action only performed for first gradle-build-action step in workflow.');
return;
}
const buildResults = (0, job_summary_1.loadBuildResults)();
core.info('Stopping all Gradle daemons');
yield stopAllDaemons(getUniqueGradleHomes(buildResults));
core.info('In final post-action step, saving state and writing summary');
const cacheListener = cache_reporting_1.CacheListener.rehydrate(core.getState(CACHE_LISTENER));
const gradleUserHome = core.getState(GRADLE_USER_HOME);
yield caches.save(gradleUserHome, cacheListener);
(0, job_summary_1.writeJobSummary)(cacheListener);
(0, job_summary_1.writeJobSummary)(buildResults, cacheListener);
});
}
exports.complete = complete;
@ -65113,6 +65106,28 @@ function determineGradleUserHome(rootDir) {
}
return path.resolve(os.homedir(), '.gradle');
}
function getUniqueGradleHomes(buildResults) {
const gradleHomes = buildResults.map(buildResult => buildResult.gradleHomeDir);
return Array.from(new Set(gradleHomes));
}
function stopAllDaemons(gradleHomes) {
return __awaiter(this, void 0, void 0, function* () {
const executions = [];
const args = ['--stop'];
for (const gradleHome of gradleHomes) {
const executable = path.resolve(gradleHome, 'bin', 'gradle');
if (!fs.existsSync(executable)) {
core.warning(`Gradle executable not found at ${executable}. Could not stop Gradle daemons.`);
continue;
}
core.info(`Stopping Gradle daemons in ${gradleHome}`);
executions.push(exec.exec(executable, args, {
ignoreReturnCode: true
}));
}
yield Promise.all(executions);
});
}
/***/ }),

File diff suppressed because one or more lines are too long

View file

@ -9,7 +9,6 @@ import {ConfigurationCacheEntryExtractor, GradleHomeEntryExtractor} from './cach
const RESTORED_CACHE_KEY_KEY = 'restored-cache-key'
export const META_FILE_DIR = '.gradle-build-action'
export const PROJECT_ROOTS_FILE = 'project-roots.txt'
const INCLUDE_PATHS_PARAMETER = 'gradle-home-cache-includes'
const EXCLUDE_PATHS_PARAMETER = 'gradle-home-cache-excludes'
@ -167,15 +166,7 @@ export class GradleStateCache {
}
private initializeGradleUserHome(gradleUserHome: string, initScriptsDir: string): void {
const propertiesFile = path.resolve(gradleUserHome, 'gradle.properties')
fs.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'
]
const initScriptFilenames = ['build-result-capture.init.gradle', 'build-result-capture-service.plugin.groovy']
for (const initScriptFilename of initScriptFilenames) {
const initScriptContent = this.readResourceAsString(initScriptFilename)
const initScriptPath = path.resolve(initScriptsDir, initScriptFilename)

View file

@ -3,7 +3,7 @@ import fs from 'fs'
import * as core from '@actions/core'
import * as glob from '@actions/glob'
import {META_FILE_DIR, PROJECT_ROOTS_FILE} from './cache-base'
import {META_FILE_DIR} from './cache-base'
import {CacheEntryListener, CacheListener} from './cache-reporting'
import {
cacheDebug,
@ -14,6 +14,7 @@ import {
saveCache,
tryDelete
} from './cache-utils'
import {loadBuildResults} from './job-summary'
const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE'
@ -387,13 +388,8 @@ 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(process.env['RUNNER_TEMP']!, PROJECT_ROOTS_FILE)
if (!fs.existsSync(projectList)) {
core.info(`Missing project list file ${projectList}`)
return []
}
const projectRoots = fs.readFileSync(projectList, 'utf-8')
core.info(`Found project roots '${projectRoots}' in ${projectList}`)
return projectRoots.trim().split('\n')
const buildResults = loadBuildResults()
const projectRootDirs = buildResults.map(x => x.rootProjectDir)
return [...new Set(projectRootDirs)] // Remove duplicates
}
}

View file

@ -3,18 +3,19 @@ import fs from 'fs'
import path from 'path'
import {logCachingReport, CacheListener} from './cache-reporting'
interface BuildResult {
get rootProject(): string
export interface BuildResult {
get rootProjectName(): string
get rootProjectDir(): string
get requestedTasks(): string
get gradleVersion(): string
get gradleHomeDir(): string
get buildFailed(): boolean
get buildScanUri(): string
}
export function writeJobSummary(cacheListener: CacheListener): void {
export function writeJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): void {
core.info('Writing job summary')
const buildResults = loadBuildResults()
if (buildResults.length === 0) {
core.debug('No Gradle build results found. Summary table will not be generated.')
} else {
@ -26,7 +27,7 @@ export function writeJobSummary(cacheListener: CacheListener): void {
core.summary.write()
}
function loadBuildResults(): BuildResult[] {
export function loadBuildResults(): BuildResult[] {
const buildResultsDir = path.resolve(process.env['RUNNER_TEMP']!, '.build-results')
if (!fs.existsSync(buildResultsDir)) {
return []
@ -50,7 +51,7 @@ function writeSummaryTable(results: BuildResult[]): void {
{data: 'Outcome', header: true}
],
...results.map(result => [
result.rootProject,
result.rootProjectName,
result.requestedTasks,
result.gradleVersion,
renderOutcome(result)

View file

@ -6,8 +6,10 @@ import org.gradle.util.GradleVersion
// 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().getRootProjectName().set(gradle.rootProject.name)
spec.getParameters().getRootProjectDir().set(gradle.rootProject.rootDir.absolutePath)
spec.getParameters().getRequestedTasks().set(gradle.startParameter.taskNames.join(" "))
spec.getParameters().getGradleHomeDir().set(gradle.gradleHomeDir.absolutePath)
spec.getParameters().getInvocationId().set(gradle.ext.invocationId)
})
@ -17,8 +19,10 @@ projectsEvaluated {
abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder.Params>, OperationCompletionListener, AutoCloseable {
private boolean buildFailed = false
interface Params extends BuildServiceParameters {
Property<String> getRootProject()
Property<String> getRootProjectName()
Property<String> getRootProjectDir()
Property<String> getRequestedTasks()
Property<String> getGradleHomeDir()
Property<String> getInvocationId()
}
@ -31,9 +35,11 @@ abstract class BuildResultsRecorder implements BuildService<BuildResultsRecorder
@Override
public void close() {
def buildResults = [
rootProject: getParameters().getRootProject().get(),
rootProjectName: getParameters().getRootProjectName().get(),
rootProjectDir: getParameters().getRootProjectDir().get(),
requestedTasks: getParameters().getRequestedTasks().get(),
gradleVersion: GradleVersion.current().version,
gradleHomeDir: getParameters().getGradleHomeDir().get(),
buildFailed: buildFailed,
buildScanUri: null
]

View file

@ -18,7 +18,7 @@ if (isTopLevelBuild) {
settingsEvaluated { settings ->
// The `buildScanPublished` hook is the only way to capture the build scan URI.
if (settings.pluginManager.hasPlugin("com.gradle.enterprise")) {
captureUsingBuildScanPublished(settings.extensions["gradleEnterprise"].buildScan, settings.rootProject.name, invocationId)
captureUsingBuildScanPublished(settings.extensions["gradleEnterprise"].buildScan, settings.rootProject, invocationId)
}
// We also need to add hooks in case the plugin is applied but no build scan is published
if (useBuildService) {
@ -30,7 +30,7 @@ if (isTopLevelBuild) {
} else if (atLeastGradle3) {
projectsEvaluated { gradle ->
if (gradle.rootProject.pluginManager.hasPlugin("com.gradle.build-scan")) {
captureUsingBuildScanPublished(gradle.rootProject.extensions["buildScan"], gradle.rootProject.name, invocationId)
captureUsingBuildScanPublished(gradle.rootProject.extensions["buildScan"], gradle.rootProject, invocationId)
}
// We need to capture in buildFinished in case the plugin is applied but no build scan is published
captureUsingBuildFinished(gradle, invocationId)
@ -38,10 +38,13 @@ if (isTopLevelBuild) {
}
}
def captureUsingBuildScanPublished(buildScanExtension, rootProjectName, invocationId) {
def captureUsingBuildScanPublished(buildScanExtension, rootProject, invocationId) {
buildScanExtension.with {
def requestedTasks = gradle.startParameter.taskNames.join(" ")
def rootProjectName = rootProject.name
def rootProjectDir = rootProject.projectDir.absolutePath
def gradleVersion = GradleVersion.current().version
def gradleHomeDir = gradle.gradleHomeDir.absolutePath
def buildFailed = false
buildFinished { result ->
@ -52,9 +55,11 @@ def captureUsingBuildScanPublished(buildScanExtension, rootProjectName, invocati
def buildScanUri = buildScan.buildScanUri.toASCIIString()
def buildResults = [
rootProject: rootProjectName,
rootProjectName: rootProjectName,
rootProjectDir: rootProjectDir,
requestedTasks: requestedTasks,
gradleVersion: gradleVersion,
gradleHomeDir: gradleHomeDir,
buildFailed: buildFailed,
buildScanUri: buildScanUri
]
@ -78,9 +83,11 @@ def captureUsingBuildScanPublished(buildScanExtension, rootProjectName, invocati
def captureUsingBuildFinished(gradle, invocationId) {
gradle.buildFinished { result ->
def buildResults = [
rootProject: gradle.rootProject.name,
rootProjectName: gradle.rootProject.name,
rootProjectDir: gradle.rootProject.rootDir.absolutePath,
requestedTasks: gradle.startParameter.taskNames.join(" "),
gradleVersion: GradleVersion.current().version,
gradleHomeDir: gradle.gradleHomeDir.absolutePath,
buildFailed: result.failure != null,
buildScanUri: null
]

View file

@ -1,10 +0,0 @@
import org.gradle.util.GradleVersion
// Only run against root build. Do not run against included builds.
def isTopLevelBuild = gradle.getParent() == null
// Only record configuration-cache entries for Gradle 7+
def isAtLeastGradle7 = GradleVersion.current() >= GradleVersion.version('7.0')
if (isTopLevelBuild && isAtLeastGradle7) {
apply from: 'project-root-capture.plugin.groovy'
}

View file

@ -1,40 +0,0 @@
/*
* Capture the build root directory for each executed Gradle build.
* This is used to save/restore configuration-cache files, so:
* - The implementation only makes sense if it's config-cache compatible
* - We only need to support Gradle 7+
*/
import org.gradle.tooling.events.*
settingsEvaluated { settings ->
def rootDir = settings.rootDir.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);
spec.getParameters().getRootListLocation().set(rootListLocation);
})
gradle.services.get(BuildEventsListenerRegistry).onTaskCompletion(projectTracker)
}
abstract class ProjectTracker implements BuildService<ProjectTracker.Params>, OperationCompletionListener, AutoCloseable {
interface Params extends BuildServiceParameters {
Property<String> getRootDir();
Property<String> getRootListLocation();
}
public void onFinish(FinishEvent finishEvent) {}
@Override
public void close() {
def rootDir = getParameters().getRootDir().get()
def rootDirEntry = rootDir + '\n'
def rootListFile = new File(getParameters().getRootListLocation().get())
if (!rootListFile.exists() || !rootListFile.text.contains(rootDirEntry)) {
rootListFile << rootDirEntry
}
}
}

View file

@ -1,9 +1,12 @@
import * as core from '@actions/core'
import * as exec from '@actions/exec'
import * as fs from 'fs'
import * as path from 'path'
import * as os from 'os'
import * as caches from './caches'
import {CacheListener} from './cache-reporting'
import {writeJobSummary} from './job-summary'
import {BuildResult, loadBuildResults, writeJobSummary} from './job-summary'
const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED'
const GRADLE_USER_HOME = 'GRADLE_USER_HOME'
@ -38,13 +41,18 @@ export async function complete(): Promise<void> {
return
}
const buildResults = loadBuildResults()
core.info('Stopping all Gradle daemons')
await stopAllDaemons(getUniqueGradleHomes(buildResults))
core.info('In final post-action step, saving state and writing summary')
const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER))
const gradleUserHome = core.getState(GRADLE_USER_HOME)
await caches.save(gradleUserHome, cacheListener)
writeJobSummary(cacheListener)
writeJobSummary(buildResults, cacheListener)
}
function determineGradleUserHome(rootDir: string): string {
@ -55,3 +63,28 @@ function determineGradleUserHome(rootDir: string): string {
return path.resolve(os.homedir(), '.gradle')
}
function getUniqueGradleHomes(buildResults: BuildResult[]): string[] {
const gradleHomes = buildResults.map(buildResult => buildResult.gradleHomeDir)
return Array.from(new Set(gradleHomes))
}
async function stopAllDaemons(gradleHomes: string[]): Promise<void> {
const executions: Promise<number>[] = []
const args = ['--stop']
for (const gradleHome of gradleHomes) {
const executable = path.resolve(gradleHome, 'bin', 'gradle')
if (!fs.existsSync(executable)) {
core.warning(`Gradle executable not found at ${executable}. Could not stop Gradle daemons.`)
continue
}
core.info(`Stopping Gradle daemons in ${gradleHome}`)
executions.push(
exec.exec(executable, args, {
ignoreReturnCode: true
})
)
}
await Promise.all(executions)
}

View file

@ -120,9 +120,11 @@ class TestBuildResultRecorder extends BaseInitScriptTest {
void assertResults(String task, TestGradleVersion testGradleVersion, boolean hasFailure, boolean hasBuildScan) {
def results = new JsonSlurper().parse(buildResultFile)
assert results['rootProject'] == ROOT_PROJECT_NAME
assert results['rootProjectName'] == ROOT_PROJECT_NAME
assert results['rootProjectDir'] == testProjectDir.canonicalPath
assert results['requestedTasks'] == task
assert results['gradleVersion'] == testGradleVersion.gradleVersion.version
assert results['gradleHomeDir'] != null
assert results['buildFailed'] == hasFailure
assert results['buildScanUri'] == (hasBuildScan ? "${mockScansServer.address}s/${PUBLIC_BUILD_SCAN_ID}" : null)
}

View file

@ -1,77 +0,0 @@
package com.gradle.gradlebuildaction
import static org.junit.Assume.assumeTrue
class TestProjectRootCapture extends BaseInitScriptTest {
def initScript = 'project-root-capture.init.gradle'
def "captures project root on #testGradleVersion"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
when:
run(['help'], initScript, testGradleVersion.gradleVersion)
then:
assertCapturesProjectRoot()
where:
testGradleVersion << CONFIGURATION_CACHE_VERSIONS
}
def "captures project root on #testGradleVersion when build fails"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
addFailingTaskToBuild()
when:
runAndFail(['expectFailure'], initScript, testGradleVersion.gradleVersion)
then:
assertCapturesProjectRoot()
where:
testGradleVersion << CONFIGURATION_CACHE_VERSIONS
}
def "captures project root on #testGradleVersion with --configuration-cache"() {
assumeTrue testGradleVersion.compatibleWithCurrentJvm
when:
run(['help', '--configuration-cache'], initScript, testGradleVersion.gradleVersion)
then:
assertCapturesProjectRoot()
assert projectRootList.delete()
when:
run(['help', '--configuration-cache'], initScript, testGradleVersion.gradleVersion)
then:
assertCapturesProjectRoot()
where:
testGradleVersion << CONFIGURATION_CACHE_VERSIONS
}
def "has no effect on #testVersion"() {
assumeTrue testVersion.compatibleWithCurrentJvm
when:
run(['help'], initScript, testVersion.gradleVersion)
then:
assert !projectRootList.exists()
where:
testVersion << (ALL_VERSIONS - CONFIGURATION_CACHE_VERSIONS)
}
private void assertCapturesProjectRoot() {
assert projectRootList.exists()
assert new File(projectRootList.text.trim()).canonicalPath == testProjectDir.canonicalPath
}
private File getProjectRootList() {
new File(testProjectDir, 'project-roots.txt')
}
}