Rewritten PythonSdkConfigurator to prefer associated Python environments

This commit is contained in:
Andrey Vlasovskikh
2018-06-15 21:24:41 +03:00
parent a65a3f894c
commit 1a1252077b
3 changed files with 81 additions and 103 deletions
@@ -1,102 +0,0 @@
/*
* 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;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.platform.DirectoryProjectConfigurator;
import com.intellij.util.SystemProperties;
import com.jetbrains.python.sdk.PreferredSdkComparator;
import com.jetbrains.python.sdk.PythonSdkAdditionalData;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* @author dcheryasov
*/
public class PythonSdkConfigurator implements DirectoryProjectConfigurator {
public void configureProject(final Project project, @NotNull final VirtualFile baseDir, Ref<Module> moduleRef) {
// it it a virtualenv?
final List<VirtualFile> children = VfsUtil.getChildren(baseDir, new VirtualFileFilter() {
@Override
public boolean accept(VirtualFile file) {
return !Project.DIRECTORY_STORE_FOLDER.equals(file.getName());
}
});
if (children.isEmpty()) return;
final PythonSdkType sdkType = PythonSdkType.getInstance();
//find virtualEnv in project directory
final List<String> candidates = new ArrayList<>();
if (project == null) return;
final VirtualFile rootDir = project.getBaseDir();
if (rootDir != null)
candidates.addAll(VirtualEnvSdkFlavor.findInDirectory(rootDir));
if (!candidates.isEmpty()) {
String filePath = candidates.get(0);
if (StringUtil.startsWithChar(filePath, '~')) {
final String home = SystemProperties.getUserHome();
filePath = home + filePath.substring(1);
}
final Sdk virtualEnvSdk = SdkConfigurationUtil.createAndAddSDK(filePath, sdkType);
if (virtualEnvSdk != null) {
SdkConfigurationUtil.setDirectoryProjectSdk(project, virtualEnvSdk);
SdkAdditionalData additionalData = virtualEnvSdk.getSdkAdditionalData();
if (additionalData == null) {
additionalData = new PythonSdkAdditionalData(PythonSdkFlavor.getFlavor(virtualEnvSdk.getHomePath()));
((ProjectJdkImpl)virtualEnvSdk).setSdkAdditionalData(additionalData);
}
((PythonSdkAdditionalData) additionalData).setAssociatedModulePath(project.getBasePath());
return;
}
return;
}
final Sdk existingSdk = ProjectRootManager.getInstance(project).getProjectSdk();
if (existingSdk != null && existingSdk.getSdkType() == sdkType) return; // SdkConfigurationUtil does the same
final File executableFile = PythonSdkType.findExecutableFile(new File(project.getBasePath(), "bin"), "python");
if (executableFile != null) {
final File virtualEnvRoot = PythonSdkType.getVirtualEnvRoot(executableFile.getPath());
if (virtualEnvRoot != null) {
// yes, an unknown virtualenv; set it up as SDK
final Sdk sdk = SdkConfigurationUtil.createAndAddSDK(executableFile.getPath(), sdkType);
if (sdk != null) {
SdkConfigurationUtil.setDirectoryProjectSdk(project, sdk);
return;
}
}
}
// default
SdkConfigurationUtil.configureDirectoryProjectSdk(project, new PreferredSdkComparator(), sdkType);
}
}
@@ -0,0 +1,80 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel
import com.intellij.openapi.util.Ref
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.DirectoryProjectConfigurator
import com.jetbrains.python.sdk.*
/**
* @author vlan
*/
class PythonSdkConfigurator : DirectoryProjectConfigurator {
companion object {
fun findExistingAssociatedSdk(module: Module, existingSdks: List<Sdk>): Sdk? {
return existingSdks
.asSequence()
.filter { it.sdkType is PythonSdkType && it.isAssociatedWithModule(module) }
.sortedByDescending { it.homePath }
.firstOrNull()
}
fun findDetectedAssociatedEnvironment(module: Module, existingSdks: List<Sdk>): PyDetectedSdk? {
detectVirtualEnvs(module, existingSdks).firstOrNull { it.isAssociatedWithModule(module) }?.let {
return it
}
detectCondaEnvs(module, existingSdks).firstOrNull { it.isAssociatedWithModule(module) }?.let {
return it
}
return null
}
fun findExistingSystemWideSdk(existingSdks: List<Sdk>): Sdk? {
return existingSdks.filter { it.isSystemWide }.sortedWith(PreferredSdkComparator.INSTANCE).firstOrNull()
}
fun findDetectedSystemWideSdk(existingSdks: List<Sdk>): Sdk? {
return detectSystemWideSdks(existingSdks).firstOrNull()
}
}
override fun configureProject(project: Project?, baseDir: VirtualFile, moduleRef: Ref<Module>?) {
if (project == null || project.pythonSdk != null) {
return
}
val module = ModuleManager.getInstance(project).modules.firstOrNull() ?: return
val existingSdks = ProjectSdksModel().apply { reset(project) }.sdks.filter { it.sdkType is PythonSdkType }
findExistingAssociatedSdk(module, existingSdks)?.let {
SdkConfigurationUtil.setDirectoryProjectSdk(project, it)
return
}
findDetectedAssociatedEnvironment(module, existingSdks)?.let {
val newSdk = it.setupAssociated(existingSdks, module.basePath) ?: return
SdkConfigurationUtil.addSdk(newSdk)
newSdk.associateWithModule(module, false)
SdkConfigurationUtil.setDirectoryProjectSdk(project, newSdk)
return
}
findExistingSystemWideSdk(existingSdks)?.let {
SdkConfigurationUtil.setDirectoryProjectSdk(project, it)
return
}
findDetectedSystemWideSdk(existingSdks)?.let {
SdkConfigurationUtil.createAndAddSDK(it.homePath, PythonSdkType.getInstance())?.apply {
SdkConfigurationUtil.setDirectoryProjectSdk(project, this)
}
}
SdkConfigurationUtil.configureDirectoryProjectSdk(project, PreferredSdkComparator.INSTANCE, PythonSdkType.getInstance())
}
}
@@ -176,7 +176,7 @@ private fun suggestAssociatedSdkName(sdkHome: String, associatedPath: String?):
val File.isNotEmptyDirectory: Boolean
get() = exists() && isDirectory && list()?.isEmpty()?.not() ?: false
private val Sdk.isSystemWide: Boolean
val Sdk.isSystemWide: Boolean
get() = !PythonSdkType.isRemote(this) && !PythonSdkType.isVirtualEnv(
this) && !PythonSdkType.isCondaVirtualEnv(this)