| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.jetbrains.python.sdk; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.intellij.execution.ExecutionException; |
| import com.intellij.execution.configurations.GeneralCommandLine; |
| import com.intellij.execution.process.ProcessOutput; |
| import com.intellij.facet.Facet; |
| import com.intellij.facet.FacetConfiguration; |
| import com.intellij.facet.FacetManager; |
| import com.intellij.ide.DataManager; |
| import com.intellij.notification.Notification; |
| import com.intellij.notification.NotificationListener; |
| import com.intellij.notification.NotificationType; |
| import com.intellij.notification.Notifications; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.PathManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileChooser.FileChooserDescriptor; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.progress.Task; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.projectRoots.*; |
| import com.intellij.openapi.roots.ModuleRootManager; |
| import com.intellij.openapi.roots.OrderRootType; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.SystemInfo; |
| import com.intellij.openapi.util.io.FileSystemUtil; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.text.CharFilter; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.JarFileSystem; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VfsUtilCore; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.reference.SoftReference; |
| import com.intellij.remote.CredentialsType; |
| import com.intellij.remote.RemoteSdkCredentialsHolder; |
| import com.intellij.remote.VagrantNotStartedException; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.NullableConsumer; |
| import com.jetbrains.python.PyBundle; |
| import com.jetbrains.python.PyNames; |
| import com.jetbrains.python.PythonFileType; |
| import com.jetbrains.python.PythonHelpersLocator; |
| import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil; |
| import com.jetbrains.python.facet.PythonFacetSettings; |
| import com.jetbrains.python.psi.LanguageLevel; |
| import com.jetbrains.python.psi.impl.PyBuiltinCache; |
| import com.jetbrains.python.psi.search.PyProjectScopeBuilder; |
| import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; |
| import com.jetbrains.python.remote.PythonRemoteInterpreterManager; |
| import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor; |
| import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; |
| import icons.PythonIcons; |
| import org.jdom.Element; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.HyperlinkEvent; |
| import java.awt.*; |
| import java.io.File; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.lang.ref.WeakReference; |
| import java.util.*; |
| import java.util.List; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author yole |
| */ |
| public class PythonSdkType extends SdkType { |
| public static final String REMOTE_SOURCES_DIR_NAME = "remote_sources"; |
| private static final Logger LOG = Logger.getInstance("#" + PythonSdkType.class.getName()); |
| private static final String[] WINDOWS_EXECUTABLE_SUFFIXES = new String[]{"cmd", "exe", "bat", "com"}; |
| |
| static final int MINUTE = 60 * 1000; // 60 seconds, used with script timeouts |
| @NonNls public static final String SKELETONS_TOPIC = "Skeletons"; |
| private static final String[] DIRS_WITH_BINARY = new String[]{"", "bin", "Scripts"}; |
| private static final String[] UNIX_BINARY_NAMES = new String[]{"jython", "pypy", "python"}; |
| private static final String[] WIN_BINARY_NAMES = new String[]{"jython.bat", "ipy.exe", "pypy.exe", "python.exe"}; |
| |
| private static final Key<WeakReference<Component>> SDK_CREATOR_COMPONENT_KEY = Key.create("#com.jetbrains.python.sdk.creatorComponent"); |
| |
| public static PythonSdkType getInstance() { |
| return SdkType.findInstance(PythonSdkType.class); |
| } |
| |
| public PythonSdkType() { |
| super("Python SDK"); |
| } |
| |
| protected PythonSdkType(@NonNls String name) { |
| super(name); |
| } |
| |
| public Icon getIcon() { |
| return PythonIcons.Python.Python; |
| } |
| |
| @NotNull |
| @Override |
| public String getHelpTopic() { |
| return "reference.project.structure.sdk.python"; |
| } |
| |
| public Icon getIconForAddAction() { |
| return PythonFileType.INSTANCE.getIcon(); |
| } |
| |
| /** |
| * Name of directory where skeleton files (despite the value) are stored. |
| */ |
| public static final String SKELETON_DIR_NAME = "python_stubs"; |
| |
| /** |
| * @return name of builtins skeleton file; for Python 2.x it is '{@code __builtins__.py}'. |
| */ |
| @NotNull |
| @NonNls |
| public static String getBuiltinsFileName(@NotNull Sdk sdk) { |
| final LanguageLevel level = getLanguageLevelForSdk(sdk); |
| return level.isOlderThan(LanguageLevel.PYTHON30) ? PyBuiltinCache.BUILTIN_FILE : PyBuiltinCache.BUILTIN_FILE_3K; |
| } |
| |
| @NonNls |
| @Nullable |
| public String suggestHomePath() { |
| final String pythonFromPath = findPythonInPath(); |
| if (pythonFromPath != null) { |
| return pythonFromPath; |
| } |
| for (PythonSdkFlavor flavor : PythonSdkFlavor.getApplicableFlavors()) { |
| TreeSet<String> candidates = createVersionSet(); |
| candidates.addAll(flavor.suggestHomePaths()); |
| if (!candidates.isEmpty()) { |
| // return latest version |
| String[] candidateArray = ArrayUtil.toStringArray(candidates); |
| return candidateArray[candidateArray.length - 1]; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static String findPythonInPath() { |
| final String defaultCommand = SystemInfo.isWindows ? "python.exe" : "python"; |
| final String path = System.getenv("PATH"); |
| for (String root : path.split(File.pathSeparator)) { |
| final File file = new File(root, defaultCommand); |
| if (file.exists()) { |
| try { |
| return file.getCanonicalPath(); |
| } |
| catch (IOException ignored) { |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Collection<String> suggestHomePaths() { |
| List<String> candidates = new ArrayList<String>(); |
| for (PythonSdkFlavor flavor : PythonSdkFlavor.getApplicableFlavors()) { |
| candidates.addAll(flavor.suggestHomePaths()); |
| } |
| return candidates; |
| } |
| |
| private static TreeSet<String> createVersionSet() { |
| return new TreeSet<String>(new Comparator<String>() { |
| public int compare(String o1, String o2) { |
| return findDigits(o1).compareTo(findDigits(o2)); |
| } |
| }); |
| } |
| |
| private static String findDigits(String s) { |
| int pos = StringUtil.findFirst(s, new CharFilter() { |
| public boolean accept(char ch) { |
| return Character.isDigit(ch); |
| } |
| }); |
| if (pos >= 0) { |
| return s.substring(pos); |
| } |
| return s; |
| } |
| |
| public static boolean hasValidSdk() { |
| for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) { |
| if (sdk.getSdkType() instanceof PythonSdkType) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public boolean isValidSdkHome(@Nullable final String path) { |
| return PythonSdkFlavor.getFlavor(path) != null; |
| } |
| |
| public static boolean isInvalid(@NotNull Sdk sdk) { |
| if (isRemote(sdk)) { |
| return false; |
| } |
| final VirtualFile interpreter = sdk.getHomeDirectory(); |
| return interpreter == null || !interpreter.exists(); |
| } |
| |
| public static boolean isRemote(@Nullable Sdk sdk) { |
| return PySdkUtil.isRemote(sdk); |
| } |
| |
| public static boolean isVagrant(@Nullable Sdk sdk) { |
| if (sdk != null && sdk.getSdkAdditionalData() instanceof PyRemoteSdkAdditionalDataBase) { |
| PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase) sdk.getSdkAdditionalData(); |
| |
| return data.getRemoteConnectionType() == CredentialsType.VAGRANT; |
| } |
| return false; |
| } |
| |
| public static boolean isRemote(@Nullable String sdkPath) { |
| return isRemote(findSdkByPath(sdkPath)); |
| } |
| |
| @Override |
| public FileChooserDescriptor getHomeChooserDescriptor() { |
| final boolean is_windows = SystemInfo.isWindows; |
| return new FileChooserDescriptor(true, false, false, false, false, false) { |
| @Override |
| public void validateSelectedFiles(VirtualFile[] files) throws Exception { |
| if (files.length != 0) { |
| if (!isValidSdkHome(files[0].getPath())) { |
| throw new Exception(PyBundle.message("sdk.error.invalid.interpreter.name.$0", files[0].getName())); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { |
| // TODO: add a better, customizable filtering |
| if (!file.isDirectory()) { |
| if (is_windows) { |
| String path = file.getPath(); |
| boolean looks_executable = false; |
| for (String ext : WINDOWS_EXECUTABLE_SUFFIXES) { |
| if (path.endsWith(ext)) { |
| looks_executable = true; |
| break; |
| } |
| } |
| return looks_executable && super.isFileVisible(file, showHiddenFiles); |
| } |
| } |
| return super.isFileVisible(file, showHiddenFiles); |
| } |
| }.withTitle(PyBundle.message("sdk.select.path")).withShowHiddenFiles(SystemInfo.isUnix); |
| } |
| |
| public boolean supportsCustomCreateUI() { |
| return true; |
| } |
| |
| public void showCustomCreateUI(SdkModel sdkModel, final JComponent parentComponent, final Consumer<Sdk> sdkCreatedCallback) { |
| Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent)); |
| final PointerInfo pointerInfo = MouseInfo.getPointerInfo(); |
| if (pointerInfo == null) return; |
| final Point point = pointerInfo.getLocation(); |
| PythonSdkDetailsStep |
| .show(project, sdkModel.getSdks(), null, parentComponent, point, new NullableConsumer<Sdk>() { |
| @Override |
| public void consume(@Nullable Sdk sdk) { |
| if (sdk != null) { |
| sdk.putUserData(SDK_CREATOR_COMPONENT_KEY, new WeakReference<Component>(parentComponent)); |
| sdkCreatedCallback.consume(sdk); |
| } |
| } |
| }); |
| } |
| |
| public static boolean isVirtualEnv(Sdk sdk) { |
| final String path = sdk.getHomePath(); |
| return path != null && getVirtualEnvRoot(path) != null; |
| } |
| |
| @Nullable |
| public Sdk getVirtualEnvBaseSdk(Sdk sdk) { |
| if (isVirtualEnv(sdk)) { |
| final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk); |
| final String version = getVersionString(sdk); |
| if (flavor != null && version != null) { |
| for (Sdk baseSdk : getAllSdks()) { |
| final PythonSdkFlavor baseFlavor = PythonSdkFlavor.getFlavor(baseSdk); |
| if (!isVirtualEnv(baseSdk) && flavor.equals(baseFlavor) && version.equals(getVersionString(baseSdk))) { |
| return baseSdk; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param binaryPath must point to a Python interpreter |
| * @return if the surroundings look like a virtualenv installation, its root is returned (normally the grandparent of binaryPath). |
| */ |
| @Nullable |
| public static File getVirtualEnvRoot(@NotNull final String binaryPath) { |
| final File bin = new File(binaryPath).getParentFile(); |
| if (bin != null) { |
| final String rootPath = bin.getParent(); |
| if (rootPath != null) { |
| final File root = new File(rootPath); |
| final File activateThis = new File(bin, "activate_this.py"); |
| // binaryPath should contain an 'activate' script, and root should have bin (with us) and include and libp |
| if (activateThis.exists()) { |
| final File activate = findExecutableFile(bin, "activate"); |
| if (activate != null) { |
| return root; |
| } |
| } |
| // Python 3.3 virtualenvs can be found as described in PEP 405 |
| final String pyVenvCfg = "pyvenv.cfg"; |
| if (new File(root, pyVenvCfg).exists() || new File(bin, pyVenvCfg).exists()) { |
| return root; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Finds a file that looks executable: an .exe or .cmd under windows, plain file under *nix. |
| * |
| * @param parent directory to look at |
| * @param name name of the executable without suffix |
| * @return File representing the executable, or null. |
| */ |
| @Nullable |
| public static File findExecutableFile(File parent, String name) { |
| if (SystemInfo.isWindows || SystemInfo.isOS2) { |
| for (String suffix : WINDOWS_EXECUTABLE_SUFFIXES) { |
| File file = new File(parent, name + "." + suffix); |
| if (file.exists()) return file; |
| } |
| } |
| else if (SystemInfo.isUnix) { |
| File file = new File(parent, name); |
| if (file.exists()) return file; |
| } |
| return null; |
| } |
| |
| /** |
| * Alters PATH so that a virtualenv is activated, if present. |
| * |
| * @param commandLine what to patch |
| * @param sdkHome home of SDK we're using |
| * @param passParentEnvironment iff true, include system paths in PATH |
| */ |
| public static void patchCommandLineForVirtualenv(GeneralCommandLine commandLine, String sdkHome, boolean passParentEnvironment) { |
| File virtualEnvRoot = getVirtualEnvRoot(sdkHome); |
| if (virtualEnvRoot != null) { |
| @NonNls final String PATH = "PATH"; |
| |
| // prepend virtualenv bin if it's not already on PATH |
| File bin = new File(virtualEnvRoot, "bin"); |
| if (!bin.exists()) { |
| bin = new File(virtualEnvRoot, "Scripts"); // on Windows |
| } |
| String virtualenvBin = bin.getPath(); |
| |
| Map<String, String> env = commandLine.getEnvironment(); |
| String pathValue; |
| if (env.containsKey(PATH)) { |
| pathValue = PythonEnvUtil.appendToPathEnvVar(env.get(PATH), virtualenvBin); |
| } |
| else if (passParentEnvironment) { |
| // append to PATH |
| pathValue = PythonEnvUtil.appendToPathEnvVar(System.getenv(PATH), virtualenvBin); |
| } |
| else { |
| pathValue = virtualenvBin; |
| } |
| env.put(PATH, pathValue); |
| } |
| } |
| |
| public String suggestSdkName(final String currentSdkName, final String sdkHome) { |
| String name = getVersionString(sdkHome); |
| return suggestSdkNameFromVersion(sdkHome, name); |
| } |
| |
| public static String suggestSdkNameFromVersion(String sdkHome, String version) { |
| sdkHome = FileUtil.toSystemDependentName(sdkHome); |
| final String shortHomeName = FileUtil.getLocationRelativeToUserHome(sdkHome); |
| if (version != null) { |
| File virtualEnvRoot = getVirtualEnvRoot(sdkHome); |
| if (virtualEnvRoot != null) { |
| version += " virtualenv at " + FileUtil.getLocationRelativeToUserHome(virtualEnvRoot.getAbsolutePath()); |
| } |
| else { |
| version += " (" + shortHomeName + ")"; |
| } |
| } |
| else { |
| version = "Unknown at " + shortHomeName; |
| } // last resort |
| return version; |
| } |
| |
| @Nullable |
| public AdditionalDataConfigurable createAdditionalDataConfigurable(final SdkModel sdkModel, final SdkModificator sdkModificator) { |
| return null; |
| } |
| |
| @Override |
| public void saveAdditionalData(@NotNull final SdkAdditionalData additionalData, @NotNull final Element additional) { |
| if (additionalData instanceof PythonSdkAdditionalData) { |
| ((PythonSdkAdditionalData)additionalData).save(additional); |
| } |
| } |
| |
| @Override |
| public SdkAdditionalData loadAdditionalData(@NotNull final Sdk currentSdk, final Element additional) { |
| if (RemoteSdkCredentialsHolder.isRemoteSdk(currentSdk.getHomePath())) { |
| PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); |
| if (manager != null) { |
| return manager.loadRemoteSdkData(currentSdk, additional); |
| } |
| } |
| return PythonSdkAdditionalData.load(currentSdk, additional); |
| } |
| |
| @Nullable |
| public static String findSkeletonsPath(Sdk sdk) { |
| final String[] urls = sdk.getRootProvider().getUrls(BUILTIN_ROOT_TYPE); |
| for (String url : urls) { |
| if (isSkeletonsPath(url)) { |
| return VfsUtilCore.urlToPath(url); |
| } |
| } |
| return null; |
| } |
| |
| public static boolean isSkeletonsPath(String path) { |
| return path.contains(SKELETON_DIR_NAME); |
| } |
| |
| @NonNls |
| public String getPresentableName() { |
| return "Python SDK"; |
| } |
| |
| @Override |
| public String sdkPath(VirtualFile homePath) { |
| String path = super.sdkPath(homePath); |
| PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(path); |
| if (flavor != null) { |
| VirtualFile sdkPath = flavor.getSdkPath(homePath); |
| if (sdkPath != null) { |
| return FileUtil.toSystemDependentName(sdkPath.getPath()); |
| } |
| } |
| return FileUtil.toSystemDependentName(path); |
| } |
| |
| public void setupSdkPaths(@NotNull final Sdk sdk) { |
| final Project project; |
| final WeakReference<Component> ownerComponentRef = sdk.getUserData(SDK_CREATOR_COMPONENT_KEY); |
| Component ownerComponent = SoftReference.dereference(ownerComponentRef); |
| if (ownerComponent != null) { |
| project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(ownerComponent)); |
| } |
| else { |
| project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()); |
| } |
| setupSdkPaths(sdk, project, ownerComponent); |
| } |
| |
| @Override |
| public boolean setupSdkPaths(Sdk sdk, SdkModel sdkModel) { |
| return true; // run setupSdkPaths only once (from PythonSdkDetailsStep). Skip this from showCustomCreateUI |
| } |
| |
| public static void setupSdkPaths(Sdk sdk, @Nullable Project project, @Nullable Component ownerComponent) { |
| final SdkModificator sdkModificator = sdk.getSdkModificator(); |
| final boolean success = setupSdkPaths(project, ownerComponent, sdk, sdkModificator); |
| if (success) { |
| sdkModificator.commitChanges(); |
| } |
| else { |
| Messages.showErrorDialog( |
| project, |
| PyBundle.message("MSG.cant.setup.sdk.$0", FileUtil.toSystemDependentName(sdk.getSdkModificator().getHomePath())), |
| PyBundle.message("MSG.title.bad.sdk") |
| ); |
| } |
| } |
| |
| public static boolean setupSdkPaths(@Nullable final Project project, |
| @Nullable final Component ownerComponent, |
| @NotNull final Sdk sdk, |
| @NotNull final SdkModificator sdkModificator) { |
| if (isRemote(sdk) && project == null && ownerComponent == null) { |
| LOG.error("For refreshing skeletons of remote SDK, either project or owner component must be specified"); |
| } |
| final ProgressManager progressManager = ProgressManager.getInstance(); |
| final Ref<Boolean> sdkPathsUpdatedRef = new Ref<Boolean>(false); |
| final Task.Modal setupTask = new Task.Modal(project, "Setting up library files for " + sdk.getName(), false) { |
| public void run(@NotNull final ProgressIndicator indicator) { |
| sdkModificator.removeAllRoots(); |
| try { |
| updateSdkRootsFromSysPath(sdk, sdkModificator, indicator); |
| updateUserAddedPaths(sdk, sdkModificator, indicator); |
| PythonSdkUpdater.getInstance().markAlreadyUpdated(sdk.getHomePath()); |
| sdkPathsUpdatedRef.set(true); |
| } |
| catch (InvalidSdkException ignored) { |
| } |
| } |
| }; |
| progressManager.run(setupTask); |
| final Boolean sdkPathsUpdated = sdkPathsUpdatedRef.get(); |
| final Application application = ApplicationManager.getApplication(); |
| if (sdkPathsUpdated && !application.isUnitTestMode()) { |
| application.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| progressManager.run(new Task.Backgroundable(project, PyBundle.message("sdk.gen.updating.skels"), false) { |
| @Override |
| public void run(@NotNull ProgressIndicator indicator) { |
| try { |
| final String skeletonsPath = getSkeletonsPath(PathManager.getSystemPath(), sdk.getHomePath()); |
| PythonSdkUpdater.updateSdk(project, ownerComponent, sdk, skeletonsPath); |
| } |
| catch (InvalidSdkException e) { |
| // If the SDK is invalid, the user should worry about the SDK itself, not about skeletons generation errors |
| if (isVagrant(sdk)) { |
| notifyRemoteSdkSkeletonsFail(e, new Runnable() { |
| @Override |
| public void run() { |
| setupSdkPaths(project, ownerComponent, sdk, sdkModificator); |
| } |
| }); |
| } |
| else if (!isInvalid(sdk)) { |
| LOG.error(e); |
| } |
| } |
| } |
| }); |
| } |
| }); |
| } |
| return sdkPathsUpdated; |
| } |
| |
| public static void notifyRemoteSdkSkeletonsFail(final InvalidSdkException e, @Nullable final Runnable restartAction) { |
| NotificationListener notificationListener; |
| |
| if (e.getCause() instanceof VagrantNotStartedException) { |
| notificationListener = |
| new NotificationListener() { |
| @Override |
| public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { |
| final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); |
| if (manager != null) { |
| try { |
| manager.runVagrant(((VagrantNotStartedException)e.getCause()).getVagrantFolder()); |
| } |
| catch (ExecutionException e1) { |
| throw new RuntimeException(e1); |
| } |
| } |
| if (restartAction != null) { |
| restartAction.run(); |
| } |
| } |
| }; |
| } |
| else { |
| notificationListener = null; |
| } |
| |
| Notifications.Bus.notify( |
| new Notification( |
| SKELETONS_TOPIC, "Couldn't refresh skeletons for remote interpreter", e.getMessage() + "\n<a href=\"#\">Launch vagrant and refresh skeletons</a>", |
| NotificationType.WARNING, |
| notificationListener |
| ) |
| ); |
| } |
| |
| /** |
| * In which root type built-in skeletons are put. |
| */ |
| public static final OrderRootType BUILTIN_ROOT_TYPE = OrderRootType.CLASSES; |
| |
| private final static Pattern PYTHON_NN_RE = Pattern.compile("python\\d\\.\\d.*"); |
| |
| public static void updateSdkRootsFromSysPath(Sdk sdk, SdkModificator sdkModificator, ProgressIndicator indicator) |
| throws InvalidSdkException { |
| Application application = ApplicationManager.getApplication(); |
| boolean not_in_unit_test_mode = (application != null && !application.isUnitTestMode()); |
| |
| String sdkHome = sdkModificator.getHomePath(); |
| assert sdkHome != null; |
| final String sep = File.separator; |
| // we have a number of lib dirs, those listed in python's sys.path |
| if (indicator != null) { |
| indicator.setText("Adding library roots"); |
| } |
| // Add folders from sys.path |
| if (!PySdkUtil.isRemote(sdk)) { //no sense to add roots of remote sdk |
| final List<String> paths = getSysPath(sdkHome); |
| if (paths.size() > 0) { |
| // add every path as root. |
| for (String path : paths) { |
| if (!path.contains(sep)) continue; // TODO: interpret possible 'special' paths reasonably |
| if (indicator != null) { |
| indicator.setText2(path); |
| } |
| addSdkRoot(sdkModificator, path); |
| } |
| } |
| } |
| |
| PyUserSkeletonsUtil.addUserSkeletonsRoot(sdkModificator); |
| addSkeletonsRoot(sdkModificator, sdkHome); |
| |
| if (not_in_unit_test_mode) { |
| File venv_root = getVirtualEnvRoot(sdkHome); |
| if (venv_root != null && venv_root.isDirectory()) { |
| File lib_root = new File(venv_root, "lib"); |
| if (lib_root.isDirectory()) { |
| String[] inside = lib_root.list(); |
| for (String s : inside) { |
| if (PYTHON_NN_RE.matcher(s).matches()) { |
| File py_lib_root = new File(lib_root, s); |
| String[] flag_files = py_lib_root.list(new FilenameFilter() { |
| @Override |
| public boolean accept(File file, String s) { |
| return "no-global-site-packages.txt".equals(s); |
| } |
| }); |
| if (flag_files != null) return; // don't add hardcoded paths |
| } |
| } |
| } |
| } |
| addHardcodedPaths(sdkModificator); |
| } |
| } |
| |
| public static void updateUserAddedPaths(Sdk sdk, SdkModificator sdkModificator, ProgressIndicator indicator) |
| throws InvalidSdkException { |
| if (indicator != null) { |
| indicator.setText("Adding user-added roots"); |
| } |
| SdkAdditionalData data = sdk.getSdkAdditionalData(); |
| if (data instanceof PythonSdkAdditionalData) { |
| for (VirtualFile file : ((PythonSdkAdditionalData)data).getAddedPathFiles()) { |
| addSdkRoot(sdkModificator, file); |
| } |
| } |
| } |
| |
| private static void addSkeletonsRoot(@NotNull SdkModificator sdkModificator, String sdkHome) { |
| @NonNls final String skeletonsPath = getSkeletonsPath(PathManager.getSystemPath(), sdkHome); |
| new File(skeletonsPath).mkdirs(); |
| final VirtualFile builtins_root = LocalFileSystem.getInstance().refreshAndFindFileByPath(skeletonsPath); |
| assert builtins_root != null : "Cannot find skeletons path " + skeletonsPath + " in VFS"; |
| sdkModificator.addRoot(builtins_root, BUILTIN_ROOT_TYPE); |
| } |
| |
| protected static void addHardcodedPaths(SdkModificator sdkModificator) { |
| // Add python-django installed as package in Linux |
| // NOTE: fragile and arbitrary |
| if (SystemInfo.isLinux) { |
| final VirtualFile file = LocalFileSystem.getInstance().findFileByPath("/usr/lib/python-django"); |
| if (file != null) { |
| sdkModificator.addRoot(file, OrderRootType.CLASSES); |
| } |
| } |
| } |
| |
| public static void addSdkRoot(SdkModificator sdkModificator, String path) { |
| final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); |
| if (file != null) { |
| addSdkRoot(sdkModificator, file); |
| } |
| else { |
| LOG.info("Bogus sys.path entry " + path); |
| } |
| } |
| |
| private static void addSdkRoot(@NotNull SdkModificator sdkModificator, @NotNull VirtualFile child) { |
| // NOTE: Files marked as library sources are not considered part of project source. Since the directory of the project the |
| // user is working on is included in PYTHONPATH with many configurations (e.g. virtualenv), we must not mark SDK paths as |
| // library sources, only as classes. |
| sdkModificator.addRoot(getSdkRootVirtualFile(child), OrderRootType.CLASSES); |
| } |
| |
| @NotNull |
| public static VirtualFile getSdkRootVirtualFile(@NotNull VirtualFile path) { |
| String suffix = path.getExtension(); |
| if (suffix != null) { |
| suffix = suffix.toLowerCase(); // Why on earth empty suffix is null and not ""? |
| } |
| if ((!path.isDirectory()) && ("zip".equals(suffix) || "egg".equals(suffix))) { |
| // a .zip / .egg file must have its root extracted first |
| final VirtualFile jar = JarFileSystem.getInstance().getJarRootForLocalFile(path); |
| if (jar != null) { |
| return jar; |
| } |
| } |
| return path; |
| } |
| |
| public static String getSkeletonsPath(String basePath, String sdkHome) { |
| String sep = File.separator; |
| return getSkeletonsRootPath(basePath) + sep + FileUtil.toSystemIndependentName(sdkHome).hashCode() + sep; |
| } |
| |
| public static String getSkeletonsRootPath(String basePath) { |
| return basePath + File.separator + SKELETON_DIR_NAME; |
| } |
| |
| @NotNull |
| public static List<String> getSysPath(String bin_path) throws InvalidSdkException { |
| String working_dir = new File(bin_path).getParent(); |
| Application application = ApplicationManager.getApplication(); |
| if (application != null && !application.isUnitTestMode()) { |
| return getSysPathsFromScript(bin_path); |
| } |
| else { // mock sdk |
| List<String> ret = new ArrayList<String>(1); |
| ret.add(working_dir); |
| return ret; |
| } |
| } |
| |
| @NotNull |
| public static List<String> getSysPathsFromScript(@NotNull String binaryPath) throws InvalidSdkException { |
| String scriptFile = PythonHelpersLocator.getHelperPath("syspath.py"); |
| // to handle the situation when PYTHONPATH contains ., we need to run the syspath script in the |
| // directory of the script itself - otherwise the dir in which we run the script (e.g. /usr/bin) will be added to SDK path |
| final ProcessOutput run_result = PySdkUtil.getProcessOutput(new File(scriptFile).getParent(), new String[]{binaryPath, scriptFile}, |
| getVirtualEnvExtraEnv(binaryPath), MINUTE); |
| if (!run_result.checkSuccess(LOG)) { |
| throw new InvalidSdkException(String.format("Failed to determine Python's sys.path value:\nSTDOUT: %s\nSTDERR: %s", |
| run_result.getStdout(), |
| run_result.getStderr())); |
| } |
| return run_result.getStdoutLines(); |
| } |
| |
| /** |
| * Returns a piece of env good as additional env for getProcessOutput. |
| */ |
| @Nullable |
| public static Map<String, String> getVirtualEnvExtraEnv(@NotNull String binaryPath) { |
| final File root = getVirtualEnvRoot(binaryPath); |
| if (root != null) { |
| return ImmutableMap.of("PATH", root.toString()); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public String getVersionString(final String sdkHome) { |
| final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdkHome); |
| return flavor != null ? flavor.getVersionString(sdkHome) : null; |
| } |
| |
| public static List<Sdk> getAllSdks() { |
| return ProjectJdkTable.getInstance().getSdksOfType(getInstance()); |
| } |
| |
| @Nullable |
| public static Sdk findPythonSdk(@Nullable Module module) { |
| if (module == null) return null; |
| final Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); |
| if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) return sdk; |
| final Facet[] facets = FacetManager.getInstance(module).getAllFacets(); |
| for (Facet facet : facets) { |
| final FacetConfiguration configuration = facet.getConfiguration(); |
| if (configuration instanceof PythonFacetSettings) { |
| return ((PythonFacetSettings)configuration).getSdk(); |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static Sdk findSdkByPath(@Nullable String path) { |
| if (path != null) { |
| return findSdkByPath(getAllSdks(), path); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static Sdk findSdkByPath(List<Sdk> sdkList, @Nullable String path) { |
| if (path != null) { |
| for (Sdk sdk : sdkList) { |
| if (sdk != null && FileUtil.pathsEqual(path, sdk.getHomePath())) { |
| return sdk; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public static LanguageLevel getLanguageLevelForSdk(@Nullable Sdk sdk) { |
| if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) { |
| final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk); |
| if (flavor != null) { |
| return flavor.getLanguageLevel(sdk); |
| } |
| } |
| return LanguageLevel.getDefault(); |
| } |
| |
| public boolean isRootTypeApplicable(final OrderRootType type) { |
| return type == OrderRootType.CLASSES; |
| } |
| |
| public boolean sdkHasValidPath(@NotNull Sdk sdk) { |
| if (PySdkUtil.isRemote(sdk)) { |
| return true; |
| } |
| VirtualFile homeDir = sdk.getHomeDirectory(); |
| return homeDir != null && homeDir.isValid(); |
| } |
| |
| public static boolean isStdLib(VirtualFile vFile, Sdk pythonSdk) { |
| if (pythonSdk != null) { |
| final VirtualFile libDir = PyProjectScopeBuilder.findLibDir(pythonSdk); |
| if (libDir != null && VfsUtilCore.isAncestor(libDir, vFile, false)) { |
| return isNotSitePackages(vFile, libDir); |
| } |
| final VirtualFile venvLibDir = PyProjectScopeBuilder.findVirtualEnvLibDir(pythonSdk); |
| if (venvLibDir != null && VfsUtilCore.isAncestor(venvLibDir, vFile, false)) { |
| return isNotSitePackages(vFile, venvLibDir); |
| } |
| final VirtualFile skeletonsDir = PySdkUtil.findSkeletonsDir(pythonSdk); |
| if (skeletonsDir != null && |
| Comparing.equal(vFile.getParent(), skeletonsDir)) { // note: this will pick up some of the binary libraries not in packages |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isNotSitePackages(VirtualFile vFile, VirtualFile libDir) { |
| final VirtualFile sitePackages = libDir.findChild(PyNames.SITE_PACKAGES); |
| if (sitePackages != null && VfsUtilCore.isAncestor(sitePackages, vFile, false)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Nullable |
| public static Sdk findPython2Sdk(@Nullable Module module) { |
| Sdk moduleSDK = findPythonSdk(module); |
| if (moduleSDK != null && !getLanguageLevelForSdk(moduleSDK).isPy3K()) { |
| return moduleSDK; |
| } |
| List<Sdk> allSdks = getAllSdks(); |
| Collections.sort(allSdks, PreferredSdkComparator.INSTANCE); |
| for (Sdk sdk : allSdks) { |
| if (!getLanguageLevelForSdk(sdk).isPy3K()) { |
| return sdk; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static Sdk findPython2Sdk(List<Sdk> sdks) { |
| Collections.sort(sdks, PreferredSdkComparator.INSTANCE); |
| for (Sdk sdk : sdks) { |
| if (!getLanguageLevelForSdk(sdk).isPy3K()) { |
| return sdk; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static Sdk findLocalCPython(@Nullable Module module) { |
| Sdk moduleSDK = findPythonSdk(module); |
| if (moduleSDK != null && !isRemote(moduleSDK) && PythonSdkFlavor.getFlavor(moduleSDK) instanceof CPythonSdkFlavor) { |
| return moduleSDK; |
| } |
| List<Sdk> allSdks = getAllSdks(); |
| Collections.sort(allSdks, PreferredSdkComparator.INSTANCE); |
| for (Sdk sdk : allSdks) { |
| if (!isRemote(sdk)) { |
| return sdk; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static String getPythonExecutable(@NotNull String rootPath) { |
| final File rootFile = new File(rootPath); |
| if (rootFile.isFile()) { |
| return rootFile.getAbsolutePath(); |
| } |
| for (String dir : DIRS_WITH_BINARY) { |
| final File subDir; |
| if (StringUtil.isEmpty(dir)) { |
| subDir = rootFile; |
| } |
| else { |
| subDir = new File(rootFile, dir); |
| } |
| if (!subDir.isDirectory()) { |
| continue; |
| } |
| for (String binaryName : getBinaryNames()) { |
| final File executable = new File(subDir, binaryName); |
| if (executable.isFile()) { |
| return executable.getAbsolutePath(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static String getExecutablePath(@NotNull final String homeDirectory, @NotNull String name) { |
| File binPath = new File(homeDirectory); |
| File binDir = binPath.getParentFile(); |
| if (binDir == null) return null; |
| File runner = new File(binDir, name); |
| if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath()); |
| runner = new File(new File(binDir, "Scripts"), name); |
| if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath()); |
| runner = new File(new File(binDir.getParentFile(), "Scripts"), name); |
| if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath()); |
| runner = new File(new File(binDir.getParentFile(), "local"), name); |
| if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath()); |
| runner = new File(new File(new File(binDir.getParentFile(), "local"), "bin"), name); |
| if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath()); |
| |
| // if interpreter is a symlink |
| if (FileSystemUtil.isSymLink(homeDirectory)) { |
| String resolvedPath = FileSystemUtil.resolveSymLink(homeDirectory); |
| if (resolvedPath != null) { |
| return getExecutablePath(resolvedPath, name); |
| } |
| } |
| // Search in standard unix path |
| runner = new File(new File("/usr", "bin"), name); |
| if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath()); |
| runner = new File(new File(new File("/usr", "local"), "bin"), name); |
| if (runner.exists()) return LocalFileSystem.getInstance().extractPresentableUrl(runner.getPath()); |
| return null; |
| } |
| |
| private static String[] getBinaryNames() { |
| if (SystemInfo.isUnix) { |
| return UNIX_BINARY_NAMES; |
| } |
| else { |
| return WIN_BINARY_NAMES; |
| } |
| } |
| |
| public static boolean isIncompleteRemote(Sdk sdk) { |
| if (PySdkUtil.isRemote(sdk)) { |
| //noinspection ConstantConditions |
| if (!((PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData()).isValid()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Nullable |
| public static Sdk getSdk(@NotNull final PsiElement element) { |
| Module module = ModuleUtilCore.findModuleForPsiElement(element); |
| if (module == null) { |
| return null; |
| } |
| return ModuleRootManager.getInstance(module).getSdk(); |
| } |
| } |
| |