From e464ea9467798b34b7171b4ff1901b13b254c221 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Sun, 27 May 2018 21:23:48 +0300 Subject: [PATCH] PY-26492 Package indices based on Pipfile.lock --- .../packaging/PyPackageManagersImpl.java | 3 + .../PyPipEnvPackageManagementService.kt | 67 +++++++++++++++++++ .../ui/PyPackageManagementService.java | 9 ++- .../jetbrains/python/sdk/flavors/pipenv.kt | 39 +++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 python/src/com/jetbrains/python/packaging/PyPipEnvPackageManagementService.kt diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java index f6a840518fa1..3331c7320b69 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java @@ -62,6 +62,9 @@ public class PyPackageManagersImpl extends PyPackageManagers { if (PyCondaPackageManagerImpl.isConda(sdk)) { return new PyCondaManagementService(project, sdk); } + else if (PipenvKt.isPipEnv(sdk)) { + return new PyPipEnvPackageManagementService(project, sdk); + } return new PyPackageManagementService(project, sdk); } diff --git a/python/src/com/jetbrains/python/packaging/PyPipEnvPackageManagementService.kt b/python/src/com/jetbrains/python/packaging/PyPipEnvPackageManagementService.kt new file mode 100644 index 000000000000..8741dfee7362 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/PyPipEnvPackageManagementService.kt @@ -0,0 +1,67 @@ +// 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.packaging + +import com.intellij.execution.ExecutionException +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.util.CatchingConsumer +import com.intellij.webcore.packaging.InstalledPackage +import com.intellij.webcore.packaging.RepoPackage +import com.jetbrains.python.packaging.ui.PyPackageManagementService +import com.jetbrains.python.sdk.flavors.pipFileLockSources +import java.lang.Exception + +/** + * @author vlan + */ +class PyPipEnvPackageManagementService(project: Project, sdk: Sdk) : PyPackageManagementService(project, sdk) { + override fun getAllRepositories(): List? = null + + override fun canInstallToUser() = false + + override fun getAllPackages(): List { + PyPIPackageUtil.INSTANCE.loadAdditionalPackages(sdk.pipFileLockSources, false) + return allPackagesCached + } + + override fun reloadAllPackages(): List { + PyPIPackageUtil.INSTANCE.loadAdditionalPackages(sdk.pipFileLockSources, true) + return allPackagesCached + } + + override fun getAllPackagesCached(): List = + PyPIPackageUtil.INSTANCE.getAdditionalPackages(sdk.pipFileLockSources) + + override fun installPackage(repoPackage: RepoPackage, + version: String?, + forceUpgrade: Boolean, + extraOptions: String?, + listener: Listener, + installToUser: Boolean) { + val ui = PyPackageManagerUI(project, sdk, object : PyPackageManagerUI.Listener { + override fun started() { + listener.operationStarted(repoPackage.name) + } + + override fun finished(exceptions: List?) { + listener.operationFinished(repoPackage.name, toErrorDescription(exceptions, sdk)) + } + }) + val requirement = when { + version != null -> PyRequirement(repoPackage.name, version) + else -> PyRequirement(repoPackage.name) + } + val extraArgs = extraOptions?.split(" +".toRegex()) ?: emptyList() + ui.install(listOf(requirement), extraArgs) + } + + override fun fetchPackageVersions(packageName: String?, consumer: CatchingConsumer, Exception>?) { + // TODO: Override it for pipenv indices + super.fetchPackageVersions(packageName, consumer) + } + + override fun fetchLatestVersion(pkg: InstalledPackage, consumer: CatchingConsumer) { + // TODO: Override it for pipenv indices + super.fetchLatestVersion(pkg, consumer) + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java index 6457c37b015b..0f4c6ef54a8d 100644 --- a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java +++ b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java @@ -56,8 +56,8 @@ public class PyPackageManagementService extends PackageManagementServiceEx { @NonNls private static final String TEXT_SUFFIX = ""; - private final Project myProject; - protected final Sdk mySdk; + @NotNull private final Project myProject; + @NotNull protected final Sdk mySdk; protected final ExecutorService myExecutorService; public PyPackageManagementService(@NotNull Project project, @NotNull Sdk sdk) { @@ -72,6 +72,11 @@ public class PyPackageManagementService extends PackageManagementServiceEx { return mySdk; } + @NotNull + public Project getProject() { + return myProject; + } + @Nullable @Override public List getAllRepositories() { diff --git a/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt b/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt index eb99676eac13..2be8cf68a0e6 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt +++ b/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt @@ -1,6 +1,9 @@ // 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.sdk.flavors +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException +import com.google.gson.annotations.SerializedName import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.ProblemDescriptor import com.intellij.execution.ExecutionException @@ -10,6 +13,8 @@ import com.intellij.execution.configurations.PathEnvironmentVariableUtil import com.intellij.execution.process.CapturingProcessHandler import com.intellij.execution.process.ProcessOutput import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.progress.ProgressIndicator @@ -21,6 +26,7 @@ import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.PathUtil import com.jetbrains.python.packaging.PyExecutionException @@ -35,6 +41,8 @@ import javax.swing.Icon */ const val PIP_FILE: String = "Pipfile" +const val PIP_FILE_LOCK: String = "Pipfile.lock" +const val PIPENV_DEFAULT_SOURCE_URL: String = "https://pypi.org/simple" // TODO: Provide a special icon for pipenv val PIPENV_ICON: Icon = PythonIcons.Python.PythonClosed @@ -171,6 +179,13 @@ fun detectAndSetupPipEnv(project: Project?, module: Module?, existingSdks: List< return setupPipEnvSdkUnderProgress(project, module, existingSdks, null, null, false) } +/** + * URLs of package sources configured in the Pipfile.lock of the module associated with this SDK. + */ +val Sdk.pipFileLockSources: List + get() = parsePipFileLock()?.meta?.sources?.mapNotNull { it.url } ?: + listOf(PIPENV_DEFAULT_SOURCE_URL) + /** * A quick-fix for setting up the pipenv for the module of the current PSI element. */ @@ -209,3 +224,27 @@ class UsePipEnvQuickFix : LocalQuickFix { } } } + +private fun Sdk.parsePipFileLock(): PipFileLock? { + // TODO: Log errors if Pipfile.lock is not found + val file = pipFileLock ?: return null + val text = ReadAction.compute { FileDocumentManager.getInstance().getDocument(file)?.text } + return try { + Gson().fromJson(text, PipFileLock::class.java) + } + catch (e: JsonSyntaxException) { + // TODO: Log errors + return null + } +} + +private val Sdk.pipFileLock: VirtualFile? + get() = + associatedModulePath?.let { StandardFileSystems.local().findFileByPath(it)?.findChild(PIP_FILE_LOCK) } + +private data class PipFileLock(@SerializedName("_meta") var meta: PipFileLockMeta?) + +private data class PipFileLockMeta(@SerializedName("sources") var sources: List?) + +private data class PipFileLockSource(@SerializedName("url") var url: String?) +