diff --git a/cpplite/cpplite.debugger/nbproject/project.xml b/cpplite/cpplite.debugger/nbproject/project.xml index 7574c06a34a1..55bb7e8d8f13 100644 --- a/cpplite/cpplite.debugger/nbproject/project.xml +++ b/cpplite/cpplite.debugger/nbproject/project.xml @@ -60,13 +60,22 @@ 1.59 + + org.netbeans.modules.extexecution.base + + + + 2 + 1.20 + + org.netbeans.modules.nativeimage.api 0 - 0.4 + 0.5 diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java index 946f04720bb4..7c30f7c5138b 100644 --- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java +++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebugger.java @@ -50,6 +50,7 @@ import org.netbeans.api.debugger.DebuggerInfo; import org.netbeans.api.debugger.DebuggerManager; import org.netbeans.api.debugger.DebuggerManagerAdapter; +import org.netbeans.api.extexecution.base.ExplicitProcessParameters; import org.netbeans.modules.cnd.debugger.gdb2.mi.MICommand; import org.netbeans.modules.cnd.debugger.gdb2.mi.MICommandInjector; import org.netbeans.modules.cnd.debugger.gdb2.mi.MIConst; @@ -75,6 +76,7 @@ import org.openide.text.Annotatable; import org.openide.text.Line; import org.openide.util.Exceptions; +import org.openide.util.Lookup; import org.openide.util.Pair; import org.openide.util.RequestProcessor; @@ -811,15 +813,20 @@ public Object[] getServices () { CPPLiteDebugger debugger = es[0].lookupFirst(null, CPPLiteDebugger.class); List executable = new ArrayList<>(); executable.add(configuration.getDebugger()); - executable.add("--interpreter=mi"); - executable.add("--tty=" + pty.getSlaveName()); + executable.add("--interpreter=mi"); // NOI18N + executable.add("--tty=" + pty.getSlaveName()); // NOI18N if (configuration.isAttach()) { - executable.add("-p"); + executable.add("-p"); // NOI18N executable.add(Long.toString(configuration.getAttachProcessId())); } + if (configuration.getExecutable().size() > 1) { + executable.add("--args"); // NOI18N + } executable.addAll(configuration.getExecutable()); - Process debuggee = new ProcessBuilder(executable).directory(configuration.getDirectory()).start(); - new RequestProcessor(configuration.getDisplayName() + " (pty deallocator)").post(() -> { + ProcessBuilder processBuilder = new ProcessBuilder(executable); + setParameters(processBuilder, configuration); + Process debuggee = processBuilder.start(); + new RequestProcessor(configuration.getDisplayName() + " (pty deallocator)").post(() -> { // NOI18N try { while (debuggee.isAlive()) { try { @@ -878,6 +885,25 @@ public void destroy() { }; } + private static void setParameters(ProcessBuilder processBuilder, CPPLiteDebuggerConfig configuration) { + ExplicitProcessParameters processParameters = configuration.getProcessParameters(); + if (processParameters.getWorkingDirectory() != null) { + processBuilder.directory(processParameters.getWorkingDirectory()); + } + if (!processParameters.getEnvironmentVariables().isEmpty()) { + Map environment = processBuilder.environment(); + for (Map.Entry entry : processParameters.getEnvironmentVariables().entrySet()) { + String env = entry.getKey(); + String val = entry.getValue(); + if (val != null) { + environment.put(env, val); + } else { + environment.remove(env); + } + } + } + } + private class BreakpointsHandler extends DebuggerManagerAdapter implements PropertyChangeListener { private final Map breakpointsById = new ConcurrentHashMap<>(); diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebuggerConfig.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebuggerConfig.java index 3a8ac18f375d..7ea6634831a4 100644 --- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebuggerConfig.java +++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/CPPLiteDebuggerConfig.java @@ -19,10 +19,12 @@ package org.netbeans.modules.cpplite.debugger; -import java.io.File; +import java.util.ArrayList; import java.util.List; import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.api.extexecution.base.ExplicitProcessParameters; +import org.openide.util.Exceptions; /** * @@ -31,16 +33,33 @@ public final class CPPLiteDebuggerConfig { private final List executable; - private final File directory; + private final ExplicitProcessParameters processParameters; @NullAllowed private final Long processId; private final String debugger; - public CPPLiteDebuggerConfig(List executable, File directory, @NullAllowed Long processId, String debugger) { - this.executable = executable; - this.directory = directory; + public CPPLiteDebuggerConfig(List executable, ExplicitProcessParameters processParameters, @NullAllowed Long processId, String debugger) { + this.processParameters = processParameters; this.processId = processId; this.debugger = debugger; + if (processParameters.isArgReplacement()) { + this.executable = new ArrayList<>(); + this.executable.add(executable.get(0)); + if (processParameters.getArguments() != null) { + this.executable.addAll(processParameters.getArguments()); + } + } else { + if (processParameters.getArguments() != null) { + this.executable = new ArrayList<>(); + this.executable.addAll(executable); + this.executable.addAll(processParameters.getArguments()); + } else { + this.executable = executable; + } + } + if (processParameters.getLauncherArguments() != null) { + Exceptions.printStackTrace(new IllegalStateException("Launcher arguments " + processParameters.getLauncherArguments() + " can not be accepted by CPPLite debugger")); + } } public String getDisplayName() { @@ -55,10 +74,10 @@ public List getExecutable() { } /** - * Get the directory in which the executable command is to be launched. + * Get the parameters which the executable command is to be launched with. */ - public File getDirectory() { - return directory; + public ExplicitProcessParameters getProcessParameters() { + return processParameters; } /** diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/api/Debugger.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/api/Debugger.java index fc8e741b6d0b..ef98531b2e1d 100644 --- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/api/Debugger.java +++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/api/Debugger.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.util.List; +import org.netbeans.api.extexecution.base.ExplicitProcessParameters; import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger; import org.netbeans.modules.cpplite.debugger.CPPLiteDebuggerConfig; @@ -38,8 +39,9 @@ public static Process startInDebugger(List command) throws IOException { public static Process startInDebugger(List command, File directory) throws IOException { CPPLiteDebugger[] debugger = new CPPLiteDebugger[] { null }; + ExplicitProcessParameters processParameters = ExplicitProcessParameters.builder().workingDirectory(directory).build(); Process engineProcess = CPPLiteDebugger.startDebugging( - new CPPLiteDebuggerConfig(command, directory, null, "gdb"), + new CPPLiteDebuggerConfig(command, processParameters, null, "gdb"), engine -> { debugger[0] = engine.lookupFirst(null, CPPLiteDebugger.class); }); diff --git a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java index 7d71aaf9e22e..552107e0fe46 100644 --- a/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java +++ b/cpplite/cpplite.debugger/src/org/netbeans/modules/cpplite/debugger/ni/NIDebuggerProviderImpl.java @@ -31,6 +31,7 @@ import org.netbeans.api.debugger.DebuggerEngine; import org.netbeans.api.extexecution.ExecutionDescriptor; import org.netbeans.api.extexecution.ExecutionService; +import org.netbeans.api.extexecution.base.ExplicitProcessParameters; import org.netbeans.modules.cpplite.debugger.CPPFrame; import org.netbeans.modules.cpplite.debugger.CPPLiteDebugger; import org.netbeans.modules.cpplite.debugger.CPPLiteDebuggerConfig; @@ -42,12 +43,14 @@ import org.netbeans.modules.nativeimage.api.debug.NIFrame; import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor; import org.netbeans.modules.nativeimage.api.debug.NIVariable; +import org.netbeans.modules.nativeimage.api.debug.StartDebugParameters; import org.netbeans.modules.nativeimage.spi.debug.NIDebuggerProvider; import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer; import org.openide.LifecycleManager; import org.openide.util.RequestProcessor; import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer; +import org.openide.util.Lookup; /** * @@ -85,10 +88,20 @@ public void setVariablesDisplayer(VariableDisplayer variablesDisplayer) { } @Override - public CompletableFuture start(List command, File workingDirectory, String miDebugger, String displayName, ExecutionDescriptor executionDescriptor, Consumer startedEngine) { + public CompletableFuture start(StartDebugParameters debugParameters, Consumer startedEngine) { if (debugger != null) { throw new IllegalStateException("Debugger has started already."); } + List command = debugParameters.getCommand(); + String miDebugger = debugParameters.getDebugger(); + String displayName = debugParameters.getDisplayName(); + ExecutionDescriptor executionDescriptor = debugParameters.getExecutionDescriptor(); + Lookup contextLookup = debugParameters.getContextLookup(); + ExplicitProcessParameters explicitParameters = contextLookup != null ? ExplicitProcessParameters.buildExplicitParameters(contextLookup) : null; + if (explicitParameters == null) { + explicitParameters = ExplicitProcessParameters.builder().workingDirectory(debugParameters.getWorkingDirectory()).build(); + } + final ExplicitProcessParameters processParameters = explicitParameters; if (executionDescriptor == null) { executionDescriptor = new ExecutionDescriptor() .showProgress(true) @@ -104,7 +117,7 @@ public CompletableFuture start(List command, File workingDirectory try { LifecycleManager.getDefault().saveAll(); engineProcess = CPPLiteDebugger.startDebugging( - new CPPLiteDebuggerConfig(command, workingDirectory, null, miDebugger), + new CPPLiteDebuggerConfig(command, processParameters, null, miDebugger), engine -> { debugger[0] = engine.lookupFirst(null, CPPLiteDebugger.class); this.debugger = debugger[0]; @@ -147,10 +160,11 @@ public CompletableFuture attach(String executablePath, long processId, Str if (debugger != null) { throw new IllegalStateException("Debugger has started already."); } + ExplicitProcessParameters processParameters = ExplicitProcessParameters.builder().workingDirectory(new File(System.getProperty("user.dir", ""))).build(); // NOI18N CompletableFuture completed = new CompletableFuture<>(); try { CPPLiteDebugger.startDebugging( - new CPPLiteDebuggerConfig(Collections.singletonList(executablePath), new File(System.getProperty("user.dir")), processId, miDebugger), + new CPPLiteDebuggerConfig(Collections.singletonList(executablePath), processParameters, processId, miDebugger), engine -> { CPPLiteDebugger debugger = engine.lookupFirst(null, CPPLiteDebugger.class); this.debugger = debugger; diff --git a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/AbstractDebugTest.java b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/AbstractDebugTest.java index d3aca0c56d8d..26aff70a167a 100644 --- a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/AbstractDebugTest.java +++ b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/AbstractDebugTest.java @@ -18,21 +18,34 @@ */ package org.netbeans.modules.cpplite.debugger; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.Writer; import java.net.URI; import java.util.Arrays; +import java.util.List; import junit.framework.Test; import org.netbeans.api.debugger.DebuggerEngine; +import org.netbeans.api.extexecution.base.ExplicitProcessParameters; import org.netbeans.junit.NbModuleSuite; import org.netbeans.junit.NbTestCase; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; public abstract class AbstractDebugTest extends NbTestCase { protected DebuggerEngine engine; protected CPPLiteDebugger debugger; protected Process process; + protected StringBuffer stdOut; + protected StringBuffer stdErr; private final int[] suspendCount = new int[]{0}; private final int[] resumeCount = new int[]{0}; @@ -41,6 +54,14 @@ protected AbstractDebugTest(String s) { super(s); } + protected final static void createSourceFile(String fileName, File wd, String content) throws IOException { + FileObject source = FileUtil.createData(FileUtil.toFileObject(wd), "main.cpp"); + try (OutputStream os = source.getOutputStream(); + Writer w = new OutputStreamWriter(os)) { + w.append(content); + } + } + protected final static void compileC(String name, File wd) throws IOException, InterruptedException { Process compile = new ProcessBuilder("gcc", "-o", name, "-g", name + ".c").directory(wd).start(); assertEquals(0, compile.waitFor()); @@ -52,9 +73,20 @@ protected final static void compileCPP(String name, File wd) throws IOException, } protected final void startDebugging(String name, File wd) throws IOException { + ExplicitProcessParameters processParameters = ExplicitProcessParameters.builder().workingDirectory(wd).build(); + startDebugging(name, wd, processParameters); + } + + protected final void startDebugging(String name, File wd, ExplicitProcessParameters processParameters) throws IOException { + startDebugging(Arrays.asList(new File(wd, name).getAbsolutePath()), processParameters); + } + + protected final void startDebugging(List executable, ExplicitProcessParameters processParameters) throws IOException { this.process = CPPLiteDebugger.startDebugging( - new CPPLiteDebuggerConfig(Arrays.asList(new File(wd, name).getAbsolutePath()), wd, null, "gdb"), + new CPPLiteDebuggerConfig(executable, processParameters, null, "gdb"), engine -> this.engine = engine); + stdOut = outputFrom(process.getInputStream()); + stdErr = outputFrom(process.getErrorStream()); debugger = engine.lookupFirst(null, CPPLiteDebugger.class); debugger.addStateListener(new CPPLiteDebugger.StateListener() { @Override @@ -120,4 +152,23 @@ protected final void assertStoppedAt(URI file, int line) { public static Test suite() { return NbModuleSuite.emptyConfiguration().gui(false).suite(); } + + private static StringBuffer outputFrom(InputStream inputStream) { + StringBuffer buffer = new StringBuffer(); + new Thread("Process output") { + @Override + public void run() { + try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = br.readLine()) != null) { + buffer.append(line); + buffer.append('\n'); + } + } catch (IOException ex) { + buffer.append(ex); + } + } + }.start(); + return buffer; + } } diff --git a/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/ProcessParametersTest.java b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/ProcessParametersTest.java new file mode 100644 index 000000000000..fd5eb185cc95 --- /dev/null +++ b/cpplite/cpplite.debugger/test/unit/src/org/netbeans/modules/cpplite/debugger/ProcessParametersTest.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.cpplite.debugger; + +import java.io.File; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import junit.framework.Test; +import org.netbeans.api.extexecution.base.ExplicitProcessParameters; +import org.netbeans.junit.NbModuleSuite; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; + +/** + * Tests application parameters set through {@link ExplicitProcessParameters}. + * + * @author Martin Entlicher + */ +public class ProcessParametersTest extends AbstractDebugTest { + + private static final String CPP_APP = + "#include \n" + + "#include \n" + + "#include \n" + + "\n" + + "int main(int argc, char **argv, char **envp) {\n" + + " char buffer[PATH_MAX];\n" + + " if (getcwd(buffer, sizeof(buffer)) != NULL) {\n" + + " printf(\"CWD:%s\\n\", buffer);\n" + + " } else {\n" + + " perror(\"getcwd() error\");\n" + + " return 1;\n" + + " }\n" + + " while (--argc) {\n" + + " printf(\"ARG:%s\\n\", *(++argv));\n" + + " }\n" + + " for (char **e = envp; *e; e++) {\n" + + " printf(\"ENV:%s\\n\", *e);\n" + + " }\n" + + " return 0;\n" + + "}"; + + public ProcessParametersTest(String s) { + super(s); + } + + @Override + protected void setUp() throws Exception { + clearWorkDir(); + } + + public void testWorkingDir() throws Exception { + File wd = getWorkDir(); + createSourceFile("main.cpp", wd, CPP_APP); + compileCPP("main", wd); + File appWD = Files.createTempDirectory("TestCWD").toFile(); + try { + ExplicitProcessParameters params = ExplicitProcessParameters.builder(). + workingDirectory(appWD). + build(); + startDebugging("main", wd, params); + assertEquals(0, waitAppProcessExit()); + String output = stdOut.toString(); + assertEquals("CWD:" + appWD.getAbsolutePath(), output.substring(0, output.indexOf('\n'))); + } finally { + appWD.delete(); + } + } + + public void testArgumentsOwn() throws Exception { + File wd = getWorkDir(); + createSourceFile("main.cpp", wd, CPP_APP); + compileCPP("main", wd); + ExplicitProcessParameters params = ExplicitProcessParameters.empty(); + startDebugging(Arrays.asList(new File(wd, "main").getAbsolutePath(), "Param1", "Param 2"), params); + assertEquals(0, waitAppProcessExit()); + assertArguments("Param1", "Param 2"); + } + + public void testArgumentsExplicit() throws Exception { + File wd = getWorkDir(); + createSourceFile("main.cpp", wd, CPP_APP); + compileCPP("main", wd); + ExplicitProcessParameters params = ExplicitProcessParameters.builder(). + arg("ExpParam1"). + arg("ExpParam 2"). + build(); + startDebugging(Arrays.asList(new File(wd, "main").getAbsolutePath()), params); + assertEquals(0, waitAppProcessExit()); + assertArguments("ExpParam1", "ExpParam 2"); + } + + public void testArgumentsCombinedReplace() throws Exception { + File wd = getWorkDir(); + createSourceFile("main.cpp", wd, CPP_APP); + compileCPP("main", wd); + ExplicitProcessParameters params = ExplicitProcessParameters.builder(). + arg("ExpParam1"). + arg("ExpParam 2"). + build(); + startDebugging(Arrays.asList(new File(wd, "main").getAbsolutePath(), "Param1", "Param 2"), params); + assertEquals(0, waitAppProcessExit()); + assertArguments("ExpParam1", "ExpParam 2"); + } + + public void testArgumentsCombinedAppend() throws Exception { + File wd = getWorkDir(); + createSourceFile("main.cpp", wd, CPP_APP); + compileCPP("main", wd); + ExplicitProcessParameters params = ExplicitProcessParameters.builder(). + arg("ExpParam1"). + arg("ExpParam 2"). + replaceArgs(false). + build(); + startDebugging(Arrays.asList(new File(wd, "main").getAbsolutePath(), "Param1", "Param 2"), params); + assertEquals(0, waitAppProcessExit()); + assertArguments("Param1", "Param 2", "ExpParam1", "ExpParam 2"); + } + + public void testEnvironmentVariables() throws Exception { + File wd = getWorkDir(); + createSourceFile("main.cpp", wd, CPP_APP); + compileCPP("main", wd); + ExplicitProcessParameters params = ExplicitProcessParameters.builder(). + environmentVariable("TEST_VAR1", "test variable value 1"). + environmentVariable("TEST_VAR2", "test variable value 2"). + environmentVariable("USER", null). + build(); + startDebugging("main", wd, params); + assertEquals(0, waitAppProcessExit()); + String output = stdOut.toString(); + Map env = new HashMap<>(); + String[] lines = output.split("\n"); + for (String line : lines) { + if (line.startsWith("ENV:")) { + line = line.substring("ENV:".length()); + String[] var = line.split("="); + env.put(var[0], var[1]); + } + } + assertEquals("test variable value 1", env.get("TEST_VAR1")); + assertEquals("test variable value 2", env.get("TEST_VAR2")); + assertFalse("Removed variable is present", env.containsKey("USER")); + } + + public static Test suite() { + return NbModuleSuite.emptyConfiguration() + .addTest(ProcessParametersTest.class) + .enableModules(".*", ".*") + .gui(false) + .suite(); + } + + private void assertArguments(String... args) { + String output = stdOut.toString(); + int index1 = 0; + int index2 = 0; + for (int i = 0; i < args.length; i++) { + index1 = output.indexOf("ARG:", index2); + if (index1 < 0) { + fail("Missing argument " + i + " : " + args[i]); + } + index2 = output.indexOf('\n', index1); + assertEquals("ARG:" + args[i] , output.substring(index1, index2)); + } + index1 = output.indexOf("ARG:", index2); + if (index1 >= 0) { + fail("An extra argument: " + output.substring(index1)); + } + } +} diff --git a/extide/gradle/apichanges.xml b/extide/gradle/apichanges.xml index b21982e7b3ed..a9592af2865f 100644 --- a/extide/gradle/apichanges.xml +++ b/extide/gradle/apichanges.xml @@ -83,6 +83,23 @@ is the proper place. + + + NetBeans Tooling plugin recognizes "runWorkingDir" and "runEnvironment" properties. + + + + + + Introduced runWorkingDir and runEnvironment project properties that can be passed + to NetBeans Tooling plugin in order to inject user parameters into gradle JavaExec task configurations. + The property runWorkingDir shall contain an absolute path of the working directory. + The property runEnvironment shall contain possibly quoted space-separated expressions of + VAR_NAME=VAR_VALUE, or !VAR_NAME. The VAR_NAME=VAR_VALUE sets + environment variable VAR_NAME to value VAR_VALUE and !VAR_NAME + removes variable VAR_NAME from the set of environment variables. + + diff --git a/extide/gradle/arch.xml b/extide/gradle/arch.xml index 1c686f1c46ca..78d3e8f8d325 100644 --- a/extide/gradle/arch.xml +++ b/extide/gradle/arch.xml @@ -156,6 +156,20 @@ If passing more arguments, the whole -P must be properly quoted. Arguments that contain spaces must also use quoting or escapes. + + NetBeans Tooling plugin recognizes runWorkingDir project property, and injects + specified working directory into JavaExec executing the user code. Use -PrunWorkingDir=.... + If the working directory contain spaces, it must also use quoting or escapes. + + + NetBeans Tooling plugin recognizes runEnvironment project property, and injects + specified environmental variables into JavaExec executing the user code. Use -PrunEnvironment=.... + The value consists of expressions of VAR_NAME=VAR_VALUE, or !VAR_NAME. + The VAR_NAME=VAR_VALUE sets environment variable VAR_NAME to value VAR_VALUE + and !VAR_NAME removes variable VAR_NAME from the set of environment variables. + If passing more arguments, the whole -P must be properly quoted. Variables whose values contain spaces must + also use quoting or escapes. + diff --git a/extide/gradle/manifest.mf b/extide/gradle/manifest.mf index c94781e817af..3ea411489141 100644 --- a/extide/gradle/manifest.mf +++ b/extide/gradle/manifest.mf @@ -3,4 +3,4 @@ AutoUpdate-Show-In-Client: false OpenIDE-Module: org.netbeans.modules.gradle/2 OpenIDE-Module-Layer: org/netbeans/modules/gradle/layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/gradle/Bundle.properties -OpenIDE-Module-Specification-Version: 2.20 +OpenIDE-Module-Specification-Version: 2.21 diff --git a/extide/gradle/netbeans-gradle-tooling/src/main/groovy/org/netbeans/modules/gradle/tooling/NetBeansRunSinglePlugin.groovy b/extide/gradle/netbeans-gradle-tooling/src/main/groovy/org/netbeans/modules/gradle/tooling/NetBeansRunSinglePlugin.groovy index 066333475b60..8470da82d5d4 100644 --- a/extide/gradle/netbeans-gradle-tooling/src/main/groovy/org/netbeans/modules/gradle/tooling/NetBeansRunSinglePlugin.groovy +++ b/extide/gradle/netbeans-gradle-tooling/src/main/groovy/org/netbeans/modules/gradle/tooling/NetBeansRunSinglePlugin.groovy @@ -35,6 +35,8 @@ class NetBeansRunSinglePlugin implements Plugin { static String RUN_SINGLE_MAIN = "runClassName" static String RUN_SINGLE_ARGS = "runArgs" static String RUN_SINGLE_JVM_ARGS = "runJvmArgs" + static String RUN_SINGLE_CWD = "runWorkingDir" + static String RUN_SINGLE_ENV = "runEnvironment" void apply(Project project) { project.afterEvaluate { @@ -65,7 +67,107 @@ class NetBeansRunSinglePlugin implements Plugin { if (project.hasProperty(RUN_SINGLE_ARGS)) { args = project.getProperty(RUN_SINGLE_ARGS).tokenize(' ') } + if (project.hasProperty(RUN_SINGLE_CWD)) { + workingDir = project.getProperty(RUN_SINGLE_CWD) + } + if (project.hasProperty(RUN_SINGLE_ENV)) { + // Quoted space-separated expressions of = + // to set environment variables, or ! + // to remove environment variables + unescapeParameters(project.getProperty(RUN_SINGLE_ENV)).each { + env -> + if (env.startsWith("!")) { + environment.remove(env.substring(1)) + } else { + def i = env.indexOf("=") + if (i > 0) { + environment[env.substring(0, i)] = env.substring(i + 1) + } + } + } + } } } - + + private String[] unescapeParameters(String s) { + final int NULL = 0x0; + final int IN_PARAM = 0x1; + final int IN_DOUBLE_QUOTE = 0x2; + final int IN_SINGLE_QUOTE = 0x3; + ArrayList params = new ArrayList<>(5); + char c; + + int state = NULL; + StringBuilder buff = new StringBuilder(20); + int slength = s.length(); + + for (int i = 0; i < slength; i++) { + c = s.charAt(i); + switch (state) { + case NULL: + switch (c) { + case '\'': + state = IN_SINGLE_QUOTE; + break; + case '"': + state = IN_DOUBLE_QUOTE; + break; + default: + if (!Character.isWhitespace(c)) { + buff.append(c); + state = IN_PARAM; + } + } + break; + case IN_SINGLE_QUOTE: + if (c != '\'') { + buff.append(c); + } else { + state = IN_PARAM; + } + break; + case IN_DOUBLE_QUOTE: + switch (c) { + case '\\': + char peek = (i < slength - 1) ? s.charAt(i+1) : Character.MIN_VALUE; + if (peek == '"' || peek =='\\') { + buff.append(peek); + i++; + } else { + buff.append(c); + } + break; + case '"': + state = IN_PARAM; + break; + default: + buff.append(c); + } + break; + case IN_PARAM: + switch (c) { + case '\'': + state = IN_SINGLE_QUOTE; + break; + case '"': + state = IN_DOUBLE_QUOTE; + break; + default: + if (Character.isWhitespace(c)) { + params.add(buff.toString()); + buff.setLength(0); + state = NULL; + } else { + buff.append(c); + } + } + break; + } + } + if (buff.length() > 0) { + params.add(buff.toString()); + } + + return params.toArray(new String[params.size()]) + } } diff --git a/ide/extexecution.base/manifest.mf b/ide/extexecution.base/manifest.mf index 21ce6ef39596..8c1e50ff55a2 100644 --- a/ide/extexecution.base/manifest.mf +++ b/ide/extexecution.base/manifest.mf @@ -2,6 +2,6 @@ Manifest-Version: 1.0 AutoUpdate-Show-In-Client: false OpenIDE-Module: org.netbeans.modules.extexecution.base/2 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/extexecution/base/resources/Bundle.properties -OpenIDE-Module-Specification-Version: 1.19 +OpenIDE-Module-Specification-Version: 1.20 OpenIDE-Module-Recommends: org.netbeans.spi.extexecution.base.ProcessesImplementation diff --git a/ide/extexecution.base/src/org/netbeans/api/extexecution/base/ExplicitProcessParameters.java b/ide/extexecution.base/src/org/netbeans/api/extexecution/base/ExplicitProcessParameters.java index 9aa9d0328f33..91ee25a1d760 100644 --- a/ide/extexecution.base/src/org/netbeans/api/extexecution/base/ExplicitProcessParameters.java +++ b/ide/extexecution.base/src/org/netbeans/api/extexecution/base/ExplicitProcessParameters.java @@ -18,11 +18,15 @@ */ package org.netbeans.api.extexecution.base; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.annotations.common.NullAllowed; import org.openide.util.Lookup; @@ -45,8 +49,7 @@ * process arguments. *
* For java applications when {@code java} executable is used to launch the application, or even Maven project (see below), the launcherArgs should correspond to VM - * arguments, and args correspond to the main class' arguments (passed to the main class). Environment variables for the new process are not - * supported at the moment. + * arguments, and args correspond to the main class' arguments (passed to the main class). Additional environment variables can be specified. *
*

* If the object is marked as {@link #isArgReplacement()}, the launcher implementor SHOULD replace all @@ -92,17 +95,22 @@ public final class ExplicitProcessParameters { private final List arguments; private final boolean replaceArgs; private final boolean replaceLauncherArgs; + private final File workingDirectory; + private final Map environmentVars; private ExplicitProcessParameters(int position, List launcherArguments, - List arguments, boolean appendArgs, boolean appendLauncherArgs) { + List arguments, boolean appendArgs, boolean appendLauncherArgs, + File workingDirectory, Map environmentVars) { this.position = position; this.launcherArguments = launcherArguments == null ? null : Collections.unmodifiableList(launcherArguments); this.arguments = arguments == null ? null : Collections.unmodifiableList(arguments); this.replaceArgs = appendArgs; this.replaceLauncherArgs = appendLauncherArgs; + this.workingDirectory = workingDirectory; + this.environmentVars = environmentVars == null ? null : Collections.unmodifiableMap(environmentVars); } - private static final ExplicitProcessParameters EMPTY = new ExplicitProcessParameters(0, null, null, false, false); + private static final ExplicitProcessParameters EMPTY = new ExplicitProcessParameters(0, null, null, false, false, null, null); /** * Returns an empty instance of parameters that has no effect. DO NOT check for emptiness by @@ -122,7 +130,10 @@ public boolean isEmpty() { if (isArgReplacement() || isLauncherArgReplacement()) { return false; } - return (((arguments == null) || arguments.isEmpty()) && (launcherArguments == null || launcherArguments.isEmpty())); + return ((arguments == null) || arguments.isEmpty()) && + (launcherArguments == null || launcherArguments.isEmpty()) && + workingDirectory == null && + (environmentVars == null || environmentVars.isEmpty()); } /** @@ -189,6 +200,27 @@ public boolean isLauncherArgReplacement() { return getAllArguments(middle == null ? Collections.emptyList() : Arrays.asList(middle)); } + /** + * Returns working directory to be set for the process. + * + * @return working directory, or nul + * @since 1.20 + */ + public @CheckForNull File getWorkingDirectory() { + return workingDirectory; + } + + /** + * Returns a map of additional environment variables to be set for the process. + * Always non-null. Values of existing environment variables are overridden. + * + * @return map of additional environment variables + * @since 1.20 + */ + public @NonNull Map getEnvironmentVariables() { + return environmentVars != null ? environmentVars : Collections.emptyMap(); + } + /** * Merges ExplicitProcessParameters instructions found in the Lookup. See {@link #buildExplicitParameters(java.util.Collection)} * for more details. @@ -238,7 +270,7 @@ public static Builder builder() { *

  • appends launcher arguments *
  • replaces (normal) arguments * - * and the mode can be overriden for each group. + * and the mode can be overridden for each group. */ public final static class Builder { private int position = 0; @@ -246,6 +278,8 @@ public final static class Builder { private List arguments = null; private Boolean replaceArgs; private Boolean replaceLauncherArgs; + private File workingDirectory = null; + private Map environmentVars; private void initArgs() { if (arguments == null) { @@ -370,6 +404,53 @@ public Builder replaceLauncherArgs(boolean replace) { return this; } + /** + * Sets working directory to be used for the process. + * + * @param workingDirectory the working directory + * @return the builder + * @since 1.20 + */ + public Builder workingDirectory(File workingDirectory) { + this.workingDirectory = workingDirectory; + return this; + } + + /** + * Provide additional environment variables for the process. Values of + * existing environment variables are overridden. + * + * @param env a map of additional environment variables + * @return the builder + * @since 1.20 + */ + public Builder environmentVariables(Map env) { + if (!env.isEmpty()) { + if (this.environmentVars == null) { + this.environmentVars = new HashMap<>(); + } + this.environmentVars.putAll(env); + } + return this; + } + + /** + * Provide an additional environment variable for the process. If the variable + * already exists, it's overridden with the new value. + * + * @param name name of the environment variable + * @param value value of the environment variable + * @return the builder + * @since 1.20 + */ + public Builder environmentVariable(String name, String value) { + if (this.environmentVars == null) { + this.environmentVars = new HashMap<>(); + } + this.environmentVars.put(name, value); + return this; + } + /** * Defines a position for combining. The default rank is {@code 0}. When used in a collection in * {@link ExplicitProcessParameters#buildExplicitParameters(java.util.Collection)}, instances are sorted @@ -416,6 +497,12 @@ public Builder combine(@NullAllowed ExplicitProcessParameters p) { if (p.getArguments() != null) { args(p.getArguments()); } + if (p.getWorkingDirectory() != null) { + workingDirectory(p.getWorkingDirectory()); + } + if (!p.getEnvironmentVariables().isEmpty()) { + environmentVariables(p.getEnvironmentVariables()); + } return this; } @@ -430,7 +517,7 @@ public ExplicitProcessParameters build() { return new ExplicitProcessParameters(position, launcherArguments, arguments, // if no args / launcher args given and no explicit instruction on append, // make the args appending. - aa, apa); + aa, apa, workingDirectory, environmentVars); } } } diff --git a/ide/nativeimage.api/manifest.mf b/ide/nativeimage.api/manifest.mf index 8989b99cbc4f..4fee1da371d8 100644 --- a/ide/nativeimage.api/manifest.mf +++ b/ide/nativeimage.api/manifest.mf @@ -2,5 +2,5 @@ Manifest-Version: 1.0 AutoUpdate-Show-In-Client: false OpenIDE-Module: org.netbeans.modules.nativeimage.api/0 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/nativeimage/api/Bundle.properties -OpenIDE-Module-Specification-Version: 0.4 +OpenIDE-Module-Specification-Version: 0.5 diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/EvaluateException.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/EvaluateException.java index 1d542bb1752a..88a2cd8f6dc1 100644 --- a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/EvaluateException.java +++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/EvaluateException.java @@ -21,7 +21,7 @@ /** * Thrown when evaluation in the native debugger fails. * - * @since 1.0 + * @since 0.1 */ public class EvaluateException extends Exception { @@ -30,7 +30,7 @@ public class EvaluateException extends Exception { * specified detail message. * * @param msg the detail message. - * @since 1.0 + * @since 0.1 */ public EvaluateException(String msg) { super(msg); @@ -41,10 +41,9 @@ public EvaluateException(String msg) { * existing exception. * * @param t exception. - * @since 1.0 + * @since 0.1 */ public EvaluateException(Throwable t) { super(t.getLocalizedMessage(), t); - } } diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java index bdd0245845f5..a66b353d4d47 100644 --- a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java +++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIDebugger.java @@ -21,6 +21,7 @@ import java.io.File; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; @@ -42,7 +43,7 @@ /** * Representation of a native image debugger. - * @since 1.0 + * @since 0.1 */ public final class NIDebugger { @@ -59,7 +60,7 @@ public final class NIDebugger { * @throws IllegalStateException when the native debugger is not available * (there is not an implementation of {@link NIDebuggerServiceProvider} * registered in the default lookup). - * @since 1.0 + * @since 0.1 */ @NbBundle.Messages({"MSG_NoNativeDebug=No native debugger is available. Please install native debugger module."}) public static Builder newBuilder() throws IllegalStateException { @@ -79,7 +80,7 @@ public static Builder newBuilder() throws IllegalStateException { * @param id a unique ID of the breakpoint * @param breakpointDescriptor the breakpoint descriptor * @return an instance of the native breakpoint - * @since 1.0 + * @since 0.1 */ public Breakpoint addLineBreakpoint(Object id, NILineBreakpointDescriptor breakpointDescriptor) { Breakpoint breakpoint = provider.addLineBreakpoint(id, breakpointDescriptor); @@ -91,7 +92,7 @@ public Breakpoint addLineBreakpoint(Object id, NILineBreakpointDescriptor breakp * Remove breakpoint with the given id. * * @param id the ID of the breakpoint to remove - * @since 1.0 + * @since 0.1 */ public void removeBreakpoint(Object id) { provider.removeBreakpoint(id); @@ -107,12 +108,41 @@ public void removeBreakpoint(Object id) { * @param executionDescriptor execution descriptor that describes the runtime attributes * @param startedEngine the corresponding DebuggerEngine is passed to this consumer * @return future that completes on the execution finish - * @since 1.0 + * @since 0.1 + * @deprecated Use {@link #start(org.netbeans.modules.nativeimage.api.debug.StartDebugParameters, java.util.function.Consumer)} */ + @Deprecated public CompletableFuture start(List command, File workingDirectory, String debugger, String displayName, ExecutionDescriptor executionDescriptor, Consumer startedEngine) { return provider.start(command, workingDirectory, debugger, displayName, executionDescriptor, startedEngine); } + /** + * Start the actual debugging session. Call this typically after breakpoints are added. + * + * @param debugParameters parameters to start the debugging with + * @param startedEngine the corresponding DebuggerEngine is passed to this consumer + * @return a future which is completed when the started debugger session finishes + * @since 0.5 + */ + public CompletableFuture start(StartDebugParameters debugParameters, Consumer startedEngine) { + Objects.requireNonNull(debugParameters); + CompletableFuture start = provider.start(debugParameters, startedEngine); + if (start.isCompletedExceptionally()) { + try { + start.get(); + } catch (ExecutionException ex) { + if (ex.getCause() instanceof UnsupportedOperationException) { + return startLegacy(debugParameters, startedEngine); + } + } catch (Throwable ex) {} + } + return start; + } + + private CompletableFuture startLegacy(StartDebugParameters debugParameters, Consumer startedEngine) { + return provider.start(debugParameters.getCommand(), debugParameters.getWorkingDirectory(), debugParameters.getDebugger(), debugParameters.getDisplayName(), debugParameters.getExecutionDescriptor(), startedEngine); + } + /** * Attach to a process and create a debugging session. Call this typically after breakpoints are added. * @@ -135,7 +165,7 @@ public CompletableFuture attach(String executablePath, long processId, Str * when null the expression is used as the name * @param frame the frame to evaluate at * @return the completable future with the evaluation result - * @since 1.0 + * @since 0.1 */ public CompletableFuture evaluateAsync(String expression, String resultName, NIFrame frame) { return provider.evaluateAsync(expression, resultName, frame); @@ -151,7 +181,7 @@ public CompletableFuture evaluateAsync(String expression, String res * @param frame the frame to evaluate at * @return the evaluation result * @throws EvaluateException when evaluation fails - * @since 1.0 + * @since 0.1 */ public NIVariable evaluate(String expression, String resultName, NIFrame frame) throws EvaluateException { try { @@ -169,7 +199,7 @@ public NIVariable evaluate(String expression, String resultName, NIFrame frame) * @param length number of bytes to read * @return hexadecimal representation of the memory content, or null * when the read is not successful - * @since 1.0 + * @since 0.1 */ public String readMemory(String address, long offset, int length) { return provider.readMemory(address, offset, length); @@ -178,7 +208,7 @@ public String readMemory(String address, long offset, int length) { /** * Get version of the underlying native debugger. * - * @since 1.0 + * @since 0.1 */ public String getVersion() { return provider.getVersion(); @@ -230,7 +260,7 @@ public Map> listVariables(String name, boolean includeN /** * A builder that creates a Native Image debugger with optional displayers. * - * @since 1.0 + * @since 0.1 */ public static final class Builder { @@ -244,7 +274,7 @@ public static final class Builder { * Displayer of native frames. * * @param frameDisplayer translator of the native frame to it's displayed information - * @since 1.0 + * @since 0.1 */ public Builder frameDisplayer(FrameDisplayer frameDisplayer) { this.debuggerProvider.setFrameDisplayer(frameDisplayer); @@ -255,7 +285,7 @@ public Builder frameDisplayer(FrameDisplayer frameDisplayer) { * Displayer of native variables. * * @param variablesDisplayer translator of native variables to displayed variables. - * @since 1.0 + * @since 0.1 */ public Builder variablesDisplayer(VariableDisplayer variablesDisplayer) { this.debuggerProvider.setVariablesDisplayer(variablesDisplayer); @@ -264,7 +294,7 @@ public Builder variablesDisplayer(VariableDisplayer variablesDisplayer) { /** * Create the debugger instance. - * @since 1.0 + * @since 0.1 */ public NIDebugger build() { return new NIDebugger(debuggerProvider); diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIFrame.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIFrame.java index 4ffc48c1259e..9cc338cdfef3 100644 --- a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIFrame.java +++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIFrame.java @@ -21,49 +21,49 @@ /** * Representation of a native stack frame. * - * @since 1.0 + * @since 0.1 */ public interface NIFrame { /** * Frame's thread ID. - * @since 1.0 + * @since 0.1 */ String getThreadId(); /** * Frame's depth level. The top frame has level 0. - * @since 1.0 + * @since 0.1 */ int getLevel(); /** * Frame's native address. - * @since 1.0 + * @since 0.1 */ String getAddress(); /** * A short name of the file associated with the frame. - * @since 1.0 + * @since 0.1 */ String getShortFileName(); /** * A full name of the file associated with the frame. - * @since 1.0 + * @since 0.1 */ String getFullFileName(); /** * Name of the function associated with the frame. - * @since 1.0 + * @since 0.1 */ String getFunctionName(); /** * 1-based line of the frame location. - * @since 1.0 + * @since 0.1 */ int getLine(); } diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NILineBreakpointDescriptor.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NILineBreakpointDescriptor.java index 192be780bb27..2033f392f9a0 100644 --- a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NILineBreakpointDescriptor.java +++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NILineBreakpointDescriptor.java @@ -23,7 +23,7 @@ /** * Description of a line native breakpoint. * - * @since 1.0 + * @since 0.1 */ public final class NILineBreakpointDescriptor { @@ -46,7 +46,7 @@ private NILineBreakpointDescriptor(String filePath, int line, boolean enabled, S * * @param filePath file path of the breakpoint * @param lineNumber 1-based line number - * @since 1.0 + * @since 0.1 */ public static Builder newBuilder(String filePath, int lineNumber) { return new Builder(filePath, lineNumber); @@ -55,7 +55,7 @@ public static Builder newBuilder(String filePath, int lineNumber) { /** * Get path of the file. * - * @since 1.0 + * @since 0.1 */ public String getFilePath() { return filePath; @@ -64,7 +64,7 @@ public String getFilePath() { /** * Get 1-based line number. * - * @since 1.0 + * @since 0.1 */ public int getLine() { return line; @@ -73,7 +73,7 @@ public int getLine() { /** * Check if the breakpoint is to be enabled. * - * @since 1.0 + * @since 0.1 */ public boolean isEnabled() { return enabled; @@ -83,7 +83,7 @@ public boolean isEnabled() { * Get the breakpoint condition. * * @return the condition, or null when the breakpoint does not have any condition. - * @since 1.0 + * @since 0.1 */ @CheckForNull public String getCondition() { @@ -93,7 +93,7 @@ public String getCondition() { /** * Check if the breakpoint is to be hidden (not user-visible). * - * @since 1.0 + * @since 0.1 */ public boolean isHidden() { return hidden; @@ -103,7 +103,7 @@ public boolean isHidden() { * Builder of a line native breakpoint descriptor. The builder is reusable * and the built breakpoint descriptor can be used to update existing breakpoints. * - * @since 1.0 + * @since 0.1 */ public static final class Builder { @@ -121,7 +121,7 @@ public static final class Builder { /** * Set a file path. * - * @since 1.0 + * @since 0.1 */ public Builder filePath(String filePath) { this.filePath = filePath; @@ -131,7 +131,7 @@ public Builder filePath(String filePath) { /** * Set a 1-based line number. * - * @since 1.0 + * @since 0.1 */ public Builder line(int lineNumber) { this.line = lineNumber; @@ -141,7 +141,7 @@ public Builder line(int lineNumber) { /** * Set a condition. * - * @since 1.0 + * @since 0.1 */ public Builder condition(String condition) { this.condition = condition; @@ -151,7 +151,7 @@ public Builder condition(String condition) { /** * Set an enabled state of the breakpoint. The breakpoint is enabled by default. * - * @since 1.0 + * @since 0.1 */ public Builder enabled(boolean enabled) { this.enabled = enabled; @@ -162,7 +162,7 @@ public Builder enabled(boolean enabled) { * Set a hidden state of the breakpoint. Hidden breakpoints are not visible * to users. The breakpoint is not hidden by default. * - * @since 1.0 + * @since 0.1 */ public Builder hidden(boolean hidden) { this.hidden = hidden; diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIVariable.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIVariable.java index cd46e7c892a5..d2001929f303 100644 --- a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIVariable.java +++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/NIVariable.java @@ -20,28 +20,28 @@ /** * Representation of a native variable. - * @since 1.0 + * @since 0.1 */ public interface NIVariable { /** * Name of the variable. * - * @since 1.0 + * @since 0.1 */ String getName(); /** * Type of the variable value. * - * @since 1.0 + * @since 0.1 */ String getType(); /** * String representation of the variable value. * - * @since 1.0 + * @since 0.1 */ String getValue(); @@ -50,14 +50,14 @@ public interface NIVariable { * variable. * * @return the parent variable, or null. - * @since 1.0 + * @since 0.1 */ NIVariable getParent(); /** * Number of child variables (properties, or array elements) of this variable. * - * @since 1.0 + * @since 0.1 */ int getNumChildren(); @@ -66,14 +66,14 @@ public interface NIVariable { * from index and up to and excluding to index will * be returned. If from is less than zero, all children are returned. * - * @since 1.0 + * @since 0.1 */ NIVariable[] getChildren(int from, int to); /** * Get all variable's children. * - * @since 1.0 + * @since 0.1 */ default NIVariable[] getChildren() { return getChildren(0, Integer.MAX_VALUE); @@ -82,7 +82,7 @@ default NIVariable[] getChildren() { /** * Get the full expression that this variable object represents. * - * @since 1.0 + * @since 0.1 */ String getExpressionPath(); @@ -90,7 +90,7 @@ default NIVariable[] getChildren() { * Get the frame this variable is associated with. * * @return the frame, or null. - * @since 1.0 + * @since 0.1 */ NIFrame getFrame(); } diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/StartDebugParameters.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/StartDebugParameters.java new file mode 100644 index 000000000000..2716714f6ef9 --- /dev/null +++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/api/debug/StartDebugParameters.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.nativeimage.api.debug; + +import java.io.File; +import java.util.List; +import org.netbeans.api.extexecution.ExecutionDescriptor; +import org.openide.util.Lookup; + +/** + * Parameters passed to {@link NIDebugger#start(org.netbeans.modules.nativeimage.api.debug.StartDebugParameters, java.util.function.Consumer)}. + * + * @since 0.5 + */ +public final class StartDebugParameters { + + private final List command; + private final File workingDirectory; + private final String debugger; + private final String displayName; + private final ExecutionDescriptor executionDescriptor; + private final Lookup contextLookup; + + private StartDebugParameters(List command, File workingDirectory, String debugger, String displayName, ExecutionDescriptor executionDescriptor, Lookup contextLookup) { + this.command = command; + this.workingDirectory = workingDirectory; + this.debugger = debugger; + this.displayName = displayName; + this.executionDescriptor = executionDescriptor; + this.contextLookup = contextLookup; + } + + /** + * The command to run the native image. + * + * @return the command with arguments. + */ + public List getCommand() { + return command; + } + + /** + * Working directory of the process. + * + * @return the working directory + */ + public File getWorkingDirectory() { + return workingDirectory; + } + + /** + * The native debugger command. + * + * @return the debugger command + */ + public String getDebugger() { + return debugger; + } + + /** + * Display name of the debugger task. + * + * @return the display name of debugger task + */ + public String getDisplayName() { + return displayName; + } + + /** + * Execution descriptor that describes the runtime attributes. + * + * @return the execution descriptor + */ + public ExecutionDescriptor getExecutionDescriptor() { + return executionDescriptor; + } + + /** + * Context lookup. The lookup may contain other parameters, like ExplicitProcessParameters. + * + * @return the context lookup + */ + public Lookup getContextLookup() { + return contextLookup; + } + + /** + * Create a new debug parameters builder. + * + * @param command the command to run the native image. + * @return a new builder + */ + public static Builder newBuilder(List command) { + return new Builder(command); + } + + /** + * Builder of start debug parameters. + * + * @since 0.5 + */ + public static final class Builder { + + private final List command; + private File workingDirectory; + private String debugger; + private String displayName; + private ExecutionDescriptor executionDescriptor; + private Lookup contextLookup; + + Builder(List command) { + this.command = command; + } + + /** + * Set the working directory. + * @return the builder + */ + public Builder workingDirectory(File workingDirectory) { + this.workingDirectory = workingDirectory; + return this; + } + + /** + * Set the native debugger command. + * @return the builder + */ + public Builder debugger(String debugger) { + this.debugger = debugger; + return this; + } + + /** + * Set display name of the debugger task. + * + * @return the builder + */ + public Builder displayName(String displayName) { + this.displayName = displayName; + return this; + } + + /** + * Set execution descriptor that describes the runtime attributes. + * + * @return the builder + */ + public Builder executionDescriptor(ExecutionDescriptor executionDescriptor) { + this.executionDescriptor = executionDescriptor; + return this; + } + + /** + * Context lookup. The lookup may contain other parameters, like ExplicitProcessParameters. + * + * @return the builder + */ + public Builder lookup(Lookup contextLookup) { + this.contextLookup = contextLookup; + return this; + } + + /** + * Build the {@link StartDebugParameters} based on the properties set. + * + * @return a new instance of {@link StartDebugParameters}. + */ + public StartDebugParameters build() { + return new StartDebugParameters(command, workingDirectory, debugger, displayName, executionDescriptor, contextLookup); + } + } +} diff --git a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java index 10a5b8062b61..893f8399203d 100644 --- a/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java +++ b/ide/nativeimage.api/src/org/netbeans/modules/nativeimage/spi/debug/NIDebuggerProvider.java @@ -33,6 +33,7 @@ import org.netbeans.modules.nativeimage.api.debug.NIFrame; import org.netbeans.modules.nativeimage.api.debug.NILineBreakpointDescriptor; import org.netbeans.modules.nativeimage.api.debug.NIVariable; +import org.netbeans.modules.nativeimage.api.debug.StartDebugParameters; import org.netbeans.modules.nativeimage.spi.debug.filters.FrameDisplayer; import org.netbeans.modules.nativeimage.spi.debug.filters.VariableDisplayer; @@ -40,7 +41,7 @@ * Provider of the native image debugger. * * @author martin - * @since 1.0 + * @since 0.1 */ public interface NIDebuggerProvider { @@ -52,7 +53,7 @@ public interface NIDebuggerProvider { * @param id a unique ID of the breakpoint * @param breakpointDescriptor the breakpoint descriptor * @return an instance of the native breakpoint - * @since 1.0 + * @since 0.1 */ Breakpoint addLineBreakpoint(Object id, NILineBreakpointDescriptor breakpointDescriptor); @@ -60,7 +61,7 @@ public interface NIDebuggerProvider { * Remove breakpoint with the given id. * * @param id the ID of the breakpoint to remove - * @since 1.0 + * @since 0.1 */ void removeBreakpoint(Object id); @@ -68,7 +69,7 @@ public interface NIDebuggerProvider { * Set a displayer of native frames. * * @param frameDisplayer translator of the native frame to it's displayed information - * @since 1.0 + * @since 0.1 */ void setFrameDisplayer(FrameDisplayer frameDisplayer); @@ -76,7 +77,7 @@ public interface NIDebuggerProvider { * Set a displayer of native variables. * * @param variablesDisplayer translator of native variables to displayed variables. - * @since 1.0 + * @since 0.1 */ void setVariablesDisplayer(VariableDisplayer variablesDisplayer); @@ -89,10 +90,33 @@ public interface NIDebuggerProvider { * @param displayName display name of the debugger task * @param executionDescriptor execution descriptor that describes the runtime attributes * @param startedEngine the corresponding DebuggerEngine is passed to this consumer - * @param finishedCallback notification of the execution finish - * @since 1.0 + * @since 0.1 + * @deprecated Use {@link #start(org.netbeans.modules.nativeimage.api.debug.StartDebugParameters, java.util.function.Consumer)} */ - CompletableFuture start(List command, File workingDirectory, String debugger, String displayName, ExecutionDescriptor executionDescriptor, Consumer startedEngine); + @Deprecated + default CompletableFuture start(List command, File workingDirectory, String debugger, String displayName, ExecutionDescriptor executionDescriptor, Consumer startedEngine) { + StartDebugParameters parameters = StartDebugParameters.newBuilder(command) + .workingDirectory(workingDirectory) + .debugger(debugger) + .displayName(displayName) + .executionDescriptor(executionDescriptor) + .build(); + return start(parameters, startedEngine); + } + + /** + * Start the actual debugging session. Called typically after breakpoints are added. + * + * @param debugParameters parameters to start the debugging with + * @param startedEngine the corresponding DebuggerEngine is passed to this consumer + * @return a future which is completed when the started debugger session finishes + * @since 0.5 + */ + default CompletableFuture start(StartDebugParameters debugParameters, Consumer startedEngine) { + CompletableFuture cf = new CompletableFuture(); + cf.completeExceptionally(new UnsupportedOperationException()); + return cf; + } /** * Attach to a process and create a debugging session. Called typically after breakpoints are added. @@ -118,7 +142,7 @@ default CompletableFuture attach(String executablePath, long processId, St * when null the expression is used as the name * @param frame the frame to evaluate at * @return the completable future with the evaluation result - * @since 1.0 + * @since 0.1 */ CompletableFuture evaluateAsync(String expression, String resultName, NIFrame frame); @@ -130,14 +154,14 @@ default CompletableFuture attach(String executablePath, long processId, St * @param length number of bytes to read * @return hexadecimal representation of the memory content, or null * when the read is not successful - * @since 1.0 + * @since 0.1 */ String readMemory(String address, long offset, int length); /** * Get version of the underlying native debugger. * - * @since 1.0 + * @since 0.1 */ String getVersion(); @@ -153,10 +177,20 @@ default List listLocations(String filePath) { return null; } + /** + * Provide a list of functions in the debuggee. + * + * @since 0.2 + */ default Map> listFunctions(String name, boolean includeNondebug, int maxResults) { return null; } + /** + * Provide a list of global variables in the debuggee. + * + * @since 0.2 + */ default Map> listVariables(String name, boolean includeNondebug, int maxResults) { return null; } diff --git a/java/gradle.java/arch.xml b/java/gradle.java/arch.xml index f9d2db2d2ddb..7c06a5f86046 100644 --- a/java/gradle.java/arch.xml +++ b/java/gradle.java/arch.xml @@ -110,6 +110,18 @@ in action's context Lookup. See ProjectActions.TOKEN_JAVAEXEC_ARGS for more details. + + {@code ${javaExec.runWorkingDir}} token is replaced in action mappings with working directory retrieved + from ExplicitProcessParameters + in action's context Lookup. See ProjectActions.TOKEN_JAVAEXEC_CWD + for more details. + + + {@code ${javaExec.runEnvironment}} token is replaced in action mappings with environment variables retrieved + from ExplicitProcessParameters + in action's context Lookup. See ProjectActions.TOKEN_JAVAEXEC_ENV + for more details. + diff --git a/java/gradle.java/manifest.mf b/java/gradle.java/manifest.mf index 2dee3f2d33bc..67ff8454a292 100644 --- a/java/gradle.java/manifest.mf +++ b/java/gradle.java/manifest.mf @@ -3,4 +3,4 @@ AutoUpdate-Show-In-Client: false OpenIDE-Module: org.netbeans.modules.gradle.java OpenIDE-Module-Layer: org/netbeans/modules/gradle/java/layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/gradle/java/Bundle.properties -OpenIDE-Module-Specification-Version: 1.14 +OpenIDE-Module-Specification-Version: 1.15 diff --git a/java/gradle.java/nbproject/project.xml b/java/gradle.java/nbproject/project.xml index b8ba6b9011a6..30877a756995 100644 --- a/java/gradle.java/nbproject/project.xml +++ b/java/gradle.java/nbproject/project.xml @@ -31,7 +31,7 @@ 2 - 2.14 + 2.21 @@ -110,7 +110,7 @@ 2 - 1.18 + 1.20 diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/action-mapping.xml b/java/gradle.java/src/org/netbeans/modules/gradle/java/action-mapping.xml index 77de6e464888..6a3b21061701 100644 --- a/java/gradle.java/src/org/netbeans/modules/gradle/java/action-mapping.xml +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/action-mapping.xml @@ -50,21 +50,21 @@ - -PrunClassName=${selectedClass} runSingle ${javaExec.jvmArgs} ${javaExec.args} + -PrunClassName=${selectedClass} ${javaExec.workingDir} ${javaExec.environment} runSingle --stacktrace ${javaExec.jvmArgs} ${javaExec.args} - -PrunClassName=${selectedClass} runSingle --debug-jvm ${javaExec.jvmArgs} ${javaExec.args} + -PrunClassName=${selectedClass} ${javaExec.workingDir} ${javaExec.environment} runSingle --stacktrace --debug-jvm ${javaExec.jvmArgs} ${javaExec.args} - run ${javaExec.jvmArgs} ${javaExec.args} + ${javaExec.workingDir} ${javaExec.environment} run ${javaExec.jvmArgs} ${javaExec.args} - run --debug-jvm ${javaExec.jvmArgs} ${javaExec.args} + ${javaExec.workingDir} ${javaExec.environment} run --debug-jvm ${javaExec.jvmArgs} ${javaExec.args} @@ -88,7 +88,7 @@ - -PrunClassName=${selectedClass} runSingle --continuous ${javaExec.jvmArgs} ${javaExec.args} + -PrunClassName=${selectedClass} ${javaExec.workingDir} ${javaExec.environment} runSingle --continuous ${javaExec.jvmArgs} ${javaExec.args} cleanTest test --tests "${selectedClass}" --continuous @@ -111,7 +111,7 @@ - run --continuous ${javaExec.jvmArgs} ${javaExec.args} + ${javaExec.workingDir} ${javaExec.environment} run --continuous ${javaExec.jvmArgs} ${javaExec.args} diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/api/ProjectActions.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/api/ProjectActions.java index a382e74c2cee..b4cafee0d4da 100644 --- a/java/gradle.java/src/org/netbeans/modules/gradle/java/api/ProjectActions.java +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/api/ProjectActions.java @@ -41,15 +41,34 @@ public final class ProjectActions { * {@codesnippet JavaExecTokenProviderTest#testExamplePassJvmAndArguments} * */ - public static String TOKEN_JAVAEXEC_JVMARGS = "javaExec.jvmArgs"; + public static String TOKEN_JAVAEXEC_JVMARGS = "javaExec.jvmArgs"; // NOI18N /** * Replaceable token for program parameters as a commandline option. Generates --args <parameter-list>, if the extra parameters are present, otherwise * generates an empty String. See {@link #TOKEN_JAVAEXEC_JVMARGS} for code examples. * @since 1.9 */ - public static String TOKEN_JAVAEXEC_ARGS = "javaExec.args"; + public static String TOKEN_JAVAEXEC_ARGS = "javaExec.args"; // NOI18N + /** + * Replaceable token for program working directory. Generates project property for NB Tooling Gradle plugin, which is used in action-mapping.xml + * and can be customized by the user. This feature cooperates with NetBeans Tooling Gradle plugin provided by org.netbeans.gradle module. + * The Gradle Java project support consumes {@link ExplicitProcessParameters} from the action's context Lookup, and populates the replaceable token mapping + * from {@link ExplicitProcessParameters#getWorkingDirectory()}. + * + * @since 1.15 + */ + public static String TOKEN_JAVAEXEC_CWD = "javaExec.workingDir"; // NOI18N + + /** + * Replaceable token for program environment variables. Generates project property for NB Tooling Gradle plugin, which is used in action-mapping.xml + * and can be customized by the user. This feature cooperates with NetBeans Tooling Gradle plugin provided by org.netbeans.gradle module. + * The Gradle Java project support consumes {@link ExplicitProcessParameters} from the action's context Lookup, and populates the replaceable token mapping + * from {@link ExplicitProcessParameters#getEnvironmentVariables()}. + * + * @since 1.15 + */ + public static String TOKEN_JAVAEXEC_ENV = "javaExec.environment"; // NOI18N private ProjectActions() {} } diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/execute/JavaExecTokenProvider.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/execute/JavaExecTokenProvider.java index f4770568de67..f0096e1fc105 100644 --- a/java/gradle.java/src/org/netbeans/modules/gradle/java/execute/JavaExecTokenProvider.java +++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/execute/JavaExecTokenProvider.java @@ -63,6 +63,20 @@ public class JavaExecTokenProvider implements ReplaceTokenProvider { */ public static String TOKEN_JAVAEXEC_ARGS = ProjectActions.TOKEN_JAVAEXEC_ARGS; + /** + * Replaceable token for working directory project property. Generates project property for NB Tooling Gradle plugin, if the working directory is present, otherwise + * generates an empty String. + * @see #TOKEN_JAVA_CWD + */ + public static String TOKEN_JAVAEXEC_CWD = ProjectActions.TOKEN_JAVAEXEC_CWD; + + /** + * Replaceable token for environment variables project property. Generates project property for NB Tooling Gradle plugin, if the environment variables are present, + * otherwise generates an empty String. + * @see #TOKEN_JAVA_ENV + */ + public static String TOKEN_JAVAEXEC_ENV = ProjectActions.TOKEN_JAVAEXEC_ENV; + /** * Replaceable token for JVM arguments. Generates escaped / quoted arguments as a single String. */ @@ -73,7 +87,19 @@ public class JavaExecTokenProvider implements ReplaceTokenProvider { * a space-delimited String. */ public static String TOKEN_JAVA_JVMARGS = "java.jvmArgs"; // NOI18N - + + /** + * Replaceable token for application working directory. + */ + public static String TOKEN_JAVA_CWD = "java.workingDir"; // NOI18N + + /** + * Replaceable token for environment variables. Generates escaped / quoted variable assignments as a single String. + * Expression VAR_NAME=VAR_VALUE sets environment variable of name VAR_NAME to value + * VAR_VALUE, expression !VAR_NAME unsets environment variable VAR_NAME. + */ + public static String TOKEN_JAVA_ENV = "java.environment"; // NOI18N + private static final Set TOKENS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( TOKEN_JAVAEXEC_ARGS, TOKEN_JAVAEXEC_JVMARGS, TOKEN_JAVA_ARGS, TOKEN_JAVA_JVMARGS @@ -144,8 +170,12 @@ public Map createReplacements(String action, Lookup context) { Map result = new HashMap<>(); result.put(TOKEN_JAVAEXEC_ARGS, ""); // NOI18N result.put(TOKEN_JAVAEXEC_JVMARGS, ""); // NOI18N + result.put(TOKEN_JAVAEXEC_CWD, ""); // NOI18N + result.put(TOKEN_JAVAEXEC_ENV, ""); // NOI18N result.put(TOKEN_JAVA_ARGS, ""); // NOI18N result.put(TOKEN_JAVA_JVMARGS, ""); // NOI18N + result.put(TOKEN_JAVA_CWD, ""); // NOI18N + result.put(TOKEN_JAVA_ENV, ""); // NOI18N if (extraArgs.isEmpty() && contextParams.isEmpty()) { return result; } @@ -178,6 +208,32 @@ public Map createReplacements(String action, Lookup context) { result.put(TOKEN_JAVA_ARGS, args); result.put(TOKEN_JAVAEXEC_ARGS, "--args " + prop); // NOI18N } + if (changedParams.getWorkingDirectory() != null) { + String wd = Utilities.escapeParameters(new String[] { + changedParams.getWorkingDirectory().getAbsolutePath() + }); + result.put(TOKEN_JAVA_CWD, wd); + String prop = Utilities.escapeParameters(new String[] { + "-PrunWorkingDir=" + wd // NOI18N + }); + result.put(TOKEN_JAVAEXEC_CWD, prop); + } + if (!changedParams.getEnvironmentVariables().isEmpty()) { + List envParams = new ArrayList<>(changedParams.getEnvironmentVariables().size()); + for (Map.Entry entry : changedParams.getEnvironmentVariables().entrySet()) { + if (entry.getValue() != null) { + envParams.add(entry.getKey() + "=" + entry.getValue()); + } else { + envParams.add("!" + entry.getKey()); + } + } + String env = Utilities.escapeParameters(envParams.toArray(new String[envParams.size()])); + result.put(TOKEN_JAVA_ENV, env); + String prop = Utilities.escapeParameters(new String[] { + "-PrunEnvironment=" + env // NOI18N + }); + result.put(TOKEN_JAVAEXEC_ENV, prop); + } return result; } } diff --git a/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/execute/JavaExecTokenProviderTest.java b/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/execute/JavaExecTokenProviderTest.java index 50943f0c950c..197924c35cfd 100644 --- a/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/execute/JavaExecTokenProviderTest.java +++ b/java/gradle.java/test/unit/src/org/netbeans/modules/gradle/java/execute/JavaExecTokenProviderTest.java @@ -18,10 +18,12 @@ */ package org.netbeans.modules.gradle.java.execute; +import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -174,6 +176,38 @@ public void testAppParamsWithSpace() throws Exception { assertParamList(map.get("java.args"), THREE_PROPS); } + public void testAppCWD() throws Exception { + JavaExecTokenProvider jetp = new JavaExecTokenProvider(createSimpleJavaProject()); + + File wdf = new File("A test working/directory"); + String wd = wdf.getAbsolutePath(); + ExplicitProcessParameters params = ExplicitProcessParameters.builder(). + workingDirectory(wdf). + build(); + + Map map = jetp.createReplacements(ActionProvider.COMMAND_RUN, Lookups.singleton(params)); + assertEquals(6, map.size()); + + assertParamList(map.get("java.workingDir"), wd); + assertParamList(map.get("javaExec.workingDir"), "-PrunWorkingDir=\"" + wd + "\""); + } + + public void testAppEnvironment() throws Exception { + JavaExecTokenProvider jetp = new JavaExecTokenProvider(createSimpleJavaProject()); + + Map envVars = new LinkedHashMap<>(); + envVars.put("TestVar1", "Env Value 1"); + envVars.put("TestVar2", null); + ExplicitProcessParameters params = ExplicitProcessParameters.builder(). + environmentVariables(envVars). + build(); + + Map map = jetp.createReplacements(ActionProvider.COMMAND_RUN, Lookups.singleton(params)); + assertEquals(6, map.size()); + + assertParamList(map.get("java.environment"), new String[]{"TestVar1=Env Value 1", "!TestVar2"}); + assertParamList(map.get("javaExec.environment"), "-PrunEnvironment=\"TestVar1=Env Value 1\" !TestVar2"); + } /** * Note: this code is not actually run, as it would require an entire diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml index 9e4f7a395a44..9914925ce7df 100644 --- a/java/java.lsp.server/nbproject/project.xml +++ b/java/java.lsp.server/nbproject/project.xml @@ -197,7 +197,7 @@ 2 - 1.17 + 1.20 @@ -344,7 +344,7 @@ 0 - 0.4 + 0.5 diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java index f05b675960d6..7667b2af08ab 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -72,6 +73,7 @@ import org.netbeans.modules.java.lsp.server.progress.TestProgressHandler; import org.netbeans.modules.java.nativeimage.debugger.api.NIDebugRunner; import org.netbeans.modules.nativeimage.api.debug.NIDebugger; +import org.netbeans.modules.nativeimage.api.debug.StartDebugParameters; import org.netbeans.spi.project.ActionProgress; import org.netbeans.spi.project.ActionProvider; import org.netbeans.spi.project.ProjectConfiguration; @@ -173,16 +175,7 @@ public void close() throws IOException { W writer = new W(); CompletableFuture> commandFuture = findTargetWithPossibleRebuild(toRun, singleMethod, debug, testRun, ioContext); commandFuture.thenAccept((providerAndCommand) -> { - List args = argsToStringList(launchArguments.get("args")); - List vmArgs = argsToStringList(launchArguments.get("vmArgs")); - ExplicitProcessParameters params = ExplicitProcessParameters.empty(); - if (!(args.isEmpty() && vmArgs.isEmpty())) { - ExplicitProcessParameters.Builder bld = ExplicitProcessParameters.builder(); - bld.launcherArgs(vmArgs); - bld.args(args); - bld.replaceArgs(false); - params = bld.build(); - } + ExplicitProcessParameters params = createExplicitProcessParameters(launchArguments); OperationContext ctx = OperationContext.find(Lookup.getDefault()); ctx.addProgressOperationListener(null, new ProgressOperationListener() { @Override @@ -320,6 +313,7 @@ public void propertyChange(PropertyChangeEvent evt) { .showSuspended(true) .frontWindowOnError(true) .controllable(true); + ExplicitProcessParameters params = createExplicitProcessParameters(launchArguments); Lookup launchCtx = new ProxyLookup( Lookups.fixed(ioContext, progress), Lookup.getDefault() @@ -335,7 +329,7 @@ public void propertyChange(PropertyChangeEvent evt) { }); Lookups.executeWith(execLookup, () -> { String miDebugger = (String) launchArguments.get("miDebugger"); - startNativeDebug(nativeImageFile, args, miDebugger, context, ed, launchFuture, debugProgress); + startNativeDebug(nativeImageFile, args, miDebugger, context, ed, Lookups.fixed(params), launchFuture, debugProgress); }); }); } else { @@ -344,19 +338,64 @@ public void propertyChange(PropertyChangeEvent evt) { notifyFinished(context, exitCode != null && exitCode == 0); }); Lookups.executeWith(execLookup, () -> { - execNative(nativeImageFile, args, context, ed, launchFuture); + execNative(nativeImageFile, args, context, ed, params, launchFuture); }); } } return launchFuture; } - private static void execNative(File nativeImageFile, List args, DebugAdapterContext context, ExecutionDescriptor executionDescriptor, CompletableFuture launchFuture) { + private static ExplicitProcessParameters createExplicitProcessParameters(Map launchArguments) { + List args = argsToStringList(launchArguments.get("args")); + List vmArgs = argsToStringList(launchArguments.get("vmArgs")); + String cwd = Objects.toString(launchArguments.get("cwd"), null); + Object envObj = launchArguments.get("env"); + Map env = envObj != null ? (Map) envObj : Collections.emptyMap(); + ExplicitProcessParameters.Builder bld = ExplicitProcessParameters.builder(); + if (!args.isEmpty()) { + bld.launcherArgs(vmArgs); + } + if (!vmArgs.isEmpty()) { + bld.args(args); + } + bld.replaceArgs(false); + if (cwd != null) { + bld.workingDirectory(new File(cwd)); + } + if (!env.isEmpty()) { + bld.environmentVariables(env); + } + ExplicitProcessParameters params = bld.build(); + return params; + } + + private static void execNative(File nativeImageFile, List args, + DebugAdapterContext context, + ExecutionDescriptor executionDescriptor, + ExplicitProcessParameters params, + CompletableFuture launchFuture) { ExecutionService.newService(() -> { launchFuture.complete(null); - List command = args.isEmpty() ? Collections.singletonList(nativeImageFile.getAbsolutePath()) : join(nativeImageFile.getAbsolutePath(), args); + List command = join(nativeImageFile.getAbsolutePath(), args); try { - return new ProcessBuilder(command).start(); + ProcessBuilder pb = new ProcessBuilder(command); + File workingDirectory = params.getWorkingDirectory(); + if (workingDirectory != null) { + pb.directory(workingDirectory); + } + if (!params.getEnvironmentVariables().isEmpty()) { + Map environment = pb.environment(); + for (Map.Entry entry : params.getEnvironmentVariables().entrySet()) { + String env = entry.getKey(); + String val = entry.getValue(); + if (val != null) { + environment.put(env, val); + } else { + environment.remove(env); + } + } + } + return pb.start(); } catch (IOException ex) { ErrorUtilities.completeExceptionally(launchFuture, "Failed to run debuggee native image: " + ex.getLocalizedMessage(), @@ -367,18 +406,32 @@ private static void execNative(File nativeImageFile, List args, DebugAda } private static List join(String first, List next) { + if (next.isEmpty()) { + return Collections.singletonList(first); + } List joined = new ArrayList<>(next.size() + 1); joined.add(first); joined.addAll(next); return joined; } - private static void startNativeDebug(File nativeImageFile, List args, String miDebugger, DebugAdapterContext context, ExecutionDescriptor executionDescriptor, CompletableFuture launchFuture, ActionProgress debugProgress) { + private static void startNativeDebug(File nativeImageFile, List args, + String miDebugger, DebugAdapterContext context, + ExecutionDescriptor executionDescriptor, + Lookup contextLookup, + CompletableFuture launchFuture, + ActionProgress debugProgress) { AtomicReference debugSessionRef = new AtomicReference<>(); CompletableFuture finished = new CompletableFuture<>(); + List command = join(nativeImageFile.getAbsolutePath(), args); + StartDebugParameters.Builder parametersBuilder = StartDebugParameters.newBuilder(command) + .debugger(miDebugger) + .executionDescriptor(executionDescriptor) + .lookup(contextLookup); + StartDebugParameters parameters = parametersBuilder.build(); NIDebugger niDebugger; try { - niDebugger = NIDebugRunner.start(nativeImageFile, args, miDebugger, null, null, executionDescriptor, engine -> { + niDebugger = NIDebugRunner.start(nativeImageFile, parameters, null, engine -> { Session session = engine.lookupFirst(null, Session.class); NbDebugSession debugSession = new NbDebugSession(session); debugSessionRef.set(debugSession); @@ -405,7 +458,7 @@ private static void startNativeDebug(File nativeImageFile, List args, St } @NonNull - private List argsToStringList(Object o) { + private static List argsToStringList(Object o) { if (o == null) { return Collections.emptyList(); } diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json index 418692271c3a..663ea1d4de7d 100644 --- a/java/java.lsp.server/vscode/package.json +++ b/java/java.lsp.server/vscode/package.json @@ -152,6 +152,21 @@ "description": "Arguments for the Java VM", "default": null }, + "cwd": { + "type": [ + "string", + "null" + ], + "description": "Working directory for the program execution", + "default": null + }, + "env": { + "type": [ + "object" + ], + "description": "Environment variables for the program execution", + "default": {} + }, "launchConfiguration": { "type": [ "string", @@ -389,7 +404,7 @@ "nbcode": "node ./out/nbcode.js", "nbjavac": "node ./out/nbcode.js -J-Dnetbeans.close=true --modules --install .*nbjavac.*", "apisupport": "node ./out/nbcode.js -J-Dnetbeans.close=true --modules --install '(org.netbeans.libs.xerces|org.netbeans.modules.editor.structure|org.netbeans.modules.xml|org.netbeans.modules.xml.axi|org.netbeans.modules.xml.retriever|org.netbeans.modules.xml.schema.model|org.netbeans.modules.xml.tax|org.netbeans.modules.xml.text|org.netbeans.modules.ant.browsetask|.*apisupport.*|org.netbeans.modules.debugger.jpda.ant)' && node ./out/nbcode.js -J-Dnetbeans.close=true --modules --enable .*apisupport.ant" - }, + }, "devDependencies": { "@types/glob": "^7.1.1", "@types/mocha": "^7.0.2", diff --git a/java/java.nativeimage.debugger/nbproject/project.xml b/java/java.nativeimage.debugger/nbproject/project.xml index b54add065505..5e7fb4b285ef 100644 --- a/java/java.nativeimage.debugger/nbproject/project.xml +++ b/java/java.nativeimage.debugger/nbproject/project.xml @@ -85,7 +85,7 @@ 0 - 0.4 + 0.5 diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.java index 6cbd10cc8298..0b5eb01f7a7b 100644 --- a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.java +++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/actions/NIAttachCustomizer.java @@ -39,6 +39,7 @@ import org.netbeans.api.project.Project; import org.netbeans.modules.java.nativeimage.debugger.actions.Processes.ProcessInfo; import org.netbeans.modules.java.nativeimage.debugger.api.NIDebugRunner; +import org.netbeans.modules.nativeimage.api.debug.StartDebugParameters; import org.netbeans.spi.debugger.ui.Controller; import static org.netbeans.spi.debugger.ui.Controller.PROP_VALID; import org.netbeans.spi.debugger.ui.PersistentController; diff --git a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/api/NIDebugRunner.java b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/api/NIDebugRunner.java index 0db96e4122e4..a1eee905a2db 100644 --- a/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/api/NIDebugRunner.java +++ b/java/java.nativeimage.debugger/src/org/netbeans/modules/java/nativeimage/debugger/api/NIDebugRunner.java @@ -32,6 +32,7 @@ import org.netbeans.modules.java.nativeimage.debugger.breakpoints.JPDABreakpointsHandler; import org.netbeans.modules.java.nativeimage.debugger.displayer.JavaFrameDisplayer; import org.netbeans.modules.java.nativeimage.debugger.displayer.JavaVariablesDisplayer; +import org.netbeans.modules.nativeimage.api.debug.StartDebugParameters; import static org.netbeans.spi.project.ActionProvider.COMMAND_DEBUG; import org.openide.DialogDisplayer; @@ -61,7 +62,9 @@ private NIDebugRunner() { * @param startedEngine consumer of the started {@link DebuggerEngine}. * @return an instance of {@link NIDebugger}. * @throws IllegalStateException when the native debugger is not available. + * @deprecated Use {@link #start(File, StartDebugParameters, Project, Consumer)} instead. */ + @Deprecated public static NIDebugger start(File niFile, List arguments, String debuggerCommand, Project project, String displayName, ExecutionDescriptor executionDescriptor, Consumer startedEngine) throws IllegalStateException { JavaVariablesDisplayer variablesDisplayer = new JavaVariablesDisplayer(); JavaFrameDisplayer frameDisplayer = new JavaFrameDisplayer(project); @@ -91,6 +94,28 @@ public static NIDebugger start(File niFile, List arguments, String debug return debugger; } + public static NIDebugger start(File niFile, StartDebugParameters debugParameters, Project project, Consumer startedEngine) throws IllegalStateException { + JavaVariablesDisplayer variablesDisplayer = new JavaVariablesDisplayer(); + JavaFrameDisplayer frameDisplayer = new JavaFrameDisplayer(project); + NIDebugger debugger = NIDebugger.newBuilder() + .frameDisplayer(frameDisplayer) + .variablesDisplayer(variablesDisplayer) + .build(); + variablesDisplayer.setDebugger(debugger); + JPDABreakpointsHandler breakpointsHandler = new JPDABreakpointsHandler(niFile, debugger); + DialogDisplayer displayer = DialogDisplayer.getDefault(); // The launcher might provide a special displayer in the lookup. This is why we grab it eagerly. + debugger.start(debugParameters, + (engine) -> { + if (startedEngine != null) { + startedEngine.accept(engine); + } + }).thenRun(() -> { + breakpointsHandler.dispose(); + }); + checkVersion(debugger.getVersion(), displayer); + return debugger; + } + /** * Attach debugger to a Native Image. * diff --git a/java/maven/nbproject/project.xml b/java/maven/nbproject/project.xml index 9d105e239d71..f8fb496191f3 100644 --- a/java/maven/nbproject/project.xml +++ b/java/maven/nbproject/project.xml @@ -140,7 +140,7 @@ 2 - 1.17 + 1.20 diff --git a/java/maven/src/org/netbeans/modules/maven/debug/DebuggerChecker.java b/java/maven/src/org/netbeans/modules/maven/debug/DebuggerChecker.java index 974f98e2abb4..8df8831aae6b 100644 --- a/java/maven/src/org/netbeans/modules/maven/debug/DebuggerChecker.java +++ b/java/maven/src/org/netbeans/modules/maven/debug/DebuggerChecker.java @@ -248,25 +248,35 @@ protected void doReload(final RunConfig config, final String cname) { } String val = start.execute(context.getInputOutput()); for (Map.Entry entry : NbCollections.checkedMapByFilter(config.getProperties(), String.class, String.class, true).entrySet()) { - StringBuilder buf = new StringBuilder(entry.getValue()); + String value = entry.getValue(); + StringBuilder buf = null; String replaceItem = "${jpda.address}"; //NOI18N - int index = buf.indexOf(replaceItem); + int index = value.indexOf(replaceItem); while (index > -1) { String newItem = val; newItem = newItem == null ? "" : newItem; //NOI18N + if (buf == null) { + buf = new StringBuilder(value); + } buf.replace(index, index + replaceItem.length(), newItem); index = buf.indexOf(replaceItem); } // debug must properly update debug port in runconfig... if(entry.getKey().equals(MAVENSUREFIREDEBUG)) { String address = "address="; //NOI18N - index = buf.indexOf(address); + index = value.indexOf(address); if(index > -1) { + if (buf == null) { + buf = new StringBuilder(value); + } buf.replace(index + 8, buf.length(), val); } } + if (buf != null) { // Change the value when necessary only, to keep it's identity otherwise. + value = buf.toString(); + } // System.out.println("setting property=" + key + "=" + buf.toString()); - config.setProperty(entry.getKey(), buf.toString()); + config.setProperty(entry.getKey(), value); } config.setProperty("jpda.address", val); //NOI18N } catch (Throwable th) { diff --git a/java/maven/src/org/netbeans/modules/maven/execute/DefaultReplaceTokenProvider.java b/java/maven/src/org/netbeans/modules/maven/execute/DefaultReplaceTokenProvider.java index 45027bba6ae2..18591d8c6243 100644 --- a/java/maven/src/org/netbeans/modules/maven/execute/DefaultReplaceTokenProvider.java +++ b/java/maven/src/org/netbeans/modules/maven/execute/DefaultReplaceTokenProvider.java @@ -42,6 +42,7 @@ import org.netbeans.modules.maven.api.NbMavenProject; import org.netbeans.modules.maven.classpath.MavenSourcesImpl; import org.netbeans.modules.maven.configurations.M2ConfigProvider; +import org.netbeans.modules.maven.runjar.MavenExecuteUtils; import org.netbeans.modules.maven.spi.actions.ActionConvertor; import org.netbeans.modules.maven.spi.actions.ReplaceTokenProvider; import org.netbeans.spi.project.ActionProvider; diff --git a/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java b/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java index d303f7fa9531..471ac4121a5b 100644 --- a/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java +++ b/java/maven/src/org/netbeans/modules/maven/execute/MavenCommandLineExecutor.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.TreeMap; import java.util.UUID; import java.util.function.Consumer; import java.util.logging.Level; @@ -79,6 +80,7 @@ import org.netbeans.modules.maven.indexer.api.RepositoryIndexer; import org.netbeans.modules.maven.indexer.api.RepositoryPreferences; import org.netbeans.modules.maven.options.MavenSettings; +import org.netbeans.modules.maven.runjar.MavenExecuteUtils; import org.netbeans.spi.project.ui.support.BuildExecutionSupport; import org.openide.LifecycleManager; import org.openide.awt.HtmlBrowser; @@ -123,9 +125,9 @@ * @author Svata Dedic (svatopluk.dedic@gmail.com) */ public class MavenCommandLineExecutor extends AbstractMavenExecutor { - static final String ENV_PREFIX = "Env."; //NOI18N + static final String ENV_PREFIX = MavenExecuteUtils.ENV_PREFIX; static final String INTERNAL_PREFIX = "NbIde."; //NOI18N - static final String ENV_JAVAHOME = "Env.JAVA_HOME"; //NOI18N + static final String ENV_JAVAHOME = ENV_PREFIX + "JAVA_HOME"; //NOI18N private static final String KEY_UUID = "NB_EXEC_MAVEN_PROCESS_UUID"; //NOI18N @@ -363,7 +365,7 @@ int executeProcess(CommandLineOutputHandler out, ProcessBuilder builder, Consume out.waitFor(); return executionresult; } - + private void kill(Process prcs, String uuid) { Map env = new HashMap(); env.put(KEY_UUID, uuid); @@ -674,7 +676,11 @@ private ProcessBuilder constructBuilder(final RunConfig clonedConfig, InputOutpu continue;// #191374: would prevent bin/mvn from using selected installation } // TODO: do we really put *all* the env vars there? maybe filter, M2_HOME and JDK_HOME? - builder.environment().put(env, val); + if (MavenExecuteUtils.isEnvRemovedValue(val)) { + builder.environment().remove(env); + } else { + builder.environment().put(env, val); + } if (!env.equals(CosChecker.NETBEANS_PROJECT_MAPPINGS) && !env.equals(NETBEANS_MAVEN_COMMAND_LINE)) { //don't show to user display.append(Utilities.escapeParameters(new String[] {env + "=" + val})).append(' '); // NOI18N diff --git a/java/maven/src/org/netbeans/modules/maven/runjar/LaunchArgPrereqsChecker.java b/java/maven/src/org/netbeans/modules/maven/runjar/LaunchArgPrereqsChecker.java index a5438e0edf05..234115bd1b4b 100644 --- a/java/maven/src/org/netbeans/modules/maven/runjar/LaunchArgPrereqsChecker.java +++ b/java/maven/src/org/netbeans/modules/maven/runjar/LaunchArgPrereqsChecker.java @@ -18,6 +18,7 @@ */ package org.netbeans.modules.maven.runjar; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -122,6 +123,20 @@ public boolean checkRunConfig(RunConfig config, ExecutionContext con) { config.setProperty(MavenExecuteUtils.RUN_APP_PARAMS, MavenExecuteUtils.joinParameters(appArgsValue)); } + File workingDirectory = injectParams.getWorkingDirectory(); + if (workingDirectory != null) { + config.setProperty(MavenExecuteUtils.RUN_WORKDIR, + workingDirectory.getAbsolutePath()); + } + Map environmentVariables = injectParams.getEnvironmentVariables(); + for (Map.Entry env : environmentVariables.entrySet()) { + String value = env.getValue(); + if (value == null) { + // The environment variable is to be removed when the value is null + value = MavenExecuteUtils.ENV_REMOVED; + } + config.setProperty(MavenExecuteUtils.ENV_PREFIX + env.getKey(), value); + } return true; } } diff --git a/java/maven/src/org/netbeans/modules/maven/runjar/MavenExecuteUtils.java b/java/maven/src/org/netbeans/modules/maven/runjar/MavenExecuteUtils.java index 4367fa43a52e..6c747af48235 100644 --- a/java/maven/src/org/netbeans/modules/maven/runjar/MavenExecuteUtils.java +++ b/java/maven/src/org/netbeans/modules/maven/runjar/MavenExecuteUtils.java @@ -84,6 +84,9 @@ public final class MavenExecuteUtils { static final String DEFAULT_DEBUG_PARAMS = "-agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address}"; //NOI18N static final String DEFAULT_EXEC_ARGS_CLASSPATH2 = "${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs}"; // NOI18N + public static final String ENV_PREFIX = "Env."; // NOI18N + public static final String ENV_REMOVED = new String("null"); // A special instance for env vars to be removed. // NOI18N + /** * ID of the 'profile' action. */ @@ -513,6 +516,10 @@ public static String doesNotSpecifyCustomExecArgs(boolean exact, Map environmentVariables = injectParams.getEnvironmentVariables(); + for (Map.Entry env : environmentVariables.entrySet()) { + String value = env.getValue(); + if (value == null) { + // The environment variable is to be removed when the value is null + value = MavenExecuteUtils.ENV_REMOVED; + } + config.setProperty(MavenExecuteUtils.ENV_PREFIX + env.getKey(), value); + } if ("test".equals(props.get("exec.classpathScope"))) { isTestScope = true; diff --git a/java/maven/test/unit/src/org/netbeans/modules/maven/execute/MavenExecutionTestBase.java b/java/maven/test/unit/src/org/netbeans/modules/maven/execute/MavenExecutionTestBase.java index 195a3922805c..7543fc27abe1 100644 --- a/java/maven/test/unit/src/org/netbeans/modules/maven/execute/MavenExecutionTestBase.java +++ b/java/maven/test/unit/src/org/netbeans/modules/maven/execute/MavenExecutionTestBase.java @@ -90,6 +90,7 @@ public class MavenExecutionTestBase extends NbTestCase { protected String mavenAppArgs = ""; // NOI18N protected Map mavenExecutorDefines = new HashMap<>(); protected Map mavenExecutorRawDefines = new HashMap<>(); + protected Map mavenExecutorEnvironment = new HashMap<>(); protected final InstanceContent actionData = new InstanceContent(); protected final Lookup actionLookup = new AbstractLookup(actionData); @@ -240,7 +241,7 @@ private static String interpolate(String expr, ExpressionEvaluator mvnEval, Map< * Evaluates property references in arguments. Uses Maven evaluator for the project + properties defined in the * ModelRunConfig (which are passed as -D to the maven executor). Collects all -D defines into {@link #mavenExecutorDefines} */ - protected List substituteProperties(List args, Project p, Map properties) { + protected List substituteProperties(List args, Map environment, Project p, Map properties) { Map props = new HashMap<>(properties); for (int i = 0; i < args.size(); i++) { String a = args.get(i); @@ -261,6 +262,7 @@ protected List substituteProperties(List args, Project p, Map(environment); return args; } @@ -327,7 +329,7 @@ protected void assertMavenRunAction(Project project, NetbeansActionMapping mappi MavenCommandLineExecutor exec = new MavenCommandLineExecutor(cfg, InputOutput.NULL, null) { @Override int executeProcess(CommandLineOutputHandler out, ProcessBuilder builder, Consumer processSetter) throws IOException, InterruptedException { - List args = substituteProperties(builder.command(), project, cfg.getProperties()); + List args = substituteProperties(builder.command(), builder.environment(), project, cfg.getProperties()); commandLineAcceptor.accept(args); return 0; } diff --git a/java/maven/test/unit/src/org/netbeans/modules/maven/runjar/ExecutionEnvHelperTest.java b/java/maven/test/unit/src/org/netbeans/modules/maven/runjar/ExecutionEnvHelperTest.java index 796e2d39ba12..9d539875c58f 100644 --- a/java/maven/test/unit/src/org/netbeans/modules/maven/runjar/ExecutionEnvHelperTest.java +++ b/java/maven/test/unit/src/org/netbeans/modules/maven/runjar/ExecutionEnvHelperTest.java @@ -18,6 +18,7 @@ */ package org.netbeans.modules.maven.runjar; +import java.io.File; import org.netbeans.modules.maven.execute.MavenExecutionTestBase; import java.io.StringReader; import java.util.Map; @@ -63,6 +64,10 @@ public static Test suite() { protected void assertActionCustomVMProperties(String vmArg, String mainClass, String appArg) throws Exception {} + protected void assertActionWorkingDir(String workingDir) throws Exception {} + + protected void assertActionEnvVariable(String varName, String varValue) throws Exception {} + public static class NetBeans123Config extends ExecutionEnvHelperTest { public NetBeans123Config(String name) { @@ -158,6 +163,16 @@ protected void assertActionCustomVMProperties(String vmArg, String mainClass, St } } + @Override + protected void assertActionWorkingDir(String workingDir) throws Exception { + assertEquals(workingDir, mavenExecutorDefines.get(MavenExecuteUtils.RUN_WORKDIR)); + } + + @Override + protected void assertActionEnvVariable(String varName, String varValue) throws Exception { + assertEquals(varValue, mavenExecutorEnvironment.get(varName)); + } + @Override protected String defaultCommandLineArgs() { return MavenExecuteUtils.DEFAULT_EXEC_ARGS_CLASSPATH2; @@ -463,6 +478,43 @@ public void testNewActionVMReplaceStillMergesWithExtenders() throws Exception { assertTrue(mavenVmArgs.contains("-Dbar=foo")); } + /** + * Checks that pre-12.3 default actions will inject working directory from Lookup. + */ + public void test123DefaultActionWithCWD() throws Exception { + initCustomizedProperties(); + createNbActions(runP, debugP, profileP); + File wd = new File("WorkingDirectory"); + ExplicitProcessParameters explicit = ExplicitProcessParameters.builder(). + workingDirectory(wd).build(); + actionData.add(explicit); + createPomWithArguments(); + final Project project = ProjectManager.getDefault().findProject(pom.getParent()); + loadActionMappings(project); + assertMavenRunAction(project, runMapping, "run", c -> {}); + assertActionWorkingDir(wd.getAbsolutePath()); + } + + /** + * Checks that pre-12.3 default actions will inject environment variables from Lookup. + */ + public void test123DefaultActionWithEnvVars() throws Exception { + initCustomizedProperties(); + createNbActions(runP, debugP, profileP); + ExplicitProcessParameters explicit = ExplicitProcessParameters.builder(). + environmentVariable("TEST_VAR1", "test value 1"). + environmentVariable("TEST_VAR2", "test value 2"). + environmentVariable("PATH", null).build(); + actionData.add(explicit); + createPomWithArguments(); + final Project project = ProjectManager.getDefault().findProject(pom.getParent()); + loadActionMappings(project); + assertMavenRunAction(project, runMapping, "run", c -> {}); + assertActionEnvVariable("TEST_VAR1", "test value 1"); + assertActionEnvVariable("TEST_VAR2", "test value 2"); + assertActionEnvVariable("PATH", null); + } + private ExecutionEnvHelper load123ProjectExecutionHelper(boolean defaultProps) throws Exception { if (defaultProps) { initDefaultProperties();