From d4e24dfc10f735809202e48ea5aee6c741cf04a8 Mon Sep 17 00:00:00 2001 From: daz Date: Wed, 3 Jan 2024 21:04:20 -0700 Subject: [PATCH] Register pre-installed JDKs in .m2/toolchains.xml Since adding these to the `org.gradle.java.installations.fromEnv` property is problematic (#1024), this mechanism allows the default toolchains to be discovered by Gradle via a different mechanism. The default JDK installations are added to `~/.m2/toolchains.xml` such that they are discoverable by Gradle toolchain support. The `setup-java` action also writes to this file, so we merge with any existing content: this allows both pre-installed and "setup" JDKs to be automatically detected by Gradle. --- .../integ-test-detect-java-toolchains.yml | 44 ++++---------- src/cache-base.ts | 57 ++++++++++++++----- src/caches.ts | 7 ++- src/resources/toolchains.xml | 44 ++++++++++++++ src/setup-gradle.ts | 10 +++- 5 files changed, 110 insertions(+), 52 deletions(-) create mode 100644 src/resources/toolchains.xml diff --git a/.github/workflows/integ-test-detect-java-toolchains.yml b/.github/workflows/integ-test-detect-java-toolchains.yml index 1d0efc1..8277d20 100644 --- a/.github/workflows/integ-test-detect-java-toolchains.yml +++ b/.github/workflows/integ-test-detect-java-toolchains.yml @@ -21,6 +21,7 @@ jobs: # Test that pre-installed runner JDKs are detected pre-installed-toolchains: strategy: + fail-fast: false matrix: os: ${{fromJSON(inputs.runner-os)}} runs-on: ${{ matrix.os }} @@ -35,7 +36,7 @@ jobs: shell: bash working-directory: .github/workflow-samples/groovy-dsl run: | - gradle -q javaToolchains > output.txt + gradle --info javaToolchains > output.txt cat output.txt - name: Verify detected toolchains shell: bash @@ -44,10 +45,12 @@ jobs: grep -q 'Eclipse Temurin JDK 1.8' output.txt || (echo "::error::Did not detect preinstalled JDK 1.8" && exit 1) grep -q 'Eclipse Temurin JDK 11' output.txt || (echo "::error::Did not detect preinstalled JDK 11" && exit 1) grep -q 'Eclipse Temurin JDK 17' output.txt || (echo "::error::Did not detect preinstalled JDK 17" && exit 1) + grep -q 'Eclipse Temurin JDK 21' output.txt || (echo "::error::Did not detect preinstalled JDK 21" && exit 1) # Test that JDKs provisioned by setup-java are detected setup-java-installed-toolchain: strategy: + fail-fast: false matrix: os: ${{fromJSON(inputs.runner-os)}} runs-on: ${{ matrix.os }} @@ -72,42 +75,19 @@ jobs: shell: bash working-directory: .github/workflow-samples/groovy-dsl run: | - gradle -q javaToolchains > output.txt + gradle --info javaToolchains > output.txt cat output.txt - - name: Verify detected toolchains + - name: Verify setup JDKs are detected shell: bash working-directory: .github/workflow-samples/groovy-dsl run: | grep -q 'Eclipse Temurin JDK 16' output.txt || (echo "::error::Did not detect setup-java installed JDK 16" && exit 1) grep -q 'Eclipse Temurin JDK 20' output.txt || (echo "::error::Did not detect setup-java installed JDK 20" && exit 1) - - # Test that predefined JDK detection property is not overwritten by action - check-no-overwrite: - strategy: - matrix: - os: ${{fromJSON(inputs.runner-os)}} - runs-on: ${{ matrix.os }} - steps: - - name: Checkout sources - uses: actions/checkout@v4 - - name: Download distribution if required - uses: ./.github/actions/download-dist - - name: Configure java installations env var in Gradle User Home + - name: Verify pre-installed toolchains are detected shell: bash + working-directory: .github/workflow-samples/groovy-dsl run: | - mkdir -p ~/.gradle - echo "org.gradle.java.installations.fromEnv=XXXXX" > ~/.gradle/gradle.properties - - name: Setup Gradle - uses: ./ - - name: Check gradle.properties - shell: bash - run: | - cat ~/.gradle/gradle.properties - if grep -q 'org.gradle.java.installations.fromEnv=JAVA_HOME' ~/.gradle/gradle.properties ; then - echo 'Found overwritten fromEnv' - exit 1 - fi - if ! grep -q 'org.gradle.java.installations.fromEnv=XXXXX' ~/.gradle/gradle.properties ; then - echo 'Did NOT find original fromEnv' - exit 1 - fi + grep -q 'Eclipse Temurin JDK 1.8' output.txt || (echo "::error::Did not detect preinstalled JDK 1.8" && exit 1) + grep -q 'Eclipse Temurin JDK 11' output.txt || (echo "::error::Did not detect preinstalled JDK 11" && exit 1) + grep -q 'Eclipse Temurin JDK 17' output.txt || (echo "::error::Did not detect preinstalled JDK 17" && exit 1) + grep -q 'Eclipse Temurin JDK 21' output.txt || (echo "::error::Did not detect preinstalled JDK 21" && exit 1) diff --git a/src/cache-base.ts b/src/cache-base.ts index d1a4093..d63ee3f 100644 --- a/src/cache-base.ts +++ b/src/cache-base.ts @@ -17,23 +17,18 @@ export class GradleStateCache { private cacheName: string private cacheDescription: string + protected readonly userHome: string protected readonly gradleUserHome: string - constructor(gradleUserHome: string) { + constructor(userHome: string, gradleUserHome: string) { + this.userHome = userHome this.gradleUserHome = gradleUserHome this.cacheName = 'gradle' this.cacheDescription = 'Gradle User Home' } init(): void { - // Copy init-scripts to Gradle User Home - const actionCacheDir = path.resolve(this.gradleUserHome, '.gradle-build-action') - fs.mkdirSync(actionCacheDir, {recursive: true}) - - const initScriptsDir = path.resolve(this.gradleUserHome, 'init.d') - fs.mkdirSync(initScriptsDir, {recursive: true}) - - this.initializeGradleUserHome(this.gradleUserHome, initScriptsDir) + this.initializeGradleUserHome() // Export the GRADLE_ENCRYPTION_KEY variable if provided const encryptionKey = params.getCacheEncryptionKey() @@ -188,8 +183,21 @@ export class GradleStateCache { return path.resolve(this.gradleUserHome, rawPath) } - private initializeGradleUserHome(gradleUserHome: string, initScriptsDir: string): void { - // Copy init scripts from src/resources + private initializeGradleUserHome(): void { + // Create a directory for storing action metadata + const actionCacheDir = path.resolve(this.gradleUserHome, '.gradle-build-action') + fs.mkdirSync(actionCacheDir, {recursive: true}) + + this.copyInitScripts() + + // Copy the default toolchain definitions to `~/.m2/toolchains.xml` + this.registerToolchains() + } + + private copyInitScripts(): void { + // Copy init scripts from src/resources to Gradle UserHome + const initScriptsDir = path.resolve(this.gradleUserHome, 'init.d') + fs.mkdirSync(initScriptsDir, {recursive: true}) const initScriptFilenames = [ 'gradle-build-action.build-result-capture.init.gradle', 'gradle-build-action.build-result-capture-service.plugin.groovy', @@ -198,15 +206,36 @@ export class GradleStateCache { 'gradle-build-action.inject-gradle-enterprise.init.gradle' ] for (const initScriptFilename of initScriptFilenames) { - const initScriptContent = this.readInitScriptAsString(initScriptFilename) + const initScriptContent = this.readResourceFileAsString('init-scripts', initScriptFilename) const initScriptPath = path.resolve(initScriptsDir, initScriptFilename) fs.writeFileSync(initScriptPath, initScriptContent) } } - private readInitScriptAsString(resource: string): string { + private registerToolchains(): void { + const preInstalledToolchains = this.readResourceFileAsString('toolchains.xml') + const m2dir = path.resolve(this.userHome, '.m2') + const toolchainXmlTarget = path.resolve(m2dir, 'toolchains.xml') + if (!fs.existsSync(toolchainXmlTarget)) { + // Write a new toolchains.xml file if it doesn't exist + fs.mkdirSync(m2dir, {recursive: true}) + fs.writeFileSync(toolchainXmlTarget, preInstalledToolchains) + + core.info(`Wrote default JDK locations to ${toolchainXmlTarget}`) + } else { + // Merge into an existing toolchains.xml file + const existingToolchainContent = fs.readFileSync(toolchainXmlTarget, 'utf8') + const appendedContent = preInstalledToolchains.split('').pop()! + const mergedContent = existingToolchainContent.replace('', appendedContent) + + fs.writeFileSync(toolchainXmlTarget, mergedContent) + core.info(`Merged default JDK locations into ${toolchainXmlTarget}`) + } + } + + private readResourceFileAsString(...paths: string[]): string { // Resolving relative to __dirname will allow node to find the resource at runtime - const absolutePath = path.resolve(__dirname, '..', '..', 'src', 'resources', 'init-scripts', resource) + const absolutePath = path.resolve(__dirname, '..', '..', 'src', 'resources', ...paths) return fs.readFileSync(absolutePath, 'utf8') } diff --git a/src/caches.ts b/src/caches.ts index f2b7d82..2980444 100644 --- a/src/caches.ts +++ b/src/caches.ts @@ -13,7 +13,7 @@ import {CacheCleaner} from './cache-cleaner' const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED' -export async function restore(gradleUserHome: string, cacheListener: CacheListener): Promise { +export async function restore(userHome: string, gradleUserHome: string, cacheListener: CacheListener): Promise { // Bypass restore cache on all but first action step in workflow. if (process.env[CACHE_RESTORED_VAR]) { core.info('Cache only restored on first action step.') @@ -21,7 +21,7 @@ export async function restore(gradleUserHome: string, cacheListener: CacheListen } core.exportVariable(CACHE_RESTORED_VAR, true) - const gradleStateCache = new GradleStateCache(gradleUserHome) + const gradleStateCache = new GradleStateCache(userHome, gradleUserHome) if (isCacheDisabled()) { core.info('Cache is disabled: will not restore state from previous builds.') @@ -65,6 +65,7 @@ export async function restore(gradleUserHome: string, cacheListener: CacheListen } export async function save( + userHome: string, gradleUserHome: string, cacheListener: CacheListener, daemonController: DaemonController @@ -98,6 +99,6 @@ export async function save( } await core.group('Caching Gradle state', async () => { - return new GradleStateCache(gradleUserHome).save(cacheListener) + return new GradleStateCache(userHome, gradleUserHome).save(cacheListener) }) } diff --git a/src/resources/toolchains.xml b/src/resources/toolchains.xml new file mode 100644 index 0000000..f9ba374 --- /dev/null +++ b/src/resources/toolchains.xml @@ -0,0 +1,44 @@ + + + + + jdk + + 8 + Eclipse Temurin + + + ${env.JAVA_HOME_8_X64} + + + + jdk + + 11 + Eclipse Temurin + + + ${env.JAVA_HOME_11_X64} + + + + jdk + + 17 + Eclipse Temurin + + + ${env.JAVA_HOME_17_X64} + + + + jdk + + 21 + Eclipse Temurin + + + ${env.JAVA_HOME_21_X64} + + + diff --git a/src/setup-gradle.ts b/src/setup-gradle.ts index 84ecce7..b1f1c93 100644 --- a/src/setup-gradle.ts +++ b/src/setup-gradle.ts @@ -13,10 +13,12 @@ import {CacheListener} from './cache-reporting' import {DaemonController} from './daemon-controller' const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED' +const USER_HOME = 'USER_HOME' const GRADLE_USER_HOME = 'GRADLE_USER_HOME' const CACHE_LISTENER = 'CACHE_LISTENER' export async function setup(): Promise { + const userHome = await determineUserHome() const gradleUserHome = await determineGradleUserHome() // Bypass setup on all but first action step in workflow. @@ -29,11 +31,12 @@ export async function setup(): Promise { // Record setup complete: visible in post-action, to control action completion core.saveState(GRADLE_SETUP_VAR, true) - // Save the Gradle User Home for use in the post-action step. + // Save the User Home and Gradle User Home for use in the post-action step. + core.saveState(USER_HOME, userHome) core.saveState(GRADLE_USER_HOME, gradleUserHome) const cacheListener = new CacheListener() - await caches.restore(gradleUserHome, cacheListener) + await caches.restore(userHome, gradleUserHome, cacheListener) core.saveState(CACHE_LISTENER, cacheListener.stringify()) @@ -49,11 +52,12 @@ export async function complete(): Promise { const buildResults = loadBuildResults() + const userHome = core.getState(USER_HOME) const gradleUserHome = core.getState(GRADLE_USER_HOME) const cacheListener: CacheListener = CacheListener.rehydrate(core.getState(CACHE_LISTENER)) const daemonController = new DaemonController(buildResults) - await caches.save(gradleUserHome, cacheListener, daemonController) + await caches.save(userHome, gradleUserHome, cacheListener, daemonController) await jobSummary.generateJobSummary(buildResults, cacheListener)