ee4b320503d4e71dec360d76a5b585afacccea65
[idea/community.git] / python / src / com / jetbrains / python / sdk / PythonSdkType.java
1 /*
2  * Copyright 2000-2017 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.google.common.collect.ImmutableMap;
19 import com.google.common.collect.Lists;
20 import com.intellij.execution.ExecutionException;
21 import com.intellij.execution.configurations.GeneralCommandLine;
22 import com.intellij.execution.process.ProcessOutput;
23 import com.intellij.facet.Facet;
24 import com.intellij.facet.FacetConfiguration;
25 import com.intellij.facet.FacetManager;
26 import com.intellij.ide.DataManager;
27 import com.intellij.notification.Notification;
28 import com.intellij.notification.NotificationListener;
29 import com.intellij.notification.NotificationType;
30 import com.intellij.notification.Notifications;
31 import com.intellij.openapi.actionSystem.CommonDataKeys;
32 import com.intellij.openapi.application.Application;
33 import com.intellij.openapi.application.ApplicationManager;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
36 import com.intellij.openapi.module.Module;
37 import com.intellij.openapi.module.ModuleUtilCore;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.projectRoots.*;
40 import com.intellij.openapi.roots.ModuleRootManager;
41 import com.intellij.openapi.roots.OrderRootType;
42 import com.intellij.openapi.util.Comparing;
43 import com.intellij.openapi.util.Key;
44 import com.intellij.openapi.util.Ref;
45 import com.intellij.openapi.util.SystemInfo;
46 import com.intellij.openapi.util.io.FileSystemUtil;
47 import com.intellij.openapi.util.io.FileUtil;
48 import com.intellij.openapi.util.text.CharFilter;
49 import com.intellij.openapi.util.text.StringUtil;
50 import com.intellij.openapi.vfs.JarFileSystem;
51 import com.intellij.openapi.vfs.LocalFileSystem;
52 import com.intellij.openapi.vfs.VfsUtilCore;
53 import com.intellij.openapi.vfs.VirtualFile;
54 import com.intellij.psi.PsiElement;
55 import com.intellij.reference.SoftReference;
56 import com.intellij.remote.*;
57 import com.intellij.remote.ext.CredentialsCase;
58 import com.intellij.remote.ext.LanguageCaseCollector;
59 import com.intellij.util.ArrayUtil;
60 import com.intellij.util.Consumer;
61 import com.intellij.util.ExceptionUtil;
62 import com.intellij.util.containers.ContainerUtil;
63 import com.jetbrains.python.PyBundle;
64 import com.jetbrains.python.PyNames;
65 import com.jetbrains.python.PythonFileType;
66 import com.jetbrains.python.PythonHelper;
67 import com.jetbrains.python.codeInsight.typing.PyTypeShed;
68 import com.jetbrains.python.facet.PythonFacetSettings;
69 import com.jetbrains.python.packaging.PyCondaPackageManagerImpl;
70 import com.jetbrains.python.psi.LanguageLevel;
71 import com.jetbrains.python.psi.impl.PyBuiltinCache;
72 import com.jetbrains.python.psi.search.PyProjectScopeBuilder;
73 import com.jetbrains.python.remote.PyCredentialsContribution;
74 import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
75 import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
76 import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor;
77 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
78 import icons.PythonIcons;
79 import org.jdom.Element;
80 import org.jetbrains.annotations.NonNls;
81 import org.jetbrains.annotations.NotNull;
82 import org.jetbrains.annotations.Nullable;
83
84 import javax.swing.*;
85 import javax.swing.event.HyperlinkEvent;
86 import java.awt.*;
87 import java.io.File;
88 import java.io.IOException;
89 import java.lang.ref.WeakReference;
90 import java.util.*;
91 import java.util.List;
92 import java.util.function.Predicate;
93 import java.util.stream.Collectors;
94
95 /**
96  * Class should be final and singleton since some code checks its instance by ref.
97  *
98  * @author yole
99  */
100 public final class PythonSdkType extends SdkType {
101   public static final String REMOTE_SOURCES_DIR_NAME = "remote_sources";
102   private static final Logger LOG = Logger.getInstance(PythonSdkType.class);
103   private static final String[] WINDOWS_EXECUTABLE_SUFFIXES = new String[]{"cmd", "exe", "bat", "com"};
104
105   static final int MINUTE = 60 * 1000; // 60 seconds, used with script timeouts
106   @NonNls public static final String SKELETONS_TOPIC = "Skeletons";
107   private static final String[] DIRS_WITH_BINARY = new String[]{"", "bin", "Scripts"};
108   private static final String[] UNIX_BINARY_NAMES = new String[]{"jython", "pypy", "python"};
109   private static final String[] WIN_BINARY_NAMES = new String[]{"jython.bat", "ipy.exe", "pypy.exe", "python.exe"};
110
111   private static final Key<WeakReference<Component>> SDK_CREATOR_COMPONENT_KEY = Key.create("#com.jetbrains.python.sdk.creatorComponent");
112   public static final Predicate<Sdk> REMOTE_SDK_PREDICATE = sdk -> isRemote(sdk);
113
114   public static PythonSdkType getInstance() {
115     return SdkType.findInstance(PythonSdkType.class);
116   }
117
118   private PythonSdkType() {
119     super("Python SDK");
120   }
121
122   public Icon getIcon() {
123     return PythonIcons.Python.Python;
124   }
125
126   @NotNull
127   @Override
128   public String getHelpTopic() {
129     return "reference.project.structure.sdk.python";
130   }
131
132   @NotNull
133   public Icon getIconForAddAction() {
134     return PythonFileType.INSTANCE.getIcon();
135   }
136
137   /**
138    * Name of directory where skeleton files (despite the value) are stored.
139    */
140   public static final String SKELETON_DIR_NAME = "python_stubs";
141
142   /**
143    * @return name of builtins skeleton file; for Python 2.x it is '{@code __builtins__.py}'.
144    */
145   @NotNull
146   @NonNls
147   public static String getBuiltinsFileName(@NotNull Sdk sdk) {
148     final LanguageLevel level = getLanguageLevelForSdk(sdk);
149     return level.isOlderThan(LanguageLevel.PYTHON30) ? PyBuiltinCache.BUILTIN_FILE : PyBuiltinCache.BUILTIN_FILE_3K;
150   }
151
152   @NonNls
153   @Nullable
154   public String suggestHomePath() {
155     final String pythonFromPath = findPythonInPath();
156     if (pythonFromPath != null) {
157       return pythonFromPath;
158     }
159     for (PythonSdkFlavor flavor : PythonSdkFlavor.getApplicableFlavors()) {
160       TreeSet<String> candidates = createVersionSet();
161       candidates.addAll(flavor.suggestHomePaths());
162       if (!candidates.isEmpty()) {
163         // return latest version
164         String[] candidateArray = ArrayUtil.toStringArray(candidates);
165         return candidateArray[candidateArray.length - 1];
166       }
167     }
168     return null;
169   }
170
171   @Nullable
172   private static String findPythonInPath() {
173     final String defaultCommand = SystemInfo.isWindows ? "python.exe" : "python";
174     final String path = System.getenv("PATH");
175     for (String root : path.split(File.pathSeparator)) {
176       final File file = new File(root, defaultCommand);
177       if (file.exists()) {
178         try {
179           return file.getCanonicalPath();
180         }
181         catch (IOException ignored) {
182         }
183       }
184     }
185     return null;
186   }
187
188   @NotNull
189   @Override
190   public Collection<String> suggestHomePaths() {
191     List<String> candidates = new ArrayList<>();
192     for (PythonSdkFlavor flavor : PythonSdkFlavor.getApplicableFlavors()) {
193       candidates.addAll(flavor.suggestHomePaths());
194     }
195     return candidates;
196   }
197
198   private static TreeSet<String> createVersionSet() {
199     return new TreeSet<>(Comparator.comparing(PythonSdkType::findDigits));
200   }
201
202   private static String findDigits(String s) {
203     int pos = StringUtil.findFirst(s, new CharFilter() {
204       public boolean accept(char ch) {
205         return Character.isDigit(ch);
206       }
207     });
208     if (pos >= 0) {
209       return s.substring(pos);
210     }
211     return s;
212   }
213
214   public static boolean hasValidSdk() {
215     for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
216       if (sdk.getSdkType() instanceof PythonSdkType) {
217         return true;
218       }
219     }
220     return false;
221   }
222
223   public boolean isValidSdkHome(@Nullable final String path) {
224     return PythonSdkFlavor.getFlavor(path) != null;
225   }
226
227   public static boolean isInvalid(@NotNull Sdk sdk) {
228     if (isRemote(sdk)) {
229       return false;
230     }
231     final VirtualFile interpreter = sdk.getHomeDirectory();
232     return interpreter == null || !interpreter.exists();
233   }
234
235   public static boolean isRemote(@Nullable Sdk sdk) {
236     return PySdkUtil.isRemote(sdk);
237   }
238
239   public static boolean isVagrant(@Nullable Sdk sdk) {
240     if (sdk != null && sdk.getSdkAdditionalData() instanceof PyRemoteSdkAdditionalDataBase) {
241       PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData();
242
243       return data.connectionCredentials().getRemoteConnectionType() == CredentialsType.VAGRANT;
244     }
245     return false;
246   }
247
248   public static boolean isRemote(@Nullable String sdkPath) {
249     return isRemote(findSdkByPath(sdkPath));
250   }
251
252   @NotNull
253   @Override
254   public FileChooserDescriptor getHomeChooserDescriptor() {
255     final boolean isWindows = SystemInfo.isWindows;
256     return new FileChooserDescriptor(true, false, false, false, false, false) {
257       @Override
258       public void validateSelectedFiles(VirtualFile[] files) throws Exception {
259         if (files.length != 0) {
260           if (!isValidSdkHome(files[0].getPath())) {
261             throw new Exception(PyBundle.message("sdk.error.invalid.interpreter.name.$0", files[0].getName()));
262           }
263         }
264       }
265
266       @Override
267       public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
268         // TODO: add a better, customizable filtering
269         if (!file.isDirectory()) {
270           if (isWindows) {
271             String path = file.getPath();
272             boolean looksExecutable = false;
273             for (String ext : WINDOWS_EXECUTABLE_SUFFIXES) {
274               if (path.endsWith(ext)) {
275                 looksExecutable = true;
276                 break;
277               }
278             }
279             return looksExecutable && super.isFileVisible(file, showHiddenFiles);
280           }
281         }
282         return super.isFileVisible(file, showHiddenFiles);
283       }
284     }.withTitle(PyBundle.message("sdk.select.path")).withShowHiddenFiles(SystemInfo.isUnix);
285   }
286
287   public boolean supportsCustomCreateUI() {
288     return true;
289   }
290
291   public void showCustomCreateUI(@NotNull SdkModel sdkModel,
292                                  @NotNull final JComponent parentComponent,
293                                  @NotNull final Consumer<Sdk> sdkCreatedCallback) {
294     Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent));
295     final PointerInfo pointerInfo = MouseInfo.getPointerInfo();
296     if (pointerInfo == null) return;
297     final Point point = pointerInfo.getLocation();
298     PythonSdkDetailsStep
299       .show(project, sdkModel.getSdks(), null, parentComponent, point, sdk -> {
300         if (sdk != null) {
301           sdk.putUserData(SDK_CREATOR_COMPONENT_KEY, new WeakReference<>(parentComponent));
302           sdkCreatedCallback.consume(sdk);
303         }
304       });
305   }
306
307   public static boolean isVirtualEnv(@NotNull Sdk sdk) {
308     final String path = sdk.getHomePath();
309     return isVirtualEnv(path);
310   }
311
312   public static boolean isVirtualEnv(String path) {
313     return path != null && getVirtualEnvRoot(path) != null;
314   }
315
316   public static boolean isCondaVirtualEnv(@NotNull Sdk sdk) {
317     final String path = sdk.getHomePath();
318     return path != null && PyCondaPackageManagerImpl.isCondaVEnv(sdk);
319   }
320
321   @Nullable
322   public Sdk getVirtualEnvBaseSdk(Sdk sdk) {
323     if (isVirtualEnv(sdk)) {
324       final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk);
325       final String version = getVersionString(sdk);
326       if (flavor != null && version != null) {
327         for (Sdk baseSdk : getAllSdks()) {
328           if (!isRemote(baseSdk)) {
329             final PythonSdkFlavor baseFlavor = PythonSdkFlavor.getFlavor(baseSdk);
330             if (!isVirtualEnv(baseSdk) && flavor.equals(baseFlavor) && version.equals(getVersionString(baseSdk))) {
331               return baseSdk;
332             }
333           }
334         }
335       }
336     }
337     return null;
338   }
339
340   /**
341    * @param binaryPath must point to a Python interpreter
342    * @return if the surroundings look like a virtualenv installation, its root is returned (normally the grandparent of binaryPath).
343    */
344   @Nullable
345   public static File getVirtualEnvRoot(@NotNull final String binaryPath) {
346     final File bin = new File(binaryPath).getParentFile();
347     if (bin != null) {
348       final String rootPath = bin.getParent();
349       if (rootPath != null) {
350         final File root = new File(rootPath);
351         final File activateThis = new File(bin, "activate_this.py");
352         // binaryPath should contain an 'activate' script, and root should have bin (with us) and include and libp
353         if (activateThis.exists()) {
354           final File activate = findExecutableFile(bin, "activate");
355           if (activate != null) {
356             return root;
357           }
358         }
359         // Python 3.3 virtualenvs can be found as described in PEP 405
360         final String pyVenvCfg = "pyvenv.cfg";
361         if (new File(root, pyVenvCfg).exists() || new File(bin, pyVenvCfg).exists()) {
362           return root;
363         }
364       }
365     }
366     return null;
367   }
368
369   /**
370    * Finds a file that looks executable: an .exe or .cmd under windows, plain file under *nix.
371    *
372    * @param parent directory to look at
373    * @param name   name of the executable without suffix
374    * @return File representing the executable, or null.
375    */
376   @Nullable
377   public static File findExecutableFile(File parent, String name) {
378     if (SystemInfo.isWindows) {
379       for (String suffix : WINDOWS_EXECUTABLE_SUFFIXES) {
380         File file = new File(parent, name + "." + suffix);
381         if (file.exists()) return file;
382       }
383     }
384     else if (SystemInfo.isUnix) {
385       File file = new File(parent, name);
386       if (file.exists()) return file;
387     }
388     return null;
389   }
390
391   /**
392    * Alters PATH so that a virtualenv is activated, if present.
393    *
394    * @param commandLine           what to patch
395    * @param sdkHome               home of SDK we're using
396    * @param passParentEnvironment iff true, include system paths in PATH
397    */
398   public static void patchCommandLineForVirtualenv(GeneralCommandLine commandLine, String sdkHome, boolean passParentEnvironment) {
399     File virtualEnvRoot = getVirtualEnvRoot(sdkHome);
400     if (virtualEnvRoot != null) {
401       @NonNls final String PATH = "PATH";
402
403       // prepend virtualenv bin if it's not already on PATH
404       File bin = new File(virtualEnvRoot, "bin");
405       if (!bin.exists()) {
406         bin = new File(virtualEnvRoot, "Scripts");   // on Windows
407       }
408       String virtualenvBin = bin.getPath();
409
410       Map<String, String> env = commandLine.getEnvironment();
411       String pathValue;
412       if (env.containsKey(PATH)) {
413         pathValue = PythonEnvUtil.appendToPathEnvVar(env.get(PATH), virtualenvBin);
414       }
415       else if (passParentEnvironment) {
416         // append to PATH
417         pathValue = PythonEnvUtil.appendToPathEnvVar(System.getenv(PATH), virtualenvBin);
418       }
419       else {
420         pathValue = virtualenvBin;
421       }
422       env.put(PATH, pathValue);
423     }
424   }
425
426   public String suggestSdkName(final String currentSdkName, final String sdkHome) {
427     String name = getVersionString(sdkHome);
428     return suggestSdkNameFromVersion(sdkHome, name);
429   }
430
431   private static String suggestSdkNameFromVersion(String sdkHome, String version) {
432     sdkHome = FileUtil.toSystemDependentName(sdkHome);
433     final String shortHomeName = FileUtil.getLocationRelativeToUserHome(sdkHome);
434     if (version != null) {
435       File virtualEnvRoot = getVirtualEnvRoot(sdkHome);
436       if (virtualEnvRoot != null) {
437         version += " virtualenv at " + FileUtil.getLocationRelativeToUserHome(virtualEnvRoot.getAbsolutePath());
438       }
439       else {
440         version += " (" + shortHomeName + ")";
441       }
442     }
443     else {
444       version = "Unknown at " + shortHomeName;
445     } // last resort
446     return version;
447   }
448
449   @Nullable
450   public AdditionalDataConfigurable createAdditionalDataConfigurable(@NotNull final SdkModel sdkModel,
451                                                                      @NotNull final SdkModificator sdkModificator) {
452     return null;
453   }
454
455   @Override
456   public void saveAdditionalData(@NotNull final SdkAdditionalData additionalData, @NotNull final Element additional) {
457     if (additionalData instanceof PythonSdkAdditionalData) {
458       ((PythonSdkAdditionalData)additionalData).save(additional);
459     }
460   }
461
462   @Override
463   public SdkAdditionalData loadAdditionalData(@NotNull final Sdk currentSdk, final Element additional) {
464     if (RemoteSdkCredentialsHolder.isRemoteSdk(currentSdk.getHomePath())) {
465       PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
466       if (manager != null) {
467         return manager.loadRemoteSdkData(currentSdk, additional);
468       }
469     }
470     return PythonSdkAdditionalData.load(currentSdk, additional);
471   }
472
473   public static boolean isSkeletonsPath(String path) {
474     return path.contains(SKELETON_DIR_NAME);
475   }
476
477   @NotNull
478   @NonNls
479   public String getPresentableName() {
480     return "Python SDK";
481   }
482
483   @Override
484   public String sdkPath(@NotNull VirtualFile homePath) {
485     String path = super.sdkPath(homePath);
486     PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(path);
487     if (flavor != null) {
488       VirtualFile sdkPath = flavor.getSdkPath(homePath);
489       if (sdkPath != null) {
490         return FileUtil.toSystemDependentName(sdkPath.getPath());
491       }
492     }
493     return FileUtil.toSystemDependentName(path);
494   }
495
496   public void setupSdkPaths(@NotNull Sdk sdk) {
497     final Project project;
498     final WeakReference<Component> ownerComponentRef = sdk.getUserData(SDK_CREATOR_COMPONENT_KEY);
499     final Component ownerComponent = SoftReference.dereference(ownerComponentRef);
500     if (ownerComponent != null) {
501       project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(ownerComponent));
502     }
503     else {
504       project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
505     }
506     PythonSdkUpdater.updateOrShowError(sdk, null, project, ownerComponent);
507   }
508
509   @Override
510   public boolean setupSdkPaths(@NotNull Sdk sdk, @NotNull SdkModel sdkModel) {
511     return true;  // run setupSdkPaths only once (from PythonSdkDetailsStep). Skip this from showCustomCreateUI
512   }
513
514   public static void notifyRemoteSdkSkeletonsFail(final InvalidSdkException e, @Nullable final Runnable restartAction) {
515     NotificationListener notificationListener;
516     String notificationMessage;
517     if (e.getCause() instanceof VagrantNotStartedException) {
518       notificationListener =
519         new NotificationListener() {
520           @Override
521           public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
522             final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
523             if (manager != null) {
524               try {
525                 VagrantNotStartedException cause = (VagrantNotStartedException)e.getCause();
526                 manager.runVagrant(cause.getVagrantFolder(), cause.getMachineName());
527               }
528               catch (ExecutionException e1) {
529                 throw new RuntimeException(e1);
530               }
531             }
532             if (restartAction != null) {
533               restartAction.run();
534             }
535           }
536         };
537       notificationMessage = e.getMessage() + "\n<a href=\"#\">Launch vagrant and refresh skeletons</a>";
538     }
539     else if (ExceptionUtil.causedBy(e, ExceptionFix.class)) {
540       //noinspection ThrowableResultOfMethodCallIgnored
541       final ExceptionFix fix = ExceptionUtil.findCause(e, ExceptionFix.class);
542       notificationListener =
543         new NotificationListener() {
544           @Override
545           public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
546             fix.apply();
547             if (restartAction != null) {
548               restartAction.run();
549             }
550           }
551         };
552       notificationMessage = fix.getNotificationMessage(e.getMessage());
553     }
554     else {
555       notificationListener = null;
556       notificationMessage = e.getMessage();
557     }
558
559     Notifications.Bus.notify(
560       new Notification(
561         SKELETONS_TOPIC, "Couldn't refresh skeletons for remote interpreter",
562         notificationMessage,
563         NotificationType.WARNING,
564         notificationListener
565       )
566     );
567   }
568
569   /**
570    * In which root type built-in skeletons are put.
571    */
572   public static final OrderRootType BUILTIN_ROOT_TYPE = OrderRootType.CLASSES;
573
574   @NotNull
575   public static VirtualFile getSdkRootVirtualFile(@NotNull VirtualFile path) {
576     String suffix = path.getExtension();
577     if (suffix != null) {
578       suffix = suffix.toLowerCase(); // Why on earth empty suffix is null and not ""?
579     }
580     if ((!path.isDirectory()) && ("zip".equals(suffix) || "egg".equals(suffix))) {
581       // a .zip / .egg file must have its root extracted first
582       final VirtualFile jar = JarFileSystem.getInstance().getJarRootForLocalFile(path);
583       if (jar != null) {
584         return jar;
585       }
586     }
587     return path;
588   }
589
590   /**
591    * Returns skeletons location on the local machine. Independent of SDK credentials type (e.g. ssh, Vagrant, Docker or else).
592    */
593   public static String getSkeletonsPath(String basePath, String sdkHome) {
594     String sep = File.separator;
595     return getSkeletonsRootPath(basePath) + sep + FileUtil.toSystemIndependentName(sdkHome).hashCode() + sep;
596   }
597
598   public static String getSkeletonsRootPath(String basePath) {
599     return basePath + File.separator + SKELETON_DIR_NAME;
600   }
601
602   @NotNull
603   public static List<String> getSysPath(String bin_path) throws InvalidSdkException {
604     String working_dir = new File(bin_path).getParent();
605     Application application = ApplicationManager.getApplication();
606     if (application != null && !application.isUnitTestMode()) {
607       return getSysPathsFromScript(bin_path);
608     }
609     else { // mock sdk
610       List<String> ret = new ArrayList<>(1);
611       ret.add(working_dir);
612       return ret;
613     }
614   }
615
616   @NotNull
617   public static List<String> getSysPathsFromScript(@NotNull String binaryPath) throws InvalidSdkException {
618     // to handle the situation when PYTHONPATH contains ., we need to run the syspath script in the
619     // directory of the script itself - otherwise the dir in which we run the script (e.g. /usr/bin) will be added to SDK path
620     GeneralCommandLine cmd = PythonHelper.SYSPATH.newCommandLine(binaryPath, Lists.newArrayList());
621     final ProcessOutput runResult = PySdkUtil.getProcessOutput(cmd, new File(binaryPath).getParent(),
622                                                                getVirtualEnvExtraEnv(binaryPath), MINUTE);
623     if (!runResult.checkSuccess(LOG)) {
624       throw new InvalidSdkException(String.format("Failed to determine Python's sys.path value:\nSTDOUT: %s\nSTDERR: %s",
625                                                   runResult.getStdout(),
626                                                   runResult.getStderr()));
627     }
628     return runResult.getStdoutLines();
629   }
630
631   /**
632    * Returns a piece of env good as additional env for getProcessOutput.
633    */
634   @Nullable
635   public static Map<String, String> getVirtualEnvExtraEnv(@NotNull String binaryPath) {
636     final File root = getVirtualEnvRoot(binaryPath);
637     if (root != null) {
638       return ImmutableMap.of("PATH", root.toString());
639     }
640     return null;
641   }
642
643   @Nullable
644   @Override
645   public String getVersionString(@NotNull Sdk sdk) {
646     if (isRemote(sdk)) {
647       final PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData();
648       assert data != null;
649       String versionString = data.getVersionString();
650       if (StringUtil.isEmpty(versionString)) {
651         final PythonRemoteInterpreterManager remoteInterpreterManager = PythonRemoteInterpreterManager.getInstance();
652         if (remoteInterpreterManager != null) {
653           try {
654             versionString =
655               remoteInterpreterManager.getInterpreterVersion(null, data);
656           }
657           catch (Exception e) {
658             LOG.warn("Couldn't get interpreter version:" + e.getMessage(), e);
659             versionString = "undefined";
660           }
661         }
662         data.setVersionString(versionString);
663       }
664       return versionString;
665     }
666     else {
667       return getVersionString(sdk.getHomePath());
668     }
669   }
670
671   @Nullable
672   public String getVersionString(final String sdkHome) {
673     final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdkHome);
674     return flavor != null ? flavor.getVersionString(sdkHome) : null;
675   }
676
677   public static List<Sdk> getAllSdks() {
678     return ProjectJdkTable.getInstance().getSdksOfType(getInstance());
679   }
680
681   @Nullable
682   public static Sdk findPythonSdk(@Nullable Module module) {
683     if (module == null) return null;
684     final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
685     if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) return sdk;
686     final Facet[] facets = FacetManager.getInstance(module).getAllFacets();
687     for (Facet facet : facets) {
688       final FacetConfiguration configuration = facet.getConfiguration();
689       if (configuration instanceof PythonFacetSettings) {
690         return ((PythonFacetSettings)configuration).getSdk();
691       }
692     }
693     return null;
694   }
695
696   @Nullable
697   public static Sdk findPythonSdk(@NotNull final PsiElement element) {
698     return findPythonSdk(ModuleUtilCore.findModuleForPsiElement(element));
699   }
700
701   @Nullable
702   public static Sdk findSdkByPath(@Nullable String path) {
703     if (path != null) {
704       return findSdkByPath(getAllSdks(), path);
705     }
706     return null;
707   }
708
709   @Nullable
710   public static Sdk findSdkByPath(List<Sdk> sdkList, @Nullable String path) {
711     if (path != null) {
712       for (Sdk sdk : sdkList) {
713         if (sdk != null && FileUtil.pathsEqual(path, sdk.getHomePath())) {
714           return sdk;
715         }
716       }
717     }
718     return null;
719   }
720
721   @NotNull
722   public static LanguageLevel getLanguageLevelForSdk(@Nullable Sdk sdk) {
723     if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) {
724       final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk);
725       if (flavor != null) {
726         return flavor.getLanguageLevel(sdk);
727       }
728     }
729     return LanguageLevel.getDefault();
730   }
731
732   public boolean isRootTypeApplicable(@NotNull final OrderRootType type) {
733     return type == OrderRootType.CLASSES;
734   }
735
736   public boolean sdkHasValidPath(@NotNull Sdk sdk) {
737     if (PySdkUtil.isRemote(sdk)) {
738       return true;
739     }
740     VirtualFile homeDir = sdk.getHomeDirectory();
741     return homeDir != null && homeDir.isValid();
742   }
743
744   public static boolean isStdLib(@NotNull VirtualFile vFile, @Nullable Sdk pythonSdk) {
745     if (pythonSdk != null) {
746       final VirtualFile libDir = PyProjectScopeBuilder.findLibDir(pythonSdk);
747       if (libDir != null && VfsUtilCore.isAncestor(libDir, vFile, false)) {
748         return isNotSitePackages(vFile, libDir);
749       }
750       final VirtualFile venvLibDir = PyProjectScopeBuilder.findVirtualEnvLibDir(pythonSdk);
751       if (venvLibDir != null && VfsUtilCore.isAncestor(venvLibDir, vFile, false)) {
752         return isNotSitePackages(vFile, venvLibDir);
753       }
754       final VirtualFile skeletonsDir = PySdkUtil.findSkeletonsDir(pythonSdk);
755       if (skeletonsDir != null &&
756           Comparing.equal(vFile.getParent(), skeletonsDir)) {   // note: this will pick up some of the binary libraries not in packages
757         return true;
758       }
759       if (PyTypeShed.INSTANCE.isInStandardLibrary(vFile) && PyTypeShed.INSTANCE.isInside(vFile)) {
760         return true;
761       }
762     }
763     return false;
764   }
765
766   private static boolean isNotSitePackages(VirtualFile vFile, VirtualFile libDir) {
767     final VirtualFile sitePackages = libDir.findChild(PyNames.SITE_PACKAGES);
768     if (sitePackages != null && VfsUtilCore.isAncestor(sitePackages, vFile, false)) {
769       return false;
770     }
771     return true;
772   }
773
774   @Nullable
775   public static Sdk findPython2Sdk(@Nullable Module module) {
776     final Sdk moduleSDK = findPythonSdk(module);
777     if (moduleSDK != null && !getLanguageLevelForSdk(moduleSDK).isPy3K()) {
778       return moduleSDK;
779     }
780     return findPython2Sdk(getAllSdks());
781   }
782
783   @Nullable
784   public static Sdk findPython2Sdk(@NotNull List<Sdk> sdks) {
785     for (Sdk sdk : ContainerUtil.sorted(sdks, PreferredSdkComparator.INSTANCE)) {
786       if (!getLanguageLevelForSdk(sdk).isPy3K()) {
787         return sdk;
788       }
789     }
790     return null;
791   }
792
793   @Nullable
794   public static Sdk findLocalCPython(@Nullable Module module) {
795     final Sdk moduleSDK = findPythonSdk(module);
796     if (moduleSDK != null && !isRemote(moduleSDK) && PythonSdkFlavor.getFlavor(moduleSDK) instanceof CPythonSdkFlavor) {
797       return moduleSDK;
798     }
799     for (Sdk sdk : ContainerUtil.sorted(getAllSdks(), PreferredSdkComparator.INSTANCE)) {
800       if (!isRemote(sdk)) {
801         return sdk;
802       }
803     }
804     return null;
805   }
806
807   public static List<Sdk> getAllLocalCPythons() {
808     return getAllSdks().stream().filter(REMOTE_SDK_PREDICATE.negate()).collect(Collectors.toList());
809   }
810
811   @Nullable
812   public static String getPythonExecutable(@NotNull String rootPath) {
813     final File rootFile = new File(rootPath);
814     if (rootFile.isFile()) {
815       return rootFile.getAbsolutePath();
816     }
817     for (String dir : DIRS_WITH_BINARY) {
818       final File subDir;
819       if (StringUtil.isEmpty(dir)) {
820         subDir = rootFile;
821       }
822       else {
823         subDir = new File(rootFile, dir);
824       }
825       if (!subDir.isDirectory()) {
826         continue;
827       }
828       for (String binaryName : getBinaryNames()) {
829         final File executable = new File(subDir, binaryName);
830         if (executable.isFile()) {
831           return executable.getAbsolutePath();
832         }
833       }
834     }
835     return null;
836   }
837
838   @Nullable
839   public static String getExecutablePath(@NotNull final String homeDirectory, @NotNull String name) {
840     File binPath = new File(homeDirectory);
841     File binDir = binPath.getParentFile();
842     if (binDir == null) return null;
843     File runner = new File(binDir, name);
844     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
845     runner = new File(new File(binDir, "Scripts"), name);
846     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
847     runner = new File(new File(binDir.getParentFile(), "Scripts"), name);
848     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
849     runner = new File(new File(binDir.getParentFile(), "local"), name);
850     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
851     runner = new File(new File(new File(binDir.getParentFile(), "local"), "bin"), name);
852     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
853
854     // if interpreter is a symlink
855     if (FileSystemUtil.isSymLink(homeDirectory)) {
856       String resolvedPath = FileSystemUtil.resolveSymLink(homeDirectory);
857       if (resolvedPath != null) {
858         return getExecutablePath(resolvedPath, name);
859       }
860     }
861     // Search in standard unix path
862     runner = new File(new File("/usr", "bin"), name);
863     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
864     runner = new File(new File(new File("/usr", "local"), "bin"), name);
865     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
866     return null;
867   }
868
869   private static String[] getBinaryNames() {
870     if (SystemInfo.isUnix) {
871       return UNIX_BINARY_NAMES;
872     }
873     else {
874       return WIN_BINARY_NAMES;
875     }
876   }
877
878   public static boolean isIncompleteRemote(Sdk sdk) {
879     if (PySdkUtil.isRemote(sdk)) {
880       //noinspection ConstantConditions
881       if (!((PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData()).isValid()) {
882         return true;
883       }
884     }
885     return false;
886   }
887
888   public static boolean hasInvalidRemoteCredentials(Sdk sdk) {
889     if (PySdkUtil.isRemote(sdk)) {
890       final Ref<Boolean> result = Ref.create(false);
891       //noinspection ConstantConditions
892       ((PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData()).switchOnConnectionType(
893         new LanguageCaseCollector<PyCredentialsContribution>() {
894
895           @Override
896           protected void processLanguageContribution(PyCredentialsContribution languageContribution, Object credentials) {
897             result.set(!languageContribution.isValid(credentials));
898           }
899         }.collectCases(
900           PyCredentialsContribution.class,
901           new CredentialsCase.Vagrant() {
902             @Override
903             public void process(VagrantBasedCredentialsHolder cred) {
904               result.set(StringUtil.isEmpty(cred.getVagrantFolder()));
905             }
906           }
907         ));
908       return result.get();
909     }
910     return false;
911   }
912
913   @Deprecated
914   @Nullable
915   public static Sdk getSdk(@NotNull final PsiElement element) {
916     return findPythonSdk(element);
917   }
918
919   @NotNull
920   public static String getSdkKey(@NotNull Sdk sdk) {
921     return sdk.getName();
922   }
923
924   @Nullable
925   public static Sdk findSdkByKey(@NotNull String key) {
926     return ProjectJdkTable.getInstance().findJdk(key);
927   }
928 }
929