Merge "Fixed bug where settings.gradle file was updated when project validation fails before a sync." into idea133-milestone
diff --git a/android/src/com/android/tools/idea/gradle/GradleSyncState.java b/android/src/com/android/tools/idea/gradle/GradleSyncState.java
index 00193d4..89065bd 100644
--- a/android/src/com/android/tools/idea/gradle/GradleSyncState.java
+++ b/android/src/com/android/tools/idea/gradle/GradleSyncState.java
@@ -17,7 +17,6 @@
import com.android.SdkConstants;
import com.android.tools.idea.gradle.project.GradleSyncListener;
-import com.android.tools.idea.gradle.project.ProjectValidator;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.android.tools.idea.gradle.variant.view.BuildVariantView;
import com.android.tools.idea.startup.AndroidStudioSpecificInitializer;
@@ -99,7 +98,6 @@
@Override
public void run() {
myMessageBus.syncPublisher(GRADLE_SYNC_TOPIC).syncFailed(myProject, message);
- ProjectValidator.mergeQueuedMessages(myProject);
}
});
}
@@ -115,7 +113,6 @@
@Override
public void run() {
myMessageBus.syncPublisher(GRADLE_SYNC_TOPIC).syncSucceeded(myProject);
- ProjectValidator.mergeQueuedMessages(myProject);
}
});
}
diff --git a/android/src/com/android/tools/idea/gradle/actions/CleanImportProjectAction.java b/android/src/com/android/tools/idea/gradle/actions/CleanImportProjectAction.java
index 654ff5b..a06635f 100644
--- a/android/src/com/android/tools/idea/gradle/actions/CleanImportProjectAction.java
+++ b/android/src/com/android/tools/idea/gradle/actions/CleanImportProjectAction.java
@@ -77,7 +77,7 @@
delete(filesToDelete, projectName);
try {
LOG.info(String.format("About to import project '%1$s'.", projectName));
- GradleProjectImporter.getInstance().importNewlyCreatedProject(projectName, projectDir, null);
+ GradleProjectImporter.getInstance().importNewlyCreatedProject(projectName, projectDir, null, null, null);
LOG.info(String.format("Done importing project '%1$s'.", projectName));
}
catch (Exception error) {
diff --git a/android/src/com/android/tools/idea/gradle/eclipse/AdtImportBuilder.java b/android/src/com/android/tools/idea/gradle/eclipse/AdtImportBuilder.java
index 8e10009..0b7fd98 100644
--- a/android/src/com/android/tools/idea/gradle/eclipse/AdtImportBuilder.java
+++ b/android/src/com/android/tools/idea/gradle/eclipse/AdtImportBuilder.java
@@ -180,7 +180,7 @@
};
final GradleProjectImporter importer = GradleProjectImporter.getInstance();
if (myCreateProject) {
- importer.importProject(project.getName(), destDir, callback, project);
+ importer.importProject(project.getName(), destDir, true, callback, project, null);
} else {
importer.requestProjectSync(project, true, callback);
}
diff --git a/android/src/com/android/tools/idea/gradle/project/GradleBuildFileUpdater.java b/android/src/com/android/tools/idea/gradle/project/GradleBuildFileUpdater.java
index d913276..f0d1956 100644
--- a/android/src/com/android/tools/idea/gradle/project/GradleBuildFileUpdater.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleBuildFileUpdater.java
@@ -16,22 +16,25 @@
package com.android.tools.idea.gradle.project;
import com.android.SdkConstants;
+import com.android.tools.idea.gradle.GradleSyncState;
import com.android.tools.idea.gradle.facet.AndroidGradleFacet;
import com.android.tools.idea.gradle.parser.GradleSettingsFile;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.google.common.base.Joiner;
-import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.ModuleAdapter;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent;
+import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -41,8 +44,6 @@
* sees.
*/
public class GradleBuildFileUpdater extends ModuleAdapter implements BulkFileListener {
- private static final Logger LOG = Logger.getInstance(GradleBuildFileUpdater.class);
-
private final Project myProject;
public GradleBuildFileUpdater(@NotNull Project project) {
@@ -51,18 +52,40 @@
@Override
public void moduleAdded(@NotNull final Project project, @NotNull final Module module) {
- // The module has probably already been added to the settings file but let's call this to be safe.
- GradleSettingsFile settingsFile = GradleSettingsFile.get(project);
+ // Don't do anything if we are in the middle of a project sync.
+ if (GradleSyncState.getInstance(project).isSyncInProgress()) {
+ return;
+ }
+ final GradleSettingsFile settingsFile = GradleSettingsFile.get(project);
if (settingsFile != null) {
- settingsFile.addModule(module);
+ // if settings.gradle does not have a module, we are in the middle of setting up a project.
+ final PsiFile psiFile = settingsFile.getPsiFile();
+ Module found = ModuleUtilCore.findModuleForPsiElement(psiFile);
+ if (found != null) {
+ new WriteCommandAction<Void>(project, "Update settings.gradle", psiFile) {
+ @Override
+ protected void run(@NotNull Result<Void> result) throws Throwable {
+ settingsFile.addModule(module);
+ }
+ }.execute();
+ }
}
}
@Override
public void moduleRemoved(@NotNull Project project, @NotNull final Module module) {
- GradleSettingsFile settingsFile = GradleSettingsFile.get(project);
+ // Don't do anything if we are in the middle of a project sync.
+ if (GradleSyncState.getInstance(project).isSyncInProgress()) {
+ return;
+ }
+ final GradleSettingsFile settingsFile = GradleSettingsFile.get(project);
if (settingsFile != null) {
- settingsFile.removeModule(module);
+ new WriteCommandAction<Void>(project, "Update settings.gradle", settingsFile.getPsiFile()) {
+ @Override
+ protected void run(@NotNull Result<Void> result) throws Throwable {
+ settingsFile.removeModule(module);
+ }
+ }.execute();
}
}
@@ -83,7 +106,7 @@
if (!(event instanceof VFilePropertyChangeEvent)) {
continue;
}
- VFilePropertyChangeEvent propChangeEvent = (VFilePropertyChangeEvent) event;
+ VFilePropertyChangeEvent propChangeEvent = (VFilePropertyChangeEvent)event;
if (!(VirtualFile.PROP_NAME.equals(propChangeEvent.getPropertyName()))) {
continue;
}
diff --git a/android/src/com/android/tools/idea/gradle/project/GradleModuleImporter.java b/android/src/com/android/tools/idea/gradle/project/GradleModuleImporter.java
index 9399255..72b886b 100644
--- a/android/src/com/android/tools/idea/gradle/project/GradleModuleImporter.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleModuleImporter.java
@@ -15,21 +15,36 @@
*/
package com.android.tools.idea.gradle.project;
+import com.android.SdkConstants;
+import com.android.tools.idea.gradle.parser.GradleSettingsFile;
+import com.android.tools.idea.gradle.util.GradleUtil;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Suppliers;
+import com.google.common.base.Throwables;
+import com.google.common.collect.*;
import com.intellij.ide.util.projectWizard.ModuleWizardStep;
import com.intellij.ide.util.projectWizard.WizardContext;
+import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.util.GradleConstants;
+import java.io.File;
import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
+
+import static com.google.common.base.Predicates.in;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.base.Predicates.notNull;
/**
* Creates new project module from source files with Gradle configuration.
@@ -39,7 +54,6 @@
@Nullable private final Project myProject;
private final boolean myIsWizard;
- private final GradleProjectImporter myImporter;
public GradleModuleImporter(@NotNull WizardContext context) {
this(context.getProject(), true);
@@ -52,7 +66,6 @@
private GradleModuleImporter(@Nullable Project project, boolean isWizard) {
myIsWizard = isWizard;
myProject = project;
- myImporter = GradleProjectImporter.getInstance();
}
public static boolean isGradleProject(VirtualFile importSource) {
@@ -73,7 +86,7 @@
@Override
public void importProjects(Map<String, VirtualFile> projects) {
try {
- myImporter.importModules(projects, myProject, null);
+ importModules(this, projects, myProject, null);
}
catch (IOException e) {
LOG.error(e);
@@ -102,6 +115,200 @@
@Override
public Set<ModuleToImport> findModules(VirtualFile importSource) throws IOException {
assert myProject != null;
- return myImporter.getRelatedProjects(importSource, myProject);
+ return getRelatedProjects(importSource, myProject);
+ }
+
+ /**
+ * Find related modules that should be imported into Android Studio together with the project user chose so it could be built.
+ * <p/>
+ * Top-level use-cases:
+ * 1. If the user selects top-level project (e.g. the one with settings.gradle) Android Studio will import all its sub-projects.
+ * 2. For leaf projects (ones with build.gradle), Android Studio will import selected project and the projects it depends on.
+ *
+ * @param sourceProject the destinationProject that user wants to import
+ * @param destinationProject destination destinationProject
+ * @return mapping from module name to {@code VirtualFile} containing module contents. Values will be null if the module location was not
+ * found.
+ */
+ @NotNull
+ public static Set<ModuleToImport> getRelatedProjects(@NotNull VirtualFile sourceProject, @NotNull Project destinationProject) {
+ VirtualFile settingsGradle = sourceProject.findFileByRelativePath(SdkConstants.FN_SETTINGS_GRADLE);
+ if (settingsGradle != null) {
+ return buildModulesSet(getSubProjects(settingsGradle, destinationProject),
+ GradleProjectDependencyParser.newInstance(destinationProject));
+ }
+ else {
+ return getRequiredProjects(sourceProject, destinationProject);
+ }
+ }
+
+ /**
+ * Find direct and transitive dependency projects.
+ */
+ @NotNull
+ private static Set<ModuleToImport> getRequiredProjects(VirtualFile sourceProject, Project destinationProject) {
+ GradleSiblingLookup subProjectLocations = new GradleSiblingLookup(sourceProject, destinationProject);
+ Function<VirtualFile, Iterable<String>> parser = GradleProjectDependencyParser.newInstance(destinationProject);
+ Map<String, VirtualFile> modules = Maps.newHashMap();
+ List<VirtualFile> toAnalyze = Lists.newLinkedList();
+ toAnalyze.add(sourceProject);
+
+ while (!toAnalyze.isEmpty()) {
+ Set<String> dependencies = Sets.newHashSet(Iterables.concat(Iterables.transform(toAnalyze, parser)));
+ Iterable<String> notAnalyzed = Iterables.filter(dependencies, not(in(modules.keySet())));
+ // Turns out, Maps#toMap does not allow null values...
+ Map<String, VirtualFile> dependencyToLocation = Maps.newHashMap();
+ for (String dependency : notAnalyzed) {
+ dependencyToLocation.put(dependency, subProjectLocations.apply(dependency));
+ }
+ modules.putAll(dependencyToLocation);
+ toAnalyze = FluentIterable.from(dependencyToLocation.values()).filter(notNull()).toList();
+ }
+ modules.put(subProjectLocations.getPrimaryProjectName(), sourceProject);
+ return buildModulesSet(modules, parser);
+ }
+
+ private static Set<ModuleToImport> buildModulesSet(Map<String, VirtualFile> modules, Function<VirtualFile, Iterable<String>> parser) {
+ Set<ModuleToImport> modulesSet = new HashSet<ModuleToImport>(modules.size());
+ for (Map.Entry<String, VirtualFile> entry : modules.entrySet()) {
+ modulesSet
+ .add(new ModuleToImport(entry.getKey(), entry.getValue(), Suppliers.compose(parser, Suppliers.ofInstance(entry.getValue()))));
+ }
+ return modulesSet;
+ }
+
+ @NotNull
+ public static Map<String, VirtualFile> getSubProjects(@NotNull final VirtualFile settingsGradle, Project destinationProject) {
+ final GradleSettingsFile settingsFile = new GradleSettingsFile(settingsGradle, destinationProject);
+ Map<String, File> allProjects = settingsFile.getModulesWithLocation();
+ return Maps.transformValues(allProjects, new ResolvePath(VfsUtilCore.virtualToIoFile(settingsGradle.getParent())));
+ }
+
+ /**
+ * Import set of gradle modules into existing Android Studio project. Note that no validation will be performed, modules will
+ * be copied as is and settings.xml will be updated for the imported modules. It is callers' responsibility to ensure content
+ * can be copied to the target directory and that module list is valid.
+ *
+ * @param modules mapping between module names and locations on the filesystem. Neither name nor location should be null
+ * @param project project to import the modules to
+ * @param listener optional object that gets notified of operation success or failure
+ */
+ @VisibleForTesting
+ static void importModules(@NotNull final Object requestor,
+ @NotNull final Map<String, VirtualFile> modules,
+ @Nullable final Project project,
+ @Nullable final GradleSyncListener listener) throws IOException, ConfigurationException {
+ String error = validateProjectsForImport(modules);
+ if (error != null) {
+ if (listener != null && project != null) {
+ listener.syncFailed(project, error);
+ return;
+ }
+ else {
+ throw new IOException(error);
+ }
+ }
+
+ assert project != null;
+ Throwable throwable = new WriteCommandAction.Simple(project) {
+ @Override
+ protected void run() throws Throwable {
+ copyAndRegisterModule(requestor, modules, project, listener);
+ }
+
+ @Override
+ public boolean isSilentExecution() {
+ return true;
+ }
+ }.execute().getThrowable();
+ rethrowAsProperlyTypedException(throwable);
+ }
+
+ /**
+ * Ensures that we know paths for all projects we are trying to import.
+ *
+ * @return message string if import is not possible or <code>null</code> otherwise
+ */
+ @Nullable
+ private static String validateProjectsForImport(@NotNull Map<String, VirtualFile> modules) {
+ Set<String> projects = new TreeSet<String>();
+ for (Map.Entry<String, VirtualFile> mapping : modules.entrySet()) {
+ if (mapping.getValue() == null) {
+ projects.add(mapping.getKey());
+ }
+ }
+ if (projects.isEmpty()) {
+ return null;
+ }
+ else if (projects.size() == 1) {
+ return String.format("Sources for module '%1$s' were not found", Iterables.getFirst(projects, null));
+ }
+ else {
+ String projectsList = Joiner.on("', '").join(projects);
+ return String.format("Sources were not found for modules '%1$s'", projectsList);
+ }
+ }
+
+ /**
+ * Recover actual type of the exception.
+ */
+ private static void rethrowAsProperlyTypedException(Throwable throwable) throws IOException, ConfigurationException {
+ if (throwable != null) {
+ Throwables.propagateIfPossible(throwable, IOException.class, ConfigurationException.class);
+ throw new IllegalStateException(throwable);
+ }
+ }
+
+ /**
+ * Copy modules and adds it to settings.gradle
+ */
+ private static void copyAndRegisterModule(@NotNull Object requestor,
+ @NotNull Map<String, VirtualFile> modules,
+ @NotNull Project project,
+ @Nullable GradleSyncListener listener) throws IOException, ConfigurationException {
+ VirtualFile projectRoot = project.getBaseDir();
+ if (projectRoot.findChild(SdkConstants.FN_SETTINGS_GRADLE) == null) {
+ projectRoot.createChildData(requestor, SdkConstants.FN_SETTINGS_GRADLE);
+ }
+ GradleSettingsFile gradleSettingsFile = GradleSettingsFile.get(project);
+ assert gradleSettingsFile != null : "File should have been created";
+ for (Map.Entry<String, VirtualFile> module : modules.entrySet()) {
+ String name = module.getKey();
+ File targetFile = GradleUtil.getDefaultSubprojectLocation(projectRoot, name);
+ VirtualFile moduleSource = module.getValue();
+ if (moduleSource != null) {
+ if (!VfsUtilCore.isAncestor(projectRoot, moduleSource, true)) {
+ VirtualFile target = VfsUtil.createDirectoryIfMissing(targetFile.getAbsolutePath());
+ if (target == null) {
+ throw new IOException(String.format("Unable to create directory %1$s", targetFile));
+ }
+ moduleSource.copy(requestor, target.getParent(), target.getName());
+ }
+ else {
+ targetFile = VfsUtilCore.virtualToIoFile(moduleSource);
+ }
+ }
+ gradleSettingsFile.addModule(name, targetFile);
+ }
+ GradleProjectImporter.getInstance().requestProjectSync(project, false, listener);
+ }
+
+ /**
+ * Resolves paths that may be either relative to provided directory or absolute.
+ */
+ private static class ResolvePath implements Function<File, VirtualFile> {
+ private final File mySourceDir;
+
+ public ResolvePath(File sourceDir) {
+ mySourceDir = sourceDir;
+ }
+
+ @Override
+ public VirtualFile apply(File path) {
+ if (!path.isAbsolute()) {
+ path = new File(mySourceDir, path.getPath());
+ }
+ return VfsUtil.findFileByIoFile(path, true);
+ }
}
}
diff --git a/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java b/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
index 928a225..e46b0a4 100755
--- a/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
@@ -18,22 +18,16 @@
import com.android.SdkConstants;
import com.android.tools.idea.gradle.GradleSyncState;
import com.android.tools.idea.gradle.invoker.GradleInvoker;
-import com.android.tools.idea.gradle.parser.GradleSettingsFile;
import com.android.tools.idea.gradle.util.FilePaths;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.android.tools.idea.gradle.util.LocalProperties;
import com.android.tools.idea.gradle.util.Projects;
import com.android.tools.idea.startup.AndroidStudioSpecificInitializer;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Suppliers;
-import com.google.common.base.Throwables;
-import com.google.common.collect.*;
+import com.google.common.collect.ImmutableList;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
-import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.model.DataNode;
@@ -66,13 +60,11 @@
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.ui.AppUIUtil;
import com.intellij.util.SystemProperties;
-import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
@@ -85,10 +77,10 @@
import java.io.File;
import java.io.IOException;
-import java.util.*;
+import java.util.Collection;
-import static com.google.common.base.Predicates.*;
import static com.intellij.notification.NotificationType.ERROR;
+import static com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode.MODAL_SYNC;
import static org.jetbrains.plugins.gradle.util.GradleUtil.getLastUsedGradleHome;
import static org.jetbrains.plugins.gradle.util.GradleUtil.isGradleDefaultWrapperFilesExist;
@@ -172,111 +164,6 @@
createProjectFileForGradleProject(selectedFile, null);
}
- /**
- * Import set of gradle modules into existing Android Studio project. Note that no validation will be performed, modules will
- * be copied as is and settings.xml will be updated for the imported modules. It is callers' responsibility to ensure content
- * can be copied to the target directory and that module list is valid.
- *
- * @param modules mapping between module names and locations on the filesystem. Neither name nor location should be null
- * @param project project to import the modules to
- * @param listener optional object that gets notified of operation success or failure
- */
- public void importModules(@NotNull final Map<String, VirtualFile> modules,
- @Nullable final Project project,
- @Nullable final GradleSyncListener listener) throws IOException, ConfigurationException {
- String error = validateProjectsForImport(modules);
- if (error != null) {
- if (listener != null && project != null) {
- listener.syncFailed(project, error);
- return;
- }
- else {
- throw new IOException(error);
- }
- }
-
- assert project != null;
- Throwable throwable = new WriteCommandAction.Simple(project) {
- @Override
- protected void run() throws Throwable {
- copyAndRegisterModule(modules, project, listener);
- }
-
- @Override
- public boolean isSilentExecution() {
- return true;
- }
- }.execute().getThrowable();
- rethrowAsProperlyTypedException(throwable);
- }
-
- /**
- * Ensures that we know paths for all projects we are trying to import.
- *
- * @return message string if import is not possible or <code>null</code> otherwise
- */
- @Nullable
- private static String validateProjectsForImport(@NotNull Map<String, VirtualFile> modules) {
- Set<String> projects = new TreeSet<String>();
- for (Map.Entry<String, VirtualFile> mapping : modules.entrySet()) {
- if (mapping.getValue() == null) {
- projects.add(mapping.getKey());
- }
- }
- if (projects.isEmpty()) {
- return null;
- }
- else if (projects.size() == 1) {
- return String.format("Sources for module '%1$s' were not found", Iterables.getFirst(projects, null));
- }
- else {
- String projectsList = Joiner.on("', '").join(projects);
- return String.format("Sources were not found for modules '%1$s'", projectsList);
- }
- }
-
- /**
- * Recover actual type of the exception.
- */
- private static void rethrowAsProperlyTypedException(Throwable throwable) throws IOException, ConfigurationException {
- if (throwable != null) {
- Throwables.propagateIfPossible(throwable, IOException.class, ConfigurationException.class);
- throw new IllegalStateException(throwable);
- }
- }
-
- /**
- * Copy modules and adds it to settings.gradle
- */
- private void copyAndRegisterModule(@NotNull Map<String, VirtualFile> modules,
- @NotNull Project project,
- @Nullable GradleSyncListener listener) throws IOException, ConfigurationException {
- VirtualFile projectRoot = project.getBaseDir();
- if (projectRoot.findChild(SdkConstants.FN_SETTINGS_GRADLE) == null) {
- projectRoot.createChildData(this, SdkConstants.FN_SETTINGS_GRADLE);
- }
- GradleSettingsFile gradleSettingsFile = GradleSettingsFile.get(project);
- assert gradleSettingsFile != null : "File should have been created";
- for (Map.Entry<String, VirtualFile> module : modules.entrySet()) {
- String name = module.getKey();
- File targetFile = GradleUtil.getDefaultSubprojectLocation(projectRoot, name);
- VirtualFile moduleSource = module.getValue();
- if (moduleSource != null) {
- if (!VfsUtilCore.isAncestor(projectRoot, moduleSource, true)) {
- VirtualFile target = VfsUtil.createDirectoryIfMissing(targetFile.getAbsolutePath());
- if (target == null) {
- throw new IOException(String.format("Unable to create directory %1$s", targetFile));
- }
- moduleSource.copy(this, target.getParent(), target.getName());
- }
- else {
- targetFile = VfsUtilCore.virtualToIoFile(moduleSource);
- }
- }
- gradleSettingsFile.addModule(name, targetFile);
- }
- requestProjectSync(project, false, listener);
- }
/**
* Creates IntelliJ project file in the root of the project directory.
@@ -288,12 +175,12 @@
VirtualFile projectDir = selectedFile.isDirectory() ? selectedFile : selectedFile.getParent();
File projectDirPath = VfsUtilCore.virtualToIoFile(projectDir);
try {
- importProject(projectDir.getName(), projectDirPath, new NewProjectImportGradleSyncListener() {
+ importProject(projectDir.getName(), projectDirPath, true, new NewProjectImportGradleSyncListener() {
@Override
public void syncSucceeded(@NotNull Project project) {
activateProjectView(project);
}
- }, parentProject);
+ }, parentProject, null);
}
catch (Exception e) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
@@ -347,7 +234,7 @@
@Override
public void run() {
try {
- doRequestSync(project, ProgressExecutionMode.MODAL_SYNC, generateSourcesOnSuccess, listener);
+ doRequestSync(project, MODAL_SYNC, generateSourcesOnSuccess, listener);
}
catch (ConfigurationException e) {
Messages.showErrorDialog(project, e.getMessage(), e.getTitle());
@@ -365,7 +252,7 @@
FileDocumentManager.getInstance().saveAllDocuments();
setUpGradleSettings(project);
resetProject(project);
- doImport(project, true /* validate */, false /* existing project */, progressExecutionMode, generateSourcesOnSuccess, listener);
+ doImport(project, false /* existing project */, progressExecutionMode, generateSourcesOnSuccess, listener);
}
else {
Runnable notificationTask = new Runnable() {
@@ -428,32 +315,22 @@
* Imports and opens an Android project that has been created with the "New Project" wizard. This method does not perform any project
* validation before importing the project (assuming that the wizard properly created the new project.)
*
- * @param projectName name of the project.
- * @param projectRootDir root directory of the project.
- * @param listener called after the project has been imported.
+ * @param projectName name of the project.
+ * @param projectRootDir root directory of the project.
+ * @param listener called after the project has been imported.
+ * @param project the given project. This method does nothing if the project is not an Android-Gradle project.
+ * @param initialLanguageLevel when creating a new project, sets the language level to the given version early on (this is because you
+ * cannot set a language level later on in the process without telling the user that the language level
+ * has changed and to re-open the project)
* @throws IOException if any file I/O operation fails (e.g. creating the '.idea' directory.)
* @throws ConfigurationException if any required configuration option is missing (e.g. Gradle home directory path.)
*/
- public void importNewlyCreatedProject(@NotNull String projectName, @NotNull File projectRootDir, @Nullable GradleSyncListener listener)
- throws IOException, ConfigurationException {
- doImport(projectName, projectRootDir, false /* do not validate */, true, listener, null, null);
- }
-
- /**
- * Imports and opens an Android project.
- *
- * @param projectName name of the project.
- * @param projectRootDir root directory of the project.
- * @param listener called after the project has been imported.
- * @param project the given project. This method does nothing if the project is not an Android-Gradle project.
- * @throws IOException if any file I/O operation fails (e.g. creating the '.idea' directory.)
- * @throws ConfigurationException if any required configuration option is missing (e.g. Gradle home directory path.)
- */
- public void importProject(@NotNull String projectName,
- @NotNull File projectRootDir,
- @Nullable GradleSyncListener listener,
- @Nullable Project project) throws IOException, ConfigurationException {
- importProject(projectName, projectRootDir, true, listener, project, null);
+ public void importNewlyCreatedProject(@NotNull String projectName,
+ @NotNull File projectRootDir,
+ @Nullable GradleSyncListener listener,
+ @Nullable Project project,
+ @Nullable LanguageLevel initialLanguageLevel) throws IOException, ConfigurationException {
+ doImport(projectName, projectRootDir, true, listener, project, initialLanguageLevel);
}
/**
@@ -476,12 +353,11 @@
@Nullable GradleSyncListener listener,
@Nullable Project project,
@Nullable LanguageLevel initialLanguageLevel) throws IOException, ConfigurationException {
- doImport(projectName, projectRootDir, true, generateSourcesOnSuccess, listener, project, initialLanguageLevel);
+ doImport(projectName, projectRootDir, generateSourcesOnSuccess, listener, project, initialLanguageLevel);
}
private void doImport(@NotNull String projectName,
@NotNull File projectRootDir,
- boolean validateBeforeImporting,
boolean generateSourcesOnSuccess,
@Nullable GradleSyncListener listener,
@Nullable Project project,
@@ -496,8 +372,7 @@
newProject.save();
}
- doImport(newProject, validateBeforeImporting, true /* new project */, ProgressExecutionMode.MODAL_SYNC /* synchronous import */,
- generateSourcesOnSuccess, listener);
+ doImport(newProject, true /* new project */, MODAL_SYNC /* synchronous import */, generateSourcesOnSuccess, listener);
}
private static void createTopLevelBuildFileIfNotExisting(@NotNull File projectRootDir) throws IOException {
@@ -599,20 +474,11 @@
}
private void doImport(@NotNull final Project project,
- boolean validateBeforeImporting,
final boolean newProject,
@NotNull final ProgressExecutionMode progressExecutionMode,
boolean generateSourcesOnSuccess,
@Nullable final GradleSyncListener listener) throws ConfigurationException {
- // temporarily disable to check whether this is the cause for broken tests
- if (validateBeforeImporting && !ApplicationManager.getApplication().isUnitTestMode()) {
- if (!ProjectValidator.validate(project, new File(project.getBasePath()))) {
- // The project failed validation. Bail out on Gradle import, but create a top-level module so that the entire project directory
- // contents will show up in the project window and the user can edit files to fix the validation problems.
- NewProjectImportGradleSyncListener.createTopLevelProjectAndOpen(project);
- return;
- }
- }
+ PreSyncChecks.check(project);
if (AndroidStudioSpecificInitializer.isAndroidStudio() && Projects.isDirectGradleInvocationEnabled(project)) {
// We cannot do the same when using JPS. We don't have access to the contents of the Message view used by JPS.
@@ -710,70 +576,6 @@
});
}
- /**
- * Find related modules that should be imported into Android Studio together with the project user chose so it could be built.
- * <p/>
- * Top-level use-cases:
- * 1. If the user selects top-level project (e.g. the one with settings.gradle) Android Studio will import all its subprojects.
- * 2. For leaf projects (ones with build.gradle), Android Studio will import selected project and the projects it depends on.
- *
- * @param sourceProject the destinationProject that user wants to import
- * @param destinationProject destination destinationProject
- * @return mapping from module name to {@link com.intellij.openapi.vfs.VirtualFile} containing module contents. Values will be null
- * if the module location was not found.
- */
- @NotNull
- public Set<ModuleToImport> getRelatedProjects(@NotNull VirtualFile sourceProject, @NotNull Project destinationProject) {
- VirtualFile settingsGradle = sourceProject.findFileByRelativePath(SdkConstants.FN_SETTINGS_GRADLE);
- if (settingsGradle != null) {
- return buildModulesSet(getSubprojects(settingsGradle, destinationProject),
- GradleProjectDependencyParser.newInstance(destinationProject));
- }
- else {
- return getRequiredProjects(sourceProject, destinationProject);
- }
- }
-
- /**
- * Find direct and transitive dependency projects.
- */
- @NotNull
- private static Set<ModuleToImport> getRequiredProjects(VirtualFile sourceProject, Project destinationProject) {
- GradleSiblingLookup subprojectLocations = new GradleSiblingLookup(sourceProject, destinationProject);
- Function<VirtualFile, Iterable<String>> parser = GradleProjectDependencyParser.newInstance(destinationProject);
- Map<String, VirtualFile> modules = Maps.newHashMap();
- List<VirtualFile> toAnalyze = Lists.newLinkedList();
- toAnalyze.add(sourceProject);
-
- while (!toAnalyze.isEmpty()) {
- Set<String> dependencies = Sets.newHashSet(Iterables.concat(Iterables.transform(toAnalyze, parser)));
- Iterable<String> notAnalyzed = Iterables.filter(dependencies, not(in(modules.keySet())));
- // Turns out, Maps#toMap does not allow null values...
- Map<String, VirtualFile> dependencyToLocation = Maps.newHashMap();
- for (String dependency : notAnalyzed) {
- dependencyToLocation.put(dependency, subprojectLocations.apply(dependency));
- }
- modules.putAll(dependencyToLocation);
- toAnalyze = FluentIterable.from(dependencyToLocation.values()).filter(notNull()).toList();
- }
- modules.put(subprojectLocations.getPrimaryProjectName(), sourceProject);
- return buildModulesSet(modules, parser);
- }
-
- private static Set<ModuleToImport> buildModulesSet(Map<String, VirtualFile> modules, Function<VirtualFile, Iterable<String>> parser) {
- Set<ModuleToImport> modulesSet = new HashSet<ModuleToImport>(modules.size());
- for (Map.Entry<String, VirtualFile> entry : modules.entrySet()) {
- modulesSet.add(new ModuleToImport(entry.getKey(), entry.getValue(), Suppliers.compose(parser, Suppliers.ofInstance(entry.getValue()))));
- }
- return modulesSet;
- }
-
- @NotNull
- public static Map<String, VirtualFile> getSubprojects(@NotNull final VirtualFile settingsGradle, Project destinationProject) {
- final GradleSettingsFile settingsFile = new GradleSettingsFile(settingsGradle, destinationProject);
- Map<String, File> allProjects = settingsFile.getModulesWithLocation();
- return Maps.transformValues(allProjects, new ResolvePath(VfsUtilCore.virtualToIoFile(settingsGradle.getParent())));
- }
// Makes it possible to mock invocations to the Gradle Tooling API.
static class ImporterDelegate {
@@ -791,23 +593,4 @@
}
}
}
-
- /**
- * Functor that resolves paths that may be either relative to provided directory or absolute.
- */
- private static class ResolvePath implements Function<File, VirtualFile> {
- private final File mySourceDir;
-
- public ResolvePath(File sourceDir) {
- mySourceDir = sourceDir;
- }
-
- @Override
- public VirtualFile apply(File path) {
- if (!path.isAbsolute()) {
- path = new File(mySourceDir, path.getPath());
- }
- return VfsUtil.findFileByIoFile(path, true);
- }
- }
}
diff --git a/android/src/com/android/tools/idea/gradle/project/GradleSiblingLookup.java b/android/src/com/android/tools/idea/gradle/project/GradleSiblingLookup.java
index d775193..297e0f7 100644
--- a/android/src/com/android/tools/idea/gradle/project/GradleSiblingLookup.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleSiblingLookup.java
@@ -58,7 +58,7 @@
return findSiblings(directory.getParent(), project, seen);
}
else {
- return GradleProjectImporter.getSubprojects(settings, project);
+ return GradleModuleImporter.getSubProjects(settings, project);
}
}
}
diff --git a/android/src/com/android/tools/idea/gradle/project/PreSyncChecks.java b/android/src/com/android/tools/idea/gradle/project/PreSyncChecks.java
new file mode 100644
index 0000000..8b20072
--- /dev/null
+++ b/android/src/com/android/tools/idea/gradle/project/PreSyncChecks.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2014 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;
+
+import com.android.SdkConstants;
+import com.android.sdklib.repository.FullRevision;
+import com.android.tools.idea.gradle.util.GradleUtil;
+import com.android.tools.idea.gradle.util.PropertiesUtil;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Processor;
+import org.gradle.wrapper.WrapperExecutor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.gradle.settings.DistributionType;
+import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+final class PreSyncChecks {
+ private static final Pattern ANDROID_GRADLE_PLUGIN_DEPENDENCY_PATTERN = Pattern.compile("['\"]com.android.tools.build:gradle:(.+)['\"]");
+ private static final Pattern GRADLE_DISTRIBUTION_URL_PATTERN =
+ Pattern.compile("http://services\\.gradle\\.org/distributions/gradle-(.+)-(.+)\\.zip");
+
+ private static final Logger LOG = Logger.getInstance(PreSyncChecks.class);
+
+ private static final String MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13 = "2.0";
+ private static final FullRevision MINIMUM_SUPPORTED_GRADLE_REVISION_FOR_PLUGIN_0_13 =
+ FullRevision.parseRevision(MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13);
+
+ private PreSyncChecks() {
+ }
+
+ static void check(@NotNull Project project) {
+ VirtualFile baseDir = project.getBaseDir();
+ if (baseDir == null) {
+ return;
+ }
+ final List<File> filesToProcess = Lists.newArrayList();
+ VfsUtil.processFileRecursivelyWithoutIgnored(baseDir, new Processor<VirtualFile>() {
+ @Override
+ public boolean process(VirtualFile virtualFile) {
+ if (SdkConstants.FN_BUILD_GRADLE.equals(virtualFile.getName())) {
+ filesToProcess.add(VfsUtilCore.virtualToIoFile(virtualFile));
+ }
+ return true;
+ }
+ });
+
+ attemptToUpdateGradleVersionIfApplicable(project, filesToProcess);
+ }
+
+ private static void attemptToUpdateGradleVersionIfApplicable(@NotNull Project project, @NotNull List<File> gradleFiles) {
+ String originalPluginVersion = null;
+ for (File fileToCheck : gradleFiles) {
+ if (SdkConstants.FN_BUILD_GRADLE.equals(fileToCheck.getName())) {
+ try {
+ String contents = Files.toString(fileToCheck, Charsets.UTF_8);
+ Matcher matcher = ANDROID_GRADLE_PLUGIN_DEPENDENCY_PATTERN.matcher(contents);
+ if (matcher.find()) {
+ originalPluginVersion = matcher.group(1);
+ if (!StringUtil.isEmpty(originalPluginVersion)) {
+ break;
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.warn("Failed to read contents of " + fileToCheck.getPath());
+ }
+ }
+ }
+ if (StringUtil.isEmpty(originalPluginVersion)) {
+ // Could not obtain plug-in version. Continue with sync.
+ return;
+ }
+ String pluginVersion = originalPluginVersion.replace('+', '0');
+ FullRevision pluginRevision = null;
+ try {
+ pluginRevision = FullRevision.parseRevision(pluginVersion);
+ }
+ catch (NumberFormatException e) {
+ LOG.warn("Failed to parse '" + pluginVersion + "'");
+ }
+ if (pluginRevision == null || (pluginRevision.getMajor() == 0 && pluginRevision.getMinor() <= 12)) {
+ // Unable to parse the plug-in version, or the plug-in is version 0.12 or older. Continue with sync.
+ return;
+ }
+
+ GradleProjectSettings gradleSettings = GradleUtil.getGradleProjectSettings(project);
+ File wrapperPropertiesFile = GradleUtil.findWrapperPropertiesFile(project);
+
+ DistributionType distributionType = gradleSettings != null ? gradleSettings.getDistributionType() : null;
+
+ boolean usingWrapper = distributionType == DistributionType.DEFAULT_WRAPPED && wrapperPropertiesFile != null;
+ if (usingWrapper) {
+ attemptToUpdateGradleVersionInWrapper(wrapperPropertiesFile, originalPluginVersion, project);
+ }
+ else if (distributionType == DistributionType.LOCAL) {
+ attemptToUseSupportedLocalGradle(gradleSettings);
+ }
+ }
+
+ private static void attemptToUpdateGradleVersionInWrapper(@NotNull final File wrapperPropertiesFile,
+ @NotNull String pluginVersion,
+ @NotNull Project project) {
+ Properties wrapperProperties = null;
+ try {
+ wrapperProperties = PropertiesUtil.getProperties(wrapperPropertiesFile);
+ }
+ catch (IOException e) {
+ LOG.warn("Failed to read file " + wrapperPropertiesFile.getPath());
+ }
+
+ if (wrapperProperties == null) {
+ // There is a wrapper, but the Gradle version could not be read. Continue with sync.
+ return;
+ }
+ String url = wrapperProperties.getProperty(WrapperExecutor.DISTRIBUTION_URL_PROPERTY);
+ Matcher matcher = GRADLE_DISTRIBUTION_URL_PATTERN.matcher(url);
+ if (!matcher.matches()) {
+ // Could not get URL of Gradle distribution. Continue with sync.
+ return;
+ }
+ String gradleVersion = matcher.group(1);
+ FullRevision gradleRevision = FullRevision.parseRevision(gradleVersion);
+
+ if (!isSupportedGradleVersion(gradleRevision)) {
+ String newGradleVersion = MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13;
+ String msg = "Version " + pluginVersion + " of the Android Gradle plug-in requires Gradle " + newGradleVersion + " or newer.\n\n" +
+ "Click 'OK' to automatically update the Gradle version in the Gradle wrapper and continue.";
+ Messages.showMessageDialog(project, msg, "Gradle Sync", Messages.getQuestionIcon());
+ try {
+ GradleUtil.updateGradleDistributionUrl(newGradleVersion, wrapperPropertiesFile);
+ }
+ catch (IOException e) {
+ LOG.warn("Failed to update Gradle wrapper file to Gradle version " + newGradleVersion);
+ }
+ }
+ }
+
+ private static void attemptToUseSupportedLocalGradle(@NotNull GradleProjectSettings gradleSettings) {
+ String gradleHome = gradleSettings.getGradleHome();
+ if (StringUtil.isEmpty(gradleHome)) {
+ // Unable to obtain the path of the Gradle local installation. Continue with sync.
+ return;
+ }
+ File gradleHomePath = new File(gradleHome);
+ FullRevision gradleVersion = GradleUtil.getGradleVersion(gradleHomePath);
+
+ if (gradleVersion == null) {
+ // Unable to obtain the path of the Gradle local installation. Continue with sync.
+ return;
+ }
+
+ if (!isSupportedGradleVersion(gradleVersion)) {
+ String newGradleVersion = MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13;
+ ChooseGradleHomeDialog dialog = new ChooseGradleHomeDialog(newGradleVersion);
+ dialog.setTitle(String.format("Please select the location of a Gradle distribution version %1$s or newer", newGradleVersion));
+ if (dialog.showAndGet()) {
+ String enteredGradleHomePath = dialog.getEnteredGradleHomePath();
+ gradleSettings.setGradleHome(enteredGradleHomePath);
+ }
+ }
+ }
+
+ private static boolean isSupportedGradleVersion(@NotNull FullRevision gradleVersion) {
+ // Plug-in v0.13.+ supports Gradle 2.0+ only.
+ return gradleVersion.compareTo(MINIMUM_SUPPORTED_GRADLE_REVISION_FOR_PLUGIN_0_13) >= 0;
+ }
+}
diff --git a/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java b/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java
deleted file mode 100644
index 381f5a2..0000000
--- a/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Copyright (C) 2014 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;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.repository.FullRevision;
-import com.android.tools.idea.gradle.messages.Message;
-import com.android.tools.idea.gradle.messages.ProjectSyncMessages;
-import com.android.tools.idea.gradle.util.GradleUtil;
-import com.android.tools.idea.gradle.util.PropertiesUtil;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.LintRequest;
-import com.android.tools.lint.detector.api.*;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.Key;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.vfs.VfsUtilCore;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiDocumentManager;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiManager;
-import com.intellij.util.Processor;
-import org.gradle.wrapper.WrapperExecutor;
-import org.jetbrains.android.inspections.lint.IntellijLintClient;
-import org.jetbrains.android.inspections.lint.IntellijLintIssueRegistry;
-import org.jetbrains.android.inspections.lint.IntellijLintRequest;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.plugins.gradle.settings.DistributionType;
-import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class ProjectValidator {
- private static final Pattern ANDROID_GRADLE_PLUGIN_DEPENDENCY_PATTERN = Pattern.compile("['\"]com.android.tools.build:gradle:(.+)['\"]");
- private static final Pattern GRADLE_DISTRIBUTION_URL_PATTERN =
- Pattern.compile("http://services\\.gradle\\.org/distributions/gradle-(.+)-(.+)\\.zip");
-
- private static final Logger LOG = Logger.getInstance(ProjectValidator.class);
-
- public static final Key<List<Message>> VALIDATION_MESSAGES = Key.create("gradle.validation.messages");
-
- private static final Set<String> FILES_TO_PROCESS =
- ImmutableSet.of(SdkConstants.FN_SETTINGS_GRADLE, SdkConstants.FN_BUILD_GRADLE, SdkConstants.FN_LOCAL_PROPERTIES,
- SdkConstants.FN_GRADLE_WRAPPER_PROPERTIES);
-
- private static final String MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13 = "2.0";
- private static final FullRevision MINIMUM_SUPPORTED_GRADLE_REVISION_FOR_PLUGIN_0_13 =
- FullRevision.parseRevision(MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13);
-
- private ProjectValidator() {
- }
-
- /**
- * Runs lint checks on all the Gradle files found underneath the given root directory and reports messages to the
- * {@link ProjectSyncMessages} message window. Returns true if there are no significant problems, or false if there are critical errors.
- * Any errors or warnings that are generated are saved, and are send to the Messages window upon a subsequent call to the
- * {@link #mergeQueuedMessages(com.intellij.openapi.project.Project)} method.
- */
- public static boolean validate(@NotNull Project project, @NotNull File rootDir) {
- VirtualFile file = VfsUtil.findFileByIoFile(rootDir, true);
- if (file == null) {
- return false;
- }
- VirtualFile rootDirectory = file.isDirectory() ? file : file.getParent();
-
- final List<File> filesToProcess = Lists.newArrayList();
- VfsUtil.processFileRecursivelyWithoutIgnored(rootDirectory, new Processor<VirtualFile>() {
- @Override
- public boolean process(VirtualFile virtualFile) {
- if (FILES_TO_PROCESS.contains(virtualFile.getName().toLowerCase())) {
- filesToProcess.add(VfsUtilCore.virtualToIoFile(virtualFile));
- }
- return true;
- }
- });
-
- attemptToUpdateGradleVersionIfApplicable(project, filesToProcess);
-
- if (project.isInitialized()) {
- // Lint requires the project to be initialized.
- MyLintClient lintClient = new MyLintClient(project);
- ImmutableList<Module> modules = ImmutableList.of();
- LintRequest request = new IntellijLintRequest(lintClient, project, null, modules, false) {
- @NonNull
- @Override
- public List<File> getFiles() {
- return filesToProcess;
- }
- };
- LintDriver lintDriver = new LintDriver(new IntellijLintIssueRegistry(), lintClient);
- lintDriver.analyze(request);
- return !lintClient.hasFatalError();
- }
-
- return true;
- }
-
- private static void attemptToUpdateGradleVersionIfApplicable(@NotNull Project project, @NotNull List<File> gradleFiles) {
- String originalPluginVersion = null;
- for (File fileToCheck : gradleFiles) {
- if (SdkConstants.FN_BUILD_GRADLE.equals(fileToCheck.getName())) {
- try {
- String contents = Files.toString(fileToCheck, Charsets.UTF_8);
- Matcher matcher = ANDROID_GRADLE_PLUGIN_DEPENDENCY_PATTERN.matcher(contents);
- if (matcher.find()) {
- originalPluginVersion = matcher.group(1);
- if (!StringUtil.isEmpty(originalPluginVersion)) {
- break;
- }
- }
- }
- catch (IOException e) {
- LOG.warn("Failed to read contents of " + fileToCheck.getPath());
- }
- }
- }
- if (StringUtil.isEmpty(originalPluginVersion)) {
- // Could not obtain plug-in version. Continue with sync.
- return;
- }
- String pluginVersion = originalPluginVersion.replace('+', '0');
- FullRevision pluginRevision = null;
- try {
- pluginRevision = FullRevision.parseRevision(pluginVersion);
- }
- catch (NumberFormatException e) {
- LOG.warn("Failed to parse '" + pluginVersion + "'");
- }
- if (pluginRevision == null || (pluginRevision.getMajor() == 0 && pluginRevision.getMinor() <= 12)) {
- // Unable to parse the plug-in version, or the plug-in is version 0.12 or older. Continue with sync.
- return;
- }
-
- GradleProjectSettings gradleSettings = GradleUtil.getGradleProjectSettings(project);
- File wrapperPropertiesFile = GradleUtil.findWrapperPropertiesFile(project);
-
- DistributionType distributionType = gradleSettings != null ? gradleSettings.getDistributionType() : null;
-
- boolean usingWrapper = distributionType == DistributionType.DEFAULT_WRAPPED && wrapperPropertiesFile != null;
- if (usingWrapper) {
- attemptToUpdateGradleVersionInWrapper(wrapperPropertiesFile, originalPluginVersion, project);
- }
- else if (distributionType == DistributionType.LOCAL) {
- attemptToUseSupportedLocalGradle(gradleSettings);
- }
- }
-
- private static void attemptToUpdateGradleVersionInWrapper(@NotNull final File wrapperPropertiesFile,
- @NotNull String pluginVersion,
- @NotNull Project project) {
- Properties wrapperProperties = null;
- try {
- wrapperProperties = PropertiesUtil.getProperties(wrapperPropertiesFile);
- }
- catch (IOException e) {
- LOG.warn("Failed to read file " + wrapperPropertiesFile.getPath());
- }
-
- if (wrapperProperties == null) {
- // There is a wrapper, but the Gradle version could not be read. Continue with sync.
- return;
- }
- String url = wrapperProperties.getProperty(WrapperExecutor.DISTRIBUTION_URL_PROPERTY);
- Matcher matcher = GRADLE_DISTRIBUTION_URL_PATTERN.matcher(url);
- if (!matcher.matches()) {
- // Could not get URL of Gradle distribution. Continue with sync.
- return;
- }
- String gradleVersion = matcher.group(1);
- FullRevision gradleRevision = FullRevision.parseRevision(gradleVersion);
-
- if (!isSupportedGradleVersion(gradleRevision)) {
- String newGradleVersion = MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13;
- String msg = "Version " + pluginVersion + " of the Android Gradle plug-in requires Gradle " + newGradleVersion + " or newer.\n\n" +
- "Click 'OK' to automatically update the Gradle version in the Gradle wrapper and continue.";
- Messages.showMessageDialog(project, msg, "Gradle Sync", Messages.getQuestionIcon());
- try {
- GradleUtil.updateGradleDistributionUrl(newGradleVersion, wrapperPropertiesFile);
- }
- catch (IOException e) {
- LOG.warn("Failed to update Gradle wrapper file to Gradle version " + newGradleVersion);
- }
- }
- }
-
- private static void attemptToUseSupportedLocalGradle(@NotNull GradleProjectSettings gradleSettings) {
- String gradleHome = gradleSettings.getGradleHome();
- if (StringUtil.isEmpty(gradleHome)) {
- // Unable to obtain the path of the Gradle local installation. Continue with sync.
- return;
- }
- File gradleHomePath = new File(gradleHome);
- FullRevision gradleVersion = GradleUtil.getGradleVersion(gradleHomePath);
-
- if (gradleVersion == null) {
- // Unable to obtain the path of the Gradle local installation. Continue with sync.
- return;
- }
-
- if (!isSupportedGradleVersion(gradleVersion)) {
- String newGradleVersion = MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13;
- ChooseGradleHomeDialog dialog = new ChooseGradleHomeDialog(newGradleVersion);
- dialog.setTitle(String.format("Please select the location of a Gradle distribution version %1$s or newer", newGradleVersion));
- if (dialog.showAndGet()) {
- String enteredGradleHomePath = dialog.getEnteredGradleHomePath();
- gradleSettings.setGradleHome(enteredGradleHomePath);
- }
- }
- }
-
- private static boolean isSupportedGradleVersion(@NotNull FullRevision gradleVersion) {
- // Plug-in v0.13.+ supports Gradle 2.0+ only.
- return gradleVersion.compareTo(MINIMUM_SUPPORTED_GRADLE_REVISION_FOR_PLUGIN_0_13) >= 0;
- }
-
- /**
- * Takes any accumulated validation errors that have been stored with the project and shows them in the Messages tool window.
- */
- public static void mergeQueuedMessages(@NotNull Project project) {
- List<Message> messages = project.getUserData(VALIDATION_MESSAGES);
- if (messages == null) {
- return;
- }
- ProjectSyncMessages projectSyncMessages = ProjectSyncMessages.getInstance(project);
- for (Message message : messages) {
- projectSyncMessages.add(message);
- }
- project.putUserData(VALIDATION_MESSAGES, null);
- }
-
- private static class MyLintClient extends IntellijLintClient {
- private static final String GROUP_NAME = "Project import";
- private boolean myFatalError = false;
- private final List<Message> myMessages = Lists.newArrayList();
-
- protected MyLintClient(@NonNull Project project) {
- super(project);
- project.putUserData(VALIDATION_MESSAGES, myMessages);
- }
-
- @Override
- public void report(@NonNull Context context,
- @NonNull Issue issue,
- @NonNull Severity severity,
- @Nullable Location location,
- @NonNull String message,
- @Nullable Object data) {
- myFatalError |= severity.compareTo(Severity.ERROR) <= 0;
-
- File file = location != null ? location.getFile() : null;
- VirtualFile virtualFile = file != null ? LocalFileSystem.getInstance().findFileByIoFile(file) : null;
- Position start = location != null ? location.getStart() : null;
- Message.Type type = convertSeverity(severity);
- if (virtualFile != null && start != null) {
- try {
- int line = start.getLine();
- int column = start.getColumn();
- int offset = start.getOffset();
- if (line == -1 && offset >= 0) {
- PsiManager psiManager = PsiManager.getInstance(myProject);
- PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(myProject);
- PsiFile psiFile = psiManager.findFile(virtualFile);
- if (psiFile != null) {
- Document document = psiDocumentManager.getDocument(psiFile);
- if (document != null) {
- line = document.getLineNumber(offset);
- column = offset - document.getLineStartOffset(line);
- }
- }
- }
- myMessages.add(new Message(myProject, GROUP_NAME, type, virtualFile, line, column, message));
- }
- catch (Exception e) {
- // There are cases where the offset lookup is wrong; e.g.
- // java.lang.IndexOutOfBoundsException: Wrong offset: 312. Should be in range: [0, 16]
- // in this case, just report the message without a location
- myMessages.add(new Message(GROUP_NAME, type, message));
- }
- }
- else {
- myMessages.add(new Message(GROUP_NAME, type, message));
- }
- }
-
- @Override
- public void log(@NonNull Severity severity, @Nullable Throwable exception, @Nullable String format, @Nullable Object... args) {
- myMessages.add(new Message(GROUP_NAME, convertSeverity(severity), format != null ? String.format(format, args) : ""));
- if (exception != null) {
- LOG.warn("Exception occurred during validation of project", exception);
- }
- }
-
- @NonNull
- private static Message.Type convertSeverity(@NonNull Severity severity) {
- switch (severity) {
- case ERROR:
- case FATAL:
- return Message.Type.ERROR;
- case IGNORE:
- return Message.Type.INFO;
- default:
- case INFORMATIONAL:
- return Message.Type.INFO;
- case WARNING:
- return Message.Type.WARNING;
- }
- }
-
- public boolean hasFatalError() {
- return myFatalError;
- }
-
- @NonNull
- @Override
- protected List<Issue> getIssues() {
- return new IntellijLintIssueRegistry().getIssues();
- }
-
- @Nullable
- @Override
- protected Module getModule() {
- return null;
- }
-
- @Override
- public boolean isProjectDirectory(@NonNull File dir) {
- return new File(dir, SdkConstants.FN_BUILD_GRADLE).exists();
- }
- }
-}
diff --git a/android/src/com/android/tools/idea/wizard/NewProjectWizard.java b/android/src/com/android/tools/idea/wizard/NewProjectWizard.java
index 3b01d6e..69bd51a 100644
--- a/android/src/com/android/tools/idea/wizard/NewProjectWizard.java
+++ b/android/src/com/android/tools/idea/wizard/NewProjectWizard.java
@@ -186,7 +186,7 @@
if (version != null) {
initialLanguageLevel = LanguageLevel.parse(version.toString());
}
- projectImporter.importProject(projectName, projectRoot, true, new NewProjectImportGradleSyncListener() {
+ projectImporter.importNewlyCreatedProject(projectName, projectRoot, new NewProjectImportGradleSyncListener() {
@Override
public void syncSucceeded(@NotNull final Project project) {
// Open files -- but wait until the Android facets are available, otherwise for example
diff --git a/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java b/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java
index 90edf80..18973e3 100644
--- a/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java
+++ b/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java
@@ -158,7 +158,7 @@
private boolean openTemplateFiles(Project project) {
return TemplateUtils.openEditors(project, myFilesToOpen, true);
}
- });
+ }, null, null);
}
catch (IOException e) {
Messages.showErrorDialog(e.getMessage(), ERROR_MSG_TITLE);
diff --git a/android/testSrc/com/android/tools/idea/gradle/project/GradleModuleImportTest.java b/android/testSrc/com/android/tools/idea/gradle/project/GradleModuleImportTest.java
index 22fc357..d6973e1 100644
--- a/android/testSrc/com/android/tools/idea/gradle/project/GradleModuleImportTest.java
+++ b/android/testSrc/com/android/tools/idea/gradle/project/GradleModuleImportTest.java
@@ -216,7 +216,7 @@
*/
public void testImportSimpleGradleProject() throws IOException, ConfigurationException {
VirtualFile moduleRoot = createGradleProjectToImport(dir, MODULE_NAME);
- GradleProjectImporter.getInstance().importModules(Collections.singletonMap(moduleRoot.getName(), moduleRoot), getProject(), null);
+ GradleModuleImporter.importModules(this, Collections.singletonMap(moduleRoot.getName(), moduleRoot), getProject(), null);
assertModuleImported(getProject(), MODULE_NAME, moduleRoot);
}
@@ -227,14 +227,13 @@
String[] paths = {module(1), module(2), SAMPLE_PROJECT_PATH};
VirtualFile projectRoot = createProjectWithSubprojects(projectsWithDefaultLocations(paths));
- GradleProjectImporter importer = GradleProjectImporter.getInstance();
- Map<String, VirtualFile> toImport = moduleListToMap(importer.getRelatedProjects(projectRoot, getProject()));
+ Map<String, VirtualFile> toImport = moduleListToMap(GradleModuleImporter.getRelatedProjects(projectRoot, getProject()));
assertEquals(paths.length, toImport.size());
for (String path : paths) {
assertEquals(projectRoot.findFileByRelativePath(path), toImport.get(pathToGradleName(path)));
}
- importer.importModules(toImport, getProject(), null);
+ GradleModuleImporter.importModules(this, toImport, getProject(), null);
for (String path : paths) {
VirtualFile moduleRoot = projectRoot.findFileByRelativePath(path);
@@ -246,17 +245,16 @@
}
/**
- * Missing submodule will be on the list but with a <code>null</code> path. It is up to client code to decide what to do with it.
+ * Missing sub-module will be on the list but with a <code>null</code> path. It is up to client code to decide what to do with it.
*/
- public void testImportSubprojectsWithMissingSubmodule() throws IOException, ConfigurationException {
+ public void testImportSubProjectsWithMissingSubModule() throws IOException, ConfigurationException {
VirtualFile projectRoot = createProjectWithSubprojects(projectsWithDefaultLocations(module(1)), module(2));
- GradleProjectImporter importer = GradleProjectImporter.getInstance();
- Map<String, VirtualFile> toImport = moduleListToMap(importer.getRelatedProjects(projectRoot, getProject()));
+ Map<String, VirtualFile> toImport = moduleListToMap(GradleModuleImporter.getRelatedProjects(projectRoot, getProject()));
assertEquals(2, toImport.size());
assertModuleRequiredButNotFound(module(2), toImport);
try {
- importer.importModules(toImport, getProject(), null);
+ GradleModuleImporter.importModules(this, toImport, getProject(), null);
fail();
}
catch (IOException e) {
@@ -267,17 +265,16 @@
/**
* Verify discovery of projects with non-default locations
*/
- public void testImportSubprojectWithCustomLocation() throws IOException, ConfigurationException {
+ public void testImportSubProjectWithCustomLocation() throws IOException, ConfigurationException {
VirtualFile projectRoot =
createProjectWithSubprojects(Collections.singletonMap(pathToGradleName(SAMPLE_PROJECT_NAME), SAMPLE_PROJECT_PATH));
- GradleProjectImporter importer = GradleProjectImporter.getInstance();
- Map<String, VirtualFile> subprojects = moduleListToMap(importer.getRelatedProjects(projectRoot, getProject()));
- assertEquals(1, subprojects.size());
+ Map<String, VirtualFile> subProjects = moduleListToMap(GradleModuleImporter.getRelatedProjects(projectRoot, getProject()));
+ assertEquals(1, subProjects.size());
VirtualFile moduleLocation = projectRoot.findFileByRelativePath(SAMPLE_PROJECT_PATH);
assert moduleLocation != null;
- assertEquals(moduleLocation, subprojects.get(pathToGradleName(SAMPLE_PROJECT_NAME)));
+ assertEquals(moduleLocation, subProjects.get(pathToGradleName(SAMPLE_PROJECT_NAME)));
- importer.importModules(subprojects, getProject(), null);
+ GradleModuleImporter.importModules(this, subProjects, getProject(), null);
assertModuleImported(getProject(), SAMPLE_PROJECT_NAME, moduleLocation);
}
@@ -298,7 +295,7 @@
assert project1 != null && project2 != null : "Something wrong with the setup";
configureTopLevelProject(dir, Arrays.asList(module(1), module(2)), Collections.<String>emptySet());
- Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(project2, getProject()));
+ Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project2, getProject()));
assertEquals(2, projects.size());
assertEquals(project1, projects.get(pathToGradleName(module(1))));
assertEquals(project2, projects.get(pathToGradleName(module(2))));
@@ -312,7 +309,7 @@
assert project2 != null : "Something wrong with the setup";
configureTopLevelProject(dir, Arrays.asList(module(1), module(2)), Collections.<String>emptySet());
- Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(project2, getProject()));
+ Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project2, getProject()));
assertEquals(2, projects.size());
assertModuleRequiredButNotFound(module(1), projects);
assertEquals(project2, projects.get(pathToGradleName(module(2))));
@@ -325,7 +322,7 @@
VirtualFile module = createGradleProjectToImport(dir, module(1), module(2));
assert module != null;
- Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(module, getProject()));
+ Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(module, getProject()));
assertEquals(2, projects.size());
assertModuleRequiredButNotFound(module(2), projects);
assertEquals(module, projects.get(pathToGradleName(module(1))));
@@ -340,7 +337,7 @@
VirtualFile project3 = createGradleProjectToImport(dir, module(3), module(2));
configureTopLevelProject(dir, Arrays.asList(module(1), module(2), module(3)), Collections.<String>emptySet());
- Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(project3, getProject()));
+ Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project3, getProject()));
assertEquals(3, projects.size());
assertEquals(project1, projects.get(pathToGradleName(module(1))));
assertEquals(project2, projects.get(pathToGradleName(module(2))));
@@ -356,7 +353,7 @@
VirtualFile project3 = createGradleProjectToImport(dir, module(3), module(2));
configureTopLevelProject(dir, Arrays.asList(module(1), module(2), module(3)), Collections.<String>emptySet());
- Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(project3, getProject()));
+ Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project3, getProject()));
assertEquals(3, projects.size());
assertEquals(project1, projects.get(pathToGradleName(module(1))));
assertEquals(project2, projects.get(pathToGradleName(module(2))));
@@ -385,12 +382,11 @@
return moduleWithDependency;
}
});
- GradleProjectImporter importer = GradleProjectImporter.getInstance();
- Map<String, VirtualFile> importInput = moduleListToMap(importer.getRelatedProjects(module, getProject()));
+ Map<String, VirtualFile> importInput = moduleListToMap(GradleModuleImporter.getRelatedProjects(module, getProject()));
assertEquals(2, importInput.size());
assertEquals(module, importInput.get(pathToGradleName(module(2))));
- importer.importModules(importInput, getProject(), null);
+ GradleModuleImporter.importModules(this, importInput, getProject(), null);
GradleSettingsFile settingsFile = GradleSettingsFile.get(getProject());
assertNotNull(settingsFile);
diff --git a/android/testSrc/com/android/tools/idea/gradle/project/GradleProjectImporterTest.java b/android/testSrc/com/android/tools/idea/gradle/project/GradleProjectImporterTest.java
index 93055c7..e357916 100644
--- a/android/testSrc/com/android/tools/idea/gradle/project/GradleProjectImporterTest.java
+++ b/android/testSrc/com/android/tools/idea/gradle/project/GradleProjectImporterTest.java
@@ -87,7 +87,7 @@
public void testImportNewlyCreatedProject() throws Exception {
MyGradleSyncListener callback = new MyGradleSyncListener();
- myImporter.importNewlyCreatedProject(myProjectName, myProjectRootDir, callback);
+ myImporter.importNewlyCreatedProject(myProjectName, myProjectRootDir, callback, null, null);
}
private class MyGradleSyncListener extends GradleSyncListener.Adapter {