Prompt to install dependencies from plugins repository when installing plugins from disk (IDEA-245012)

GitOrigin-RevId: 9c3d0833d13e7aee01a5fc9e929d027de7dd0dc7
This commit is contained in:
Dmitry Jemerov
2020-07-07 12:03:00 +00:00
committed by intellij-monorepo-bot
parent 94500445ed
commit 3d1778cc1d
3 changed files with 102 additions and 59 deletions
@@ -27,7 +27,7 @@ public class PluginDropHandler extends CustomFileDropHandler {
public boolean handleDrop(@NotNull Transferable t, @Nullable Editor editor, Project project) {
File file = getFile(t);
if (file == null) return false;
return PluginInstaller.install(new InstalledPluginsTableModel(), file, PluginInstallCallbackDataKt::installPluginFromCallbackData, null);
return PluginInstaller.installFromDisk(new InstalledPluginsTableModel(), file, PluginInstallCallbackDataKt::installPluginFromCallbackData, null);
}
@Nullable
@@ -19,7 +19,6 @@ import com.intellij.openapi.updateSettings.impl.PluginDownloader;
import com.intellij.openapi.updateSettings.impl.UpdateSettings;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.ApiStatus;
@@ -37,7 +36,7 @@ public class PluginInstallOperation {
private final PluginManagerMain.PluginEnabler myPluginEnabler;
private final ProgressIndicator myIndicator;
private boolean mySuccess = true;
private final Set<PluginNode> myDependant = new HashSet<>();
private final Set<PluginInstallCallbackData> myDependant = new HashSet<>();
private boolean myAllowInstallWithoutRestart = false;
private final List<PendingDynamicPluginInstall> myPendingDynamicPluginInstalls = new ArrayList<>();
private boolean myRestartRequired = false;
@@ -87,7 +86,7 @@ public class PluginInstallOperation {
return mySuccess;
}
public Set<PluginNode> getInstalledDependentPlugins() {
public Set<PluginInstallCallbackData> getInstalledDependentPlugins() {
return myDependant;
}
@@ -160,16 +159,53 @@ public class PluginInstallOperation {
private boolean prepareToInstall(PluginNode pluginNode, List<PluginId> pluginIds) throws IOException {
myDependant.add(pluginNode);
Ref<IdeaPluginDescriptor> toDisable = checkDependenciesAndReplacements(pluginNode, pluginIds);
if (toDisable == null) return false;
myShownErrors = false;
PluginDownloader downloader = PluginDownloader.createDownloader(pluginNode, pluginNode.getRepositoryName(), null);
IdeaPluginDescriptorImpl descriptor = downloader.prepareToInstallAndLoadDescriptor(myIndicator);
if (descriptor != null) {
boolean allowNoRestart = myAllowInstallWithoutRestart && DynamicPlugins.allowLoadUnloadWithoutRestart(descriptor);
if (allowNoRestart) {
myPendingDynamicPluginInstalls.add(new PendingDynamicPluginInstall(downloader.getFile(), descriptor));
InstalledPluginsState state = InstalledPluginsState.getInstanceIfLoaded();
if (state != null) {
state.onPluginInstall(downloader.getDescriptor(), false, false);
}
}
else {
myRestartRequired = true;
synchronized (PluginInstaller.ourLock) {
downloader.install();
}
}
myDependant.add(new PluginInstallCallbackData(downloader.getFile(), descriptor, !allowNoRestart));
pluginNode.setStatus(PluginNode.Status.DOWNLOADED);
if (!toDisable.isNull()) {
myPluginEnabler.disablePlugins(Collections.singleton(toDisable.get()));
}
}
else {
myShownErrors = downloader.isShownErrors();
return false;
}
return true;
}
@Nullable
public Ref<IdeaPluginDescriptor> checkDependenciesAndReplacements(IdeaPluginDescriptor pluginNode, @Nullable List<PluginId> pluginIds) {
// check for dependent plugins at first.
if (pluginNode.getDepends() != null && !pluginNode.getDepends().isEmpty()) {
List<IdeaPluginDependency> dependencies = pluginNode.getDependencies();
if (!dependencies.isEmpty()) {
// prepare plugins list for install
final PluginId[] optionalDependentPluginIds = pluginNode.getOptionalDependentPluginIds();
final List<PluginNode> depends = new ArrayList<>();
final List<PluginNode> optionalDeps = new ArrayList<>();
for (int i = 0; i < pluginNode.getDepends().size(); i++) {
PluginId depPluginId = pluginNode.getDepends().get(i);
for (IdeaPluginDependency dependency : dependencies) {
PluginId depPluginId = dependency.getPluginId();
if (PluginManagerCore.isModuleDependency(depPluginId)) {
PluginId pluginIdByModule = PluginModulesHelper.getInstance().getMarketplacePluginIdByModule(depPluginId);
@@ -194,7 +230,7 @@ public class PluginInstallOperation {
}
if (depPluginDescriptor != null) {
if (ArrayUtil.indexOf(optionalDependentPluginIds, depPluginId) != -1) {
if (dependency.isOptional()) {
optionalDeps.add(depPlugin);
}
else {
@@ -215,10 +251,10 @@ public class PluginInstallOperation {
}, ModalityState.any());
}
catch (Exception e) {
return false;
return null;
}
if (!proceed[0] || !prepareToInstall(depends)) {
return false;
return null;
}
}
@@ -234,10 +270,10 @@ public class PluginInstallOperation {
}, ModalityState.any());
}
catch (Exception e) {
return false;
return null;
}
if (proceed[0] && !prepareToInstall(optionalDeps)) {
return false;
return null;
}
}
}
@@ -262,37 +298,7 @@ public class PluginInstallOperation {
}, ModalityState.any());
}
}
myShownErrors = false;
PluginDownloader downloader = PluginDownloader.createDownloader(pluginNode, pluginNode.getRepositoryName(), null);
IdeaPluginDescriptorImpl descriptor = downloader.prepareToInstallAndLoadDescriptor(myIndicator);
if (descriptor != null) {
if (myAllowInstallWithoutRestart && DynamicPlugins.allowLoadUnloadWithoutRestart(descriptor)) {
myPendingDynamicPluginInstalls.add(new PendingDynamicPluginInstall(downloader.getFile(), descriptor));
InstalledPluginsState state = InstalledPluginsState.getInstanceIfLoaded();
if (state != null) {
state.onPluginInstall(downloader.getDescriptor(), false, false);
}
}
else {
myRestartRequired = true;
synchronized (PluginInstaller.ourLock) {
downloader.install();
}
}
pluginNode.setStatus(PluginNode.Status.DOWNLOADED);
if (!toDisable.isNull()) {
myPluginEnabler.disablePlugins(Collections.singleton(toDisable.get()));
}
}
else {
myShownErrors = downloader.isShownErrors();
return false;
}
return true;
return toDisable;
}
@NotNull
@@ -25,8 +25,8 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.Decompressor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -230,10 +230,10 @@ public final class PluginInstaller {
PluginStateManager.addStateListener(listener);
}
public static boolean install(@NotNull InstalledPluginsTableModel model,
@NotNull File file,
@NotNull Consumer<? super PluginInstallCallbackData> callback,
@Nullable Component parent) {
public static boolean installFromDisk(@NotNull InstalledPluginsTableModel model,
@NotNull File file,
@NotNull Consumer<? super PluginInstallCallbackData> callback,
@Nullable Component parent) {
try {
IdeaPluginDescriptorImpl pluginDescriptor = PluginDescriptorLoader.loadDescriptorFromArtifact(file.toPath(), null);
if (pluginDescriptor == null) {
@@ -273,19 +273,55 @@ public final class PluginInstaller {
return false;
}
PluginManagerMain.PluginEnabler pluginEnabler = model instanceof PluginManagerMain.PluginEnabler
? (PluginManagerMain.PluginEnabler)model
: new PluginManagerMain.PluginEnabler.HEADLESS();
Ref<Boolean> cancel = Ref.create(false);
Ref<IdeaPluginDescriptor> toDisable = new Ref<>();
Ref<Boolean> dependenciesRequireRestart = Ref.create(false);
Set<PluginInstallCallbackData> installedDependencies = new HashSet<>();
boolean success = ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
PluginInstallOperation dependencyInstallOperation = new PluginInstallOperation(
Collections.emptyList(),
CustomPluginRepositoryService.getInstance().getCustomRepositoryPlugins(),
pluginEnabler,
ProgressManager.getInstance().getProgressIndicator());
dependencyInstallOperation.setAllowInstallWithoutRestart(true);
Ref<IdeaPluginDescriptor> ref = dependencyInstallOperation.checkDependenciesAndReplacements(pluginDescriptor, null);
if (ref == null) {
cancel.set(true);
}
else {
dependenciesRequireRestart.set(dependencyInstallOperation.isRestartRequired());
installedDependencies.addAll(dependencyInstallOperation.getInstalledDependentPlugins());
toDisable.set(ref.get());
}
}, IdeBundle.message("progress.title.checking.plugin.dependencies"), true, null, (JComponent) parent);
if (!success || cancel.get()) return false;
Path oldFile = null;
if (installedPlugin != null && !installedPlugin.isBundled()) {
oldFile = installedPlugin.getPluginPath();
}
boolean installWithoutRestart = oldFile == null && DynamicPlugins.allowLoadUnloadWithoutRestart(pluginDescriptor);
boolean installWithoutRestart = oldFile == null && DynamicPlugins.allowLoadUnloadWithoutRestart(pluginDescriptor) && !dependenciesRequireRestart.get();
if (!installWithoutRestart) {
installAfterRestart(file, false, oldFile, pluginDescriptor);
}
ourState.onPluginInstall(pluginDescriptor, installedPlugin != null, !installWithoutRestart);
checkInstalledPluginDependencies(model, pluginDescriptor, parent);
if (!toDisable.isNull()) {
// TODO[yole] unload and check for restart
pluginEnabler.disablePlugins(Collections.singleton(toDisable.get()));
}
checkInstalledPluginDependencies(model, pluginDescriptor, parent,
ContainerUtil.map2Set(installedDependencies, (callbackData) -> callbackData.getPluginDescriptor().getPluginId()));
callback.consume(new PluginInstallCallbackData(file, pluginDescriptor, !installWithoutRestart));
for (PluginInstallCallbackData callbackData: installedDependencies) {
if (!callbackData.getPluginDescriptor().getPluginId().equals(pluginDescriptor.getPluginId())) {
callback.consume(callbackData);
}
}
return true;
}
catch (IOException ex) {
@@ -309,13 +345,14 @@ public final class PluginInstaller {
private static void checkInstalledPluginDependencies(@NotNull InstalledPluginsTableModel model,
@NotNull IdeaPluginDescriptorImpl pluginDescriptor,
@Nullable Component parent) {
@Nullable Component parent,
Set<PluginId> installedDependencies) {
final Set<PluginId> notInstalled = new HashSet<>();
final Set<PluginId> disabledIds = new HashSet<>();
final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
final PluginId[] optionalDependentPluginIds = pluginDescriptor.getOptionalDependentPluginIds();
for (PluginId id : dependentPluginIds) {
if (ArrayUtilRt.find(optionalDependentPluginIds, id) > -1) continue;
for (IdeaPluginDependency dep : pluginDescriptor.getDependencies()) {
if (dep.isOptional()) continue;
PluginId id = dep.getPluginId();
if (installedDependencies.contains(id)) continue;
final boolean disabled = model.isDisabled(id);
final boolean enabled = model.isEnabled(id);
if (!enabled && !disabled && !PluginManagerCore.isModuleDependency(id)) {
@@ -364,7 +401,7 @@ public final class PluginInstaller {
FileChooser.chooseFile(descriptor, null, parent, toSelect, virtualFile -> {
File file = VfsUtilCore.virtualToIoFile(virtualFile);
PropertiesComponent.getInstance().setValue(PLUGINS_PRESELECTION_PATH, FileUtil.toSystemIndependentName(file.getParent()));
install(model, file, callback, parent);
installFromDisk(model, file, callback, parent);
});
}
}