Proguard omits directory entries in output JAR, causing `ClassLoader#getResources` to fail
When processing JAR files through Proguard, directory entries in the output JAR are not properly preserved. This results in ClassLoader#getResources(String) being unable to locate resources when queried using directory paths.
Environment Proguard 7.6.1, jdk-21, Windows 11.
Steps to Reproduce
- Create a project with directory structure: src/main/resources/META-INF/data/
- Add sample files to the directory
- Configure Proguard to keep everything
- Build project with Proguard processing
Attempt to load resources using:
Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/data");
Expected Behavior The directory resource URL should be returned when querying existing directory paths.
Actual Behavior Empty enumeration is returned because the JAR lacks directory entries, even though files exist in those directories.
This breaks resource loading patterns commonly used in Resource discovery mechanisms and Framework classpath scanning implementations.
And this looks very similar to #381.
It is hard to understand how proguard write data entries to jar file, so I did a very hack fix, maybe helpful:
diff --git a/base/src/main/java/proguard/OutputWriter.java b/base/src/main/java/proguard/OutputWriter.java
--- a/base/src/main/java/proguard/OutputWriter.java (revision fbcf41fd670cc6ffdf2db6cfddc07f5a527142e2)
+++ b/base/src/main/java/proguard/OutputWriter.java (date 1738083442253)
@@ -41,6 +41,9 @@
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
/**
@@ -397,6 +400,48 @@
// Close all output entries.
writer.close();
+
+ // read all the entries from the output jar and write them back to the jar
+ Map<String, byte[]> entries = new TreeMap<>();
+ File file = classPath.get(toOutputIndex - 1).getFile();
+ try (JarInputStream jis = new JarInputStream(new FileInputStream(file))) {
+ JarEntry entry;
+ while ((entry = jis.getNextJarEntry()) != null) {
+ if (!entry.isDirectory()) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = jis.read(buffer)) != -1) {
+ baos.write(buffer, 0, bytesRead);
+ }
+ entries.put(entry.getName(), baos.toByteArray());
+ }
+ }
+ }
+ // add missing directories
+ Set<String> dirs = new HashSet<>();
+ for (String entry : entries.keySet()) {
+ while (entry.contains("/")) {
+ entry = entry.substring(0, entry.lastIndexOf('/'));
+ dirs.add(entry + "/");
+ }
+ }
+ for (String dir : dirs) {
+ if (!entries.containsKey(dir)) {
+ entries.put(dir, null);
+ }
+ }
+
+ // write the entries back to the jar
+ try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(file))) {
+ for (Map.Entry<String, byte[]> entry : entries.entrySet()) {
+ jos.putNextEntry(new JarEntry(entry.getKey()));
+ if (entry.getValue() != null) {
+ jos.write(entry.getValue());
+ }
+ jos.closeEntry();
+ }
+ }
}
catch (IOException ex)
{
It works for me.