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