diff options
author | Fuyao Zhao <fuyaoz@google.com> | 2015-08-04 01:24:52 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2015-08-04 01:24:52 +0000 |
commit | bf7d61f5bf517e687c35401457392f8f5a6c200f (patch) | |
tree | a84190758ee434af95709c2bbea146b2a230aa6a /android | |
parent | a8293775d548da831993be955249a60fea8c5984 (diff) | |
parent | 59f98028d2018cfe553e786e3d367cfdcb5917b8 (diff) | |
download | idea-bf7d61f5bf517e687c35401457392f8f5a6c200f.tar.gz |
Merge "Implement gradle aware 'Add library depedency'." into studio-1.4-dev
Diffstat (limited to 'android')
3 files changed, 290 insertions, 9 deletions
diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/gradle/AddGradleDependencyTest.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/gradle/AddGradleDependencyTest.java index 964c264d3d2..ff70110236c 100644 --- a/android/guiTestSrc/com/android/tools/idea/tests/gui/gradle/AddGradleDependencyTest.java +++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/gradle/AddGradleDependencyTest.java @@ -27,8 +27,12 @@ import org.junit.Test; import java.io.File; import java.io.IOException; +import static com.android.SdkConstants.FN_BUILD_GRADLE; import static com.android.tools.idea.tests.gui.framework.TestGroup.PROJECT_SUPPORT; +import static com.intellij.openapi.util.io.FileUtil.appendToFile; +import static com.intellij.openapi.util.io.FileUtil.join; import static com.intellij.vcsUtil.VcsUtil.getFileContent; +import static org.fest.assertions.Assertions.assertThat; import static org.junit.Assert.assertTrue; @BelongsToTestGroups({PROJECT_SUPPORT}) @@ -57,6 +61,38 @@ public class AddGradleDependencyTest extends GuiTestCase { assertBuildFileContains(projectFrame, "app/build.gradle", "androidTestCompile project(':library3')"); } + @Test @IdeGuiTest + public void testAddLibDependencyDeclaredInJavaProject() throws IOException { + IdeFrameFixture projectFrame = importProjectAndWaitForProjectSyncToFinish("MultiModule"); + File buildFile = new File(projectFrame.getProjectPath(), join("library3", FN_BUILD_GRADLE)); + assertThat(buildFile).isFile(); + appendToFile(buildFile, "dependencies { compile 'com.google.guava:guava:18.0' }"); + projectFrame.requestProjectSync(); + + EditorFixture editor = projectFrame.getEditor(); + editor.open("app/src/androidTest/java/com/android/multimodule/ApplicationTest.java"); + typeImportAndInvokeAction(projectFrame, "com.android.multimodule;\n^","import com.google.common.base.Obje^cts;", + "Add library 'com.google.guava:guava:18.0' to classpath"); + + assertBuildFileContains(projectFrame, "app/build.gradle", "compile 'com.google.guava:guava:18.0'"); + } + + @Test @IdeGuiTest + public void testAddLibDependencyDeclaredInAndroidProject() throws IOException { + IdeFrameFixture projectFrame = importProjectAndWaitForProjectSyncToFinish("MultiModule"); + File buildFile = new File(projectFrame.getProjectPath(), join("app", FN_BUILD_GRADLE)); + assertThat(buildFile).isFile(); + appendToFile(buildFile, "dependencies { compile 'com.google.guava:guava:18.0' }"); + projectFrame.requestProjectSync(); + + EditorFixture editor = projectFrame.getEditor(); + editor.open("library3/src/main/java/com/example/MyLibrary.java"); + typeImportAndInvokeAction(projectFrame, "package com.example;\n^", "import com.google.common.base.Obje^cts;", + "Add library 'com.google.guava:guava:18.0' to classpath"); + + assertBuildFileContains(projectFrame, "app/build.gradle", "compile 'com.google.guava:guava:18.0'"); + } + private static void typeImportAndInvokeAction(@NotNull IdeFrameFixture projectFrame, @NotNull String lineToType, @NotNull String testImportStatement, @NotNull String intention) { EditorFixture editor = projectFrame.getEditor(); diff --git a/android/src/com/android/tools/idea/quickfix/AddGradleLibraryDependencyFix.java b/android/src/com/android/tools/idea/quickfix/AddGradleLibraryDependencyFix.java new file mode 100644 index 00000000000..f4fc7d7aa49 --- /dev/null +++ b/android/src/com/android/tools/idea/quickfix/AddGradleLibraryDependencyFix.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2015 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.quickfix; + +import com.android.builder.model.*; +import com.android.tools.idea.gradle.IdeaAndroidProject; +import com.android.tools.idea.gradle.parser.Dependency; +import com.android.tools.idea.gradle.project.GradleProjectImporter; +import com.android.tools.idea.gradle.project.GradleSyncListener; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.daemon.impl.actions.AddImportAction; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.LibraryOrderEntry; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Quickfix to add dependency to another library in gradle.build file and sync the project. + */ +public class AddGradleLibraryDependencyFix extends GradleDependencyFix { + @NotNull private final LibraryOrderEntry myLibraryEntry; + @NotNull private final Module myCurrentModule; + @NotNull private final PsiClass myClass; + @NotNull private final PsiReference myReference; + @Nullable private final String myLibraryGradleEntry; + + public AddGradleLibraryDependencyFix(@NotNull LibraryOrderEntry libraryEntry, @NotNull Module currentModule, @NotNull PsiClass aCLass, + @NotNull PsiReference reference) { + myLibraryEntry = libraryEntry; + myCurrentModule = currentModule; + myClass = aCLass; + myReference = reference; + myLibraryGradleEntry = getLibraryGradleEntry(); + } + + @Override + @NotNull + public String getText() { + return QuickFixBundle.message("orderEntry.fix.add.library.to.classpath", myLibraryGradleEntry); + } + + @Override + @NotNull + public String getFamilyName() { + return QuickFixBundle.message("orderEntry.fix.family.add.library.to.classpath"); + } + + @Override + public boolean isAvailable(@NotNull Project project, @Nullable Editor editor, @Nullable PsiFile file) { + return !project.isDisposed() && !myCurrentModule.isDisposed() && myLibraryEntry.isValid() && myLibraryGradleEntry != null; + } + + @Override + public void invoke(@NotNull final Project project, @Nullable final Editor editor, @Nullable PsiFile file) { + if (myLibraryGradleEntry == null) { + return; + } + final Dependency dependency = new Dependency(getDependencyScope(myCurrentModule, false), Dependency.Type.EXTERNAL, + myLibraryGradleEntry); + + final Application application = ApplicationManager.getApplication(); + application.invokeAndWait(new Runnable() { + @Override + public void run() { + application.runWriteAction(new Runnable() { + @Override + public void run() { + addDependency(myCurrentModule, dependency); + gradleSyncAndImportClass(myCurrentModule, editor, myReference, new Function<Void, List<PsiClass>>() { + @Override + public List<PsiClass> apply(@Nullable Void input) { + return ImmutableList.of(myClass); + } + }); + } + }); + } + }, application.getDefaultModalityState()); + } + + /** + * Given a library entry, find out its corresponded gradle dependency entry like 'group:name:version". + */ + @Nullable + private String getLibraryGradleEntry() { + AndroidFacet androidFacet = AndroidFacet.getInstance(myLibraryEntry.getOwnerModule()); + + String result = null; + if (androidFacet != null) { + result = getLibraryGradleEntryByAndroidFacet(androidFacet); + } + if (result == null) { + result = getLibraryGradleEntryByExaminingPath(); + } + return result; + } + + @Nullable + private String getLibraryGradleEntryByAndroidFacet(@NotNull AndroidFacet androidFacet) { + IdeaAndroidProject androidProject = androidFacet.getAndroidModel(); + if (androidProject == null) { + return null; + } + + Variant selectedVariant = androidProject.getSelectedVariant(); + BaseArtifact testArtifact = androidProject.findSelectedTestArtifactInSelectedVariant(); + + Library matchedLibrary = null; + if (testArtifact != null) { + matchedLibrary = findMatchedibrary(testArtifact); + } + if (matchedLibrary == null) { + matchedLibrary = findMatchedibrary(selectedVariant.getMainArtifact()); + } + if (matchedLibrary == null) { + return null; + } + + // TODO use getRequestedCoordinates once the interface is fixed. + MavenCoordinates mavenCoordinates = matchedLibrary.getResolvedCoordinates(); + if (mavenCoordinates == null) { + return null; + } + return mavenCoordinates.getGroupId() + ":" + mavenCoordinates.getArtifactId() + ":" + mavenCoordinates.getVersion(); + } + + @Nullable + Library findMatchedibrary(@NotNull BaseArtifact artifact) { + for (JavaLibrary library : artifact.getDependencies().getJavaLibraries()) { + String libraryName = FileUtil.getNameWithoutExtension(library.getJarFile()); + if (libraryName.equals(myLibraryEntry.getLibraryName())) { + return library; + } + } + return null; + } + + /** + * Gradle dependencies are stored in following path: xxx/:groupId/:artifactId/:version/xxx/:artifactId-:version.jar + * therefor, if we can't get the artifact information from model, then try to extract from path. + */ + @Nullable + private String getLibraryGradleEntryByExaminingPath() { + VirtualFile file = myLibraryEntry.getFiles(OrderRootType.CLASSES)[0]; + String libraryName = myLibraryEntry.getLibraryName(); + if (libraryName == null) { + return null; + } + String[] splittedPath = file.getPath().split(System.getProperty("file.separator")); + + for (int i = 1; i < splittedPath.length - 2; i++) { + if (libraryName.startsWith(splittedPath[i])) { + String groupId = splittedPath[i - 1]; + String artifactId = splittedPath[i]; + String version = splittedPath[i + 1]; + if (libraryName.endsWith(version)) { + return groupId + ":" + artifactId + ":" + version; + } + } + } + return null; + } +} diff --git a/android/src/com/android/tools/idea/quickfix/AndroidUnresolvedReferenceQuickFixProvider.java b/android/src/com/android/tools/idea/quickfix/AndroidUnresolvedReferenceQuickFixProvider.java index d5e301109a9..00dd73d9643 100644 --- a/android/src/com/android/tools/idea/quickfix/AndroidUnresolvedReferenceQuickFixProvider.java +++ b/android/src/com/android/tools/idea/quickfix/AndroidUnresolvedReferenceQuickFixProvider.java @@ -17,6 +17,7 @@ package com.android.tools.idea.quickfix; import com.android.tools.idea.gradle.facet.AndroidGradleFacet; import com.android.tools.idea.gradle.parser.GradleBuildFile; +import com.google.common.collect.Sets; import com.intellij.codeInsight.daemon.QuickFixActionRegistrar; import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix; import com.intellij.codeInsight.intention.IntentionAction; @@ -24,18 +25,19 @@ import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; import com.intellij.jarFinder.FindJarFix; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.packageDependencies.DependencyValidationManager; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiJavaCodeReferenceElement; +import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiShortNamesCache; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.Set; import static com.android.tools.idea.gradle.util.GradleUtil.getGradleBuildFile; import static com.intellij.openapi.module.ModuleUtilCore.findModuleForPsiElement; @@ -43,12 +45,11 @@ import static com.intellij.openapi.module.ModuleUtilCore.findModuleForPsiElement public class AndroidUnresolvedReferenceQuickFixProvider extends UnresolvedReferenceQuickFixProvider<PsiJavaCodeReferenceElement> { @Override - public void registerFixes(@NotNull PsiJavaCodeReferenceElement reference, @NotNull QuickFixActionRegistrar registrar) { + public void registerFixes(final @NotNull PsiJavaCodeReferenceElement reference, @NotNull QuickFixActionRegistrar registrar) { Module contextModule = findModuleForPsiElement(reference); if (contextModule == null) { return; } - AndroidGradleFacet gradleFacet = AndroidGradleFacet.getInstance(contextModule); if (gradleFacet == null) { return; @@ -63,6 +64,10 @@ public class AndroidUnresolvedReferenceQuickFixProvider extends UnresolvedRefere if (contextFile == null) { return; } + final VirtualFile classVFile = contextFile.getVirtualFile(); + if (classVFile == null) { + return; + } // Since this is a gradle android project, we need to unregister: // "add module dependency fix", @@ -82,8 +87,7 @@ public class AndroidUnresolvedReferenceQuickFixProvider extends UnresolvedRefere return; } - // TODO implement a quickfix that could properly "add junit dependency", "add library dependency" to the gradle file. - + // TODO implement a quickfix that could properly "add junit dependency" to the gradle file. PsiElement psiElement = reference.getElement(); String referenceName = reference.getRangeInElement().substring(psiElement.getText()); Project project = psiElement.getProject(); @@ -94,8 +98,57 @@ public class AndroidUnresolvedReferenceQuickFixProvider extends UnresolvedRefere if (!allowedDependencies.isEmpty()) { classes = allowedDependencies.toArray(new PsiClass[allowedDependencies.size()]); - registrar.register(new AddGradleProjectDependencyFix(contextModule, contextFile.getVirtualFile(), classes, reference)); + registrar.register(new AddGradleProjectDependencyFix(contextModule, classVFile, classes, reference)); + } + + JavaPsiFacade facade = JavaPsiFacade.getInstance(psiElement.getProject()); + ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + + Set<Object> librariesToAdd = Sets.newHashSet(); + for (PsiClass aClass : classes) { + if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) { + continue; + } + PsiFile psiFile = aClass.getContainingFile(); + if (psiFile == null) { + continue; + } + VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile == null) { + continue; + } + ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(contextModule).getFileIndex(); + for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) { + if (orderEntry instanceof LibraryOrderEntry) { + LibraryOrderEntry libraryEntry = (LibraryOrderEntry)orderEntry; + Library library = libraryEntry.getLibrary(); + if (library == null) { + continue; + } + VirtualFile[] files = library.getFiles(OrderRootType.CLASSES); + if (files.length == 0) { + continue; + } + final VirtualFile jar = files[0]; + + if (jar == null || libraryEntry.isModuleLevel() && !librariesToAdd.add(jar) || !librariesToAdd.add(library)) { + continue; + } + OrderEntry entryForFile = moduleFileIndex.getOrderEntryForFile(virtualFile); + if (entryForFile != null) { + if (entryForFile instanceof ExportableOrderEntry && + ((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST && + !ModuleRootManager.getInstance(contextModule).getFileIndex().isInTestSourceContent(classVFile)) { + } + else { + continue; + } + } + registrar.register(new AddGradleLibraryDependencyFix(libraryEntry, contextModule, aClass, reference)); + } + } } + } // Duplicated from com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix.filterAllowedDependencies |