From 32a13f66f9167c0bc2bbbdf8ea3d9efb17274f99 Mon Sep 17 00:00:00 2001
From: Johannes Feichtner <343448+Churro@users.noreply.github.com>
Date: Sun, 15 Sep 2024 06:59:25 +0200
Subject: [PATCH] feat(gradle-wrapper): add support for gradle daemon JVM
 discovery (#31319)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
---
 .../manager/gradle-wrapper/artifacts.ts       |  3 +-
 .../manager/gradle-wrapper/util.spec.ts       | 57 ++++++++++++++-----
 lib/modules/manager/gradle-wrapper/utils.ts   | 43 +++++++++++++-
 lib/modules/manager/gradle/artifacts.ts       |  5 +-
 4 files changed, 90 insertions(+), 18 deletions(-)

diff --git a/lib/modules/manager/gradle-wrapper/artifacts.ts b/lib/modules/manager/gradle-wrapper/artifacts.ts
index be7f31cf1a..b523ae6986 100644
--- a/lib/modules/manager/gradle-wrapper/artifacts.ts
+++ b/lib/modules/manager/gradle-wrapper/artifacts.ts
@@ -177,7 +177,8 @@ export async function updateArtifacts({
         {
           toolName: 'java',
           constraint:
-            config.constraints?.java ?? getJavaConstraint(config.currentValue),
+            config.constraints?.java ??
+            (await getJavaConstraint(config.currentValue, gradlewFile)),
         },
       ],
     };
diff --git a/lib/modules/manager/gradle-wrapper/util.spec.ts b/lib/modules/manager/gradle-wrapper/util.spec.ts
index 9670f9de7b..5396da52fd 100644
--- a/lib/modules/manager/gradle-wrapper/util.spec.ts
+++ b/lib/modules/manager/gradle-wrapper/util.spec.ts
@@ -1,10 +1,12 @@
 import type { Stats } from 'node:fs';
 import os from 'node:os';
+import { codeBlock } from 'common-tags';
 import { fs, partial } from '../../../../test/util';
 import { GlobalConfig } from '../../../config/global';
 import {
   extractGradleVersion,
   getJavaConstraint,
+  getJvmConfiguration,
   gradleWrapperFileName,
   prepareGradleCommand,
 } from './utils';
@@ -16,34 +18,63 @@ describe('modules/manager/gradle-wrapper/util', () => {
   beforeEach(() => GlobalConfig.reset());
 
   describe('getJavaConstraint()', () => {
-    it('return ^8.0.0 for global mode', () => {
-      expect(getJavaConstraint('4')).toBe('^8.0.0');
+    it('return ^8.0.0 for global mode', async () => {
+      expect(await getJavaConstraint('4', '')).toBe('^8.0.0');
     });
 
-    it('return ^11.0.0 for docker mode and undefined gradle', () => {
+    it('return ^11.0.0 for docker mode and undefined gradle', async () => {
       GlobalConfig.set({ binarySource: 'docker' });
-      expect(getJavaConstraint('')).toBe('^11.0.0');
+      expect(await getJavaConstraint('', '')).toBe('^11.0.0');
     });
 
-    it('return ^8.0.0 for docker gradle < 5', () => {
+    it('return ^8.0.0 for docker gradle < 5', async () => {
       GlobalConfig.set({ binarySource: 'docker' });
-      expect(getJavaConstraint('4.9')).toBe('^8.0.0');
+      expect(await getJavaConstraint('4.9', '')).toBe('^8.0.0');
     });
 
-    it('return ^11.0.0 for docker gradle >=5 && <7', () => {
+    it('return ^11.0.0 for docker gradle >=5 && <7', async () => {
       GlobalConfig.set({ binarySource: 'docker' });
-      expect(getJavaConstraint('6.0')).toBe('^11.0.0');
+      expect(await getJavaConstraint('6.0', '')).toBe('^11.0.0');
     });
 
-    it('return ^16.0.0 for docker gradle >= 7', () => {
+    it('return ^16.0.0 for docker gradle >= 7', async () => {
       GlobalConfig.set({ binarySource: 'docker' });
-      expect(getJavaConstraint('7.0.1')).toBe('^16.0.0');
+      expect(await getJavaConstraint('7.0.1', '')).toBe('^16.0.0');
     });
 
-    it('return ^17.0.0 for docker gradle >= 7.3', () => {
+    it('return ^17.0.0 for docker gradle >= 7.3', async () => {
       GlobalConfig.set({ binarySource: 'docker' });
-      expect(getJavaConstraint('7.3.0')).toBe('^17.0.0');
-      expect(getJavaConstraint('8.0.1')).toBe('^17.0.0');
+      expect(await getJavaConstraint('7.3.0', '')).toBe('^17.0.0');
+      expect(await getJavaConstraint('8.0.1', '')).toBe('^17.0.0');
+    });
+
+    it('returns toolChainVersion constraint if daemon JVM configured', async () => {
+      const daemonJvm = codeBlock`
+        #This file is generated by updateDaemonJvm
+        toolchainVersion=999
+      `;
+      fs.readLocalFile.mockResolvedValue(daemonJvm);
+      expect(await getJavaConstraint('8.8', './gradlew')).toBe('^999.0.0');
+    });
+  });
+
+  describe('getJvmConfiguration', () => {
+    it('extracts toolChainVersion value', async () => {
+      const daemonJvm = codeBlock`
+        #This file is generated by updateDaemonJvm
+        toolchainVersion=21
+      `;
+      fs.readLocalFile.mockResolvedValue(daemonJvm);
+      expect(await getJvmConfiguration('')).toBe('21');
+    });
+
+    it('returns null if gradle-daemon-jvm.properties file not found', async () => {
+      fs.readLocalFile.mockResolvedValueOnce(null);
+      expect(await getJvmConfiguration('sub/gradlew')).toBeNull();
+      expect(fs.readLocalFile).toHaveBeenCalledWith(
+        'sub/gradle/gradle-daemon-jvm.properties',
+        'utf8',
+      );
     });
   });
 
diff --git a/lib/modules/manager/gradle-wrapper/utils.ts b/lib/modules/manager/gradle-wrapper/utils.ts
index 713a6f8b26..9ebdd68b94 100644
--- a/lib/modules/manager/gradle-wrapper/utils.ts
+++ b/lib/modules/manager/gradle-wrapper/utils.ts
@@ -1,7 +1,8 @@
 import os from 'node:os';
+import { dirname, join } from 'upath';
 import { GlobalConfig } from '../../../config/global';
 import { logger } from '../../../logger';
-import { chmodLocalFile, statLocalFile } from '../../../util/fs';
+import { chmodLocalFile, readLocalFile, statLocalFile } from '../../../util/fs';
 import { newlineRegex, regEx } from '../../../util/regex';
 import gradleVersioning from '../../versioning/gradle';
 import type { GradleVersionExtract } from './types';
@@ -41,13 +42,24 @@ export async function prepareGradleCommand(
  * Find compatible java version for gradle.
  * see https://docs.gradle.org/current/userguide/compatibility.html
  * @param gradleVersion current gradle version
+ * @param gradlewFile path to gradle wrapper
  * @returns A Java semver range
  */
-export function getJavaConstraint(
+export async function getJavaConstraint(
   gradleVersion: string | null | undefined,
-): string {
+  gradlewFile: string,
+): Promise<string> {
   const major = gradleVersion ? gradleVersioning.getMajor(gradleVersion) : null;
   const minor = gradleVersion ? gradleVersioning.getMinor(gradleVersion) : null;
+
+  // https://docs.gradle.org/8.8/release-notes.html#daemon-toolchains
+  if (major && (major > 8 || (major === 8 && minor && minor >= 8))) {
+    const toolChainVersion = await getJvmConfiguration(gradlewFile);
+    if (toolChainVersion) {
+      return `^${toolChainVersion}.0.0`;
+    }
+  }
+
   if (major && (major > 7 || (major >= 7 && minor && minor >= 3))) {
     return '^17.0.0';
   }
@@ -61,6 +73,31 @@ export function getJavaConstraint(
   return '^11.0.0';
 }
 
+/**
+ * https://docs.gradle.org/current/userguide/gradle_daemon.html#sec:daemon_jvm_criteria
+ */
+export async function getJvmConfiguration(
+  gradlewFile: string,
+): Promise<string | null> {
+  const daemonJvmFile = join(
+    dirname(gradlewFile),
+    'gradle/gradle-daemon-jvm.properties',
+  );
+  const daemonJvm = await readLocalFile(daemonJvmFile, 'utf8');
+  if (daemonJvm) {
+    const TOOLCHAIN_VERSION_REGEX = regEx(
+      '^(?:toolchainVersion\\s*=\\s*)(?<version>\\d+)$',
+      'm',
+    );
+    const toolChainMatch = TOOLCHAIN_VERSION_REGEX.exec(daemonJvm);
+    if (toolChainMatch?.groups) {
+      return toolChainMatch.groups.version;
+    }
+  }
+
+  return null;
+}
+
 // https://regex101.com/r/IcOs7P/1
 const DISTRIBUTION_URL_REGEX = regEx(
   '^(?:distributionUrl\\s*=\\s*)(?<url>\\S*-(?<version>\\d+\\.\\d+(?:\\.\\d+)?(?:-\\w+)*)-(?<type>bin|all)\\.zip)\\s*$',
diff --git a/lib/modules/manager/gradle/artifacts.ts b/lib/modules/manager/gradle/artifacts.ts
index 1a1f3cd193..bf73a81e7b 100644
--- a/lib/modules/manager/gradle/artifacts.ts
+++ b/lib/modules/manager/gradle/artifacts.ts
@@ -188,7 +188,10 @@ export async function updateArtifacts({
           toolName: 'java',
           constraint:
             config.constraints?.java ??
-            getJavaConstraint(await getGradleVersion(gradlewFile)),
+            (await getJavaConstraint(
+              await getGradleVersion(gradlewFile),
+              gradlewFile,
+            )),
         },
       ],
     };
-- 
GitLab