Bug 565902 - Support for argument files during launch
Adds a new setting to the arguments tab for writing arguments into a
file. This allows for endless long command lines when launching Java
applications.
Change-Id: I6e45fc470fd39eadeeb9e12880e2aa6a24df7825
Bug: 565902
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 10086fb..d365270 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
@@ -55,6 +55,7 @@
import org.eclipse.jdt.debug.tests.console.JavaStackTraceConsoleTest;
import org.eclipse.jdt.debug.tests.core.AlternateStratumTests;
import org.eclipse.jdt.debug.tests.core.ArgumentTests;
+import org.eclipse.jdt.debug.tests.core.ArgumentTestsWithArgfile;
import org.eclipse.jdt.debug.tests.core.ArrayTests;
import org.eclipse.jdt.debug.tests.core.BootpathTests;
import org.eclipse.jdt.debug.tests.core.ClasspathContainerTests;
@@ -244,6 +245,7 @@
addTest(new TestSuite(EnvironmentTests.class));
addTest(new TestSuite(ExecutionEnvironmentTests.class));
addTest(new TestSuite(ArgumentTests.class));
+ addTest(new TestSuite(ArgumentTestsWithArgfile.class));
//Console tests
addTest(new TestSuite(ConsoleTests.class));
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTests.java
index 3c22791..50a9967 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTests.java
@@ -13,13 +13,20 @@
*******************************************************************************/
package org.eclipse.jdt.debug.tests.core;
+import static java.util.stream.Collectors.joining;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
@@ -42,6 +49,7 @@
public class ArgumentTests extends AbstractDebugTest {
private Object fLock = new Object();
+ protected boolean fUseArgfile = false;
private class ConsoleArgumentOutputRetriever implements IConsoleLineTrackerExtension {
@@ -343,20 +351,23 @@
*/
private void testOutput(String mainTypeName, String vmArgs, String programArgs, String outputValue) throws CoreException {
ILaunchConfigurationWorkingCopy workingCopy = newConfiguration(null, "config1");
+
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, get14Project().getProject().getName());
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, mainTypeName);
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_STOP_IN_MAIN, true);
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, vmArgs);
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, programArgs);
+ workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, fUseArgfile);
- Map<String, String> env = getLaunchManager().getNativeEnvironment().entrySet().stream()
- .filter(e -> !"JAVA_TOOL_OPTIONS".equals(e.getKey()))
- .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
- workingCopy.setAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, false);
+ Map<String, String> env = getLaunchManager().getNativeEnvironment().entrySet().stream().filter(e -> !"JAVA_TOOL_OPTIONS".equals(e.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ workingCopy.setAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, false);
workingCopy.setAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, env);
IVMInstall vm = JavaRuntime.getVMInstall(get14Project());
assertNotNull("shold be able to get the default VM install from the 1.4 project", vm);
+ if (fUseArgfile) {
+ assertTrue("test requires a JVM >= 9", JavaRuntime.isModularJava(vm));
+ }
//workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, JavaRuntime.newJREContainerPath(vm).toPortableString());
// use 'java' instead of 'javaw' to launch tests (javaw is problematic on JDK1.4.2)
@@ -368,23 +379,37 @@
ConsoleLineTracker.setDelegate(retriever);
IProcess process = null;
ILaunch launch = null;
+ String commandLine = null;
try {
HashSet<String> set = new HashSet<>();
set.add(ILaunchManager.RUN_MODE);
ensurePreferredDelegate(workingCopy, set);
launch = workingCopy.launch(ILaunchManager.RUN_MODE, null);
process = launch.getProcesses()[0];
+ commandLine = process.getAttribute(IProcess.ATTR_CMDLINE);
} catch (Exception e) {
e.printStackTrace();
}
+ assertNotNull(commandLine);
+ if (!fUseArgfile) {
+ assertTrue("command line must not contain an @argfile", commandLine.indexOf(" @") == -1);
+ } else {
+ assertTrue("command line must contain an @argfile", commandLine.indexOf(" @") > -1);
+ }
try {
String output = retriever.getOutput();
// output if in error
if (!outputValue.equals(output)) {
System.out.println();
System.out.println(getName());
- System.out.println("\tExpected: " + outputValue);
- System.out.println("\tActual: " + output);
+ System.out.println("\tExpected: " + outputValue);
+ System.out.println("\tActual: " + output);
+ System.out.println("\tCommand Line: " + commandLine);
+ if (fUseArgfile) {
+ System.out.println("\tArgfile: "
+ + readArgfile(commandLine).stream().collect(joining("\n\t ")));
+ System.out.println();
+ }
}
assertEquals(outputValue, output);
} finally {
@@ -398,6 +423,18 @@
}
}
+ private List<String> readArgfile(String commandLine) {
+ String[] arguments = DebugPlugin.parseArguments(commandLine);
+ assertEquals("command line too long, only command @argfile expected", 2, arguments.length);
+ String argfile = arguments[1];
+ assertTrue("wrong command line, expected @argfile at index 1: " + commandLine, argfile != null && argfile.startsWith("@"));
+ try {
+ return Files.readAllLines(Path.of(argfile.substring(1)));
+ } catch (IOException e) {
+ throw new IllegalStateException("Error reading @argfile: " + argfile, e);
+ }
+ }
+
/**
* Tests the default VM args
* @throws CoreException
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTestsWithArgfile.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTestsWithArgfile.java
new file mode 100644
index 0000000..ddd03ae
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTestsWithArgfile.java
@@ -0,0 +1,10 @@
+package org.eclipse.jdt.debug.tests.core;
+
+public class ArgumentTestsWithArgfile extends ArgumentTests {
+
+ public ArgumentTestsWithArgfile(String name) {
+ super(name);
+ fUseArgfile = true;
+ }
+
+}
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaArgumentsTab.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaArgumentsTab.java
index 093772a..e1c3d37 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaArgumentsTab.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaArgumentsTab.java
@@ -320,6 +320,7 @@
getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, LauncherMessages.JavaArgumentsTab_AttributeLabel_VMArguments);
getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_USE_START_ON_FIRST_THREAD, LauncherMessages.JavaArgumentsTab_AttributeLabel_UseAtStart);
getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES, LauncherMessages.JavaArgumentsTab_AttributeLabel_ActivateHelpfulNullPointerExceptions);
+ getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, LauncherMessages.JavaArgumentsTab_AttributeLabel_UseArgfile);
getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, LauncherMessages.JavaArgumentsTab_AttributeLabel_WorkingDirectory);
}
}
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 eea647b..ef34e7d 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
@@ -31,6 +31,8 @@
public static String JavaArgumentsTab_AttributeLabel_UseAtStart;
public static String JavaArgumentsTab_AttributeLabel_ActivateHelpfulNullPointerExceptions;
public static String JavaArgumentsTab_AttributeTooltip_ActivateHelpfulNullPointerExceptions;
+ public static String JavaArgumentsTab_AttributeLabel_UseArgfile;
+ public static String JavaArgumentsTab_AttributeTooltip_UseArgfile;
public static String JavaArgumentsTab_AttributeLabel_WorkingDirectory;
@@ -55,6 +57,7 @@
public static String VMArgumentsBlock_0;
public static String VMArgumentsBlock_1;
public static String VMArgumentsBlock_2;
+ public static String VMArgumentsBlock_3;
public static String VMArgumentsBlock_VM_Arguments;
public static String VMArgumentsBlock_4;
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 02c59f3..fac6a6f 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
@@ -24,7 +24,9 @@
JavaArgumentsTab_AttributeLabel_VMArguments=VM arguments
JavaArgumentsTab_AttributeLabel_UseAtStart=Use at start on 1st thread
JavaArgumentsTab_AttributeLabel_WorkingDirectory=WorkingDirectory
+JavaArgumentsTab_AttributeLabel_UseArgfile=Use @&argfile
JavaArgumentsTab_AttributeTooltip_ActivateHelpfulNullPointerExceptions=You need at least Java14 to make use of it!
+JavaArgumentsTab_AttributeTooltip_UseArgfile=Write all arguments into an @argfile to allow an unlimited number when launching with Java 9 or higher.
RuntimeClasspathAdvancedDialog_6=Add &Variable String:
RuntimeClasspathAdvancedDialog_7=Va&riables...
@@ -32,6 +34,7 @@
VMArgumentsBlock_0=Use the -&XstartOnFirstThread argument when launching with SWT
VMArgumentsBlock_1=Use temporary JAR to speci&fy classpath (to avoid classpath length limitations)
VMArgumentsBlock_2=Use the -XX:+ShowCode&DetailsInExceptionMessages argument when launching
+VMArgumentsBlock_3=Use @&argfile when launching
VMArgumentsBlock_VM_Arguments=VM Arguments
JavaConnectTab__Allow_termination_of_remote_VM_6=&Allow termination of remote VM
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/VMArgumentsBlock.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/VMArgumentsBlock.java
index b99745e..8e7ced3 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/VMArgumentsBlock.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/VMArgumentsBlock.java
@@ -46,6 +46,7 @@
protected Text fVMArgumentsText;
private Button fUseStartOnFirstThread = null;
private Button fHelpfulExceptions = null;
+ private Button fUseArgfile = null;
private Button fPgrmArgVariableButton;
/**
@@ -122,6 +123,15 @@
scheduleUpdateJob();
}
});
+ fUseArgfile = SWTFactory.createCheckButton(group, LauncherMessages.VMArgumentsBlock_3, null, true, 1);
+ fUseArgfile.setEnabled(false);
+ fUseArgfile.setToolTipText(LauncherMessages.JavaArgumentsTab_AttributeTooltip_UseArgfile);
+ fUseArgfile.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ scheduleUpdateJob();
+ }
+ });
}
/**
@@ -132,6 +142,7 @@
configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, (String)null);
configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_START_ON_FIRST_THREAD, true);
configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES, true);
+ configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, false);
}
/**
@@ -147,6 +158,9 @@
if (fHelpfulExceptions != null) {
fHelpfulExceptions.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES, true));
}
+ if (fUseArgfile != null) {
+ fUseArgfile.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, true));
+ }
} catch (CoreException e) {
setErrorMessage(LauncherMessages.JavaArgumentsTab_Exception_occurred_reading_configuration___15 + e.getStatus().getMessage());
JDIDebugUIPlugin.log(e);
@@ -168,6 +182,12 @@
} else {
fHelpfulExceptions.setEnabled(false);
}
+ if (isJavaNewerThan(configuration, JavaCore.VERSION_1_8)) {
+ configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, fUseArgfile.getSelection());
+ fUseArgfile.setEnabled(true);
+ } else {
+ fUseArgfile.setEnabled(false);
+ }
}
/**
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineQuoting.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineQuoting.java
new file mode 100644
index 0000000..9ca92a7
--- /dev/null
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineQuoting.java
@@ -0,0 +1,60 @@
+package org.eclipse.jdt.internal.launching;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.osgi.service.environment.Constants;
+
+/**
+ * Utility for quoting of command line Arguments
+ */
+public class CommandLineQuoting {
+
+ private CommandLineQuoting() {
+ // empty
+ }
+
+ public static String[] quoteWindowsArgs(String[] cmdLine) {
+ // see https://bugs.eclipse.org/387504 , workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6511002
+ if (Platform.getOS().equals(Constants.OS_WIN32)) {
+ String[] winCmdLine = new String[cmdLine.length];
+ if (cmdLine.length > 0) {
+ winCmdLine[0] = cmdLine[0];
+ }
+ for (int i = 1; i < cmdLine.length; i++) {
+ winCmdLine[i] = winQuote(cmdLine[i]);
+ }
+ cmdLine = winCmdLine;
+ }
+ return cmdLine;
+ }
+
+ static boolean needsQuoting(String s) {
+ int len = s.length();
+ if (len == 0) {
+ return true;
+ }
+ if ("\"\"".equals(s)) //$NON-NLS-1$
+ {
+ return false; // empty quotes must not be quoted again
+ }
+ for (int i = 0; i < len; i++) {
+ switch (s.charAt(i)) {
+ case ' ':
+ case '\t':
+ case '\\':
+ case '"':
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String winQuote(String s) {
+ if (!needsQuoting(s)) {
+ return s;
+ }
+ s = s.replaceAll("([\\\\]*)\"", "$1$1\\\\\""); //$NON-NLS-1$ //$NON-NLS-2$
+ s = s.replaceAll("([\\\\]*)\\z", "$1$1"); //$NON-NLS-1$ //$NON-NLS-2$
+ return "\"" + s + "\""; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineShortener.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineShortener.java
new file mode 100644
index 0000000..c2b1f0d
--- /dev/null
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineShortener.java
@@ -0,0 +1,230 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Gunnar Wagenknecht and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Gunnar Wagenknecht - copied from ClasspathShortener and simplified to shorten all arguments
+ *******************************************************************************/
+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.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+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.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jdt.launching.IVMInstall;
+import org.eclipse.jdt.launching.IVMInstall2;
+import org.eclipse.osgi.service.environment.Constants;
+
+/**
+ * Shortens the command line by writing all commands into an arguments file.
+ */
+public class CommandLineShortener {
+ public static String getJavaVersion(IVMInstall vmInstall) {
+ if (vmInstall instanceof IVMInstall2) {
+ IVMInstall2 install = (IVMInstall2) vmInstall;
+ return install.getJavaVersion();
+ }
+ return null;
+ }
+
+ private final String javaVersion;
+ private final ILaunch launch;
+ private final String[] cmdLine;
+ 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 CommandLineShortener(IVMInstall vmInstall, ILaunch launch, String[] cmdLine, File workingDir) {
+ this(getJavaVersion(vmInstall), launch, cmdLine, workingDir);
+ }
+
+ protected CommandLineShortener(String javaVersion, ILaunch launch, String[] cmdLine, File workingDir) {
+ Assert.isNotNull(javaVersion);
+ Assert.isNotNull(launch);
+ Assert.isNotNull(cmdLine);
+ this.javaVersion = javaVersion;
+ this.launch = launch;
+ this.cmdLine = cmdLine;
+ this.processTempFilesDir = workingDir != null ? workingDir : Paths.get(".").toAbsolutePath().normalize().toFile(); //$NON-NLS-1$
+ }
+
+ protected void addProcessTempFile(File file) {
+ processTempFiles.add(file);
+ }
+
+ protected File createArgumentFile(String[] cmdLine) throws CoreException {
+ try {
+ String timeStamp = getLaunchTimeStamp();
+ File argumentsFile = new File(processTempFilesDir, String.format(LAUNCH_TEMP_FILE_PREFIX
+ + "%s-args-%s.txt", getLaunchConfigurationName(), timeStamp)); //$NON-NLS-1$
+
+ cmdLine = quoteForArgfile(cmdLine);
+
+ Files.write(argumentsFile.toPath(), Arrays.asList(cmdLine));
+ return argumentsFile;
+ } catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, "Cannot create argument file", e)); //$NON-NLS-1$
+ }
+ }
+
+ String[] quoteForArgfile(String[] cmdLine) {
+ if (Platform.getOS().equals(Constants.OS_WIN32)) {
+ return CommandLineQuoting.quoteWindowsArgs(cmdLine);
+ }
+
+ String[] quotedCmdLine = new String[cmdLine.length];
+ for (int i = 0; i < cmdLine.length; i++) {
+ String arg = cmdLine[i];
+ if (CommandLineQuoting.needsQuoting(arg)) {
+ StringBuilder escapedArg = new StringBuilder();
+ for (int j = 0; j < arg.length(); j++) {
+ char c = arg.charAt(j);
+ if (c == '\\') {
+ escapedArg.append('\\');
+ } else if (c == '\"') {
+ escapedArg.append('\\');
+ }
+ escapedArg.append(c);
+ }
+ arg = "\"" + escapedArg.toString() + "\""; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ quotedCmdLine[i] = arg;
+ }
+ return quotedCmdLine;
+ }
+
+ protected String getLaunchConfigurationName() {
+ return launch.getLaunchConfiguration().getName();
+ }
+
+ protected String getLaunchTimeStamp() {
+ String timeStamp = launch.getAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP);
+ if (timeStamp == null) {
+ timeStamp = Long.toString(System.currentTimeMillis());
+ }
+ return timeStamp;
+ }
+
+ /**
+ * 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);
+ }
+
+ public File getProcessTempFilesDir() {
+ return processTempFilesDir;
+ }
+
+ /**
+ * @return the original, unshortened command line
+ */
+ public String[] getOriginalCmdLine() {
+ return cmdLine;
+ }
+
+ /**
+ * @return <code>true</code> if the JVM supports launching with argument files, <code>false</code> otherwise
+ */
+ protected boolean isArgumentFileSupported() {
+ return JavaCore.compareJavaVersions(javaVersion, JavaCore.VERSION_9) >= 0;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Writes the command line into an arguments file and returns the shortened command line.
+ *
+ * @return a shortened command line
+ * @throws CoreException
+ */
+ public String[] shortenCommandLine() throws CoreException {
+ List<String> fullCommandLine = new ArrayList<>(Arrays.asList(cmdLine));
+ List<String> shortCommandLine = new ArrayList<>();
+
+ shortCommandLine.add(fullCommandLine.remove(0));
+
+ File argumentFile = createArgumentFile(fullCommandLine.toArray(new String[fullCommandLine.size()]));
+ addProcessTempFile(argumentFile);
+ shortCommandLine.add("@" + argumentFile.getAbsolutePath());//$NON-NLS-1$
+
+ return shortCommandLine.toArray(new String[shortCommandLine.size()]);
+ }
+
+ /**
+ * Indicates if the command line {@link #isArgumentFileSupported() can} and should be shortened.
+ * <p>
+ * The command line should only be shortened if at least Java 9 is used and the launch is configured to do so.
+ * </p>
+ *
+ * @return <code>true</code> if {@link #isArgumentFileSupported()} returns <code>true</code> and command line should be shortened,
+ * <code>false</code> otherwise
+ * @throws CoreException
+ */
+ public boolean shouldShortenCommandLine() throws CoreException {
+ if (!isArgumentFileSupported()) {
+ return false;
+ }
+
+ if (cmdLine.length < 2) {
+ // no need to shorten if it's just the program argument
+ return false;
+ }
+
+ ILaunchConfiguration configuration = launch.getLaunchConfiguration();
+ if (configuration != null) {
+ return configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, true);
+ }
+
+ return false;
+ }
+}
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 f89b4c7..fea720b 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
@@ -13,7 +13,6 @@
*******************************************************************************/
package org.eclipse.jdt.internal.launching;
-
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
@@ -25,6 +24,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -43,7 +43,6 @@
import org.eclipse.jdt.launching.VMRunnerConfiguration;
import org.eclipse.osgi.util.NLS;
-
/**
* A launcher for running Java main classes.
*/
@@ -351,6 +350,7 @@
private String[] envp;
private File workingDir;
private ClasspathShortener classpathShortener;
+ private CommandLineShortener commandLineShortener;
private int port;
public String[] getEnvp() {
@@ -385,6 +385,14 @@
this.classpathShortener = classpathShortener;
}
+ public CommandLineShortener getCommandLineShortener() {
+ return commandLineShortener;
+ }
+
+ public void setCommandLineShortener(CommandLineShortener commandLineShortener) {
+ this.commandLineShortener = commandLineShortener;
+ }
+
public int getPort() {
return port;
}
@@ -477,11 +485,16 @@
if (newCmdLine != null) {
cmdLine = newCmdLine;
}
+ CommandLineShortener commandLineShortener = new CommandLineShortener(fVMInstance, launch, newCmdLine, workingDir);
+ if (commandLineShortener.shouldShortenCommandLine()) {
+ cmdLine = commandLineShortener.shortenCommandLine();
+ }
CommandDetails cmd = new CommandDetails();
cmd.setCommandLine(cmdLine);
cmd.setEnvp(envp);
cmd.setWorkingDir(workingDir);
cmd.setClasspathShortener(classpathShortener);
+ cmd.setCommandLineShortener(commandLineShortener);
subMonitor.worked(1);
return cmd;
}
@@ -531,8 +544,9 @@
}
process.setAttribute(DebugPlugin.ATTR_ENVIRONMENT, buff.toString());
}
- if (!cmdDetails.getClasspathShortener().getProcessTempFiles().isEmpty()) {
- String tempFiles = cmdDetails.getClasspathShortener().getProcessTempFiles().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator));
+ if (!cmdDetails.getClasspathShortener().getProcessTempFiles().isEmpty()
+ || !cmdDetails.getCommandLineShortener().getProcessTempFiles().isEmpty()) {
+ String tempFiles = Stream.concat(cmdDetails.getClasspathShortener().getProcessTempFiles().stream(), cmdDetails.getCommandLineShortener().getProcessTempFiles().stream()).map(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/launching/AbstractVMRunner.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMRunner.java
index e6ef2e6..5ce14ef 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMRunner.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMRunner.java
@@ -21,7 +21,6 @@
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.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
@@ -29,9 +28,9 @@
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.internal.launching.CommandLineQuoting;
import org.eclipse.jdt.internal.launching.LaunchingMessages;
import org.eclipse.jdt.internal.launching.LaunchingPlugin;
-import org.eclipse.osgi.service.environment.Constants;
/**
* Abstract implementation of a VM runner.
@@ -121,46 +120,7 @@
* @since 3.11
*/
protected static String[] quoteWindowsArgs(String[] cmdLine) {
- // see https://bugs.eclipse.org/387504 , workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6511002
- if (Platform.getOS().equals(Constants.OS_WIN32)) {
- String[] winCmdLine = new String[cmdLine.length];
- if(cmdLine.length > 0) {
- winCmdLine[0] = cmdLine[0];
- }
- for (int i = 1; i < cmdLine.length; i++) {
- winCmdLine[i] = winQuote(cmdLine[i]);
- }
- cmdLine = winCmdLine;
- }
- return cmdLine;
- }
-
-
- private static boolean needsQuoting(String s) {
- int len = s.length();
- if (len == 0) {
- return true;
- }
- if ("\"\"".equals(s)) //$NON-NLS-1$
- {
- return false; // empty quotes must not be quoted again
- }
- for (int i = 0; i < len; i++) {
- switch (s.charAt(i)) {
- case ' ': case '\t': case '\\': case '"':
- return true;
- }
- }
- return false;
- }
-
- private static String winQuote(String s) {
- if (! needsQuoting(s)) {
- return s;
- }
- s = s.replaceAll("([\\\\]*)\"", "$1$1\\\\\""); //$NON-NLS-1$ //$NON-NLS-2$
- s = s.replaceAll("([\\\\]*)\\z", "$1$1"); //$NON-NLS-1$ //$NON-NLS-2$
- return "\"" + s + "\""; //$NON-NLS-1$ //$NON-NLS-2$
+ return CommandLineQuoting.quoteWindowsArgs(cmdLine);
}
/**
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 ff147bb..8e55ebb 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
@@ -379,6 +379,15 @@
*/
public static final String ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES = LaunchingPlugin.getUniqueIdentifier()
+ ".ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES"; //$NON-NLS-1$
+
+ /**
+ * Attribute key to write arguments into an file when launching allowing an unlimited number of arguments. Only valid for java > 8 see
+ * https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8027634
+ *
+ * @since 3.18
+ */
+ public static final String ATTR_USE_ARGFILE = LaunchingPlugin.getUniqueIdentifier() + ".ATTR_ATTR_USE_ARGFILE"; //$NON-NLS-1$
+
/**
* Launch configuration attribute key. The value is a boolean specifying whether output folders corresponding to test sources should not be added
* to the runtime classpath and test dependencies should not be added to the default classpath.