blob: ac32784a87c45dd3e688076ddc0881c59470f618 [file] [log] [blame]
/*
* Copyright (C) 2013 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.run;
import com.android.tools.idea.gradle.GradleSyncState;
import com.android.tools.idea.gradle.IdeaGradleProject;
import com.android.tools.idea.gradle.facet.AndroidGradleFacet;
import com.android.tools.idea.gradle.invoker.GradleInvocationResult;
import com.android.tools.idea.gradle.invoker.GradleInvoker;
import com.android.tools.idea.gradle.invoker.GradleInvoker.TestCompileType;
import com.android.tools.idea.gradle.project.GradleProjectImporter;
import com.android.tools.idea.gradle.project.GradleSyncListener;
import com.android.tools.idea.gradle.util.Projects;
import com.android.tools.idea.startup.AndroidStudioSpecificInitializer;
import com.google.common.collect.Lists;
import com.intellij.compiler.options.CompileStepBeforeRun;
import com.intellij.execution.BeforeRunTaskProvider;
import com.intellij.execution.configurations.ModuleRunProfile;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.junit.JUnitConfiguration;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.compiler.CompileScope;
import com.intellij.openapi.compiler.CompilerManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ThreeState;
import com.intellij.util.concurrency.Semaphore;
import icons.AndroidIcons;
import org.jetbrains.android.run.AndroidRunConfigurationBase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
* Provides the "Gradle-aware Make" task for Run Configurations, which
* <ul>
* <li>is only available in Android Studio</li>
* <li>delegates to the regular "Make" if the project is not an Android Gradle project</li>
* <li>otherwise, invokes Gradle directly, to build the project</li>
* </ul>
*/
public class MakeBeforeRunTaskProvider extends BeforeRunTaskProvider<MakeBeforeRunTask> {
@NotNull public static final Key<MakeBeforeRunTask> ID = Key.create("Android.Gradle.BeforeRunTask");
private static final Logger LOG = Logger.getInstance(MakeBeforeRunTask.class);
private static final String TASK_NAME = "Gradle-aware Make";
@NotNull private final Project myProject;
public MakeBeforeRunTaskProvider(@NotNull Project project) {
myProject = project;
}
@Override
public Key<MakeBeforeRunTask> getId() {
return ID;
}
@Nullable
@Override
public Icon getIcon() {
return AndroidIcons.Android;
}
@Nullable
@Override
public Icon getTaskIcon(MakeBeforeRunTask task) {
return AndroidIcons.Android;
}
@Override
public String getName() {
return TASK_NAME;
}
@Override
public String getDescription(MakeBeforeRunTask task) {
String goal = task.getGoal();
return StringUtil.isEmpty(goal) ? TASK_NAME : "gradle " + goal;
}
@Override
public boolean isConfigurable() {
return true;
}
@Nullable
@Override
public MakeBeforeRunTask createTask(RunConfiguration runConfiguration) {
// "Gradle-aware Make" is only available in Android Studio.
if (AndroidStudioSpecificInitializer.isAndroidStudio() && configurationTypeIsSupported(runConfiguration)) {
MakeBeforeRunTask task = new MakeBeforeRunTask();
if (runConfiguration instanceof AndroidRunConfigurationBase) {
// For Android configurations, we want to replace the default make, so this new task needs to be enabled.
// In AndroidRunConfigurationType#configureBeforeTaskDefaults we disable the default make, which is
// enabled by default. For other configurations we leave it disabled, so we don't end up with two different
// make steps executed by default. If the task is added to the run configuration manually, it will be
// enabled by the UI layer later.
task.setEnabled(true);
}
return task;
} else {
return null;
}
}
private static boolean configurationTypeIsSupported(@NotNull RunConfiguration runConfiguration) {
return runConfiguration instanceof AndroidRunConfigurationBase || isUnitTestConfiguration(runConfiguration);
}
private static boolean isUnitTestConfiguration(@NotNull RunConfiguration runConfiguration) {
return runConfiguration instanceof JUnitConfiguration ||
// Avoid direct dependency on the TestNG plugin:
runConfiguration.getClass().getSimpleName().equals("TestNGConfiguration");
}
@Override
public boolean configureTask(RunConfiguration runConfiguration, MakeBeforeRunTask task) {
GradleEditTaskDialog dialog = new GradleEditTaskDialog(myProject);
dialog.setGoal(task.getGoal());
dialog.setAvailableGoals(createAvailableTasks());
if (!dialog.showAndGet()) {
// since we allow tasks without any arguments (assumed to be equivalent to assembling the app),
// we need a way to specify that a task is not valid. This is because of the current restriction
// of this API, where the return value from configureTask is ignored.
task.setInvalid();
return false;
}
task.setGoal(dialog.getGoal());
return true;
}
private List<String> createAvailableTasks() {
ModuleManager moduleManager = ModuleManager.getInstance(myProject);
List<String> gradleTasks = Lists.newArrayList();
for (Module module : moduleManager.getModules()) {
AndroidGradleFacet facet = AndroidGradleFacet.getInstance(module);
if (facet == null) {
continue;
}
IdeaGradleProject gradleProject = facet.getGradleProject();
if (gradleProject == null) {
continue;
}
gradleTasks.addAll(gradleProject.getTaskNames());
}
return gradleTasks;
}
@Override
public boolean canExecuteTask(RunConfiguration configuration, MakeBeforeRunTask task) {
return task.isValid();
}
@Override
public boolean executeTask(final DataContext context,
final RunConfiguration configuration,
ExecutionEnvironment env,
final MakeBeforeRunTask task) {
if (!Projects.requiresAndroidModel(myProject) || !Projects.isDirectGradleInvocationEnabled(myProject)) {
CompileStepBeforeRun regularMake = new CompileStepBeforeRun(myProject);
return regularMake.executeTask(context, configuration, env, new CompileStepBeforeRun.MakeBeforeRunTask());
}
final AtomicBoolean success = new AtomicBoolean();
try {
final Semaphore done = new Semaphore();
done.down();
final AtomicReference<String> errorMsgRef = new AtomicReference<String>();
// If the model needs a sync, we need to sync "synchronously" before running.
// See: https://code.google.com/p/android/issues/detail?id=70718
GradleSyncState syncState = GradleSyncState.getInstance(myProject);
if (syncState.isSyncNeeded() != ThreeState.NO) {
GradleProjectImporter.getInstance().syncProjectSynchronously(myProject, false, new GradleSyncListener.Adapter() {
@Override
public void syncFailed(@NotNull Project project, @NotNull String errorMessage) {
errorMsgRef.set(errorMessage);
}
});
}
String errorMsg = errorMsgRef.get();
if (errorMsg != null) {
// Sync failed. There is no point on continuing, because most likely the model is either not there, or has stale information,
// including the path of the APK.
LOG.info("Unable to launch '" + TASK_NAME + "' task. Project sync failed with message: " + errorMsg);
return false;
}
final GradleInvoker gradleInvoker = GradleInvoker.getInstance(myProject);
final GradleInvoker.AfterGradleInvocationTask afterTask = new GradleInvoker.AfterGradleInvocationTask() {
@Override
public void execute(@NotNull GradleInvocationResult result) {
success.set(result.isBuildSuccessful());
gradleInvoker.removeAfterGradleInvocationTask(this);
done.up();
}
};
if (myProject.isDisposed()) {
done.up();
}
else {
// To ensure that the "Run Configuration" waits for the Gradle tasks to be executed, we use SwingUtilities.invokeAndWait. I tried
// using Application.invokeAndWait but it never worked. IDEA also uses SwingUtilities in this scenario (see CompileStepBeforeRun.)
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
Module[] modules;
if (configuration instanceof ModuleRunProfile) {
// ModuleBasedConfiguration includes Android and JUnit run configurations, including "JUnit: Rerun Failed Tests",
// which is AbstractRerunFailedTestsAction.MyRunProfile.
modules = ((ModuleRunProfile)configuration).getModules();
}
else {
modules = Projects.getModulesToBuildFromSelection(myProject, context);
}
gradleInvoker.addAfterGradleInvocationTask(afterTask);
String goal = task.getGoal();
if (StringUtil.isEmpty(goal)) {
if (isUnitTestConfiguration(configuration)) {
// Make sure all "intermediates/classes" directories are up-to-date.
Module[] affectedModules = getAffectedModules(modules);
gradleInvoker.compileJava(affectedModules, TestCompileType.JAVA_TESTS);
} else {
TestCompileType testCompileType = getTestCompileType(configuration);
gradleInvoker.assemble(modules, testCompileType);
}
} else {
gradleInvoker.executeTasks(Lists.newArrayList(goal));
}
}
});
done.waitFor();
}
}
catch (Throwable t) {
LOG.info("Unable to launch '" + TASK_NAME + "' task", t);
return false;
}
return success.get();
}
@NotNull
private Module[] getAffectedModules(@NotNull Module[] modules) {
final CompilerManager compilerManager = CompilerManager.getInstance(myProject);
CompileScope scope = compilerManager.createModulesCompileScope(modules, true, true);
return scope.getAffectedModules();
}
@NotNull
private static TestCompileType getTestCompileType(@Nullable RunConfiguration runConfiguration) {
String id = runConfiguration != null ? runConfiguration.getType().getId() : null;
return GradleInvoker.getTestCompileType(id);
}
}