[IFT] Add another type of sdk for Python learning project: virtual environment.

Venv will be used if "ide.features.trainer.use.venv" registry option set to true. This flag was added because venv doesn't work properly in master due to classpath problems.

IDEA-CR-69125

GitOrigin-RevId: a16c5c2943a8fc19d331e00660a0ebdee8e178ae
This commit is contained in:
Konstantin Hudyakov
2020-12-15 12:27:49 +00:00
committed by intellij-monorepo-bot
parent 18fb6cb193
commit 8076cfc8dd
6 changed files with 75 additions and 16 deletions
@@ -1,6 +1,7 @@
// Copyright 2000-2019 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 training.lang
import com.intellij.ide.impl.OpenProjectTask
import com.intellij.ide.impl.ProjectUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
@@ -25,7 +26,9 @@ abstract class AbstractLangSupport : LangSupport {
override fun installAndOpenLearningProject(projectPath: Path,
projectToClose: Project?,
postInitCallback: (learnProject: Project) -> Unit) {
ProjectUtils.simpleInstallAndOpenLearningProject(projectPath, projectToClose, this, postInitCallback)
ProjectUtils.simpleInstallAndOpenLearningProject(projectPath, this,
OpenProjectTask(projectToClose = projectToClose),
postInitCallback)
}
override fun getSdkForProject(project: Project): Sdk? {
@@ -173,7 +173,9 @@ internal object OpenLessonActivities {
//open next lesson if current is passed
LOG.debug("${project.name}: Set lesson view")
LearningUiManager.activeToolWindow?.setLearnPanel()
LearningUiManager.activeToolWindow = LearnToolWindowFactory.learnWindowPerProject[project]?.also {
it.setLearnPanel()
}
LOG.debug("${project.name}: XmlLesson onStart()")
lesson.onStart()
@@ -269,9 +271,9 @@ internal object OpenLessonActivities {
val learnToolWindow = toolWindowManager.getToolWindow(LearnToolWindowFactory.LEARN_TOOL_WINDOW)
if (learnToolWindow != null) {
val runnable = if (lesson.properties.showLearnToolwindowAtStart) null else Runnable { learnToolWindow.hide() }
learnToolWindow.show(runnable)
DumbService.getInstance(myLearnProject).runWhenSmart {
// Try to fix PyChar double startup indexing :(
learnToolWindow.show(runnable)
// Try to fix PyCharm double startup indexing :(
val openWhenSmart = {
DumbService.getInstance(myLearnProject).runWhenSmart {
openLessonForPreparedProject(myLearnProject, lesson)
@@ -106,8 +106,8 @@ object ProjectUtils {
}
fun simpleInstallAndOpenLearningProject(projectPath: Path,
projectToClose: Project?,
langSupport: LangSupport,
openProjectTask: OpenProjectTask,
postInitCallback: (learnProject: Project) -> Unit) {
val copied = copyLearningProjectFiles(projectPath, langSupport)
if (!copied) return
@@ -115,7 +115,7 @@ object ProjectUtils {
val projectDirectoryVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByNioFile(projectPath)
?: error("Copied Learn project folder is null")
invokeLater {
val project = ProjectUtil.openOrImport(projectDirectoryVirtualFile.toNioPath(), OpenProjectTask(projectToClose = projectToClose))
val project = ProjectUtil.openOrImport(projectDirectoryVirtualFile.toNioPath(), openProjectTask)
?: error("Could not create project for ${langSupport.primaryLanguage}")
PropertiesComponent.getInstance(project).setValue(LEARNING_PROJECT_MODIFICATION, System.currentTimeMillis().toString())
postInitCallback(project)
@@ -16,6 +16,5 @@
<orderEntry type="module" module-name="intellij.platform.debugger" />
<orderEntry type="module" module-name="intellij.platform.testGuiFramework" />
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
<orderEntry type="module" module-name="intellij.pycharm.community.customization.impl" />
</component>
</module>
@@ -4,4 +4,9 @@
<ift.language.extension language="Python" implementationClass="com.jetbrains.python.ift.PythonLangSupport"/>
<ift.learning.course language="Python" implementationClass="com.jetbrains.python.ift.PythonLearningCourse"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
<registryKey defaultValue="true" description="Initialize Python learning project with virtual environment."
key="ide.features.trainer.use.venv"/>
</extensions>
</idea-plugin>
@@ -1,6 +1,7 @@
// Copyright 2000-2020 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.ift
import com.intellij.ide.impl.OpenProjectTask
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
@@ -8,17 +9,23 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setReadyToUseSdk
import com.jetbrains.python.sdk.configuration.PyProjectVirtualEnvConfiguration
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
import com.jetbrains.python.statistics.modules
import training.lang.AbstractLangSupport
import training.learn.LearnBundle
import training.learn.exceptons.NoSdkException
import training.learn.lesson.kimpl.LessonUtil
import training.project.ProjectUtils
import java.nio.file.Path
class PythonLangSupport : AbstractLangSupport() {
@@ -51,7 +58,26 @@ class PythonLangSupport : AbstractLangSupport() {
}
}
override fun installAndOpenLearningProject(projectPath: Path,
projectToClose: Project?,
postInitCallback: (learnProject: Project) -> Unit) {
// if we open project with isProjectCreatedFromWizard flag as true, PythonSdkConfigurator will not run and configure our sdks
// and we will configure it individually without any race conditions
val openProjectTask = OpenProjectTask(projectToClose = projectToClose, isProjectCreatedWithWizard = true)
ProjectUtils.simpleInstallAndOpenLearningProject(projectPath, this, openProjectTask, postInitCallback)
}
override fun getSdkForProject(project: Project): Sdk? {
if (project.pythonSdk != null) return null // sdk already configured
return if (useVenv) {
createVenv(project)
}
else {
findSystemWideSdk(project)
}
}
private fun findSystemWideSdk(project: Project): Sdk? {
val sdkList = ProgressManager.getInstance().runProcessWithProgressSynchronously<List<Sdk>, Exception>(
{ findAllPythonSdks(Path.of(project.basePath)) },
LearnBundle.message("learn.project.initializing.python.sdk.finding.progress.title"),
@@ -74,6 +100,37 @@ class PythonLangSupport : AbstractLangSupport() {
return preferredSdk
}
private fun createVenv(project: Project): Sdk? {
val module = project.modules.first()
val existingSdks = getExistingSdks()
val baseSdks = findBaseSdks(existingSdks, module, project)
val preferredSdk = PyProjectVirtualEnvConfiguration.findPreferredVirtualEnvBaseSdk(baseSdks)
val venvRoot = FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(project.basePath))
val venvSdk = PyProjectVirtualEnvConfiguration.createVirtualEnvSynchronously(preferredSdk, existingSdks, venvRoot,
project.basePath, project, module, project)
return venvSdk?.also {
SdkConfigurationUtil.addSdk(it)
}
}
override fun applyProjectSdk(sdk: Sdk, project: Project) {
setReadyToUseSdk(project, project.modules.first(), sdk)
if (!useVenv && LessonUtil.productName != defaultProductName) {
// This is a workaround for IDEA SetupJavaProjectFromSourcesActivity that replaces our SDK with Java SDK
val rootManager = ProjectRootManagerEx.getInstanceEx(project)
rootManager.addProjectJdkListener {
if (rootManager.projectSdk?.sdkType !is PythonSdkType) {
setReadyToUseSdk(project, project.modules.first(), sdk)
}
}
}
}
private fun getExistingSdks(): List<Sdk> {
return PyConfigurableInterpreterList.getInstance(null).allPythonSdks
.sortedWith(PreferredSdkComparator.INSTANCE)
}
private fun isNoOlderThan27(sdk: Sdk): Boolean {
val languageLevel = if (sdk is PyDetectedSdk) {
sdk.guessedLanguageLevel
@@ -84,15 +141,8 @@ class PythonLangSupport : AbstractLangSupport() {
return languageLevel?.isAtLeast(LanguageLevel.PYTHON27) ?: false
}
override fun applyProjectSdk(sdk: Sdk, project: Project) {
setReadyToUseSdk(project, project.modules.first(), sdk)
val rootManager = ProjectRootManagerEx.getInstanceEx(project)
rootManager.addProjectJdkListener {
if (rootManager.projectSdk?.sdkType !is PythonSdkType) {
setReadyToUseSdk(project, project.modules.first(), sdk)
}
}
}
private val useVenv: Boolean
get() = Registry.`is`("ide.features.trainer.use.venv")
override fun checkSdk(sdk: Sdk?, project: Project) {
}