JavaExec fails for long classpaths on Windows


(Thipor Kong) #1

JavaExec doesn’t work for long classpaths on Windows. The problem is similar to the ones described here or here.

The underlying reason is, that the JavaExecHandleBuilder (from which DefaultJavaExecAction derives) just puts the whole classpath into the ‘-cp’ command line parameter for the JVM.

  • Are there any plans for supporting long classpaths properly out-of-the-box?
  • If not, what’s the proper approach to configure the use of your own JavaExecAction implementation?

I have seen there are ExecActionFactory.newJavaExecAction() and ProjectScopeServices.createExecActionFactory(), but couldn’t figure out how to plug in my own factories.


Why distZip rather than fat jar?
(Thipor Kong) #2

Feel free to make use of this specialization of DefaultJavaExecAction:

public class LongClasspathExecAction extends DefaultJavaExecAction {
    public LongClasspathExecAction(final FileResolver fileResolver) {
        super(fileResolver);
    }

    @Override
    public List<String> getAllJvmArgs() {
        final List<String> allArgs = new ArrayList<>(getJvmArgs());
        final FileCollection classpath = getClasspath();
        if (!classpath.isEmpty()) {
            try {
                final File jarFile = toJarWithClasspath(classpath.getFiles());
                allArgs.add("-cp");
                allArgs.add(jarFile.toString());
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }
        return allArgs;
    }

    private static final String MATCH_CHUNKS_OF_70_CHARACTERS = "(?<=\\G.{70})";

    private static File toJarWithClasspath(final Set<File> files) throws IOException {
        final File jarFile = File.createTempFile("long-classpath", ".jar");
        try (final ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(jarFile))) {
            zip.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
            try (final PrintWriter pw = new PrintWriter(new OutputStreamWriter(zip, StandardCharsets.UTF_8))) {
                pw.println("Manifest-Version: 1.0");
                final String classPath = CollectionUtils.join(" ", CollectionUtils.collect(files, File::toURI));
                final String classPathEntry = "Class-Path: " + classPath;
                pw.println(CollectionUtils.join("\n ", classPathEntry.split(MATCH_CHUNKS_OF_70_CHARACTERS)));
            }
        }
        return jarFile;
    }
}

(Ned Twigg) #3

How do we make use of this? I can hack something together by with project.javaExec(), but is there a way to swap the default implementation with this one?

Btw, for how many posts there are about this, it’s crazy there’s no warning or real fix in gradle core yet.