Bug 385738 - Shorten the classpath/modulepath if necessary.
Shorten the classpath and/or modulepath if it gets longer than current
operating system limits.
Depending on the java version, os and launch configuration, the
classpath argument will be replaced by an argument file, a
classpath-only jar or an env variable.
The modulepath is replaced by an argument file if necessary.
If classpath-only jar needs to be used to shorten the classpath, an
explicit error popup ask if user wants to use classpath-only jar.
Change-Id: Ibcc23b6fa5c5baa05d91aef0f03529fb8e598e08
Signed-off-by: Cedric Chabanois <cchabanois@gmail.com>
diff --git a/org.eclipse.jdt.debug.tests/java8/LongClasspath.java b/org.eclipse.jdt.debug.tests/java8/LongClasspath.java
new file mode 100644
index 0000000..0b196af
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/java8/LongClasspath.java
@@ -0,0 +1,7 @@
+public class LongClasspath {
+
+ public static void main(String[] args) {
+ System.out.println("Hello");
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java
index c68f75f..ca6728e 100644
--- a/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java
+++ b/org.eclipse.jdt.debug.tests/test plugin/org/eclipse/jdt/debug/testplugin/JavaProjectHelper.java
@@ -59,6 +59,7 @@
public static final String JAVA_SE_1_6_EE_NAME = "JavaSE-1.6";
public static final String JAVA_SE_1_7_EE_NAME = "JavaSE-1.7";
public static final String JAVA_SE_1_8_EE_NAME = "JavaSE-1.8";
+ public static final String JAVA_SE_9_EE_NAME = "JavaSE-9";
/**
* path to the test src for 'testprograms'
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/lib/classpath.jar b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/lib/classpath.jar
new file mode 100644
index 0000000..401654f
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/lib/classpath.jar
Binary files differ
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/module-info.java b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/module-info.java
new file mode 100644
index 0000000..188c640
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/module-info.java
@@ -0,0 +1,3 @@
+module test.classpath {
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/test/classpath/Main.java b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/test/classpath/Main.java
new file mode 100644
index 0000000..2c24818
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathModuleProject/src/test/classpath/Main.java
@@ -0,0 +1,9 @@
+package test.classpath;
+
+public class Main {
+
+ public static void main(String[] args) {
+ System.out.println("Hello world");
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathProject/lib/classpath.jar b/org.eclipse.jdt.debug.tests/testresources/classpathProject/lib/classpath.jar
new file mode 100644
index 0000000..401654f
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathProject/lib/classpath.jar
Binary files differ
diff --git a/org.eclipse.jdt.debug.tests/testresources/classpathProject/src/test/classpath/Main.java b/org.eclipse.jdt.debug.tests/testresources/classpathProject/src/test/classpath/Main.java
new file mode 100644
index 0000000..2c24818
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/classpathProject/src/test/classpath/Main.java
@@ -0,0 +1,9 @@
+package test.classpath;
+
+public class Main {
+
+ public static void main(String[] args) {
+ System.out.println("Hello world");
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
index 9a50482..1c8e529 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
@@ -84,6 +84,7 @@
import org.eclipse.jdt.debug.tests.core.WorkspaceSourceContainerTests;
import org.eclipse.jdt.debug.tests.eval.GeneralEvalTests;
import org.eclipse.jdt.debug.tests.eval.GenericsEvalTests;
+import org.eclipse.jdt.debug.tests.launching.ClasspathShortenerTests;
import org.eclipse.jdt.debug.tests.launching.ConfigurationEncodingTests;
import org.eclipse.jdt.debug.tests.launching.ConfigurationResourceMappingTests;
import org.eclipse.jdt.debug.tests.launching.ContributedTabTests;
@@ -94,6 +95,8 @@
import org.eclipse.jdt.debug.tests.launching.LaunchShortcutTests;
import org.eclipse.jdt.debug.tests.launching.LaunchTests;
import org.eclipse.jdt.debug.tests.launching.LaunchesTests;
+import org.eclipse.jdt.debug.tests.launching.LongClassPathTests;
+import org.eclipse.jdt.debug.tests.launching.LongModulePathTests;
import org.eclipse.jdt.debug.tests.launching.MigrationDelegateTests;
import org.eclipse.jdt.debug.tests.launching.PListParserTests;
import org.eclipse.jdt.debug.tests.launching.ProjectClasspathVariableTests;
@@ -345,5 +348,12 @@
//add the complete eval suite
addTest(new TestSuite(GeneralEvalTests.class));
//addTest(EvalTestSuite.suite());
+
+ // long classpath tests
+ addTest(new TestSuite(ClasspathShortenerTests.class));
+ addTest(LongClassPathTests.suite());
+ if (JavaProjectHelper.isJava9Compatible()) {
+ addTest(new TestSuite(LongModulePathTests.class));
+ }
}
}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/ClasspathShortenerTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/ClasspathShortenerTests.java
new file mode 100644
index 0000000..2dee9bf
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/ClasspathShortenerTests.java
@@ -0,0 +1,369 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cedric Chabanois (cchabanois@gmail.com) - initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.launching;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.URIUtil;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.debug.tests.connectors.MockLaunch;
+import org.eclipse.jdt.internal.launching.ClasspathShortener;
+
+public class ClasspathShortenerTests extends AbstractDebugTest {
+ private static final String MAIN_CLASS = "my.package.MainClass";
+ private static final String ENCODING_ARG = "-Dfile.encoding=UTF-8";
+ private static final String JAVA_10_PATH = "/usr/lib/jvm/java-10-jdk-amd64/bin/java";
+ private static final String JAVA_8_PATH = "/usr/lib/jvm/java-8-openjdk-amd64/bin/java";
+ private ClasspathShortenerForTest classpathShortener;
+ private String userHome;
+
+ public ClasspathShortenerTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ userHome = System.getProperty("user.home");
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (classpathShortener != null) {
+ classpathShortener.getProcessTempFiles().forEach(file -> file.delete());
+ }
+ super.tearDown();
+ }
+
+ public void testNoNeedToShortenShortClasspath() {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+
+ // When
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertFalse(result);
+ }
+
+ public void testShortenClasspathWhenCommandLineIsTooLong() {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+
+ // When
+ classpathShortener.setMaxCommandLineLength(100);
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ }
+
+ public void testShortenClasspathWhenClasspathArgumentIsTooLong() {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+
+ // When
+ classpathShortener.setMaxArgLength(40);
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ }
+
+ public void testForceClasspathOnlyJar() throws Exception {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+
+ // When
+ classpathShortener.setForceUseClasspathOnlyJar(true);
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ assertEquals(1, classpathShortener.getProcessTempFiles().size());
+ assertArrayEquals(new String[] { JAVA_10_PATH, ENCODING_ARG, "-classpath", classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+ MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+ List<File> classpathJars = getClasspathJarsFromJarManifest(classpathShortener.getProcessTempFiles().get(0));
+ assertEquals(new File(userHomePath("/workspace/myProject/bin")), classpathJars.get(0).getCanonicalFile());
+ assertEquals(new File(userHomePath("/workspace/myProject/lib/lib 1.jar")), classpathJars.get(1).getCanonicalFile());
+ }
+
+ public void testArgFileUsedForLongClasspathOnJava9() throws Exception {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "10.0.1", cmdLine, 4, null);
+ classpathShortener.setMaxCommandLineLength(100);
+
+ // When
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ assertEquals(1, classpathShortener.getProcessTempFiles().size());
+ assertArrayEquals(new String[] { JAVA_10_PATH, ENCODING_ARG, "@" + classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+ MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+ assertEquals("-classpath " + classpath, getFileContents(classpathShortener.getProcessTempFiles().get(0)));
+ }
+
+ public void testArgFileUsedForLongModulePath() throws Exception {
+ // Given
+ String modulepath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-p", modulepath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "10.0.1", cmdLine, 4, null);
+ classpathShortener.setMaxCommandLineLength(100);
+
+ // When
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ assertEquals(1, classpathShortener.getProcessTempFiles().size());
+ assertArrayEquals(new String[] { JAVA_10_PATH, ENCODING_ARG, "@" + classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+ MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+ assertEquals("--module-path " + modulepath, getFileContents(classpathShortener.getProcessTempFiles().get(0)));
+ }
+
+ public void testLongClasspathAndLongModulePath() throws Exception {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/lib/lib 2.jar"), userHomePath("/workspace/myProject/lib/lib 3.jar"));
+ String modulepath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_10_PATH, ENCODING_ARG, "-cp", classpath, "-p", modulepath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "10.0.1", cmdLine, 6, null);
+ classpathShortener.setMaxCommandLineLength(100);
+
+ // When
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ assertEquals(2, classpathShortener.getProcessTempFiles().size());
+ assertArrayEquals(new String[] { JAVA_10_PATH, ENCODING_ARG, "@" + classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+ "@" + classpathShortener.getProcessTempFiles().get(1).getAbsolutePath(), MAIN_CLASS, "-arg1",
+ "arg2" }, classpathShortener.getCmdLine());
+ assertEquals("-classpath " + classpath, getFileContents(classpathShortener.getProcessTempFiles().get(0)));
+ assertEquals("--module-path " + modulepath, getFileContents(classpathShortener.getProcessTempFiles().get(1)));
+ }
+
+ public void testClasspathOnlyJarUsedForLongClasspathOnJava8() throws Exception {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_LINUX, "1.8.0_171", cmdLine, 4, null);
+ classpathShortener.setMaxCommandLineLength(100);
+
+ // When
+ classpathShortener.setAllowToUseClasspathOnlyJar(true);
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ assertEquals(1, classpathShortener.getProcessTempFiles().size());
+ assertArrayEquals(new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpathShortener.getProcessTempFiles().get(0).getAbsolutePath(),
+ MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+ List<File> classpathJars = getClasspathJarsFromJarManifest(classpathShortener.getProcessTempFiles().get(0));
+ assertEquals(new File(userHomePath("/workspace/myProject/bin")), classpathJars.get(0).getCanonicalFile());
+ assertEquals(new File(userHomePath("/workspace/myProject/lib/lib 1.jar")), classpathJars.get(1).getCanonicalFile());
+ }
+
+ public void testClasspathEnvVariableUsedForLongClasspathOnJava8OnWindows() {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "1.8.0_171", cmdLine, 4, null);
+ classpathShortener.setMaxCommandLineLength(100);
+
+ // When
+ Map<String, String> nativeEnvironment = new LinkedHashMap<>();
+ nativeEnvironment.put("PATH", "C:\\WINDOWS\\System32;C:\\WINDOWS");
+ classpathShortener.setNativeEnvironment(nativeEnvironment);
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ assertEquals(0, classpathShortener.getProcessTempFiles().size());
+ assertArrayEquals(new String[] { "PATH=C:\\WINDOWS\\System32;C:\\WINDOWS", "CLASSPATH=" + classpath }, classpathShortener.getEnvp());
+ assertArrayEquals(new String[] { JAVA_8_PATH, ENCODING_ARG, MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+ }
+
+ public void testClasspathUsedOnWindowsWhenEnvp() {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+ String[] envp = new String[] { "MYVAR1=value1", "MYVAR2=value2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "1.8.0_171", cmdLine, 4, envp);
+ classpathShortener.setMaxCommandLineLength(100);
+
+ // When
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ assertEquals(0, classpathShortener.getProcessTempFiles().size());
+ assertArrayEquals(new String[] { "MYVAR1=value1", "MYVAR2=value2", "CLASSPATH=" + classpath }, classpathShortener.getEnvp());
+ assertArrayEquals(new String[] { JAVA_8_PATH, ENCODING_ARG, MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+ }
+
+ public void testClasspathInEnvironmentReplacedOnWindows() {
+ // Given
+ String classpath = getClasspathOrModulePath(userHomePath("/workspace/myProject/bin"), userHomePath("/workspace/myProject/lib/lib 1.jar"));
+ String[] cmdLine = new String[] { JAVA_8_PATH, ENCODING_ARG, "-cp", classpath, MAIN_CLASS, "-arg1", "arg2" };
+ classpathShortener = new ClasspathShortenerForTest(Platform.OS_WIN32, "1.8.0_171", cmdLine, 4, null);
+ classpathShortener.setMaxCommandLineLength(100);
+
+ // When
+ Map<String, String> nativeEnvironment = new LinkedHashMap<>();
+ nativeEnvironment.put("PATH", "C:\\WINDOWS\\System32;C:\\WINDOWS");
+ nativeEnvironment.put("CLASSPATH", "C:\\myJars\\jar1.jar");
+ classpathShortener.setNativeEnvironment(nativeEnvironment);
+ boolean result = classpathShortener.shortenCommandLineIfNecessary();
+
+ // Then
+ assertTrue(result);
+ assertEquals(0, classpathShortener.getProcessTempFiles().size());
+ assertArrayEquals(new String[] { "PATH=C:\\WINDOWS\\System32;C:\\WINDOWS", "CLASSPATH=" + classpath }, classpathShortener.getEnvp());
+ assertArrayEquals(new String[] { JAVA_8_PATH, ENCODING_ARG, MAIN_CLASS, "-arg1", "arg2" }, classpathShortener.getCmdLine());
+ }
+
+ private String getFileContents(File file) throws UnsupportedEncodingException, IOException {
+ return new String(Files.readAllBytes(file.toPath()), "UTF-8");
+ }
+
+ private String getClasspathAttributeFromJarManifest(File jarFile) throws FileNotFoundException, IOException {
+ try (JarInputStream jarStream = new JarInputStream(new FileInputStream(jarFile))) {
+ Manifest mf = jarStream.getManifest();
+ return mf.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
+ }
+ }
+
+ private List<File> getClasspathJarsFromJarManifest(File jarFile) throws FileNotFoundException, IOException {
+ File parentDir = jarFile.getParentFile();
+ String classpath = getClasspathAttributeFromJarManifest(jarFile);
+ return Arrays.stream(classpath.split(" ")).map(relativePath -> {
+ try {
+ return new File(URIUtil.makeAbsolute(new URI(relativePath), parentDir.toURI()));
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }).collect(Collectors.toList());
+ }
+
+ private String userHomePath(String path) {
+ return userHome + path;
+ }
+
+ private String getClasspathOrModulePath(String... classpathElements) {
+ return String.join(";", classpathElements);
+ }
+
+ private static class ClasspathShortenerForTest extends ClasspathShortener {
+ private boolean forceUseClasspathOnlyJar = false;
+ private boolean allowToUseClasspathOnlyJar = false;
+ private int maxArgLength = Integer.MAX_VALUE;
+ private int maxCommandLineLength = Integer.MAX_VALUE;
+ private Map<String, String> nativeEnvironment = new HashMap<>();
+
+ public ClasspathShortenerForTest(String os, String javaVersion, String[] cmdLine, int classpathArgumentIndex, String[] envp) {
+ super(os, javaVersion, new MockLaunch(), cmdLine, classpathArgumentIndex, null, envp);
+ }
+
+ public void setAllowToUseClasspathOnlyJar(boolean allowToUseClasspathOnlyJar) {
+ this.allowToUseClasspathOnlyJar = allowToUseClasspathOnlyJar;
+ }
+
+ public void setForceUseClasspathOnlyJar(boolean forceUseClasspathOnlyJar) {
+ this.forceUseClasspathOnlyJar = forceUseClasspathOnlyJar;
+ }
+
+ @Override
+ protected String getLaunchConfigurationName() {
+ return "launch";
+ }
+
+ @Override
+ protected String getLaunchTimeStamp() {
+ return Long.toString(System.currentTimeMillis());
+ }
+
+ @Override
+ protected boolean handleClasspathTooLongStatus() throws CoreException {
+ return allowToUseClasspathOnlyJar;
+ }
+
+ @Override
+ protected boolean getLaunchConfigurationUseClasspathOnlyJarAttribute() throws CoreException {
+ return forceUseClasspathOnlyJar;
+ }
+
+ @Override
+ protected int getMaxArgLength() {
+ return maxArgLength;
+ }
+
+ @Override
+ protected int getMaxCommandLineLength() {
+ return maxCommandLineLength;
+ }
+
+ public void setMaxArgLength(int maxArgLength) {
+ this.maxArgLength = maxArgLength;
+ }
+
+ public void setMaxCommandLineLength(int maxCommandLineLength) {
+ this.maxCommandLineLength = maxCommandLineLength;
+ }
+
+ public void setNativeEnvironment(Map<String, String> nativeEnvironment) {
+ this.nativeEnvironment = nativeEnvironment;
+ }
+
+ @Override
+ protected Map<String, String> getNativeEnvironment() {
+ return nativeEnvironment;
+ }
+
+ @Override
+ protected char getPathSeparatorChar() {
+ // always use ';' as separator (tests would fail on windows otherwise with paths c:\ ...)
+ return ';';
+ }
+
+ }
+
+}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongClassPathTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongClassPathTests.java
new file mode 100644
index 0000000..758bcac
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongClassPathTests.java
@@ -0,0 +1,243 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cedric Chabanois (cchabanois@gmail.com) - initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.launching;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.model.IProcess;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.debug.core.IJavaThread;
+import org.eclipse.jdt.debug.testplugin.JavaProjectHelper;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.internal.launching.LaunchingPlugin;
+import org.eclipse.jdt.launching.AbstractVMInstall;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jdt.launching.IVMInstall;
+import org.eclipse.jdt.launching.JavaRuntime;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test long classpaths. OSs have limits in term of command line length or argument length. We handle this limit differently depending on the VM
+ * version and OS.
+ *
+ */
+public class LongClassPathTests extends AbstractDebugTest {
+ private static final String MAIN_TYPE_NAME = "test.classpath.Main";
+ private static final IPath CLASSPATH_PROJECT_CONTENT_PATH = new Path("testresources/classpathProject");
+ private IJavaProject javaProject;
+ private ILaunchConfiguration launchConfiguration;
+ private IJavaThread thread;
+
+ public LongClassPathTests(String name) {
+ super(name);
+ }
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTest(new LongClassPathTests("testVeryLongClasspathWithClasspathOnlyJar"));
+ if (JavaProjectHelper.isJava9Compatible()) {
+ suite.addTest(new LongClassPathTests("testVeryLongClasspathWithArgumentFile"));
+ } else if (Platform.getOS().equals(Platform.OS_WIN32)) {
+ suite.addTest(new LongClassPathTests("testVeryLongClasspathWithEnvironmentVariable"));
+ }
+ return suite;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ try {
+ if (thread != null) {
+ terminateAndRemove(thread);
+ }
+ if (javaProject != null) {
+ javaProject.getProject().delete(true, true, null);
+ }
+ if (launchConfiguration != null) {
+ launchConfiguration.delete();
+ }
+ } catch (CoreException ce) {
+ // ignore
+ } finally {
+ super.tearDown();
+ }
+ }
+
+ /*
+ * When classpathOnlyJar is enabled, a classpath-only jar is created.
+ */
+ public void testVeryLongClasspathWithClasspathOnlyJar() throws Exception {
+ // Given
+ javaProject = createJavaProjectClone("testVeryLongClasspathWithClasspathOnlyJar", CLASSPATH_PROJECT_CONTENT_PATH.toString(), JavaProjectHelper.JAVA_SE_1_6_EE_NAME, true);
+ launchConfiguration = createLaunchConfigurationStopInMain(javaProject, MAIN_TYPE_NAME);
+ int minClasspathLength = 300000;
+ setLongClasspath(javaProject, minClasspathLength);
+ launchConfiguration = enableClasspathOnlyJar(launchConfiguration);
+ waitForBuild();
+
+ // When
+ thread = launchAndSuspend(launchConfiguration);
+
+ // Then
+ File tempFile = getTempFile(thread.getLaunch()).orElseThrow(() -> new RuntimeException("No temp file"));
+ assertTrue(tempFile.exists());
+ assertTrue(tempFile.getName().endsWith(".jar"));
+ String actualClasspath = doEval(thread, "System.getProperty(\"java.class.path\")").getValueString();
+ assertTrue(actualClasspath.contains(tempFile.getAbsolutePath()));
+ assertTrue(actualClasspath.length() < minClasspathLength);
+
+ // When
+ resumeAndExit(thread);
+
+ // Then
+ if (!Platform.getOS().equals(Platform.OS_WIN32)) {
+ // On windows, temp file deletion may fail
+ assertFalse(tempFile.exists());
+ }
+ }
+
+ /*
+ * When JVM > 9, an argument file for the classpath is created when classpath is too long
+ */
+ public void testVeryLongClasspathWithArgumentFile() throws Exception {
+ javaProject = createJavaProjectClone("testVeryLongClasspathWithArgumentFile", CLASSPATH_PROJECT_CONTENT_PATH.toString(), JavaProjectHelper.JAVA_SE_9_EE_NAME, true);
+ launchConfiguration = createLaunchConfigurationStopInMain(javaProject, MAIN_TYPE_NAME);
+ assumeTrue(isArgumentFileSupported(launchConfiguration));
+ int minClasspathLength = 300000;
+
+ // Given
+ setLongClasspath(javaProject, minClasspathLength);
+ waitForBuild();
+
+ // When
+ thread = launchAndSuspend(launchConfiguration);
+
+ // Then
+ File tempFile = getTempFile(thread.getLaunch()).orElseThrow(() -> new RuntimeException("No temp file"));
+ assertTrue(tempFile.exists());
+ assertTrue(tempFile.getName().endsWith(".txt"));
+ assertTrue(doEval(thread, "System.getProperty(\"java.class.path\")").getValueString().length() >= minClasspathLength);
+
+ // When
+ resumeAndExit(thread);
+
+ // Then
+ if (!Platform.getOS().equals(Platform.OS_WIN32)) {
+ // On windows, temp file deletion may fail
+ assertFalse(tempFile.exists());
+ }
+ }
+
+ /*
+ * On Windows, for JVM < 9, the CLASSPATH env variable is used if classpath is too long
+ */
+ public void testVeryLongClasspathWithEnvironmentVariable() throws Exception {
+ assumeThat(Platform.getOS(), equalTo(Platform.OS_WIN32));
+
+ // Given
+ javaProject = createJavaProjectClone("testVeryLongClasspath", CLASSPATH_PROJECT_CONTENT_PATH.toString(), JavaProjectHelper.JAVA_SE_1_6_EE_NAME, true);
+ launchConfiguration = createLaunchConfigurationStopInMain(javaProject, MAIN_TYPE_NAME);
+ assumeFalse(isArgumentFileSupported(launchConfiguration));
+ int minClasspathLength = 300000;
+ setLongClasspath(javaProject, minClasspathLength);
+ waitForBuild();
+
+ // When
+ thread = launchAndSuspend(launchConfiguration);
+
+ // Then
+ assertTrue(doEval(thread, "System.getProperty(\"java.class.path\")").getValueString().length() >= minClasspathLength);
+ resumeAndExit(thread);
+ }
+
+ private Optional<File> getTempFile(ILaunch launch) {
+ IProcess process = launch.getProcesses()[0];
+ String tempFile = process.getAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES);
+ if (tempFile == null) {
+ return Optional.empty();
+ }
+ return Optional.of(new File(tempFile));
+ }
+
+ private boolean isArgumentFileSupported(ILaunchConfiguration launchConfiguration) throws CoreException {
+ IVMInstall vmInstall = JavaRuntime.computeVMInstall(launchConfiguration);
+ if (vmInstall instanceof AbstractVMInstall) {
+ AbstractVMInstall install = (AbstractVMInstall) vmInstall;
+ String vmver = install.getJavaVersion();
+ if (JavaCore.compareJavaVersions(vmver, JavaCore.VERSION_9) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private ILaunchConfiguration enableClasspathOnlyJar(ILaunchConfiguration launchConfiguration) throws CoreException {
+ ILaunchConfigurationWorkingCopy configurationWorkingCopy = launchConfiguration.getWorkingCopy();
+ configurationWorkingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, true);
+ return configurationWorkingCopy.doSave();
+ }
+
+ private ILaunchConfiguration createLaunchConfigurationStopInMain(IJavaProject javaProject, String mainTypeName) throws Exception, CoreException {
+ ILaunchConfiguration launchConfiguration;
+ launchConfiguration = createLaunchConfiguration(javaProject, mainTypeName);
+ ILaunchConfigurationWorkingCopy wc = launchConfiguration.getWorkingCopy();
+ wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_STOP_IN_MAIN, true);
+ launchConfiguration = wc.doSave();
+ return launchConfiguration;
+ }
+
+ private void setLongClasspath(IJavaProject javaProject, int minClassPathLength) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ List<IClasspathEntry> classpathEntries = new ArrayList<>();
+ int i = 0;
+ while (sb.length() < minClassPathLength) {
+ String jarName = "library" + i + ".jar";
+ IPath targetPath = javaProject.getPath().append("lib/" + jarName);
+ javaProject.getProject().getFile("lib/classpath.jar").copy(targetPath, IResource.FORCE, new NullProgressMonitor());
+ IClasspathAttribute moduleClasspathAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true");
+ classpathEntries.add(JavaCore.newLibraryEntry(targetPath, null, null, null, new IClasspathAttribute[] {
+ moduleClasspathAttribute }, false));
+ if (i != 0) {
+ sb.append(File.pathSeparator);
+ }
+ sb.append(javaProject.getProject().getFile("lib/" + jarName).getLocation().toString());
+ i++;
+ }
+ classpathEntries.add(JavaCore.newLibraryEntry(javaProject.getPath().append("lib/classpath.jar"), null, null));
+ sb.append(File.pathSeparator);
+ sb.append(javaProject.getProject().getFile("lib/classpath.jar").getLocation().toString());
+ classpathEntries.addAll(Arrays.asList(javaProject.getRawClasspath()));
+ javaProject.setRawClasspath(classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]), null);
+ }
+
+}
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongModulePathTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongModulePathTests.java
new file mode 100644
index 0000000..b22ddca
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/LongModulePathTests.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cedric Chabanois (cchabanois@gmail.com) - initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.launching;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.model.IProcess;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.debug.core.IJavaThread;
+import org.eclipse.jdt.debug.testplugin.JavaProjectHelper;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.internal.launching.LaunchingPlugin;
+import org.eclipse.jdt.internal.ui.wizards.buildpaths.BuildPathSupport;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jdt.launching.JavaRuntime;
+import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
+
+/**
+ * Test long module-path. OSs have limits in term of command line length or argument length. We use an argument file when module-path is too long.
+ *
+ */
+public class LongModulePathTests extends AbstractDebugTest {
+ private static final IPath CLASSPATH_PROJECT_CONTENT_PATH = new Path("testresources/classpathModuleProject");
+ private static final String MAIN_TYPE_NAME = "test.classpath.Main";
+ private IJavaProject javaProject;
+ private IJavaThread thread;
+ private ILaunchConfiguration launchConfiguration;
+
+ public LongModulePathTests(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ try {
+ if (thread != null) {
+ terminateAndRemove(thread);
+ }
+ if (javaProject != null) {
+ javaProject.getProject().delete(true, true, null);
+ }
+ if (launchConfiguration != null) {
+ launchConfiguration.delete();
+ }
+ } catch (CoreException ce) {
+ // ignore
+ } finally {
+ super.tearDown();
+ }
+ }
+
+ /*
+ * When JVM > 9, an argument file for the modulepath is created when modulepath is too long
+ */
+ public void testVeryLongModulepathWithArgumentFile() throws Exception {
+ // Given
+ javaProject = createJavaProjectClone("testVeryLongModulePath", CLASSPATH_PROJECT_CONTENT_PATH.toString(), JavaProjectHelper.JAVA_SE_9_EE_NAME, true);
+ useComplianceFromExecutionEnvironment(javaProject);
+ useModuleForJREContainer(javaProject);
+ launchConfiguration = createLaunchConfigurationStopInMain(javaProject, MAIN_TYPE_NAME);
+ int minModulePathLength = 300000;
+ setLongModulepath(javaProject, minModulePathLength);
+ waitForBuild();
+
+ // When
+ thread = launchAndSuspend(launchConfiguration);
+
+ // Then
+ File tempFile = getTempFile(thread.getLaunch()).orElseThrow(() -> new RuntimeException("No temp file"));
+ assertTrue(tempFile.exists());
+ assertTrue(tempFile.getName().endsWith(".txt"));
+ assertTrue(doEval(thread, "System.getProperty(\"jdk.module.path\")").getValueString().length() >= minModulePathLength);
+
+ // When
+ resumeAndExit(thread);
+
+ // Then
+ if (!Platform.getOS().equals(Platform.OS_WIN32)) {
+ // On windows, temp file deletion may fail
+ assertFalse(tempFile.exists());
+ }
+ }
+
+ private ILaunchConfiguration createLaunchConfigurationStopInMain(IJavaProject javaProject, String mainTypeName) throws Exception, CoreException {
+ ILaunchConfiguration launchConfiguration;
+ launchConfiguration = createLaunchConfiguration(javaProject, mainTypeName);
+ ILaunchConfigurationWorkingCopy wc = launchConfiguration.getWorkingCopy();
+ wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_STOP_IN_MAIN, true);
+ launchConfiguration = wc.doSave();
+ return launchConfiguration;
+ }
+
+ private void setLongModulepath(IJavaProject javaProject, int minModulePathLength) throws Exception {
+ StringBuilder sb = new StringBuilder();
+ List<IClasspathEntry> classpathEntries = new ArrayList<>();
+ int i = 0;
+ while (sb.length() < minModulePathLength) {
+ String jarName = "library" + i + ".jar";
+ IPath targetPath = javaProject.getPath().append("lib/" + jarName);
+ javaProject.getProject().getFile("lib/classpath.jar").copy(targetPath, IResource.FORCE, new NullProgressMonitor());
+ IClasspathAttribute moduleClasspathAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true");
+ classpathEntries.add(JavaCore.newLibraryEntry(targetPath, null, null, null, new IClasspathAttribute[] {
+ moduleClasspathAttribute }, false));
+ if (i != 0) {
+ sb.append(File.pathSeparator);
+ }
+ sb.append(javaProject.getProject().getFile("lib/" + jarName).getLocation().toString());
+ i++;
+ }
+ IClasspathAttribute moduleClasspathAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true");
+ classpathEntries.add(JavaCore.newLibraryEntry(javaProject.getPath().append("lib/classpath.jar"), null, null, null, new IClasspathAttribute[] {
+ moduleClasspathAttribute }, false));
+ sb.append(File.pathSeparator);
+ sb.append(javaProject.getProject().getFile("lib/classpath.jar").getLocation().toString());
+ classpathEntries.addAll(Arrays.asList(javaProject.getRawClasspath()));
+ javaProject.setRawClasspath(classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]), null);
+ }
+
+ private void useComplianceFromExecutionEnvironment(IJavaProject javaProject) throws JavaModelException {
+ IExecutionEnvironment executionEnvironment = getExecutionEnvironment(javaProject);
+ Map<String, String> eeOptions = BuildPathSupport.getEEOptions(executionEnvironment);
+ eeOptions.forEach((optionName, optionValue) -> javaProject.setOption(optionName, optionValue));
+ }
+
+ private IExecutionEnvironment getExecutionEnvironment(IJavaProject javaProject) throws JavaModelException {
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+ for (int i = 0; i < entries.length; i++) {
+ IClasspathEntry entry = entries[i];
+ if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+ String eeId = JavaRuntime.getExecutionEnvironmentId(entry.getPath());
+ if (eeId != null) {
+ return JavaRuntime.getExecutionEnvironmentsManager().getEnvironment(eeId);
+ }
+ }
+ }
+ return null;
+ }
+
+ private void useModuleForJREContainer(IJavaProject javaProject) throws JavaModelException {
+ IClasspathEntry[] rawClasspath = javaProject.getRawClasspath();
+ for (int i = 0; i < rawClasspath.length; i++) {
+ IClasspathEntry classpathEntry = rawClasspath[i];
+ if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+ IClasspathAttribute moduleClasspathAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true");
+ classpathEntry = JavaCore.newContainerEntry(classpathEntry.getPath(), classpathEntry.getAccessRules(), new IClasspathAttribute[] {
+ moduleClasspathAttribute }, classpathEntry.isExported());
+ rawClasspath[i] = classpathEntry;
+ }
+ }
+ javaProject.setRawClasspath(rawClasspath, null);
+ }
+
+ private Optional<File> getTempFile(ILaunch launch) {
+ IProcess process = launch.getProcesses()[0];
+ String tempFile = process.getAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES);
+ if (tempFile == null) {
+ return Optional.empty();
+ }
+ return Optional.of(new File(tempFile));
+ }
+
+}
diff --git a/org.eclipse.jdt.debug.ui/plugin.xml b/org.eclipse.jdt.debug.ui/plugin.xml
index 7d4b03a..9cdacd2 100644
--- a/org.eclipse.jdt.debug.ui/plugin.xml
+++ b/org.eclipse.jdt.debug.ui/plugin.xml
@@ -2495,6 +2495,12 @@
class="org.eclipse.jdt.internal.debug.ui.EvaluationStackFrameContextStatusHandler"
id="org.eclipse.jdt.debug.ui.statusHandler.evaluationStackFrameContextStatusHandler"
plugin="org.eclipse.jdt.debug"/>
+ <statusHandler
+ class="org.eclipse.jdt.internal.debug.ui.launcher.ClasspathTooLongStatusHandler"
+ code="125"
+ id="org.eclipse.jdt.debug.ui.statusHandler.classpathTooLongStatusHandler"
+ plugin="org.eclipse.jdt.launching">
+ </statusHandler>
</extension>
<extension
point="org.eclipse.debug.core.sourceLocators">
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java
index ff7f8f5..6fadda6 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaClasspathTab.java
@@ -96,6 +96,7 @@
protected ILaunchConfiguration fLaunchConfiguration;
private Button fExcludeTestCodeButton;
+ private Button fUseClasspathOnlyJarButton;
/**
* Constructor
@@ -155,6 +156,14 @@
updateLaunchConfigurationDialog();
}
});
+ fUseClasspathOnlyJarButton = SWTFactory.createCheckButton(comp, LauncherMessages.VMArgumentsBlock_1, null, false, 1);
+ fUseClasspathOnlyJarButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ setDirty(true);
+ updateLaunchConfigurationDialog();
+ }
+ });
}
/**
@@ -228,6 +237,7 @@
fClasspathViewer.getTreeViewer().expandToLevel(2);
try {
fExcludeTestCodeButton.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false));
+ fUseClasspathOnlyJarButton.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, false));
} catch (CoreException e) {
}
}
@@ -321,6 +331,7 @@
catch (CoreException e) {
JDIDebugUIPlugin.statusDialog(LauncherMessages.JavaClasspathTab_Unable_to_save_classpath_1, e.getStatus());
}
+ configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, fUseClasspathOnlyJarButton.getSelection());
}
}
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaJRETab.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaJRETab.java
index 33f200f..fa87bd3 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaJRETab.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaJRETab.java
@@ -220,6 +220,11 @@
configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, (String)null);
configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, (String)null);
+ // we don't want to use classpath only jars for modular projects
+ if (JavaRuntime.isModularConfiguration(configuration)) {
+ configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, false);
+ }
+
// Handle any attributes in the VM-specific area
ILaunchConfigurationTab dynamicTab = getDynamicTab();
if (dynamicTab == null) {
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/ClasspathTooLongStatusHandler.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/ClasspathTooLongStatusHandler.java
new file mode 100644
index 0000000..a755cdc
--- /dev/null
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/ClasspathTooLongStatusHandler.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cedric Chabanois (cchabanois@gmail.com) - Launching command line exceeds the process creation command limit on *nix - https://bugs.eclipse.org/bugs/show_bug.cgi?id=385738
+ *******************************************************************************/
+package org.eclipse.jdt.internal.debug.ui.launcher;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.IStatusHandler;
+import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jface.dialogs.MessageDialog;
+
+/**
+ * Handle status when classpath is too long and that this could not be solved automatically.
+ *
+ * We ask the user if he wants to use classpath-only jar. We need confirmation from the user because it can have side effects
+ * (System.getProperty("java.class.path") will return a classpath with only one jar).
+ */
+public class ClasspathTooLongStatusHandler implements IStatusHandler {
+
+ @Override
+ public Object handleStatus(IStatus status, Object source) {
+ ILaunch launch = (ILaunch) source;
+ ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration();
+ boolean enableClasspathOnlyJar = askEnableClasspathOnlyJar();
+ if (enableClasspathOnlyJar && launchConfiguration != null) {
+ try {
+ ILaunchConfigurationWorkingCopy configurationWorkingCopy = launchConfiguration.getWorkingCopy();
+ configurationWorkingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, true);
+ configurationWorkingCopy.doSave();
+ } catch (CoreException e) {
+ JDIDebugUIPlugin.log(e);
+ }
+ }
+ return Boolean.valueOf(enableClasspathOnlyJar);
+ }
+
+ private boolean askEnableClasspathOnlyJar() {
+ final boolean[] result = new boolean[1];
+ JDIDebugUIPlugin.getStandardDisplay().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ String title = LauncherMessages.ClasspathTooLongStatusHandler_0;
+ String message = LauncherMessages.ClasspathTooLongStatusHandler_1;
+ result[0] = (MessageDialog.openQuestion(JDIDebugUIPlugin.getActiveWorkbenchShell(), title, message));
+ }
+ });
+ return result[0];
+ }
+
+}
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
index b2bfa90..10d9c95 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
@@ -48,6 +48,8 @@
public static String VMArgumentsBlock_0;
+ public static String VMArgumentsBlock_1;
+
public static String VMArgumentsBlock_VM_Arguments;
public static String VMArgumentsBlock_4;
@@ -180,6 +182,10 @@
public static String AppletSelectionDialog_Searching____1;
+ public static String ClasspathTooLongStatusHandler_0;
+
+ public static String ClasspathTooLongStatusHandler_1;
+
public static String ExecutionEnvironmentSelector_0;
public static String ExecutionEnvironmentSelector_1;
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
index 4cd96bd..faf071e 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
@@ -25,6 +25,7 @@
RuntimeClasspathAdvancedDialog_7=Va&riables...
VMArgumentsBlock_0=Use the -&XstartOnFirstThread argument when launching with SWT
+VMArgumentsBlock_1=Use temporary jar to specify classpath (to avoid classpath length limitations)
VMArgumentsBlock_VM_Arguments=VM Arguments
JavaConnectTab__Allow_termination_of_remote_VM_6=&Allow termination of remote VM
@@ -177,6 +178,8 @@
AbstractJavaMainTab_2=&Search...
AbstractJavaMainTab_4=Project Selection
AbstractJavaMainTab_3=Select a project to constrain your search.
+ClasspathTooLongStatusHandler_0=Java Application
+ClasspathTooLongStatusHandler_1=Cannot start the Java Virtual Machine because classpath is too long. Do you want to enable classpath-only jar for this launch configuration ?
ExecutionEnvironmentSelector_0=Select Execution Environment
ExecutionEnvironmentSelector_1=Choose an environment (? = any character, * = any string):
ProjectClasspathArugumentSelector_0=Select Java Project
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ClasspathShortener.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ClasspathShortener.java
new file mode 100644
index 0000000..a4a1be6
--- /dev/null
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/ClasspathShortener.java
@@ -0,0 +1,481 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Cedric Chabanois and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Cedric Chabanois (cchabanois@gmail.com) - Launching command line exceeds the process creation command limit on *nix - https://bugs.eclipse.org/bugs/show_bug.cgi?id=385738
+ * IBM Corporation - Launching command line exceeds the process creation command limit on Windows - https://bugs.eclipse.org/bugs/show_bug.cgi?id=327193
+ *******************************************************************************/
+package org.eclipse.jdt.internal.launching;
+
+import static org.eclipse.jdt.internal.launching.LaunchingPlugin.LAUNCH_TEMP_FILE_PREFIX;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.URIUtil;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.IStatusHandler;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jdt.launching.IVMInstall;
+import org.eclipse.jdt.launching.IVMInstall2;
+
+/**
+ * Shorten the classpath/modulepath if necessary.
+ *
+ * Depending on the java version, os and launch configuration, the classpath argument will be replaced by an argument file, a classpath-only jar or
+ * env variable. The modulepath is replaced by an argument file if necessary.
+ *
+ */
+public class ClasspathShortener {
+ private static final String CLASSPATH_ENV_VAR_PREFIX = "CLASSPATH="; //$NON-NLS-1$
+ public static final int ARG_MAX_LINUX = 2097152;
+ public static final int ARG_MAX_WINDOWS = 32767;
+ public static final int ARG_MAX_MACOS = 262144;
+ public static final int MAX_ARG_STRLEN_LINUX = 131072;
+ private final String os;
+ private final String javaVersion;
+ private final ILaunch launch;
+ private final List<String> cmdLine;
+ private int lastJavaArgumentIndex;
+ private String[] envp;
+ private File processTempFilesDir;
+ private final List<File> processTempFiles = new ArrayList<>();
+
+ /**
+ *
+ * @param vmInstall
+ * the vm installation
+ * @param launch
+ * the launch
+ * @param cmdLine
+ * the command line (java executable + VM arguments + program arguments)
+ * @param lastJavaArgumentIndex
+ * the index of the last java argument in cmdLine (next arguments if any are program arguments)
+ * @param workingDir
+ * the working dir to use for the launched VM or null
+ * @param envp
+ * array of strings, each element of which has environment variable settings in the format name=value, or null if the subprocess should
+ * inherit the environment of the current process.
+ */
+ public ClasspathShortener(IVMInstall vmInstall, ILaunch launch, String[] cmdLine, int lastJavaArgumentIndex, File workingDir, String[] envp) {
+ this(Platform.getOS(), getJavaVersion(vmInstall), launch, cmdLine, lastJavaArgumentIndex, workingDir, envp);
+ }
+
+ protected ClasspathShortener(String os, String javaVersion, ILaunch launch, String[] cmdLine, int lastJavaArgumentIndex, File workingDir, String[] envp) {
+ Assert.isNotNull(os);
+ Assert.isNotNull(javaVersion);
+ Assert.isNotNull(launch);
+ Assert.isNotNull(cmdLine);
+ this.os = os;
+ this.javaVersion = javaVersion;
+ this.launch = launch;
+ this.cmdLine = new ArrayList<>(Arrays.asList(cmdLine));
+ this.lastJavaArgumentIndex = lastJavaArgumentIndex;
+ this.envp = envp == null ? null : Arrays.copyOf(envp, envp.length);
+ this.processTempFilesDir = workingDir != null ? workingDir : Paths.get(".").toAbsolutePath().normalize().toFile(); //$NON-NLS-1$
+ }
+
+ /**
+ * The directory to use to create temp files needed when shortening the classpath. By default, the working directory is used
+ *
+ * The java.io.tmpdir should not be used on MacOs (does not work for classpath-only jars)
+ *
+ * @param processTempFilesDir
+ */
+ public void setProcessTempFilesDir(File processTempFilesDir) {
+ this.processTempFilesDir = processTempFilesDir;
+ }
+
+ public File getProcessTempFilesDir() {
+ return processTempFilesDir;
+ }
+
+ /**
+ * Get the new envp. May have been modified to shorten the classpath
+ *
+ * @return environment variables in the format name=value or null
+ */
+ public String[] getEnvp() {
+ return envp;
+ }
+
+ /**
+ * Get the new command line. Modified if command line or classpath argument were too long
+ *
+ * @return the command line (java executable + VM arguments + program arguments)
+ */
+ public String[] getCmdLine() {
+ return cmdLine.toArray(new String[cmdLine.size()]);
+ }
+
+ /**
+ * The files that were created while shortening the path. They can be deleted once the process is terminated
+ *
+ * @return created files
+ */
+ public List<File> getProcessTempFiles() {
+ return new ArrayList<>(processTempFiles);
+ }
+
+ /**
+ * Shorten the command line if necessary. Each OS has different limits for command line length or command line argument length. And depending on
+ * the OS, JVM version and launch configuration, we shorten the classpath using an argument file, a classpath-only jar or env variable.
+ *
+ * If we need to use a classpath-only jar to shorten the classpath, we ask confirmation from the user because it can have side effects
+ * (System.getProperty("java.class.path") will return a classpath with only one jar). If
+ * {@link IJavaLaunchConfigurationConstants#ATTR_USE_CLASSPATH_ONLY_JAR} is set, a classpath-only jar is used (without asking confirmation).
+ *
+ * @return true if command line has been shortened or false if it was not necessary or not possible. Use {@link #getCmdLine()} and
+ * {@link #getEnvp()} to get the new command line/environment.
+ */
+ public boolean shortenCommandLineIfNecessary() {
+ // '|' used on purpose (not short-circuiting)
+ return shortenClasspathIfNecessary() | shortenModulePathIfNecessary();
+ }
+
+ private int getClasspathArgumentIndex() {
+ for (int i = 0; i <= lastJavaArgumentIndex; i++) {
+ String element = cmdLine.get(i);
+ if ("-cp".equals(element) || "-classpath".equals(element) || "--class-path".equals(element)) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ return i + 1;
+ }
+ }
+ return -1;
+ }
+
+ private int getModulepathArgumentIndex() {
+ for (int i = 0; i <= lastJavaArgumentIndex; i++) {
+ String element = cmdLine.get(i);
+ if ("-p".equals(element) || "--module-path".equals(element)) { //$NON-NLS-1$ //$NON-NLS-2$
+ return i + 1;
+ }
+ }
+ return -1;
+ }
+
+ private boolean shortenModulePathIfNecessary() {
+ int modulePathArgumentIndex = getModulepathArgumentIndex();
+ if (modulePathArgumentIndex == -1) {
+ return false;
+ }
+ try {
+ String modulePath = cmdLine.get(modulePathArgumentIndex);
+ if (getCommandLineLength() <= getMaxCommandLineLength() && modulePath.length() <= getMaxArgLength()) {
+ return false;
+ }
+ if (isArgumentFileSupported()) {
+ shortenModulePathUsingModulePathArgumentFile(modulePathArgumentIndex);
+ return true;
+ }
+ } catch (CoreException e) {
+ LaunchingPlugin.log(e.getStatus());
+ }
+ return false;
+ }
+
+ private boolean shortenClasspathIfNecessary() {
+ int classpathArgumentIndex = getClasspathArgumentIndex();
+ if (classpathArgumentIndex == -1) {
+ return false;
+ }
+ try {
+ boolean forceUseClasspathOnlyJar = getLaunchConfigurationUseClasspathOnlyJarAttribute();
+ if (forceUseClasspathOnlyJar) {
+ shortenClasspathUsingClasspathOnlyJar(classpathArgumentIndex);
+ return true;
+ }
+ String classpath = cmdLine.get(classpathArgumentIndex);
+ if (getCommandLineLength() <= getMaxCommandLineLength() && classpath.length() <= getMaxArgLength()) {
+ return false;
+ }
+ if (isArgumentFileSupported()) {
+ shortenClasspathUsingClasspathArgumentFile(classpathArgumentIndex);
+ return true;
+ }
+ if (os.equals(Platform.OS_WIN32)) {
+ shortenClasspathUsingClasspathEnvVariable(classpathArgumentIndex);
+ return true;
+ } else if (handleClasspathTooLongStatus()) {
+ shortenClasspathUsingClasspathOnlyJar(classpathArgumentIndex);
+ return true;
+ }
+ } catch (CoreException e) {
+ LaunchingPlugin.log(e.getStatus());
+ }
+ return false;
+ }
+
+ protected boolean getLaunchConfigurationUseClasspathOnlyJarAttribute() throws CoreException {
+ ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration();
+ if (launchConfiguration == null) {
+ return false;
+ }
+ return launchConfiguration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_CLASSPATH_ONLY_JAR, false);
+ }
+
+ public static String getJavaVersion(IVMInstall vmInstall) {
+ if (vmInstall instanceof IVMInstall2) {
+ IVMInstall2 install = (IVMInstall2) vmInstall;
+ return install.getJavaVersion();
+ }
+ return null;
+ }
+
+ private boolean isArgumentFileSupported() {
+ return JavaCore.compareJavaVersions(javaVersion, JavaCore.VERSION_9) >= 0;
+ }
+
+ private int getCommandLineLength() {
+ return cmdLine.stream().map(argument -> argument.length() + 1).reduce((a, b) -> a + b).get();
+ }
+
+ private int getEnvironmentLength() {
+ if (envp == null) {
+ return 0;
+ }
+ return Arrays.stream(envp).map(element -> element.length() + 1).reduce((a, b) -> a + b).orElse(0);
+ }
+
+ protected int getMaxCommandLineLength() {
+ // for Posix systems, ARG_MAX is the maximum length of argument to the exec functions including environment data.
+ // POSIX suggests to subtract 2048 additionally so that the process may safely modify its environment.
+ // see https://www.in-ulm.de/~mascheck/various/argmax/
+ switch (os) {
+ case Platform.OS_LINUX:
+ // ARG_MAX will be 1/4 of the stack size. 2097152 by default
+ return ARG_MAX_LINUX - getEnvironmentLength() - 2048;
+ case Platform.OS_MACOSX:
+ // on MacOs, ARG_MAX is 262144
+ return ARG_MAX_MACOS - getEnvironmentLength() - 2048;
+ case Platform.OS_WIN32:
+ // On Windows, the maximum length of the command line is 32,768 characters, including the Unicode terminating null character.
+ // see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
+ return ARG_MAX_WINDOWS - 2048;
+ default:
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ protected int getMaxArgLength() {
+ if (os.equals(Platform.OS_LINUX)) {
+ // On Linux, MAX_ARG_STRLEN (kernel >= 2.6.23) is the maximum length of a command line argument (or environment variable). Its value
+ // cannot be changed without recompiling the kernel.
+ return MAX_ARG_STRLEN_LINUX - 2048;
+ }
+ return Integer.MAX_VALUE;
+ }
+
+ private void shortenClasspathUsingClasspathArgumentFile(int classpathArgumentIndex) throws CoreException {
+ String classpath = cmdLine.get(classpathArgumentIndex);
+ File file = createClassPathArgumentFile(classpath);
+ removeCmdLineArgs(classpathArgumentIndex - 1, 2);
+ addCmdLineArgs(classpathArgumentIndex - 1, '@' + file.getAbsolutePath());
+ addProcessTempFile(file);
+ }
+
+ private void shortenModulePathUsingModulePathArgumentFile(int modulePathArgumentIndex) throws CoreException {
+ String modulePath = cmdLine.get(modulePathArgumentIndex);
+ File file = createModulePathArgumentFile(modulePath);
+ removeCmdLineArgs(modulePathArgumentIndex - 1, 2);
+ addCmdLineArgs(modulePathArgumentIndex - 1, '@' + file.getAbsolutePath());
+ addProcessTempFile(file);
+ }
+
+ private void shortenClasspathUsingClasspathOnlyJar(int classpathArgumentIndex) throws CoreException {
+ String classpath = cmdLine.get(classpathArgumentIndex);
+ File classpathOnlyJar = createClasspathOnlyJar(classpath);
+ removeCmdLineArgs(classpathArgumentIndex, 1);
+ addCmdLineArgs(classpathArgumentIndex, classpathOnlyJar.getAbsolutePath());
+ addProcessTempFile(classpathOnlyJar);
+ }
+
+ protected void addProcessTempFile(File file) {
+ processTempFiles.add(file);
+ }
+
+ protected boolean handleClasspathTooLongStatus() throws CoreException {
+ IStatus status = new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IJavaLaunchConfigurationConstants.ERR_CLASSPATH_TOO_LONG, "", null); //$NON-NLS-1$
+ IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(status);
+ if (handler == null) {
+ return false;
+ }
+ Object result = handler.handleStatus(status, launch);
+ if (!(result instanceof Boolean)) {
+ return false;
+ }
+ return (boolean) result;
+ }
+
+ private File createClasspathOnlyJar(String classpath) throws CoreException {
+ try {
+ String timeStamp = getLaunchTimeStamp();
+ File jarFile = new File(processTempFilesDir, String.format(LAUNCH_TEMP_FILE_PREFIX
+ + "%s-classpathOnly-%s.jar", getLaunchConfigurationName(), timeStamp)); //$NON-NLS-1$
+ URI workingDirUri = processTempFilesDir.toURI();
+ StringBuilder manifestClasspath = new StringBuilder();
+ String[] classpathArray = getClasspathAsArray(classpath);
+ for (int i = 0; i < classpathArray.length; i++) {
+ if (i != 0) {
+ manifestClasspath.append(' ');
+ }
+ File file = new File(classpathArray[i]);
+ String relativePath = URIUtil.makeRelative(file.toURI(), workingDirUri).toString();
+ manifestClasspath.append(relativePath);
+ }
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); //$NON-NLS-1$
+ manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, manifestClasspath.toString());
+ try (JarOutputStream target = new JarOutputStream(new FileOutputStream(jarFile), manifest)) {
+ }
+ return jarFile;
+ } catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, "Cannot create classpath only jar", e)); // $NON-NLS-1$ //$NON-NLS-1$
+ }
+ }
+
+ private String[] getClasspathAsArray(String classpath) {
+ return classpath.split("" + getPathSeparatorChar()); //$NON-NLS-1$
+ }
+
+ protected char getPathSeparatorChar() {
+ char separator = ':';
+ if (os.equals(Platform.OS_WIN32)) {
+ separator = ';';
+ }
+ return separator;
+ }
+
+ protected String getLaunchConfigurationName() {
+ return launch.getLaunchConfiguration().getName();
+ }
+
+ private File createClassPathArgumentFile(String classpath) throws CoreException {
+ try {
+ String timeStamp = getLaunchTimeStamp();
+ File classPathFile = new File(processTempFilesDir, String.format(LAUNCH_TEMP_FILE_PREFIX
+ + "%s-classpath-arg-%s.txt", getLaunchConfigurationName(), timeStamp)); //$NON-NLS-1$
+
+ byte[] bytes = ("-classpath " + classpath).getBytes(StandardCharsets.UTF_8); //$NON-NLS-1$
+
+ Files.write(classPathFile.toPath(), bytes);
+ return classPathFile;
+ } catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, "Cannot create classpath argument file", e)); //$NON-NLS-1$
+ }
+ }
+
+ private File createModulePathArgumentFile(String modulePath) throws CoreException {
+ try {
+ String timeStamp = getLaunchTimeStamp();
+ File modulePathFile = new File(processTempFilesDir, String.format(LAUNCH_TEMP_FILE_PREFIX
+ + "%s-module-path-arg-%s.txt", getLaunchConfigurationName(), timeStamp)); //$NON-NLS-1$
+
+ byte[] bytes = ("--module-path " + modulePath).getBytes(StandardCharsets.UTF_8); //$NON-NLS-1$
+
+ Files.write(modulePathFile.toPath(), bytes);
+ return modulePathFile;
+ } catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, "Cannot create module-path argument file", e)); //$NON-NLS-1$
+ }
+ }
+
+ protected String getLaunchTimeStamp() {
+ String timeStamp = launch.getAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP);
+ if (timeStamp == null) {
+ timeStamp = Long.toString(System.currentTimeMillis());
+ }
+ return timeStamp;
+ }
+
+ private String[] getEnvpFromNativeEnvironment() {
+ Map<String, String> nativeEnvironment = getNativeEnvironment();
+ String[] envp = new String[nativeEnvironment.size()];
+ int idx = 0;
+ for (Entry<String, String> entry : nativeEnvironment.entrySet()) {
+ String value = entry.getValue();
+ if (value == null) {
+ value = ""; //$NON-NLS-1$
+ }
+ String key = entry.getKey();
+ envp[idx] = key + '=' + value;
+ idx++;
+ }
+ return envp;
+ }
+
+ protected Map<String, String> getNativeEnvironment() {
+ return DebugPlugin.getDefault().getLaunchManager().getNativeEnvironment();
+ }
+
+ private void shortenClasspathUsingClasspathEnvVariable(int classpathArgumentIndex) {
+ String classpath = cmdLine.get(classpathArgumentIndex);
+ if (envp == null) {
+ envp = getEnvpFromNativeEnvironment();
+ }
+ String classpathEnvVar = CLASSPATH_ENV_VAR_PREFIX + classpath;
+ int index = getEnvClasspathIndex(envp);
+ if (index < 0) {
+ envp = Arrays.copyOf(envp, envp.length + 1);
+ envp[envp.length - 1] = classpathEnvVar;
+ } else {
+ envp[index] = classpathEnvVar;
+ }
+ removeCmdLineArgs(classpathArgumentIndex - 1, 2);
+ }
+
+ private void removeCmdLineArgs(int index, int length) {
+ for (int i = 0; i < length; i++) {
+ cmdLine.remove(index);
+ lastJavaArgumentIndex--;
+ }
+ }
+
+ private void addCmdLineArgs(int index, String... newArgs) {
+ cmdLine.addAll(index, Arrays.asList(newArgs));
+ lastJavaArgumentIndex += newArgs.length;
+ }
+
+ /**
+ * Returns the index in the given array for the CLASSPATH variable
+ *
+ * @param env
+ * the environment array or <code>null</code>
+ * @return -1 or the index of the CLASSPATH variable
+ */
+ private int getEnvClasspathIndex(String[] env) {
+ if (env != null) {
+ for (int i = 0; i < env.length; i++) {
+ if (env[i].regionMatches(true, 0, CLASSPATH_ENV_VAR_PREFIX, 0, 10)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingPlugin.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingPlugin.java
index bdc2ee4..9c20eb8 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingPlugin.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/LaunchingPlugin.java
@@ -22,6 +22,7 @@
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
@@ -104,6 +105,17 @@
public static final String DEBUG_FLAG = "org.eclipse.jdt.launching/debug"; //$NON-NLS-1$
/**
+ * list of temp files for the launch (separated by the path separator char). Files must start with {@link #LAUNCH_TEMP_FILE_PREFIX} and will be
+ * deleted once the process is terminated
+ */
+ public static final String ATTR_LAUNCH_TEMP_FILES = "tempFiles"; //$NON-NLS-1$
+
+ /**
+ * prefix for temp files
+ */
+ public static final String LAUNCH_TEMP_FILE_PREFIX = ".temp-"; //$NON-NLS-1$
+
+ /**
* The {@link DebugTrace} object to print to OSGi tracing
* @since 3.8
*/
@@ -1178,11 +1190,31 @@
Object source = event.getSource();
if (source instanceof IDebugTarget || source instanceof IProcess) {
ArchiveSourceLocation.closeArchives();
+ IProcess process;
+ if (source instanceof IProcess) {
+ process = (IProcess) source;
+ } else {
+ process = ((IDebugTarget) source).getProcess();
+ }
+ deleteProcessTempFiles(process);
}
}
}
}
+ private void deleteProcessTempFiles(IProcess process) {
+ String tempFiles = process.getAttribute(ATTR_LAUNCH_TEMP_FILES);
+ if (tempFiles == null) {
+ return;
+ }
+ // we only delete files starting with LAUNCH_TEMP_FILE_PREFIX²
+ Arrays.stream(tempFiles.split(File.pathSeparator)).map(path -> new File(path)).filter(file -> isValidProcessTempFile(file)).forEach(file -> file.delete());
+ }
+
+ private boolean isValidProcessTempFile(File file) {
+ return file.getName().startsWith(LAUNCH_TEMP_FILE_PREFIX);
+ }
+
/**
* Returns a shared XML parser.
*
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/Standard11xVMRunner.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/Standard11xVMRunner.java
index fb9f643..6b08810 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/Standard11xVMRunner.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/Standard11xVMRunner.java
@@ -15,6 +15,7 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import java.util.stream.Collectors;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -83,9 +84,7 @@
combinedPath[offset] = classPath[i];
offset++;
}
- int cpidx = -1;
if (combinedPath.length > 0) {
- cpidx = arguments.size();
arguments.add("-classpath"); //$NON-NLS-1$
arguments.add(convertClassPath(combinedPath));
}
@@ -94,12 +93,7 @@
String[] programArgs= config.getProgramArguments();
String[] envp = prependJREPath(config.getEnvironment());
- String[] newenvp = checkClasspath(arguments, classPath, envp);
- if(newenvp != null) {
- envp = newenvp;
- arguments.remove(cpidx);
- arguments.remove(cpidx);
- }
+ int lastVMArgumentIndex = arguments.size() - 1;
addArguments(programArgs, arguments);
String[] cmdLine= new String[arguments.size()];
@@ -109,12 +103,17 @@
if (monitor.isCanceled()) {
return;
}
+ File workingDir = getWorkingDir(config);
+ ClasspathShortener classpathShortener = new ClasspathShortener(fVMInstance, launch, cmdLine, lastVMArgumentIndex, workingDir, envp);
+ if (classpathShortener.shortenCommandLineIfNecessary()) {
+ cmdLine = classpathShortener.getCmdLine();
+ envp = classpathShortener.getEnvp();
+ }
subMonitor.worked(1);
subMonitor.subTask(LaunchingMessages.StandardVMRunner_Starting_virtual_machine____3);
Process p= null;
- File workingDir = getWorkingDir(config);
String[] newCmdLine = validateCommandLine(launch.getLaunchConfiguration(), cmdLine);
if(newCmdLine != null) {
cmdLine = newCmdLine;
@@ -138,6 +137,10 @@
if(workingDir != null) {
process.setAttribute(DebugPlugin.ATTR_WORKING_DIRECTORY, workingDir.getAbsolutePath());
}
+ if (!classpathShortener.getProcessTempFiles().isEmpty()) {
+ String tempFiles = classpathShortener.getProcessTempFiles().stream().map(file -> file.getAbsolutePath()).collect(Collectors.joining(File.pathSeparator));
+ process.setAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES, tempFiles);
+ }
subMonitor.worked(1);
}
}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java
index f652aa0..caa4d05 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java
@@ -21,6 +21,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
@@ -217,9 +218,7 @@
}
String[] cp= config.getClassPath();
- int cpidx = -1;
if (cp.length > 0) {
- cpidx = arguments.size();
arguments.add("-classpath"); //$NON-NLS-1$
arguments.add(convertClassPath(cp));
}
@@ -239,7 +238,7 @@
} else {
arguments.add(config.getClassToLaunch());
}
-
+ int lastVMArgumentIndex = arguments.size() - 1;
/*
* String[] cp= config.getClassPath(); int cpidx = -1; if (cp.length > 0) { cpidx = arguments.size(); arguments.add("-classpath");
* //$NON-NLS-1$ arguments.add(convertClassPath(cp)); }
@@ -253,13 +252,6 @@
//format: <jdk path>/jre/bin
String[] envp = prependJREPath(config.getEnvironment(), new Path(program));
- String[] newenvp = checkClasspath(arguments, cp, envp);
- if(newenvp != null) {
- envp = newenvp;
- arguments.remove(cpidx);
- arguments.remove(cpidx);
- }
-
String[] cmdLine= new String[arguments.size()];
arguments.toArray(cmdLine);
@@ -267,6 +259,12 @@
if (monitor.isCanceled()) {
return;
}
+ File workingDir = getWorkingDir(config);
+ ClasspathShortener classpathShortener = new ClasspathShortener(fVMInstance, launch, cmdLine, lastVMArgumentIndex, workingDir, envp);
+ if (classpathShortener.shortenCommandLineIfNecessary()) {
+ cmdLine = classpathShortener.getCmdLine();
+ envp = classpathShortener.getEnvp();
+ }
subMonitor.worked(1);
subMonitor.subTask(LaunchingMessages.StandardVMDebugger_Starting_virtual_machine____4);
@@ -288,7 +286,6 @@
connector.startListening(map);
- File workingDir = getWorkingDir(config);
String[] newCmdLine = validateCommandLine(launch.getLaunchConfiguration(), cmdLine);
if(newCmdLine != null) {
cmdLine = newCmdLine;
@@ -323,6 +320,10 @@
}
process.setAttribute(DebugPlugin.ATTR_ENVIRONMENT, buff.toString());
}
+ if (!classpathShortener.getProcessTempFiles().isEmpty()) {
+ String tempFiles = classpathShortener.getProcessTempFiles().stream().map(file -> file.getAbsolutePath()).collect(Collectors.joining(File.pathSeparator));
+ process.setAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES, tempFiles);
+ }
subMonitor.worked(1);
subMonitor.subTask(LaunchingMessages.StandardVMDebugger_Establishing_debug_connection____5);
int retryCount = 0;
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
index fffbb21..47060d2 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
@@ -20,6 +20,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.stream.Collectors;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -380,9 +381,7 @@
arguments.add(convertClassPath(mp));
}
String[] cp = config.getClassPath();
- int cpidx = -1;
if (cp.length > 0) {
- cpidx = arguments.size();
arguments.add("-classpath"); //$NON-NLS-1$
arguments.add(convertClassPath(cp));
}
@@ -395,26 +394,18 @@
}
}
-
if (isModular(config, fVMInstance)) {
arguments.add("-m"); //$NON-NLS-1$
arguments.add(config.getModuleDescription() + "/" + config.getClassToLaunch()); //$NON-NLS-1$
} else {
arguments.add(config.getClassToLaunch());
}
-
+ int lastVMArgumentIndex = arguments.size() - 1;
String[] programArgs= config.getProgramArguments();
addArguments(programArgs, arguments);
String[] envp = prependJREPath(config.getEnvironment());
- String[] newenvp = checkClasspath(arguments, cp, envp);
- if(newenvp != null) {
- envp = newenvp;
- arguments.remove(cpidx);
- arguments.remove(cpidx);
- }
-
String[] cmdLine= new String[arguments.size()];
arguments.toArray(cmdLine);
@@ -424,10 +415,15 @@
if (monitor.isCanceled()) {
return;
}
+ File workingDir = getWorkingDir(config);
+ ClasspathShortener classpathShortener = new ClasspathShortener(fVMInstance, launch, cmdLine, lastVMArgumentIndex, workingDir, envp);
+ if (classpathShortener.shortenCommandLineIfNecessary()) {
+ cmdLine = classpathShortener.getCmdLine();
+ envp = classpathShortener.getEnvp();
+ }
subMonitor.subTask(LaunchingMessages.StandardVMRunner_Starting_virtual_machine____3);
Process p= null;
- File workingDir = getWorkingDir(config);
String[] newCmdLine = validateCommandLine(launch.getLaunchConfiguration(), cmdLine);
if(newCmdLine != null) {
cmdLine = newCmdLine;
@@ -462,6 +458,10 @@
}
process.setAttribute(DebugPlugin.ATTR_ENVIRONMENT, buff.toString());
}
+ if (!classpathShortener.getProcessTempFiles().isEmpty()) {
+ String tempFiles = classpathShortener.getProcessTempFiles().stream().map(file -> file.getAbsolutePath()).collect(Collectors.joining(File.pathSeparator));
+ process.setAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES, tempFiles);
+ }
subMonitor.worked(1);
subMonitor.done();
}
@@ -484,71 +484,6 @@
}
/**
- * Checks to see if the command / classpath needs to be shortened for Windows. Returns the modified
- * environment or <code>null</code> if no changes are needed.
- *
- * @param args the raw arguments from the runner
- * @param cp the raw classpath from the runner configuration
- * @param env the current environment
- * @return the modified environment or <code>null</code> if no changes were made
- * @sine 3.6.200
- */
- String[] checkClasspath(List<String> args, String[] cp, String[] env) {
- if(Platform.getOS().equals(Platform.OS_WIN32)) {
- //count the complete command length
- int size = 0;
- for (String arg : args) {
- if(arg != null) {
- size += arg.length();
- }
- }
- //greater than 32767 is a no-go
- //see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
- if(size > 32767) {
- StringBuilder newcp = new StringBuilder("CLASSPATH="); //$NON-NLS-1$
- for (int i = 0; i < cp.length; i++) {
- newcp.append(cp[i]);
- newcp.append(File.pathSeparatorChar);
- }
- String[] newenvp = null;
- int index = -1;
- if(env == null) {
- Map<String, String> nenv = DebugPlugin.getDefault().getLaunchManager().getNativeEnvironment();
- Entry<String, String> entry = null;
- newenvp = new String[nenv.size()];
- int idx = 0;
- for (Iterator<Entry<String, String>> i = nenv.entrySet().iterator(); i.hasNext();) {
- entry = i.next();
- String value = entry.getValue();
- if(value == null) {
- value = ""; //$NON-NLS-1$
- }
- String key = entry.getKey();
- if(key.equalsIgnoreCase("CLASSPATH")) { //$NON-NLS-1$
- index = idx;
- }
- newenvp[idx] = key+'='+value;
- idx++;
- }
- }
- else {
- newenvp = env;
- index = getCPIndex(newenvp);
- }
- if(index < 0) {
- String[] newenv = new String[newenvp.length+1];
- System.arraycopy(newenvp, 0, newenv, 0, newenvp.length);
- newenv[newenvp.length] = newcp.toString();
- return newenv;
- }
- newenvp[index] = newcp.toString();
- return newenvp;
- }
- }
- return null;
- }
-
- /**
* Prepends the correct java version variable state to the environment path for Mac VMs
*
* @param env the current array of environment variables to run with
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
index 5e910b6..873e5c6 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
@@ -369,8 +369,12 @@
public static final String ATTR_EXCLUDE_TEST_CODE = LaunchingPlugin.getUniqueIdentifier() + ".ATTR_EXCLUDE_TEST_CODE"; //$NON-NLS-1$
/**
- * Status code indicating a launch configuration does not
- * specify a project when a project is required.
+ * @since 3.11
+ */
+ public static final String ATTR_USE_CLASSPATH_ONLY_JAR = LaunchingPlugin.getUniqueIdentifier() + ".ATTR_USE_CLASSPATH_ONLY_JAR"; //$NON-NLS-1$
+
+ /**
+ * Status code indicating a launch configuration does not specify a project when a project is required.
*/
public static final int ERR_UNSPECIFIED_PROJECT = 100;
@@ -540,6 +544,11 @@
public static final int ERR_PROJECT_CLOSED = 124;
/**
+ * @since 3.11
+ */
+ public static final int ERR_CLASSPATH_TOO_LONG = 125;
+
+ /**
* Status code indicating an unexpected internal error.
*/
public static final int ERR_INTERNAL_ERROR = 150;