Update several libraries in project all at once

This commit is contained in:
Nikolay Krasko
2013-03-20 14:25:22 +04:00
parent 29880fc79d
commit 99520c9f54
4 changed files with 213 additions and 43 deletions
@@ -18,8 +18,10 @@ package org.jetbrains.jet.plugin.framework;
import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.libraries.*; import com.intellij.openapi.roots.libraries.*;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -34,17 +36,33 @@ public abstract class CachingLibraryPresentationProvider<T extends LibraryProper
} }
public boolean isDetected(@NotNull List<VirtualFile> classesRoots) { public boolean isDetected(@NotNull List<VirtualFile> classesRoots) {
return cachedDetect(classesRoots, getKind()); return cachedDetect(classesRoots, getKind()) != null;
} }
private static boolean cachedDetect(@NotNull List<VirtualFile> classesRoots, @NotNull final LibraryKind libraryKind) { @Nullable
return !LibraryDetectionManager.getInstance().processProperties( public T getLibraryProperties(@NotNull Library library) {
return cachedDetect(Arrays.asList(library.getFiles(OrderRootType.CLASSES)), getKind());
}
@Nullable
private T cachedDetect(@NotNull List<VirtualFile> classesRoots, @NotNull final LibraryKind libraryKind) {
final Ref<T> propertiesRef = new Ref<T>();
LibraryDetectionManager.getInstance().processProperties(
classesRoots, classesRoots,
new LibraryDetectionManager.LibraryPropertiesProcessor() { new LibraryDetectionManager.LibraryPropertiesProcessor() {
@Override @Override
public <P extends LibraryProperties> boolean processProperties(@NotNull LibraryKind processedKind, @NotNull P properties) { public <P extends LibraryProperties> boolean processProperties(@NotNull LibraryKind processedKind, @NotNull P properties) {
return !libraryKind.equals(processedKind); if (libraryKind.equals(processedKind)) {
//noinspection unchecked
propertiesRef.set((T)properties);
return false;
}
return true;
} }
}); });
return propertiesRef.get();
} }
} }
@@ -17,6 +17,8 @@
package org.jetbrains.jet.plugin.framework; package org.jetbrains.jet.plugin.framework;
import com.intellij.framework.library.LibraryVersionProperties; import com.intellij.framework.library.LibraryVersionProperties;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryPresentationProvider; import com.intellij.openapi.roots.libraries.LibraryPresentationProvider;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -26,6 +28,7 @@ import org.jetbrains.jet.plugin.versions.KotlinRuntimeLibraryUtil;
import org.jetbrains.jet.utils.PathUtil; import org.jetbrains.jet.utils.PathUtil;
import javax.swing.*; import javax.swing.*;
import java.util.Arrays;
import java.util.List; import java.util.List;
public class JavaRuntimePresentationProvider extends CachingLibraryPresentationProvider<LibraryVersionProperties> { public class JavaRuntimePresentationProvider extends CachingLibraryPresentationProvider<LibraryVersionProperties> {
@@ -46,12 +49,27 @@ public class JavaRuntimePresentationProvider extends CachingLibraryPresentationP
@Nullable @Nullable
@Override @Override
public LibraryVersionProperties detect(@NotNull List<VirtualFile> classesRoots) { public LibraryVersionProperties detect(@NotNull List<VirtualFile> classesRoots) {
VirtualFile stdJar = getRuntimeJar(classesRoots);
if (stdJar != null) {
return new LibraryVersionProperties(KotlinRuntimeLibraryUtil.getLibraryVersion(stdJar));
}
return null;
}
@Nullable
public static VirtualFile getRuntimeJar(@NotNull List<VirtualFile> classesRoots) {
for (VirtualFile root : classesRoots) { for (VirtualFile root : classesRoots) {
if (root.getName().equals(PathUtil.KOTLIN_JAVA_RUNTIME_JAR)) { if (root.getName().equals(PathUtil.KOTLIN_JAVA_RUNTIME_JAR)) {
return new LibraryVersionProperties(KotlinRuntimeLibraryUtil.getLibraryVersion(root)); return root;
} }
} }
return null; return null;
} }
@Nullable
public static VirtualFile getRuntimeJar(@NotNull Library library) {
return getRuntimeJar(Arrays.asList(library.getFiles(OrderRootType.CLASSES)));
}
} }
@@ -150,10 +150,7 @@ public class KotlinRuntimeLibraryUtil {
return null; return null;
} }
public static void updateRuntime( public static void updateRuntime(@NotNull final Project project, @NotNull final Runnable jarNotFoundHandler) {
@NotNull final Project project,
@NotNull final Runnable jarNotFoundHandler
) {
ApplicationManager.getApplication().invokeLater(new Runnable() { ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -163,28 +160,14 @@ public class KotlinRuntimeLibraryUtil {
return; return;
} }
VirtualFile runtimeJar = getLocalKotlinRuntimeJar(project); VirtualFile localJar = getLocalKotlinRuntimeJar(project);
assert runtimeJar != null; assert localJar != null;
try { replaceFile(runtimePath, localJar);
File libraryJarPath = new File(runtimeJar.getPath());
assert !FileUtil.filesEqual(runtimePath, libraryJarPath) : "Shouldn't be called for updating same file";
FileUtil.copy(runtimePath, libraryJarPath);
}
catch (IOException e) {
throw new AssertionError(e);
}
runtimeJar.refresh(true, true);
} }
}); });
} }
@Nullable
public static String getRuntimeVersion(@NotNull Project project) {
return getLibraryVersion(getKotlinRuntimeJar(project));
}
@Nullable @Nullable
public static String getLibraryVersion(@Nullable VirtualFile kotlinStdJar) { public static String getLibraryVersion(@Nullable VirtualFile kotlinStdJar) {
if (kotlinStdJar == null) return null; if (kotlinStdJar == null) return null;
@@ -207,7 +190,6 @@ public class KotlinRuntimeLibraryUtil {
VirtualFile virtualFile = markerClass.getContainingFile().getVirtualFile(); VirtualFile virtualFile = markerClass.getContainingFile().getVirtualFile();
if (virtualFile == null) return null; if (virtualFile == null) return null;
ProjectFileIndex projectFileIndex = ProjectFileIndex.SERVICE.getInstance(project); ProjectFileIndex projectFileIndex = ProjectFileIndex.SERVICE.getInstance(project);
return projectFileIndex.getClassRootForFile(virtualFile); return projectFileIndex.getClassRootForFile(virtualFile);
} }
@@ -223,4 +205,24 @@ public class KotlinRuntimeLibraryUtil {
} }
return kotlinRuntimeJar; return kotlinRuntimeJar;
} }
static void replaceFile(File updatedFile, VirtualFile replacedFile) {
try {
String localPath = com.intellij.util.PathUtil.getLocalPath(replacedFile);
assert localPath != null;
File libraryJarPath = new File(localPath);
if (FileUtil.filesEqual(updatedFile, libraryJarPath)) {
throw new IllegalArgumentException("Shouldn't be called for updating same file: " + updatedFile);
}
FileUtil.copy(updatedFile, libraryJarPath);
}
catch (IOException e) {
throw new AssertionError(e);
}
replacedFile.refresh(true, true);
}
} }
@@ -16,6 +16,9 @@
package org.jetbrains.jet.plugin.versions; package org.jetbrains.jet.plugin.versions;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.framework.library.LibraryVersionProperties;
import com.intellij.ide.util.PropertiesComponent; import com.intellij.ide.util.PropertiesComponent;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationListener;
@@ -23,14 +26,30 @@ import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications; import com.intellij.notification.Notifications;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LibraryOrderEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import com.intellij.util.text.VersionComparatorUtil; import com.intellij.util.text.VersionComparatorUtil;
import com.sun.istack.internal.Nullable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.plugin.JetPluginUtil; import org.jetbrains.jet.plugin.JetPluginUtil;
import org.jetbrains.jet.plugin.framework.JavaRuntimePresentationProvider;
import org.jetbrains.jet.utils.PathUtil;
import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkEvent;
import java.io.File;
import java.util.Collection;
import java.util.List;
public class OutdatedKotlinRuntimeNotification extends AbstractProjectComponent { public class OutdatedKotlinRuntimeNotification extends AbstractProjectComponent {
private static final String SUPPRESSED_PROPERTY_NAME = "oudtdated.runtime.suppressed.plugin.version"; private static final String SUPPRESSED_PROPERTY_NAME = "oudtdated.runtime.suppressed.plugin.version";
@@ -39,41 +58,82 @@ public class OutdatedKotlinRuntimeNotification extends AbstractProjectComponent
super(project); super(project);
} }
private static class VersionedLibrary extends Pair<Library, String> {
public VersionedLibrary(@NotNull Library library, @Nullable String version) {
super(library, version);
}
@NotNull
public Library getLibrary() {
return first;
}
@Nullable
public String getVersion() {
return second;
}
}
@Override @Override
public void projectOpened() { public void projectOpened() {
if (ApplicationManager.getApplication().isInternal()) return; if (ApplicationManager.getApplication().isInternal()) return;
StartupManager.getInstance(myProject).registerPostStartupActivity(new Runnable() { StartupManager.getInstance(myProject).registerPostStartupActivity(new Runnable() {
@Override @Override
public void run() { public void run() {
String runtimeVersion = KotlinRuntimeLibraryUtil.getRuntimeVersion(myProject);
final String pluginVersion = JetPluginUtil.getPluginVersion(); final String pluginVersion = JetPluginUtil.getPluginVersion();
if (runtimeVersion == null) return; // runtime is not present in project
if ("@snapshot@".equals(pluginVersion)) return; // plugin is run from sources, can't compare versions if ("@snapshot@".equals(pluginVersion)) return; // plugin is run from sources, can't compare versions
// user already clicked suppress // user already clicked suppress
if (pluginVersion.equals(PropertiesComponent.getInstance(myProject).getValue(SUPPRESSED_PROPERTY_NAME))) return; if (pluginVersion.equals(PropertiesComponent.getInstance(myProject).getValue(SUPPRESSED_PROPERTY_NAME))) return;
boolean isRuntimeOutdated = "snapshot".equals(runtimeVersion) Collection<VersionedLibrary> versionedOutdatedLibraries = findOutdatedKotlinLibraries(myProject, pluginVersion);
|| KotlinRuntimeLibraryUtil.UNKNOWN_VERSION.equals(runtimeVersion) if (versionedOutdatedLibraries.isEmpty()) {
|| runtimeVersion.startsWith("internal-") != pluginVersion.startsWith("internal-") return;
|| VersionComparatorUtil.compare(pluginVersion, runtimeVersion) > 0; }
if (!isRuntimeOutdated) return; final Collection<Library> outdatedLibraries = extractLibraries(versionedOutdatedLibraries);
String message = String.format("<p>Your version of Kotlin runtime library is %s, while plugin version is %s." + String message;
" Runtime library should be updated to avoid compatibility problems.</p>" + if (versionedOutdatedLibraries.size() == 1) {
"<p><a href=\"update\">Update Runtime</a> <a href=\"ignore\">Ignore</a></p>", VersionedLibrary versionedLibrary = versionedOutdatedLibraries.iterator().next();
KotlinRuntimeLibraryUtil.UNKNOWN_VERSION.equals(runtimeVersion)
? "older than 0.1.2296" String version = versionedLibrary.getVersion();
: runtimeVersion, pluginVersion); String readableVersion = version == null ? "unknown" : version;
Notifications.Bus.notify(new Notification("Outdated Kotlin Runtime", "Outdated Kotlin Runtime", String libraryName = versionedLibrary.getLibrary().getName();
message,
message = String.format(
"<p>Your version of Kotlin runtime in '%s' library is %s, while plugin version is %s.</p>" +
"<p>Runtime library should be updated to avoid compatibility problems.</p>" +
"<p><a href=\"update\">Update Runtime</a> <a href=\"ignore\">Ignore</a></p>",
libraryName,
readableVersion,
pluginVersion);
}
else {
String libraryNames = StringUtil.join(outdatedLibraries, new Function<Library, String>() {
@Override
public String fun(Library library) {
return library.getName();
}
}, ", ");
message = String.format(
"<p>Version of Kotlin runtime is outdated in several libraries (%s). Plugin version is %s.</p>" +
"<p>Runtime libraries should be updated to avoid compatibility problems.</p>" +
"<p><a href=\"update\">Update All</a> <a href=\"ignore\">Ignore</a></p>",
libraryNames,
pluginVersion);
}
Notifications.Bus.notify(new Notification("Outdated Kotlin Runtime", "Outdated Kotlin Runtime", message,
NotificationType.WARNING, new NotificationListener() { NotificationType.WARNING, new NotificationListener() {
@Override @Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
if ("update".equals(event.getDescription())) { if ("update".equals(event.getDescription())) {
KotlinRuntimeLibraryUtil.updateRuntime(myProject, showRuntimeJarNotFoundDialog(myProject)); updateKotlinLibraries(outdatedLibraries, showRuntimeJarNotFoundDialog(myProject));
} }
else if ("ignore".equals(event.getDescription())) { else if ("ignore".equals(event.getDescription())) {
PropertiesComponent.getInstance(myProject).setValue(SUPPRESSED_PROPERTY_NAME, pluginVersion); PropertiesComponent.getInstance(myProject).setValue(SUPPRESSED_PROPERTY_NAME, pluginVersion);
@@ -89,6 +149,78 @@ public class OutdatedKotlinRuntimeNotification extends AbstractProjectComponent
}); });
} }
private static Collection<Library> extractLibraries(Collection<VersionedLibrary> libraries) {
return Collections2.transform(libraries, new com.google.common.base.Function<VersionedLibrary, Library>() {
@Override
public Library apply(@Nullable VersionedLibrary versionedLibrary) {
assert versionedLibrary != null;
return versionedLibrary.getLibrary();
}
});
}
private static void updateKotlinLibraries(Collection<Library> libraries, @NotNull Runnable jarNotFoundHandler) {
File runtimePath = PathUtil.getKotlinPathsForIdeaPlugin().getRuntimePath();
if (!runtimePath.exists()) {
jarNotFoundHandler.run();
return;
}
for (Library library : libraries) {
VirtualFile libraryJar = JavaRuntimePresentationProvider.getRuntimeJar(library);
assert JavaRuntimePresentationProvider.getInstance().isDetected(library) && libraryJar != null : "Only java runtime libraries are expected";
KotlinRuntimeLibraryUtil.replaceFile(runtimePath, libraryJar);
}
}
private static Collection<VersionedLibrary> findOutdatedKotlinLibraries(Project project, String pluginVersion) {
List<VersionedLibrary> outdatedLibraries = Lists.newArrayList();
for (VersionedLibrary versionedLibrary : findKotlinLibraries(project)) {
String libraryVersion = versionedLibrary.getVersion();
boolean isOutdated = "snapshot".equals(libraryVersion)
|| libraryVersion == null
|| libraryVersion.startsWith("internal-") != pluginVersion.startsWith("internal-")
|| VersionComparatorUtil.compare(pluginVersion, libraryVersion) > 0;
if (isOutdated) {
outdatedLibraries.add(versionedLibrary);
}
}
return outdatedLibraries;
}
private static Collection<VersionedLibrary> findKotlinLibraries(Project project) {
List<VersionedLibrary> libraries = Lists.newArrayList();
for (Module module : ModuleManager.getInstance(project).getModules()) {
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
for (OrderEntry entry : moduleRootManager.getOrderEntries()) {
if (entry instanceof LibraryOrderEntry) {
LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry) entry;
Library library = libraryOrderEntry.getLibrary();
if (library == null) {
continue;
}
LibraryVersionProperties javaRuntimeProperties = JavaRuntimePresentationProvider.getInstance().getLibraryProperties(library);
if (javaRuntimeProperties != null) {
libraries.add(new VersionedLibrary(library, javaRuntimeProperties.getVersionString()));
}
// TODO: search js libraries as well
}
}
}
return libraries;
}
@NotNull @NotNull
public static Runnable showRuntimeJarNotFoundDialog(@NotNull final Project project) { public static Runnable showRuntimeJarNotFoundDialog(@NotNull final Project project) {
return new Runnable() { return new Runnable() {