You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/08/29 19:15:14 UTC

(maven) 05/16: Use Session properties instead of System.* in JdkToolchainDiscoverer

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git

commit aa78689edc7702e62d07245ce81d47a7f038926c
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Fri Jul 31 11:01:47 2026 +0200

    Use Session properties instead of System.* in JdkToolchainDiscoverer
    
    Replace direct System.getProperty/System.getenv calls with the
    properties map from Session.getSystemProperties(), where env vars
    are available as "env.VAR_NAME" entries. This follows Maven's
    convention of accessing environment through the Session and makes
    the discoverer fully testable without relying on JVM global state.
    
    Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
 .../apache/maven/impl/DefaultToolchainManager.java |  2 +-
 .../apache/maven/impl/JdkToolchainDiscoverer.java  | 77 +++++++++++++---------
 .../maven/impl/JdkToolchainDiscovererTest.java     | 50 +++++++++++++-
 3 files changed, 95 insertions(+), 34 deletions(-)

diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultToolchainManager.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultToolchainManager.java
index 45df52a465..9169252983 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultToolchainManager.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultToolchainManager.java
@@ -163,7 +163,7 @@ Optional<Toolchain> autoSelectJdkToolchain(Session session) {
         // 2. Fall back to lazy filesystem discovery
         if (bestMatch == null && discoverer != null) {
             logger.debug("No compatible JDK in configured toolchains, discovering JDKs from filesystem...");
-            List<ToolchainModel> discoveredModels = discoverer.discoverToolchains();
+            List<ToolchainModel> discoveredModels = discoverer.discoverToolchains(session.getSystemProperties());
             List<Toolchain> discoveredToolchains = discoveredModels.stream()
                     .map(this::createToolchain)
                     .flatMap(Optional::stream)
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/JdkToolchainDiscoverer.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/JdkToolchainDiscoverer.java
index 78524a7e6c..1d7dfe767b 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/impl/JdkToolchainDiscoverer.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/JdkToolchainDiscoverer.java
@@ -49,6 +49,11 @@
  * <p>
  * JDK version is read from the {@code release} file present in every JDK since Java 9
  * (and backported to JDK 8u updates), avoiding the need to execute {@code java} processes.
+ * <p>
+ * All environment and system property access goes through the {@code properties} map
+ * supplied by the caller (typically {@link org.apache.maven.api.Session#getSystemProperties()}),
+ * where environment variables are available as {@code env.VAR_NAME} entries and JVM system
+ * properties as plain keys ({@code java.home}, {@code user.home}, etc.).
  */
 @Named
 @Singleton
@@ -59,15 +64,20 @@ public class JdkToolchainDiscoverer {
     private volatile List<ToolchainModel> cachedToolchains;
 
     /**
-     * Returns discovered JDK toolchain models. Results are cached after first invocation.
+     * Returns discovered JDK toolchain models. Results are cached after first invocation
+     * (the properties map from the first call wins; subsequent calls with different
+     * properties still return the cached result, since JDK locations don't change mid-build).
+     *
+     * @param properties system properties map (from {@code Session.getSystemProperties()}).
+     *                   Environment variables are expected as {@code env.VAR_NAME} entries.
      */
-    public List<ToolchainModel> discoverToolchains() {
+    public List<ToolchainModel> discoverToolchains(Map<String, String> properties) {
         List<ToolchainModel> result = cachedToolchains;
         if (result == null) {
             synchronized (this) {
                 result = cachedToolchains;
                 if (result == null) {
-                    result = doDiscover();
+                    result = doDiscover(properties);
                     cachedToolchains = result;
                 }
             }
@@ -75,11 +85,11 @@ public List<ToolchainModel> discoverToolchains() {
         return result;
     }
 
-    private List<ToolchainModel> doDiscover() {
+    private List<ToolchainModel> doDiscover(Map<String, String> properties) {
         Set<Path> candidates = new LinkedHashSet<>();
-        collectFromEnvironment(candidates);
-        collectFromToolManagers(candidates);
-        collectFromSystemDirectories(candidates);
+        collectFromEnvironment(candidates, properties);
+        collectFromToolManagers(candidates, properties);
+        collectFromSystemDirectories(candidates, properties);
 
         List<ToolchainModel> toolchains = new ArrayList<>();
         for (Path candidate : candidates) {
@@ -100,34 +110,33 @@ private List<ToolchainModel> doDiscover() {
 
     /**
      * Collects JDK candidates from environment variables matching {@code JAVA*_HOME}.
+     * Environment variables are read from the properties map as {@code env.VAR_NAME} entries.
      */
-    void collectFromEnvironment(Set<Path> candidates) {
-        // Current JDK
-        String javaHome = System.getProperty("java.home");
+    void collectFromEnvironment(Set<Path> candidates, Map<String, String> properties) {
+        // Current JDK (from java.home system property)
+        String javaHome = properties.get("java.home");
         if (javaHome != null) {
             addCandidate(candidates, Paths.get(javaHome));
         }
 
-        // JAVA*_HOME env vars (e.g. JAVA11_HOME, JAVA17_HOME)
-        for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
+        // env.JAVA*_HOME env vars (e.g. env.JAVA11_HOME, env.JAVA17_HOME, env.JAVA_HOME)
+        for (Map.Entry<String, String> entry : properties.entrySet()) {
             String name = entry.getKey();
-            if (name.startsWith("JAVA") && name.endsWith("_HOME")) {
+            if (name.startsWith("env.JAVA") && name.endsWith("_HOME")) {
                 addCandidate(candidates, Paths.get(entry.getValue()));
             }
         }
-
-        // JAVA_HOME
-        String envJavaHome = System.getenv("JAVA_HOME");
-        if (envJavaHome != null) {
-            addCandidate(candidates, Paths.get(envJavaHome));
-        }
     }
 
     /**
      * Collects JDK candidates from common tool manager directories under the user's home.
      */
-    void collectFromToolManagers(Set<Path> candidates) {
-        Path userHome = Paths.get(System.getProperty("user.home"));
+    void collectFromToolManagers(Set<Path> candidates, Map<String, String> properties) {
+        String userHomeProp = properties.get("user.home");
+        if (userHomeProp == null) {
+            return;
+        }
+        Path userHome = Paths.get(userHomeProp);
 
         // IntelliJ IDEA / common
         scanSubdirectories(candidates, userHome.resolve(".jdks"));
@@ -161,11 +170,11 @@ void collectFromToolManagers(Set<Path> candidates) {
     /**
      * Collects JDK candidates from OS-specific system directories.
      */
-    void collectFromSystemDirectories(Set<Path> candidates) {
+    void collectFromSystemDirectories(Set<Path> candidates, Map<String, String> properties) {
         if (Os.IS_WINDOWS) {
-            collectWindowsDirectories(candidates);
+            collectWindowsDirectories(candidates, properties);
         } else if (Os.isFamily("mac")) {
-            collectMacDirectories(candidates);
+            collectMacDirectories(candidates, properties);
         } else {
             collectLinuxDirectories(candidates);
         }
@@ -181,14 +190,17 @@ private void collectLinuxDirectories(Set<Path> candidates) {
         scanSubdirectories(candidates, Paths.get("/opt/hostedtoolcache"));
     }
 
-    private void collectMacDirectories(Set<Path> candidates) {
-        Path userHome = Paths.get(System.getProperty("user.home"));
+    private void collectMacDirectories(Set<Path> candidates, Map<String, String> properties) {
         scanSubdirectories(candidates, Paths.get("/Library/Java/JavaVirtualMachines"));
-        scanSubdirectories(
-                candidates, userHome.resolve("Library").resolve("Java").resolve("JavaVirtualMachines"));
+        String userHomeProp = properties.get("user.home");
+        if (userHomeProp != null) {
+            Path userHome = Paths.get(userHomeProp);
+            scanSubdirectories(
+                    candidates, userHome.resolve("Library").resolve("Java").resolve("JavaVirtualMachines"));
+        }
     }
 
-    private void collectWindowsDirectories(Set<Path> candidates) {
+    private void collectWindowsDirectories(Set<Path> candidates, Map<String, String> properties) {
         Path progFiles = Paths.get("C:\\Program Files");
         scanSubdirectories(candidates, progFiles.resolve("Java"));
         scanSubdirectories(candidates, progFiles.resolve("Eclipse Adoptium"));
@@ -196,8 +208,11 @@ private void collectWindowsDirectories(Set<Path> candidates) {
         scanSubdirectories(candidates, progFiles.resolve("Amazon Corretto"));
         scanSubdirectories(candidates, progFiles.resolve("BellSoft"));
         // Scoop
-        Path userHome = Paths.get(System.getProperty("user.home"));
-        scanSubdirectories(candidates, userHome.resolve("scoop").resolve("apps"));
+        String userHomeProp = properties.get("user.home");
+        if (userHomeProp != null) {
+            Path userHome = Paths.get(userHomeProp);
+            scanSubdirectories(candidates, userHome.resolve("scoop").resolve("apps"));
+        }
     }
 
     /**
diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/JdkToolchainDiscovererTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/JdkToolchainDiscovererTest.java
index 041a9cce1d..76d0d6e576 100644
--- a/impl/maven-impl/src/test/java/org/apache/maven/impl/JdkToolchainDiscovererTest.java
+++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/JdkToolchainDiscovererTest.java
@@ -21,7 +21,10 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
 import org.apache.maven.api.toolchain.ToolchainModel;
 import org.junit.jupiter.api.Test;
@@ -157,13 +160,56 @@ void resolveJdkHomeInvalidPath() {
 
     @Test
     void discoverToolchainsIsCached() {
+        Map<String, String> properties = Map.of("user.home", tempDir.toString());
         // Two calls should return the same list instance (cached)
-        var first = discoverer.discoverToolchains();
-        var second = discoverer.discoverToolchains();
+        var first = discoverer.discoverToolchains(properties);
+        var second = discoverer.discoverToolchains(properties);
         assertNotNull(first);
         assertTrue(first == second, "Expected cached result (same instance)");
     }
 
+    @Test
+    void collectFromEnvironmentUsesProperties() throws IOException {
+        Path jdkHome = createFakeJdk(tempDir, "jdk-21");
+        Map<String, String> properties = Map.of(
+                "java.home", jdkHome.toString(),
+                "env.JAVA17_HOME", tempDir.resolve("jdk-17-env").toString());
+
+        Set<Path> candidates = new LinkedHashSet<>();
+        discoverer.collectFromEnvironment(candidates, properties);
+
+        assertTrue(candidates.stream().anyMatch(p -> p.toString().contains("jdk-21")), "Should include java.home");
+        assertTrue(
+                candidates.stream().anyMatch(p -> p.toString().contains("jdk-17-env")),
+                "Should include env.JAVA17_HOME");
+    }
+
+    @Test
+    void collectFromToolManagersUsesProperties() throws IOException {
+        Path fakeHome = tempDir.resolve("fakehome");
+        Path jdksDir = fakeHome.resolve(".jdks");
+        Path jdk21 = jdksDir.resolve("temurin-21");
+        Files.createDirectories(jdk21);
+
+        Map<String, String> properties = Map.of("user.home", fakeHome.toString());
+
+        Set<Path> candidates = new LinkedHashSet<>();
+        discoverer.collectFromToolManagers(candidates, properties);
+
+        assertTrue(
+                candidates.stream().anyMatch(p -> p.toString().contains("temurin-21")), "Should find JDK in ~/.jdks");
+    }
+
+    @Test
+    void collectFromToolManagersSkipsWhenNoUserHome() {
+        Map<String, String> properties = Map.of();
+
+        Set<Path> candidates = new LinkedHashSet<>();
+        discoverer.collectFromToolManagers(candidates, properties);
+
+        assertTrue(candidates.isEmpty(), "Should collect nothing without user.home");
+    }
+
     private Path createFakeJdk(Path parent, String name) throws IOException {
         Path jdkHome = parent.resolve(name);
         Path binDir = jdkHome.resolve("bin");