You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/05/02 05:50:21 UTC

(groovy) branch master updated: GROOVY-11978: RootLoader duplicates the core jar already on the bootstrap classpath

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

paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 57662d1a1a GROOVY-11978: RootLoader duplicates the core jar already on the bootstrap classpath
57662d1a1a is described below

commit 57662d1a1aa870434d3b5e40389840f482b34938
Author: Paul King <pa...@asert.com.au>
AuthorDate: Sat May 2 09:20:01 2026 +1000

    GROOVY-11978: RootLoader duplicates the core jar already on the bootstrap classpath
---
 .../java/org/codehaus/groovy/tools/RootLoader.java | 43 +++++++++++++++++++++-
 1 file changed, 42 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/codehaus/groovy/tools/RootLoader.java b/src/main/java/org/codehaus/groovy/tools/RootLoader.java
index ff8779c430..14c775a1cd 100644
--- a/src/main/java/org/codehaus/groovy/tools/RootLoader.java
+++ b/src/main/java/org/codehaus/groovy/tools/RootLoader.java
@@ -19,12 +19,16 @@
 package org.codehaus.groovy.tools;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.MalformedURLException;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * This ClassLoader should be used as root of class loaders. Any
@@ -109,8 +113,16 @@ public class RootLoader extends URLClassLoader {
 
         Thread.currentThread().setContextClassLoader(this);
 
+        // Skip URLs already on the JVM startup (system) classpath: the launcher
+        // puts the core groovy jar on -classpath to start GroovyStarter, and
+        // groovy-starter.conf's "load lib/*.jar" then re-globs it. Adding the
+        // same jar to both loaders lets each define its own copy of every core
+        // class, and trips duplicate-resource detection.
+        Set<File> startup = startupClasspathFiles();
         for (URL url : lc.getClassPathUrls()) {
-            addURL(url);
+            if (!startup.contains(canonicalFile(url))) {
+                addURL(url);
+            }
         }
         // TODO M12N eventually defer this until later when we have a full Groovy
         // environment and use normal Grape.grab()
@@ -179,4 +191,33 @@ public class RootLoader extends URLClassLoader {
     protected Class<?> findClass(final String name) throws ClassNotFoundException {
         throw new ClassNotFoundException(name);
     }
+
+    private static Set<File> startupClasspathFiles() {
+        Set<File> files = new HashSet<>();
+        String cp = System.getProperty("java.class.path");
+        if (cp == null || cp.isEmpty()) return files;
+        for (String entry : cp.split(File.pathSeparator)) {
+            if (entry.isEmpty()) continue;
+            File f = canonicalFile(new File(entry));
+            if (f != null) files.add(f);
+        }
+        return files;
+    }
+
+    private static File canonicalFile(URL url) {
+        if (!"file".equals(url.getProtocol())) return null;
+        try {
+            return canonicalFile(new File(url.toURI()));
+        } catch (URISyntaxException | IllegalArgumentException e) {
+            return null;
+        }
+    }
+
+    private static File canonicalFile(File f) {
+        try {
+            return f.getCanonicalFile();
+        } catch (IOException e) {
+            return f.getAbsoluteFile();
+        }
+    }
 }