blob: 2f304f09a132059969104ae029d07e5e9b92b204 [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.util;
import com.android.builder.model.AndroidProject;
import com.android.tools.idea.gradle.GradleSyncState;
import com.android.tools.idea.gradle.IdeaAndroidProject;
import com.android.tools.idea.gradle.JavaModel;
import com.android.tools.idea.gradle.compiler.AndroidGradleBuildConfiguration;
import com.android.tools.idea.gradle.dependency.DependencySetupErrors;
import com.android.tools.idea.gradle.dependency.LibraryDependency;
import com.android.tools.idea.gradle.facet.AndroidGradleFacet;
import com.android.tools.idea.gradle.facet.JavaGradleFacet;
import com.android.tools.idea.gradle.messages.ProjectSyncMessages;
import com.android.tools.idea.gradle.project.PostProjectSetupTasksExecutor;
import com.intellij.ide.DataManager;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.projectView.impl.AbstractProjectViewPane;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.externalSystem.model.DataNode;
import com.intellij.openapi.externalSystem.model.ProjectKeys;
import com.intellij.openapi.externalSystem.model.project.ModuleData;
import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.IdeFrameEx;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.settings.GradleSettings;
import javax.swing.*;
import java.io.File;
import java.util.Collection;
import static com.android.tools.idea.gradle.messages.CommonMessageGroupNames.*;
import static com.android.tools.idea.startup.AndroidStudioSpecificInitializer.isAndroidStudio;
import static com.intellij.ide.impl.ProjectUtil.updateLastProjectLocation;
import static com.intellij.openapi.actionSystem.LangDataKeys.MODULE;
import static com.intellij.openapi.actionSystem.LangDataKeys.MODULE_CONTEXT_ARRAY;
import static com.intellij.openapi.util.io.FileUtil.*;
import static com.intellij.openapi.util.text.StringUtil.isNotEmpty;
import static com.intellij.openapi.wm.impl.IdeFrameImpl.SHOULD_OPEN_IN_FULL_SCREEN;
import static com.intellij.util.ui.UIUtil.invokeAndWaitIfNeeded;
import static java.lang.Boolean.TRUE;
/**
* Utility methods for {@link Project}s.
*/
public final class Projects {
private static final Key<String> GRADLE_VERSION = Key.create("project.gradle.version");
private static final Key<LibraryDependency> MODULE_COMPILED_ARTIFACT = Key.create("module.compiled.artifact");
private static final Key<Boolean> HAS_SYNC_ERRORS = Key.create("project.has.sync.errors");
private static final Key<Boolean> HAS_WRONG_JDK = Key.create("project.has.wrong.jdk");
private static final Key<DependencySetupErrors> DEPENDENCY_SETUP_ERRORS = Key.create("project.dependency.setup.errors");
private Projects() {
}
@NotNull
public static File getBaseDirPath(@NotNull Project project) {
String basePath = project.getBasePath();
assert basePath != null;
return new File(toCanonicalPath(basePath));
}
public static void setGradleVersionUsed(@NotNull Project project, @Nullable String gradleVersion) {
project.putUserData(GRADLE_VERSION, gradleVersion);
}
@Nullable
public static String getGradleVersionUsed(@NotNull Project project) {
return project.getUserData(GRADLE_VERSION);
}
public static void removeAllModuleCompiledArtifacts(@NotNull Project project) {
ModuleManager moduleManager = ModuleManager.getInstance(project);
for (Module module : moduleManager.getModules()) {
setModuleCompiledArtifact(module, null);
}
}
public static void setModuleCompiledArtifact(@NotNull Module module, @Nullable LibraryDependency compiledArtifact) {
module.putUserData(MODULE_COMPILED_ARTIFACT, compiledArtifact);
}
@Nullable
public static LibraryDependency getModuleCompiledArtifact(@NotNull Module module) {
return module.getUserData(MODULE_COMPILED_ARTIFACT);
}
public static void populate(@NotNull final Project project, @NotNull final Collection<DataNode<ModuleData>> modules) {
invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
ProjectSyncMessages messages = ProjectSyncMessages.getInstance(project);
messages.removeMessages(PROJECT_STRUCTURE_ISSUES, MISSING_DEPENDENCIES_BETWEEN_MODULES, FAILED_TO_SET_UP_DEPENDENCIES,
VARIANT_SELECTION_CONFLICTS, EXTRA_GENERATED_SOURCES);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
if (!project.isDisposed()) {
ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring(new Runnable() {
@Override
public void run() {
ProjectDataManager dataManager = ServiceManager.getService(ProjectDataManager.class);
dataManager.importData(ProjectKeys.MODULE, modules, project, true /* synchronous */);
}
});
}
}
});
// We need to call this method here, otherwise the IDE will think the project is not a Gradle project and it won't generate
// sources for it. This happens on new projects.
PostProjectSetupTasksExecutor.getInstance(project).onProjectSyncCompletion();
}
});
}
public static void executeProjectChanges(@NotNull final Project project, @NotNull final Runnable changes) {
Runnable task = new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
if (!project.isDisposed()) {
ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring(changes);
}
}
});
}
};
invokeAndWaitIfNeeded(task);
}
public static void setHasSyncErrors(@NotNull Project project, boolean hasSyncErrors) {
project.putUserData(HAS_SYNC_ERRORS, hasSyncErrors);
}
public static void setHasWrongJdk(@NotNull Project project, boolean hasWrongJdk) {
project.putUserData(HAS_WRONG_JDK, hasWrongJdk);
}
/**
* Indicates whether the last sync with Gradle failed.
*/
public static boolean lastGradleSyncFailed(@NotNull Project project) {
return !GradleSyncState.getInstance(project).isSyncInProgress() && isBuildWithGradle(project) && requiredAndroidModelMissing(project);
}
public static boolean hasErrors(@NotNull Project project) {
if (hasSyncErrors(project) || hasWrongJdk(project)) {
return true;
}
ProjectSyncMessages messages = ProjectSyncMessages.getInstance(project);
int errorCount = messages.getErrorCount();
if (errorCount > 0) {
return false;
}
// Variant selection errors do not count as "sync failed" errors.
int variantSelectionErrorCount = messages.getMessageCount(VARIANT_SELECTION_CONFLICTS);
return errorCount != variantSelectionErrorCount;
}
private static boolean hasSyncErrors(@NotNull Project project) {
return getBoolean(project, HAS_SYNC_ERRORS);
}
private static boolean hasWrongJdk(@NotNull Project project) {
return getBoolean(project, HAS_WRONG_JDK);
}
private static boolean getBoolean(@NotNull Project project, @NotNull Key<Boolean> key) {
Boolean val = project.getUserData(key);
return val != null && val.booleanValue();
}
/**
* Indicates the given project requires an Android model, but the model is {@code null}. Possible causes for this scenario to happen are:
* <ul>
* <li>the last sync with Gradle failed</li>
* <li>Studio just started up and it has not synced the project yet</li>
* </ul>
*
* @param project the project.
* @return {@code true} if the project is a Gradle-based Android project that does not contain any Gradle model.
*/
public static boolean requiredAndroidModelMissing(@NotNull Project project) {
for (Module module : ModuleManager.getInstance(project).getModules()) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null && facet.requiresAndroidModel() && facet.getAndroidModel() == null) {
return true;
}
}
return false;
}
/**
* Opens the given project in the IDE.
*
* @param project the project to open.
*/
public static void open(@NotNull Project project) {
updateLastProjectLocation(project.getBasePath());
if (WindowManager.getInstance().isFullScreenSupportedInCurrentOS()) {
IdeFocusManager instance = IdeFocusManager.findInstance();
IdeFrame lastFocusedFrame = instance.getLastFocusedFrame();
if (lastFocusedFrame instanceof IdeFrameEx) {
boolean fullScreen = ((IdeFrameEx)lastFocusedFrame).isInFullScreen();
if (fullScreen) {
project.putUserData(SHOULD_OPEN_IN_FULL_SCREEN, TRUE);
}
}
}
ProjectManagerEx.getInstanceEx().openProject(project);
}
public static boolean isDirectGradleInvocationEnabled(@NotNull Project project) {
AndroidGradleBuildConfiguration buildConfiguration = AndroidGradleBuildConfiguration.getInstance(project);
return buildConfiguration.USE_EXPERIMENTAL_FASTER_BUILD;
}
public static boolean isOfflineBuildModeEnabled(@NotNull Project project) {
return GradleSettings.getInstance(project).isOfflineWork();
}
/**
* Indicates whether the given project has at least one module backed by an {@link AndroidProject}. To check if a project is a
* "Gradle project," please use the method {@link Projects#isBuildWithGradle(Project)}.
* @param project the given project.
* @return {@code true} if the given project has one or more modules backed by an {@link AndroidProject}; {@code false} otherwise.
*/
public static boolean requiresAndroidModel(@NotNull Project project) {
ModuleManager moduleManager = ModuleManager.getInstance(project);
for (Module module : moduleManager.getModules()) {
AndroidFacet androidFacet = AndroidFacet.getInstance(module);
if (androidFacet != null && androidFacet.requiresAndroidModel()) {
return true;
}
}
return false;
}
@Nullable
public static IdeaAndroidProject getAndroidModel(@NotNull Module module) {
AndroidFacet androidFacet = AndroidFacet.getInstance(module);
return androidFacet != null ? androidFacet.getAndroidModel() : null;
}
/**
* Indicates whether the give project is a legacy IDEA Android project (which is deprecated in Android Studio.)
*
* @param project the given project.
* @return {@code true} if the given project is a legacy IDEA Android project; {@code false} otherwise.
*/
public static boolean isIdeaAndroidProject(@NotNull Project project) {
ModuleManager moduleManager = ModuleManager.getInstance(project);
for (Module module : moduleManager.getModules()) {
if (AndroidFacet.getInstance(module) != null && !isBuildWithGradle(module)) {
return true;
}
}
return false;
}
/**
* Ensures that "External Build" is enabled for the given Gradle-based project. External build is the type of build that delegates project
* building to Gradle.
*
* @param project the given project. This method does not do anything if the given project is not a Gradle-based project.
*/
public static void enforceExternalBuild(@NotNull Project project) {
if (requiresAndroidModel(project)) {
// We only enforce JPS usage when the 'android' plug-in is not being used in Android Studio.
if (!isAndroidStudio()) {
AndroidGradleBuildConfiguration.getInstance(project).USE_EXPERIMENTAL_FASTER_BUILD = false;
}
}
}
/**
* Returns the modules to build based on the current selection in the 'Project' tool window. If the module that corresponds to the project
* is selected, all the modules in such projects are returned. If there is no selection, an empty array is returned.
*
* @param project the given project.
* @param dataContext knows the modules that are selected. If {@code null}, this method gets the {@code DataContext} from the 'Project'
* tool window directly.
* @return the modules to build based on the current selection in the 'Project' tool window.
*/
@NotNull
public static Module[] getModulesToBuildFromSelection(@NotNull Project project, @Nullable DataContext dataContext) {
if (dataContext == null) {
ProjectView projectView = ProjectView.getInstance(project);
AbstractProjectViewPane pane = projectView.getCurrentProjectViewPane();
if (pane != null) {
JComponent treeComponent = pane.getComponentToFocus();
dataContext = DataManager.getInstance().getDataContext(treeComponent);
}
else {
return Module.EMPTY_ARRAY;
}
}
Module[] modules = MODULE_CONTEXT_ARRAY.getData(dataContext);
if (modules != null) {
if (modules.length == 1 && isProjectModule(modules[0])) {
return ModuleManager.getInstance(project).getModules();
}
return modules;
}
Module module = MODULE.getData(dataContext);
if (module != null) {
return isProjectModule(module) ? ModuleManager.getInstance(project).getModules() : new Module[]{module};
}
return Module.EMPTY_ARRAY;
}
public static boolean isProjectModule(@NotNull Module module) {
// if we got here is because we are dealing with a Gradle project, but if there is only one module selected and this module is the
// module that corresponds to the project itself, it won't have an android-gradle facet. In this case we treat it as if we were going
// to build the whole project.
File moduleFilePath = new File(toSystemDependentName(module.getModuleFilePath()));
File moduleRootDirPath = moduleFilePath.getParentFile();
if (moduleRootDirPath == null) {
return false;
}
String basePath = module.getProject().getBasePath();
return basePath != null && filesEqual(moduleRootDirPath, new File(basePath)) && !isBuildWithGradle(module);
}
/**
* Indicates whether Gradle is used to build this project.
* Note: {@link #requiresAndroidModel(Project)} indicates whether a project has a IdeaAndroidProject model.
* That method should be preferred in almost all cases. Use this method only if you explicitly need to check whether the model was
* generated by Gradle (this will exclude models generated by other build systems.)
*/
public static boolean isBuildWithGradle(@NotNull Project project) {
ModuleManager moduleManager = ModuleManager.getInstance(project);
for (Module module : moduleManager.getModules()) {
if (isBuildWithGradle(module)) {
return true;
}
}
return false;
}
/**
* Indicates whether Gradle is used to build the module.
*/
public static boolean isBuildWithGradle(@NotNull Module module) {
return AndroidGradleFacet.getInstance(module) != null;
}
/**
* @see #isGradleProjectModule(Module)
*/
@Nullable
public static Module findGradleProjectModule(@NotNull Project project) {
ModuleManager moduleManager = ModuleManager.getInstance(project);
Module[] modules = moduleManager.getModules();
if (modules.length == 1) {
return modules[0];
}
for (Module module : modules) {
if (isGradleProjectModule(module)) {
return module;
}
}
return null;
}
/**
* Indicates whether the given module is the one that represents the project.
* <p>
* For example, in this project:
* <pre>
* project1
* - module1
* - module1.iml
* - module2
* - module2.iml
* -project1.iml
* </pre>
* "project1" is the module that represents the project.
* </p>
*
* @param module the given module.
* @return {@code true} if the given module is the one that represents the project, {@code false} otherwise.
*/
public static boolean isGradleProjectModule(@NotNull Module module) {
AndroidFacet androidFacet = AndroidFacet.getInstance(module);
if (androidFacet != null && androidFacet.requiresAndroidModel() && isBuildWithGradle(module)) {
// If the module is an Android project, check that the module's path is the same as the project's.
File moduleFilePath = new File(toSystemDependentName(module.getModuleFilePath()));
File moduleRootDirPath = moduleFilePath.getParentFile();
return pathsEqual(moduleRootDirPath.getPath(), module.getProject().getBasePath());
}
// For non-Android project modules, the top-level one is the one without an "Android-Gradle" facet.
return !isBuildWithGradle(module);
}
@Nullable
public static File getBuildFolderPath(@NotNull Module module) {
if (module.isDisposed() || !requiresAndroidModel(module.getProject())) {
return null;
}
AndroidFacet androidFacet = AndroidFacet.getInstance(module);
if (androidFacet != null && androidFacet.getAndroidModel() != null) {
return androidFacet.getAndroidModel().getAndroidProject().getBuildFolder();
}
JavaGradleFacet javaFacet = JavaGradleFacet.getInstance(module);
if (javaFacet != null) {
JavaModel javaModel = javaFacet.getJavaModel();
if (javaModel != null) {
return javaFacet.getJavaModel().getBuildFolderPath();
}
String path = javaFacet.getConfiguration().BUILD_FOLDER_PATH;
if (isNotEmpty(path)) {
return new File(toSystemDependentName(path));
}
}
return null;
}
@Nullable
public static DependencySetupErrors getDependencySetupErrors(@NotNull Project project) {
return project.getUserData(DEPENDENCY_SETUP_ERRORS);
}
public static void setDependencySetupErrors(@NotNull Project project, @Nullable DependencySetupErrors errors) {
project.putUserData(DEPENDENCY_SETUP_ERRORS, errors);
}
@Nullable
public static AndroidProject getAndroidModel(@NotNull VirtualFile file, @NotNull Project project) {
Module module = ModuleUtilCore.findModuleForFile(file, project);
if (module == null) {
if (requiresAndroidModel(project)) {
// You've edited a file that does not correspond to a module in a Gradle project; you are
// most likely editing a file in an excluded folder under the build directory
VirtualFile base = project.getBaseDir();
VirtualFile parent = file.getParent();
while (parent != null && parent.equals(base)) {
module = ModuleUtilCore.findModuleForFile(parent, project);
if (module != null) {
break;
}
parent = parent.getParent();
}
}
if (module == null) {
return null;
}
}
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet == null) {
return null;
}
IdeaAndroidProject androidModel = facet.getAndroidModel();
if (androidModel == null) {
return null;
}
return androidModel.getAndroidProject();
}
}