diff --git a/src/build-results.ts b/src/build-results.ts new file mode 100644 index 0000000..4af5204 --- /dev/null +++ b/src/build-results.ts @@ -0,0 +1,27 @@ +import * as fs from 'fs' +import * as path from 'path' + +export interface BuildResult { + get rootProjectName(): string + get rootProjectDir(): string + get requestedTasks(): string + get gradleVersion(): string + get gradleHomeDir(): string + get buildFailed(): boolean + get buildScanUri(): string + get buildScanFailed(): boolean +} + +export function loadBuildResults(): BuildResult[] { + const buildResultsDir = path.resolve(process.env['RUNNER_TEMP']!, '.build-results') + if (!fs.existsSync(buildResultsDir)) { + return [] + } + + return fs.readdirSync(buildResultsDir).map(file => { + // Every file in the .build-results dir should be a BuildResults JSON + const filePath = path.join(buildResultsDir, file) + const content = fs.readFileSync(filePath, 'utf8') + return JSON.parse(content) as BuildResult + }) +} diff --git a/src/cache-extract-entries.ts b/src/cache-extract-entries.ts index 576d208..cb6b824 100644 --- a/src/cache-extract-entries.ts +++ b/src/cache-extract-entries.ts @@ -14,7 +14,7 @@ import { saveCache, tryDelete } from './cache-utils' -import {loadBuildResults} from './job-summary' +import {loadBuildResults} from './build-results' const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE' diff --git a/src/cache-reporting.ts b/src/cache-reporting.ts index b5ec2ab..5068519 100644 --- a/src/cache-reporting.ts +++ b/src/cache-reporting.ts @@ -7,9 +7,9 @@ import * as cache from '@actions/cache' */ export class CacheListener { cacheEntries: CacheEntryListener[] = [] - isCacheReadOnly = false - isCacheWriteOnly = false - isCacheDisabled = false + cacheReadOnly = false + cacheWriteOnly = false + cacheDisabled = false get fullyRestored(): boolean { return this.cacheEntries.every(x => !x.wasRequestedButNotRestored()) @@ -17,9 +17,9 @@ export class CacheListener { get cacheStatus(): string { if (!cache.isFeatureAvailable()) return 'not available' - if (this.isCacheDisabled) return 'disabled' - if (this.isCacheWriteOnly) return 'write-only' - if (this.isCacheReadOnly) return 'read-only' + if (this.cacheDisabled) return 'disabled' + if (this.cacheWriteOnly) return 'write-only' + if (this.cacheReadOnly) return 'read-only' return 'enabled' } @@ -156,17 +156,17 @@ function renderEntryDetails(listener: CacheListener): string { Requested Key : ${entry.requestedKey ?? ''} Restored Key : ${entry.restoredKey ?? ''} Size: ${formatSize(entry.restoredSize)} - ${getRestoredMessage(entry, listener.isCacheWriteOnly)} + ${getRestoredMessage(entry, listener.cacheWriteOnly)} Saved Key : ${entry.savedKey ?? ''} Size: ${formatSize(entry.savedSize)} - ${getSavedMessage(entry, listener.isCacheReadOnly)} + ${getSavedMessage(entry, listener.cacheReadOnly)} ` ) .join('---\n') } -function getRestoredMessage(entry: CacheEntryListener, isCacheWriteOnly: boolean): string { - if (isCacheWriteOnly) { +function getRestoredMessage(entry: CacheEntryListener, cacheWriteOnly: boolean): string { + if (cacheWriteOnly) { return '(Entry not restored: cache is write-only)' } if (entry.restoredKey === undefined) { @@ -178,12 +178,12 @@ function getRestoredMessage(entry: CacheEntryListener, isCacheWriteOnly: boolean return '(Entry restored: partial match found)' } -function getSavedMessage(entry: CacheEntryListener, isCacheReadOnly: boolean): string { +function getSavedMessage(entry: CacheEntryListener, cacheReadOnly: boolean): string { if (entry.unsaved) { return `(Entry not saved: ${entry.unsaved})` } if (entry.savedKey === undefined) { - if (isCacheReadOnly) { + if (cacheReadOnly) { return '(Entry not saved: cache is read-only)' } return '(Entry not saved: reason unknown)' diff --git a/src/caches.ts b/src/caches.ts index b77f8f0..51e3f70 100644 --- a/src/caches.ts +++ b/src/caches.ts @@ -19,7 +19,7 @@ export async function restore(gradleUserHome: string, cacheListener: CacheListen core.info('Cache is disabled: will not restore state from previous builds.') // Initialize the Gradle User Home even when caching is disabled. gradleStateCache.init() - cacheListener.isCacheDisabled = true + cacheListener.cacheDisabled = true return } @@ -36,7 +36,7 @@ export async function restore(gradleUserHome: string, cacheListener: CacheListen if (isCacheWriteOnly()) { core.info('Cache is write-only: will not restore from cache.') - cacheListener.isCacheWriteOnly = true + cacheListener.cacheWriteOnly = true return } @@ -46,13 +46,19 @@ export async function restore(gradleUserHome: string, cacheListener: CacheListen } export async function save(gradleUserHome: string, cacheListener: CacheListener): Promise { - if (!shouldSaveCaches()) { + if (isCacheDisabled()) { + core.info('Cache is disabled: will not save state for later builds.') + return + } + + if (!core.getState(CACHE_RESTORED_VAR)) { + core.info('Cache will not be saved: not restored in main action step.') return } if (isCacheReadOnly()) { core.info('Cache is read-only: will not save state for use in subsequent builds.') - cacheListener.isCacheReadOnly = true + cacheListener.cacheReadOnly = true return } @@ -60,17 +66,3 @@ export async function save(gradleUserHome: string, cacheListener: CacheListener) return new GradleStateCache(gradleUserHome).save(cacheListener) }) } - -function shouldSaveCaches(): boolean { - if (isCacheDisabled()) { - core.info('Cache is disabled: will not save state for later builds.') - return false - } - - if (!core.getState(CACHE_RESTORED_VAR)) { - core.info('Cache will not be saved: not restored in main action step.') - return false - } - - return true -} diff --git a/src/daemon-controller.ts b/src/daemon-controller.ts new file mode 100644 index 0000000..b063bc1 --- /dev/null +++ b/src/daemon-controller.ts @@ -0,0 +1,36 @@ +import * as core from '@actions/core' +import * as exec from '@actions/exec' +import * as fs from 'fs' +import * as path from 'path' +import {BuildResult} from './build-results' + +export class DaemonController { + private readonly gradleHomes + + constructor(buildResults: BuildResult[]) { + const allHomes = buildResults.map(buildResult => buildResult.gradleHomeDir) + this.gradleHomes = Array.from(new Set(allHomes)) + } + + async stopAllDaemons(): Promise { + core.info('Stopping all Gradle daemons') + + const executions: Promise[] = [] + const args = ['--stop'] + + for (const gradleHome of this.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 for ${gradleHome}`) + executions.push( + exec.exec(executable, args, { + ignoreReturnCode: true + }) + ) + } + await Promise.all(executions) + } +} diff --git a/src/job-summary.ts b/src/job-summary.ts index f7fc425..afa0f50 100644 --- a/src/job-summary.ts +++ b/src/job-summary.ts @@ -1,19 +1,7 @@ import * as core from '@actions/core' -import fs from 'fs' -import path from 'path' +import {BuildResult} from './build-results' import {writeCachingReport, CacheListener, logCachingReport} from './cache-reporting' -export interface BuildResult { - get rootProjectName(): string - get rootProjectDir(): string - get requestedTasks(): string - get gradleVersion(): string - get gradleHomeDir(): string - get buildFailed(): boolean - get buildScanUri(): string - get buildScanFailed(): boolean -} - export async function writeJobSummary(buildResults: BuildResult[], cacheListener: CacheListener): Promise { core.info('Writing job summary') @@ -38,20 +26,6 @@ export async function logJobSummary(buildResults: BuildResult[], cacheListener: logCachingReport(cacheListener) } -export function loadBuildResults(): BuildResult[] { - const buildResultsDir = path.resolve(process.env['RUNNER_TEMP']!, '.build-results') - if (!fs.existsSync(buildResultsDir)) { - return [] - } - - return fs.readdirSync(buildResultsDir).map(file => { - // Every file in the .build-results dir should be a BuildResults JSON - const filePath = path.join(buildResultsDir, file) - const content = fs.readFileSync(filePath, 'utf8') - return JSON.parse(content) as BuildResult - }) -} - function writeSummaryTable(results: BuildResult[]): void { core.summary.addHeading('Gradle Builds', 3) diff --git a/src/setup-gradle.ts b/src/setup-gradle.ts index cf25430..1ae3197 100644 --- a/src/setup-gradle.ts +++ b/src/setup-gradle.ts @@ -1,13 +1,14 @@ import * as core from '@actions/core' import * as exec from '@actions/exec' import {SUMMARY_ENV_VAR} from '@actions/core/lib/summary' -import * as fs from 'fs' import * as path from 'path' import * as os from 'os' import * as caches from './caches' +import {logJobSummary, writeJobSummary} from './job-summary' +import {loadBuildResults} from './build-results' import {CacheListener} from './cache-reporting' -import {BuildResult, loadBuildResults, logJobSummary, writeJobSummary} from './job-summary' +import {DaemonController} from './daemon-controller' const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED' const GRADLE_USER_HOME = 'GRADLE_USER_HOME' @@ -50,16 +51,15 @@ export async function complete(): Promise { core.info('Gradle setup post-action only performed for first gradle-build-action step in workflow.') return } + core.info('In final post-action step, saving state and writing summary') 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) + const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER)) + const daemonController = new DaemonController(buildResults) + + await daemonController.stopAllDaemons() await caches.save(gradleUserHome, cacheListener) if (shouldGenerateJobSummary()) { @@ -94,28 +94,3 @@ async function determineUserHome(): Promise { core.debug(`Determined user.home from java -version output: '${userHome}'`) return userHome } - -function getUniqueGradleHomes(buildResults: BuildResult[]): string[] { - const gradleHomes = buildResults.map(buildResult => buildResult.gradleHomeDir) - return Array.from(new Set(gradleHomes)) -} - -async function stopAllDaemons(gradleHomes: string[]): Promise { - const executions: Promise[] = [] - 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) -}