PY-26492 Package indices based on Pipfile.lock

This commit is contained in:
Andrey Vlasovskikh
2018-06-15 21:24:41 +03:00
parent 3236f9c171
commit e464ea9467
4 changed files with 116 additions and 2 deletions
@@ -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);
}
@@ -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<String>? = null
override fun canInstallToUser() = false
override fun getAllPackages(): List<RepoPackage> {
PyPIPackageUtil.INSTANCE.loadAdditionalPackages(sdk.pipFileLockSources, false)
return allPackagesCached
}
override fun reloadAllPackages(): List<RepoPackage> {
PyPIPackageUtil.INSTANCE.loadAdditionalPackages(sdk.pipFileLockSources, true)
return allPackagesCached
}
override fun getAllPackagesCached(): List<RepoPackage> =
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<ExecutionException>?) {
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<MutableList<String>, Exception>?) {
// TODO: Override it for pipenv indices
super.fetchPackageVersions(packageName, consumer)
}
override fun fetchLatestVersion(pkg: InstalledPackage, consumer: CatchingConsumer<String, Exception>) {
// TODO: Override it for pipenv indices
super.fetchLatestVersion(pkg, consumer)
}
}
@@ -56,8 +56,8 @@ public class PyPackageManagementService extends PackageManagementServiceEx {
@NonNls private static final String TEXT_SUFFIX = "</body></html>";
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<String> getAllRepositories() {
@@ -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<String>
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<String, Throwable> { 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<PipFileLockSource>?)
private data class PipFileLockSource(@SerializedName("url") var url: String?)