d786d9f3cbc99fdd96785bc23a9380ca685dcae5
[idea/community.git] / python / src / com / jetbrains / python / sdk / PythonSdkType.java
1 /*
2  * Copyright 2000-2015 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.application.ModalityState;
35 import com.intellij.openapi.application.PathManager;
36 import com.intellij.openapi.diagnostic.Logger;
37 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
38 import com.intellij.openapi.module.Module;
39 import com.intellij.openapi.module.ModuleUtilCore;
40 import com.intellij.openapi.progress.ProgressIndicator;
41 import com.intellij.openapi.progress.ProgressManager;
42 import com.intellij.openapi.progress.Task;
43 import com.intellij.openapi.project.DumbModePermission;
44 import com.intellij.openapi.project.DumbService;
45 import com.intellij.openapi.project.Project;
46 import com.intellij.openapi.projectRoots.*;
47 import com.intellij.openapi.roots.ModuleRootManager;
48 import com.intellij.openapi.roots.OrderRootType;
49 import com.intellij.openapi.ui.Messages;
50 import com.intellij.openapi.util.*;
51 import com.intellij.openapi.util.io.FileSystemUtil;
52 import com.intellij.openapi.util.io.FileUtil;
53 import com.intellij.openapi.util.text.CharFilter;
54 import com.intellij.openapi.util.text.StringUtil;
55 import com.intellij.openapi.vfs.JarFileSystem;
56 import com.intellij.openapi.vfs.LocalFileSystem;
57 import com.intellij.openapi.vfs.VfsUtilCore;
58 import com.intellij.openapi.vfs.VirtualFile;
59 import com.intellij.psi.PsiElement;
60 import com.intellij.reference.SoftReference;
61 import com.intellij.remote.*;
62 import com.intellij.util.ArrayUtil;
63 import com.intellij.util.Consumer;
64 import com.intellij.util.NullableConsumer;
65 import com.intellij.util.ui.UIUtil;
66 import com.jetbrains.python.PyBundle;
67 import com.jetbrains.python.PyNames;
68 import com.jetbrains.python.PythonFileType;
69 import com.jetbrains.python.PythonHelper;
70 import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
71 import com.jetbrains.python.facet.PythonFacetSettings;
72 import com.jetbrains.python.psi.LanguageLevel;
73 import com.jetbrains.python.psi.impl.PyBuiltinCache;
74 import com.jetbrains.python.psi.search.PyProjectScopeBuilder;
75 import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
76 import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
77 import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor;
78 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
79 import icons.PythonIcons;
80 import org.jdom.Element;
81 import org.jetbrains.annotations.NonNls;
82 import org.jetbrains.annotations.NotNull;
83 import org.jetbrains.annotations.Nullable;
84
85 import javax.swing.*;
86 import javax.swing.event.HyperlinkEvent;
87 import java.awt.*;
88 import java.io.File;
89 import java.io.IOException;
90 import java.lang.ref.WeakReference;
91 import java.util.*;
92 import java.util.List;
93 import java.util.regex.Pattern;
94
95 /**
96  * @author yole
97  */
98 public class PythonSdkType extends SdkType {
99   public static final String REMOTE_SOURCES_DIR_NAME = "remote_sources";
100   private static final Logger LOG = Logger.getInstance("#" + PythonSdkType.class.getName());
101   private static final String[] WINDOWS_EXECUTABLE_SUFFIXES = new String[]{"cmd", "exe", "bat", "com"};
102
103   static final int MINUTE = 60 * 1000; // 60 seconds, used with script timeouts
104   @NonNls public static final String SKELETONS_TOPIC = "Skeletons";
105   private static final String[] DIRS_WITH_BINARY = new String[]{"", "bin", "Scripts"};
106   private static final String[] UNIX_BINARY_NAMES = new String[]{"jython", "pypy", "python"};
107   private static final String[] WIN_BINARY_NAMES = new String[]{"jython.bat", "ipy.exe", "pypy.exe", "python.exe"};
108
109   private static final Key<WeakReference<Component>> SDK_CREATOR_COMPONENT_KEY = Key.create("#com.jetbrains.python.sdk.creatorComponent");
110
111   public static PythonSdkType getInstance() {
112     return SdkType.findInstance(PythonSdkType.class);
113   }
114
115   public PythonSdkType() {
116     super("Python SDK");
117   }
118
119   protected PythonSdkType(@NonNls String name) {
120     super(name);
121   }
122
123   public Icon getIcon() {
124     return PythonIcons.Python.Python;
125   }
126
127   @NotNull
128   @Override
129   public String getHelpTopic() {
130     return "reference.project.structure.sdk.python";
131   }
132
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<String>();
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<String>(new Comparator<String>() {
200       public int compare(String o1, String o2) {
201         return findDigits(o1).compareTo(findDigits(o2));
202       }
203     });
204   }
205
206   private static String findDigits(String s) {
207     int pos = StringUtil.findFirst(s, new CharFilter() {
208       public boolean accept(char ch) {
209         return Character.isDigit(ch);
210       }
211     });
212     if (pos >= 0) {
213       return s.substring(pos);
214     }
215     return s;
216   }
217
218   public static boolean hasValidSdk() {
219     for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
220       if (sdk.getSdkType() instanceof PythonSdkType) {
221         return true;
222       }
223     }
224     return false;
225   }
226
227   public boolean isValidSdkHome(@Nullable final String path) {
228     return PythonSdkFlavor.getFlavor(path) != null;
229   }
230
231   public static boolean isInvalid(@NotNull Sdk sdk) {
232     if (isRemote(sdk)) {
233       return false;
234     }
235     final VirtualFile interpreter = sdk.getHomeDirectory();
236     return interpreter == null || !interpreter.exists();
237   }
238
239   public static boolean isRemote(@Nullable Sdk sdk) {
240     return PySdkUtil.isRemote(sdk);
241   }
242
243   public static boolean isVagrant(@Nullable Sdk sdk) {
244     if (sdk != null && sdk.getSdkAdditionalData() instanceof PyRemoteSdkAdditionalDataBase) {
245       PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData();
246
247       return data.getRemoteConnectionType() == CredentialsType.VAGRANT;
248     }
249     return false;
250   }
251
252   public static boolean isRemote(@Nullable String sdkPath) {
253     return isRemote(findSdkByPath(sdkPath));
254   }
255
256   @Override
257   public FileChooserDescriptor getHomeChooserDescriptor() {
258     final boolean isWindows = SystemInfo.isWindows;
259     return new FileChooserDescriptor(true, false, false, false, false, false) {
260       @Override
261       public void validateSelectedFiles(VirtualFile[] files) throws Exception {
262         if (files.length != 0) {
263           if (!isValidSdkHome(files[0].getPath())) {
264             throw new Exception(PyBundle.message("sdk.error.invalid.interpreter.name.$0", files[0].getName()));
265           }
266         }
267       }
268
269       @Override
270       public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
271         // TODO: add a better, customizable filtering
272         if (!file.isDirectory()) {
273           if (isWindows) {
274             String path = file.getPath();
275             boolean looksExecutable = false;
276             for (String ext : WINDOWS_EXECUTABLE_SUFFIXES) {
277               if (path.endsWith(ext)) {
278                 looksExecutable = true;
279                 break;
280               }
281             }
282             return looksExecutable && super.isFileVisible(file, showHiddenFiles);
283           }
284         }
285         return super.isFileVisible(file, showHiddenFiles);
286       }
287     }.withTitle(PyBundle.message("sdk.select.path")).withShowHiddenFiles(SystemInfo.isUnix);
288   }
289
290   public boolean supportsCustomCreateUI() {
291     return true;
292   }
293
294   public void showCustomCreateUI(SdkModel sdkModel, final JComponent parentComponent, final Consumer<Sdk> sdkCreatedCallback) {
295     Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent));
296     final PointerInfo pointerInfo = MouseInfo.getPointerInfo();
297     if (pointerInfo == null) return;
298     final Point point = pointerInfo.getLocation();
299     PythonSdkDetailsStep
300       .show(project, sdkModel.getSdks(), null, parentComponent, point, new NullableConsumer<Sdk>() {
301         @Override
302         public void consume(@Nullable Sdk sdk) {
303           if (sdk != null) {
304             sdk.putUserData(SDK_CREATOR_COMPONENT_KEY, new WeakReference<Component>(parentComponent));
305             sdkCreatedCallback.consume(sdk);
306           }
307         }
308       });
309   }
310
311   public static boolean isVirtualEnv(Sdk sdk) {
312     final String path = sdk.getHomePath();
313     return path != null && getVirtualEnvRoot(path) != null;
314   }
315
316   @Nullable
317   public Sdk getVirtualEnvBaseSdk(Sdk sdk) {
318     if (isVirtualEnv(sdk)) {
319       final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk);
320       final String version = getVersionString(sdk);
321       if (flavor != null && version != null) {
322         for (Sdk baseSdk : getAllSdks()) {
323           final PythonSdkFlavor baseFlavor = PythonSdkFlavor.getFlavor(baseSdk);
324           if (!isVirtualEnv(baseSdk) && flavor.equals(baseFlavor) && version.equals(getVersionString(baseSdk))) {
325             return baseSdk;
326           }
327         }
328       }
329     }
330     return null;
331   }
332
333   /**
334    * @param binaryPath must point to a Python interpreter
335    * @return if the surroundings look like a virtualenv installation, its root is returned (normally the grandparent of binaryPath).
336    */
337   @Nullable
338   public static File getVirtualEnvRoot(@NotNull final String binaryPath) {
339     final File bin = new File(binaryPath).getParentFile();
340     if (bin != null) {
341       final String rootPath = bin.getParent();
342       if (rootPath != null) {
343         final File root = new File(rootPath);
344         final File activateThis = new File(bin, "activate_this.py");
345         // binaryPath should contain an 'activate' script, and root should have bin (with us) and include and libp
346         if (activateThis.exists()) {
347           final File activate = findExecutableFile(bin, "activate");
348           if (activate != null) {
349             return root;
350           }
351         }
352         // Python 3.3 virtualenvs can be found as described in PEP 405
353         final String pyVenvCfg = "pyvenv.cfg";
354         if (new File(root, pyVenvCfg).exists() || new File(bin, pyVenvCfg).exists()) {
355           return root;
356         }
357       }
358     }
359     return null;
360   }
361
362   /**
363    * Finds a file that looks executable: an .exe or .cmd under windows, plain file under *nix.
364    *
365    * @param parent directory to look at
366    * @param name   name of the executable without suffix
367    * @return File representing the executable, or null.
368    */
369   @Nullable
370   public static File findExecutableFile(File parent, String name) {
371     if (SystemInfo.isWindows || SystemInfo.isOS2) {
372       for (String suffix : WINDOWS_EXECUTABLE_SUFFIXES) {
373         File file = new File(parent, name + "." + suffix);
374         if (file.exists()) return file;
375       }
376     }
377     else if (SystemInfo.isUnix) {
378       File file = new File(parent, name);
379       if (file.exists()) return file;
380     }
381     return null;
382   }
383
384   /**
385    * Alters PATH so that a virtualenv is activated, if present.
386    *
387    * @param commandLine           what to patch
388    * @param sdkHome               home of SDK we're using
389    * @param passParentEnvironment iff true, include system paths in PATH
390    */
391   public static void patchCommandLineForVirtualenv(GeneralCommandLine commandLine, String sdkHome, boolean passParentEnvironment) {
392     File virtualEnvRoot = getVirtualEnvRoot(sdkHome);
393     if (virtualEnvRoot != null) {
394       @NonNls final String PATH = "PATH";
395
396       // prepend virtualenv bin if it's not already on PATH
397       File bin = new File(virtualEnvRoot, "bin");
398       if (!bin.exists()) {
399         bin = new File(virtualEnvRoot, "Scripts");   // on Windows
400       }
401       String virtualenvBin = bin.getPath();
402
403       Map<String, String> env = commandLine.getEnvironment();
404       String pathValue;
405       if (env.containsKey(PATH)) {
406         pathValue = PythonEnvUtil.appendToPathEnvVar(env.get(PATH), virtualenvBin);
407       }
408       else if (passParentEnvironment) {
409         // append to PATH
410         pathValue = PythonEnvUtil.appendToPathEnvVar(System.getenv(PATH), virtualenvBin);
411       }
412       else {
413         pathValue = virtualenvBin;
414       }
415       env.put(PATH, pathValue);
416     }
417   }
418
419   public String suggestSdkName(final String currentSdkName, final String sdkHome) {
420     String name = getVersionString(sdkHome);
421     return suggestSdkNameFromVersion(sdkHome, name);
422   }
423
424   public static String suggestSdkNameFromVersion(String sdkHome, String version) {
425     sdkHome = FileUtil.toSystemDependentName(sdkHome);
426     final String shortHomeName = FileUtil.getLocationRelativeToUserHome(sdkHome);
427     if (version != null) {
428       File virtualEnvRoot = getVirtualEnvRoot(sdkHome);
429       if (virtualEnvRoot != null) {
430         version += " virtualenv at " + FileUtil.getLocationRelativeToUserHome(virtualEnvRoot.getAbsolutePath());
431       }
432       else {
433         version += " (" + shortHomeName + ")";
434       }
435     }
436     else {
437       version = "Unknown at " + shortHomeName;
438     } // last resort
439     return version;
440   }
441
442   @Nullable
443   public AdditionalDataConfigurable createAdditionalDataConfigurable(final SdkModel sdkModel, final SdkModificator sdkModificator) {
444     return null;
445   }
446
447   @Override
448   public void saveAdditionalData(@NotNull final SdkAdditionalData additionalData, @NotNull final Element additional) {
449     if (additionalData instanceof PythonSdkAdditionalData) {
450       ((PythonSdkAdditionalData)additionalData).save(additional);
451     }
452   }
453
454   @Override
455   public SdkAdditionalData loadAdditionalData(@NotNull final Sdk currentSdk, final Element additional) {
456     if (RemoteSdkCredentialsHolder.isRemoteSdk(currentSdk.getHomePath())) {
457       PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
458       if (manager != null) {
459         return manager.loadRemoteSdkData(currentSdk, additional);
460       }
461     }
462     return PythonSdkAdditionalData.load(currentSdk, additional);
463   }
464
465   public static boolean isSkeletonsPath(String path) {
466     return path.contains(SKELETON_DIR_NAME);
467   }
468
469   @NonNls
470   public String getPresentableName() {
471     return "Python SDK";
472   }
473
474   @Override
475   public String sdkPath(VirtualFile homePath) {
476     String path = super.sdkPath(homePath);
477     PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(path);
478     if (flavor != null) {
479       VirtualFile sdkPath = flavor.getSdkPath(homePath);
480       if (sdkPath != null) {
481         return FileUtil.toSystemDependentName(sdkPath.getPath());
482       }
483     }
484     return FileUtil.toSystemDependentName(path);
485   }
486
487   public void setupSdkPaths(@NotNull final Sdk sdk) {
488     final Project project;
489     final WeakReference<Component> ownerComponentRef = sdk.getUserData(SDK_CREATOR_COMPONENT_KEY);
490     final Component ownerComponent = SoftReference.dereference(ownerComponentRef);
491     if (ownerComponent != null) {
492       project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(ownerComponent));
493     }
494     else {
495       project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
496     }
497
498     DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() {
499       @Override
500       public void run() {
501         setupSdkPaths(sdk, project, ownerComponent);
502       }
503     });
504   }
505
506   @Override
507   public boolean setupSdkPaths(Sdk sdk, SdkModel sdkModel) {
508     return true;  // run setupSdkPaths only once (from PythonSdkDetailsStep). Skip this from showCustomCreateUI
509   }
510
511   public static void setupSdkPaths(@NotNull final Sdk sdk,
512                                    @Nullable final Project project,
513                                    @Nullable final Component ownerComponent,
514                                    @NotNull final SdkModificator sdkModificator) {
515     doSetupSdkPaths(project, ownerComponent, PySdkUpdater.fromSdkModificator(sdk, sdkModificator));
516   }
517
518
519   public static void setupSdkPaths(final Sdk sdk, @Nullable final Project project, @Nullable final Component ownerComponent) {
520     ApplicationManager.getApplication().invokeLater(new Runnable() {
521       @Override
522       public void run() {
523         try {
524           final boolean success = doSetupSdkPaths(project, ownerComponent, PySdkUpdater.fromSdkPath(sdk.getHomePath()));
525
526           if (!success) {
527             Messages.showErrorDialog(
528               project,
529               PyBundle.message("MSG.cant.setup.sdk.$0", FileUtil.toSystemDependentName(sdk.getSdkModificator().getHomePath())),
530               PyBundle.message("MSG.title.bad.sdk")
531             );
532           }
533         } catch (PySdkUpdater.PySdkNotFoundException e) {
534           // sdk was removed from sdk table so no need to setup paths
535         }
536       }
537     }, ModalityState.NON_MODAL);
538   }
539
540   private static boolean doSetupSdkPaths(@Nullable final Project project,
541                                          @Nullable final Component ownerComponent,
542                                          @NotNull final PySdkUpdater sdkUpdater) {
543     if (isRemote(sdkUpdater.getSdk()) && project == null && ownerComponent == null) {
544       LOG.error("For refreshing skeletons of remote SDK, either project or owner component must be specified");
545     }
546     final ProgressManager progressManager = ProgressManager.getInstance();
547     boolean sdkPathsUpdated = UIUtil.<Boolean>invokeAndWaitIfNeeded(
548       new Computable<Boolean>() {
549         @Override
550         public Boolean compute() {
551           return updateSdkPaths(sdkUpdater);
552         }
553       }
554     );
555
556     final Application application = ApplicationManager.getApplication();
557     if (sdkPathsUpdated && !application.isUnitTestMode()) {
558       application.invokeLater(new Runnable() {
559         @Override
560         public void run() {
561           progressManager.run(new Task.Backgroundable(project, PyBundle.message("sdk.gen.updating.skels"), false) {
562             @Override
563             public void run(@NotNull ProgressIndicator indicator) {
564               try {
565                 PythonSdkUpdater.updateSdk(project, ownerComponent, sdkUpdater);
566               }
567               catch (InvalidSdkException e) {
568                 // If the SDK is invalid, the user should worry about the SDK itself, not about skeletons generation errors
569                 if (isVagrant(sdkUpdater.getSdk())) {
570                   notifyRemoteSdkSkeletonsFail(e, new Runnable() {
571                     @Override
572                     public void run() {
573                       setupSdkPaths(sdkUpdater.getSdk(), project, ownerComponent);
574                     }
575                   });
576                 }
577                 else if (!isInvalid(sdkUpdater.getSdk())) {
578                   LOG.error(e);
579                 }
580               }
581             }
582           });
583         }
584       });
585     }
586     return sdkPathsUpdated;
587   }
588
589   @NotNull
590   public static Boolean updateSdkPaths(@NotNull PySdkUpdater sdkUpdater) {
591     sdkUpdater.modifySdk(new PySdkUpdater.SdkModificationProcessor() {
592       @Override
593       public void process(@NotNull Sdk sdk,
594                           @NotNull SdkModificator sdkModificator) {
595         sdkModificator.removeAllRoots();
596       }
597     });
598     try {
599       updateSdkRootsFromSysPath(sdkUpdater);
600       updateUserAddedPaths(sdkUpdater);
601       PythonSdkUpdater.getInstance()
602         .markAlreadyUpdated(sdkUpdater.getHomePath());
603       return true;
604     }
605     catch (InvalidSdkException ignored) {
606     }
607     return false;
608   }
609
610   public static void notifyRemoteSdkSkeletonsFail(final InvalidSdkException e, @Nullable final Runnable restartAction) {
611     NotificationListener notificationListener;
612
613     if (e.getCause() instanceof VagrantNotStartedException) {
614       notificationListener =
615         new NotificationListener() {
616           @Override
617           public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
618             final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
619             if (manager != null) {
620               try {
621                 VagrantNotStartedException cause = (VagrantNotStartedException)e.getCause();
622                 manager.runVagrant(cause.getVagrantFolder(), cause.getMachineName());
623               }
624               catch (ExecutionException e1) {
625                 throw new RuntimeException(e1);
626               }
627             }
628             if (restartAction != null) {
629               restartAction.run();
630             }
631           }
632         };
633     }
634     else {
635       notificationListener = null;
636     }
637
638     Notifications.Bus.notify(
639       new Notification(
640         SKELETONS_TOPIC, "Couldn't refresh skeletons for remote interpreter",
641         e.getMessage() + "\n<a href=\"#\">Launch vagrant and refresh skeletons</a>",
642         NotificationType.WARNING,
643         notificationListener
644       )
645     );
646   }
647
648   /**
649    * In which root type built-in skeletons are put.
650    */
651   public static final OrderRootType BUILTIN_ROOT_TYPE = OrderRootType.CLASSES;
652
653   private final static Pattern PYTHON_NN_RE = Pattern.compile("python\\d\\.\\d.*");
654
655   public static void updateSdkRootsFromSysPath(PySdkUpdater sdkUpdater)
656     throws InvalidSdkException {
657     Application application = ApplicationManager.getApplication();
658     boolean not_in_unit_test_mode = (application != null && !application.isUnitTestMode());
659
660     String sdkHome = sdkUpdater.getHomePath();
661     assert sdkHome != null;
662     final String sep = File.separator;
663     // Add folders from sys.path
664     if (!PySdkUtil.isRemote(sdkUpdater.getSdk())) { //no sense to add roots of remote sdk
665       final List<String> paths = getSysPath(sdkHome);
666       if (paths.size() > 0) {
667         // add every path as root.
668         for (String path : paths) {
669           if (!path.contains(sep)) continue; // TODO: interpret possible 'special' paths reasonably
670           addSdkRoot(sdkUpdater, path);
671         }
672       }
673     }
674
675     PyUserSkeletonsUtil.addUserSkeletonsRoot(sdkUpdater);
676     addSkeletonsRoot(sdkUpdater, sdkHome);
677
678     if (not_in_unit_test_mode) {
679       File venv_root = getVirtualEnvRoot(sdkHome);
680       if (venv_root != null && venv_root.isDirectory()) {
681         File lib_root = new File(venv_root, "lib");
682         if (lib_root.isDirectory()) {
683           String[] inside = lib_root.list();
684           for (String s : inside) {
685             if (PYTHON_NN_RE.matcher(s).matches()) {
686               File py_lib_root = new File(lib_root, s);
687               if (new File(py_lib_root, "no-global-site-packages.txt").exists()) return; // don't add hardcoded paths
688             }
689           }
690         }
691       }
692       addHardcodedPaths(sdkUpdater);
693     }
694   }
695
696   public static void updateUserAddedPaths(PySdkUpdater sdkUpdater)
697     throws InvalidSdkException {
698     SdkAdditionalData data = sdkUpdater.getSdk().getSdkAdditionalData();
699     if (data instanceof PythonSdkAdditionalData) {
700       for (VirtualFile file : ((PythonSdkAdditionalData)data).getAddedPathFiles()) {
701         addSdkRoot(sdkUpdater, file);
702       }
703     }
704   }
705
706   private static void addSkeletonsRoot(@NotNull PySdkUpdater sdkUpdater, String sdkHome) {
707     @NonNls final String skeletonsPath = getSkeletonsPath(PathManager.getSystemPath(), sdkHome);
708     new File(skeletonsPath).mkdirs();
709     final VirtualFile builtins_root = LocalFileSystem.getInstance().refreshAndFindFileByPath(skeletonsPath);
710     assert builtins_root != null : "Cannot find skeletons path " + skeletonsPath + " in VFS";
711     sdkUpdater.addRoot(builtins_root, BUILTIN_ROOT_TYPE);
712   }
713
714   protected static void addHardcodedPaths(PySdkUpdater sdkUpdater) {
715     // Add python-django installed as package in Linux
716     // NOTE: fragile and arbitrary
717     if (SystemInfo.isLinux) {
718       final VirtualFile file = LocalFileSystem.getInstance().findFileByPath("/usr/lib/python-django");
719       if (file != null) {
720         sdkUpdater.addRoot(file, OrderRootType.CLASSES);
721       }
722     }
723   }
724
725   public static void addSdkRoot(PySdkUpdater sdkUpdater, String path) {
726     final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
727     if (file != null) {
728       addSdkRoot(sdkUpdater, file);
729     }
730     else {
731       LOG.info("Bogus sys.path entry " + path);
732     }
733   }
734
735   private static void addSdkRoot(@NotNull PySdkUpdater sdkUpdater, final @NotNull VirtualFile child) {
736     // NOTE: Files marked as library sources are not considered part of project source. Since the directory of the project the
737     // user is working on is included in PYTHONPATH with many configurations (e.g. virtualenv), we must not mark SDK paths as
738     // library sources, only as classes.
739     sdkUpdater.addRoot(getSdkRootVirtualFile(child), OrderRootType.CLASSES);
740   }
741
742   @NotNull
743   public static VirtualFile getSdkRootVirtualFile(@NotNull VirtualFile path) {
744     String suffix = path.getExtension();
745     if (suffix != null) {
746       suffix = suffix.toLowerCase(); // Why on earth empty suffix is null and not ""?
747     }
748     if ((!path.isDirectory()) && ("zip".equals(suffix) || "egg".equals(suffix))) {
749       // a .zip / .egg file must have its root extracted first
750       final VirtualFile jar = JarFileSystem.getInstance().getJarRootForLocalFile(path);
751       if (jar != null) {
752         return jar;
753       }
754     }
755     return path;
756   }
757
758   public static String getSkeletonsPath(String basePath, String sdkHome) {
759     String sep = File.separator;
760     return getSkeletonsRootPath(basePath) + sep + FileUtil.toSystemIndependentName(sdkHome).hashCode() + sep;
761   }
762
763   public static String getSkeletonsRootPath(String basePath) {
764     return basePath + File.separator + SKELETON_DIR_NAME;
765   }
766
767   @NotNull
768   public static List<String> getSysPath(String bin_path) throws InvalidSdkException {
769     String working_dir = new File(bin_path).getParent();
770     Application application = ApplicationManager.getApplication();
771     if (application != null && !application.isUnitTestMode()) {
772       return getSysPathsFromScript(bin_path);
773     }
774     else { // mock sdk
775       List<String> ret = new ArrayList<String>(1);
776       ret.add(working_dir);
777       return ret;
778     }
779   }
780
781   @NotNull
782   public static List<String> getSysPathsFromScript(@NotNull String binaryPath) throws InvalidSdkException {
783     // to handle the situation when PYTHONPATH contains ., we need to run the syspath script in the
784     // directory of the script itself - otherwise the dir in which we run the script (e.g. /usr/bin) will be added to SDK path
785     GeneralCommandLine cmd = PythonHelper.SYSPATH.newCommandLine(binaryPath, Lists.<String>newArrayList());
786     final ProcessOutput runResult = PySdkUtil.getProcessOutput(cmd, new File(binaryPath).getParent(),
787                                                                 getVirtualEnvExtraEnv(binaryPath), MINUTE);
788     if (!runResult.checkSuccess(LOG)) {
789       throw new InvalidSdkException(String.format("Failed to determine Python's sys.path value:\nSTDOUT: %s\nSTDERR: %s",
790                                                   runResult.getStdout(),
791                                                   runResult.getStderr()));
792     }
793     return runResult.getStdoutLines();
794   }
795
796   /**
797    * Returns a piece of env good as additional env for getProcessOutput.
798    */
799   @Nullable
800   public static Map<String, String> getVirtualEnvExtraEnv(@NotNull String binaryPath) {
801     final File root = getVirtualEnvRoot(binaryPath);
802     if (root != null) {
803       return ImmutableMap.of("PATH", root.toString());
804     }
805     return null;
806   }
807
808   @Nullable
809   public String getVersionString(final String sdkHome) {
810     final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdkHome);
811     return flavor != null ? flavor.getVersionString(sdkHome) : null;
812   }
813
814   public static List<Sdk> getAllSdks() {
815     return ProjectJdkTable.getInstance().getSdksOfType(getInstance());
816   }
817
818   @Nullable
819   public static Sdk findPythonSdk(@Nullable Module module) {
820     if (module == null) return null;
821     final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
822     if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) return sdk;
823     final Facet[] facets = FacetManager.getInstance(module).getAllFacets();
824     for (Facet facet : facets) {
825       final FacetConfiguration configuration = facet.getConfiguration();
826       if (configuration instanceof PythonFacetSettings) {
827         return ((PythonFacetSettings)configuration).getSdk();
828       }
829     }
830     return null;
831   }
832
833   @Nullable
834   public static Sdk findSdkByPath(@Nullable String path) {
835     if (path != null) {
836       return findSdkByPath(getAllSdks(), path);
837     }
838     return null;
839   }
840
841   @Nullable
842   public static Sdk findSdkByPath(List<Sdk> sdkList, @Nullable String path) {
843     if (path != null) {
844       for (Sdk sdk : sdkList) {
845         if (sdk != null && FileUtil.pathsEqual(path, sdk.getHomePath())) {
846           return sdk;
847         }
848       }
849     }
850     return null;
851   }
852
853   @NotNull
854   public static LanguageLevel getLanguageLevelForSdk(@Nullable Sdk sdk) {
855     if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) {
856       final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk);
857       if (flavor != null) {
858         return flavor.getLanguageLevel(sdk);
859       }
860     }
861     return LanguageLevel.getDefault();
862   }
863
864   public boolean isRootTypeApplicable(final OrderRootType type) {
865     return type == OrderRootType.CLASSES;
866   }
867
868   public boolean sdkHasValidPath(@NotNull Sdk sdk) {
869     if (PySdkUtil.isRemote(sdk)) {
870       return true;
871     }
872     VirtualFile homeDir = sdk.getHomeDirectory();
873     return homeDir != null && homeDir.isValid();
874   }
875
876   public static boolean isStdLib(VirtualFile vFile, Sdk pythonSdk) {
877     if (pythonSdk != null) {
878       final VirtualFile libDir = PyProjectScopeBuilder.findLibDir(pythonSdk);
879       if (libDir != null && VfsUtilCore.isAncestor(libDir, vFile, false)) {
880         return isNotSitePackages(vFile, libDir);
881       }
882       final VirtualFile venvLibDir = PyProjectScopeBuilder.findVirtualEnvLibDir(pythonSdk);
883       if (venvLibDir != null && VfsUtilCore.isAncestor(venvLibDir, vFile, false)) {
884         return isNotSitePackages(vFile, venvLibDir);
885       }
886       final VirtualFile skeletonsDir = PySdkUtil.findSkeletonsDir(pythonSdk);
887       if (skeletonsDir != null &&
888           Comparing.equal(vFile.getParent(), skeletonsDir)) {   // note: this will pick up some of the binary libraries not in packages
889         return true;
890       }
891     }
892     return false;
893   }
894
895   private static boolean isNotSitePackages(VirtualFile vFile, VirtualFile libDir) {
896     final VirtualFile sitePackages = libDir.findChild(PyNames.SITE_PACKAGES);
897     if (sitePackages != null && VfsUtilCore.isAncestor(sitePackages, vFile, false)) {
898       return false;
899     }
900     return true;
901   }
902
903   @Nullable
904   public static Sdk findPython2Sdk(@Nullable Module module) {
905     Sdk moduleSDK = findPythonSdk(module);
906     if (moduleSDK != null && !getLanguageLevelForSdk(moduleSDK).isPy3K()) {
907       return moduleSDK;
908     }
909     List<Sdk> allSdks = getAllSdks();
910     Collections.sort(allSdks, PreferredSdkComparator.INSTANCE);
911     for (Sdk sdk : allSdks) {
912       if (!getLanguageLevelForSdk(sdk).isPy3K()) {
913         return sdk;
914       }
915     }
916     return null;
917   }
918
919   @Nullable
920   public static Sdk findPython2Sdk(List<Sdk> sdks) {
921     Collections.sort(sdks, PreferredSdkComparator.INSTANCE);
922     for (Sdk sdk : sdks) {
923       if (!getLanguageLevelForSdk(sdk).isPy3K()) {
924         return sdk;
925       }
926     }
927     return null;
928   }
929
930   @Nullable
931   public static Sdk findLocalCPython(@Nullable Module module) {
932     Sdk moduleSDK = findPythonSdk(module);
933     if (moduleSDK != null && !isRemote(moduleSDK) && PythonSdkFlavor.getFlavor(moduleSDK) instanceof CPythonSdkFlavor) {
934       return moduleSDK;
935     }
936     List<Sdk> allSdks = getAllSdks();
937     Collections.sort(allSdks, PreferredSdkComparator.INSTANCE);
938     for (Sdk sdk : allSdks) {
939       if (!isRemote(sdk)) {
940         return sdk;
941       }
942     }
943     return null;
944   }
945
946   @Nullable
947   public static String getPythonExecutable(@NotNull String rootPath) {
948     final File rootFile = new File(rootPath);
949     if (rootFile.isFile()) {
950       return rootFile.getAbsolutePath();
951     }
952     for (String dir : DIRS_WITH_BINARY) {
953       final File subDir;
954       if (StringUtil.isEmpty(dir)) {
955         subDir = rootFile;
956       }
957       else {
958         subDir = new File(rootFile, dir);
959       }
960       if (!subDir.isDirectory()) {
961         continue;
962       }
963       for (String binaryName : getBinaryNames()) {
964         final File executable = new File(subDir, binaryName);
965         if (executable.isFile()) {
966           return executable.getAbsolutePath();
967         }
968       }
969     }
970     return null;
971   }
972
973   @Nullable
974   public static String getExecutablePath(@NotNull final String homeDirectory, @NotNull String name) {
975     File binPath = new File(homeDirectory);
976     File binDir = binPath.getParentFile();
977     if (binDir == null) return null;
978     File runner = new File(binDir, name);
979     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
980     runner = new File(new File(binDir, "Scripts"), name);
981     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
982     runner = new File(new File(binDir.getParentFile(), "Scripts"), name);
983     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
984     runner = new File(new File(binDir.getParentFile(), "local"), name);
985     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
986     runner = new File(new File(new File(binDir.getParentFile(), "local"), "bin"), name);
987     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
988
989     // if interpreter is a symlink
990     if (FileSystemUtil.isSymLink(homeDirectory)) {
991       String resolvedPath = FileSystemUtil.resolveSymLink(homeDirectory);
992       if (resolvedPath != null) {
993         return getExecutablePath(resolvedPath, name);
994       }
995     }
996     // Search in standard unix path
997     runner = new File(new File("/usr", "bin"), name);
998     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
999     runner = new File(new File(new File("/usr", "local"), "bin"), name);
1000     if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
1001     return null;
1002   }
1003
1004   private static String[] getBinaryNames() {
1005     if (SystemInfo.isUnix) {
1006       return UNIX_BINARY_NAMES;
1007     }
1008     else {
1009       return WIN_BINARY_NAMES;
1010     }
1011   }
1012
1013   public static boolean isIncompleteRemote(Sdk sdk) {
1014     if (PySdkUtil.isRemote(sdk)) {
1015       //noinspection ConstantConditions
1016       if (!((PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData()).isValid()) {
1017         return true;
1018       }
1019     }
1020     return false;
1021   }
1022
1023   public static boolean hasInvalidRemoteCredentials(Sdk sdk) {
1024     if (PySdkUtil.isRemote(sdk)) {
1025       final Ref<Boolean> result = Ref.create(false);
1026       //noinspection ConstantConditions
1027       ((PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData()).switchOnConnectionType(new RemoteSdkConnectionAcceptor() {
1028         @Override
1029         public void ssh(@NotNull RemoteCredentialsHolder cred) {
1030         }
1031
1032         @Override
1033         public void vagrant(@NotNull VagrantBasedCredentialsHolder cred) {
1034           result.set(StringUtil.isEmpty(cred.getVagrantFolder()));
1035         }
1036
1037         @Override
1038         public void deployment(@NotNull WebDeploymentCredentialsHolder cred) {
1039         }
1040       });
1041       return result.get();
1042     }
1043     return false;
1044   }
1045
1046   @Nullable
1047   public static Sdk getSdk(@NotNull final PsiElement element) {
1048     Module module = ModuleUtilCore.findModuleForPsiElement(element);
1049     if (module == null) {
1050       return null;
1051     }
1052     return ModuleRootManager.getInstance(module).getSdk();
1053   }
1054 }
1055