summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuriy Solodkyy <solodkyy@google.com>2022-08-17 10:56:14 +0100
committerYuriy Solodkyy <solodkyy@google.com>2022-08-17 13:07:00 +0000
commita3bbf69d171044d69a71a38228d9d77d8f587696 (patch)
tree66880a3a55d1cc81e431ece685250573250b739e
parentb2c5d2058701f45e952d39e82a948255165c689d (diff)
downloadidea-a3bbf69d171044d69a71a38228d9d77d8f587696.tar.gz
Convert GradleTasksExecutorImpl to Kotlin
Bug: n/a Test: n/a Change-Id: I046bea8a2bcc6068cb319354a4042b2b6e2acebe
-rw-r--r--project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/GradleTasksExecutorImpl.java689
-rw-r--r--project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/GradleTasksExecutorImpl.kt590
2 files changed, 590 insertions, 689 deletions
diff --git a/project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/GradleTasksExecutorImpl.java b/project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/GradleTasksExecutorImpl.java
deleted file mode 100644
index b5aecf8a702..00000000000
--- a/project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/GradleTasksExecutorImpl.java
+++ /dev/null
@@ -1,689 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * 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.android.tools.idea.gradle.project.build.invoker;
-
-import static com.android.builder.model.InjectedProperties.PROPERTY_ATTRIBUTION_FILE_LOCATION;
-import static com.android.builder.model.InjectedProperties.PROPERTY_INVOKED_FROM_IDE;
-import static com.android.tools.idea.gradle.project.AndroidStudioGradleInstallationManager.setJdkAsProjectJdk;
-import static com.android.tools.idea.gradle.project.build.BuildStatus.CANCELED;
-import static com.android.tools.idea.gradle.project.build.BuildStatus.FAILED;
-import static com.android.tools.idea.gradle.project.build.BuildStatus.SUCCESS;
-import static com.android.tools.idea.ui.GuiTestingService.isInTestingMode;
-import static com.android.tools.idea.gradle.util.AndroidGradleSettings.createProjectProperty;
-import static com.android.tools.idea.gradle.util.GradleBuilds.PARALLEL_BUILD_OPTION;
-import static com.android.tools.idea.gradle.util.GradleUtil.attemptToUseEmbeddedGradle;
-import static com.android.tools.idea.gradle.util.GradleUtil.getOrCreateGradleExecutionSettings;
-import static com.android.tools.idea.gradle.util.GradleUtil.hasCause;
-import static com.google.common.base.Strings.nullToEmpty;
-import static com.intellij.openapi.ui.MessageType.ERROR;
-import static com.intellij.openapi.ui.MessageType.INFO;
-import static com.intellij.openapi.util.text.StringUtil.formatDuration;
-import static com.intellij.openapi.util.text.StringUtil.isNotEmpty;
-import static com.intellij.ui.AppUIUtil.invokeLaterIfProjectAlive;
-import static com.intellij.util.ArrayUtil.toStringArray;
-import static com.intellij.util.ExceptionUtil.getRootCause;
-import static com.intellij.util.ui.UIUtil.invokeLaterIfNeeded;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static org.jetbrains.plugins.gradle.service.execution.GradleExecutionHelper.prepare;
-
-import com.android.flags.Flags;
-import com.android.ide.common.repository.GradleVersion;
-import com.android.tools.idea.IdeInfo;
-import com.android.tools.idea.flags.StudioFlags;
-import com.android.tools.idea.gradle.project.ProjectStructure;
-import com.android.tools.idea.gradle.project.build.BuildContext;
-import com.android.tools.idea.gradle.project.build.BuildSummary;
-import com.android.tools.idea.gradle.project.build.GradleBuildState;
-import com.android.tools.idea.gradle.project.build.attribution.BasicBuildAttributionInfo;
-import com.android.tools.idea.gradle.project.build.attribution.BuildAttributionManager;
-import com.android.tools.idea.gradle.project.build.attribution.BuildAttributionUtil;
-import com.android.tools.idea.gradle.project.build.compiler.AndroidGradleBuildConfiguration;
-import com.android.tools.idea.gradle.project.common.AndroidSupportVersionUtilKt;
-import com.android.tools.idea.gradle.project.common.GradleInitScripts;
-import com.android.tools.idea.gradle.project.sync.hyperlink.SyncProjectWithExtraCommandLineOptionsHyperlink;
-import com.android.tools.idea.sdk.IdeSdks;
-import com.android.tools.idea.sdk.SelectSdkDialog;
-import com.android.tools.idea.ui.GuiTestingService;
-import com.android.tools.tracer.Trace;
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.SettableFuture;
-import com.intellij.compiler.CompilerConfiguration;
-import com.intellij.compiler.CompilerManagerImpl;
-import com.intellij.notification.NotificationGroup;
-import com.intellij.notification.NotificationGroupManager;
-import com.intellij.notification.NotificationType;
-import com.intellij.openapi.application.Application;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.PathManager;
-import com.intellij.openapi.compiler.CompilerManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.extensions.PluginId;
-import com.intellij.openapi.externalSystem.model.ExternalSystemException;
-import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
-import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationEvent;
-import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener;
-import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter;
-import com.intellij.openapi.progress.EmptyProgressIndicator;
-import com.intellij.openapi.progress.ProcessCanceledException;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.progress.Task;
-import com.intellij.openapi.progress.TaskInfo;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
-import com.intellij.openapi.project.VetoableProjectManagerListener;
-import com.intellij.openapi.ui.MessageType;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.wm.IdeFrame;
-import com.intellij.openapi.wm.WindowManager;
-import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
-import com.intellij.openapi.wm.ex.StatusBarEx;
-import com.intellij.openapi.wm.ex.WindowManagerEx;
-import com.intellij.ui.AppIcon;
-import com.intellij.ui.content.ContentManagerListener;
-import com.intellij.util.Function;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
-import org.gradle.tooling.BuildAction;
-import org.gradle.tooling.BuildActionExecuter;
-import org.gradle.tooling.BuildCancelledException;
-import org.gradle.tooling.BuildException;
-import org.gradle.tooling.BuildLauncher;
-import org.gradle.tooling.CancellationTokenSource;
-import org.gradle.tooling.GradleConnector;
-import org.gradle.tooling.LongRunningOperation;
-import org.gradle.tooling.ProjectConnection;
-import org.gradle.tooling.events.OperationType;
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.plugins.gradle.service.execution.GradleExecutionHelper;
-import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver;
-import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverExtension;
-import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings;
-
-class GradleTasksExecutorImpl implements GradleTasksExecutor {
- @Override
- public @NotNull ListenableFuture<GradleInvocationResult> execute(GradleBuildInvoker.@NotNull Request request,
- @Nullable BuildAction<?> buildAction,
- @NotNull BuildStopper buildStopper,
- @NotNull ExternalSystemTaskNotificationListener listener) {
- SettableFuture<GradleInvocationResult> resultFuture = SettableFuture.create();
- new TaskImpl(request, buildAction, buildStopper, listener, resultFuture).queue();
- return resultFuture;
- }
-
- @Override
- public boolean internalIsBuildRunning(@NotNull Project project) {
- IdeFrame frame = ((WindowManagerEx)WindowManager.getInstance()).findFrameFor(project);
- StatusBarEx statusBar = frame == null ? null : (StatusBarEx)frame.getStatusBar();
- if (statusBar == null) {
- return false;
- }
- for (Pair<TaskInfo, ProgressIndicator> backgroundProcess : statusBar.getBackgroundProcesses()) {
- TaskInfo task = backgroundProcess.getFirst();
- if (task instanceof TaskImpl) {
- ProgressIndicator second = backgroundProcess.getSecond();
- if (second.isRunning()) {
- return true;
- }
- }
- }
- return false;
- }
-
- private static class TaskImpl extends Task.Backgroundable {
- private static final long ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/;
- @NotNull public static final NotificationGroup LOGGING_NOTIFICATION =
- NotificationGroup.logOnlyGroup("Gradle Build (Logging)", PluginId.getId("org.jetbrains.android"));
- @NotNull public static final NotificationGroup BALLOON_NOTIFICATION =
- NotificationGroup.balloonGroup("Gradle Build (Balloon)", PluginId.getId("org.jetbrains.android"));
-
-
- @NonNls private static final String APP_ICON_ID = "compiler";
-
- private static final String GRADLE_RUNNING_MSG_TITLE = "Gradle Running";
- private static final String PASSWORD_KEY_SUFFIX = ".password=";
-
- @NotNull private final GradleBuildInvoker.Request myRequest;
- private BuildAction<?> myBuildAction;
- @NotNull private final BuildStopper myBuildStopper;
- @NotNull private final SettableFuture<GradleInvocationResult> myResultFuture;
- @NotNull private final ExternalSystemTaskNotificationListener myListener;
-
- @NotNull private final GradleExecutionHelper myHelper = new GradleExecutionHelper();
-
- private volatile int myErrorCount;
-
- @NotNull private volatile ProgressIndicator myProgressIndicator = new EmptyProgressIndicator();
-
- TaskImpl(@NotNull GradleBuildInvoker.Request request,
- @Nullable BuildAction<?> buildAction,
- @NotNull BuildStopper buildStopper,
- @NotNull ExternalSystemTaskNotificationListener listener,
- @NotNull SettableFuture<GradleInvocationResult> resultFuture) {
- super(request.getProject(), "Gradle Build Running", true);
- myRequest = request;
- myBuildAction = buildAction;
- myBuildStopper = buildStopper;
- myListener = listener;
- myResultFuture = resultFuture;
- }
-
- @Override
- @Nullable
- public NotificationInfo getNotificationInfo() {
- return new NotificationInfo(myErrorCount > 0 ? "Gradle Invocation (errors)" : "Gradle Invocation (success)",
- "Gradle Invocation Finished", myErrorCount + " Errors", true);
- }
-
- @Override
- public void run(@NotNull ProgressIndicator indicator) {
- try {
- myProgressIndicator = indicator;
-
- ProjectManager projectManager = ProjectManager.getInstance();
- Project project = myRequest.getProject();
- CloseListener closeListener = new CloseListener();
- projectManager.addProjectManagerListener(project, closeListener);
-
- Semaphore semaphore = ((CompilerManagerImpl)CompilerManager.getInstance(project)).getCompilationSemaphore();
- boolean acquired = false;
- try {
- try {
- while (!acquired) {
- acquired = semaphore.tryAcquire(300, MILLISECONDS);
- if (myProgressIndicator.isCanceled()) {
- // Give up obtaining the semaphore, let compile work begin in order to stop gracefully on cancel event.
- break;
- }
- }
- }
- catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- addIndicatorDelegate();
- myResultFuture.set(invokeGradleTasks(myBuildAction));
- }
- finally {
- try {
- myProgressIndicator.stop();
- projectManager.removeProjectManagerListener(project, closeListener);
- }
- finally {
- if (acquired) {
- semaphore.release();
- }
- }
- }
- }
- catch (Throwable t) {
- myResultFuture.setException(t);
- throw t;
- }
- }
-
- private void addIndicatorDelegate() {
- if (myProgressIndicator instanceof ProgressIndicatorEx) {
- ProgressIndicatorEx indicator = (ProgressIndicatorEx)myProgressIndicator;
- indicator.addStateDelegate(new ProgressIndicatorStateDelegate(myRequest.getTaskId(), myBuildStopper));
- }
- }
-
- private void setUpBuildAttributionManager(LongRunningOperation operation,
- BuildAttributionManager buildAttributionManager,
- boolean skipIfNull) {
- if (skipIfNull && buildAttributionManager == null) {
- return;
- }
- operation.addProgressListener(
- buildAttributionManager,
- OperationType.PROJECT_CONFIGURATION,
- OperationType.TASK,
- OperationType.TEST,
- OperationType.FILE_DOWNLOAD
- );
- buildAttributionManager.onBuildStart(myRequest);
- }
-
- @NotNull
- private GradleInvocationResult invokeGradleTasks(@Nullable BuildAction<?> buildAction) {
- Project project = myRequest.getProject();
- GradleExecutionSettings executionSettings = getOrCreateGradleExecutionSettings(project);
-
- AtomicReference<Object> model = new AtomicReference<>(null);
-
- String gradleRootProjectPath = myRequest.getRootProjectPath().getPath();
- Function<ProjectConnection, GradleInvocationResult> executeTasksFunction = connection -> {
- Stopwatch stopwatch = Stopwatch.createStarted();
-
- boolean isRunBuildAction = buildAction != null;
-
- List<String> gradleTasks = myRequest.getGradleTasks();
- String executingTasksText = "Executing tasks: " + gradleTasks + " in project " + gradleRootProjectPath;
- addToEventLog(executingTasksText, INFO);
-
- Throwable buildError = null;
- ExternalSystemTaskId id = myRequest.getTaskId();
- ExternalSystemTaskNotificationListener taskListener = myListener;
- CancellationTokenSource cancellationTokenSource = GradleConnector.newCancellationTokenSource();
- myBuildStopper.register(id, cancellationTokenSource);
-
- taskListener.onStart(id, gradleRootProjectPath);
- taskListener.onTaskOutput(id, executingTasksText + System.lineSeparator() + System.lineSeparator(), true);
-
- GradleBuildState buildState = GradleBuildState.getInstance(myProject);
- buildState.buildStarted(new BuildContext(myRequest));
-
- BuildAttributionManager buildAttributionManager = null;
- boolean enableBuildAttribution = BuildAttributionUtil.isBuildAttributionEnabledForProject(myProject);
-
- try {
- AndroidGradleBuildConfiguration buildConfiguration = AndroidGradleBuildConfiguration.getInstance(project);
- List<String> commandLineArguments = Lists.newArrayList(buildConfiguration.getCommandLineOptions());
-
- if (!commandLineArguments.contains(PARALLEL_BUILD_OPTION) &&
- CompilerConfiguration.getInstance(project).isParallelCompilationEnabled()) {
- commandLineArguments.add(PARALLEL_BUILD_OPTION);
- }
-
- commandLineArguments.add(createProjectProperty(PROPERTY_INVOKED_FROM_IDE, true));
-
- AndroidSupportVersionUtilKt.addAndroidSupportVersionArg(commandLineArguments);
-
- if (enableBuildAttribution) {
- File attributionFileDir = BuildAttributionUtil.getAgpAttributionFileDir(myRequest);
- commandLineArguments.add(createProjectProperty(PROPERTY_ATTRIBUTION_FILE_LOCATION,
- attributionFileDir.getAbsolutePath()));
- }
- commandLineArguments.addAll(myRequest.getCommandLineArguments());
-
- // Inject embedded repository if it's enabled by user.
- if (StudioFlags.USE_DEVELOPMENT_OFFLINE_REPOS.get() && !isInTestingMode()) {
- GradleInitScripts.getInstance().addLocalMavenRepoInitScriptCommandLineArg(commandLineArguments);
- attemptToUseEmbeddedGradle(project);
- }
-
- // Don't include passwords in the log
- String logMessage = "Build command line options: " + commandLineArguments;
- if (logMessage.contains(PASSWORD_KEY_SUFFIX)) {
- List<String> replaced = new ArrayList<>(commandLineArguments.size());
- for (String option : commandLineArguments) {
- // -Pandroid.injected.signing.store.password=, -Pandroid.injected.signing.key.password=
- int index = option.indexOf(".password=");
- if (index == -1) {
- replaced.add(option);
- }
- else {
- replaced.add(option.substring(0, index + PASSWORD_KEY_SUFFIX.length()) + "*********");
- }
- }
- logMessage = replaced.toString();
- }
- getLogger().info(logMessage);
-
- List<String> jvmArguments = new ArrayList<>(myRequest.getJvmArguments());
- // Add trace arguments to jvmArguments.
- Trace.addVmArgs(jvmArguments);
- executionSettings
- .withVmOptions(jvmArguments)
- .withArguments(commandLineArguments)
- .withEnvironmentVariables(myRequest.getEnv())
- .passParentEnvs(myRequest.isPassParentEnvs());
- LongRunningOperation operation = isRunBuildAction ? connection.action(buildAction) : connection.newBuild();
- prepare(operation, id, executionSettings, new ExternalSystemTaskNotificationListenerAdapter() {
- @Override
- public void onStatusChange(@NotNull ExternalSystemTaskNotificationEvent event) {
- if (myBuildStopper.contains(id)) {
- taskListener.onStatusChange(event);
- }
- }
-
- @Override
- public void onTaskOutput(@NotNull ExternalSystemTaskId id, @NotNull String text, boolean stdOut) {
- // For test use only: save the logs to a file. Note that if there are multiple tasks at once
- // the output will be interleaved.
- if (StudioFlags.GRADLE_SAVE_LOG_TO_FILE.get()) {
- try {
- Path path = Paths.get(PathManager.getLogPath(), "gradle.log");
- Files.writeString(path, text, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
- } catch (IOException e) {
- // Ignore
- }
- }
- if (myBuildStopper.contains(id)) {
- taskListener.onTaskOutput(id, text, stdOut);
- }
- }
- }, connection);
-
- if (enableBuildAttribution) {
- buildAttributionManager = myProject.getService(BuildAttributionManager.class);
- setUpBuildAttributionManager(operation, buildAttributionManager,
- // In some tests we don't care about build attribution being setup
- ApplicationManager.getApplication().isUnitTestMode());
- }
-
- if (isRunBuildAction) {
- ((BuildActionExecuter<?>)operation).forTasks(toStringArray(gradleTasks));
- }
- else {
- ((BuildLauncher)operation).forTasks(toStringArray(gradleTasks));
- }
-
- operation.withCancellationToken(cancellationTokenSource.token());
-
- if (isRunBuildAction) {
- model.set(((BuildActionExecuter<?>)operation).run());
- }
- else {
- ((BuildLauncher)operation).run();
- }
-
- buildState.buildFinished(SUCCESS);
- taskListener.onSuccess(id);
- BasicBuildAttributionInfo buildInfo;
- if (buildAttributionManager != null) {
- buildInfo = buildAttributionManager.onBuildSuccess(myRequest);
- }
- else {
- buildInfo = null;
- }
- if (buildInfo != null && buildInfo.getAgpVersion() != null) {
- reportAgpVersionMismatch(project, buildInfo);
- }
- }
- catch (BuildException e) {
- buildError = e;
- }
- catch (Throwable e) {
- buildError = e;
- handleTaskExecutionError(e);
- }
- finally {
- Application application = ApplicationManager.getApplication();
- if (buildError != null) {
- if (buildAttributionManager != null) {
- buildAttributionManager.onBuildFailure(myRequest);
- }
-
- if (wasBuildCanceled(buildError)) {
- buildState.buildFinished(CANCELED);
- taskListener.onCancel(id);
- }
- else {
- buildState.buildFinished(FAILED);
- GradleProjectResolverExtension projectResolverChain = GradleProjectResolver.createProjectResolverChain();
- ExternalSystemException userFriendlyError =
- projectResolverChain.getUserFriendlyError(null, buildError, gradleRootProjectPath, null);
- taskListener.onFailure(id, userFriendlyError);
- }
- }
- taskListener.onEnd(id);
- myBuildStopper.remove(id);
-
- if (GuiTestingService.getInstance().isGuiTestingMode()) {
- String testOutput = application.getUserData(GuiTestingService.GRADLE_BUILD_OUTPUT_IN_GUI_TEST_KEY);
- if (isNotEmpty(testOutput)) {
- application.putUserData(GuiTestingService.GRADLE_BUILD_OUTPUT_IN_GUI_TEST_KEY, null);
- }
- }
-
- application.invokeLater(() -> notifyGradleInvocationCompleted(buildState, stopwatch.elapsed(MILLISECONDS)));
-
- if (!getProject().isDisposed()) {
- return new GradleInvocationResult(myRequest.getRootProjectPath(), myRequest.getGradleTasks(), buildError, model.get());
- }
- }
- return new GradleInvocationResult(myRequest.getRootProjectPath(), myRequest.getGradleTasks(), null);
- };
-
- if (GuiTestingService.getInstance().isGuiTestingMode()) {
- // We use this task in GUI tests to simulate errors coming from Gradle project sync.
- Application application = ApplicationManager.getApplication();
- Runnable task = application.getUserData(GuiTestingService.EXECUTE_BEFORE_PROJECT_BUILD_IN_GUI_TEST_KEY);
- if (task != null) {
- application.putUserData(GuiTestingService.EXECUTE_BEFORE_PROJECT_BUILD_IN_GUI_TEST_KEY, null);
- task.run();
- }
- }
-
- try {
- return myHelper.execute(gradleRootProjectPath, executionSettings,
- myRequest.getTaskId(), myListener, null, executeTasksFunction);
- }
- catch (ExternalSystemException e) {
- if (e.getOriginalReason().startsWith("com.intellij.openapi.progress.ProcessCanceledException")) {
- getLogger().info("Gradle execution cancelled.", e);
- return new GradleInvocationResult(myRequest.getRootProjectPath(), myRequest.getGradleTasks(), e);
- }
- else {
- throw e;
- }
- }
- }
-
- private void reportAgpVersionMismatch(Project project, BasicBuildAttributionInfo buildInfo) {
- List<GradleVersion> syncedAgpVersions = ProjectStructure.getInstance(project).getAndroidPluginVersions().getAllVersions();
- if (!syncedAgpVersions.contains(buildInfo.getAgpVersion())) {
- String incompatibilityMessage =
- String.format("Project was built with Android Gradle Plugin (AGP) %s but it is synced with %s.",
- buildInfo.getAgpVersion(),
- syncedAgpVersions.stream().map(GradleVersion::toString).collect(Collectors.joining(", "))
- );
- getLogger().error(incompatibilityMessage);
- SyncProjectWithExtraCommandLineOptionsHyperlink quickFix =
- new SyncProjectWithExtraCommandLineOptionsHyperlink("Sync project", "");
- NotificationGroupManager.getInstance()
- .getNotificationGroup("Android Gradle Sync Issues")
- .createNotification(
- "Gradle sync needed",
- incompatibilityMessage +
- "\nPlease sync the project with Gradle Files.\n\n" +
- quickFix.toHtml(),
- NotificationType.ERROR
- )
- .setImportant(true)
- .setListener((notification, event) -> {
- quickFix.executeIfClicked(project, event);
- notification.hideBalloon();
- })
- .notify(project);
-
- throw new ProcessCanceledException();
- }
- }
-
- private static boolean wasBuildCanceled(@NotNull Throwable buildError) {
- return hasCause(buildError, BuildCancelledException.class) || hasCause(buildError, ProcessCanceledException.class);
- }
-
- private void handleTaskExecutionError(@NotNull Throwable e) {
- if (myProgressIndicator.isCanceled()) {
- getLogger().info("Failed to complete Gradle execution. Project may be closing or already closed.", e);
- return;
- }
- Throwable rootCause = getRootCause(e);
- String error = nullToEmpty(rootCause.getMessage());
- if (error.contains("Build cancelled")) {
- return;
- }
- Runnable showErrorTask = () -> {
- myErrorCount++;
-
- // This is temporary. Once we have support for hyperlinks in "Messages" window, we'll show the error message the with a
- // hyperlink to set the JDK home.
- // For now we show the "Select SDK" dialog, but only giving the option to set the JDK path.
- if (IdeInfo.getInstance().isAndroidStudio() && error.startsWith("Supplied javaHome is not a valid folder")) {
- IdeSdks ideSdks = IdeSdks.getInstance();
- File androidHome = ideSdks.getAndroidSdkPath();
- String androidSdkPath = androidHome != null ? androidHome.getPath() : null;
- SelectSdkDialog selectSdkDialog = new SelectSdkDialog(null, androidSdkPath);
- selectSdkDialog.setModal(true);
- if (selectSdkDialog.showAndGet()) {
- String jdkHome = selectSdkDialog.getJdkHome();
- invokeLaterIfNeeded(
- () -> ApplicationManager.getApplication().runWriteAction(() -> setJdkAsProjectJdk(myRequest.getProject(), jdkHome)));
- }
- }
- };
- invokeLaterIfProjectAlive(myRequest.getProject(), showErrorTask);
- }
-
- @NotNull
- private static Logger getLogger() {
- return Logger.getInstance(GradleBuildInvoker.class);
- }
-
- private void notifyGradleInvocationCompleted(@NotNull GradleBuildState buildState, long durationMillis) {
- Project project = myRequest.getProject();
- if (!project.isDisposed()) {
- String statusMsg = createStatusMessage(buildState, durationMillis);
- MessageType messageType = myErrorCount > 0 ? ERROR : INFO;
- if (durationMillis > ONE_MINUTE_MS) {
- BALLOON_NOTIFICATION.createNotification(statusMsg, messageType).notify(project);
- }
- else {
- addToEventLog(statusMsg, messageType);
- }
- getLogger().info(statusMsg);
- }
- }
-
- @NotNull
- private String createStatusMessage(@NotNull GradleBuildState buildState, long durationMillis) {
- String message = "Gradle build " + formatBuildStatusFromState(buildState);
- if (myErrorCount > 0) {
- message += String.format(Locale.US, " with %d error(s)", myErrorCount);
- }
- message = message + " in " + formatDuration(durationMillis);
- return message;
- }
-
- @NotNull
- private static String formatBuildStatusFromState(@NotNull GradleBuildState state) {
- BuildSummary summary = state.getLastFinishedBuildSummary();
- if (summary != null) {
- switch (summary.getStatus()) {
- case SUCCESS:
- return "finished";
- case FAILED:
- return "failed";
- case CANCELED:
- return "cancelled";
- }
- }
- return "finished";
- }
-
- private void addToEventLog(@NotNull String message, @NotNull MessageType type) {
- LOGGING_NOTIFICATION.createNotification(message, type).notify(myProject);
- }
-
- private void attemptToStopBuild() {
- myBuildStopper.attemptToStopBuild(myRequest.getTaskId(), myProgressIndicator);
- }
-
- private class CloseListener implements ContentManagerListener, VetoableProjectManagerListener {
- private boolean myIsApplicationExitingOrProjectClosing;
- private boolean myUserAcceptedCancel;
-
- @Override
- public void projectOpened(@NotNull Project project) {
- }
-
- @Override
- public boolean canClose(@NotNull Project project) {
- if (!project.equals(myProject)) {
- return true;
- }
- if (shouldPromptUser()) {
- myUserAcceptedCancel = askUserToCancelGradleExecution();
- if (!myUserAcceptedCancel) {
- return false; // veto closing
- }
- attemptToStopBuild();
- return true;
- }
- return !myProgressIndicator.isRunning();
- }
-
- @Override
- public void projectClosing(@NotNull Project project) {
- if (project.equals(myProject)) {
- myIsApplicationExitingOrProjectClosing = true;
- }
- }
-
- private boolean shouldPromptUser() {
- return !myUserAcceptedCancel && !myIsApplicationExitingOrProjectClosing && myProgressIndicator.isRunning();
- }
-
- private boolean askUserToCancelGradleExecution() {
- String msg = "Gradle is running. Proceed with Project closing?";
- int result = Messages.showYesNoDialog(myProject, msg, GRADLE_RUNNING_MSG_TITLE, Messages.getQuestionIcon());
- return result == Messages.YES;
- }
- }
-
- private class ProgressIndicatorStateDelegate extends TaskExecutionProgressIndicator {
- ProgressIndicatorStateDelegate(@NotNull ExternalSystemTaskId taskId,
- @NotNull BuildStopper buildStopper) {
- super(taskId, buildStopper);
- }
-
- @Override
- void onCancel() {
- stopAppIconProgress();
- }
-
- @Override
- public void stop() {
- super.stop();
- stopAppIconProgress();
- }
-
- private void stopAppIconProgress() {
- invokeLaterIfNeeded(() -> {
- AppIcon appIcon = AppIcon.getInstance();
- Project project = myRequest.getProject();
- if (appIcon.hideProgress(project, APP_ICON_ID)) {
- if (myErrorCount > 0) {
- appIcon.setErrorBadge(project, String.valueOf(myErrorCount));
- appIcon.requestAttention(project, true);
- }
- else {
- appIcon.setOkBadge(project, true);
- appIcon.requestAttention(project, false);
- }
- }
- });
- }
- }
- }
-}
diff --git a/project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/GradleTasksExecutorImpl.kt b/project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/GradleTasksExecutorImpl.kt
new file mode 100644
index 00000000000..82322c88512
--- /dev/null
+++ b/project-system-gradle/src/com/android/tools/idea/gradle/project/build/invoker/GradleTasksExecutorImpl.kt
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * 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.android.tools.idea.gradle.project.build.invoker
+
+import com.android.builder.model.PROPERTY_ATTRIBUTION_FILE_LOCATION
+import com.android.builder.model.PROPERTY_INVOKED_FROM_IDE
+import com.android.ide.common.repository.GradleVersion
+import com.android.tools.idea.IdeInfo
+import com.android.tools.idea.flags.StudioFlags
+import com.android.tools.idea.gradle.project.AndroidStudioGradleInstallationManager
+import com.android.tools.idea.gradle.project.ProjectStructure
+import com.android.tools.idea.gradle.project.build.BuildContext
+import com.android.tools.idea.gradle.project.build.BuildStatus
+import com.android.tools.idea.gradle.project.build.GradleBuildState
+import com.android.tools.idea.gradle.project.build.attribution.BasicBuildAttributionInfo
+import com.android.tools.idea.gradle.project.build.attribution.BuildAttributionManager
+import com.android.tools.idea.gradle.project.build.attribution.getAgpAttributionFileDir
+import com.android.tools.idea.gradle.project.build.attribution.isBuildAttributionEnabledForProject
+import com.android.tools.idea.gradle.project.build.compiler.AndroidGradleBuildConfiguration
+import com.android.tools.idea.gradle.project.build.invoker.GradleBuildInvoker
+import com.android.tools.idea.gradle.project.common.GradleInitScripts
+import com.android.tools.idea.gradle.project.common.addAndroidSupportVersionArg
+import com.android.tools.idea.gradle.project.sync.hyperlink.SyncProjectWithExtraCommandLineOptionsHyperlink
+import com.android.tools.idea.gradle.util.AndroidGradleSettings
+import com.android.tools.idea.gradle.util.GradleBuilds
+import com.android.tools.idea.gradle.util.GradleUtil
+import com.android.tools.idea.sdk.IdeSdks
+import com.android.tools.idea.sdk.SelectSdkDialog
+import com.android.tools.idea.ui.GuiTestingService
+import com.android.tools.tracer.Trace
+import com.google.common.base.Stopwatch
+import com.google.common.base.Strings
+import com.google.common.collect.Lists
+import com.google.common.util.concurrent.ListenableFuture
+import com.google.common.util.concurrent.SettableFuture
+import com.intellij.compiler.CompilerConfiguration
+import com.intellij.compiler.CompilerManagerImpl
+import com.intellij.notification.Notification
+import com.intellij.notification.NotificationGroup
+import com.intellij.notification.NotificationGroupManager
+import com.intellij.notification.NotificationType
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.PathManager
+import com.intellij.openapi.compiler.CompilerManager
+import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.extensions.PluginId
+import com.intellij.openapi.externalSystem.model.ExternalSystemException
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationEvent
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener
+import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter
+import com.intellij.openapi.progress.EmptyProgressIndicator
+import com.intellij.openapi.progress.ProcessCanceledException
+import com.intellij.openapi.progress.ProgressIndicator
+import com.intellij.openapi.progress.Task
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.ProjectManager
+import com.intellij.openapi.project.VetoableProjectManagerListener
+import com.intellij.openapi.ui.MessageType
+import com.intellij.openapi.ui.Messages
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.openapi.wm.WindowManager
+import com.intellij.openapi.wm.ex.ProgressIndicatorEx
+import com.intellij.openapi.wm.ex.StatusBarEx
+import com.intellij.openapi.wm.ex.WindowManagerEx
+import com.intellij.ui.AppIcon
+import com.intellij.ui.AppUIUtil
+import com.intellij.ui.content.ContentManagerListener
+import com.intellij.util.ArrayUtil
+import com.intellij.util.ExceptionUtil
+import com.intellij.util.Function
+import com.intellij.util.ui.UIUtil
+import org.gradle.tooling.BuildAction
+import org.gradle.tooling.BuildActionExecuter
+import org.gradle.tooling.BuildCancelledException
+import org.gradle.tooling.BuildException
+import org.gradle.tooling.BuildLauncher
+import org.gradle.tooling.GradleConnector
+import org.gradle.tooling.LongRunningOperation
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.events.OperationType
+import org.jetbrains.annotations.NonNls
+import org.jetbrains.plugins.gradle.service.execution.GradleExecutionHelper
+import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver
+import java.io.IOException
+import java.nio.file.Files
+import java.nio.file.Paths
+import java.nio.file.StandardOpenOption
+import java.util.Locale
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
+import javax.swing.event.HyperlinkEvent
+
+internal class GradleTasksExecutorImpl : GradleTasksExecutor {
+ override fun execute(
+ request: GradleBuildInvoker.Request,
+ buildAction: BuildAction<*>?,
+ buildStopper: BuildStopper,
+ listener: ExternalSystemTaskNotificationListener
+ ): ListenableFuture<GradleInvocationResult> {
+ val resultFuture = SettableFuture.create<GradleInvocationResult>()
+ TaskImpl(request, buildAction, buildStopper, listener, resultFuture).queue()
+ return resultFuture
+ }
+
+ override fun internalIsBuildRunning(project: Project): Boolean {
+ val frame = (WindowManager.getInstance() as WindowManagerEx).findFrameFor(project)
+ val statusBar = (if (frame == null) null else frame.statusBar as StatusBarEx?) ?: return false
+ for (backgroundProcess in statusBar.backgroundProcesses) {
+ val task = backgroundProcess.getFirst()
+ if (task is TaskImpl) {
+ val second = backgroundProcess.getSecond()
+ if (second.isRunning) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ private class TaskImpl constructor(
+ private val myRequest: GradleBuildInvoker.Request,
+ private val myBuildAction: BuildAction<*>?,
+ private val myBuildStopper: BuildStopper,
+ private val myListener: ExternalSystemTaskNotificationListener,
+ private val myResultFuture: SettableFuture<GradleInvocationResult>
+ ) : Task.Backgroundable(myRequest.project, "Gradle Build Running", true) {
+ private val myHelper = GradleExecutionHelper()
+
+ @Volatile
+ private var myErrorCount = 0
+
+ @Volatile
+ private var myProgressIndicator: ProgressIndicator = EmptyProgressIndicator()
+ override fun getNotificationInfo(): NotificationInfo? {
+ return NotificationInfo(
+ if (myErrorCount > 0) "Gradle Invocation (errors)" else "Gradle Invocation (success)",
+ "Gradle Invocation Finished", "$myErrorCount Errors", true
+ )
+ }
+
+ override fun run(indicator: ProgressIndicator) {
+ try {
+ myProgressIndicator = indicator
+ val projectManager = ProjectManager.getInstance()
+ val project = myRequest.project
+ val closeListener: CloseListener = CloseListener()
+ projectManager.addProjectManagerListener(project, closeListener)
+ val semaphore = (CompilerManager.getInstance(project) as CompilerManagerImpl).compilationSemaphore
+ var acquired = false
+ try {
+ try {
+ while (!acquired) {
+ acquired = semaphore.tryAcquire(300, TimeUnit.MILLISECONDS)
+ if (myProgressIndicator.isCanceled) {
+ // Give up obtaining the semaphore, let compile work begin in order to stop gracefully on cancel event.
+ break
+ }
+ }
+ } catch (e: InterruptedException) {
+ Thread.currentThread().interrupt()
+ }
+ addIndicatorDelegate()
+ myResultFuture.set(invokeGradleTasks(myBuildAction))
+ } finally {
+ try {
+ myProgressIndicator.stop()
+ projectManager.removeProjectManagerListener(project, closeListener)
+ } finally {
+ if (acquired) {
+ semaphore.release()
+ }
+ }
+ }
+ } catch (t: Throwable) {
+ myResultFuture.setException(t)
+ throw t
+ }
+ }
+
+ private fun addIndicatorDelegate() {
+ if (myProgressIndicator is ProgressIndicatorEx) {
+ val indicator = myProgressIndicator as ProgressIndicatorEx
+ indicator.addStateDelegate(ProgressIndicatorStateDelegate(myRequest.taskId, myBuildStopper))
+ }
+ }
+
+ private fun setUpBuildAttributionManager(
+ operation: LongRunningOperation,
+ buildAttributionManager: BuildAttributionManager?,
+ skipIfNull: Boolean
+ ) {
+ if (skipIfNull && buildAttributionManager == null) {
+ return
+ }
+ operation.addProgressListener(
+ buildAttributionManager,
+ OperationType.PROJECT_CONFIGURATION,
+ OperationType.TASK,
+ OperationType.TEST,
+ OperationType.FILE_DOWNLOAD
+ )
+ buildAttributionManager!!.onBuildStart(myRequest)
+ }
+
+ private fun invokeGradleTasks(buildAction: BuildAction<*>?): GradleInvocationResult {
+ val project = myRequest.project
+ val executionSettings = GradleUtil.getOrCreateGradleExecutionSettings(project)
+ val model = AtomicReference<Any?>(null)
+ val gradleRootProjectPath = myRequest.rootProjectPath.path
+ val executeTasksFunction = Function { connection: ProjectConnection ->
+ val stopwatch = Stopwatch.createStarted()
+ val isRunBuildAction = buildAction != null
+ val gradleTasks = myRequest.gradleTasks
+ val executingTasksText = "Executing tasks: $gradleTasks in project $gradleRootProjectPath"
+ addToEventLog(executingTasksText, MessageType.INFO)
+ var buildError: Throwable? = null
+ val id = myRequest.taskId
+ val taskListener = myListener
+ val cancellationTokenSource = GradleConnector.newCancellationTokenSource()
+ myBuildStopper.register(id, cancellationTokenSource)
+ taskListener.onStart(id, gradleRootProjectPath)
+ taskListener.onTaskOutput(id, executingTasksText + System.lineSeparator() + System.lineSeparator(), true)
+ val buildState = GradleBuildState.getInstance(myProject!!)
+ buildState.buildStarted(BuildContext(myRequest))
+ var buildAttributionManager: BuildAttributionManager? = null
+ val enableBuildAttribution = isBuildAttributionEnabledForProject(myProject)
+ try {
+ val buildConfiguration = AndroidGradleBuildConfiguration.getInstance(project)
+ val commandLineArguments: MutableList<String?> = Lists.newArrayList(*buildConfiguration.commandLineOptions)
+ if (!commandLineArguments.contains(GradleBuilds.PARALLEL_BUILD_OPTION) &&
+ CompilerConfiguration.getInstance(project).isParallelCompilationEnabled
+ ) {
+ commandLineArguments.add(GradleBuilds.PARALLEL_BUILD_OPTION)
+ }
+ commandLineArguments.add(AndroidGradleSettings.createProjectProperty(PROPERTY_INVOKED_FROM_IDE, true))
+ addAndroidSupportVersionArg(commandLineArguments)
+ if (enableBuildAttribution) {
+ val attributionFileDir = getAgpAttributionFileDir(myRequest)
+ commandLineArguments.add(
+ AndroidGradleSettings.createProjectProperty(
+ PROPERTY_ATTRIBUTION_FILE_LOCATION,
+ attributionFileDir.absolutePath
+ )
+ )
+ }
+ commandLineArguments.addAll(myRequest.commandLineArguments)
+
+ // Inject embedded repository if it's enabled by user.
+ if (StudioFlags.USE_DEVELOPMENT_OFFLINE_REPOS.get() && !GuiTestingService.isInTestingMode()) {
+ GradleInitScripts.getInstance().addLocalMavenRepoInitScriptCommandLineArg(commandLineArguments)
+ GradleUtil.attemptToUseEmbeddedGradle(project)
+ }
+
+ // Don't include passwords in the log
+ var logMessage = "Build command line options: $commandLineArguments"
+ if (logMessage.contains(PASSWORD_KEY_SUFFIX)) {
+ val replaced: MutableList<String?> = ArrayList(commandLineArguments.size)
+ for (option in commandLineArguments) {
+ // -Pandroid.injected.signing.store.password=, -Pandroid.injected.signing.key.password=
+ val index = option!!.indexOf(".password=")
+ if (index == -1) {
+ replaced.add(option)
+ } else {
+ replaced.add(option.substring(0, index + PASSWORD_KEY_SUFFIX.length) + "*********")
+ }
+ }
+ logMessage = replaced.toString()
+ }
+ logger.info(logMessage)
+ val jvmArguments: List<String> = ArrayList(myRequest.jvmArguments)
+ // Add trace arguments to jvmArguments.
+ Trace.addVmArgs(jvmArguments)
+ executionSettings
+ .withVmOptions(jvmArguments)
+ .withArguments(commandLineArguments)
+ .withEnvironmentVariables(myRequest.env)
+ .passParentEnvs(myRequest.isPassParentEnvs)
+ val operation: LongRunningOperation = if (isRunBuildAction) connection.action(buildAction) else connection.newBuild()
+ GradleExecutionHelper.prepare(operation, id, executionSettings, object : ExternalSystemTaskNotificationListenerAdapter() {
+ override fun onStatusChange(event: ExternalSystemTaskNotificationEvent) {
+ if (myBuildStopper.contains(id)) {
+ taskListener.onStatusChange(event)
+ }
+ }
+
+ override fun onTaskOutput(id: ExternalSystemTaskId, text: String, stdOut: Boolean) {
+ // For test use only: save the logs to a file. Note that if there are multiple tasks at once
+ // the output will be interleaved.
+ if (StudioFlags.GRADLE_SAVE_LOG_TO_FILE.get()) {
+ try {
+ val path = Paths.get(PathManager.getLogPath(), "gradle.log")
+ Files.writeString(path, text, StandardOpenOption.APPEND, StandardOpenOption.CREATE)
+ } catch (e: IOException) {
+ // Ignore
+ }
+ }
+ if (myBuildStopper.contains(id)) {
+ taskListener.onTaskOutput(id, text, stdOut)
+ }
+ }
+ }, connection)
+ if (enableBuildAttribution) {
+ buildAttributionManager = myProject.getService(BuildAttributionManager::class.java)
+ setUpBuildAttributionManager(
+ operation, buildAttributionManager, // In some tests we don't care about build attribution being setup
+ ApplicationManager.getApplication().isUnitTestMode
+ )
+ }
+ if (isRunBuildAction) {
+ (operation as BuildActionExecuter<*>).forTasks(*ArrayUtil.toStringArray(gradleTasks))
+ } else {
+ (operation as BuildLauncher).forTasks(*ArrayUtil.toStringArray(gradleTasks))
+ }
+ operation.withCancellationToken(cancellationTokenSource.token())
+ if (isRunBuildAction) {
+ model.set((operation as BuildActionExecuter<*>).run())
+ } else {
+ (operation as BuildLauncher).run()
+ }
+ buildState.buildFinished(BuildStatus.SUCCESS)
+ taskListener.onSuccess(id)
+ val buildInfo: BasicBuildAttributionInfo?
+ buildInfo = buildAttributionManager?.onBuildSuccess(myRequest)
+ if (buildInfo != null && buildInfo.agpVersion != null) {
+ reportAgpVersionMismatch(project, buildInfo)
+ }
+ } catch (e: BuildException) {
+ buildError = e
+ } catch (e: Throwable) {
+ buildError = e
+ handleTaskExecutionError(e)
+ } finally {
+ val application = ApplicationManager.getApplication()
+ if (buildError != null) {
+ buildAttributionManager?.onBuildFailure(myRequest)
+ if (wasBuildCanceled(buildError)) {
+ buildState.buildFinished(BuildStatus.CANCELED)
+ taskListener.onCancel(id)
+ } else {
+ buildState.buildFinished(BuildStatus.FAILED)
+ val projectResolverChain = GradleProjectResolver.createProjectResolverChain()
+ val userFriendlyError = projectResolverChain.getUserFriendlyError(null, buildError, gradleRootProjectPath, null)
+ taskListener.onFailure(id, userFriendlyError)
+ }
+ }
+ taskListener.onEnd(id)
+ myBuildStopper.remove(id)
+ if (GuiTestingService.getInstance().isGuiTestingMode) {
+ val testOutput = application.getUserData(GuiTestingService.GRADLE_BUILD_OUTPUT_IN_GUI_TEST_KEY)
+ if (StringUtil.isNotEmpty(testOutput)) {
+ application.putUserData(GuiTestingService.GRADLE_BUILD_OUTPUT_IN_GUI_TEST_KEY, null)
+ }
+ }
+ application.invokeLater { notifyGradleInvocationCompleted(buildState, stopwatch.elapsed(TimeUnit.MILLISECONDS)) }
+ if (!getProject().isDisposed) {
+ return@Function GradleInvocationResult(myRequest.rootProjectPath, myRequest.gradleTasks, buildError, model.get())
+ }
+ }
+ GradleInvocationResult(myRequest.rootProjectPath, myRequest.gradleTasks, null)
+ }
+ if (GuiTestingService.getInstance().isGuiTestingMode) {
+ // We use this task in GUI tests to simulate errors coming from Gradle project sync.
+ val application = ApplicationManager.getApplication()
+ val task = application.getUserData(GuiTestingService.EXECUTE_BEFORE_PROJECT_BUILD_IN_GUI_TEST_KEY)
+ if (task != null) {
+ application.putUserData(GuiTestingService.EXECUTE_BEFORE_PROJECT_BUILD_IN_GUI_TEST_KEY, null)
+ task.run()
+ }
+ }
+ return try {
+ myHelper.execute(
+ gradleRootProjectPath, executionSettings,
+ myRequest.taskId, myListener, null, executeTasksFunction
+ )
+ } catch (e: ExternalSystemException) {
+ if (e.originalReason.startsWith("com.intellij.openapi.progress.ProcessCanceledException")) {
+ logger.info("Gradle execution cancelled.", e)
+ GradleInvocationResult(myRequest.rootProjectPath, myRequest.gradleTasks, e)
+ } else {
+ throw e
+ }
+ }
+ }
+
+ private fun reportAgpVersionMismatch(project: Project, buildInfo: BasicBuildAttributionInfo) {
+ val syncedAgpVersions = ProjectStructure.getInstance(project).androidPluginVersions.allVersions
+ if (!syncedAgpVersions.contains(buildInfo.agpVersion)) {
+ val incompatibilityMessage = String.format("Project was built with Android Gradle Plugin (AGP) %s but it is synced with %s.",
+ buildInfo.agpVersion,
+ syncedAgpVersions.joinToString(", ") { obj: GradleVersion? -> obj.toString() }
+ )
+ logger.error(incompatibilityMessage)
+ val quickFix = SyncProjectWithExtraCommandLineOptionsHyperlink("Sync project", "")
+ NotificationGroupManager.getInstance()
+ .getNotificationGroup("Android Gradle Sync Issues")
+ .createNotification(
+ "Gradle sync needed",
+ """
+ $incompatibilityMessage
+ Please sync the project with Gradle Files.
+
+ ${quickFix.toHtml()}
+ """.trimIndent(),
+ NotificationType.ERROR
+ )
+ .setImportant(true)
+ .setListener { notification: Notification, event: HyperlinkEvent? ->
+ quickFix.executeIfClicked(project, event!!)
+ notification.hideBalloon()
+ }
+ .notify(project)
+ throw ProcessCanceledException()
+ }
+ }
+
+ private fun handleTaskExecutionError(e: Throwable) {
+ if (myProgressIndicator.isCanceled) {
+ logger.info("Failed to complete Gradle execution. Project may be closing or already closed.", e)
+ return
+ }
+ val rootCause = ExceptionUtil.getRootCause(e)
+ val error = Strings.nullToEmpty(rootCause.message)
+ if (error.contains("Build cancelled")) {
+ return
+ }
+ val showErrorTask = Runnable {
+ myErrorCount++
+
+ // This is temporary. Once we have support for hyperlinks in "Messages" window, we'll show the error message the with a
+ // hyperlink to set the JDK home.
+ // For now we show the "Select SDK" dialog, but only giving the option to set the JDK path.
+ if (IdeInfo.getInstance().isAndroidStudio && error.startsWith("Supplied javaHome is not a valid folder")) {
+ val ideSdks = IdeSdks.getInstance()
+ val androidHome = ideSdks.androidSdkPath
+ val androidSdkPath = androidHome?.path
+ val selectSdkDialog = SelectSdkDialog(null, androidSdkPath)
+ selectSdkDialog.isModal = true
+ if (selectSdkDialog.showAndGet()) {
+ val jdkHome = selectSdkDialog.jdkHome
+ UIUtil.invokeLaterIfNeeded {
+ ApplicationManager.getApplication()
+ .runWriteAction { AndroidStudioGradleInstallationManager.setJdkAsProjectJdk(myRequest.project, jdkHome) }
+ }
+ }
+ }
+ }
+ AppUIUtil.invokeLaterIfProjectAlive(myRequest.project, showErrorTask)
+ }
+
+ private fun notifyGradleInvocationCompleted(buildState: GradleBuildState, durationMillis: Long) {
+ val project = myRequest.project
+ if (!project.isDisposed) {
+ val statusMsg = createStatusMessage(buildState, durationMillis)
+ val messageType = if (myErrorCount > 0) MessageType.ERROR else MessageType.INFO
+ if (durationMillis > ONE_MINUTE_MS) {
+ BALLOON_NOTIFICATION.createNotification(statusMsg, messageType).notify(project)
+ } else {
+ addToEventLog(statusMsg, messageType)
+ }
+ logger.info(statusMsg)
+ }
+ }
+
+ private fun createStatusMessage(buildState: GradleBuildState, durationMillis: Long): String {
+ var message = "Gradle build " + formatBuildStatusFromState(buildState)
+ if (myErrorCount > 0) {
+ message += String.format(Locale.US, " with %d error(s)", myErrorCount)
+ }
+ message = message + " in " + StringUtil.formatDuration(durationMillis)
+ return message
+ }
+
+ private fun addToEventLog(message: String, type: MessageType) {
+ LOGGING_NOTIFICATION.createNotification(message, type).notify(myProject)
+ }
+
+ private fun attemptToStopBuild() {
+ myBuildStopper.attemptToStopBuild(myRequest.taskId, myProgressIndicator)
+ }
+
+ private inner class CloseListener : ContentManagerListener, VetoableProjectManagerListener {
+ private var myIsApplicationExitingOrProjectClosing = false
+ private var myUserAcceptedCancel = false
+ override fun projectOpened(project: Project) {}
+ override fun canClose(project: Project): Boolean {
+ if (project != myProject) {
+ return true
+ }
+ if (shouldPromptUser()) {
+ myUserAcceptedCancel = askUserToCancelGradleExecution()
+ if (!myUserAcceptedCancel) {
+ return false // veto closing
+ }
+ attemptToStopBuild()
+ return true
+ }
+ return !myProgressIndicator.isRunning
+ }
+
+ override fun projectClosing(project: Project) {
+ if (project == myProject) {
+ myIsApplicationExitingOrProjectClosing = true
+ }
+ }
+
+ private fun shouldPromptUser(): Boolean {
+ return !myUserAcceptedCancel && !myIsApplicationExitingOrProjectClosing && myProgressIndicator.isRunning
+ }
+
+ private fun askUserToCancelGradleExecution(): Boolean {
+ val msg = "Gradle is running. Proceed with Project closing?"
+ val result = Messages.showYesNoDialog(myProject, msg, GRADLE_RUNNING_MSG_TITLE, Messages.getQuestionIcon())
+ return result == Messages.YES
+ }
+ }
+
+ private inner class ProgressIndicatorStateDelegate internal constructor(
+ taskId: ExternalSystemTaskId,
+ buildStopper: BuildStopper
+ ) : TaskExecutionProgressIndicator(taskId, buildStopper) {
+ public override fun onCancel() {
+ stopAppIconProgress()
+ }
+
+ override fun stop() {
+ super.stop()
+ stopAppIconProgress()
+ }
+
+ private fun stopAppIconProgress() {
+ UIUtil.invokeLaterIfNeeded {
+ val appIcon = AppIcon.getInstance()
+ val project = myRequest.project
+ if (appIcon.hideProgress(project, APP_ICON_ID)) {
+ if (myErrorCount > 0) {
+ appIcon.setErrorBadge(project, myErrorCount.toString())
+ appIcon.requestAttention(project, true)
+ } else {
+ appIcon.setOkBadge(project, true)
+ appIcon.requestAttention(project, false)
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/
+ val LOGGING_NOTIFICATION = NotificationGroup.logOnlyGroup("Gradle Build (Logging)", PluginId.getId("org.jetbrains.android"))
+ val BALLOON_NOTIFICATION = NotificationGroup.balloonGroup("Gradle Build (Balloon)", PluginId.getId("org.jetbrains.android"))
+ private val APP_ICON_ID: @NonNls String? = "compiler"
+ private const val GRADLE_RUNNING_MSG_TITLE = "Gradle Running"
+ private const val PASSWORD_KEY_SUFFIX = ".password="
+ private fun wasBuildCanceled(buildError: Throwable): Boolean {
+ return GradleUtil.hasCause(buildError, BuildCancelledException::class.java) || GradleUtil.hasCause(
+ buildError,
+ ProcessCanceledException::class.java
+ )
+ }
+
+ private val logger: Logger
+ get() = Logger.getInstance(GradleBuildInvoker::class.java)
+
+ private fun formatBuildStatusFromState(state: GradleBuildState): String {
+ val summary = state.lastFinishedBuildSummary
+ return if (summary != null) {
+ when (summary.status) {
+ BuildStatus.SUCCESS -> "finished"
+ BuildStatus.FAILED -> "failed"
+ BuildStatus.CANCELED -> "cancelled"
+ }
+ } else "finished"
+ }
+ }
+ }
+} \ No newline at end of file