mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-18 20:41:22 +07:00
PY-78817: Migrate a learning project to createVenvAndSdk, cover with test.
This is a top-level function used by a Misc project. GitOrigin-RevId: 64f334813091cfc8b12bf1dfe85f1ed1ecb8637a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
80f39095c8
commit
63282e2d43
@@ -24,5 +24,10 @@
|
||||
<orderEntry type="library" name="kotlinx-serialization-json" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.pycharm.community" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.services.systemPython" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.junit5Tests.framework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl.venv" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.services.internal.impl" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -4,36 +4,40 @@ package com.intellij.python.featuresTrainer.ift
|
||||
import com.intellij.ide.impl.OpenProjectTask
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.invokeLater
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PySdkBundle
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.inspections.PyInterpreterInspection
|
||||
import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.projectCreation.createVenvAndSdk
|
||||
import com.jetbrains.python.sdk.PySdkToInstall
|
||||
import com.jetbrains.python.sdk.add.PySdkPathChoosingComboBox
|
||||
import com.jetbrains.python.sdk.add.addBaseInterpretersAsync
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setReadyToUseSdk
|
||||
import com.jetbrains.python.sdk.configuration.createVirtualEnvAndSdkSynchronously
|
||||
import com.jetbrains.python.sdk.configuration.findPreferredVirtualEnvBaseSdk
|
||||
import com.jetbrains.python.sdk.findBaseSdks
|
||||
import com.jetbrains.python.sdk.pythonSdk
|
||||
import com.jetbrains.python.statistics.modules
|
||||
import com.jetbrains.python.util.ShowingMessageErrorSync
|
||||
import training.dsl.LessonContext
|
||||
import training.lang.AbstractLangSupport
|
||||
import training.learn.CourseManager
|
||||
import training.learn.course.KLesson
|
||||
import training.learn.exceptons.NoSdkException
|
||||
import training.project.ProjectUtils
|
||||
import training.project.ReadMeCreator
|
||||
import training.statistic.LearningInternalProblems
|
||||
@@ -47,7 +51,7 @@ import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import kotlin.math.max
|
||||
|
||||
internal class PythonLangSupport : AbstractLangSupport() {
|
||||
internal class PythonLangSupport(private val errorSink: ErrorSink = ShowingMessageErrorSync) : AbstractLangSupport() {
|
||||
|
||||
override val contentRootDirectoryName = "PyCharmLearningProject"
|
||||
|
||||
@@ -68,9 +72,11 @@ internal class PythonLangSupport : AbstractLangSupport() {
|
||||
override fun blockProjectFileModification(project: Project, file: VirtualFile): Boolean = true
|
||||
override val readMeCreator = ReadMeCreator()
|
||||
|
||||
override fun installAndOpenLearningProject(contentRoot: Path,
|
||||
projectToClose: Project?,
|
||||
postInitCallback: (learnProject: Project) -> Unit) {
|
||||
override fun installAndOpenLearningProject(
|
||||
contentRoot: 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 {
|
||||
@@ -80,56 +86,23 @@ internal class PythonLangSupport : AbstractLangSupport() {
|
||||
ProjectUtils.simpleInstallAndOpenLearningProject(contentRoot, this, openProjectTask, postInitCallback)
|
||||
}
|
||||
|
||||
override fun getSdkForProject(project: Project, selectedSdk: Sdk?): Sdk? {
|
||||
if (selectedSdk != null) {
|
||||
val module = project.modules.first()
|
||||
val existingSdks = getExistingSdks()
|
||||
return applyBaseSdk(project, selectedSdk, existingSdks, module)
|
||||
}
|
||||
if (project.pythonSdk != null) return null // sdk already configured
|
||||
|
||||
// Run in parallel, because we can not wait for SDK here
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
createAndSetVenvSdk(project)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun createAndSetVenvSdk(project: Project) {
|
||||
val module = project.modules.first()
|
||||
val existingSdks = getExistingSdks()
|
||||
val baseSdks = findBaseSdks(existingSdks, module, project)
|
||||
val preferredSdk = findPreferredVirtualEnvBaseSdk(baseSdks) ?: return
|
||||
invokeLater {
|
||||
val venvSdk = applyBaseSdk(project, preferredSdk, existingSdks, module)
|
||||
if (venvSdk != null) {
|
||||
applyProjectSdk(venvSdk, project)
|
||||
@Throws(NoSdkException::class)
|
||||
@RequiresEdt
|
||||
override fun getSdkForProject(project: Project, selectedSdk: Sdk?): Sdk = runWithModalProgressBlocking(project, "...") {
|
||||
when (val r = createVenvAndSdk(project)) {
|
||||
is Result.Failure -> {
|
||||
errorSink.emit(r.error)
|
||||
null
|
||||
}
|
||||
}
|
||||
is Result.Success -> r.result
|
||||
} ?: throw NoSdkException()
|
||||
}
|
||||
|
||||
private fun applyBaseSdk(project: Project,
|
||||
preferredSdk: Sdk,
|
||||
existingSdks: List<Sdk>,
|
||||
module: Module?): Sdk? {
|
||||
val venvRoot = FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(project.basePath))
|
||||
val venvSdk = createVirtualEnvAndSdkSynchronously(preferredSdk, existingSdks, venvRoot, project.basePath, project, module, project)
|
||||
return venvSdk.also {
|
||||
SdkConfigurationUtil.addSdk(it)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
override fun applyProjectSdk(sdk: Sdk, project: Project) {
|
||||
setReadyToUseSdk(project, project.modules.first(), sdk)
|
||||
}
|
||||
|
||||
private fun getExistingSdks(): List<Sdk> {
|
||||
return PyConfigurableInterpreterList.getInstance(null).allPythonSdks
|
||||
.sortedWith(PreferredSdkComparator.INSTANCE)
|
||||
}
|
||||
|
||||
override fun checkSdk(sdk: Sdk?, project: Project) {
|
||||
}
|
||||
@@ -223,4 +196,18 @@ internal class PythonLangSupport : AbstractLangSupport() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresReadLock
|
||||
override fun getProtectedDirs(project: Project): Set<Path> {
|
||||
return project.getSdks().mapNotNull { it.homePath }.map { Path.of(it) }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresReadLock
|
||||
private fun Project.getSdks(): Set<Sdk> {
|
||||
val projectSdk = ProjectRootManager.getInstance(this).projectSdk
|
||||
val moduleSdks = modules.mapNotNull {
|
||||
ModuleRootManager.getInstance(it).sdk
|
||||
}
|
||||
return (moduleSdks + (projectSdk?.let { listOf(it) } ?: emptyList())).toSet()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.intellij.python.junit5Tests.env
|
||||
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.writeAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.python.community.impl.venv.createVenv
|
||||
import com.intellij.python.community.services.internal.impl.PythonWithLanguageLevelImpl
|
||||
import com.intellij.python.featuresTrainer.ift.PythonLangSupport
|
||||
import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase
|
||||
import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath
|
||||
import com.intellij.testFramework.common.timeoutRunBlocking
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.sdk.pythonSdk
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader.Companion.DEFAULT_VIRTUALENV_DIRNAME
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.ValueSource
|
||||
import training.learn.NewLearnProjectUtil
|
||||
import training.project.ProjectUtils
|
||||
import java.nio.file.Path
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@PyEnvTestCase
|
||||
class PythonLangSupportTest {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@TempDir
|
||||
lateinit var temporarySystemPath: Path
|
||||
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun setUp() {
|
||||
ProjectUtils.customSystemPath = temporarySystemPath
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@AfterAll
|
||||
fun tearDown() {
|
||||
ProjectUtils.customSystemPath = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = [true, false])
|
||||
fun ensureVenvCreatedTest(venvAlreadyExists: Boolean, @PythonBinaryPath python: PythonBinary): Unit = timeoutRunBlocking(10.minutes) {
|
||||
val learningProjectsPath = ProjectUtils.learningProjectsPath
|
||||
assert(learningProjectsPath.startsWith(temporarySystemPath)) { "$learningProjectsPath must reside in $temporarySystemPath" }
|
||||
|
||||
val sut = PythonLangSupport(ErrorSink {
|
||||
Assertions.fail(it.message)
|
||||
})
|
||||
|
||||
if (venvAlreadyExists) {
|
||||
val venvPath = learningProjectsPath.resolve(DEFAULT_VIRTUALENV_DIRNAME)
|
||||
createVenv(python, venvPath).orThrow()
|
||||
}
|
||||
|
||||
val sema = CompletableDeferred<Project>()
|
||||
withContext(Dispatchers.EDT) {
|
||||
|
||||
NewLearnProjectUtil.createLearnProject(null, sut, null) { project ->
|
||||
sema.complete(project)
|
||||
}
|
||||
}
|
||||
val project = sema.await()
|
||||
withContext(Dispatchers.IO) {
|
||||
sut.cleanupBeforeLessons(project)
|
||||
}
|
||||
val sdk = project.pythonSdk!!
|
||||
try {
|
||||
val pythonBinary = Path.of(sdk.homePath!!)
|
||||
Assertions.assertTrue(PythonWithLanguageLevelImpl.createByPythonBinary(pythonBinary).orThrow().languageLevel.isPy3K, "Sdk is broken")
|
||||
}
|
||||
finally {
|
||||
writeAction {
|
||||
ProjectJdkTable.getInstance().removeJdk(sdk)
|
||||
}
|
||||
withContext(Dispatchers.EDT) {
|
||||
ProjectManager.getInstance().closeAndDispose(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user