2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.execution.configurations;
18 import com.intellij.execution.CommandLineUtil;
19 import com.intellij.execution.ExecutionException;
20 import com.intellij.execution.Platform;
21 import com.intellij.execution.process.ProcessNotCreatedException;
22 import com.intellij.ide.IdeBundle;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.util.Key;
25 import com.intellij.openapi.util.SystemInfo;
26 import com.intellij.openapi.util.UserDataHolder;
27 import com.intellij.openapi.util.text.StringUtil;
28 import com.intellij.openapi.vfs.CharsetToolkit;
29 import com.intellij.util.EnvironmentUtil;
30 import com.intellij.util.PlatformUtils;
31 import com.intellij.util.containers.ContainerUtil;
32 import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
33 import gnu.trove.THashMap;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
38 import java.io.IOException;
39 import java.nio.charset.Charset;
43 * OS-independent way of executing external processes with complex parameters.
45 * Main idea of the class is to accept parameters "as-is", just as they should look to an external process, and quote/escape them
46 * as required by the underlying platform.
48 * @see com.intellij.execution.process.OSProcessHandler
50 public class GeneralCommandLine implements UserDataHolder {
51 private static final Logger LOG = Logger.getInstance("#com.intellij.execution.configurations.GeneralCommandLine");
53 private String myExePath = null;
54 private File myWorkDirectory = null;
55 private final Map<String, String> myEnvParams = new MyTHashMap();
56 private boolean myPassParentEnvironment = true;
57 private final ParametersList myProgramParams = new ParametersList();
58 private Charset myCharset = CharsetToolkit.getDefaultSystemCharset();
59 private boolean myRedirectErrorStream = false;
60 private Map<Object, Object> myUserData = null;
62 public GeneralCommandLine() { }
64 public GeneralCommandLine(@NotNull String... command) {
65 this(Arrays.asList(command));
68 public GeneralCommandLine(@NotNull List<String> command) {
69 int size = command.size();
71 setExePath(command.get(0));
73 addParameters(command.subList(1, size));
78 public String getExePath() {
83 public GeneralCommandLine withExePath(@NotNull String exePath) {
84 myExePath = exePath.trim();
88 public void setExePath(@NotNull String exePath) {
92 public File getWorkDirectory() {
93 return myWorkDirectory;
97 public GeneralCommandLine withWorkDirectory(@Nullable String path) {
98 return withWorkDirectory(path != null ? new File(path) : null);
102 public GeneralCommandLine withWorkDirectory(@Nullable File workDirectory) {
103 myWorkDirectory = workDirectory;
107 public void setWorkDirectory(@Nullable String path) {
108 withWorkDirectory(path);
111 public void setWorkDirectory(@Nullable File workDirectory) {
112 withWorkDirectory(workDirectory);
116 * Note: the map returned is forgiving to passing null values into putAll().
119 public Map<String, String> getEnvironment() {
124 public GeneralCommandLine withEnvironment(@Nullable Map<String, String> environment) {
125 if (environment != null) {
126 getEnvironment().putAll(environment);
131 public boolean isPassParentEnvironment() {
132 return myPassParentEnvironment;
136 public GeneralCommandLine withPassParentEnvironment(boolean passParentEnvironment) {
137 myPassParentEnvironment = passParentEnvironment;
141 public void setPassParentEnvironment(boolean passParentEnvironment) {
142 withPassParentEnvironment(passParentEnvironment);
146 * @return Environment, that will be passed to the process if isPassParentEnvironment() == true
149 public Map<String, String> getParentEnvironment() {
150 return Collections.unmodifiableMap(PlatformUtils.isAppCode() ? System.getenv() // Temporarily fix for OC-8606
151 : EnvironmentUtil.getEnvironmentMap());
154 public void addParameters(String... parameters) {
155 for (String parameter : parameters) {
156 addParameter(parameter);
160 public void addParameters(@NotNull List<String> parameters) {
161 for (String parameter : parameters) {
162 addParameter(parameter);
166 public void addParameter(@NotNull String parameter) {
167 myProgramParams.add(parameter);
170 public ParametersList getParametersList() {
171 return myProgramParams;
175 public Charset getCharset() {
180 public GeneralCommandLine withCharset(@NotNull Charset charset) {
185 public void setCharset(@NotNull Charset charset) {
186 withCharset(charset);
189 public boolean isRedirectErrorStream() {
190 return myRedirectErrorStream;
194 public GeneralCommandLine withRedirectErrorStream(boolean redirectErrorStream) {
195 myRedirectErrorStream = redirectErrorStream;
199 public void setRedirectErrorStream(boolean redirectErrorStream) {
200 withRedirectErrorStream(redirectErrorStream);
204 * Returns string representation of this command line.<br/>
205 * Warning: resulting string is not OS-dependent - <b>do not</b> use it for executing this command line.
207 * @return single-string representation of this command line.
209 public String getCommandLineString() {
210 return getCommandLineString(null);
214 * Returns string representation of this command line.<br/>
215 * Warning: resulting string is not OS-dependent - <b>do not</b> use it for executing this command line.
217 * @param exeName use this executable name instead of given by {@link #setExePath(String)}
218 * @return single-string representation of this command line.
220 public String getCommandLineString(@Nullable String exeName) {
221 return ParametersList.join(getCommandLineList(exeName));
224 public List<String> getCommandLineList(@Nullable String exeName) {
225 List<String> commands = new ArrayList<String>();
226 if (exeName != null) {
227 commands.add(exeName);
229 else if (myExePath != null) {
230 commands.add(myExePath);
233 commands.add("<null>");
235 commands.addAll(myProgramParams.getList());
240 * Prepares command (quotes and escapes all arguments) and returns it as a newline-separated list
241 * (suitable e.g. for passing in an environment variable).
243 * @param platform a target platform
244 * @return command as a newline-separated list.
247 public String getPreparedCommandLine(@NotNull Platform platform) {
248 String exePath = myExePath != null ? myExePath : "";
249 return StringUtil.join(CommandLineUtil.toCommandLine(exePath, myProgramParams.getList(), platform), "\n");
253 public Process createProcess() throws ExecutionException {
254 if (LOG.isDebugEnabled()) {
255 LOG.debug("Executing [" + getCommandLineString() + "]");
258 List<String> commands;
260 checkWorkingDirectory();
262 if (StringUtil.isEmptyOrSpaces(myExePath)) {
263 throw new ExecutionException(IdeBundle.message("run.configuration.error.executable.not.specified"));
266 commands = CommandLineUtil.toCommandLine(myExePath, myProgramParams.getList());
268 catch (ExecutionException e) {
274 return startProcess(commands);
276 catch (IOException e) {
278 throw new ProcessNotCreatedException(e.getMessage(), e, this);
283 protected Process startProcess(@NotNull List<String> commands) throws IOException {
284 ProcessBuilder builder = new ProcessBuilder(commands);
285 setupEnvironment(builder.environment());
286 builder.directory(myWorkDirectory);
287 builder.redirectErrorStream(myRedirectErrorStream);
288 return builder.start();
291 private void checkWorkingDirectory() throws ExecutionException {
292 if (myWorkDirectory == null) {
295 if (!myWorkDirectory.exists()) {
296 throw new ExecutionException(
297 IdeBundle.message("run.configuration.error.working.directory.does.not.exist", myWorkDirectory.getAbsolutePath()));
299 if (!myWorkDirectory.isDirectory()) {
300 throw new ExecutionException(IdeBundle.message("run.configuration.error.working.directory.not.directory"));
304 protected void setupEnvironment(@NotNull Map<String, String> environment) {
307 if (myPassParentEnvironment) {
308 environment.putAll(getParentEnvironment());
311 if (!myEnvParams.isEmpty()) {
312 if (SystemInfo.isWindows) {
313 THashMap<String, String> envVars = new THashMap<String, String>(CaseInsensitiveStringHashingStrategy.INSTANCE);
314 envVars.putAll(environment);
315 envVars.putAll(myEnvParams);
317 environment.putAll(envVars);
320 environment.putAll(myEnvParams);
326 * Normally, double quotes in parameters are escaped so they arrive to a called program as-is.
327 * But some commands (e.g. {@code 'cmd /c start "title" ...'}) should get they quotes non-escaped.
328 * Wrapping a parameter by this method (instead of using quotes) will do exactly this.
330 * @see com.intellij.execution.util.ExecUtil#getTerminalCommand(String, String)
333 public static String inescapableQuote(@NotNull String parameter) {
334 return CommandLineUtil.specialQuote(parameter);
338 public String toString() {
339 return myExePath + " " + myProgramParams;
343 public <T> T getUserData(@NotNull Key<T> key) {
344 if (myUserData != null) {
345 @SuppressWarnings({"UnnecessaryLocalVariable", "unchecked"}) T t = (T)myUserData.get(key);
352 public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
353 if (myUserData == null) {
354 myUserData = ContainerUtil.newHashMap();
356 myUserData.put(key, value);
359 private static class MyTHashMap extends THashMap<String, String> {
361 public String put(String key, String value) {
362 if (key == null || value == null) {
363 LOG.error(new Exception("Nulls are not allowed"));
367 // Windows: passing an environment variable with empty name causes "CreateProcess error=87, The parameter is incorrect"
368 LOG.warn("Skipping environment variable with empty name, value: " + value);
371 return super.put(key, value);
375 public void putAll(Map<? extends String, ? extends String> map) {