PY-26492 Quick-fix to install missing requirements via 'pipenv install'

This commit is contained in:
Andrey Vlasovskikh
2018-06-15 21:24:41 +03:00
committed by Andrey Vlasovskikh
parent 5ad19e832e
commit 5fc64675b9
7 changed files with 83 additions and 38 deletions
@@ -45,7 +45,7 @@ public abstract class PyPackageManager {
public abstract void install(@NotNull String requirementString) throws ExecutionException;
public abstract void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws ExecutionException;
public abstract void install(@Nullable List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws ExecutionException;
public abstract void uninstall(@NotNull List<PyPackage> packages) throws ExecutionException;
@@ -32,6 +32,8 @@ import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.sdk.PySdkExtKt;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.flavors.PipEnvInstallQuickFix;
import com.jetbrains.python.sdk.flavors.PipenvKt;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -111,7 +113,12 @@ public class PyPackageRequirementsInspection extends PyInspection {
unsatisfiedNames.add(req.getName() + req.getExtras());
}
final List<LocalQuickFix> quickFixes = new ArrayList<>();
quickFixes.add(new PyInstallRequirementsFix(null, module, sdk, unsatisfied));
if (PipenvKt.isPipEnv(sdk)) {
quickFixes.add(new PipEnvInstallQuickFix());
}
else {
quickFixes.add(new PyInstallRequirementsFix(null, module, sdk, unsatisfied));
}
quickFixes.add(new IgnoreRequirementFix(unsatisfiedNames));
registerProblem(file, msg,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, null,
@@ -402,7 +409,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
return;
}
if (!PyPackageUtil.hasManagement(packages)) {
final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) {
final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new RunningPackagingTasksListener(myModule) {
@Override
public void finished(List<ExecutionException> exceptions) {
super.finished(exceptions);
@@ -419,7 +426,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
}
private void installRequirements(Project project, List<PyRequirement> requirements) {
final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule));
final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new RunningPackagingTasksListener(myModule));
ui.install(requirements, Collections.emptyList());
}
}
@@ -468,7 +475,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
private void installAndImportPackage(@NotNull Project project) {
if (mySdk == null) return;
final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) {
final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new RunningPackagingTasksListener(myModule) {
@Override
public void finished(List<ExecutionException> exceptions) {
super.finished(exceptions);
@@ -488,10 +495,10 @@ public class PyPackageRequirementsInspection extends PyInspection {
}
}
private static class UIListener implements PyPackageManagerUI.Listener {
private final Module myModule;
public static class RunningPackagingTasksListener implements PyPackageManagerUI.Listener {
@NotNull private final Module myModule;
public UIListener(Module module) {
public RunningPackagingTasksListener(@NotNull Module module) {
myModule = module;
}
@@ -57,8 +57,9 @@ public class PyCondaPackageManagerImpl extends PyPackageManagerImpl {
}
@Override
public void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws ExecutionException {
public void install(@Nullable List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws ExecutionException {
if (useConda) {
if (requirements == null) return;
final ArrayList<String> arguments = new ArrayList<>();
for (PyRequirement requirement : requirements) {
arguments.add(requirement.getPresentableText());
@@ -178,7 +178,8 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
@Override
public void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws ExecutionException {
public void install(@Nullable List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws ExecutionException {
if (requirements == null) return;
installManagement();
final List<String> args = new ArrayList<>();
args.add(INSTALL);
@@ -68,7 +68,7 @@ public class PyPackageManagerUI {
ProgressManager.getInstance().run(new InstallManagementTask(myProject, mySdk, myListener));
}
public void install(@NotNull final List<PyRequirement> requirements, @NotNull final List<String> extraArgs) {
public void install(@Nullable final List<PyRequirement> requirements, @NotNull final List<String> extraArgs) {
ProgressManager.getInstance().run(new InstallTask(myProject, mySdk, requirements, extraArgs, myListener));
}
@@ -225,12 +225,12 @@ public class PyPackageManagerUI {
}
private static class InstallTask extends PackagingTask {
@NotNull private final List<PyRequirement> myRequirements;
@Nullable private final List<PyRequirement> myRequirements;
@NotNull private final List<String> myExtraArgs;
public InstallTask(@Nullable Project project,
@NotNull Sdk sdk,
@NotNull List<PyRequirement> requirements,
@Nullable List<PyRequirement> requirements,
@NotNull List<String> extraArgs,
@Nullable Listener listener) {
super(project, sdk, "Installing packages", listener);
@@ -242,29 +242,44 @@ public class PyPackageManagerUI {
@Override
protected List<ExecutionException> runTask(@NotNull ProgressIndicator indicator) {
final List<ExecutionException> exceptions = new ArrayList<>();
final int size = myRequirements.size();
final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
for (int i = 0; i < size; i++) {
final PyRequirement requirement = myRequirements.get(i);
indicator.setText(String.format("Installing package '%s'...", requirement.getPresentableText()));
if (i == 0) {
indicator.setIndeterminate(true);
}
else {
indicator.setIndeterminate(false);
indicator.setFraction((double)i / size);
}
if (myRequirements == null) {
indicator.setText("Installing packages...");
indicator.setIndeterminate(true);
try {
manager.install(Collections.singletonList(requirement), myExtraArgs);
manager.install(null, myExtraArgs);
}
catch (RunCanceledByUserException e) {
exceptions.add(e);
break;
}
catch (ExecutionException e) {
exceptions.add(e);
}
}
else {
final int size = myRequirements.size();
for (int i = 0; i < size; i++) {
final PyRequirement requirement = myRequirements.get(i);
indicator.setText(String.format("Installing package '%s'...", requirement.getPresentableText()));
if (i == 0) {
indicator.setIndeterminate(true);
}
else {
indicator.setIndeterminate(false);
indicator.setFraction((double)i / size);
}
try {
manager.install(Collections.singletonList(requirement), myExtraArgs);
}
catch (RunCanceledByUserException e) {
exceptions.add(e);
break;
}
catch (ExecutionException e) {
exceptions.add(e);
}
}
}
manager.refresh();
return exceptions;
}
@@ -278,7 +293,9 @@ public class PyPackageManagerUI {
@NotNull
@Override
protected String getSuccessDescription() {
return "Installed packages: " + PyPackageUtil.requirementsToString(myRequirements);
return myRequirements != null ?
"Installed packages: " + PyPackageUtil.requirementsToString(myRequirements) :
"Installed all requirements";
}
@NotNull
@@ -30,10 +30,11 @@ class PyPipEnvPackageManager(val sdk: Sdk) : PyPackageManager() {
install(parseRequirements(requirementString), emptyList())
}
override fun install(requirements: List<PyRequirement>, extraArgs: List<String>) {
val args = listOf("install") +
requirements.flatMap { it.installOptions } +
extraArgs
override fun install(requirements: List<PyRequirement>?, extraArgs: List<String>) {
val args = listOfNotNull(listOf("install"),
requirements?.flatMap { it.installOptions },
extraArgs)
.flatten()
try {
runPipEnv(sdk, *args.toTypedArray())
}
@@ -29,10 +29,8 @@ 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
import com.jetbrains.python.packaging.PyPackageManager
import com.jetbrains.python.packaging.PyPackageManagers
import com.jetbrains.python.packaging.PyRequirement
import com.jetbrains.python.inspections.PyPackageRequirementsInspection
import com.jetbrains.python.packaging.*
import com.jetbrains.python.sdk.*
import icons.PythonIcons
import org.jetbrains.annotations.SystemDependent
@@ -225,9 +223,6 @@ class UsePipEnvQuickFix : LocalQuickFix {
fun isApplicable(module: Module): Boolean = module.pipFile != null
fun setUpPipEnv(project: Project, module: Module) {
if (project.isDisposed || module.isDisposed) {
return
}
val sdksModel = ProjectSdksModel().apply {
reset(project)
}
@@ -251,11 +246,34 @@ class UsePipEnvQuickFix : LocalQuickFix {
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return
// Invoke the setup later to escape the write action of the quick fix in order to show the modal progress dialog
ApplicationManager.getApplication().invokeLater {
if (project.isDisposed || module.isDisposed) return@invokeLater
setUpPipEnv(project, module)
}
}
}
/**
* A quick-fix for installing packages specified in Pipfile.
*/
class PipEnvInstallQuickFix : LocalQuickFix {
companion object {
fun pipEnvInstall(project: Project, module: Module) {
val sdk = module.pythonSdk ?: return
val listener = PyPackageRequirementsInspection.RunningPackagingTasksListener(module)
val ui = PyPackageManagerUI(project, sdk, listener)
ui.install(null, listOf("--dev"))
}
}
override fun getFamilyName() = "Install requirements from Pipfile"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement ?: return
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return
pipEnvInstall(project, module)
}
}
private fun Sdk.parsePipFileLock(): PipFileLock? {
// TODO: Log errors if Pipfile.lock is not found
val file = pipFileLock ?: return null