/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package com.intellij.execution.configurations;
import com.intellij.diagnostic.LoadingState;
import com.intellij.execution.CommandLineUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.IllegalEnvVarException;
import com.intellij.execution.Platform;
import com.intellij.execution.process.ProcessNotCreatedException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.util.EnvironmentUtil;
import com.intellij.util.containers.CollectionFactory;
import com.intellij.util.containers.FastUtilHashingStrategies;
import com.intellij.util.execution.ParametersListUtil;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;
public class GeneralCommandLine implements UserDataHolder {
private static final Logger LOG = Logger.getInstance(GeneralCommandLine.class);
/**
* Determines the scope of a parent environment passed to a child process.
*
* {@code NONE} means a child process will receive an empty environment.
* {@code SYSTEM} will provide it with the same environment as an IDE.
* {@code CONSOLE} provides the child with a similar environment as if it was launched from, well, a console.
* On OS X, a console environment is simulated (see {@link EnvironmentUtil#getEnvironmentMap()} for reasons it's needed
* and details on how it works). On Windows and Unix hosts, this option is no different from {@code SYSTEM}
* since there is no drastic distinction in environment between GUI and console apps.
*/
public enum ParentEnvironmentType {NONE, SYSTEM, CONSOLE}
private String myExePath;
private File myWorkDirectory;
private final Map myEnvParams = new MyMap();
private ParentEnvironmentType myParentEnvironmentType = ParentEnvironmentType.CONSOLE;
private final ParametersList myProgramParams = new ParametersList();
private Charset myCharset = defaultCharset();
private boolean myRedirectErrorStream;
private File myInputFile;
private Map
*
If you need to alter the command line passed in, override the {@link #prepareCommandLine(String, List, Platform)} method instead.
*/
@NotNull
protected Process startProcess(@NotNull List escapedCommands) throws IOException {
if (LOG.isDebugEnabled()) {
LOG.debug("Building process with commands: " + escapedCommands);
}
return toProcessBuilderInternal(escapedCommands).start();
}
// This is caused by the fact there are external usages overriding startProcess(List).
// Ideally, it should have been startProcess(ProcessBuilder), and the design would be more straightforward.
@NotNull
private ProcessBuilder toProcessBuilderInternal(@NotNull List escapedCommands) {
ProcessBuilder builder = new ProcessBuilder(escapedCommands);
setupEnvironment(builder.environment());
builder.directory(myWorkDirectory);
builder.redirectErrorStream(myRedirectErrorStream);
if (myInputFile != null) {
builder.redirectInput(ProcessBuilder.Redirect.from(myInputFile));
}
return buildProcess(builder);
}
/**
* Executed with pre-filled ProcessBuilder as the param and
* gives the last chance to configure starting process
* parameters before a process is started
* @param builder filed ProcessBuilder
*/
@NotNull
protected ProcessBuilder buildProcess(@NotNull ProcessBuilder builder) {
return builder;
}
protected void setupEnvironment(@NotNull Map environment) {
environment.clear();
if (myParentEnvironmentType != ParentEnvironmentType.NONE) {
environment.putAll(getParentEnvironment());
}
if (SystemInfoRt.isUnix) {
File workDirectory = getWorkDirectory();
if (workDirectory != null) {
environment.put("PWD", FileUtil.toSystemDependentName(workDirectory.getAbsolutePath()));
}
}
if (!myEnvParams.isEmpty()) {
if (SystemInfoRt.isWindows) {
Map envVars = CollectionFactory.createCaseInsensitiveStringMap();
envVars.putAll(environment);
envVars.putAll(myEnvParams);
environment.clear();
environment.putAll(envVars);
}
else {
environment.putAll(myEnvParams);
}
}
}
/**
* Normally, double quotes in parameters are escaped, so they arrive to a called program as-is.
* But some commands (e.g. {@code 'cmd /c start "title" ...'}) should get their quotes non-escaped.
* Wrapping a parameter by this method (instead of using quotes) will do exactly this.
*
* @see com.intellij.execution.util.ExecUtil#getTerminalCommand(String, String)
*/
@NotNull
public static String inescapableQuote(@NotNull String parameter) {
return CommandLineUtil.specialQuote(parameter);
}
@Override
public String toString() {
return myExePath + " " + myProgramParams;
}
@Nullable
@Override
public T getUserData(@NotNull Key key) {
if (myUserData == null) return null;
@SuppressWarnings("unchecked") T t = (T)myUserData.get(key);
return t;
}
@Override
public void putUserData(@NotNull Key key, @Nullable T value) {
if (myUserData == null) {
if (value == null) return;
myUserData = new HashMap<>();
}
myUserData.put(key, value);
}
private static final class MyMap extends Object2ObjectOpenCustomHashMap {
private MyMap() {
super(FastUtilHashingStrategies.getStringStrategy(!SystemInfoRt.isWindows));
}
@Override
public void putAll(Map extends String, ? extends String> map) {
if (map != null) {
super.putAll(map);
}
}
}
}