From 8076cfc8dd5c7c8e7465b356a4c1969f780e3fb4 Mon Sep 17 00:00:00 2001 From: Konstantin Hudyakov Date: Tue, 15 Dec 2020 15:06:01 +0300 Subject: [PATCH] [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 --- .../src/training/lang/AbstractLangSupport.kt | 5 +- .../training/learn/OpenLessonActivities.kt | 8 ++- .../src/training/project/ProjectUtils.kt | 4 +- .../intellij.python.featuresTrainer.iml | 1 - .../META-INF/python-features-trainer.xml | 5 ++ .../jetbrains/python/ift/PythonLangSupport.kt | 68 ++++++++++++++++--- 6 files changed, 75 insertions(+), 16 deletions(-) diff --git a/plugins/ide-features-trainer/src/training/lang/AbstractLangSupport.kt b/plugins/ide-features-trainer/src/training/lang/AbstractLangSupport.kt index 278435807ca0..4a5ced5aa99e 100644 --- a/plugins/ide-features-trainer/src/training/lang/AbstractLangSupport.kt +++ b/plugins/ide-features-trainer/src/training/lang/AbstractLangSupport.kt @@ -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? { diff --git a/plugins/ide-features-trainer/src/training/learn/OpenLessonActivities.kt b/plugins/ide-features-trainer/src/training/learn/OpenLessonActivities.kt index 0173ee1ec450..e6c559d3a9d4 100644 --- a/plugins/ide-features-trainer/src/training/learn/OpenLessonActivities.kt +++ b/plugins/ide-features-trainer/src/training/learn/OpenLessonActivities.kt @@ -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) diff --git a/plugins/ide-features-trainer/src/training/project/ProjectUtils.kt b/plugins/ide-features-trainer/src/training/project/ProjectUtils.kt index 45c7814bf2d8..ad3f607ca167 100644 --- a/plugins/ide-features-trainer/src/training/project/ProjectUtils.kt +++ b/plugins/ide-features-trainer/src/training/project/ProjectUtils.kt @@ -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) diff --git a/python/python-features-trainer/intellij.python.featuresTrainer.iml b/python/python-features-trainer/intellij.python.featuresTrainer.iml index 65199bfaaf02..5acb56445e52 100644 --- a/python/python-features-trainer/intellij.python.featuresTrainer.iml +++ b/python/python-features-trainer/intellij.python.featuresTrainer.iml @@ -16,6 +16,5 @@ - diff --git a/python/python-features-trainer/resources/META-INF/python-features-trainer.xml b/python/python-features-trainer/resources/META-INF/python-features-trainer.xml index bb5763492891..e296aa263164 100644 --- a/python/python-features-trainer/resources/META-INF/python-features-trainer.xml +++ b/python/python-features-trainer/resources/META-INF/python-features-trainer.xml @@ -4,4 +4,9 @@ + + + + diff --git a/python/python-features-trainer/src/com/jetbrains/python/ift/PythonLangSupport.kt b/python/python-features-trainer/src/com/jetbrains/python/ift/PythonLangSupport.kt index f5f4c136a5e4..2c249e925cc5 100644 --- a/python/python-features-trainer/src/com/jetbrains/python/ift/PythonLangSupport.kt +++ b/python/python-features-trainer/src/com/jetbrains/python/ift/PythonLangSupport.kt @@ -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, 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 { + 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) { }