mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-52688 support Poetry for remote targets
(cherry picked from commit 29f62aa09b653caf57a7d2c1a577f1eea9509d66) IJ-MR-215198 GitOrigin-RevId: 548f46186b0fa110776c1539b51d08ba659f9012
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8b9a94a301
commit
eab2befd1a
@@ -586,6 +586,7 @@ sdk.create.custom.hatch.error.execution.failed=Please verify Hatch tool, execute
|
||||
sdk.create.custom.hatch.error.environment.is.not.selected=Hatch environment is not selected
|
||||
sdk.create.custom.hatch.error.hatch.executable.path.is.not.valid=Hatch executable path is not valid: {0}
|
||||
sdk.create.custom.hatch.error.base.python.executable.is.not.available=Base Python executable is not available
|
||||
sdk.create.custom.poetry.error.poetry.executable.path.is.not.valid=Poetry executable path is not valid
|
||||
|
||||
sdk.create.targets.local=Local Machine
|
||||
sdk.create.custom.virtualenv=Virtualenv
|
||||
|
||||
@@ -3,13 +3,13 @@ package com.intellij.python.hatch.impl
|
||||
|
||||
import com.intellij.python.community.common.tools.ToolId
|
||||
import com.intellij.python.hatch.icons.PythonHatchIcons
|
||||
import com.intellij.python.hatch.impl.sdk.HatchSdkFlavor
|
||||
import com.intellij.python.pyproject.model.spi.ProjectName
|
||||
import com.intellij.python.pyproject.model.spi.ProjectStructureInfo
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectTomlProject
|
||||
import com.intellij.python.pyproject.model.spi.PyProjectManager
|
||||
import com.intellij.python.pyproject.model.spi.TomlDependencySpecification
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.hatch.sdk.HatchSdkAdditionalData
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -23,7 +23,7 @@ internal class HatchPyProjectManager : PyProjectManager {
|
||||
override val id: ToolId = HATCH_TOOL_ID
|
||||
override val ui: PyToolUIInfo = HATCH_UI_INFO
|
||||
|
||||
override val additionalDataType: Class<HatchSdkAdditionalData> = HatchSdkAdditionalData::class.java
|
||||
override val flavorDataType: Class<HatchSdkFlavor> = HatchSdkFlavor::class.java
|
||||
|
||||
override suspend fun getSrcRoots(toml: TomlTable, projectRoot: Directory): Set<Directory> = emptySet()
|
||||
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import com.intellij.python.pyproject.model.spi.PySdkDependencyGroupSupport
|
||||
import com.intellij.python.pyproject.model.spi.TomlDependencySpecification
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.sdk.poetry.PoetryDependencyGroupSupport
|
||||
import com.jetbrains.python.sdk.poetry.PyPoetrySdkAdditionalData
|
||||
import com.jetbrains.python.sdk.poetry.PyPoetrySdkFlavor
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
|
||||
@@ -22,7 +22,7 @@ internal class PoetryPyProjectManager : PyProjectManager {
|
||||
override val id: ToolId = POETRY_TOOL_ID
|
||||
override val ui: PyToolUIInfo = POETRY_UI_INFO
|
||||
|
||||
override val additionalDataType: Class<PyPoetrySdkAdditionalData> = PyPoetrySdkAdditionalData::class.java
|
||||
override val flavorDataType: Class<PyPoetrySdkFlavor> = PyPoetrySdkFlavor::class.java
|
||||
|
||||
override val dependencyGroupSupport: PySdkDependencyGroupSupport = PoetryDependencyGroupSupport
|
||||
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.python.community.common.tools.ToolId
|
||||
import com.intellij.python.pyproject.dependencies.spi.PyDependencyGroupLocator
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
import com.jetbrains.python.sdk.pySdkAdditionalData
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import org.apache.tuweni.toml.TomlTable
|
||||
@@ -20,14 +20,14 @@ interface PyProjectManager : PyDependencyGroupLocator {
|
||||
|
||||
fun forSdk(sdk: Sdk): PyProjectManager? {
|
||||
val additionalData = sdk.pySdkAdditionalData
|
||||
return EP.extensionList.firstOrNull { it.additionalDataType.isInstance(additionalData) }
|
||||
return EP.extensionList.firstOrNull { it.flavorDataType.isInstance(additionalData.flavor) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used by [forSdk]
|
||||
*/
|
||||
val additionalDataType: Class<out PythonSdkAdditionalData>
|
||||
val flavorDataType: Class<out PythonSdkFlavor<*>>
|
||||
|
||||
/**
|
||||
* CLI adapter for `pyproject.toml` dependency groups (PEP 735 / PEP 621), or `null` when this
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.intellij.python.community.execService.DownloadConfig
|
||||
import com.intellij.python.community.execService.ExecOptions
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.ProcessOutputTransformer
|
||||
import com.intellij.python.community.execService.UploadConfig
|
||||
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
|
||||
import com.intellij.python.community.execService.execute
|
||||
import com.jetbrains.python.Result
|
||||
@@ -37,9 +38,16 @@ suspend fun <T> runExecutableWithProgress(
|
||||
transformer: ProcessOutputTransformer<T>,
|
||||
execService: ExecService = ExecService(),
|
||||
processWeight: ConcurrentProcessWeight = ConcurrentProcessWeight.LIGHT,
|
||||
uploadConfig: UploadConfig? = null,
|
||||
downloadConfig: DownloadConfig? = null,
|
||||
): PyResult<T> {
|
||||
val execOptions = ExecOptions(timeout = timeout, env = env, weight = processWeight, downloadAfterExecution = downloadConfig)
|
||||
val execOptions = ExecOptions(
|
||||
timeout = timeout,
|
||||
env = env,
|
||||
weight = processWeight,
|
||||
uploadBeforeExecution = uploadConfig,
|
||||
downloadAfterExecution = downloadConfig,
|
||||
)
|
||||
|
||||
val errorHandlerTransformer: ProcessOutputTransformer<T> = { output ->
|
||||
when {
|
||||
|
||||
@@ -5,7 +5,9 @@ import com.intellij.execution.Platform
|
||||
import com.intellij.execution.target.FullPathOnTarget
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.python.community.execService.DownloadConfig
|
||||
import com.intellij.python.community.execService.ProcessOutputTransformer
|
||||
import com.intellij.python.community.execService.UploadConfig
|
||||
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.isSuccess
|
||||
@@ -69,12 +71,22 @@ data class ToolCommandExecutor(
|
||||
dirPath: Path?,
|
||||
vararg args: String,
|
||||
env: Map<String, String> = emptyMap(),
|
||||
uploadConfig: UploadConfig? = null,
|
||||
downloadConfig: DownloadConfig? = null,
|
||||
transformer: ProcessOutputTransformer<T>,
|
||||
): PyResult<T> {
|
||||
val executable = getToolExecutable(fileSystem, pathFromSdk)
|
||||
?: return PyResult.localizedError(PySdkBundle.message("cannot.find.executable", toolName, fileSystem.userReadableName))
|
||||
val bin = fileSystem.getBinaryToExec(executable, dirPath)
|
||||
return runExecutableWithProgress(bin, 10.minutes, env = env, args = args, transformer = transformer)
|
||||
return runExecutableWithProgress(
|
||||
binaryToExec = bin,
|
||||
timeout = 10.minutes,
|
||||
env = env,
|
||||
args = args,
|
||||
uploadConfig = uploadConfig,
|
||||
downloadConfig = downloadConfig,
|
||||
transformer = transformer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,5 +100,16 @@ suspend fun <P : PathHolder> ToolCommandExecutor.runTool(
|
||||
dirPath: Path?,
|
||||
vararg args: String,
|
||||
env: Map<String, String> = emptyMap(),
|
||||
uploadConfig: UploadConfig? = null,
|
||||
downloadConfig: DownloadConfig? = null,
|
||||
): PyResult<String> =
|
||||
runTool(fileSystem, pathFromSdk, dirPath, args = args, env = env, transformer = ZeroCodeStdoutTransformer)
|
||||
runTool(
|
||||
fileSystem,
|
||||
pathFromSdk,
|
||||
dirPath,
|
||||
args = args,
|
||||
env = env,
|
||||
uploadConfig = uploadConfig,
|
||||
downloadConfig = downloadConfig,
|
||||
transformer = ZeroCodeStdoutTransformer,
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ import com.intellij.python.uv.common.UV_UI_INFO
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.packaging.PyPackageName
|
||||
import com.jetbrains.python.sdk.uv.UvSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.uv.UvSdkFlavor
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -36,7 +36,7 @@ internal class UvPyProjectManager : PyProjectManager {
|
||||
|
||||
override val ui: PyToolUIInfo = UV_UI_INFO
|
||||
|
||||
override val additionalDataType: Class<UvSdkAdditionalData> get() = UvSdkAdditionalData::class.java
|
||||
override val flavorDataType: Class<UvSdkFlavor> = UvSdkFlavor::class.java
|
||||
|
||||
override val dependencyGroupSupport: PySdkDependencyGroupSupport = UvDependencyGroupSupport
|
||||
|
||||
|
||||
@@ -37,12 +37,19 @@ internal abstract class CustomExistingEnvironmentSelector<P : PathHolder>(
|
||||
|
||||
override fun setupUI(panel: Panel, validationRequestor: DialogValidationRequestor) {
|
||||
with(panel) {
|
||||
val missingExecutableText = if (model.fileSystem.toolPathCanBePersisted) {
|
||||
message("sdk.create.custom.venv.missing.text", name)
|
||||
}
|
||||
else {
|
||||
message("sdk.create.custom.tool.not.detected", name)
|
||||
}
|
||||
executablePath = validatablePathField(
|
||||
fileSystem = model.fileSystem,
|
||||
pathValidator = toolState,
|
||||
validationRequestor = validationRequestor,
|
||||
labelText = message("sdk.create.custom.venv.executable.path", name),
|
||||
missingExecutableText = message("sdk.create.custom.venv.missing.text", name),
|
||||
missingExecutableText = missingExecutableText,
|
||||
canBeEdited = model.fileSystem.toolPathCanBePersisted,
|
||||
)
|
||||
|
||||
comboBox = pythonInterpreterComboBox(
|
||||
|
||||
@@ -48,13 +48,20 @@ internal abstract class CustomNewEnvironmentCreator<P : PathHolder>(
|
||||
onPathSelected = model::addManuallyAddedSystemPython,
|
||||
)
|
||||
|
||||
val missingExecutableText = if (model.fileSystem.toolPathCanBePersisted) {
|
||||
message("sdk.create.custom.venv.missing.text", pyTool.presentableName)
|
||||
}
|
||||
else {
|
||||
message("sdk.create.custom.tool.not.detected", pyTool.presentableName)
|
||||
}
|
||||
executablePath = validatablePathField(
|
||||
fileSystem = model.fileSystem,
|
||||
pathValidator = toolValidator,
|
||||
validationRequestor = validationRequestor,
|
||||
labelText = message("sdk.create.custom.venv.executable.path", pyTool.presentableName),
|
||||
missingExecutableText = message("sdk.create.custom.venv.missing.text", pyTool.presentableName),
|
||||
missingExecutableText = missingExecutableText,
|
||||
installAction = createInstallFix(errorSink),
|
||||
canBeEdited = model.fileSystem.toolPathCanBePersisted,
|
||||
)
|
||||
|
||||
row("") {
|
||||
|
||||
@@ -382,7 +382,7 @@ data class EelFileSystem(
|
||||
|
||||
data class TargetFileSystem(
|
||||
val targetEnvironmentConfiguration: TargetEnvironmentConfiguration,
|
||||
val pythonLanguageRuntimeConfiguration: PythonLanguageRuntimeConfiguration,
|
||||
private val pythonLanguageRuntimeConfiguration: PythonLanguageRuntimeConfiguration,
|
||||
) : FileSystem<PathHolder.Target> {
|
||||
override val isReadOnly: Boolean
|
||||
get() = !PythonInterpreterTargetEnvironmentFactory.isMutable(targetEnvironmentConfiguration)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.intellij.execution.target.TargetEnvironmentConfiguration
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.jetbrains.python.target.PythonLanguageRuntimeConfiguration
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
@ApiStatus.Internal
|
||||
class TargetFileSystemCache {
|
||||
|
||||
private data class CacheKey(private val targetId: String, private val runtimeProperties: PythonLanguageRuntimeConfiguration.State)
|
||||
|
||||
private val cache: Cache<CacheKey, TargetFileSystem> = Caffeine.newBuilder()
|
||||
.expireAfterWrite(5.minutes.toJavaDuration())
|
||||
.build()
|
||||
|
||||
fun getOrCreate(
|
||||
targetConfig: TargetEnvironmentConfiguration,
|
||||
runtimeConfiguration: PythonLanguageRuntimeConfiguration,
|
||||
): TargetFileSystem = cache.get(CacheKey(targetConfig.uuid, runtimeConfiguration.state)) {
|
||||
TargetFileSystem(targetConfig, runtimeConfiguration)
|
||||
}
|
||||
}
|
||||
@@ -295,14 +295,14 @@ private fun <T, P : PathHolder, V : ValidatedPath<T, P>> Panel.installToolRow(
|
||||
installAction: ActionLink,
|
||||
validatedPathField: ValidatedPathField<T, P, V>,
|
||||
): Row {
|
||||
val selectExecutableLink = if (fileSystem.isBrowsable) ActionLink(message("sdk.create.custom.select.executable.link")) {
|
||||
val selectExecutableLink = if (fileSystem.isBrowsable && fileSystem.toolPathCanBePersisted) ActionLink(message("sdk.create.custom.select.executable.link")) {
|
||||
validatedPathField.button.doClick()
|
||||
}
|
||||
else null
|
||||
|
||||
return row("") {
|
||||
validationTooltip(missingExecutableText,
|
||||
installAction,
|
||||
if (fileSystem.toolPathCanBePersisted) installAction else null,
|
||||
selectExecutableLink,
|
||||
validationType = ValidationType.WARNING,
|
||||
inline = true)
|
||||
|
||||
@@ -144,7 +144,7 @@ enum class PythonSupportedEnvironmentManagers(
|
||||
) {
|
||||
VIRTUALENV(VENV_TOOL_ID, "sdk.create.custom.virtualenv", PythonVenvIcons.VirtualEnv, sshAutoUploadRequired = false, { true }),
|
||||
CONDA(CONDA_TOOL_ID, "sdk.create.custom.conda", PythonCommunityImplCondaIcons.Anaconda, sshAutoUploadRequired = false, { true }),
|
||||
POETRY(POETRY_TOOL_ID, "sdk.create.custom.poetry", PythonCommunityImplPoetryCommonIcons.Poetry, sshAutoUploadRequired = false),
|
||||
POETRY(POETRY_TOOL_ID, "sdk.create.custom.poetry", PythonCommunityImplPoetryCommonIcons.Poetry, sshAutoUploadRequired = true, { true }),
|
||||
PIPENV(PIPENV_TOOL_ID, "sdk.create.custom.pipenv", PythonCommunityImplPipenvIcons.PythonClosed, sshAutoUploadRequired = false),
|
||||
UV(UV_TOOL_ID, "sdk.create.custom.uv", PythonUvCommonIcons.UV, sshAutoUploadRequired = true, { true }),
|
||||
HATCH(HATCH_TOOL_ID, "sdk.create.custom.hatch", PythonHatchIcons.Logo, sshAutoUploadRequired = true, { true }),
|
||||
|
||||
@@ -193,12 +193,18 @@ private fun <P : PathHolder> Panel.addExecutableSelector(
|
||||
): ValidatedPathField<Version, P, ValidatedPath.Executable<P>> {
|
||||
|
||||
|
||||
val missingExecutableText = if (model.fileSystem.toolPathCanBePersisted) {
|
||||
message("sdk.create.custom.venv.missing.text", "hatch")
|
||||
}
|
||||
else {
|
||||
message("sdk.create.custom.tool.not.detected", "hatch")
|
||||
}
|
||||
val executablePath = validatablePathField(
|
||||
fileSystem = model.fileSystem,
|
||||
pathValidator = model.hatchViewModel.toolValidator,
|
||||
validationRequestor = validationRequestor,
|
||||
labelText = message("sdk.create.custom.venv.executable.path", "hatch"),
|
||||
missingExecutableText = message("sdk.create.custom.tool.not.detected", "hatch"),
|
||||
missingExecutableText = missingExecutableText,
|
||||
installAction = installHatchActionLink,
|
||||
canBeEdited = model.fileSystem.toolPathCanBePersisted,
|
||||
)
|
||||
|
||||
@@ -20,13 +20,13 @@ import com.intellij.python.pytools.PyTool
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.ui.dsl.builder.bindSelected
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.newProjectWizard.collector.PythonNewProjectWizardCollector
|
||||
import com.jetbrains.python.poetry.PoetryPyProjectTomlPythonVersionsService
|
||||
import com.jetbrains.python.sdk.add.v2.CustomNewEnvironmentCreator
|
||||
import com.jetbrains.python.sdk.add.v2.FileSystem
|
||||
import com.jetbrains.python.sdk.add.v2.PathHolder
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMethod.SELECT_EXISTING
|
||||
import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
|
||||
@@ -119,19 +119,22 @@ internal class EnvironmentCreatorPoetry<P : PathHolder>(
|
||||
|
||||
override suspend fun setupEnvSdk(moduleBasePath: Path): PyResult<Sdk> {
|
||||
val basePythonBinaryPath = model.getOrInstallBasePython()
|
||||
?: return PyResult.localizedError(message("python.sdk.provided.path.is.invalid", null))
|
||||
val poetryExecutable = model.poetryViewModel.poetryExecutable.get()?.pathHolder
|
||||
?: return PyResult.localizedError(message("sdk.create.custom.poetry.error.poetry.executable.path.is.not.valid"))
|
||||
|
||||
service<PoetryConfigService>().updateExistingPoetryToml(moduleBasePath)
|
||||
return when (basePythonBinaryPath) {
|
||||
is PathHolder.Eel -> withProgressText(message("python.sdk.progress.poetry.creating")) {
|
||||
createNewPoetrySdk(
|
||||
moduleBasePath = moduleBasePath,
|
||||
basePythonBinaryPath = basePythonBinaryPath.path,
|
||||
installPackages = false,
|
||||
errorSink = errorSink,
|
||||
inProjectEnv = isInProjectEnvFlow.value,
|
||||
)
|
||||
}
|
||||
else -> PyResult.localizedError(message("target.is.not.supported", basePythonBinaryPath))
|
||||
service<PoetryConfigService>().updateExistingPoetryToml(moduleBasePath, model.fileSystem, poetryExecutable)
|
||||
return withProgressText(message("python.sdk.progress.poetry.creating")) {
|
||||
createNewPoetrySdk(
|
||||
moduleBasePath = moduleBasePath,
|
||||
basePythonBinaryPath = basePythonBinaryPath,
|
||||
fileSystem = model.fileSystem,
|
||||
poetryExecutable = poetryExecutable,
|
||||
installPackages = false,
|
||||
errorSink = errorSink,
|
||||
inProjectEnv = isInProjectEnvFlow.value,
|
||||
targetPanelExtension = model.state.targetPanelExtension.get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,12 +167,21 @@ private class PoetryConfigService :
|
||||
var isInProjectEnv = false
|
||||
}
|
||||
|
||||
suspend fun updateExistingPoetryToml(moduleBasePath: Path) {
|
||||
suspend fun <P : PathHolder> updateExistingPoetryToml(
|
||||
moduleBasePath: Path,
|
||||
fileSystem: FileSystem<P>,
|
||||
poetryExecutable: P?,
|
||||
) {
|
||||
val poetryTomlExists = withContext(Dispatchers.IO) {
|
||||
moduleBasePath.resolve("poetry.toml").exists()
|
||||
}
|
||||
if (poetryTomlExists) {
|
||||
configurePoetryEnvironment(moduleBasePath, "virtualenvs.in-project", state.isInProjectEnv.toString(), "--local")
|
||||
configurePoetryEnvironment(
|
||||
modulePath = moduleBasePath,
|
||||
fileSystem = fileSystem,
|
||||
poetryExecutable = poetryExecutable,
|
||||
"virtualenvs.in-project", state.isInProjectEnv.toString(), "--local",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-13
@@ -5,7 +5,6 @@ import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.observable.properties.ObservableProperty
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
|
||||
import com.intellij.python.community.impl.poetry.common.POETRY_UI_INFO
|
||||
import com.intellij.python.community.impl.poetry.common.poetryPath
|
||||
@@ -20,15 +19,13 @@ import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
|
||||
import com.jetbrains.python.sdk.add.v2.ToolValidator
|
||||
import com.jetbrains.python.sdk.add.v2.ValidatedPath
|
||||
import com.jetbrains.python.sdk.add.v2.savePathForEelOnly
|
||||
import com.jetbrains.python.sdk.baseDir
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.moduleIfExists
|
||||
import com.jetbrains.python.sdk.poetry.createPoetrySdk
|
||||
import com.jetbrains.python.sdk.poetry.detectPoetryEnvs
|
||||
import com.jetbrains.python.sdk.poetry.isPoetry
|
||||
import com.jetbrains.python.sdk.workingDirectory
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
internal class PoetryExistingEnvironmentSelector<P : PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>, module: Module?) :
|
||||
CustomExistingEnvironmentSelector<P>("poetry", model, module) {
|
||||
@@ -41,7 +38,7 @@ internal class PoetryExistingEnvironmentSelector<P : PathHolder>(model: PythonMu
|
||||
|
||||
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): PyResult<Sdk> {
|
||||
|
||||
val pythonBinaryPath = selectedEnv.get()?.homePath as? PathHolder.Eel
|
||||
val pythonBinaryPath = selectedEnv.get()?.homePath
|
||||
?: return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid",
|
||||
selectedEnv.get()?.homePath))
|
||||
|
||||
@@ -49,18 +46,23 @@ internal class PoetryExistingEnvironmentSelector<P : PathHolder>(model: PythonMu
|
||||
return Result.success(it)
|
||||
}
|
||||
|
||||
val basePathString = moduleOrProject.moduleIfExists?.baseDir?.path ?: moduleOrProject.project.basePath
|
||||
val basePath = basePathString?.let { Path.of(it) } ?: error("module base path is not valid: $basePathString")
|
||||
val basePath = moduleOrProject.workingDirectory
|
||||
?: return PyResult.localizedError(PyBundle.message("python.sdk.project.working.directory.not.found"))
|
||||
|
||||
return createPoetrySdk(basePath, pythonBinaryPath)
|
||||
return createPoetrySdk(
|
||||
basePath = basePath,
|
||||
pythonBinaryPath = pythonBinaryPath,
|
||||
fileSystem = model.fileSystem,
|
||||
targetPanelExtension = model.state.targetPanelExtension.get(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun detectEnvironments(modulePath: Path): List<DetectedSelectableInterpreter<P>> {
|
||||
val existingEnvs = detectPoetryEnvs(modulePath).mapNotNull { pythonBinary ->
|
||||
val pythonInfo = ExecService().validatePythonAndGetInfo(pythonBinary).successOrNull ?: return@mapNotNull null
|
||||
model.fileSystem.parsePath(pythonBinary.pathString).successOrNull?.let { fsPath ->
|
||||
DetectedSelectableInterpreter(fsPath, pythonInfo, false, POETRY_UI_INFO)
|
||||
}
|
||||
val poetryExecutable = model.poetryViewModel.poetryExecutable.get()?.pathHolder
|
||||
val existingEnvs = detectPoetryEnvs(modulePath, model.fileSystem, poetryExecutable).mapNotNull { pythonBinary ->
|
||||
val pythonInfo = model.fileSystem.getBinaryToExec(pythonBinary).validatePythonAndGetInfo().successOrNull
|
||||
?: return@mapNotNull null
|
||||
DetectedSelectableInterpreter(pythonBinary, pythonInfo, false, POETRY_UI_INFO)
|
||||
}
|
||||
return existingEnvs
|
||||
}
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
package com.jetbrains.python.sdk.poetry
|
||||
|
||||
import com.intellij.execution.Platform
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.python.community.execService.DownloadConfig
|
||||
import com.intellij.python.community.execService.UploadConfig
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
|
||||
import com.intellij.python.community.impl.poetry.common.poetryPath
|
||||
import com.intellij.python.pyproject.PY_PROJECT_TOML
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.PythonHomePath
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
@@ -27,21 +32,20 @@ import com.jetbrains.python.sdk.ToolSearchPath
|
||||
import com.jetbrains.python.sdk.add.v2.EelFileSystem
|
||||
import com.jetbrains.python.sdk.add.v2.FileSystem
|
||||
import com.jetbrains.python.sdk.add.v2.PathHolder
|
||||
import com.jetbrains.python.sdk.add.v2.TargetFileSystemCache
|
||||
import com.jetbrains.python.sdk.add.v2.toEelFileSystem
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import com.jetbrains.python.sdk.pyRichSdkAsync
|
||||
import com.jetbrains.python.sdk.pySdkAdditionalData
|
||||
import com.jetbrains.python.sdk.runTool
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import com.jetbrains.python.target.PyTargetAwareAdditionalData
|
||||
import com.jetbrains.python.target.PythonLanguageRuntimeConfiguration
|
||||
import io.github.z4kn4fein.semver.Version
|
||||
import io.github.z4kn4fein.semver.toVersion
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.apache.tuweni.toml.Toml
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
/**
|
||||
* This source code is edited by @koxudaxi Koudai Aono <koxudaxi@gmail.com>
|
||||
@@ -57,6 +61,40 @@ internal val POETRY_TOOL: ToolCommandExecutor = ToolCommandExecutor(
|
||||
)
|
||||
|
||||
private val POETRY_EXCLUDE_NON_DIGITS_REGEX = Regex("""\D+$""")
|
||||
private const val POETRY_TOML = "poetry.toml"
|
||||
private const val POETRY_LOCK = "poetry.lock"
|
||||
private val POETRY_PROJECT_FILES = listOf(PY_PROJECT_TOML, POETRY_LOCK, POETRY_TOML)
|
||||
private val POETRY_PROJECT_DOWNLOAD_CONFIG = DownloadConfig(relativePaths = POETRY_PROJECT_FILES)
|
||||
private val POETRY_PROJECT_MUTATING_COMMANDS = setOf("add", "config", "init", "install", "lock", "new", "remove", "update")
|
||||
|
||||
private fun <P : PathHolder> Path.createPoetryMetadataUploadConfig(fileSystem: FileSystem<P>): UploadConfig? =
|
||||
if (fileSystem.isLocal) null
|
||||
else UploadConfig(relativePaths = POETRY_PROJECT_FILES.filter { resolve(it).isRegularFile() })
|
||||
|
||||
@Internal
|
||||
internal suspend fun <P : PathHolder> runPoetry(
|
||||
fileSystem: FileSystem<P>,
|
||||
projectPath: Path?,
|
||||
vararg args: String,
|
||||
poetryExecutable: P? = null,
|
||||
inProjectEnv: Boolean? = null,
|
||||
baseEnv: Map<String, String> = emptyMap(),
|
||||
uploadConfig: UploadConfig? = null,
|
||||
downloadConfig: DownloadConfig? = null,
|
||||
): PyResult<String> {
|
||||
val env = baseEnv.toMutableMap().apply {
|
||||
if (inProjectEnv != null) put("POETRY_VIRTUALENVS_IN_PROJECT", inProjectEnv.toString())
|
||||
}
|
||||
return POETRY_TOOL.runTool(
|
||||
fileSystem = fileSystem,
|
||||
pathFromSdk = poetryExecutable?.toString(),
|
||||
dirPath = projectPath,
|
||||
args = args,
|
||||
env = env,
|
||||
uploadConfig = uploadConfig,
|
||||
downloadConfig = downloadConfig,
|
||||
)
|
||||
}
|
||||
|
||||
@Internal
|
||||
internal suspend fun runPoetry(
|
||||
@@ -65,15 +103,12 @@ internal suspend fun runPoetry(
|
||||
inProjectEnv: Boolean? = null,
|
||||
baseEnv: Map<String, String> = emptyMap(),
|
||||
): PyResult<String> {
|
||||
val env = baseEnv.toMutableMap().apply {
|
||||
if (inProjectEnv != null) put("POETRY_VIRTUALENVS_IN_PROJECT", inProjectEnv.toString())
|
||||
}
|
||||
return POETRY_TOOL.runTool(
|
||||
return runPoetry(
|
||||
fileSystem = projectPath.toEelFileSystem(),
|
||||
pathFromSdk = null,
|
||||
dirPath = projectPath,
|
||||
projectPath = projectPath,
|
||||
args = args,
|
||||
env = env,
|
||||
inProjectEnv = inProjectEnv,
|
||||
baseEnv = baseEnv,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -100,15 +135,53 @@ internal suspend fun getPoetryExecutable(eel: EelApi = localEel): Path? =
|
||||
*/
|
||||
@Internal
|
||||
internal suspend fun runPoetryWithSdk(sdk: Sdk, vararg args: String): PyResult<String> {
|
||||
val projectPath = sdk.pySdkAdditionalData.workingDirectory
|
||||
val pythonHomePath = sdk.pyRichSdkAsync().pythonHomePath
|
||||
?: return PyResult.localizedError(PySdkBundle.message("python.sdk.broken.configuration", sdk.name))
|
||||
val data = sdk.pySdkAdditionalData
|
||||
val projectPath = data.workingDirectory.takeIf { data.hasValidWorkingDirectory() }
|
||||
?: return PyResult.localizedError(PyBundle.message("python.sdk.project.working.directory.not.found"))
|
||||
val sdkHomePath = sdk.homePath
|
||||
?: return PyResult.localizedError(PySdkBundle.message("python.sdk.broken.configuration", sdk.name))
|
||||
|
||||
return when (data) {
|
||||
is PyTargetAwareAdditionalData -> {
|
||||
val targetConfig = data.targetEnvironmentConfiguration
|
||||
?: return PyResult.localizedError(PySdkBundle.message("python.sdk.broken.configuration", sdk.name))
|
||||
val targetFileSystemCache = service<TargetFileSystemCache>()
|
||||
runPoetryWithSdk(
|
||||
fileSystem = targetFileSystemCache.getOrCreate(targetConfig, PythonLanguageRuntimeConfiguration()),
|
||||
projectPath = projectPath,
|
||||
sdkHomePath = sdkHomePath,
|
||||
args = args,
|
||||
)
|
||||
}
|
||||
else -> runPoetryWithSdk(
|
||||
fileSystem = projectPath.toEelFileSystem(),
|
||||
projectPath = projectPath,
|
||||
sdkHomePath = sdkHomePath,
|
||||
args = args,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <P : PathHolder> runPoetryWithSdk(
|
||||
fileSystem: FileSystem<P>,
|
||||
projectPath: Path,
|
||||
sdkHomePath: String,
|
||||
vararg args: String,
|
||||
): PyResult<String> {
|
||||
val pythonPath = fileSystem.parsePath(sdkHomePath).getOr { return it }
|
||||
val pythonHomePath = fileSystem.resolvePythonHome(pythonPath)
|
||||
val env = buildMap {
|
||||
put("POETRY_VIRTUALENVS_IN_PROJECT", "false")
|
||||
put("POETRY_VIRTUALENVS_PREFER_ACTIVE_PYTHON", "true")
|
||||
put("VIRTUAL_ENV", pythonHomePath.toAbsolutePath().toString())
|
||||
put("VIRTUAL_ENV", pythonHomePath.toString())
|
||||
}
|
||||
return runPoetry(projectPath, *args, baseEnv = env)
|
||||
return runPoetry(
|
||||
fileSystem = fileSystem,
|
||||
projectPath = projectPath,
|
||||
args = args,
|
||||
baseEnv = env,
|
||||
downloadConfig = POETRY_PROJECT_DOWNLOAD_CONFIG.takeIf { args.firstOrNull() in POETRY_PROJECT_MUTATING_COMMANDS },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -118,14 +191,16 @@ internal suspend fun runPoetryWithSdk(sdk: Sdk, vararg args: String): PyResult<S
|
||||
* @return the path to the poetry environment.
|
||||
*/
|
||||
@Internal
|
||||
internal suspend fun setupPoetry(
|
||||
internal suspend fun <P : PathHolder> setupPoetry(
|
||||
projectPath: Path,
|
||||
basePythonBinaryPath: PythonBinary,
|
||||
fileSystem: FileSystem<P>,
|
||||
poetryExecutable: P?,
|
||||
basePythonBinaryPath: P,
|
||||
installPackages: Boolean,
|
||||
init: Boolean,
|
||||
errorSink: ErrorSink,
|
||||
inProjectEnv: Boolean = false,
|
||||
): PyResult<PythonHomePath> {
|
||||
): PyResult<P> {
|
||||
if (init) {
|
||||
// Build poetry init command with Python version constraint if available
|
||||
val initArgs = mutableListOf("init", "-n")
|
||||
@@ -137,39 +212,88 @@ internal suspend fun setupPoetry(
|
||||
}
|
||||
|
||||
// Validate Python and get version info
|
||||
val pythonInfo = basePythonBinaryPath.validatePythonAndGetInfo().getOr { return it }
|
||||
val pythonInfo = fileSystem.getBinaryToExec(basePythonBinaryPath).validatePythonAndGetInfo().getOr { return it }
|
||||
val major = pythonInfo.languageLevel.majorVersion
|
||||
val minor = pythonInfo.languageLevel.minorVersion
|
||||
// Add --python flag with caret constraint (e.g., "^3.10")
|
||||
initArgs.add("--python")
|
||||
initArgs.add("^$major.$minor")
|
||||
|
||||
runPoetry(projectPath, *initArgs.toTypedArray(), inProjectEnv = inProjectEnv).getOr { return it }
|
||||
runPoetry(
|
||||
fileSystem = fileSystem,
|
||||
projectPath = projectPath,
|
||||
args = initArgs.toTypedArray(),
|
||||
poetryExecutable = poetryExecutable,
|
||||
inProjectEnv = inProjectEnv,
|
||||
downloadConfig = POETRY_PROJECT_DOWNLOAD_CONFIG,
|
||||
).getOr { return it }
|
||||
}
|
||||
|
||||
runPoetry(projectPath, "env", "use", basePythonBinaryPath.pathString, inProjectEnv = inProjectEnv).getOr { return it }
|
||||
runPoetry(
|
||||
fileSystem = fileSystem,
|
||||
projectPath = projectPath,
|
||||
"env", "use", basePythonBinaryPath.toString(),
|
||||
poetryExecutable = poetryExecutable,
|
||||
inProjectEnv = inProjectEnv,
|
||||
).getOr { return it }
|
||||
|
||||
if (installPackages) {
|
||||
runPoetry(projectPath, "install", "--no-root", inProjectEnv = inProjectEnv).onFailure { errorSink.emit(it) }
|
||||
runPoetry(
|
||||
fileSystem = fileSystem,
|
||||
projectPath = projectPath,
|
||||
"install", "--no-root",
|
||||
poetryExecutable = poetryExecutable,
|
||||
inProjectEnv = inProjectEnv,
|
||||
downloadConfig = POETRY_PROJECT_DOWNLOAD_CONFIG,
|
||||
).onFailure { errorSink.emit(it) }
|
||||
}
|
||||
|
||||
return runPoetry(projectPath, "env", "info", "-p", inProjectEnv = inProjectEnv).mapSuccess { Path.of(it) }
|
||||
val pythonHomePath = runPoetry(
|
||||
fileSystem = fileSystem,
|
||||
projectPath = projectPath,
|
||||
"env", "info", "-p",
|
||||
poetryExecutable = poetryExecutable,
|
||||
inProjectEnv = inProjectEnv,
|
||||
).getOr { return it }
|
||||
return fileSystem.parsePath(pythonHomePath.trim())
|
||||
}
|
||||
|
||||
internal suspend fun detectPoetryEnvs(searchPath: Path): List<PythonBinary> =
|
||||
getPoetryEnvs(searchPath).mapNotNull { getPythonExecutable(it) }
|
||||
|
||||
internal suspend fun getPoetryVersion(): String? =
|
||||
runPoetry(null, "--version")
|
||||
.getOrNull()
|
||||
?.split(' ')
|
||||
?.lastOrNull()
|
||||
?.replace(POETRY_EXCLUDE_NON_DIGITS_REGEX, "") // strip all non-numeric characters after the version
|
||||
|
||||
private suspend fun getPythonExecutable(homePathString: String): PythonBinary? = withContext(Dispatchers.IO) {
|
||||
VirtualEnvReader().findPythonInPythonRoot(Path.of(homePathString))
|
||||
@Internal
|
||||
internal suspend fun setupPoetry(
|
||||
projectPath: Path,
|
||||
basePythonBinaryPath: PythonBinary,
|
||||
installPackages: Boolean,
|
||||
init: Boolean,
|
||||
errorSink: ErrorSink,
|
||||
inProjectEnv: Boolean = false,
|
||||
): PyResult<PythonHomePath> {
|
||||
val fileSystem = projectPath.toEelFileSystem()
|
||||
return setupPoetry(
|
||||
projectPath = projectPath,
|
||||
fileSystem = fileSystem,
|
||||
poetryExecutable = null,
|
||||
basePythonBinaryPath = PathHolder.Eel(basePythonBinaryPath),
|
||||
installPackages = installPackages,
|
||||
init = init,
|
||||
errorSink = errorSink,
|
||||
inProjectEnv = inProjectEnv,
|
||||
).mapSuccess { it.path }
|
||||
}
|
||||
|
||||
internal suspend fun <P : PathHolder> detectPoetryEnvs(
|
||||
searchPath: Path,
|
||||
fileSystem: FileSystem<P>,
|
||||
poetryExecutable: P? = null,
|
||||
): List<P> = getPoetryEnvs(searchPath, fileSystem, poetryExecutable).mapNotNull { homePath ->
|
||||
fileSystem.parsePath(homePath).successOrNull?.let { fileSystem.resolvePythonBinary(it) }
|
||||
}
|
||||
|
||||
private suspend fun getPoetryVersion(sdk: Sdk): String? =
|
||||
runPoetryWithSdk(sdk, "--version").getOrNull()?.parsePoetryVersion()
|
||||
|
||||
private fun String.parsePoetryVersion(): String? =
|
||||
split(' ').lastOrNull()?.replace(POETRY_EXCLUDE_NON_DIGITS_REGEX, "")
|
||||
|
||||
/**
|
||||
* Installs a Python package using Poetry.
|
||||
* Runs `poetry add [packages] [extraArgs]`
|
||||
@@ -226,7 +350,7 @@ internal suspend fun poetryShowOutdated(sdk: Sdk): PyResult<Map<String, PythonOu
|
||||
|
||||
@Internal
|
||||
internal suspend fun poetryListPackages(sdk: Sdk): PyResult<Pair<List<PyPackage>, List<PyRequirement>>> {
|
||||
val version = getPoetryVersion()?.toVersion()
|
||||
val version = getPoetryVersion(sdk)?.toVersion()
|
||||
|
||||
// Ensure that the lock file is up to date.
|
||||
if (!Registry.get("python.poetry.list.packages.without.lock").asBoolean() && !checkLock(sdk, version)) {
|
||||
@@ -327,11 +451,36 @@ internal fun parsePoetryLockEditablePackages(lockContent: String): Map<String, S
|
||||
* @param [args] A vararg array of String arguments to pass to the Poetry configuration command.
|
||||
*/
|
||||
@Internal
|
||||
internal suspend fun configurePoetryEnvironment(modulePath: Path?, vararg args: String) {
|
||||
runPoetry(modulePath, "config", *args)
|
||||
internal suspend fun <P : PathHolder> configurePoetryEnvironment(
|
||||
modulePath: Path?,
|
||||
fileSystem: FileSystem<P>,
|
||||
poetryExecutable: P? = null,
|
||||
vararg args: String,
|
||||
) {
|
||||
runPoetry(
|
||||
fileSystem = fileSystem,
|
||||
projectPath = modulePath,
|
||||
"config", *args,
|
||||
poetryExecutable = poetryExecutable,
|
||||
downloadConfig = POETRY_PROJECT_DOWNLOAD_CONFIG,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getPoetryEnvs(projectPath: Path): List<String> {
|
||||
val executionResult = runPoetry(projectPath, "env", "list", "--full-path")
|
||||
internal suspend fun configurePoetryEnvironment(modulePath: Path?, vararg args: String) {
|
||||
configurePoetryEnvironment(modulePath, modulePath.toEelFileSystem(), args = args)
|
||||
}
|
||||
|
||||
private suspend fun <P : PathHolder> getPoetryEnvs(
|
||||
projectPath: Path,
|
||||
fileSystem: FileSystem<P>,
|
||||
poetryExecutable: P?,
|
||||
): List<String> {
|
||||
val executionResult = runPoetry(
|
||||
fileSystem = fileSystem,
|
||||
projectPath = projectPath,
|
||||
"env", "list", "--full-path",
|
||||
poetryExecutable = poetryExecutable,
|
||||
uploadConfig = projectPath.createPoetryMetadataUploadConfig(fileSystem),
|
||||
)
|
||||
return executionResult.getOrNull()?.lineSequence()?.map { it.split(" ")[0] }?.filterNot { it.isEmpty() }?.toList() ?: emptyList()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.python.pyproject.model.api.getPyProjectTomlFile
|
||||
import com.jetbrains.python.sdk.associatedModuleNioPath
|
||||
import com.jetbrains.python.sdk.findModuleForSdk
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.getOrNull
|
||||
@@ -30,10 +29,10 @@ import com.jetbrains.python.packaging.packageRequirements.PackageStructureNode
|
||||
import com.jetbrains.python.packaging.packageRequirements.TreeParser
|
||||
import com.jetbrains.python.packaging.packageRequirements.collectAllNames
|
||||
import com.jetbrains.python.packaging.pip.PipRepositoryManager
|
||||
import com.jetbrains.python.packaging.pyRequirement
|
||||
import com.intellij.python.pyproject.PY_PROJECT_TOML
|
||||
import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.jetbrains.python.poetry.POETRY_LOCK
|
||||
import com.jetbrains.python.sdk.pySdkAdditionalData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -198,12 +197,13 @@ internal class PoetryPackageManager(project: Project, sdk: Sdk) : PythonPackageM
|
||||
}
|
||||
|
||||
private suspend fun editablePackagesFromLock(): Map<String, URI?> {
|
||||
val projectPath = sdk.associatedModuleNioPath ?: return emptyMap()
|
||||
val data = sdk.pySdkAdditionalData
|
||||
val workingDir = data.workingDirectory.takeIf { data.hasValidWorkingDirectory() } ?: return emptyMap()
|
||||
val content = withContext(Dispatchers.IO) {
|
||||
try { projectPath.resolve(POETRY_LOCK.value).readText() } catch (_: IOException) { null }
|
||||
try { workingDir.resolve(POETRY_LOCK.value).readText() } catch (_: IOException) { null }
|
||||
} ?: return emptyMap()
|
||||
return parsePoetryLockEditablePackages(content).mapValues { (_, url) ->
|
||||
url?.let { resolveEditableLocation(it, projectPath) }
|
||||
url?.let { resolveEditableLocation(it, workingDir) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,4 +314,4 @@ fun parsePoetryShowOutdatedTest(input: String): Map<String, PythonOutdatedPackag
|
||||
private fun PythonPackageInstallRequest.ByLocation.toPoetryArgument(): String {
|
||||
if (location.scheme != "file") return location.toString()
|
||||
return location.path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,22 +11,20 @@ import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
|
||||
import com.jetbrains.python.sdk.add.v2.FileSystem
|
||||
import com.jetbrains.python.sdk.add.v2.PathHolder
|
||||
import com.jetbrains.python.sdk.createSdk
|
||||
import com.jetbrains.python.sdk.pySdkAdditionalData
|
||||
import com.jetbrains.python.sdk.impl.resolvePythonBinary
|
||||
import com.jetbrains.python.sdk.add.v2.toEelFileSystem
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import com.jetbrains.python.sdk.pySdkAdditionalData
|
||||
import com.jetbrains.python.target.ui.TargetPanelExtension
|
||||
import java.nio.file.Path
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
|
||||
@Internal
|
||||
internal fun suggestedSdkName(basePath: Path): @NlsSafe String = "Poetry (${PathUtil.getFileName(basePath.pathString)})"
|
||||
|
||||
|
||||
@Internal
|
||||
internal suspend fun createNewPoetrySdk(
|
||||
moduleBasePath: Path,
|
||||
basePythonBinaryPath: PythonBinary,
|
||||
@@ -34,37 +32,85 @@ internal suspend fun createNewPoetrySdk(
|
||||
errorSink: ErrorSink,
|
||||
inProjectEnv: Boolean = false,
|
||||
): PyResult<Sdk> {
|
||||
val pythonBinaryPath = setUpPoetry(moduleBasePath, basePythonBinaryPath, installPackages, errorSink, inProjectEnv).getOr { return it }
|
||||
|
||||
return createPoetrySdk(
|
||||
basePath = moduleBasePath,
|
||||
pythonBinaryPath = PathHolder.Eel(pythonBinaryPath)
|
||||
val fileSystem = moduleBasePath.toEelFileSystem()
|
||||
return createNewPoetrySdk(
|
||||
moduleBasePath = moduleBasePath,
|
||||
basePythonBinaryPath = PathHolder.Eel(basePythonBinaryPath),
|
||||
fileSystem = fileSystem,
|
||||
poetryExecutable = null,
|
||||
installPackages = installPackages,
|
||||
errorSink = errorSink,
|
||||
inProjectEnv = inProjectEnv,
|
||||
)
|
||||
}
|
||||
|
||||
@Internal
|
||||
internal suspend fun createPoetrySdk(
|
||||
basePath: Path,
|
||||
pythonBinaryPath: PathHolder.Eel,
|
||||
): PyResult<Sdk> = withProgressText(PyBundle.message("python.sdk.progress.poetry.configuring")) {
|
||||
createSdk(
|
||||
internal suspend fun <P : PathHolder> createNewPoetrySdk(
|
||||
moduleBasePath: Path,
|
||||
basePythonBinaryPath: P,
|
||||
fileSystem: FileSystem<P>,
|
||||
poetryExecutable: P?,
|
||||
installPackages: Boolean,
|
||||
errorSink: ErrorSink,
|
||||
inProjectEnv: Boolean = false,
|
||||
targetPanelExtension: TargetPanelExtension? = null,
|
||||
): PyResult<Sdk> {
|
||||
val pythonBinaryPath = setUpPoetry(
|
||||
moduleBasePath = moduleBasePath,
|
||||
basePythonBinaryPath = basePythonBinaryPath,
|
||||
fileSystem = fileSystem,
|
||||
poetryExecutable = poetryExecutable,
|
||||
installPackages = installPackages,
|
||||
errorSink = errorSink,
|
||||
inProjectEnv = inProjectEnv,
|
||||
).getOr { return it }
|
||||
|
||||
return createPoetrySdk(
|
||||
basePath = moduleBasePath,
|
||||
pythonBinaryPath = pythonBinaryPath,
|
||||
sdkAdditionalData = PyPoetrySdkAdditionalData(basePath)
|
||||
)}
|
||||
fileSystem = fileSystem,
|
||||
targetPanelExtension = targetPanelExtension,
|
||||
)
|
||||
}
|
||||
|
||||
internal suspend fun <P : PathHolder> createPoetrySdk(
|
||||
basePath: Path,
|
||||
pythonBinaryPath: P,
|
||||
fileSystem: FileSystem<P>,
|
||||
targetPanelExtension: TargetPanelExtension? = null,
|
||||
): PyResult<Sdk> = withProgressText(PyBundle.message("python.sdk.progress.poetry.configuring")) {
|
||||
fileSystem.setupSdk(
|
||||
project = null,
|
||||
pythonBinaryPath = pythonBinaryPath,
|
||||
sdkAdditionalData = PyPoetrySdkAdditionalData(basePath),
|
||||
targetPanelExtension = targetPanelExtension,
|
||||
suggestedSdkName = null,
|
||||
)
|
||||
}
|
||||
|
||||
internal val Sdk.isPoetry: Boolean
|
||||
get() {
|
||||
if (!PythonSdkUtil.isPythonSdk(this)) {
|
||||
return false
|
||||
}
|
||||
get() = PythonSdkUtil.isPythonSdk(this) && pySdkAdditionalData.flavor == PyPoetrySdkFlavor
|
||||
|
||||
return pySdkAdditionalData is PyPoetrySdkAdditionalData
|
||||
}
|
||||
|
||||
private suspend fun setUpPoetry(moduleBasePath: Path, basePythonBinaryPath: PythonBinary, installPackages: Boolean, errorSink: ErrorSink, inProjectEnv: Boolean = false): PyResult<PythonBinary> {
|
||||
private suspend fun <P : PathHolder> setUpPoetry(
|
||||
moduleBasePath: Path,
|
||||
basePythonBinaryPath: P,
|
||||
fileSystem: FileSystem<P>,
|
||||
poetryExecutable: P?,
|
||||
installPackages: Boolean,
|
||||
errorSink: ErrorSink,
|
||||
inProjectEnv: Boolean,
|
||||
): PyResult<P> {
|
||||
val init = PyProjectToml.findInRoot(moduleBasePath) == null
|
||||
val pythonHomePath = setupPoetry(moduleBasePath, basePythonBinaryPath, installPackages, init, errorSink, inProjectEnv).getOr { return it }
|
||||
val pythonBinaryPath = pythonHomePath.resolvePythonBinary()
|
||||
val pythonHomePath = setupPoetry(
|
||||
projectPath = moduleBasePath,
|
||||
fileSystem = fileSystem,
|
||||
poetryExecutable = poetryExecutable,
|
||||
basePythonBinaryPath = basePythonBinaryPath,
|
||||
installPackages = installPackages,
|
||||
init = init,
|
||||
errorSink = errorSink,
|
||||
inProjectEnv = inProjectEnv,
|
||||
).getOr { return it }
|
||||
val pythonBinaryPath = fileSystem.resolvePythonBinary(pythonHomePath)
|
||||
?: return PyResult.localizedError(PyBundle.message("python.sdk.cannot.setup.sdk", pythonHomePath))
|
||||
return PyResult.success(pythonBinaryPath)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user