Add possibility for flavor to patch command line before execution (https://github...
[idea/community.git] / python / src / com / jetbrains / python / sdk / PySdkUtil.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.jetbrains.python.sdk;
17
18 import com.intellij.execution.ExecutionException;
19 import com.intellij.execution.configurations.GeneralCommandLine;
20 import com.intellij.execution.process.CapturingProcessHandler;
21 import com.intellij.execution.process.ProcessOutput;
22 import com.intellij.openapi.application.PathManager;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.projectRoots.Sdk;
25 import com.intellij.openapi.roots.OrderRootType;
26 import com.intellij.openapi.util.SystemInfo;
27 import com.intellij.openapi.util.io.FileUtil;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.openapi.vfs.VfsUtilCore;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.psi.PsiElement;
32 import com.intellij.psi.PsiFile;
33 import com.intellij.remote.RemoteSdkAdditionalData;
34 import com.intellij.util.SystemProperties;
35 import com.intellij.util.containers.HashMap;
36 import com.jetbrains.python.run.CommandLinePatcher;
37 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
38 import org.jetbrains.annotations.NonNls;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import java.io.File;
43 import java.io.IOException;
44 import java.io.OutputStream;
45 import java.util.Map;
46
47 /**
48  * A more flexible cousin of SdkVersionUtil.
49  * Needs not to be instantiated and only holds static methods.
50  *
51  * @author dcheryasov
52  *         Date: Apr 24, 2008
53  *         Time: 1:19:47 PM
54  */
55 public class PySdkUtil {
56   protected static final Logger LOG = Logger.getInstance("#com.jetbrains.python.sdk.SdkVersionUtil");
57
58   // Windows EOF marker, Ctrl+Z
59   public static final int SUBSTITUTE = 26;
60   public static final String PATH_ENV_VARIABLE = "PATH";
61
62   private PySdkUtil() {
63     // explicitly none
64   }
65
66   /**
67    * Executes a process and returns its stdout and stderr outputs as lists of lines.
68    *
69    * @param homePath process run directory
70    * @param command  command to execute and its arguments
71    * @return a tuple of (stdout lines, stderr lines, exit_code), lines in them have line terminators stripped, or may be null.
72    */
73   @NotNull
74   public static ProcessOutput getProcessOutput(String homePath, @NonNls String[] command) {
75     return getProcessOutput(homePath, command, -1);
76   }
77
78   /**
79    * Executes a process and returns its stdout and stderr outputs as lists of lines.
80    * Waits for process for possibly limited duration.
81    *
82    * @param homePath process run directory
83    * @param command  command to execute and its arguments
84    * @param timeout  how many milliseconds to wait until the process terminates; non-positive means inifinity.
85    * @return a tuple of (stdout lines, stderr lines, exit_code), lines in them have line terminators stripped, or may be null.
86    */
87   @NotNull
88   public static ProcessOutput getProcessOutput(String homePath, @NonNls String[] command, final int timeout) {
89     return getProcessOutput(homePath, command, null, timeout);
90   }
91
92   @NotNull
93   public static ProcessOutput getProcessOutput(String homePath,
94                                                @NonNls String[] command,
95                                                @Nullable @NonNls Map<String, String> extraEnv,
96                                                final int timeout) {
97     return getProcessOutput(homePath, command, extraEnv, timeout, null, true);
98   }
99
100   @NotNull
101   public static ProcessOutput getProcessOutput(String homePath,
102                                                @NonNls String[] command,
103                                                @Nullable @NonNls Map<String, String> extraEnv,
104                                                final int timeout,
105                                                @Nullable byte[] stdin,
106                                                boolean needEOFMarker) {
107     return getProcessOutput(new GeneralCommandLine(command), homePath, extraEnv, timeout, stdin, needEOFMarker);
108   }
109
110   public static ProcessOutput getProcessOutput(@NotNull GeneralCommandLine cmd, @Nullable String homePath,
111                                                @Nullable @NonNls Map<String, String> extraEnv,
112                                                int timeout) {
113     return getProcessOutput(cmd, homePath, extraEnv, timeout, null, true);
114   }
115
116   public static ProcessOutput getProcessOutput(@NotNull GeneralCommandLine cmd, @Nullable String homePath,
117                                                @Nullable @NonNls Map<String, String> extraEnv,
118                                                int timeout,
119                                                @Nullable byte[] stdin, boolean needEOFMarker) {
120     if (homePath == null || !new File(homePath).exists()) {
121       return new ProcessOutput();
122     }
123     final Map<String, String> systemEnv = System.getenv();
124     final Map<String, String> expandedCmdEnv = mergeEnvVariables(systemEnv, cmd.getEnvironment());
125     final Map<String, String> env = extraEnv != null ? mergeEnvVariables(expandedCmdEnv, extraEnv) : expandedCmdEnv;
126     PythonEnvUtil.resetHomePathChanges(homePath, env);
127     try {
128
129       final GeneralCommandLine commandLine = cmd.withWorkDirectory(homePath).withEnvironment(env);
130
131       final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(commandLine.getExePath());
132
133       final CommandLinePatcher cmdLinePatcher = flavor.commandLinePatcher();
134       if (cmdLinePatcher != null) {
135         cmdLinePatcher.patchCommandLine(cmd);
136       }
137
138       final CapturingProcessHandler processHandler = new CapturingProcessHandler(commandLine);
139       if (stdin != null) {
140         final OutputStream processInput = processHandler.getProcessInput();
141         assert processInput != null;
142         processInput.write(stdin);
143         if (SystemInfo.isWindows && needEOFMarker) {
144           processInput.write(SUBSTITUTE);
145           processInput.flush();
146         }
147         else {
148           processInput.close();
149         }
150       }
151       return processHandler.runProcess(timeout);
152     }
153     catch (ExecutionException | IOException e) {
154       return getOutputForException(e);
155     }
156   }
157
158   private static ProcessOutput getOutputForException(final Exception e) {
159     LOG.warn(e);
160     return new ProcessOutput() {
161       @NotNull
162       @Override
163       public String getStderr() {
164         String err = super.getStderr();
165         if (!StringUtil.isEmpty(err)) {
166           err += "\n" + e.getMessage();
167         }
168         else {
169           err = e.getMessage();
170         }
171         return err;
172       }
173     };
174   }
175
176   @NotNull
177   public static Map<String, String> mergeEnvVariables(@NotNull Map<String, String> environment,
178                                                       @NotNull Map<String, String> extraEnvironment) {
179     final Map<String, String> result = new HashMap<>(environment);
180     for (Map.Entry<String, String> entry : extraEnvironment.entrySet()) {
181       final String name = entry.getKey();
182       if (PATH_ENV_VARIABLE.equals(name) || PythonEnvUtil.PYTHONPATH.equals(name)) {
183         PythonEnvUtil.addPathToEnv(result, name, entry.getValue());
184       }
185       else {
186         result.put(name, entry.getValue());
187       }
188     }
189     return result;
190   }
191
192   public static boolean isRemote(@Nullable Sdk sdk) {
193     return sdk != null && sdk.getSdkAdditionalData() instanceof RemoteSdkAdditionalData;
194   }
195
196   public static String getUserSite() {
197     if (SystemInfo.isWindows) {
198       final String appdata = System.getenv("APPDATA");
199       return appdata + File.separator + "Python";
200     }
201     else {
202       final String userHome = SystemProperties.getUserHome();
203       return userHome + File.separator + ".local";
204     }
205   }
206
207   public static boolean isElementInSkeletons(@NotNull final PsiElement element) {
208     final PsiFile file = element.getContainingFile();
209     if (file != null) {
210       final VirtualFile virtualFile = file.getVirtualFile();
211       if (virtualFile != null) {
212         final Sdk sdk = PythonSdkType.findPythonSdk(element);
213         if (sdk != null) {
214           final VirtualFile skeletonsDir = findSkeletonsDir(sdk);
215           if (skeletonsDir != null && VfsUtilCore.isAncestor(skeletonsDir, virtualFile, false)) {
216             return true;
217           }
218         }
219       }
220     }
221     return false;
222   }
223
224   public static String getRemoteSourcesLocalPath(String sdkHome) {
225     String sep = File.separator;
226
227     String basePath = PathManager.getSystemPath();
228     return basePath +
229            File.separator +
230            PythonSdkType.REMOTE_SOURCES_DIR_NAME +
231            sep +
232            FileUtil.toSystemIndependentName(sdkHome).hashCode() +
233            sep;
234   }
235
236   @Nullable
237   public static VirtualFile findSkeletonsDir(@NotNull final Sdk sdk) {
238     return findLibraryDir(sdk, PythonSdkType.SKELETON_DIR_NAME, PythonSdkType.BUILTIN_ROOT_TYPE);
239   }
240
241   @Nullable
242   public static VirtualFile findAnyRemoteLibrary(@NotNull final Sdk sdk) {
243     return findLibraryDir(sdk, PythonSdkType.REMOTE_SOURCES_DIR_NAME, OrderRootType.CLASSES);
244   }
245
246   private static VirtualFile findLibraryDir(Sdk sdk, String dirName, OrderRootType rootType) {
247     final VirtualFile[] virtualFiles = sdk.getRootProvider().getFiles(rootType);
248     for (VirtualFile virtualFile : virtualFiles) {
249       if (virtualFile.isValid() && virtualFile.getPath().contains(dirName)) {
250         return virtualFile;
251       }
252     }
253     return null;
254   }
255 }