PY-31048 Restore auto-detection of virtualenvs inside projects upon opening them for the first time

We used to detect them using an ad-hoc solution in the `PythonSdkConfigurator`. Since we switched to re-using
`detectVirtualEnvs()` which is based on `PythonSdkFlavor.suggestHomePaths()`, the detection stopped working. The reason
was the we didn't have the project in the default data context during its creation and it was needed as a starting
point for searching for virtualenvs. We now pass an explicit module for it.
This commit is contained in:
Andrey Vlasovskikh
2018-08-03 21:00:30 +03:00
parent fbff5e0cbb
commit d37d561e98
15 changed files with 51 additions and 37 deletions
@@ -39,8 +39,8 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
private fun findExistingSystemWideSdk(existingSdks: List<Sdk>) =
existingSdks.filter { it.isSystemWide }.sortedWith(PreferredSdkComparator.INSTANCE).firstOrNull()
private fun findDetectedSystemWideSdk(existingSdks: List<Sdk>) =
detectSystemWideSdks(existingSdks).firstOrNull()
private fun findDetectedSystemWideSdk(module: Module?, existingSdks: List<Sdk>) =
detectSystemWideSdks(module, existingSdks).firstOrNull()
}
override fun configureProject(project: Project?, baseDir: VirtualFile, moduleRef: Ref<Module>?) {
@@ -75,7 +75,7 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
return
}
findDetectedSystemWideSdk(existingSdks)?.let {
findDetectedSystemWideSdk(module, existingSdks)?.let {
SdkConfigurationUtil.createAndAddSDK(it.homePath, PythonSdkType.getInstance())?.apply {
SdkConfigurationUtil.setDirectoryProjectSdk(project, this)
}
@@ -47,17 +47,17 @@ import java.nio.file.Paths
* @author vlan
*/
fun findBaseSdks(existingSdks: List<Sdk>): List<Sdk> {
fun findBaseSdks(existingSdks: List<Sdk>, module: Module?): List<Sdk> {
val existing = existingSdks.filter { it.sdkType is PythonSdkType && it.isSystemWide }
val detected = detectSystemWideSdks(existingSdks)
val detected = detectSystemWideSdks(module, existingSdks)
return existing + detected
}
fun detectSystemWideSdks(existingSdks: List<Sdk>): List<PyDetectedSdk> {
fun detectSystemWideSdks(module: Module?, existingSdks: List<Sdk>): List<PyDetectedSdk> {
val existingPaths = existingSdks.map { it.homePath }.toSet()
return PythonSdkFlavor.getApplicableFlavors(false)
.asSequence()
.flatMap { it.suggestHomePaths().asSequence() }
.flatMap { it.suggestHomePaths(module).asSequence() }
.filter { it !in existingPaths }
.map { PyDetectedSdk(it) }
.sortedWith(compareBy<PyDetectedSdk>({ it.guessedLanguageLevel },
@@ -66,10 +66,10 @@ fun detectSystemWideSdks(existingSdks: List<Sdk>): List<PyDetectedSdk> {
}
fun detectVirtualEnvs(module: Module?, existingSdks: List<Sdk>): List<PyDetectedSdk> =
filterSuggestedPaths(VirtualEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, module)
filterSuggestedPaths(VirtualEnvSdkFlavor.INSTANCE.suggestHomePaths(module), existingSdks, module)
fun detectCondaEnvs(module: Module?, existingSdks: List<Sdk>): List<PyDetectedSdk> =
filterSuggestedPaths(CondaEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, module)
filterSuggestedPaths(CondaEnvSdkFlavor.INSTANCE.suggestHomePaths(module), existingSdks, module)
fun createSdkByGenerateTask(generateSdkHomePath: Task.WithResult<String, ExecutionException>,
existingSdks: List<Sdk>,
@@ -147,7 +147,7 @@ public final class PythonSdkType extends SdkType {
@Nullable
public String suggestHomePath() {
final Sdk[] existingSdks = ProjectJdkTable.getInstance().getAllJdks();
final List<PyDetectedSdk> sdks = PySdkExtKt.detectSystemWideSdks(Arrays.asList(existingSdks));
final List<PyDetectedSdk> sdks = PySdkExtKt.detectSystemWideSdks(null, Arrays.asList(existingSdks));
final PyDetectedSdk latest = StreamEx.of(sdks).findFirst().orElse(null);
if (latest != null) {
return latest.getHomePath();
@@ -62,7 +62,7 @@ class PyAddNewVirtualEnvPanel(private val project: Project?,
override val panelName: String = "New environment"
override val icon: Icon = PythonIcons.Python.Virtualenv
private val baseSdkField = PySdkPathChoosingComboBox(findBaseSdks(existingSdks), null).apply {
private val baseSdkField = PySdkPathChoosingComboBox(findBaseSdks(existingSdks, module), null).apply {
val preferredSdkPath = PySdkSettings.instance.preferredVirtualEnvBaseSdk
val detectedPreferredSdk = items.find { it.homePath == preferredSdkPath }
selectedSdk = when {
@@ -76,7 +76,7 @@ class PyAddSdkDialog private constructor(private val project: Project?,
.sortedWith(PreferredSdkComparator())
val panels = arrayListOf<PyAddSdkView>(createVirtualEnvPanel(project, module, sdks, newProjectPath),
createAnacondaPanel(project, module),
PyAddSystemWideInterpreterPanel(existingSdks))
PyAddSystemWideInterpreterPanel(module, existingSdks))
val extendedPanels = PyAddSdkProvider.EP_NAME.extensions
.mapNotNull {
it.createView(project = project, module = module, newProjectPath = newProjectPath, existingSdks = existingSdks)
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.sdk.add
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.ui.components.JBLabel
@@ -28,9 +29,9 @@ import java.awt.BorderLayout
/**
* @author vlan
*/
class PyAddSystemWideInterpreterPanel(private val existingSdks: List<Sdk>) : PyAddSdkPanel() {
class PyAddSystemWideInterpreterPanel(module: Module?, private val existingSdks: List<Sdk>) : PyAddSdkPanel() {
override val panelName: String = "System interpreter"
private val sdkComboBox = PySdkPathChoosingComboBox(detectSystemWideSdks(existingSdks), null)
private val sdkComboBox = PySdkPathChoosingComboBox(detectSystemWideSdks(module, existingSdks), null)
init {
layout = BorderLayout()
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.sdk.flavors;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.StandardFileSystems;
@@ -45,7 +46,7 @@ public class CondaEnvSdkFlavor extends CPythonSdkFlavor {
public static CondaEnvSdkFlavor INSTANCE = new CondaEnvSdkFlavor();
@Override
public Collection<String> suggestHomePaths() {
public Collection<String> suggestHomePaths(@Nullable Module module) {
List<String> candidates = new ArrayList<>();
for (VirtualFile file : getCondaDefaultLocations()) {
@@ -16,6 +16,7 @@
package com.jetbrains.python.sdk.flavors;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.PatternUtil;
import icons.PythonIcons;
@@ -39,7 +40,7 @@ public class IronPythonSdkFlavor extends PythonSdkFlavor {
public static IronPythonSdkFlavor INSTANCE = new IronPythonSdkFlavor();
@Override
public Collection<String> suggestHomePaths() {
public Collection<String> suggestHomePaths(@Nullable Module module) {
Set<String> result = new TreeSet<>();
String root = System.getenv("ProgramFiles(x86)");
if (root == null) {
@@ -15,9 +15,12 @@
*/
package com.jetbrains.python.sdk.flavors;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Collection;
@@ -34,7 +37,7 @@ public class MacPythonSdkFlavor extends CPythonSdkFlavor {
private static final String[] POSSIBLE_BINARY_NAMES = {"python", "python2", "python3"};
@Override
public Collection<String> suggestHomePaths() {
public Collection<String> suggestHomePaths(@Nullable Module module) {
Set<String> candidates = new HashSet<>();
collectPythonInstallations("/Library/Frameworks/Python.framework/Versions", candidates);
collectPythonInstallations("/System/Library/Frameworks/Python.framework/Versions", candidates);
@@ -16,6 +16,7 @@
package com.jetbrains.python.sdk.flavors;
import com.google.common.collect.Lists;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.remote.RemoteFile;
import icons.PythonIcons;
@@ -37,7 +38,7 @@ public class PyRemoteSdkFlavor extends CPythonSdkFlavor {
public static PyRemoteSdkFlavor INSTANCE = new PyRemoteSdkFlavor();
@Override
public Collection<String> suggestHomePaths() {
public Collection<String> suggestHomePaths(@Nullable Module module) {
return Lists.newArrayList();
}
@@ -21,6 +21,7 @@ import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.util.SystemInfo;
@@ -70,7 +71,15 @@ public abstract class PythonSdkFlavor {
PythonEnvUtil.addToPythonPath(envs, pythonPathList);
}
/**
* @deprecated Use {@link #suggestHomePaths(Module)}. To be removed in 2019.2.
*/
@Deprecated
public Collection<String> suggestHomePaths() {
return suggestHomePaths(null);
}
public Collection<String> suggestHomePaths(@Nullable Module module) {
return Collections.emptyList();
}
@@ -15,9 +15,11 @@
*/
package com.jetbrains.python.sdk.flavors;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashSet;
@@ -36,7 +38,7 @@ public class UnixPythonSdkFlavor extends CPythonSdkFlavor {
public static final UnixPythonSdkFlavor INSTANCE = new UnixPythonSdkFlavor();
@Override
public Collection<String> suggestHomePaths() {
public Collection<String> suggestHomePaths(@Nullable Module module) {
Set<String> candidates = new HashSet<>();
collectUnixPythons("/usr/bin", candidates);
return candidates;
@@ -15,18 +15,13 @@
*/
package com.jetbrains.python.sdk.flavors;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.SystemProperties;
import com.jetbrains.python.PythonModuleTypeBase;
import com.jetbrains.python.sdk.PySdkExtKt;
import com.jetbrains.python.sdk.PythonSdkType;
import icons.PythonIcons;
@@ -50,26 +45,25 @@ public class VirtualEnvSdkFlavor extends CPythonSdkFlavor {
public static VirtualEnvSdkFlavor INSTANCE = new VirtualEnvSdkFlavor();
@Override
public Collection<String> suggestHomePaths() {
final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
List<String> candidates = new ArrayList<>();
if (project != null) {
for (Module module : ModuleUtil.getModulesOfType(project, PythonModuleTypeBase.getInstance())) {
final VirtualFile baseDir = PySdkExtKt.getBaseDir(module);
if (baseDir != null) {
candidates.addAll(findInDirectory(baseDir));
}
public Collection<String> suggestHomePaths(@Nullable Module module) {
final List<String> candidates = new ArrayList<>();
if (module != null) {
final VirtualFile baseDir = PySdkExtKt.getBaseDir(module);
if (baseDir != null) {
candidates.addAll(findInDirectory(baseDir));
}
}
final VirtualFile path = getDefaultLocation();
if (path != null)
if (path != null) {
candidates.addAll(findInDirectory(path));
}
final VirtualFile pyEnvLocation = getPyEnvDefaultLocations();
if (pyEnvLocation != null) {
candidates.addAll(findInDirectory(pyEnvLocation));
}
return candidates;
}
@@ -16,6 +16,7 @@
package com.jetbrains.python.sdk.flavors;
import com.google.common.collect.ImmutableMap;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
@@ -23,6 +24,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.jetbrains.python.PythonHelpersLocator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
@@ -48,7 +50,7 @@ public final class WinPythonSdkFlavor extends CPythonSdkFlavor {
}
@Override
public Collection<String> suggestHomePaths() {
public Collection<String> suggestHomePaths(@Nullable Module module) {
Set<String> candidates = new TreeSet<>();
findInCandidatePaths(candidates, "python.exe", "jython.bat", "pypy.exe");
findInstallations(candidates, "python.exe", PythonHelpersLocator.getHelpersRoot().getParent());
@@ -44,7 +44,7 @@ class PyAddPipEnvPanel(private val project: Project?,
private val moduleField: JComboBox<Module>
private val baseSdkField = PySdkPathChoosingComboBox(findBaseSdks(existingSdks), null).apply {
private val baseSdkField = PySdkPathChoosingComboBox(findBaseSdks(existingSdks, module), null).apply {
val preferredSdkPath = PySdkSettings.instance.preferredVirtualEnvBaseSdk
val detectedPreferredSdk = items.find { it.homePath == preferredSdkPath }
selectedSdk = when {