From a29b744b335235c22cb07aff55d55197cb57308c Mon Sep 17 00:00:00 2001 From: irengrig Date: Thu, 9 Feb 2012 15:21:14 +0400 Subject: [PATCH 01/15] SVN 1.7: fix some tests --- .../idea/svn17/SvnCommittedViewTest.java | 20 ++++++++++++++++++- .../jetbrains/idea/svn17/SvnRenameTest.java | 4 ++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCommittedViewTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCommittedViewTest.java index 5042d6f4a18a..ae707d1e2b5f 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCommittedViewTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCommittedViewTest.java @@ -1,6 +1,7 @@ package org.jetbrains.idea.svn17; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.CommittedChangesProvider; import com.intellij.openapi.vcs.FileStatus; import com.intellij.openapi.vcs.VcsConfiguration; @@ -13,6 +14,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn17.history.SvnChangeList; import org.jetbrains.idea.svn17.history.SvnRepositoryLocation; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import java.io.File; @@ -21,6 +23,12 @@ import java.util.Collection; import java.util.List; public class SvnCommittedViewTest extends SvnTestCase { + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + SvnConfiguration17.getInstance(myProject).DETECT_NESTED_COPIES = false; + } @Test public void testAdd() throws Exception { @@ -36,6 +44,7 @@ public class SvnCommittedViewTest extends SvnTestCase { checkin(); final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + vcs.invokeRefreshSvnRoots(false); final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); final List changeListList = committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), @@ -67,6 +76,7 @@ public class SvnCommittedViewTest extends SvnTestCase { checkin(); final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + vcs.invokeRefreshSvnRoots(false); final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); final List changeListList = committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), @@ -88,13 +98,17 @@ public class SvnCommittedViewTest extends SvnTestCase { // r1, addition without history checkin(); - final String d1Path = new File(d1.getPath()).getAbsolutePath(); + File dir = new File(d1.getPath()); + final String d1Path = dir.getAbsolutePath(); verify(runSvn("delete", d1Path)); + boolean created = dir.mkdir(); + Assert.assertTrue(created); verify(runSvn("add", d1Path)); checkin(); final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + vcs.invokeRefreshSvnRoots(false); final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); final List changeListList = committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), @@ -123,6 +137,7 @@ public class SvnCommittedViewTest extends SvnTestCase { checkin(); final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + vcs.invokeRefreshSvnRoots(false); final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); final List changeListList = committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), @@ -155,6 +170,7 @@ public class SvnCommittedViewTest extends SvnTestCase { checkin(); final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + vcs.invokeRefreshSvnRoots(false); final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); final List changeListList = committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), @@ -180,6 +196,7 @@ public class SvnCommittedViewTest extends SvnTestCase { verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + vcs.invokeRefreshSvnRoots(false); final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); final List changeListList = committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), @@ -209,6 +226,7 @@ public class SvnCommittedViewTest extends SvnTestCase { checkin(); final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + vcs.invokeRefreshSvnRoots(false); final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); final List changeListList = committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java index 41e4b818363a..8c3363fae655 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java @@ -96,6 +96,10 @@ public class SvnRenameTest extends SvnTestCase { verifyChange(changes.get(3), "child" + File.separatorChar + "grandChild" + File.separatorChar + "b.txt", "childnew" + File.separatorChar + "grandChild" + File.separatorChar + "b.txt"); VirtualFile oldChild = myWorkingCopyDir.findChild("child"); + if (oldChild == null) { + myWorkingCopyDir.refresh(false, true); + oldChild = myWorkingCopyDir.findChild("child"); + } Assert.assertEquals(FileStatus.DELETED, changeListManager.getStatus(oldChild)); } From 6b94fa99eb6850a5d0e64a45abd5e89c600287b4 Mon Sep 17 00:00:00 2001 From: irengrig Date: Thu, 9 Feb 2012 15:47:03 +0400 Subject: [PATCH 02/15] SVN 1.7: wc 1.7 format is somewhy 29 (?) --- .../src/org/jetbrains/idea/svn17/WorkingCopyFormat.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopyFormat.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopyFormat.java index b47ecaafa4b2..c37bfe4a69d0 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopyFormat.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopyFormat.java @@ -15,6 +15,8 @@ */ package org.jetbrains.idea.svn17; +import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb; + /** * since not all constants are available from svnkit & constants are fixed */ @@ -57,6 +59,10 @@ public enum WorkingCopyFormat { } public static WorkingCopyFormat getInstance(final int value) { + // somewhy 1.7 wc format can also be 29 + if (ISVNWCDb.WC_FORMAT_17 == value) { + return ONE_DOT_SEVEN; + } if (ONE_DOT_FIVE.getFormat() == value) { return ONE_DOT_FIVE; } else if (ONE_DOT_FOUR.getFormat() == value) { From 5acb3713b509efd92e51d2e3501988ff6ec4f34d Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 9 Feb 2012 16:14:23 +0400 Subject: [PATCH 03/15] IDEA-81004 Deadlock (SoftWrapApplianceManager) Breaking potentially endless loop and report an error --- .../mapping/SoftWrapApplianceManager.java | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java index 948b08c57038..323e15b63b1a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -419,13 +419,31 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume * 'Token' here stands for the number of subsequent symbols that are represented using the same font by IJ editor. */ private void processNonFoldToken() { + int limit = 3 * (myContext.tokenEndOffset - myContext.lineStartPosition.offset); + int counter = 0; + int startOffset = myContext.currentPosition.offset; while (myContext.currentPosition.offset < myContext.tokenEndOffset) { - //for (int i = myContext.startOffset; i < myContext.endOffset; i++) { + if (counter++ > limit) { + String editorInfo = myEditor instanceof EditorImpl ? ((EditorImpl)myEditor).dumpState() : myEditor.getClass().toString(); + LogMessageEx.error(LOG, "Cycled soft wraps recalculation detected", String.format( + "Start recalculation offset: %d, visible area width: %d, calculation context: %s, editor info: %s", + startOffset, myVisibleAreaWidth, myContext, editorInfo)); + for (int i = myContext.currentPosition.offset; i < myContext.tokenEndOffset; i++) { + char c = myContext.text.charAt(i); + if (c == '\n') { + myContext.onNewLine(); + } + else { + myContext.onNonLineFeedSymbol(c); + } + } + return; + } int offset = myContext.currentPosition.offset; if (offset > myContext.rangeEndOffset) { return; } - + if (myContext.delayedSoftWrap != null && myContext.delayedSoftWrap.getStart() == offset) { processSoftWrap(myContext.delayedSoftWrap); myContext.delayedSoftWrap = null; @@ -442,7 +460,7 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume createSoftWrapIfPossible(); continue; } - + int newX = offsetToX(offset, c); if (myContext.exceedsVisualEdge(newX) && myContext.delayedSoftWrap == null) { createSoftWrapIfPossible(); @@ -1119,6 +1137,14 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume public boolean notifyListenersOnLineStartPosition; public boolean skipToLineEnd; + @Override + public String toString() { + return "reserved width: " + reservedWidthInPixels + ", soft wrap start offset: " + softWrapStartOffset + ", range end offset: " + + rangeEndOffset + ", token offsets: [" + tokenStartOffset + "; " + tokenEndOffset + "], font type: " + fontType + + ", skip to line end: " + skipToLineEnd + ", delayed soft wrap: " + delayedSoftWrap + ", current position: "+ currentPosition + + "line start position: " + lineStartPosition; + } + public void reset() { text = null; lineStartPosition = null; From 75a73d76daeb320cafceddbeab767b040198d859 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 9 Feb 2012 19:05:32 +0400 Subject: [PATCH 04/15] IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes 1. Refreshing gradle project on intellij module root change; 2. 'Refresh gradle action' is disabled if there is a refresh in progress; 3. Gradle tasks have 'queued' status now; --- plugins/gradle/src/META-INF/plugin.xml | 4 + .../AbstractGradleLinkedProjectAction.java | 22 ++- .../gradle/action/GradleOpenScriptAction.java | 4 +- .../action/GradleRefreshProjectAction.java | 38 ++-- .../importing/GradleProjectImportBuilder.java | 42 +---- .../GradleProgressNotificationManager.java | 9 + ...GradleProgressNotificationManagerImpl.java | 29 ++- .../GradleTaskNotificationListener.java | 10 ++ ...GradleTaskNotificationListenerAdapter.java | 5 + .../gradle/remote/GradleApiFacade.java | 25 +-- .../gradle/remote/GradleApiFacadeManager.java | 13 +- ...moteGradleProgressNotificationManager.java | 7 +- .../gradle/remote/RemoteGradleService.java | 26 +-- .../remote/impl/GradleApiFacadeImpl.java | 27 ++- .../impl/GradleProjectResolverImpl.java | 9 +- .../wrapper/GradleApiFacadeWrapper.java | 62 +++++++ .../wrapper/GradleProjectResolverWrapper.java | 68 ++++++++ ...GradleProjectStructureChangesDetector.java | 62 ++++++- .../gradle/task/AbstractGradleTask.java | 2 +- .../plugins/gradle/task/GradleTaskAware.java | 35 ++++ .../gradle/task/GradleTaskManager.java | 121 +++++++++++++ .../plugins/gradle/util/GradleUtil.java | 165 +++++++++++++++--- 22 files changed, 616 insertions(+), 169 deletions(-) create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleApiFacadeWrapper.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleProjectResolverWrapper.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskAware.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskManager.java diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml index ff4374eb52a2..df0d5da1f7de 100644 --- a/plugins/gradle/src/META-INF/plugin.xml +++ b/plugins/gradle/src/META-INF/plugin.xml @@ -49,6 +49,7 @@ + org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper + + org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesDetector + diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleLinkedProjectAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleLinkedProjectAction.java index bbd49d974a04..701da05b145b 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleLinkedProjectAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/AbstractGradleLinkedProjectAction.java @@ -2,6 +2,7 @@ package org.jetbrains.plugins.gradle.action; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,13 +22,13 @@ public abstract class AbstractGradleLinkedProjectAction extends AnAction { @Override public void update(AnActionEvent e) { - final String path = getLinkedProjectPath(e.getDataContext()); - final boolean visible = path != null; + final Pair pair = deriveProjects(e.getDataContext()); + final boolean visible = pair != null; e.getPresentation().setVisible(visible); if (!visible) { return; } - doUpdate(e.getPresentation(), path); + doUpdate(e.getPresentation(), pair.first, pair.second); } @Override @@ -36,16 +37,16 @@ public abstract class AbstractGradleLinkedProjectAction extends AnAction { if (project == null) { return; } - final String path = getLinkedProjectPath(e.getDataContext()); - if (path == null) { + final Pair pair = deriveProjects(e.getDataContext()); + if (pair == null) { e.getPresentation().setVisible(false); return; } - doActionPerformed(project, path); + doActionPerformed(project, pair.second); } @Nullable - protected static String getLinkedProjectPath(@Nullable DataContext context) { + private static Pair deriveProjects(@Nullable DataContext context) { if (context == null) { return null; } @@ -56,9 +57,12 @@ public abstract class AbstractGradleLinkedProjectAction extends AnAction { } final String path = GradleSettings.getInstance(project).LINKED_PROJECT_FILE_PATH; - return (StringUtil.isEmpty(path) || !new File(path).isFile()) ? null : path; + if (StringUtil.isEmpty(path) || !new File(path).isFile()) { + return null; + } + return new Pair(project, path); } - protected abstract void doUpdate(@NotNull Presentation presentation, @NotNull String linkedProjectPath); + protected abstract void doUpdate(@NotNull Presentation presentation, @NotNull Project project, @NotNull String linkedProjectPath); protected abstract void doActionPerformed(@NotNull Project project, @NotNull String linkedProjectPath); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleOpenScriptAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleOpenScriptAction.java index f010e59ebde8..79f35b14803a 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleOpenScriptAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleOpenScriptAction.java @@ -1,7 +1,5 @@ package org.jetbrains.plugins.gradle.action; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileEditorManager; @@ -32,7 +30,7 @@ public class GradleOpenScriptAction extends AbstractGradleLinkedProjectAction im } @Override - protected void doUpdate(@NotNull Presentation presentation, @NotNull String linkedProjectPath) { + protected void doUpdate(@NotNull Presentation presentation, @NotNull Project project, @NotNull String linkedProjectPath) { } @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java index 17fa3a0fbc4e..087e1706733b 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/action/GradleRefreshProjectAction.java @@ -1,17 +1,16 @@ package org.jetbrains.plugins.gradle.action; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.config.GradleSettings; -import org.jetbrains.plugins.gradle.task.GradleResolveProjectTask; +import org.jetbrains.plugins.gradle.task.GradleTaskManager; +import org.jetbrains.plugins.gradle.task.GradleTaskType; import org.jetbrains.plugins.gradle.util.GradleBundle; - -import java.util.concurrent.atomic.AtomicBoolean; +import org.jetbrains.plugins.gradle.util.GradleUtil; /** * Forces the 'gradle' plugin to retrieve the most up-to-date info about the @@ -21,34 +20,25 @@ import java.util.concurrent.atomic.AtomicBoolean; * @author Denis Zhdanov * @since 1/23/12 3:48 PM */ -public class GradleRefreshProjectAction extends AbstractGradleLinkedProjectAction implements DumbAware { +public class GradleRefreshProjectAction extends AbstractGradleLinkedProjectAction implements DumbAware, AnAction.TransparentUpdate { - private final AtomicBoolean myInProgress = new AtomicBoolean(); - public GradleRefreshProjectAction() { getTemplatePresentation().setText(GradleBundle.message("gradle.action.refresh.project.text")); getTemplatePresentation().setDescription(GradleBundle.message("gradle.action.refresh.project.description")); } @Override - protected void doUpdate(@NotNull Presentation presentation, @NotNull String linkedProjectPath) { - presentation.setEnabled(!myInProgress.get()); + protected void doUpdate(@NotNull Presentation presentation, @NotNull Project project, @NotNull String linkedProjectPath) { + boolean enabled = false; + final GradleTaskManager taskManager = ServiceManager.getService(GradleTaskManager.class); + if (taskManager != null) { + enabled = !taskManager.hasTaskOfTypeInProgress(GradleTaskType.RESOLVE_PROJECT); + } + presentation.setEnabled(enabled); } @Override protected void doActionPerformed(@NotNull final Project project, @NotNull final String linkedProjectPath) { - myInProgress.set(true); - ProgressManager.getInstance().run(new Task.Backgroundable(project, GradleBundle.message("gradle.sync.progress.text")) { - @Override - public void run(@NotNull final ProgressIndicator indicator) { - try { - GradleResolveProjectTask task = new GradleResolveProjectTask(project, linkedProjectPath, true); - task.execute(indicator); - } - finally { - myInProgress.set(false); - } - } - }); + GradleUtil.refreshProject(project); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java index d8a5a8d4a29c..760166cb0194 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/GradleProjectImportBuilder.java @@ -1,14 +1,10 @@ package org.jetbrains.plugins.gradle.importing; -import com.intellij.execution.rmi.RemoteUtil; import com.intellij.ide.util.PropertiesComponent; import com.intellij.ide.util.projectWizard.WizardContext; import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.projectRoots.JavaSdk; @@ -17,7 +13,6 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ui.configuration.ModulesProvider; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.packaging.artifacts.ModifiableArtifactModel; import com.intellij.projectImport.ProjectImportBuilder; import org.jetbrains.annotations.NotNull; @@ -26,11 +21,9 @@ import org.jetbrains.plugins.gradle.config.GradleSettings; import org.jetbrains.plugins.gradle.model.GradleEntity; import org.jetbrains.plugins.gradle.model.GradleModule; import org.jetbrains.plugins.gradle.model.GradleProject; -import org.jetbrains.plugins.gradle.remote.GradleApiException; -import org.jetbrains.plugins.gradle.task.GradleResolveProjectTask; import org.jetbrains.plugins.gradle.ui.GradleIcons; import org.jetbrains.plugins.gradle.util.GradleBundle; -import org.jetbrains.plugins.gradle.util.GradleLog; +import org.jetbrains.plugins.gradle.util.GradleUtil; import javax.swing.*; import java.io.File; @@ -156,38 +149,7 @@ public class GradleProjectImportBuilder extends ProjectImportBuilder errorReason = new Ref(); try { final Project project = getProject(wizardContext); - ProgressManager.getInstance().run(new Task.Modal(project, GradleBundle.message("gradle.import.progress.text"), true) { - @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - @Override - public void run(@NotNull final ProgressIndicator indicator) { - GradleResolveProjectTask task = new GradleResolveProjectTask(project, myProjectFile.getAbsolutePath(), false); - task.execute(indicator); - myGradleProject = task.getProject(); - if (myGradleProject != null) { - return; - } - final Throwable error = task.getError(); - if (error == null) { - return; - } - Throwable unwrapped = RemoteUtil.unwrap(error); - String reason = unwrapped.getLocalizedMessage(); - if (!StringUtil.isEmpty(reason)) { - errorReason.set(reason); - } - if (unwrapped.getClass() == NoClassDefFoundError.class) { - errorReason.set(GradleBundle.message("gradle.import.text.incomplete.tooling.api")); - } - else if (unwrapped.getClass() == GradleApiException.class) { - GradleLog.LOG.warn("Can't resolve gradle project. Reason: gradle api threw an exception:\n" - + ((GradleApiException)unwrapped).getOriginalReason() - ); - } - else { - GradleLog.LOG.warn("Can't resolve gradle project", unwrapped); - } - } - }); + myGradleProject = GradleUtil.refreshProject(project, myProjectFile.getAbsolutePath(), errorReason, false, true); } catch (IllegalArgumentException e) { throw new ConfigurationException(e.getMessage(), GradleBundle.message("gradle.import.text.error.cannot.parse.project")); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManager.java index 7c134a94f968..a048f1cead9f 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManager.java @@ -9,6 +9,15 @@ import org.jetbrains.plugins.gradle.task.GradleTaskId; */ public interface GradleProgressNotificationManager { + /** + * Allows to register given listener to listen events from all tasks. + * + * @param listener listener to register + * @return true if given listener was not registered before for the given key; + * false otherwise + */ + boolean addNotificationListener(@NotNull GradleTaskNotificationListener listener); + /** * Allows to register given listener within the current manager for listening events from the task with the target id. * diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManagerImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManagerImpl.java index 80787d206f06..0eb9d5570ce4 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManagerImpl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleProgressNotificationManagerImpl.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.remote.RemoteGradleProgressNotificationManager; import org.jetbrains.plugins.gradle.task.GradleTaskId; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; @@ -19,9 +20,15 @@ public class GradleProgressNotificationManagerImpl extends RemoteObject implements GradleProgressNotificationManager, RemoteGradleProgressNotificationManager { - private final ConcurrentMap> myListeners + private final ConcurrentMap/* EMPTY_SET as a sign of 'all ids' */> myListeners = new ConcurrentHashMap>(); + @Override + public boolean addNotificationListener(@NotNull GradleTaskNotificationListener listener) { + Set dummy = Collections.emptySet(); + return myListeners.put(listener, dummy) == null; + } + @Override public boolean addNotificationListener(@NotNull GradleTaskId taskId, @NotNull GradleTaskNotificationListener listener) { Set ids = null; @@ -41,10 +48,22 @@ public class GradleProgressNotificationManagerImpl extends RemoteObject return myListeners.remove(listener) != null; } + @Override + public void onQueued(@NotNull GradleTaskId id) { + for (Map.Entry> entry : myListeners.entrySet()) { + final Set ids = entry.getValue(); + if (Collections.EMPTY_SET == ids || ids.contains(id)) { + entry.getKey().onQueued(id); + } + } + + } + @Override public void onStart(@NotNull GradleTaskId id) { for (Map.Entry> entry : myListeners.entrySet()) { - if (entry.getValue().contains(id)) { + final Set ids = entry.getValue(); + if (Collections.EMPTY_SET == ids || ids.contains(id)) { entry.getKey().onStart(id); } } @@ -53,7 +72,8 @@ public class GradleProgressNotificationManagerImpl extends RemoteObject @Override public void onStatusChange(@NotNull GradleTaskNotificationEvent event) { for (Map.Entry> entry : myListeners.entrySet()) { - if (entry.getValue().contains(event.getId())) { + final Set ids = entry.getValue(); + if (Collections.EMPTY_SET == ids || ids.contains(event.getId())) { entry.getKey().onStatusChange(event); } } @@ -62,7 +82,8 @@ public class GradleProgressNotificationManagerImpl extends RemoteObject @Override public void onEnd(@NotNull GradleTaskId id) { for (Map.Entry> entry : myListeners.entrySet()) { - if (entry.getValue().contains(id)) { + final Set ids = entry.getValue(); + if (Collections.EMPTY_SET == ids || ids.contains(id)) { entry.getKey().onEnd(id); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListener.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListener.java index 1b4bda67e8ec..ba4a73fee1fa 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListener.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListener.java @@ -11,6 +11,16 @@ import org.jetbrains.plugins.gradle.task.GradleTaskId; */ public interface GradleTaskNotificationListener { + /** + * Notifies that task with the given id is queued for the execution. + *

+ * 'Queued' here means that intellij process-local codebase receives request to execute the target task and even has not been + * sent it to the slave gradle api process. + * + * @param id target task's id + */ + void onQueued(@NotNull GradleTaskId id); + /** * Notifies that task with the given id is about to be started. * diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListenerAdapter.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListenerAdapter.java index a5ff4965a90d..c2ee8ee511b9 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListenerAdapter.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/notification/GradleTaskNotificationListenerAdapter.java @@ -8,6 +8,11 @@ import org.jetbrains.plugins.gradle.task.GradleTaskId; * @since 11/10/11 12:18 PM */ public class GradleTaskNotificationListenerAdapter implements GradleTaskNotificationListener { + + @Override + public void onQueued(@NotNull GradleTaskId id) { + } + @Override public void onStart(@NotNull GradleTaskId id) { } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java index d2d5dc0e3072..2955f9e3961c 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacade.java @@ -1,12 +1,10 @@ package org.jetbrains.plugins.gradle.remote; import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.gradle.task.GradleTaskId; -import org.jetbrains.plugins.gradle.task.GradleTaskType; +import org.jetbrains.plugins.gradle.task.GradleTaskAware; import java.rmi.Remote; import java.rmi.RemoteException; -import java.util.Collection; /** * Serves as a facade for working with @@ -18,7 +16,7 @@ import java.util.Collection; * @author Denis Zhdanov * @since 8/8/11 10:52 AM */ -public interface GradleApiFacade extends Remote { +public interface GradleApiFacade extends Remote, GradleTaskAware { /** * Exposes 'resolve gradle project' service that works at another process. @@ -45,23 +43,4 @@ public interface GradleApiFacade extends Remote { * @throws RemoteException in case of unexpected I/O exception during processing */ void applyProgressManager(@NotNull RemoteGradleProgressNotificationManager progressManager) throws RemoteException; - - /** - * Asks remote gradle process to check if a task with the given id is being executed right now. - * - * @param id target task's id - * @return true if a task with the given id is executed at the moment; false otherwise - * @throws RemoteException in case of unexpected I/O exception during processing - */ - boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException; - - /** - * Allows to ask remote gradle process for the ids of the tasks with the given type being executed now. - * - * @param type target task type - * @return ids of the tasks of the target type being executed at the moment (if any) - * @throws RemoteException in case of unexpected I/O exception during processing - */ - @NotNull - Collection getTasksInProgress(@NotNull GradleTaskType type) throws RemoteException; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java index 02a42690d19f..8972c627d369 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/GradleApiFacadeManager.java @@ -31,7 +31,9 @@ import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManager; +import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl; import org.jetbrains.plugins.gradle.remote.impl.GradleApiFacadeImpl; +import org.jetbrains.plugins.gradle.remote.wrapper.GradleApiFacadeWrapper; import org.jetbrains.plugins.gradle.util.GradleBundle; import org.jetbrains.plugins.gradle.util.GradleLibraryManager; import org.jetbrains.plugins.gradle.util.GradleLog; @@ -69,8 +71,8 @@ public class GradleApiFacadeManager { private final AtomicReference myExportedProgressManager = new AtomicReference(); - private final GradleLibraryManager myGradleLibraryManager; - private final RemoteGradleProgressNotificationManager myProgressManager; + @NotNull private final GradleLibraryManager myGradleLibraryManager; + @NotNull private final GradleProgressNotificationManagerImpl myProgressManager; // Please note that we don't use RemoteGradleProcessSettings as the 'Configuration' type parameter here because we need // to apply the settings to the newly created process. I.e. every time new process is created we need to call @@ -80,7 +82,7 @@ public class GradleApiFacadeManager { public GradleApiFacadeManager(@NotNull GradleLibraryManager gradleLibraryManager, @NotNull GradleProgressNotificationManager manager) { myGradleLibraryManager = gradleLibraryManager; - myProgressManager = (RemoteGradleProgressNotificationManager)manager; + myProgressManager = (GradleProgressNotificationManagerImpl)manager; mySupport = new RemoteProcessSupport(GradleApiFacade.class) { @Override protected void fireModificationCountChanged() { @@ -223,10 +225,11 @@ public class GradleApiFacadeManager { myFacade.compareAndSet(pair, null); } - GradleApiFacade result = mySupport.acquire(this, ""); - if (result == null) { + final GradleApiFacade facade = mySupport.acquire(this, ""); + if (facade == null) { throw new IllegalStateException("Can't obtain facade to working with gradle api at the remote process"); } + final GradleApiFacade result = new GradleApiFacadeWrapper(facade, myProgressManager); Pair newPair = new Pair(result, getRemoteSettings()); if (!myFacade.compareAndSet(null, newPair)) { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProgressNotificationManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProgressNotificationManager.java index 22b87a335e12..dc35e524c44d 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProgressNotificationManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleProgressNotificationManager.java @@ -18,6 +18,9 @@ import java.rmi.RemoteException; public interface RemoteGradleProgressNotificationManager extends Remote { RemoteGradleProgressNotificationManager NULL_OBJECT = new RemoteGradleProgressNotificationManager() { + @Override + public void onQueued(@NotNull GradleTaskId id) throws RemoteException { + } @Override public void onStart(@NotNull GradleTaskId id) { } @@ -28,7 +31,9 @@ public interface RemoteGradleProgressNotificationManager extends Remote { public void onEnd(@NotNull GradleTaskId id) { } }; - + + void onQueued(@NotNull GradleTaskId id) throws RemoteException; + void onStart(@NotNull GradleTaskId id) throws RemoteException; void onStatusChange(@NotNull GradleTaskNotificationEvent event) throws RemoteException; diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java index 52d709b0cd7f..84af289eec64 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/RemoteGradleService.java @@ -2,12 +2,10 @@ package org.jetbrains.plugins.gradle.remote; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationListener; -import org.jetbrains.plugins.gradle.task.GradleTaskId; -import org.jetbrains.plugins.gradle.task.GradleTaskType; +import org.jetbrains.plugins.gradle.task.GradleTaskAware; import java.rmi.Remote; import java.rmi.RemoteException; -import java.util.Collection; /** * Generic interface with common functionality for all remote services that work with gradle tooling api. @@ -15,28 +13,8 @@ import java.util.Collection; * @author Denis Zhdanov * @since 8/9/11 3:19 PM */ -public interface RemoteGradleService extends Remote { +public interface RemoteGradleService extends Remote, GradleTaskAware { - /** - * Allows to check if current service executes the target task. - * - * @param id target task's id - * @return true if a task with the given id is executed at the moment by the current service; - * false otherwise - * @throws RemoteException as required by RMI - */ - boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException; - - /** - * Allows to ask current service for the ids of the tasks with the given type being executed now. - * - * @param type target task type - * @return ids of the tasks of the target type being executed at the moment by the current service (if any) - * @throws RemoteException as required by RMI - */ - @NotNull - Collection getTasksInProgress(@NotNull GradleTaskType type) throws RemoteException; - /** * Provides the service settings to use. * diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleApiFacadeImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleApiFacadeImpl.java index 82e74734ff4e..74ed43dd9c74 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleApiFacadeImpl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleApiFacadeImpl.java @@ -142,21 +142,30 @@ public class GradleApiFacadeImpl extends RemoteServer implements GradleApiFacade @NotNull @Override - public Collection getTasksInProgress(@NotNull GradleTaskType type) throws RemoteException { - Set result = null; + public Map> getTasksInProgress() throws RemoteException { + Map> result = null; for (RemoteGradleService service : myRemotes.values()) { - final Collection tasks = service.getTasksInProgress(type); + final Map> tasks = service.getTasksInProgress(); if (tasks.isEmpty()) { continue; } if (result == null) { - result = new HashSet(); + result = new HashMap>(); + } + for (Map.Entry> entry : tasks.entrySet()) { + Set ids = result.get(entry.getKey()); + if (ids == null) { + result.put(entry.getKey(), ids = new HashSet()); + } + ids.addAll(entry.getValue()); } - result.addAll(tasks); } - return result == null ? Collections.emptySet() : result; + if (result == null) { + result = Collections.emptyMap(); + } + return result; } - + @Override public void applySettings(@NotNull RemoteGradleProcessSettings settings) throws RemoteException { mySettings.set(settings); @@ -208,6 +217,10 @@ public class GradleApiFacadeImpl extends RemoteServer implements GradleApiFacade myManager = manager; } + @Override + public void onQueued(@NotNull GradleTaskId id) { + } + @Override public void onStart(@NotNull GradleTaskId id) { try { diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java index 7bd336a2a324..9486e30536e8 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/impl/GradleProjectResolverImpl.java @@ -81,11 +81,10 @@ public class GradleProjectResolverImpl extends RemoteObject implements GradlePro @NotNull @Override - public Collection getTasksInProgress(@NotNull GradleTaskType type) { - if (type != GradleTaskType.RESOLVE_PROJECT || myTasksInProgress.isEmpty()) { - return Collections.emptySet(); - } - return new HashSet(myTasksInProgress); + public Map> getTasksInProgress() throws RemoteException { + Map> result = new HashMap>(); + result.put(GradleTaskType.RESOLVE_PROJECT, new HashSet(myTasksInProgress)); + return result; } @NotNull diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleApiFacadeWrapper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleApiFacadeWrapper.java new file mode 100644 index 000000000000..9838e8320b21 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleApiFacadeWrapper.java @@ -0,0 +1,62 @@ +package org.jetbrains.plugins.gradle.remote.wrapper; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl; +import org.jetbrains.plugins.gradle.remote.GradleApiFacade; +import org.jetbrains.plugins.gradle.remote.GradleProjectResolver; +import org.jetbrains.plugins.gradle.remote.RemoteGradleProcessSettings; +import org.jetbrains.plugins.gradle.remote.RemoteGradleProgressNotificationManager; +import org.jetbrains.plugins.gradle.task.GradleTaskId; +import org.jetbrains.plugins.gradle.task.GradleTaskType; + +import java.rmi.RemoteException; +import java.util.Map; +import java.util.Set; + +/** + * This class acts as a point where target remote gradle services are proxied. + *

+ * Check service wrapper contracts for more details. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 2/8/12 7:21 PM + */ +public class GradleApiFacadeWrapper implements GradleApiFacade { + + @NotNull private final GradleApiFacade myDelegate; + @NotNull private final GradleProgressNotificationManagerImpl myNotificationManager; + + public GradleApiFacadeWrapper(@NotNull GradleApiFacade delegate, @NotNull GradleProgressNotificationManagerImpl notificationManager) { + myDelegate = delegate; + myNotificationManager = notificationManager; + } + + @NotNull + @Override + public GradleProjectResolver getResolver() throws RemoteException, IllegalStateException { + return new GradleProjectResolverWrapper(myDelegate.getResolver(), myNotificationManager); + } + + @Override + public void applySettings(@NotNull RemoteGradleProcessSettings settings) throws RemoteException { + myDelegate.applySettings(settings); + } + + @Override + public void applyProgressManager(@NotNull RemoteGradleProgressNotificationManager progressManager) throws RemoteException { + myDelegate.applyProgressManager(progressManager); + } + + @Override + public boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException { + return myDelegate.isTaskInProgress(id); + } + + @NotNull + @Override + public Map> getTasksInProgress() throws RemoteException { + return myDelegate.getTasksInProgress(); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleProjectResolverWrapper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleProjectResolverWrapper.java new file mode 100644 index 000000000000..b31f37f7edc0 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/remote/wrapper/GradleProjectResolverWrapper.java @@ -0,0 +1,68 @@ +package org.jetbrains.plugins.gradle.remote.wrapper; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.model.GradleProject; +import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl; +import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationListener; +import org.jetbrains.plugins.gradle.remote.GradleApiException; +import org.jetbrains.plugins.gradle.remote.GradleProjectResolver; +import org.jetbrains.plugins.gradle.remote.RemoteGradleProcessSettings; +import org.jetbrains.plugins.gradle.task.GradleTaskId; +import org.jetbrains.plugins.gradle.task.GradleTaskManager; +import org.jetbrains.plugins.gradle.task.GradleTaskType; + +import java.rmi.RemoteException; +import java.util.Map; +import java.util.Set; + +/** + * Intercepts calls to the target {@link GradleProjectResolver} and + * {@link GradleTaskManager#onQueued(GradleTaskId) updates 'queued' task status}. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 2/8/12 7:21 PM + */ +public class GradleProjectResolverWrapper implements GradleProjectResolver { + + @NotNull private final GradleProjectResolver myResolver; + @NotNull private final GradleProgressNotificationManagerImpl myNotificationManager; + + public GradleProjectResolverWrapper(@NotNull GradleProjectResolver resolver, + @NotNull GradleProgressNotificationManagerImpl notificationManager) + { + myResolver = resolver; + myNotificationManager = notificationManager; + } + + @Override + @NotNull + public GradleProject resolveProjectInfo(@NotNull GradleTaskId id, @NotNull String projectPath, boolean downloadLibraries) + throws RemoteException, GradleApiException, IllegalArgumentException, IllegalStateException + { + myNotificationManager.onQueued(id); + return myResolver.resolveProjectInfo(id, projectPath, downloadLibraries); + } + + @Override + public void setSettings(@NotNull RemoteGradleProcessSettings settings) throws RemoteException { + myResolver.setSettings(settings); + } + + @Override + public void setNotificationListener(@NotNull GradleTaskNotificationListener notificationListener) throws RemoteException { + myResolver.setNotificationListener(notificationListener); + } + + @Override + public boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException { + return myResolver.isTaskInProgress(id); + } + + @Override + @NotNull + public Map> getTasksInProgress() throws RemoteException { + return myResolver.getTasksInProgress(); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java index 05ef3cadf545..e46258ad1597 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesDetector.java @@ -1,10 +1,68 @@ package org.jetbrains.plugins.gradle.sync; +import com.intellij.ProjectTopics; +import com.intellij.openapi.components.AbstractProjectComponent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootEvent; +import com.intellij.openapi.roots.ModuleRootListener; +import com.intellij.util.Alarm; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.util.GradleUtil; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + /** - * // TODO den add doc + * Detects project structure changes and triggers linked gradle project update. * * @author Denis Zhdanov * @since 11/3/11 3:57 PM */ -public class GradleProjectStructureChangesDetector { +public class GradleProjectStructureChangesDetector extends AbstractProjectComponent { + + private static final int REFRESH_DELAY_MILLIS = (int)TimeUnit.SECONDS.toMillis(2); + + private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + private final AtomicLong myStartRefreshTime = new AtomicLong(); + private final RefreshRequest myRequest = new RefreshRequest(); + + public GradleProjectStructureChangesDetector(@NotNull Project project) { + super(project); + subscribeToRootChanges(project); + } + + private void subscribeToRootChanges(@NotNull Project project) { + project.getMessageBus().connect(project).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { + @Override + public void beforeRootsChange(ModuleRootEvent event) { + } + + @Override + public void rootsChanged(ModuleRootEvent event) { + scheduleUpdate(); + } + }); + } + + private void scheduleUpdate() { + myStartRefreshTime.set(System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + myAlarm.cancelAllRequests(); + myAlarm.addRequest(myRequest, REFRESH_DELAY_MILLIS + 16); + } + + private class RefreshRequest implements Runnable { + @Override + public void run() { + if (!myProject.isInitialized()) { + return; + } + myAlarm.cancelAllRequests(); + final long diff = System.currentTimeMillis() - myStartRefreshTime.get(); + if (diff < 0) { + myAlarm.addRequest(this, (int)-diff); + return; + } + GradleUtil.refreshProject(myProject); + } + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java index bfbf74f5d633..535ab6660ea1 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/AbstractGradleTask.java @@ -96,7 +96,7 @@ public abstract class AbstractGradleTask implements GradleTask { } finally { for (GradleTaskNotificationListener listener : listeners) { - progressManager.addNotificationListener(getId(), listener); + progressManager.removeNotificationListener(listener); } } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskAware.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskAware.java new file mode 100644 index 000000000000..6ca2482310d4 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskAware.java @@ -0,0 +1,35 @@ +package org.jetbrains.plugins.gradle.task; + +import org.jetbrains.annotations.NotNull; + +import java.rmi.RemoteException; +import java.util.Map; +import java.util.Set; + +/** + * Represents service that exposes information about the tasks being processed. + * + * @author Denis Zhdanov + * @since 2/8/12 1:46 PM + */ +public interface GradleTaskAware { + + /** + * Allows to check if current service executes the target task. + * + * @param id target task's id + * @return true if a task with the given id is executed at the moment by the current service; + * false otherwise + * @throws RemoteException as required by RMI + */ + boolean isTaskInProgress(@NotNull GradleTaskId id) throws RemoteException; + + /** + * Allows to ask current service for all tasks being executed at the moment. + * + * @return ids of all tasks being executed at the moment grouped by type + * @throws RemoteException as required by RMI + */ + @NotNull + Map> getTasksInProgress() throws RemoteException; +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskManager.java new file mode 100644 index 000000000000..86ae3f72762d --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleTaskManager.java @@ -0,0 +1,121 @@ +package org.jetbrains.plugins.gradle.task; + +import com.intellij.util.Alarm; +import com.intellij.util.containers.ConcurrentHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManager; +import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationEvent; +import org.jetbrains.plugins.gradle.notification.GradleTaskNotificationListener; +import org.jetbrains.plugins.gradle.remote.GradleApiFacadeManager; +import org.jetbrains.plugins.gradle.util.GradleLog; +import org.jetbrains.plugins.gradle.util.GradleUtil; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +/** + * Provides gradle tasks monitoring and management facilities. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 2/8/12 1:52 PM + */ +public class GradleTaskManager implements GradleTaskNotificationListener { + + /** + * We receive information about the tasks being enqueued to the slave gradle projects here. However, there is a possible + * situation when particular task has been sent to execution but remote side has not been responding for a while. There at least + * two possible explanations then: + *

+   * 
    + *
  • The task is still in progress (e.g. great number of libraries is being downloaded);
  • + *
  • Remote side has fallen (uncaught exception; manual slave gradle process kill etc);
  • + *
+ *
+ * We need to distinguish between them, so, we perform 'task pings' if any task is executed too long. Current constant holds + * criteria of 'too long execution'. + */ + private static final long REFRESH_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(10); + private static final int DETECT_HANGED_TASKS_FREQUENCY_MILLIS = (int)TimeUnit.SECONDS.toMillis(5); + + @NotNull private final ConcurrentMap myTasksInProgress = new ConcurrentHashMap(); + @NotNull private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + + @NotNull private final GradleApiFacadeManager myFacadeManager; + + public GradleTaskManager(@NotNull GradleApiFacadeManager facadeManager, @NotNull GradleProgressNotificationManager notificationManager) { + myFacadeManager = facadeManager; + notificationManager.addNotificationListener(this); + myAlarm.addRequest(new Runnable() { + @Override + public void run() { + try { + for (Long limit : myTasksInProgress.values()) { + if (limit <= System.currentTimeMillis()) { + update(); + break; + } + } + } + finally { + myAlarm.cancelAllRequests(); + myAlarm.addRequest(this, DETECT_HANGED_TASKS_FREQUENCY_MILLIS); + } + } + }, DETECT_HANGED_TASKS_FREQUENCY_MILLIS); + } + + /** + * Allows to check if any task of the given type is being executed at the moment. + * + * @param type target task type + * @return true if any task of the given type is being executed at the moment; + * false otherwise + */ + public boolean hasTaskOfTypeInProgress(@NotNull GradleTaskType type) { + for (GradleTaskId id : myTasksInProgress.keySet()) { + if (type.equals(id.getType())) { + return true; + } + } + return false; + } + + @Override + public void onQueued(@NotNull GradleTaskId id) { + myTasksInProgress.put(id, System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + } + + @Override + public void onStart(@NotNull GradleTaskId id) { + myTasksInProgress.put(id, System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + } + + @Override + public void onStatusChange(@NotNull GradleTaskNotificationEvent event) { + myTasksInProgress.put(event.getId(), System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + } + + @Override + public void onEnd(@NotNull GradleTaskId id) { + myTasksInProgress.remove(id); + } + + public void update() { + try { + final Map> currentState = myFacadeManager.getFacade().getTasksInProgress(); + myTasksInProgress.clear(); + for (Set ids : currentState.values()) { + for (GradleTaskId id : ids) { + myTasksInProgress.put(id, System.currentTimeMillis() + REFRESH_DELAY_MILLIS); + } + } + } + catch (Exception e) { + GradleLog.LOG.warn(String.format("Can't refresh active tasks. Reason: %s", GradleUtil.buildErrorMessage(e))); + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java index a285e2a27325..d1da29e53b4d 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleUtil.java @@ -1,21 +1,36 @@ package org.jetbrains.plugins.gradle.util; +import com.intellij.execution.rmi.RemoteUtil; import com.intellij.ide.actions.OpenProjectFileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileTypeDescriptor; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.BalloonBuilder; import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.PathUtil; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.config.GradleSettings; +import org.jetbrains.plugins.gradle.model.GradleProject; +import org.jetbrains.plugins.gradle.remote.GradleApiException; +import org.jetbrains.plugins.gradle.task.GradleResolveProjectTask; import org.jetbrains.plugins.gradle.ui.GradleIcons; import javax.swing.*; import java.awt.*; import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.concurrent.TimeUnit; /** @@ -26,27 +41,6 @@ import java.util.concurrent.TimeUnit; */ public class GradleUtil { - /** - * We use this class in order to avoid static initialisation of the wrapped object - it loads number of pico container-based - * dependencies that are unavailable to the slave gradle project, so, we don't want to get unexpected NPE there. - */ - private static class DescriptorHolder { - public static final FileChooserDescriptor GRADLE_BUILD_FILE_CHOOSER_DESCRIPTOR = new OpenProjectFileChooserDescriptor(true) { - @Override - public boolean isFileSelectable(VirtualFile file) { - return GradleConstants.DEFAULT_SCRIPT_NAME.equals(file.getName()); - } - - @Override - public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { - if (!super.isFileVisible(file, showHiddenFiles)) { - return false; - } - return file.isDirectory() || GradleConstants.EXTENSION.equals(file.getExtension()); - } - }; - } - private GradleUtil() { } @@ -96,4 +90,133 @@ public class GradleUtil { } balloon.show(new RelativePoint(component, new Point(x, y)), position); } + + /** + * Delegates to the {@link #refreshProject(Project, String, Ref, boolean, boolean)} with the following defaults: + *
+   * 
    + *
  • target gradle project path is retrieved from the {@link GradleSettings gradle settings} associated with the given project;
  • + *
  • refresh process is run in background;
  • + *
  • any problem occurred during the refresh is reported to the {@link GradleLog#LOG};
  • + *
+ *
+ * + * @param project target intellij project to use + */ + public static void refreshProject(@NotNull Project project) { + final GradleSettings settings = GradleSettings.getInstance(project); + final String linkedProjectPath = settings.LINKED_PROJECT_FILE_PATH; + if (StringUtil.isEmpty(linkedProjectPath)) { + return; + } + Ref errorHolder = new Ref(); + refreshProject(project, linkedProjectPath, errorHolder, true, false); + final String error = errorHolder.get(); + if (!StringUtil.isEmpty(error)) { + GradleLog.LOG.warn(error); + } + } + + /** + * {@link RemoteUtil#unwrap(Throwable) unwraps} given exception if possible and builds error message for it. + * + * @param e exception to process + * @return error message for the given exception + */ + @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "IOResourceOpenedButNotSafelyClosed"}) + public static String buildErrorMessage(@NotNull Throwable e) { + Throwable unwrapped = RemoteUtil.unwrap(e); + String reason = unwrapped.getLocalizedMessage(); + if (!StringUtil.isEmpty(reason)) { + return reason; + } + else if (unwrapped.getClass() == GradleApiException.class) { + return String.format("gradle api threw an exception: %s", ((GradleApiException)unwrapped).getOriginalReason()); + } + else { + StringWriter writer = new StringWriter(); + unwrapped.printStackTrace(new PrintWriter(writer)); + return writer.toString(); + } + } + /** + * Queries slave gradle process to refresh target gradle project. + * + * @param project target intellij project to use + * @param gradleProjectPath path of the target gradle project's file + * @param errorHolder holder for the error message that describes a problem occurred during the refresh (if any) + * @param resolveLibraries flag that identifies whether gradle libraries should be resolved during the refresh + * @return the most up-to-date gradle project (if any) + */ + @Nullable + public static GradleProject refreshProject(@NotNull final Project project, + @NotNull final String gradleProjectPath, + @NotNull final Ref errorHolder, + final boolean resolveLibraries, + final boolean modal) + { + final Ref gradleProject = new Ref(); + final TaskUnderProgress task = new TaskUnderProgress() { + @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "IOResourceOpenedButNotSafelyClosed"}) + @Override + public void execute(@NotNull ProgressIndicator indicator) { + GradleResolveProjectTask task = new GradleResolveProjectTask(project, gradleProjectPath, resolveLibraries); + task.execute(indicator); + gradleProject.set(task.getProject()); + final Throwable error = task.getError(); + if (error == null) { + return; + } + final String message = buildErrorMessage(error); + errorHolder.set(String.format("Can't resolve gradle project at '%s'. Reason: %s", gradleProjectPath, message)); + } + }; + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + if (modal) { + ProgressManager.getInstance().run(new Task.Modal(project, GradleBundle.message("gradle.import.progress.text"), true) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + task.execute(indicator); + } + }); + } + else { + ProgressManager.getInstance().run(new Task.Backgroundable(project, GradleBundle.message("gradle.sync.progress.text")) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + task.execute(indicator); + } + }); + } + } + }); + return gradleProject.get(); + } + + private interface TaskUnderProgress { + void execute(@NotNull ProgressIndicator indicator); + } + + /** + * We use this class in order to avoid static initialisation of the wrapped object - it loads number of pico container-based + * dependencies that are unavailable to the slave gradle project, so, we don't want to get unexpected NPE there. + */ + private static class DescriptorHolder { + public static final FileChooserDescriptor GRADLE_BUILD_FILE_CHOOSER_DESCRIPTOR = new OpenProjectFileChooserDescriptor(true) { + @Override + public boolean isFileSelectable(VirtualFile file) { + return GradleConstants.DEFAULT_SCRIPT_NAME.equals(file.getName()); + } + + @Override + public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { + if (!super.isFileVisible(file, showHiddenFiles)) { + return false; + } + return file.isDirectory() || GradleConstants.EXTENSION.equals(file.getExtension()); + } + }; + } } From df47c5d2b8be2f7c07bcd42a899a5ed92d45d83e Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 9 Feb 2012 15:47:16 +0100 Subject: [PATCH 05/15] allow a button to be marked as 'download', which causes it to be hidden when patch is available --- .../openapi/updateSettings/impl/ButtonInfo.java | 11 +++++++++++ .../openapi/updateSettings/impl/UpdateInfoDialog.java | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/ButtonInfo.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/ButtonInfo.java index 82820fc66cc0..6cbe0478b2dc 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/ButtonInfo.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/ButtonInfo.java @@ -23,10 +23,12 @@ import org.jdom.Element; public class ButtonInfo { private final String myName; private final String myUrl; + private final boolean myDownload; public ButtonInfo(Element element) { myName = element.getAttributeValue("name"); myUrl = element.getAttributeValue("url"); + myDownload = element.getAttributeValue("download") != null; } public String getName() { @@ -36,4 +38,13 @@ public class ButtonInfo { public String getUrl() { return myUrl; } + + /** + * The button marked with 'download=true' attribute is hidden if patch is available. + * + * @return true if the button should be hidden when the patch is available. + */ + public boolean isDownload() { + return myDownload; + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java index af11cfe29660..9f4c64875055 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateInfoDialog.java @@ -66,11 +66,14 @@ class UpdateInfoDialog extends AbstractUpdateDialog { } else { for (ButtonInfo info : buttons) { - actions.add(new ButtonAction(info)); + if (!info.isDownload()) { + actions.add(new ButtonAction(info)); + } } } } else { + // the first button replaces the OK action for (int i = 1; i < buttons.size(); i++) { actions.add(new ButtonAction(buttons.get(i))); } From 521800a076fa3aad21dc4788169e3cfa2ce0cea7 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 9 Feb 2012 16:20:25 +0100 Subject: [PATCH 06/15] delay interval should be long, not int --- .../platform-api/src/com/intellij/util/Alarm.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/platform/platform-api/src/com/intellij/util/Alarm.java b/platform/platform-api/src/com/intellij/util/Alarm.java index 8f0117dd4844..ce2f46875680 100644 --- a/platform/platform-api/src/com/intellij/util/Alarm.java +++ b/platform/platform-api/src/com/intellij/util/Alarm.java @@ -92,21 +92,21 @@ public class Alarm implements Disposable { } } - public void addRequest(final Runnable request, int delayMillis) { + public void addRequest(final Runnable request, long delayMillis) { _addRequest(request, delayMillis, myThreadToUse == ThreadToUse.SWING_THREAD ? ModalityState.current() : null); } - public void addComponentRequest(Runnable request, int delay) { + public void addComponentRequest(Runnable request, long delayMillis) { assert myActivationComponent != null; - _addRequest(request, delay, ModalityState.stateForComponent(myActivationComponent)); + _addRequest(request, delayMillis, ModalityState.stateForComponent(myActivationComponent)); } - public void addRequest(final Runnable request, int delayMillis, @Nullable final ModalityState modalityState) { + public void addRequest(final Runnable request, long delayMillis, @Nullable final ModalityState modalityState) { LOG.assertTrue(myThreadToUse == ThreadToUse.SWING_THREAD); _addRequest(request, delayMillis, modalityState); } - private void _addRequest(final Runnable request, int delayMillis, ModalityState modalityState) { + private void _addRequest(final Runnable request, long delayMillis, ModalityState modalityState) { synchronized (LOCK) { LOG.assertTrue(!myDisposed, "Already disposed"); final Request requestToSchedule = new Request(request, modalityState, delayMillis); @@ -191,9 +191,9 @@ public class Alarm implements Disposable { private Runnable myTask; private final ModalityState myModalityState; private Future myFuture; - private final int myDelay; + private final long myDelay; - private Request(final Runnable task, final ModalityState modalityState, int delayMillis) { + private Request(final Runnable task, final ModalityState modalityState, long delayMillis) { myTask = task; myModalityState = modalityState; myDelay = delayMillis; From 514f7580120ce84fedbaf584dc1f5465e0a07243 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 9 Feb 2012 16:20:53 +0100 Subject: [PATCH 07/15] check for updates every 24 hours, not only on first startup --- .../com/intellij/idea/IdeaApplication.java | 22 +------ .../updateSettings/impl/UpdateChecker.java | 23 +++++++ .../impl/UpdateCheckerComponent.java | 66 +++++++++++++++++++ .../src/componentSets/UICore.xml | 3 + 4 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateCheckerComponent.java diff --git a/platform/platform-impl/src/com/intellij/idea/IdeaApplication.java b/platform/platform-impl/src/com/intellij/idea/IdeaApplication.java index cdbb5a984407..7bfa6c2caeb8 100644 --- a/platform/platform-impl/src/com/intellij/idea/IdeaApplication.java +++ b/platform/platform-impl/src/com/intellij/idea/IdeaApplication.java @@ -31,8 +31,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.ExtensionPoint; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; -import com.intellij.openapi.updateSettings.impl.CheckForUpdateResult; -import com.intellij.openapi.updateSettings.impl.PluginDownloader; import com.intellij.openapi.updateSettings.impl.UpdateChecker; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.IconLoader; @@ -49,7 +47,6 @@ import javax.swing.*; import java.awt.*; import java.io.IOException; import java.util.Arrays; -import java.util.List; @SuppressWarnings({"CallToPrintStackTrace"}) @@ -217,7 +214,7 @@ public class IdeaApplication { if (UpdateChecker.isMyVeryFirstOpening() && UpdateChecker.checkNeeded()) { UpdateChecker.setMyVeryFirstOpening(false); - updatePlugins(true); + UpdateChecker.updateAndShowResult(); } SwingUtilities.invokeLater(new Runnable() { @@ -229,23 +226,6 @@ public class IdeaApplication { }, ModalityState.NON_MODAL); } - private void updatePlugins(final boolean showConfirmation) { - final Application app = ApplicationManager.getApplication(); - app.executeOnPooledThread(new Runnable() { - @Override - public void run() { - final CheckForUpdateResult checkForUpdateResult = UpdateChecker.checkForUpdates(); - - final List updatedPlugins = UpdateChecker.updatePlugins(false, null); - app.invokeLater(new Runnable() { - @Override - public void run() { - UpdateChecker.showUpdateResult(checkForUpdateResult, updatedPlugins, showConfirmation, true, false); - } - }); - } - }); - } } private void loadProject() { diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.java index 476dde4d0714..dd6d07f04f75 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateChecker.java @@ -22,6 +22,7 @@ import com.intellij.ide.util.PropertiesComponent; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; +import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; @@ -32,6 +33,7 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.BuildNumber; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.SystemInfo; @@ -143,6 +145,27 @@ public final class UpdateChecker { return settings.CHECK_NEEDED; } + public static ActionCallback updateAndShowResult() { + final ActionCallback result = new ActionCallback(); + final Application app = ApplicationManager.getApplication(); + app.executeOnPooledThread(new Runnable() { + @Override + public void run() { + final CheckForUpdateResult checkForUpdateResult = checkForUpdates(); + + final List updatedPlugins = updatePlugins(false, null); + app.invokeLater(new Runnable() { + @Override + public void run() { + showUpdateResult(checkForUpdateResult, updatedPlugins, true, true, false); + result.setDone(); + } + }); + } + }); + return result; + } + public static List updatePlugins(final boolean showErrorDialog, final @Nullable PluginHostsConfigurable hostsConfigurable) { final List downloaded = new ArrayList(); diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateCheckerComponent.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateCheckerComponent.java new file mode 100644 index 000000000000..67e1589b474f --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateCheckerComponent.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.updateSettings.impl; + +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.util.Disposer; +import com.intellij.util.Alarm; +import com.intellij.util.text.DateFormatUtil; +import org.jetbrains.annotations.NotNull; + +/** + * @author yole + */ +public class UpdateCheckerComponent implements ApplicationComponent { + private static final long CHECK_INTERVAL = DateFormatUtil.DAY; + private final Alarm myCheckForUpdatesAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + private final Runnable myCheckRunnable = new Runnable() { + @Override + public void run() { + UpdateChecker.updateAndShowResult().doWhenDone(new Runnable() { + @Override + public void run() { + queueNextUpdateCheck(CHECK_INTERVAL); + } + }); + } + }; + + @Override + public void initComponent() { + if (UpdateChecker.checkNeeded()) { + + } + queueNextUpdateCheck(UpdateChecker.checkNeeded() + ? CHECK_INTERVAL + : UpdateSettings.getInstance().LAST_TIME_CHECKED - System.currentTimeMillis() + CHECK_INTERVAL); + } + + private void queueNextUpdateCheck(long interval) { + myCheckForUpdatesAlarm.addRequest(myCheckRunnable, interval); + } + + @Override + public void disposeComponent() { + Disposer.dispose(myCheckForUpdatesAlarm); + } + + @NotNull + @Override + public String getComponentName() { + return "UpdateCheckerComponent"; + } +} diff --git a/platform/platform-resources/src/componentSets/UICore.xml b/platform/platform-resources/src/componentSets/UICore.xml index 1e300df10652..74c53dfecc00 100644 --- a/platform/platform-resources/src/componentSets/UICore.xml +++ b/platform/platform-resources/src/componentSets/UICore.xml @@ -57,6 +57,9 @@ com.intellij.ui.switcher.QuickAccessSettings + + com.intellij.openapi.updateSettings.impl.UpdateCheckerComponent + From 0255a710634f34c68f11126dc939942aa5f66632 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 9 Feb 2012 16:21:12 +0100 Subject: [PATCH 08/15] cleanup --- .../openapi/updateSettings/impl/UpdateCheckerComponent.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateCheckerComponent.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateCheckerComponent.java index 67e1589b474f..e625161174a0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateCheckerComponent.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/UpdateCheckerComponent.java @@ -41,9 +41,6 @@ public class UpdateCheckerComponent implements ApplicationComponent { @Override public void initComponent() { - if (UpdateChecker.checkNeeded()) { - - } queueNextUpdateCheck(UpdateChecker.checkNeeded() ? CHECK_INTERVAL : UpdateSettings.getInstance().LAST_TIME_CHECKED - System.currentTimeMillis() + CHECK_INTERVAL); From 0e54635104a8c862b7b36c9940397349ccb25a00 Mon Sep 17 00:00:00 2001 From: irengrig Date: Thu, 9 Feb 2012 20:00:44 +0400 Subject: [PATCH 09/15] SVN 1.7: tests: fix a bit & bomb not working yet --- .../idea/svn17/SvnNativeListsTest.java | 32 ++++++++++++++++--- .../jetbrains/idea/svn17/SvnRenameTest.java | 14 ++++---- .../svn17/SvnTestDirtyScopeStateTest.java | 3 ++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnNativeListsTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnNativeListsTest.java index 6429e791fa0d..f55da009f2eb 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnNativeListsTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnNativeListsTest.java @@ -43,6 +43,7 @@ public class SvnNativeListsTest extends SvnTestCase { @Test public void testAdd() throws Throwable { final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + sleepABit(); myDirtyScopeManager.markEverythingDirty(); myChangeListManager.ensureUpToDate(false); @@ -50,6 +51,7 @@ public class SvnNativeListsTest extends SvnTestCase { enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); final VirtualFile file = createFileInCommand("a.txt", "old content"); + sleepABit(); ensureAddedToNativeList(); @@ -66,6 +68,7 @@ public class SvnNativeListsTest extends SvnTestCase { @Test public void testDeleted() throws Throwable { final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + sleepABit(); myDirtyScopeManager.markEverythingDirty(); myChangeListManager.ensureUpToDate(false); @@ -74,9 +77,10 @@ public class SvnNativeListsTest extends SvnTestCase { enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); final VirtualFile file = createFileInCommand("a.txt", "old content"); + sleepABit(); checkin(); deleteFileInCommand(file); - + sleepABit(); ensureAddedToNativeList(); verify(runSvn("status"), "", "--- Changelist 'newOne':", "D a.txt"); @@ -85,6 +89,7 @@ public class SvnNativeListsTest extends SvnTestCase { @Test public void testEdit() throws Throwable { final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + sleepABit(); myDirtyScopeManager.markEverythingDirty(); myChangeListManager.ensureUpToDate(false); @@ -92,9 +97,10 @@ public class SvnNativeListsTest extends SvnTestCase { enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); final VirtualFile file = createFileInCommand("a.txt", "old content"); + sleepABit(); checkin(); editFileInCommand(myProject, file, "111"); - + sleepABit(); ensureAddedToNativeList(); verify(runSvn("status"), "", "--- Changelist 'newOne':", "M a.txt"); @@ -103,7 +109,7 @@ public class SvnNativeListsTest extends SvnTestCase { @Test public void testEditAndMove() throws Throwable { final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); - + sleepABit(); myDirtyScopeManager.markEverythingDirty(); myChangeListManager.ensureUpToDate(false); @@ -112,14 +118,16 @@ public class SvnNativeListsTest extends SvnTestCase { enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); final VirtualFile file = createFileInCommand("a.txt", "old content"); + sleepABit(); checkin(); editFileInCommand(myProject, file, "111"); - + sleepABit(); ensureAddedToNativeList(); verify(runSvn("status"), "", "--- Changelist 'newOne':", "M a.txt"); renameFileInCommand(file, "b.txt"); + sleepABit(); verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + b.txt", "D a.txt"); ensureAddedToNativeList(); @@ -130,6 +138,7 @@ public class SvnNativeListsTest extends SvnTestCase { @Test public void testMove() throws Throwable { final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + sleepABit(); myDirtyScopeManager.markEverythingDirty(); myChangeListManager.ensureUpToDate(false); @@ -138,9 +147,11 @@ public class SvnNativeListsTest extends SvnTestCase { enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); final VirtualFile file = createFileInCommand("a.txt", "old content"); + sleepABit(); checkin(); renameFileInCommand(file, "b.txt"); + sleepABit(); ensureAddedToNativeList(); verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + b.txt", "D a.txt"); @@ -149,6 +160,7 @@ public class SvnNativeListsTest extends SvnTestCase { @Test public void testMoveMove() throws Throwable { final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + sleepABit(); myDirtyScopeManager.markEverythingDirty(); myChangeListManager.ensureUpToDate(false); @@ -157,18 +169,30 @@ public class SvnNativeListsTest extends SvnTestCase { enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); final VirtualFile file = createFileInCommand("a.txt", "old content"); + sleepABit(); checkin(); renameFileInCommand(file, "b.txt"); + sleepABit(); ensureAddedToNativeList(); verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + b.txt", "D a.txt"); renameFileInCommand(file, "c.txt"); + sleepABit(); verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + c.txt", "D a.txt"); ensureAddedToNativeList(); verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + c.txt", "D a.txt"); } + + private void sleepABit() { + try { + Thread.sleep(50); + } + catch (InterruptedException e) { + // + } + } } diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java index 8c3363fae655..e16842214e91 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java @@ -1,8 +1,10 @@ package org.jetbrains.idea.svn17; import com.intellij.execution.process.ProcessOutput; +import com.intellij.idea.Bombed; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.FilePathImpl; import com.intellij.openapi.vcs.FileStatus; import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vcs.VcsException; @@ -17,10 +19,7 @@ import org.junit.Test; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import java.util.*; /** * @author yole @@ -95,12 +94,13 @@ public class SvnRenameTest extends SvnTestCase { verifyChange(changes.get(2), "child" + File.separatorChar + "grandChild", "childnew" + File.separatorChar + "grandChild"); verifyChange(changes.get(3), "child" + File.separatorChar + "grandChild" + File.separatorChar + "b.txt", "childnew" + File.separatorChar + "grandChild" + File.separatorChar + "b.txt"); - VirtualFile oldChild = myWorkingCopyDir.findChild("child"); + // there is no such directory any more + /*VirtualFile oldChild = myWorkingCopyDir.findChild("child"); if (oldChild == null) { myWorkingCopyDir.refresh(false, true); oldChild = myWorkingCopyDir.findChild("child"); } - Assert.assertEquals(FileStatus.DELETED, changeListManager.getStatus(oldChild)); + Assert.assertEquals(FileStatus.DELETED, changeListManager.getStatus(oldChild));*/ } private VirtualFile prepareDirectoriesForRename() throws IOException { @@ -178,6 +178,7 @@ public class SvnRenameTest extends SvnTestCase { return "{" + StringUtil.join(changes, StringUtil.createToStringFunction(Change.class), ",") + "}"; } + @Bombed(user = "irengrig", year = 2012, month = Calendar.FEBRUARY, day = 15) // IDEADEV-19223 @Test public void testRollbackRenameWithUnversioned() throws Exception { @@ -225,6 +226,7 @@ public class SvnRenameTest extends SvnTestCase { Assert.assertTrue(new File(unversionedDirFile, "c.txt").exists()); } + @Bombed(user = "irengrig", year = 2012, month = Calendar.FEBRUARY, day = 15) // IDEA-13824 @Test public void testRenameFileRenameDir() throws Exception { diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestDirtyScopeStateTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestDirtyScopeStateTest.java index 5d485bb224cf..f5e93a00387c 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestDirtyScopeStateTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestDirtyScopeStateTest.java @@ -1,5 +1,6 @@ package org.jetbrains.idea.svn17; +import com.intellij.idea.Bombed; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.FilePathImpl; @@ -12,6 +13,7 @@ import junit.framework.Assert; import org.junit.Test; import java.util.Arrays; +import java.util.Calendar; import java.util.Collection; import java.util.List; @@ -23,6 +25,7 @@ public class SvnTestDirtyScopeStateTest extends SvnTestCase { myInitChangeListManager = false; } + @Bombed(user = "irengrig", year = 2012, month = Calendar.FEBRUARY, day = 15) @Test public void testWhatIsDirty() throws Exception { enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); From ea648b323dc0989dbdfd742182ef14e7298f4823 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Thu, 9 Feb 2012 19:46:53 +0400 Subject: [PATCH 10/15] move createSdkManager() to AndroidCommonUtils --- .../android/util/AndroidCommonUtils.java | 42 ++++++++++++++++++- .../org/jetbrains/android/sdk/AndroidSdk.java | 3 +- .../android/sdk/AndroidSdkUtils.java | 23 +--------- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java b/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java index 450687945d47..3b54dcf9532d 100644 --- a/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java +++ b/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java @@ -1,8 +1,15 @@ package org.jetbrains.android.util; +import com.android.sdklib.ISdkLog; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.SdkManager; +import com.intellij.openapi.util.io.FileUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.io.File; +import java.io.IOException; import java.util.Collection; import java.util.Iterator; @@ -11,7 +18,7 @@ import java.util.Iterator; */ public class AndroidCommonUtils { @NonNls public static final Object MANIFEST_JAVA_FILE_NAME = "Manifest.java"; - public static final String R_JAVA_FILENAME = "R.java"; + @NonNls public static final String R_JAVA_FILENAME = "R.java"; private AndroidCommonUtils() { } @@ -29,4 +36,37 @@ public class AndroidCommonUtils { } return builder.toString(); } + + @Nullable + public static SdkManager createSdkManager(@NotNull String path, @NotNull ISdkLog log) { + path = FileUtil.toSystemDependentName(path); + + final File f = new File(path); + if (!f.exists() || !f.isDirectory()) { + return null; + } + + final File platformsDir = new File(f, SdkConstants.FD_PLATFORMS); + if (!platformsDir.exists() || !platformsDir.isDirectory()) { + return null; + } + + return SdkManager.createManager(path + File.separatorChar, log); + } + + public static void moveAllFiles(@NotNull File from, @NotNull File to, @NotNull Collection newFiles) throws IOException { + if (from.isFile()) { + FileUtil.rename(from, to); + newFiles.add(to); + } + else { + final File[] children = from.listFiles(); + + if (children != null) { + for (File child : children) { + moveAllFiles(child, new File(to, child.getName()), newFiles); + } + } + } + } } diff --git a/plugins/android/src/org/jetbrains/android/sdk/AndroidSdk.java b/plugins/android/src/org/jetbrains/android/sdk/AndroidSdk.java index 877dc802f84c..78cd3fbf1871 100644 --- a/plugins/android/src/org/jetbrains/android/sdk/AndroidSdk.java +++ b/plugins/android/src/org/jetbrains/android/sdk/AndroidSdk.java @@ -33,6 +33,7 @@ import com.intellij.reference.SoftReference; import com.intellij.util.containers.HashMap; import gnu.trove.TIntObjectHashMap; import org.jetbrains.android.actions.AndroidEnableDdmsAction; +import org.jetbrains.android.util.AndroidCommonUtils; import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.android.util.BufferingFileWrapper; import org.jetbrains.annotations.NotNull; @@ -152,7 +153,7 @@ public class AndroidSdk { @Nullable public static AndroidSdk parse(@NotNull String path, @NotNull ISdkLog log) { - final SdkManager manager = AndroidSdkUtils.createSdkManager(path, log); + final SdkManager manager = AndroidCommonUtils.createSdkManager(path, log); return manager != null ? new AndroidSdk(manager, path) : null; } diff --git a/plugins/android/src/org/jetbrains/android/sdk/AndroidSdkUtils.java b/plugins/android/src/org/jetbrains/android/sdk/AndroidSdkUtils.java index 766e05545306..1536eac0a5e8 100644 --- a/plugins/android/src/org/jetbrains/android/sdk/AndroidSdkUtils.java +++ b/plugins/android/src/org/jetbrains/android/sdk/AndroidSdkUtils.java @@ -17,9 +17,7 @@ package org.jetbrains.android.sdk; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; -import com.android.sdklib.SdkManager; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; @@ -36,12 +34,12 @@ import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.HashSet; +import org.jetbrains.android.util.AndroidCommonUtils; import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; import java.util.*; /** @@ -55,7 +53,7 @@ public class AndroidSdkUtils { } public static boolean isAndroidSdk(@NotNull String path) { - return createSdkManager(path, new EmptySdkLog()) != null; + return AndroidCommonUtils.createSdkManager(path, new EmptySdkLog()) != null; } @Nullable @@ -370,21 +368,4 @@ public class AndroidSdkUtils { public static void openModuleDependenciesConfigurable(final Module module) { ProjectSettingsService.getInstance(module.getProject()).openModuleDependenciesSettings(module, null); } - - @Nullable - public static SdkManager createSdkManager(@NotNull String path, @NotNull ISdkLog log) { - path = FileUtil.toSystemDependentName(path); - - final File f = new File(path); - if (!f.exists() || !f.isDirectory()) { - return null; - } - - final File platformsDir = new File(f, SdkConstants.FD_PLATFORMS); - if (!platformsDir.exists() || !platformsDir.isDirectory()) { - return null; - } - - return SdkManager.createManager(path + File.separatorChar, log); - } } From 4be123c3a3b1223c3653307ea1231b5986c61706 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Thu, 9 Feb 2012 19:49:22 +0400 Subject: [PATCH 11/15] move renderscript tool wrapper to 'android-rt' module --- .../compiler/tools/AndroidRenderscript.java | 58 ++++++++++++++++ .../compiler/AndroidAutogenerator.java | 29 +++----- .../compiler/AndroidRenderscriptCompiler.java | 69 ++++--------------- 3 files changed, 82 insertions(+), 74 deletions(-) create mode 100644 plugins/android/rt/src/org/jetbrains/android/compiler/tools/AndroidRenderscript.java diff --git a/plugins/android/rt/src/org/jetbrains/android/compiler/tools/AndroidRenderscript.java b/plugins/android/rt/src/org/jetbrains/android/compiler/tools/AndroidRenderscript.java new file mode 100644 index 000000000000..0bd16812613e --- /dev/null +++ b/plugins/android/rt/src/org/jetbrains/android/compiler/tools/AndroidRenderscript.java @@ -0,0 +1,58 @@ +package org.jetbrains.android.compiler.tools; + +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.ArrayUtil; +import org.jetbrains.android.util.AndroidCommonUtils; +import org.jetbrains.android.util.AndroidCompilerMessageKind; +import org.jetbrains.android.util.ExecutionUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidRenderscript { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.AndroidRenderscriptCompiler"); + + public static Map> execute(@NotNull final String sdkLocation, + @NotNull IAndroidTarget target, + @NotNull String sourceFilePath, + @NotNull final String genFolderPath, + @Nullable String depFolderPath, + @NotNull final String rawDirPath) + throws IOException { + final List command = new ArrayList(); + command.add( + FileUtil.toSystemDependentName(sdkLocation + '/' + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + SdkConstants.FN_RENDERSCRIPT)); + command.add("-I"); + command.add(target.getPath(IAndroidTarget.ANDROID_RS_CLANG)); + command.add("-I"); + command.add(target.getPath(IAndroidTarget.ANDROID_RS)); + command.add("-p"); + command.add(FileUtil.toSystemDependentName(genFolderPath)); + command.add("-o"); + command.add(FileUtil.toSystemDependentName(rawDirPath)); + + command.add("-target-api"); + command.add(Integer.toString(target.getVersion().getApiLevel())); + + if (depFolderPath != null) { + command.add("-d"); + command.add(FileUtil.toSystemDependentName(depFolderPath)); + } + + command.add("-MD"); + command.add(FileUtil.toSystemDependentName(sourceFilePath)); + + LOG.info(AndroidCommonUtils.command2string(command)); + return ExecutionUtil.doExecute(ArrayUtil.toStringArray(command)); + } +} diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidAutogenerator.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidAutogenerator.java index 5121f6a31a6f..54802f56a12e 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/AndroidAutogenerator.java +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidAutogenerator.java @@ -20,6 +20,7 @@ import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashSet; import org.jetbrains.android.compiler.tools.AndroidApt; import org.jetbrains.android.compiler.tools.AndroidIdl; +import org.jetbrains.android.compiler.tools.AndroidRenderscript; import org.jetbrains.android.dom.manifest.Manifest; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidRootUtil; @@ -448,13 +449,19 @@ public class AndroidAutogenerator { try { tempOutDir = FileUtil.createTempDirectory("android_renderscript_autogeneration", "tmp"); + final VirtualFile vTempOutDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempOutDir); - final Map> messages = AndroidRenderscriptCompiler. - launchRenderscriptCompiler(module.getProject(), item.mySdkLocation, item.myTarget, file, tempOutDir.getPath(), item.myRawDirPath); + final String depFolderPath = + vTempOutDir != null ? AndroidRenderscriptCompiler.getDependencyFolder(context.getProject(), file, vTempOutDir) : null; + + final Map> messages = AndroidCompileUtil.toCompilerMessageCategoryKeys( + AndroidRenderscript + .execute(item.mySdkLocation, item.myTarget, file.getPath(), tempOutDir.getPath(), depFolderPath, + item.myRawDirPath)); if (messages.get(CompilerMessageCategory.ERROR).size() == 0) { final List newFiles = new ArrayList(); - moveAllFiles(tempOutDir, new File(item.myGenDirPath), newFiles); + AndroidCommonUtils.moveAllFiles(tempOutDir, new File(item.myGenDirPath), newFiles); for (File newFile : newFiles) { final VirtualFile newVFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(newFile); @@ -502,22 +509,6 @@ public class AndroidAutogenerator { } } - private static void moveAllFiles(@NotNull File from, @NotNull File to, @NotNull Collection newFiles) throws IOException { - if (from.isFile()) { - FileUtil.rename(from, to); - newFiles.add(to); - } - else { - final File[] children = from.listFiles(); - - if (children != null) { - for (File child : children) { - moveAllFiles(child, new File(to, child.getName()), newFiles); - } - } - } - } - private static boolean ensureFilesWritable(@NotNull final Project project, @NotNull final Collection filesToCheck) { if (filesToCheck.size() == 0) { return true; diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidRenderscriptCompiler.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidRenderscriptCompiler.java index 9dff00c3448f..d6cbc490baad 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/AndroidRenderscriptCompiler.java +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidRenderscriptCompiler.java @@ -2,31 +2,26 @@ package org.jetbrains.android.compiler; import com.android.AndroidConstants; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.SdkConstants; import com.intellij.facet.FacetManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.*; import com.intellij.openapi.compiler.ex.CompileContextEx; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.ArrayUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashSet; +import org.jetbrains.android.compiler.tools.AndroidRenderscript; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidRootUtil; import org.jetbrains.android.fileTypes.AndroidRenderscriptFileType; import org.jetbrains.android.sdk.AndroidPlatform; -import org.jetbrains.android.util.AndroidBundle; -import org.jetbrains.android.util.AndroidCommonUtils; -import org.jetbrains.android.util.AndroidUtils; +import org.jetbrains.android.util.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,8 +35,7 @@ import java.util.*; * @author Eugene.Kudelevsky */ public class AndroidRenderscriptCompiler implements SourceGeneratingCompiler { - private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.AndroidRenderscriptCompiler"); - + private static final GenerationItem[] EMPTY_GENERATION_ITEM_ARRAY = {}; @Nullable @@ -190,13 +184,15 @@ public class AndroidRenderscriptCompiler implements SourceGeneratingCompiler { } for (final VirtualFile sourceFile : genItem.myFiles) { + final String depFolderOsPath = getDependencyFolder(context.getProject(), sourceFile, outputRootDirectory); + try { - final Map> messages = launchRenderscriptCompiler(context.getProject(), - genItem.mySdkLocation, - genItem.myAndroidTarget, - sourceFile, - genRootPath, - genItem.myRawDirPath); + final Map> messages = AndroidCompileUtil.toCompilerMessageCategoryKeys( + AndroidRenderscript + .execute(genItem.mySdkLocation, genItem.myAndroidTarget, sourceFile.getPath(), genRootPath, + depFolderOsPath, + genItem.myRawDirPath)); + ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { if (context.getProject().isDisposed()) { @@ -235,47 +231,10 @@ public class AndroidRenderscriptCompiler implements SourceGeneratingCompiler { } } - static Map> launchRenderscriptCompiler(@NotNull Project project, - @NotNull final String sdkLocation, - @NotNull IAndroidTarget target, - @NotNull final VirtualFile sourceFile, - @NotNull final String genFolderPath, - @NotNull final String rawDirPath) - throws IOException { - final List command = new ArrayList(); - command.add( - FileUtil.toSystemDependentName(sdkLocation + '/' + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + SdkConstants.FN_RENDERSCRIPT)); - command.add("-I"); - command.add(target.getPath(IAndroidTarget.ANDROID_RS_CLANG)); - command.add("-I"); - command.add(target.getPath(IAndroidTarget.ANDROID_RS)); - command.add("-p"); - command.add(FileUtil.toSystemDependentName(genFolderPath)); - command.add("-o"); - command.add(FileUtil.toSystemDependentName(rawDirPath)); - - final String sourceFilePath = FileUtil.toSystemDependentName(sourceFile.getPath()); - - final VirtualFile genFolder = LocalFileSystem.getInstance().refreshAndFindFileByPath(FileUtil.toSystemIndependentName(genFolderPath)); - if (genFolder != null) { - final String dependencyFolderPath = getDependencyFolder(project, sourceFile, genFolder); - if (dependencyFolderPath != null) { - command.add("-d"); - command.add(FileUtil.toSystemDependentName(dependencyFolderPath)); - } - } - - command.add("-MD"); - command.add(sourceFilePath); - - LOG.info(AndroidCommonUtils.command2string(command)); - return AndroidCompileUtil.execute(ArrayUtil.toStringArray(command)); - } - @Nullable - private static String getDependencyFolder(@NotNull final Project project, - @NotNull final VirtualFile sourceFile, - @NotNull final VirtualFile genFolder) { + static String getDependencyFolder(@NotNull final Project project, + @NotNull final VirtualFile sourceFile, + @NotNull final VirtualFile genFolder) { final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex(); final VirtualFile sourceRoot = index.getSourceRootForFile(sourceFile); From 01ca2633a0fc1f743d8e466c0b3604537667ece1 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Thu, 9 Feb 2012 19:55:49 +0400 Subject: [PATCH 12/15] extract interface JavaSdk --- .../src/org/jetbrains/jps/JavaSdk.groovy | 20 +++++------------- .../src/org/jetbrains/jps/JavaSdkImpl.groovy | 21 +++++++++++++++++++ .../src/org/jetbrains/jps/Project.groovy | 2 +- .../org/jetbrains/jps/idea/JavaSdkType.groovy | 4 ++-- 4 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 jps/model/src/org/jetbrains/jps/JavaSdkImpl.groovy diff --git a/jps/model/src/org/jetbrains/jps/JavaSdk.groovy b/jps/model/src/org/jetbrains/jps/JavaSdk.groovy index 2bd8812ce617..17aa8457d59c 100644 --- a/jps/model/src/org/jetbrains/jps/JavaSdk.groovy +++ b/jps/model/src/org/jetbrains/jps/JavaSdk.groovy @@ -1,21 +1,11 @@ package org.jetbrains.jps /** - * @author nik + * @author Eugene.Kudelevsky */ -class JavaSdk extends Sdk { - String jdkPath +public interface JavaSdk { - def JavaSdk(Project project, String name, String jdkPath, Closure initializer) { - super(project, name, initializer) - this.jdkPath = jdkPath - } + String getJavacExecutable(); - String getJavacExecutable() { - return jdkPath + File.separator + "bin" + File.separator + "javac"; - } - - String getJavaExecutable() { - return jdkPath + File.separator + "bin" + File.separator + "java"; - } -} + String getJavaExecutable(); +} \ No newline at end of file diff --git a/jps/model/src/org/jetbrains/jps/JavaSdkImpl.groovy b/jps/model/src/org/jetbrains/jps/JavaSdkImpl.groovy new file mode 100644 index 000000000000..9caa1092f55d --- /dev/null +++ b/jps/model/src/org/jetbrains/jps/JavaSdkImpl.groovy @@ -0,0 +1,21 @@ +package org.jetbrains.jps + +/** + * @author nik + */ +class JavaSdkImpl extends Sdk implements JavaSdk { + String jdkPath + + def JavaSdkImpl(Project project, String name, String jdkPath, Closure initializer) { + super(project, name, initializer) + this.jdkPath = jdkPath + } + + String getJavacExecutable() { + return jdkPath + File.separator + "bin" + File.separator + "javac"; + } + + String getJavaExecutable() { + return jdkPath + File.separator + "bin" + File.separator + "java"; + } +} diff --git a/jps/model/src/org/jetbrains/jps/Project.groovy b/jps/model/src/org/jetbrains/jps/Project.groovy index 60f0fdc74af5..3a89242c5f6a 100644 --- a/jps/model/src/org/jetbrains/jps/Project.groovy +++ b/jps/model/src/org/jetbrains/jps/Project.groovy @@ -43,7 +43,7 @@ class Project { } def JavaSdk createJavaSdk(String name, String path, Closure initializer) { - def sdk = new JavaSdk(this, name, path, initializer) + def sdk = new JavaSdkImpl(this, name, path, initializer) sdks[name] = sdk return sdk } diff --git a/jps/model/src/org/jetbrains/jps/idea/JavaSdkType.groovy b/jps/model/src/org/jetbrains/jps/idea/JavaSdkType.groovy index 6f8f80dc8032..c300e9235363 100644 --- a/jps/model/src/org/jetbrains/jps/idea/JavaSdkType.groovy +++ b/jps/model/src/org/jetbrains/jps/idea/JavaSdkType.groovy @@ -1,8 +1,8 @@ package org.jetbrains.jps.idea +import org.jetbrains.jps.JavaSdkImpl import org.jetbrains.jps.Project import org.jetbrains.jps.Sdk -import org.jetbrains.jps.JavaSdk /** * @author nik @@ -14,6 +14,6 @@ class JavaSdkType extends SdkTypeService { @Override Sdk createSdk(Project project, String name, String homePath, Node additionalData) { - return new JavaSdk(project, name, homePath, {}) + return new JavaSdkImpl(project, name, homePath, {}) } } From a9a41ab71d1582eacfc53e3564ffdd14b4596b5c Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Thu, 9 Feb 2012 19:57:02 +0400 Subject: [PATCH 13/15] log command --- .../org/jetbrains/android/compiler/tools/AndroidIdl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/android/rt/src/org/jetbrains/android/compiler/tools/AndroidIdl.java b/plugins/android/rt/src/org/jetbrains/android/compiler/tools/AndroidIdl.java index 16e0bc1a3175..0984f0a8e9aa 100644 --- a/plugins/android/rt/src/org/jetbrains/android/compiler/tools/AndroidIdl.java +++ b/plugins/android/rt/src/org/jetbrains/android/compiler/tools/AndroidIdl.java @@ -16,7 +16,9 @@ package org.jetbrains.android.compiler.tools; import com.android.sdklib.IAndroidTarget; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.ArrayUtil; +import org.jetbrains.android.util.AndroidCommonUtils; import org.jetbrains.android.util.AndroidCompilerMessageKind; import org.jetbrains.android.util.ExecutionUtil; import org.jetbrains.annotations.NotNull; @@ -32,6 +34,8 @@ import java.util.Map; * @author Alexey Efimov */ public final class AndroidIdl { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.tools.AndroidIdl"); + private AndroidIdl() { } @@ -51,6 +55,8 @@ public final class AndroidIdl { } commands.add(file); commands.add(outFile); + + LOG.info(AndroidCommonUtils.command2string(commands)); return ExecutionUtil.doExecute(ArrayUtil.toStringArray(commands)); } From 353fcb57a47cf116fd70bd2c07f61102d7c3176a Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Thu, 9 Feb 2012 19:57:45 +0400 Subject: [PATCH 14/15] move MessageBuilderSdkLog to 'android-rt' module --- .../org/jetbrains/android/sdk/MessageBuildingSdkLog.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) rename plugins/android/{ => rt}/src/org/jetbrains/android/sdk/MessageBuildingSdkLog.java (91%) diff --git a/plugins/android/src/org/jetbrains/android/sdk/MessageBuildingSdkLog.java b/plugins/android/rt/src/org/jetbrains/android/sdk/MessageBuildingSdkLog.java similarity index 91% rename from plugins/android/src/org/jetbrains/android/sdk/MessageBuildingSdkLog.java rename to plugins/android/rt/src/org/jetbrains/android/sdk/MessageBuildingSdkLog.java index 7c36b181cd6f..49e083d10236 100644 --- a/plugins/android/src/org/jetbrains/android/sdk/MessageBuildingSdkLog.java +++ b/plugins/android/rt/src/org/jetbrains/android/sdk/MessageBuildingSdkLog.java @@ -21,11 +21,7 @@ import com.intellij.openapi.diagnostic.Logger; import org.jetbrains.annotations.NotNull; /** - * Created by IntelliJ IDEA. - * User: Eugene.Kudelevsky - * Date: Aug 25, 2009 - * Time: 5:54:47 PM - * To change this template use File | Settings | File Templates. + * @author Eugene.Kudelevsky */ public class MessageBuildingSdkLog implements ISdkLog { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.sdk.MessageBuildingSdkLog"); From c098f1dec72ed3559cec7f9bfef0ab45e8839791 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Thu, 9 Feb 2012 19:58:53 +0400 Subject: [PATCH 15/15] android-jps-plugin initial --- .idea/modules.xml | 1 + build/scripts/layouts.gant | 4 + .../android/jps-plugin/android-jps-plugin.iml | 17 + .../org.jetbrains.jps.idea.FacetTypeService | 1 + .../org.jetbrains.jps.idea.SdkTypeService | 1 + ...g.jetbrains.jps.incremental.BuilderService | 1 + .../jps/android/AndroidBuilderService.java | 20 + .../jetbrains/jps/android/AndroidFacet.groovy | 46 ++ .../jps/android/AndroidFacetType.groovy | 22 + .../jetbrains/jps/android/AndroidSdk.groovy | 34 ++ .../jps/android/AndroidSdkType.groovy | 30 ++ .../AndroidSourceGeneratingBuilder.java | 498 ++++++++++++++++++ plugins/android/src/META-INF/plugin.xml | 6 + 13 files changed, 681 insertions(+) create mode 100644 plugins/android/jps-plugin/android-jps-plugin.iml create mode 100644 plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.FacetTypeService create mode 100644 plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.SdkTypeService create mode 100644 plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdk.groovy create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdkType.groovy create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java diff --git a/.idea/modules.xml b/.idea/modules.xml index ce8d7ca005f3..c7736ee6aa66 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -11,6 +11,7 @@ + diff --git a/build/scripts/layouts.gant b/build/scripts/layouts.gant index 6dc01096ba64..a092e36f46eb 100644 --- a/build/scripts/layouts.gant +++ b/build/scripts/layouts.gant @@ -366,6 +366,10 @@ public def layoutCommunityPlugins(String home) { } jar("android_rt.jar") {module("android-rt")} + + dir("jps") { + jar("android-jps-plugin.jar") { module("android-jps-plugin") } + } } } } diff --git a/plugins/android/jps-plugin/android-jps-plugin.iml b/plugins/android/jps-plugin/android-jps-plugin.iml new file mode 100644 index 000000000000..a2f02ccd0c76 --- /dev/null +++ b/plugins/android/jps-plugin/android-jps-plugin.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.FacetTypeService b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.FacetTypeService new file mode 100644 index 000000000000..a75fa45b2aa4 --- /dev/null +++ b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.FacetTypeService @@ -0,0 +1 @@ +org.jetbrains.jps.android.AndroidFacetType \ No newline at end of file diff --git a/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.SdkTypeService b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.SdkTypeService new file mode 100644 index 000000000000..f3084a2b366a --- /dev/null +++ b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.SdkTypeService @@ -0,0 +1 @@ +org.jetbrains.jps.android.AndroidSdkType \ No newline at end of file diff --git a/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService new file mode 100644 index 000000000000..59103ac8ba86 --- /dev/null +++ b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService @@ -0,0 +1 @@ +org.jetbrains.jps.android.AndroidBuilderService \ No newline at end of file diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java new file mode 100644 index 000000000000..81b9e6eba3de --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java @@ -0,0 +1,20 @@ +package org.jetbrains.jps.android; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.incremental.BuilderService; +import org.jetbrains.jps.incremental.ModuleLevelBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutorService; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidBuilderService extends BuilderService { + @NotNull + @Override + public List createModuleLevelBuilders(ExecutorService executorService) { + return Arrays.asList(new AndroidSourceGeneratingBuilder()); + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy new file mode 100644 index 000000000000..e9530f165ee7 --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy @@ -0,0 +1,46 @@ +package org.jetbrains.jps.android + +import com.intellij.openapi.util.io.FileUtil +import org.jetbrains.jps.Module +import org.jetbrains.jps.idea.Facet + +/** + * @author nik + */ +class AndroidFacet extends Facet { + final Module module + final String resFolderRelativePath + + AndroidFacet(Module module, String name, String resFolderRelativePath) { + this.module = module + this.name = name; + this.resFolderRelativePath = resFolderRelativePath + } + + File getResourceDir() { + return findFileByRelativeModulePath(resFolderRelativePath, true) + } + + private File findFileByRelativeModulePath(String relativePath, boolean lookInContentRoot) { + if (module.basePath != null) { + def absPath = FileUtil.toSystemIndependentName(module.basePath + relativePath) + def f = new File(absPath) + + if (f.exists()) { + return f + } + } + + if (lookInContentRoot) { + module.contentRoots.each { + def absPath = FileUtil.toSystemIndependentName(contentRoot + relativePath) + def f = new File(absPath) + + if (f.exists()) { + return f + } + } + } + return null; + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy new file mode 100644 index 000000000000..ca555e24e37e --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy @@ -0,0 +1,22 @@ +package org.jetbrains.jps.android + +import org.jetbrains.jps.MacroExpander +import org.jetbrains.jps.Module +import org.jetbrains.jps.idea.Facet +import org.jetbrains.jps.idea.FacetTypeService + +/** + * @author Eugene.Kudelevsky + */ +class AndroidFacetType extends FacetTypeService { + public static final String ID = "android" + + AndroidFacetType() { + super(ID) + } + + @Override + Facet createFacet(Module module, String name, Node facetConfiguration, MacroExpander macroExpander) { + return new AndroidFacet(module, name, ""); + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdk.groovy b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdk.groovy new file mode 100644 index 000000000000..b4ce00b91c41 --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdk.groovy @@ -0,0 +1,34 @@ +package org.jetbrains.jps.android + +import org.jetbrains.jps.JavaSdk +import org.jetbrains.jps.Project +import org.jetbrains.jps.Sdk +import org.jetbrains.jps.JavaSdkImpl + +/** + * @author Eugene.Kudelevsky + */ +class AndroidSdk extends Sdk implements JavaSdk { + final String sdkPath; + final String buildTargetHashString; + final String javaSdkName; + + AndroidSdk(Project project, String name, String sdkPath, String javaSdkName, String buildTargetHashString) { + super(project, name, {}) + this.sdkPath = sdkPath + this.buildTargetHashString = buildTargetHashString + this.javaSdkName = javaSdkName; + } + + @Override + String getJavacExecutable() { + def javaSdk = project.sdks[javaSdkName] + return javaSdk instanceof JavaSdkImpl ? javaSdk.getJavacExecutable() : null + } + + @Override + String getJavaExecutable() { + def javaSdk = project.sdks[javaSdkName] + return javaSdk instanceof JavaSdkImpl ? javaSdk.getJavaExecutable() : null + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdkType.groovy b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdkType.groovy new file mode 100644 index 000000000000..90d47d29c07c --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdkType.groovy @@ -0,0 +1,30 @@ +package org.jetbrains.jps.android + +import org.jetbrains.jps.idea.SdkTypeService +import org.jetbrains.jps.Sdk +import org.jetbrains.jps.Project + +/** + * @author Eugene.Kudelevsky + */ +class AndroidSdkType extends SdkTypeService { + AndroidSdkType() { + super("Android SDK"); + } + + @Override + Sdk createSdk(Project project, String name, String homePath, Node additionalData) { + def attributes = additionalData.attributes() + if (attributes == null) { + return null; + } + + def buildTargetHashString = (String)attributes.get("sdk") + def internalJdkName = (String)attributes.get("jdk") + + if (internalJdkName == null || buildTargetHashString == null) { + return null; + } + return new AndroidSdk(project, name, homePath, internalJdkName, buildTargetHashString) + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java new file mode 100644 index 000000000000..77abb5082d17 --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java @@ -0,0 +1,498 @@ +package org.jetbrains.jps.android; + +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.HashSet; +import org.jetbrains.android.compiler.tools.AndroidIdl; +import org.jetbrains.android.compiler.tools.AndroidRenderscript; +import org.jetbrains.android.sdk.MessageBuildingSdkLog; +import org.jetbrains.android.util.AndroidCommonUtils; +import org.jetbrains.android.util.AndroidCompilerMessageKind; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.*; +import org.jetbrains.jps.idea.Facet; +import org.jetbrains.jps.incremental.*; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; +import org.jetbrains.jps.incremental.messages.ProgressMessage; +import org.jetbrains.jps.incremental.storage.SourceToOutputMapping; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.*; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.android.AndroidSourceGeneratingBuilder"); + + @NonNls private static final String BUILDER_NAME = "android-source-generator"; + @NonNls private static final String AIDL_EXTENSION = "aidl"; + @NonNls private static final String RENDERSCRIPT_EXTENSION = "rs"; + + public AndroidSourceGeneratingBuilder() { + super(BuilderCategory.SOURCE_GENERATOR); + } + + @Override + public String getName() { + return BUILDER_NAME; + } + + @Override + public ModuleLevelBuilder.ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException { + if (context.isCompilingTests()) { + return ModuleLevelBuilder.ExitCode.OK; + } + + try { + return doBuild(context, chunk); + } + catch (ProjectBuildException e) { + throw e; + } + catch (Exception e) { + String message = e.getMessage(); + + if (message == null) { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + //noinspection IOResourceOpenedButNotSafelyClosed + e.printStackTrace(new PrintStream(out)); + message = "Internal error: \n" + out.toString(); + } + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message)); + throw new ProjectBuildException(message, e); + } + } + + private static ModuleLevelBuilder.ExitCode doBuild(CompileContext context, ModuleChunk chunk) throws Exception { + final Map idlFilesToCompile = new HashMap(); + final Map rsFilesToCompile = new HashMap(); + final Set modules = new HashSet(); + + context.processFilesToRecompile(chunk, new FileProcessor() { + @Override + public boolean apply(Module module, File file, String sourceRoot) throws IOException { + final AndroidFacet facet = getFacet(module); + + if (facet == null) { + return true; + } + final String ext = FileUtil.getExtension(file.getName()); + + if (AIDL_EXTENSION.equals(ext)) { + idlFilesToCompile.put(file, facet); + modules.add(facet.getModule()); + } + else if (RENDERSCRIPT_EXTENSION.equals(ext)) { + rsFilesToCompile.put(file, facet); + modules.add(facet.getModule()); + } + + return true; + } + }); + + final Map moduleDataMap = computeModuleDatas(modules, context); + + if (moduleDataMap == null) { + return ExitCode.OK; + } + + if (!runAidlCompiler(context, idlFilesToCompile, moduleDataMap)) { + return ExitCode.OK; + } + + if (!runRenderscriptCompiler(context, rsFilesToCompile, moduleDataMap)) { + return ExitCode.OK; + } + + return ExitCode.OK; + } + + private static boolean runAidlCompiler(@NotNull final CompileContext context, + @NotNull Map files, + @NotNull Map moduleDataMap) { + context.processMessage(new ProgressMessage("Processing AIDL files...")); + + boolean success = true; + + for (Map.Entry entry : files.entrySet()) { + final File file = entry.getKey(); + final Module module = entry.getValue().getModule(); + final String filePath = file.getPath(); + + final MyModuleData moduleData = moduleDataMap.get(module); + + if (!LOG.assertTrue(moduleData != null)) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Internal error")); + success = false; + continue; + } + final File outputDirectory = moduleData.getOutputDirectory(); + final File aidlOutputDirectory = new File(outputDirectory, "generated-aidl"); + final IAndroidTarget target = moduleData.getAndroidTarget(); + + try { + final File[] sourceRoots = getSourceRootsForModuleAndDependencies(module); + final String[] sourceRootPaths = toPaths(sourceRoots); + final String packageName = computePackageForFile(context, file); + + if (packageName == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Cannot compute package for file", + filePath)); + success = false; + continue; + } + + final File outputFile = new File(aidlOutputDirectory, packageName.replace('.', File.separatorChar) + + File.separator + FileUtil.getNameWithoutExtension(file) + ".java"); + final String outputFilePath = outputFile.getPath(); + final Map> messages = + AndroidIdl.execute(target, filePath, outputFilePath, sourceRootPaths); + + addMessages(context, messages, filePath); + + if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) { + success = false; + continue; + } + + final String moduleName = getCannonicalModuleName(module); + final SourceToOutputMapping sourceToOutputMap = context.getDataManager().getSourceToOutputMap(moduleName, false); + sourceToOutputMap.update(filePath, outputFilePath); + } + catch (final IOException e) { + reportExceptionError(context, filePath, e); + success = false; + } + } + return success; + } + + private static boolean runRenderscriptCompiler(@NotNull final CompileContext context, + @NotNull Map files, + @NotNull Map moduleDataMap) { + context.processMessage(new ProgressMessage("Processing Renderscript files...")); + + boolean success = true; + + for (Map.Entry entry : files.entrySet()) { + final File file = entry.getKey(); + final Module module = entry.getValue().getModule(); + + final MyModuleData moduleData = moduleDataMap.get(module); + if (!LOG.assertTrue(moduleData != null)) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Internal error")); + success = false; + continue; + } + + final File outputDirectory = moduleData.getOutputDirectory(); + final File rsOutputDirectory = new File(outputDirectory, "generated-rs"); + final File generatedResourcesDir = new File(outputDirectory, "generated-resources"); + + final IAndroidTarget target = moduleData.getAndroidTarget(); + final String sdkLocation = moduleData.getSdkLocation(); + final String filePath = file.getPath(); + + File tmpOutputDirectory = null; + + try { + tmpOutputDirectory = FileUtil.createTempDirectory("generated-rs-temp", null); + final String depFolderPath = getDependencyFolder(context, file, tmpOutputDirectory); + final File rawDir = new File(generatedResourcesDir, "raw"); + + final Map> messages = + AndroidRenderscript.execute(sdkLocation, target, filePath, tmpOutputDirectory.getPath(), depFolderPath, rawDir.getPath()); + + addMessages(context, messages, filePath); + + if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) { + success = false; + } + else { + final List newFiles = new ArrayList(); + AndroidCommonUtils.moveAllFiles(tmpOutputDirectory, rsOutputDirectory, newFiles); + + final File bcFile = new File(rawDir, FileUtil.getNameWithoutExtension(file) + ".bc"); + if (bcFile.exists()) { + newFiles.add(bcFile); + } + final List newFilePaths = Arrays.asList(toPaths(newFiles.toArray(new File[newFiles.size()]))); + + final String moduleName = getCannonicalModuleName(module); + final SourceToOutputMapping sourceToOutputMap = context.getDataManager().getSourceToOutputMap(moduleName, false); + sourceToOutputMap.update(filePath, newFilePaths); + } + } + catch (IOException e) { + reportExceptionError(context, filePath, e); + success = false; + } + finally { + if (tmpOutputDirectory != null) { + FileUtil.delete(tmpOutputDirectory); + } + } + } + return success; + } + + private static void reportExceptionError(@NotNull CompileContext context, @NotNull String filePath, @NotNull Exception exception) { + final String message = exception.getMessage(); + + if (message != null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message, filePath)); + LOG.debug(exception); + } + else { + context.processMessage(new CompilerMessage(BUILDER_NAME, exception)); + } + } + + @NotNull + private static String getCannonicalModuleName(@NotNull Module module) { + return module.getName().toLowerCase(Locale.US); + } + + @Nullable + private static String getDependencyFolder(@NotNull CompileContext context, @NotNull File sourceFile, @NotNull File genFolder) { + final RootDescriptor descriptor = context.getRootsIndex().getModuleAndRoot(sourceFile); + if (descriptor == null) { + return null; + } + final File sourceRoot = descriptor.root; + + final File parent = FileUtil.getParentFile(sourceFile); + if (parent == null) { + return null; + } + + if (parent.equals(sourceRoot)) { + return genFolder.getPath(); + } + final String relativePath = FileUtil.getRelativePath(sourceRoot, parent); + assert relativePath != null; + return genFolder.getPath() + '/' + relativePath; + } + + @Nullable + private static Map computeModuleDatas(@NotNull Collection modules, @NotNull CompileContext context) + throws Exception { + final Map moduleDataMap = new HashMap(); + + boolean success = true; + + for (Module module : modules) { + + final Sdk sdk = module.getSdk(); + if (!(sdk instanceof AndroidSdk)) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Android SDK is not specified for module " + module.getName())); + success = false; + continue; + } + final AndroidSdk androidSdk = (AndroidSdk)sdk; + + final IAndroidTarget target = parseAndroidTarget(androidSdk, context); + if (target == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Android SDK is invalid or not specified for module " + module.getName())); + success = false; + continue; + } + + final File outputDir = context.getProjectPaths().getModuleOutputDir(module, false); + if (outputDir == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Cannot find output directory for module " + module.getName())); + success = false; + continue; + } + + moduleDataMap.put(module, new MyModuleData(outputDir, androidSdk.getSdkPath(), target)); + } + + return success ? moduleDataMap : null; + } + + private static void addMessages(@NotNull CompileContext context, + @NotNull Map> messages, + @Nullable String sourcePath) { + for (Map.Entry> entry : messages.entrySet()) { + final AndroidCompilerMessageKind kind = entry.getKey(); + final BuildMessage.Kind buildMessageKind = toBuildMessageKind(kind); + + if (buildMessageKind == null) { + continue; + } + + for (String message : entry.getValue()) { + context.processMessage(new CompilerMessage(BUILDER_NAME, buildMessageKind, message, sourcePath)); + } + } + } + + @Nullable + private static BuildMessage.Kind toBuildMessageKind(@NotNull AndroidCompilerMessageKind kind) { + switch (kind) { + case ERROR: + return BuildMessage.Kind.ERROR; + case INFORMATION: + return BuildMessage.Kind.INFO; + case WARNING: + return BuildMessage.Kind.WARNING; + default: + LOG.error("unknown AndroidCompilerMessageKind object " + kind); + return null; + } + } + + @Nullable + private static String computePackageForFile(@NotNull CompileContext context, @NotNull File file) throws IOException { + final RootDescriptor descriptor = context.getRootsIndex().getModuleAndRoot(file); + if (descriptor == null) { + return null; + } + + final String relPath = FileUtil.getRelativePath(descriptor.root, FileUtil.getParentFile(file)); + if (relPath == null) { + return null; + } + + return FileUtil.toSystemIndependentName(relPath).replace('/', '.'); + } + + @Nullable + private static IAndroidTarget parseAndroidTarget(@NotNull AndroidSdk sdk, @NotNull CompileContext context) { + final String targetHashString = sdk.getBuildTargetHashString(); + if (targetHashString == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Cannot parse SDK " + sdk.getName() + ": build target is not specified")); + return null; + } + + final MessageBuildingSdkLog log = new MessageBuildingSdkLog(); + final SdkManager manager = AndroidCommonUtils.createSdkManager(sdk.getSdkPath(), log); + + if (manager == null) { + final String message = log.getErrorMessage(); + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Android SDK is parsed incorrectly." + + (message.length() > 0 ? " Parsing log:\n" + message : ""))); + return null; + } + + final IAndroidTarget target = manager.getTargetFromHashString(targetHashString); + if (target == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Cannot parse SDK '" + sdk.getName() + "': unknown target " + targetHashString)); + return null; + } + return target; + } + + private static void fillSourceRoots(@NotNull Module module, @NotNull Set visited, @NotNull Set result) + throws IOException { + visited.add(module); + final AndroidFacet facet = getFacet(module); + File resDir = null; + + if (facet != null) { + resDir = facet.getResourceDir(); + if (resDir != null) { + resDir = resDir.getCanonicalFile(); + } + } + + for (String sourceRootPath : module.getSourceRoots()) { + final File sourceRoot = new File(sourceRootPath).getCanonicalFile(); + + if (!sourceRoot.equals(resDir)) { + result.add(sourceRoot); + } + } + + for (ClasspathItem classpathItem : module.getClasspath(ClasspathKind.PRODUCTION_COMPILE)) { + if (classpathItem instanceof Module) { + final Module depModule = (Module)classpathItem; + + if (!visited.contains(depModule)) { + fillSourceRoots(depModule, visited, result); + } + } + } + } + + @NotNull + public static File[] getSourceRootsForModuleAndDependencies(@NotNull Module module) throws IOException { + Set result = new HashSet(); + fillSourceRoots(module, new HashSet(), result); + return result.toArray(new File[result.size()]); + } + + @Override + public String getDescription() { + return "Android Builder"; + } + + @Nullable + private static AndroidFacet getFacet(@NotNull Module module) { + AndroidFacet androidFacet = null; + + for (Facet facet : module.getFacets().values()) { + if (facet instanceof AndroidFacet) { + androidFacet = (AndroidFacet)facet; + } + } + return androidFacet; + } + + @NotNull + private static String[] toPaths(@NotNull File[] files) { + final String[] result = new String[files.length]; + + for (int i = 0; i < result.length; i++) { + result[i] = files[i].getPath(); + } + return result; + } + + private static class MyModuleData { + private final File myOutputDirectory; + private final String mySdkLocation; + private final IAndroidTarget myAndroidTarget; + + private MyModuleData(@NotNull File outputDirectory, + @NotNull String sdkLocation, + @NotNull IAndroidTarget androidTarget) { + myOutputDirectory = outputDirectory; + mySdkLocation = sdkLocation; + myAndroidTarget = androidTarget; + } + + @NotNull + public File getOutputDirectory() { + return myOutputDirectory; + } + + @NotNull + public IAndroidTarget getAndroidTarget() { + return myAndroidTarget; + } + + @NotNull + public String getSdkLocation() { + return mySdkLocation; + } + } +} diff --git a/plugins/android/src/META-INF/plugin.xml b/plugins/android/src/META-INF/plugin.xml index e6e625de60e8..67a616094d46 100644 --- a/plugins/android/src/META-INF/plugin.xml +++ b/plugins/android/src/META-INF/plugin.xml @@ -189,6 +189,12 @@ + + + + + +