2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.jetbrains.python.sdk;
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.packaging.PyCondaPackageManagerImpl;
73 import com.jetbrains.python.psi.LanguageLevel;
74 import com.jetbrains.python.psi.impl.PyBuiltinCache;
75 import com.jetbrains.python.psi.search.PyProjectScopeBuilder;
76 import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
77 import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
78 import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor;
79 import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
80 import icons.PythonIcons;
81 import org.jdom.Element;
82 import org.jetbrains.annotations.NonNls;
83 import org.jetbrains.annotations.NotNull;
84 import org.jetbrains.annotations.Nullable;
87 import javax.swing.event.HyperlinkEvent;
90 import java.io.IOException;
91 import java.lang.ref.WeakReference;
93 import java.util.List;
94 import java.util.regex.Pattern;
99 public class PythonSdkType extends SdkType {
100 public static final String REMOTE_SOURCES_DIR_NAME = "remote_sources";
101 private static final Logger LOG = Logger.getInstance("#" + PythonSdkType.class.getName());
102 private static final String[] WINDOWS_EXECUTABLE_SUFFIXES = new String[]{"cmd", "exe", "bat", "com"};
104 static final int MINUTE = 60 * 1000; // 60 seconds, used with script timeouts
105 @NonNls public static final String SKELETONS_TOPIC = "Skeletons";
106 private static final String[] DIRS_WITH_BINARY = new String[]{"", "bin", "Scripts"};
107 private static final String[] UNIX_BINARY_NAMES = new String[]{"jython", "pypy", "python"};
108 private static final String[] WIN_BINARY_NAMES = new String[]{"jython.bat", "ipy.exe", "pypy.exe", "python.exe"};
110 private static final Key<WeakReference<Component>> SDK_CREATOR_COMPONENT_KEY = Key.create("#com.jetbrains.python.sdk.creatorComponent");
112 public static PythonSdkType getInstance() {
113 return SdkType.findInstance(PythonSdkType.class);
116 public PythonSdkType() {
120 protected PythonSdkType(@NonNls String name) {
124 public Icon getIcon() {
125 return PythonIcons.Python.Python;
130 public String getHelpTopic() {
131 return "reference.project.structure.sdk.python";
134 public Icon getIconForAddAction() {
135 return PythonFileType.INSTANCE.getIcon();
139 * Name of directory where skeleton files (despite the value) are stored.
141 public static final String SKELETON_DIR_NAME = "python_stubs";
144 * @return name of builtins skeleton file; for Python 2.x it is '{@code __builtins__.py}'.
148 public static String getBuiltinsFileName(@NotNull Sdk sdk) {
149 final LanguageLevel level = getLanguageLevelForSdk(sdk);
150 return level.isOlderThan(LanguageLevel.PYTHON30) ? PyBuiltinCache.BUILTIN_FILE : PyBuiltinCache.BUILTIN_FILE_3K;
155 public String suggestHomePath() {
156 final String pythonFromPath = findPythonInPath();
157 if (pythonFromPath != null) {
158 return pythonFromPath;
160 for (PythonSdkFlavor flavor : PythonSdkFlavor.getApplicableFlavors()) {
161 TreeSet<String> candidates = createVersionSet();
162 candidates.addAll(flavor.suggestHomePaths());
163 if (!candidates.isEmpty()) {
164 // return latest version
165 String[] candidateArray = ArrayUtil.toStringArray(candidates);
166 return candidateArray[candidateArray.length - 1];
173 private static String findPythonInPath() {
174 final String defaultCommand = SystemInfo.isWindows ? "python.exe" : "python";
175 final String path = System.getenv("PATH");
176 for (String root : path.split(File.pathSeparator)) {
177 final File file = new File(root, defaultCommand);
180 return file.getCanonicalPath();
182 catch (IOException ignored) {
191 public Collection<String> suggestHomePaths() {
192 List<String> candidates = new ArrayList<String>();
193 for (PythonSdkFlavor flavor : PythonSdkFlavor.getApplicableFlavors()) {
194 candidates.addAll(flavor.suggestHomePaths());
199 private static TreeSet<String> createVersionSet() {
200 return new TreeSet<String>(new Comparator<String>() {
201 public int compare(String o1, String o2) {
202 return findDigits(o1).compareTo(findDigits(o2));
207 private static String findDigits(String s) {
208 int pos = StringUtil.findFirst(s, new CharFilter() {
209 public boolean accept(char ch) {
210 return Character.isDigit(ch);
214 return s.substring(pos);
219 public static boolean hasValidSdk() {
220 for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
221 if (sdk.getSdkType() instanceof PythonSdkType) {
228 public boolean isValidSdkHome(@Nullable final String path) {
229 return PythonSdkFlavor.getFlavor(path) != null;
232 public static boolean isInvalid(@NotNull Sdk sdk) {
236 final VirtualFile interpreter = sdk.getHomeDirectory();
237 return interpreter == null || !interpreter.exists();
240 public static boolean isRemote(@Nullable Sdk sdk) {
241 return PySdkUtil.isRemote(sdk);
244 public static boolean isVagrant(@Nullable Sdk sdk) {
245 if (sdk != null && sdk.getSdkAdditionalData() instanceof PyRemoteSdkAdditionalDataBase) {
246 PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData();
248 return data.getRemoteConnectionType() == CredentialsType.VAGRANT;
253 public static boolean isRemote(@Nullable String sdkPath) {
254 return isRemote(findSdkByPath(sdkPath));
258 public FileChooserDescriptor getHomeChooserDescriptor() {
259 final boolean isWindows = SystemInfo.isWindows;
260 return new FileChooserDescriptor(true, false, false, false, false, false) {
262 public void validateSelectedFiles(VirtualFile[] files) throws Exception {
263 if (files.length != 0) {
264 if (!isValidSdkHome(files[0].getPath())) {
265 throw new Exception(PyBundle.message("sdk.error.invalid.interpreter.name.$0", files[0].getName()));
271 public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
272 // TODO: add a better, customizable filtering
273 if (!file.isDirectory()) {
275 String path = file.getPath();
276 boolean looksExecutable = false;
277 for (String ext : WINDOWS_EXECUTABLE_SUFFIXES) {
278 if (path.endsWith(ext)) {
279 looksExecutable = true;
283 return looksExecutable && super.isFileVisible(file, showHiddenFiles);
286 return super.isFileVisible(file, showHiddenFiles);
288 }.withTitle(PyBundle.message("sdk.select.path")).withShowHiddenFiles(SystemInfo.isUnix);
291 public boolean supportsCustomCreateUI() {
295 public void showCustomCreateUI(SdkModel sdkModel, final JComponent parentComponent, final Consumer<Sdk> sdkCreatedCallback) {
296 Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent));
297 final PointerInfo pointerInfo = MouseInfo.getPointerInfo();
298 if (pointerInfo == null) return;
299 final Point point = pointerInfo.getLocation();
301 .show(project, sdkModel.getSdks(), null, parentComponent, point, new NullableConsumer<Sdk>() {
303 public void consume(@Nullable Sdk sdk) {
305 sdk.putUserData(SDK_CREATOR_COMPONENT_KEY, new WeakReference<Component>(parentComponent));
306 sdkCreatedCallback.consume(sdk);
312 public static boolean isVirtualEnv(Sdk sdk) {
313 final String path = sdk.getHomePath();
314 return path != null && getVirtualEnvRoot(path) != null;
317 public static boolean isCondaVirtualEnv(Sdk sdk) {
318 final String path = sdk.getHomePath();
319 return path != null && PyCondaPackageManagerImpl.isCondaVEnv(sdk);
323 public Sdk getVirtualEnvBaseSdk(Sdk sdk) {
324 if (isVirtualEnv(sdk)) {
325 final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk);
326 final String version = getVersionString(sdk);
327 if (flavor != null && version != null) {
328 for (Sdk baseSdk : getAllSdks()) {
329 final PythonSdkFlavor baseFlavor = PythonSdkFlavor.getFlavor(baseSdk);
330 if (!isVirtualEnv(baseSdk) && flavor.equals(baseFlavor) && version.equals(getVersionString(baseSdk))) {
340 * @param binaryPath must point to a Python interpreter
341 * @return if the surroundings look like a virtualenv installation, its root is returned (normally the grandparent of binaryPath).
344 public static File getVirtualEnvRoot(@NotNull final String binaryPath) {
345 final File bin = new File(binaryPath).getParentFile();
347 final String rootPath = bin.getParent();
348 if (rootPath != null) {
349 final File root = new File(rootPath);
350 final File activateThis = new File(bin, "activate_this.py");
351 // binaryPath should contain an 'activate' script, and root should have bin (with us) and include and libp
352 if (activateThis.exists()) {
353 final File activate = findExecutableFile(bin, "activate");
354 if (activate != null) {
358 // Python 3.3 virtualenvs can be found as described in PEP 405
359 final String pyVenvCfg = "pyvenv.cfg";
360 if (new File(root, pyVenvCfg).exists() || new File(bin, pyVenvCfg).exists()) {
369 * Finds a file that looks executable: an .exe or .cmd under windows, plain file under *nix.
371 * @param parent directory to look at
372 * @param name name of the executable without suffix
373 * @return File representing the executable, or null.
376 public static File findExecutableFile(File parent, String name) {
377 if (SystemInfo.isWindows || SystemInfo.isOS2) {
378 for (String suffix : WINDOWS_EXECUTABLE_SUFFIXES) {
379 File file = new File(parent, name + "." + suffix);
380 if (file.exists()) return file;
383 else if (SystemInfo.isUnix) {
384 File file = new File(parent, name);
385 if (file.exists()) return file;
391 * Alters PATH so that a virtualenv is activated, if present.
393 * @param commandLine what to patch
394 * @param sdkHome home of SDK we're using
395 * @param passParentEnvironment iff true, include system paths in PATH
397 public static void patchCommandLineForVirtualenv(GeneralCommandLine commandLine, String sdkHome, boolean passParentEnvironment) {
398 File virtualEnvRoot = getVirtualEnvRoot(sdkHome);
399 if (virtualEnvRoot != null) {
400 @NonNls final String PATH = "PATH";
402 // prepend virtualenv bin if it's not already on PATH
403 File bin = new File(virtualEnvRoot, "bin");
405 bin = new File(virtualEnvRoot, "Scripts"); // on Windows
407 String virtualenvBin = bin.getPath();
409 Map<String, String> env = commandLine.getEnvironment();
411 if (env.containsKey(PATH)) {
412 pathValue = PythonEnvUtil.appendToPathEnvVar(env.get(PATH), virtualenvBin);
414 else if (passParentEnvironment) {
416 pathValue = PythonEnvUtil.appendToPathEnvVar(System.getenv(PATH), virtualenvBin);
419 pathValue = virtualenvBin;
421 env.put(PATH, pathValue);
425 public String suggestSdkName(final String currentSdkName, final String sdkHome) {
426 String name = getVersionString(sdkHome);
427 return suggestSdkNameFromVersion(sdkHome, name);
430 public static String suggestSdkNameFromVersion(String sdkHome, String version) {
431 sdkHome = FileUtil.toSystemDependentName(sdkHome);
432 final String shortHomeName = FileUtil.getLocationRelativeToUserHome(sdkHome);
433 if (version != null) {
434 File virtualEnvRoot = getVirtualEnvRoot(sdkHome);
435 if (virtualEnvRoot != null) {
436 version += " virtualenv at " + FileUtil.getLocationRelativeToUserHome(virtualEnvRoot.getAbsolutePath());
439 version += " (" + shortHomeName + ")";
443 version = "Unknown at " + shortHomeName;
449 public AdditionalDataConfigurable createAdditionalDataConfigurable(final SdkModel sdkModel, final SdkModificator sdkModificator) {
454 public void saveAdditionalData(@NotNull final SdkAdditionalData additionalData, @NotNull final Element additional) {
455 if (additionalData instanceof PythonSdkAdditionalData) {
456 ((PythonSdkAdditionalData)additionalData).save(additional);
461 public SdkAdditionalData loadAdditionalData(@NotNull final Sdk currentSdk, final Element additional) {
462 if (RemoteSdkCredentialsHolder.isRemoteSdk(currentSdk.getHomePath())) {
463 PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
464 if (manager != null) {
465 return manager.loadRemoteSdkData(currentSdk, additional);
468 return PythonSdkAdditionalData.load(currentSdk, additional);
471 public static boolean isSkeletonsPath(String path) {
472 return path.contains(SKELETON_DIR_NAME);
476 public String getPresentableName() {
481 public String sdkPath(VirtualFile homePath) {
482 String path = super.sdkPath(homePath);
483 PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(path);
484 if (flavor != null) {
485 VirtualFile sdkPath = flavor.getSdkPath(homePath);
486 if (sdkPath != null) {
487 return FileUtil.toSystemDependentName(sdkPath.getPath());
490 return FileUtil.toSystemDependentName(path);
493 public void setupSdkPaths(@NotNull final Sdk sdk) {
494 final Project project;
495 final WeakReference<Component> ownerComponentRef = sdk.getUserData(SDK_CREATOR_COMPONENT_KEY);
496 final Component ownerComponent = SoftReference.dereference(ownerComponentRef);
497 if (ownerComponent != null) {
498 project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(ownerComponent));
501 project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
504 DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() {
507 setupSdkPaths(sdk, project, ownerComponent);
513 public boolean setupSdkPaths(Sdk sdk, SdkModel sdkModel) {
514 return true; // run setupSdkPaths only once (from PythonSdkDetailsStep). Skip this from showCustomCreateUI
517 public static void setupSdkPaths(@NotNull final Sdk sdk,
518 @Nullable final Project project,
519 @Nullable final Component ownerComponent,
520 @NotNull final SdkModificator sdkModificator) {
521 doSetupSdkPaths(project, ownerComponent, PySdkUpdater.fromSdkModificator(sdk, sdkModificator));
525 public static void setupSdkPaths(final Sdk sdk, @Nullable final Project project, @Nullable final Component ownerComponent) {
526 ApplicationManager.getApplication().invokeLater(new Runnable() {
530 final boolean success = doSetupSdkPaths(project, ownerComponent, PySdkUpdater.fromSdkPath(sdk.getHomePath()));
533 Messages.showErrorDialog(
535 PyBundle.message("MSG.cant.setup.sdk.$0", FileUtil.toSystemDependentName(sdk.getSdkModificator().getHomePath())),
536 PyBundle.message("MSG.title.bad.sdk")
540 catch (PySdkUpdater.PySdkNotFoundException e) {
541 // sdk was removed from sdk table so no need to setup paths
544 }, ModalityState.NON_MODAL);
547 private static boolean doSetupSdkPaths(@Nullable final Project project,
548 @Nullable final Component ownerComponent,
549 @NotNull final PySdkUpdater sdkUpdater) {
550 if (isRemote(sdkUpdater.getSdk()) && project == null && ownerComponent == null) {
551 LOG.error("For refreshing skeletons of remote SDK, either project or owner component must be specified");
553 final ProgressManager progressManager = ProgressManager.getInstance();
554 boolean sdkPathsUpdated = UIUtil.<Boolean>invokeAndWaitIfNeeded(
555 new Computable<Boolean>() {
557 public Boolean compute() {
558 return updateSdkPaths(sdkUpdater);
563 final Application application = ApplicationManager.getApplication();
564 if (sdkPathsUpdated && !application.isUnitTestMode()) {
565 application.invokeLater(new Runnable() {
568 progressManager.run(new Task.Backgroundable(project, PyBundle.message("sdk.gen.updating.skels"), false) {
570 public void run(@NotNull ProgressIndicator indicator) {
572 PythonSdkUpdater.updateSdk(project, ownerComponent, sdkUpdater);
574 catch (InvalidSdkException e) {
575 // If the SDK is invalid, the user should worry about the SDK itself, not about skeletons generation errors
576 if (isVagrant(sdkUpdater.getSdk())) {
577 notifyRemoteSdkSkeletonsFail(e, new Runnable() {
580 setupSdkPaths(sdkUpdater.getSdk(), project, ownerComponent);
584 else if (!isInvalid(sdkUpdater.getSdk())) {
593 return sdkPathsUpdated;
597 public static Boolean updateSdkPaths(@NotNull PySdkUpdater sdkUpdater) {
598 sdkUpdater.modifySdk(new PySdkUpdater.SdkModificationProcessor() {
600 public void process(@NotNull Sdk sdk,
601 @NotNull SdkModificator sdkModificator) {
602 sdkModificator.removeAllRoots();
606 updateSdkRootsFromSysPath(sdkUpdater);
607 updateUserAddedPaths(sdkUpdater);
608 PythonSdkUpdater.getInstance()
609 .markAlreadyUpdated(sdkUpdater.getHomePath());
612 catch (InvalidSdkException ignored) {
617 public static void notifyRemoteSdkSkeletonsFail(final InvalidSdkException e, @Nullable final Runnable restartAction) {
618 NotificationListener notificationListener;
620 if (e.getCause() instanceof VagrantNotStartedException) {
621 notificationListener =
622 new NotificationListener() {
624 public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
625 final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
626 if (manager != null) {
628 VagrantNotStartedException cause = (VagrantNotStartedException)e.getCause();
629 manager.runVagrant(cause.getVagrantFolder(), cause.getMachineName());
631 catch (ExecutionException e1) {
632 throw new RuntimeException(e1);
635 if (restartAction != null) {
642 notificationListener = null;
645 Notifications.Bus.notify(
647 SKELETONS_TOPIC, "Couldn't refresh skeletons for remote interpreter",
648 e.getMessage() + "\n<a href=\"#\">Launch vagrant and refresh skeletons</a>",
649 NotificationType.WARNING,
656 * In which root type built-in skeletons are put.
658 public static final OrderRootType BUILTIN_ROOT_TYPE = OrderRootType.CLASSES;
660 private final static Pattern PYTHON_NN_RE = Pattern.compile("python\\d\\.\\d.*");
662 public static void updateSdkRootsFromSysPath(PySdkUpdater sdkUpdater)
663 throws InvalidSdkException {
664 Application application = ApplicationManager.getApplication();
665 boolean not_in_unit_test_mode = (application != null && !application.isUnitTestMode());
667 String sdkHome = sdkUpdater.getHomePath();
668 assert sdkHome != null;
669 final String sep = File.separator;
670 // Add folders from sys.path
671 if (!PySdkUtil.isRemote(sdkUpdater.getSdk())) { //no sense to add roots of remote sdk
672 final List<String> paths = getSysPath(sdkHome);
673 if (paths.size() > 0) {
674 // add every path as root.
675 for (String path : paths) {
676 if (!path.contains(sep)) continue; // TODO: interpret possible 'special' paths reasonably
677 addSdkRoot(sdkUpdater, path);
682 PyUserSkeletonsUtil.addUserSkeletonsRoot(sdkUpdater);
683 addSkeletonsRoot(sdkUpdater, sdkHome);
685 if (not_in_unit_test_mode) {
686 File venv_root = getVirtualEnvRoot(sdkHome);
687 if (venv_root != null && venv_root.isDirectory()) {
688 File lib_root = new File(venv_root, "lib");
689 if (lib_root.isDirectory()) {
690 String[] inside = lib_root.list();
691 for (String s : inside) {
692 if (PYTHON_NN_RE.matcher(s).matches()) {
693 File py_lib_root = new File(lib_root, s);
694 if (new File(py_lib_root, "no-global-site-packages.txt").exists()) return; // don't add hardcoded paths
699 addHardcodedPaths(sdkUpdater);
703 public static void updateUserAddedPaths(PySdkUpdater sdkUpdater)
704 throws InvalidSdkException {
705 SdkAdditionalData data = sdkUpdater.getSdk().getSdkAdditionalData();
706 if (data instanceof PythonSdkAdditionalData) {
707 for (VirtualFile file : ((PythonSdkAdditionalData)data).getAddedPathFiles()) {
708 addSdkRoot(sdkUpdater, file);
713 private static void addSkeletonsRoot(@NotNull PySdkUpdater sdkUpdater, String sdkHome) {
714 @NonNls final String skeletonsPath = getSkeletonsPath(PathManager.getSystemPath(), sdkHome);
715 new File(skeletonsPath).mkdirs();
716 final VirtualFile builtins_root = LocalFileSystem.getInstance().refreshAndFindFileByPath(skeletonsPath);
717 assert builtins_root != null : "Cannot find skeletons path " + skeletonsPath + " in VFS";
718 sdkUpdater.addRoot(builtins_root, BUILTIN_ROOT_TYPE);
721 protected static void addHardcodedPaths(PySdkUpdater sdkUpdater) {
722 // Add python-django installed as package in Linux
723 // NOTE: fragile and arbitrary
724 if (SystemInfo.isLinux) {
725 final VirtualFile file = LocalFileSystem.getInstance().findFileByPath("/usr/lib/python-django");
727 sdkUpdater.addRoot(file, OrderRootType.CLASSES);
732 public static void addSdkRoot(PySdkUpdater sdkUpdater, String path) {
733 final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
735 addSdkRoot(sdkUpdater, file);
738 LOG.info("Bogus sys.path entry " + path);
742 private static void addSdkRoot(@NotNull PySdkUpdater sdkUpdater, final @NotNull VirtualFile child) {
743 // NOTE: Files marked as library sources are not considered part of project source. Since the directory of the project the
744 // user is working on is included in PYTHONPATH with many configurations (e.g. virtualenv), we must not mark SDK paths as
745 // library sources, only as classes.
746 sdkUpdater.addRoot(getSdkRootVirtualFile(child), OrderRootType.CLASSES);
750 public static VirtualFile getSdkRootVirtualFile(@NotNull VirtualFile path) {
751 String suffix = path.getExtension();
752 if (suffix != null) {
753 suffix = suffix.toLowerCase(); // Why on earth empty suffix is null and not ""?
755 if ((!path.isDirectory()) && ("zip".equals(suffix) || "egg".equals(suffix))) {
756 // a .zip / .egg file must have its root extracted first
757 final VirtualFile jar = JarFileSystem.getInstance().getJarRootForLocalFile(path);
766 * Returns skeletons location on the local machine. Independent of SDK credentials type (e.g. ssh, Vagrant, Docker or else).
768 public static String getSkeletonsPath(String basePath, String sdkHome) {
769 String sep = File.separator;
770 return getSkeletonsRootPath(basePath) + sep + FileUtil.toSystemIndependentName(sdkHome).hashCode() + sep;
773 public static String getSkeletonsRootPath(String basePath) {
774 return basePath + File.separator + SKELETON_DIR_NAME;
778 public static List<String> getSysPath(String bin_path) throws InvalidSdkException {
779 String working_dir = new File(bin_path).getParent();
780 Application application = ApplicationManager.getApplication();
781 if (application != null && !application.isUnitTestMode()) {
782 return getSysPathsFromScript(bin_path);
785 List<String> ret = new ArrayList<String>(1);
786 ret.add(working_dir);
792 public static List<String> getSysPathsFromScript(@NotNull String binaryPath) throws InvalidSdkException {
793 // to handle the situation when PYTHONPATH contains ., we need to run the syspath script in the
794 // directory of the script itself - otherwise the dir in which we run the script (e.g. /usr/bin) will be added to SDK path
795 GeneralCommandLine cmd = PythonHelper.SYSPATH.newCommandLine(binaryPath, Lists.<String>newArrayList());
796 final ProcessOutput runResult = PySdkUtil.getProcessOutput(cmd, new File(binaryPath).getParent(),
797 getVirtualEnvExtraEnv(binaryPath), MINUTE);
798 if (!runResult.checkSuccess(LOG)) {
799 throw new InvalidSdkException(String.format("Failed to determine Python's sys.path value:\nSTDOUT: %s\nSTDERR: %s",
800 runResult.getStdout(),
801 runResult.getStderr()));
803 return runResult.getStdoutLines();
807 * Returns a piece of env good as additional env for getProcessOutput.
810 public static Map<String, String> getVirtualEnvExtraEnv(@NotNull String binaryPath) {
811 final File root = getVirtualEnvRoot(binaryPath);
813 return ImmutableMap.of("PATH", root.toString());
820 public String getVersionString(@NotNull Sdk sdk) {
822 final PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData();
824 String versionString = data.getVersionString();
825 if (StringUtil.isEmpty(versionString)) {
826 final PythonRemoteInterpreterManager remoteInterpreterManager = PythonRemoteInterpreterManager.getInstance();
827 if (remoteInterpreterManager != null) {
830 remoteInterpreterManager.getInterpreterVersion(null, data);
832 catch (Exception e) {
833 LOG.warn("Couldn't get interpreter version:" + e.getMessage(), e);
834 versionString = "undefined";
837 data.setVersionString(versionString);
839 return versionString;
842 return getVersionString(sdk.getHomePath());
847 public String getVersionString(final String sdkHome) {
848 final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdkHome);
849 return flavor != null ? flavor.getVersionString(sdkHome) : null;
852 public static List<Sdk> getAllSdks() {
853 return ProjectJdkTable.getInstance().getSdksOfType(getInstance());
857 public static Sdk findPythonSdk(@Nullable Module module) {
858 if (module == null) return null;
859 final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
860 if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) return sdk;
861 final Facet[] facets = FacetManager.getInstance(module).getAllFacets();
862 for (Facet facet : facets) {
863 final FacetConfiguration configuration = facet.getConfiguration();
864 if (configuration instanceof PythonFacetSettings) {
865 return ((PythonFacetSettings)configuration).getSdk();
872 public static Sdk findSdkByPath(@Nullable String path) {
874 return findSdkByPath(getAllSdks(), path);
880 public static Sdk findSdkByPath(List<Sdk> sdkList, @Nullable String path) {
882 for (Sdk sdk : sdkList) {
883 if (sdk != null && FileUtil.pathsEqual(path, sdk.getHomePath())) {
892 public static LanguageLevel getLanguageLevelForSdk(@Nullable Sdk sdk) {
893 if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) {
894 final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk);
895 if (flavor != null) {
896 return flavor.getLanguageLevel(sdk);
899 return LanguageLevel.getDefault();
902 public boolean isRootTypeApplicable(final OrderRootType type) {
903 return type == OrderRootType.CLASSES;
906 public boolean sdkHasValidPath(@NotNull Sdk sdk) {
907 if (PySdkUtil.isRemote(sdk)) {
910 VirtualFile homeDir = sdk.getHomeDirectory();
911 return homeDir != null && homeDir.isValid();
914 public static boolean isStdLib(VirtualFile vFile, Sdk pythonSdk) {
915 if (pythonSdk != null) {
916 final VirtualFile libDir = PyProjectScopeBuilder.findLibDir(pythonSdk);
917 if (libDir != null && VfsUtilCore.isAncestor(libDir, vFile, false)) {
918 return isNotSitePackages(vFile, libDir);
920 final VirtualFile venvLibDir = PyProjectScopeBuilder.findVirtualEnvLibDir(pythonSdk);
921 if (venvLibDir != null && VfsUtilCore.isAncestor(venvLibDir, vFile, false)) {
922 return isNotSitePackages(vFile, venvLibDir);
924 final VirtualFile skeletonsDir = PySdkUtil.findSkeletonsDir(pythonSdk);
925 if (skeletonsDir != null &&
926 Comparing.equal(vFile.getParent(), skeletonsDir)) { // note: this will pick up some of the binary libraries not in packages
933 private static boolean isNotSitePackages(VirtualFile vFile, VirtualFile libDir) {
934 final VirtualFile sitePackages = libDir.findChild(PyNames.SITE_PACKAGES);
935 if (sitePackages != null && VfsUtilCore.isAncestor(sitePackages, vFile, false)) {
942 public static Sdk findPython2Sdk(@Nullable Module module) {
943 Sdk moduleSDK = findPythonSdk(module);
944 if (moduleSDK != null && !getLanguageLevelForSdk(moduleSDK).isPy3K()) {
947 List<Sdk> allSdks = getAllSdks();
948 Collections.sort(allSdks, PreferredSdkComparator.INSTANCE);
949 for (Sdk sdk : allSdks) {
950 if (!getLanguageLevelForSdk(sdk).isPy3K()) {
958 public static Sdk findPython2Sdk(List<Sdk> sdks) {
959 Collections.sort(sdks, PreferredSdkComparator.INSTANCE);
960 for (Sdk sdk : sdks) {
961 if (!getLanguageLevelForSdk(sdk).isPy3K()) {
969 public static Sdk findLocalCPython(@Nullable Module module) {
970 Sdk moduleSDK = findPythonSdk(module);
971 if (moduleSDK != null && !isRemote(moduleSDK) && PythonSdkFlavor.getFlavor(moduleSDK) instanceof CPythonSdkFlavor) {
974 List<Sdk> allSdks = getAllSdks();
975 Collections.sort(allSdks, PreferredSdkComparator.INSTANCE);
976 for (Sdk sdk : allSdks) {
977 if (!isRemote(sdk)) {
985 public static String getPythonExecutable(@NotNull String rootPath) {
986 final File rootFile = new File(rootPath);
987 if (rootFile.isFile()) {
988 return rootFile.getAbsolutePath();
990 for (String dir : DIRS_WITH_BINARY) {
992 if (StringUtil.isEmpty(dir)) {
996 subDir = new File(rootFile, dir);
998 if (!subDir.isDirectory()) {
1001 for (String binaryName : getBinaryNames()) {
1002 final File executable = new File(subDir, binaryName);
1003 if (executable.isFile()) {
1004 return executable.getAbsolutePath();
1012 public static String getExecutablePath(@NotNull final String homeDirectory, @NotNull String name) {
1013 File binPath = new File(homeDirectory);
1014 File binDir = binPath.getParentFile();
1015 if (binDir == null) return null;
1016 File runner = new File(binDir, name);
1017 if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
1018 runner = new File(new File(binDir, "Scripts"), name);
1019 if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
1020 runner = new File(new File(binDir.getParentFile(), "Scripts"), name);
1021 if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
1022 runner = new File(new File(binDir.getParentFile(), "local"), name);
1023 if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
1024 runner = new File(new File(new File(binDir.getParentFile(), "local"), "bin"), name);
1025 if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
1027 // if interpreter is a symlink
1028 if (FileSystemUtil.isSymLink(homeDirectory)) {
1029 String resolvedPath = FileSystemUtil.resolveSymLink(homeDirectory);
1030 if (resolvedPath != null) {
1031 return getExecutablePath(resolvedPath, name);
1034 // Search in standard unix path
1035 runner = new File(new File("/usr", "bin"), name);
1036 if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
1037 runner = new File(new File(new File("/usr", "local"), "bin"), name);
1038 if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath());
1042 private static String[] getBinaryNames() {
1043 if (SystemInfo.isUnix) {
1044 return UNIX_BINARY_NAMES;
1047 return WIN_BINARY_NAMES;
1051 public static boolean isIncompleteRemote(Sdk sdk) {
1052 if (PySdkUtil.isRemote(sdk)) {
1053 //noinspection ConstantConditions
1054 if (!((PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData()).isValid()) {
1061 public static boolean hasInvalidRemoteCredentials(Sdk sdk) {
1062 if (PySdkUtil.isRemote(sdk)) {
1063 final Ref<Boolean> result = Ref.create(false);
1064 //noinspection ConstantConditions
1065 ((PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData()).switchOnConnectionType(new RemoteSdkConnectionAcceptor() {
1067 public void ssh(@NotNull RemoteCredentialsHolder cred) {
1071 public void vagrant(@NotNull VagrantBasedCredentialsHolder cred) {
1072 result.set(StringUtil.isEmpty(cred.getVagrantFolder()));
1076 public void deployment(@NotNull WebDeploymentCredentialsHolder cred) {
1080 public void docker(@NotNull DockerCredentialsHolder credentials) {
1083 return result.get();
1089 public static Sdk getSdk(@NotNull final PsiElement element) {
1090 Module module = ModuleUtilCore.findModuleForPsiElement(element);
1091 if (module == null) {
1094 return ModuleRootManager.getInstance(module).getSdk();