Skip to content

Commit

Permalink
Merge pull request #451 from gradle/asodja/android-studio-fix
Browse files Browse the repository at this point in the history
Fix Android Studio syncs for Electric Eel Canary 10+
  • Loading branch information
asodja authored Sep 28, 2022
2 parents e29d1ca + 23bbe61 commit a84b77a
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 133 deletions.
6 changes: 3 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ androidStudioTests {
val autoDownloadAndRunInHeadless = providers.gradleProperty("autoDownloadAndRunInHeadless").orNull == "true"
runAndroidStudioInHeadlessMode.set(autoDownloadAndRunInHeadless)
autoDownloadAndroidStudio.set(autoDownloadAndRunInHeadless)
// Electric Eel (2022.1.1) Canary 8
testAndroidStudioVersion.set("2022.1.1.8")
testAndroidSdkVersion.set("7.3.0-beta04")
// Electric Eel (2022.1.1) Beta 1
testAndroidStudioVersion.set("2022.1.1.11")
testAndroidSdkVersion.set("7.3.0")
// For local development it's easier to setup Android SDK with Android Studio, since auto download needs ANDROID_SDK_ROOT to be set with
// an accepted license in it. See https://developer.android.com/studio/intro/update.html#download-with-gradle.
autoDownloadAndroidSdk.set(autoDownloadAndRunInHeadless)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class AndroidStudioIntegrationTest extends AbstractProfilerIntegrationTest {

then:
def e = thrown(Main.ScenarioFailedException)
e.getCause().message.startsWith("Gradle sync has failed with error message:")
e.getCause().message.startsWith("Gradle sync has failed with error message: 'Sync test failure'.")
logFile.find("Full Gradle execution time").size() == 1
logFile.find("Full sync has completed in").size() == 1
logFile.find("and it FAILED").size() == 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import com.intellij.ide.impl.TrustedProjects;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager;
import com.intellij.openapi.externalSystem.settings.ExternalSystemSettingsListenerAdapter;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManagerListener;
import org.gradle.profiler.client.protocol.Client;
import org.gradle.profiler.client.protocol.messages.StudioRequest;
import org.gradle.profiler.studio.plugin.client.GradleProfilerClient;
import org.gradle.profiler.studio.plugin.system.AndroidStudioSystemHelper;
import org.gradle.profiler.studio.plugin.system.GradleSystemListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
import org.jetbrains.plugins.gradle.settings.GradleSettings;
Expand Down Expand Up @@ -39,9 +41,11 @@ public void projectOpened(@NotNull Project project) {
// If we don't disable external annotations, Android Studio will download some artifacts
// to .m2 folder if some project has for example com.fasterxml.jackson.core:jackson-core as a dependency
disableDownloadOfExternalAnnotations(project);
// Register system listener already here, so we can catch any failure for syncs that are automatically started
GradleSystemListener gradleSystemListener = registerGradleSystemListener();
TrustedProjects.setTrusted(project, true);
ApplicationManager.getApplication().executeOnPooledThread(() -> {
StudioRequest lastRequest = listenForSyncRequests(project);
StudioRequest lastRequest = listenForSyncRequests(project, gradleSystemListener);
if (lastRequest.getType() == EXIT_IDE) {
AndroidStudioSystemHelper.exit(project);
}
Expand All @@ -61,10 +65,16 @@ public void onProjectsLinked(@NotNull Collection<GradleProjectSettings> linkedPr
});
}

private StudioRequest listenForSyncRequests(@NotNull Project project) {
private GradleSystemListener registerGradleSystemListener() {
GradleSystemListener gradleSystemListener = new GradleSystemListener();
ExternalSystemProgressNotificationManager.getInstance().addNotificationListener(gradleSystemListener);
return gradleSystemListener;
}

private StudioRequest listenForSyncRequests(@NotNull Project project, @NotNull GradleSystemListener gradleStartupListener) {
int port = Integer.getInteger(PROFILER_PORT_PROPERTY);
try (Client client = new Client(port)) {
return new GradleProfilerClient(client).listenForSyncRequests(project);
return new GradleProfilerClient(client).listenForSyncRequests(project, gradleStartupListener);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.gradle.profiler.studio.plugin.client;

import com.android.tools.idea.projectsystem.ProjectSystemSyncManager;
import com.android.tools.idea.projectsystem.ProjectSystemUtil;
import com.google.common.base.Stopwatch;
import com.intellij.ide.caches.CachesInvalidator;
import com.intellij.openapi.diagnostic.Logger;
Expand All @@ -10,18 +8,16 @@
import org.gradle.profiler.client.protocol.messages.StudioCacheCleanupCompleted;
import org.gradle.profiler.client.protocol.messages.StudioRequest;
import org.gradle.profiler.client.protocol.messages.StudioSyncRequestCompleted;
import org.gradle.profiler.client.protocol.messages.StudioSyncRequestCompleted.StudioSyncRequestResult;
import org.gradle.profiler.studio.plugin.system.GradleProfilerGradleSyncListener.GradleSyncResult;
import org.gradle.profiler.studio.plugin.system.GradleSystemListener;
import org.gradle.profiler.studio.plugin.system.GradleSyncResult;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

import static com.android.tools.idea.projectsystem.ProjectSystemSyncManager.SyncResult.SKIPPED;
import static com.android.tools.idea.projectsystem.ProjectSystemSyncManager.SyncResult.SKIPPED_OUT_OF_DATE;
import static com.android.tools.idea.projectsystem.ProjectSystemSyncManager.SyncResult.UNKNOWN;
import static org.gradle.profiler.client.protocol.messages.StudioRequest.StudioRequestType.EXIT_IDE;
import static org.gradle.profiler.client.protocol.messages.StudioRequest.StudioRequestType.STOP_RECEIVING_EVENTS;
import static org.gradle.profiler.studio.plugin.system.AndroidStudioSystemHelper.doGradleSync;
import static org.gradle.profiler.studio.plugin.system.AndroidStudioSystemHelper.getStartupSyncResult;
import static org.gradle.profiler.studio.plugin.system.AndroidStudioSystemHelper.startManualSync;
import static org.gradle.profiler.studio.plugin.system.AndroidStudioSystemHelper.waitOnBackgroundProcessesFinish;
import static org.gradle.profiler.studio.plugin.system.AndroidStudioSystemHelper.waitOnPostStartupActivities;
import static org.gradle.profiler.studio.plugin.system.AndroidStudioSystemHelper.waitOnPreviousGradleSyncFinish;
Expand All @@ -39,11 +35,11 @@ public GradleProfilerClient(Client client) {
this.startupStopwatch = Stopwatch.createStarted();
}

public StudioRequest listenForSyncRequests(Project project) {
public StudioRequest listenForSyncRequests(Project project, GradleSystemListener gradleSystemListener) {
StudioRequest request = receiveNextEvent();
waitOnPostStartupActivities(project);
while (shouldHandleNextEvent(request)) {
handleGradleProfilerRequest(request, project);
handleGradleProfilerRequest(request, project, gradleSystemListener);
request = receiveNextEvent();
}
return request;
Expand All @@ -57,10 +53,10 @@ private boolean shouldHandleNextEvent(StudioRequest request) {
return request.getType() != EXIT_IDE && request.getType() != STOP_RECEIVING_EVENTS;
}

private void handleGradleProfilerRequest(StudioRequest request, Project project) {
private void handleGradleProfilerRequest(StudioRequest request, Project project, GradleSystemListener gradleSystemListener) {
switch (request.getType()) {
case SYNC:
handleSyncRequest(request, project);
handleSyncRequest(request, project, gradleSystemListener);
break;
case CLEANUP_CACHE:
cleanupCache(request);
Expand All @@ -72,7 +68,7 @@ private void handleGradleProfilerRequest(StudioRequest request, Project project)
}
}

private void handleSyncRequest(StudioRequest request, Project project) {
private void handleSyncRequest(StudioRequest request, Project project, GradleSystemListener gradleSystemListener) {
LOG.info("Received sync request with id: " + request.getId());
boolean isStartup = syncCount++ == 0;

Expand All @@ -83,34 +79,11 @@ private void handleSyncRequest(StudioRequest request, Project project) {

LOG.info(String.format("[SYNC REQUEST %s] Sync has started%n", request.getId()));
Stopwatch stopwatch = isStartup ? startupStopwatch : Stopwatch.createStarted();
GradleSyncResult result = isStartup ? getStartupSyncResult(project) : startManualSync(project);
LOG.info(String.format("[SYNC REQUEST %s] '%s': '%s'%n", request.getId(), result.getResult(), result.getErrorMessage()));
GradleSyncResult result = isStartup ? getStartupSyncResult(project, gradleSystemListener) : startManualSync(project, gradleSystemListener);
LOG.info(String.format("[SYNC REQUEST %s] '%s'%n", request.getId(), result.getResult()));
client.send(new StudioSyncRequestCompleted(request.getId(), stopwatch.elapsed(TimeUnit.MILLISECONDS), result.getResult(), result.getErrorMessage()));
}

private GradleSyncResult startManualSync(Project project) {
GradleSyncResult result = doGradleSync(project);
waitOnBackgroundProcessesFinish(project);
return result;
}

/**
* Gets the result of startup sync by checking if there was already any sync done before. Otherwise, it starts a new sync.
*/
private GradleSyncResult getStartupSyncResult(Project project) {
ProjectSystemSyncManager.SyncResult lastSyncResult = ProjectSystemUtil.getSyncManager(project).getLastSyncResult();
if (lastSyncResult == UNKNOWN) {
// Sync was not run before, we need to run it manually
return startManualSync(project);
} else if ((lastSyncResult == SKIPPED || lastSyncResult == SKIPPED_OUT_OF_DATE)) {
return new GradleSyncResult(StudioSyncRequestResult.SKIPPED, "");
} else if (lastSyncResult.isSuccessful()) {
return new GradleSyncResult(StudioSyncRequestResult.SUCCEEDED, "");
} else {
return new GradleSyncResult(StudioSyncRequestResult.FAILED, "Startup failure");
}
}

/**
* This code is similar to one in com.intellij.ide.InvalidateCacheService in IntelliJ Community project,
* it just does not make a dialog to restart IDE.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package org.gradle.profiler.studio.plugin.system;

import com.android.tools.idea.gradle.project.sync.GradleSyncState;
import com.android.tools.idea.projectsystem.ProjectSystemSyncManager;
import com.android.tools.idea.projectsystem.ProjectSystemSyncManager.SyncReason;
import com.android.tools.idea.projectsystem.ProjectSystemUtil;
import com.google.common.base.Strings;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
Expand All @@ -12,12 +13,15 @@
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.util.messages.MessageBusConnection;
import org.gradle.profiler.client.protocol.messages.StudioSyncRequestCompleted.StudioSyncRequestResult;
import org.gradle.profiler.studio.plugin.system.GradleProfilerGradleSyncListener.GradleSyncResult;

import javax.annotation.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;

import static com.android.tools.idea.projectsystem.ProjectSystemSyncManager.SyncResult.SKIPPED;
import static com.android.tools.idea.projectsystem.ProjectSystemSyncManager.SyncResult.SKIPPED_OUT_OF_DATE;
import static com.android.tools.idea.projectsystem.ProjectSystemSyncManager.SyncResult.UNKNOWN;
import static com.android.tools.idea.projectsystem.ProjectSystemSyncUtil.PROJECT_SYSTEM_SYNC_TOPIC;

public class AndroidStudioSystemHelper {
Expand All @@ -26,21 +30,43 @@ public class AndroidStudioSystemHelper {
private static final long WAIT_ON_STARTUP_SLEEP_TIME = 100;

/**
* Does a Gradle sync.
* Gets the result of startup sync by checking if there was already any sync done before. Otherwise, it starts a new sync.
*/
public static GradleSyncResult doGradleSync(Project project) {
MessageBusConnection connection = project.getMessageBus().connect();
public static GradleSyncResult getStartupSyncResult(Project project, GradleSystemListener gradleSystemListener) {
ProjectSystemSyncManager.SyncResult lastSyncResult = ProjectSystemUtil.getSyncManager(project).getLastSyncResult();
if (lastSyncResult == UNKNOWN) {
// Sync was not run before, we need to run it manually
return startManualSync(project, gradleSystemListener);
}
return convertToGradleSyncResult(lastSyncResult, gradleSystemListener.getLastException());
}

/**
* Starts a manual sync and returns a result.
*/
public static GradleSyncResult startManualSync(Project project, GradleSystemListener gradleSystemListener) {
GradleSyncResult result = doGradleSync(project, gradleSystemListener);
waitOnBackgroundProcessesFinish(project);
return result;
}

private static GradleSyncResult doGradleSync(Project project, GradleSystemListener gradleSystemListener) {
try {
GradleProfilerGradleSyncListener syncListener = new GradleProfilerGradleSyncListener();
connection.subscribe(GradleSyncState.GRADLE_SYNC_TOPIC, syncListener);
// We could get sync result from the `Future` returned by the syncProject(),
// but it doesn't return error message so we rather listen to GRADLE_SYNC_TOPIC to get the sync result
ProjectSystemUtil.getSyncManager(project).syncProject(SyncReason.USER_REQUEST).get();
return syncListener.waitAndGetResult();
ProjectSystemSyncManager.SyncResult syncResult = ProjectSystemUtil.getSyncManager(project).syncProject(SyncReason.USER_REQUEST).get();
return convertToGradleSyncResult(syncResult, gradleSystemListener.getLastException());
} catch (InterruptedException | ExecutionException e) {
return new GradleSyncResult(StudioSyncRequestResult.FAILED, e.getMessage());
} finally {
connection.disconnect();
}
}

private static GradleSyncResult convertToGradleSyncResult(ProjectSystemSyncManager.SyncResult syncResult, @Nullable Throwable throwable) {
if ((syncResult == SKIPPED || syncResult == SKIPPED_OUT_OF_DATE)) {
return new GradleSyncResult(StudioSyncRequestResult.SKIPPED, "");
} else if (syncResult.isSuccessful()) {
return new GradleSyncResult(StudioSyncRequestResult.SUCCEEDED, "");
} else {
String error = throwable != null ? throwable.getMessage() : "Unknown error";
return new GradleSyncResult(StudioSyncRequestResult.FAILED, Strings.nullToEmpty(error));
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.gradle.profiler.studio.plugin.system;

import org.gradle.profiler.client.protocol.messages.StudioSyncRequestCompleted.StudioSyncRequestResult;

public class GradleSyncResult {

private final StudioSyncRequestResult status;
private final String errorMessage;

public GradleSyncResult(StudioSyncRequestResult status, String errorMessage) {
this.status = status;
this.errorMessage = errorMessage;
}

public StudioSyncRequestResult getResult() {
return status;
}

public String getErrorMessage() {
return errorMessage;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.gradle.profiler.studio.plugin.system;

import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.gradle.util.GradleConstants;

import javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicReference;

public class GradleSystemListener extends ExternalSystemTaskNotificationListenerAdapter {
private final AtomicReference<Exception> exception = new AtomicReference<>();

@Override
public void onStart(@NotNull ExternalSystemTaskId id, String workingDir) {
if (GradleConstants.SYSTEM_ID.equals(id.getProjectSystemId())) {
exception.set(null);
}
}

@Override
public void onFailure(@NotNull ExternalSystemTaskId id, @NotNull Exception e) {
if (GradleConstants.SYSTEM_ID.equals(id.getProjectSystemId())) {
exception.set(e);
}
}

@Nullable
public Exception getLastException() {
return exception.get();
}
}

0 comments on commit a84b77a

Please sign in to comment.