/* * 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.customizer.android; import com.android.builder.model.*; import com.android.tools.idea.gradle.IdeaAndroidProject; import com.android.tools.idea.gradle.customizer.AbstractContentRootModuleCustomizer; import com.android.tools.idea.gradle.util.FilePaths; import com.google.common.collect.Lists; import com.intellij.openapi.module.Module; import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.model.java.JavaResourceRootType; import org.jetbrains.jps.model.java.JavaSourceRootType; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; import java.io.File; import java.util.Collection; import java.util.List; import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES; import static com.android.builder.model.AndroidProject.FD_OUTPUTS; import static com.intellij.openapi.util.io.FileUtil.join; /** * Sets the content roots of an IDEA module imported from an {@link com.android.builder.model.AndroidProject}. */ public class ContentRootModuleCustomizer extends AbstractContentRootModuleCustomizer<IdeaAndroidProject> { // TODO This is a temporary solution. The real fix is in the Android Gradle plug-in we need to take exploded-aar/${library}/${version}/res // folder somewhere else out of "exploded-aar" so the IDE can index it, but we need to exclude everything else in "exploded-aar" // (e.g. jar files) to avoid unnecessary indexing. private static final String[] EXCLUDED_INTERMEDIATE_FOLDER_NAMES = {"assets", "bundles", "classes", "coverage-instrumented-classes", "dependency-cache", "dex-cache", "dex", "incremental", "jacoco", "javaResources", "libs", "lint", "manifests", "ndk", "pre-dexed", "proguard", "res", "rs", "symbols"}; @NotNull public static final List<String> EXCLUDED_OUTPUT_FOLDER_NAMES = Lists.newArrayList(FD_OUTPUTS); static { for (String name : EXCLUDED_INTERMEDIATE_FOLDER_NAMES) { EXCLUDED_OUTPUT_FOLDER_NAMES.add(join(FD_INTERMEDIATES, name)); } } @Override @NotNull protected Collection<ContentEntry> findOrCreateContentEntries(@NotNull ModifiableRootModel model, @NotNull IdeaAndroidProject androidProject) { VirtualFile rootDir = androidProject.getRootDir(); File rootDirPath = VfsUtilCore.virtualToIoFile(rootDir); List<ContentEntry> contentEntries = Lists.newArrayList(model.addContentEntry(rootDir)); File buildFolderPath = androidProject.getDelegate().getBuildFolder(); if (!FileUtil.isAncestor(rootDirPath, buildFolderPath, false)) { contentEntries.add(model.addContentEntry(FilePaths.pathToIdeaUrl(buildFolderPath))); } return contentEntries; } @Override protected void setUpContentEntries(@NotNull Module module, @NotNull Collection<ContentEntry> contentEntries, @NotNull IdeaAndroidProject androidProject, @NotNull List<RootSourceFolder> orphans) { Variant selectedVariant = androidProject.getSelectedVariant(); AndroidArtifact mainArtifact = selectedVariant.getMainArtifact(); addSourceFolders(androidProject, contentEntries, mainArtifact, false, orphans); AndroidArtifact testArtifact = androidProject.findInstrumentationTestArtifactInSelectedVariant(); if (testArtifact != null) { addSourceFolders(androidProject, contentEntries, testArtifact, true, orphans); } for (String flavorName : selectedVariant.getProductFlavors()) { ProductFlavorContainer flavor = androidProject.findProductFlavor(flavorName); if (flavor != null) { addSourceFolder(androidProject, contentEntries, flavor, orphans); } } String buildTypeName = selectedVariant.getBuildType(); BuildTypeContainer buildTypeContainer = androidProject.findBuildType(buildTypeName); if (buildTypeContainer != null) { addSourceFolder(androidProject, contentEntries, buildTypeContainer.getSourceProvider(), false, orphans); } ProductFlavorContainer defaultConfig = androidProject.getDelegate().getDefaultConfig(); addSourceFolder(androidProject, contentEntries, defaultConfig, orphans); addExcludedOutputFolders(contentEntries, androidProject); } private void addSourceFolders(@NotNull IdeaAndroidProject androidProject, @NotNull Collection<ContentEntry> contentEntry, @NotNull AndroidArtifact androidArtifact, boolean isTest, @NotNull List<RootSourceFolder> orphans) { addGeneratedSourceFolder(androidProject, contentEntry, androidArtifact, isTest, orphans); SourceProvider variantSourceProvider = androidArtifact.getVariantSourceProvider(); if (variantSourceProvider != null) { addSourceFolder(androidProject, contentEntry, variantSourceProvider, isTest, orphans); } SourceProvider multiFlavorSourceProvider = androidArtifact.getMultiFlavorSourceProvider(); if (multiFlavorSourceProvider != null) { addSourceFolder(androidProject, contentEntry, multiFlavorSourceProvider, isTest, orphans); } } private void addGeneratedSourceFolder(@NotNull IdeaAndroidProject androidProject, @NotNull Collection<ContentEntry> contentEntries, @NotNull AndroidArtifact androidArtifact, boolean isTest, @NotNull List<RootSourceFolder> orphans) { JpsModuleSourceRootType sourceType = getSourceType(isTest); addSourceFolders(androidProject, contentEntries, androidArtifact.getGeneratedSourceFolders(), sourceType, true, orphans); sourceType = getResourceSourceType(isTest); addSourceFolders(androidProject, contentEntries, androidArtifact.getGeneratedResourceFolders(), sourceType, true, orphans); } private void addSourceFolder(@NotNull IdeaAndroidProject androidProject, @NotNull Collection<ContentEntry> contentEntries, @NotNull ProductFlavorContainer flavor, @NotNull List<RootSourceFolder> orphans) { addSourceFolder(androidProject, contentEntries, flavor.getSourceProvider(), false, orphans); Collection<SourceProviderContainer> extraArtifactSourceProviders = flavor.getExtraSourceProviders(); for (SourceProviderContainer sourceProviders : extraArtifactSourceProviders) { String artifactName = sourceProviders.getArtifactName(); if (AndroidProject.ARTIFACT_ANDROID_TEST.equals(artifactName)) { addSourceFolder(androidProject, contentEntries, sourceProviders.getSourceProvider(), true, orphans); break; } } } private void addSourceFolder(@NotNull IdeaAndroidProject androidProject, @NotNull Collection<ContentEntry> contentEntries, @NotNull SourceProvider sourceProvider, boolean isTest, @NotNull List<RootSourceFolder> orphans) { JpsModuleSourceRootType sourceType = getResourceSourceType(isTest); addSourceFolders(androidProject, contentEntries, sourceProvider.getResDirectories(), sourceType, false, orphans); addSourceFolders(androidProject, contentEntries, sourceProvider.getResourcesDirectories(), sourceType, false, orphans); addSourceFolders(androidProject, contentEntries, sourceProvider.getAssetsDirectories(), sourceType, false, orphans); sourceType = getSourceType(isTest); addSourceFolders(androidProject, contentEntries, sourceProvider.getAidlDirectories(), sourceType, false, orphans); addSourceFolders(androidProject, contentEntries, sourceProvider.getJavaDirectories(), sourceType, false, orphans); addSourceFolders(androidProject, contentEntries, sourceProvider.getJniDirectories(), sourceType, false, orphans); addSourceFolders(androidProject, contentEntries, sourceProvider.getRenderscriptDirectories(), sourceType, false, orphans); } @NotNull private static JpsModuleSourceRootType getResourceSourceType(boolean isTest) { return isTest ? JavaResourceRootType.TEST_RESOURCE : JavaResourceRootType.RESOURCE; } @NotNull private static JpsModuleSourceRootType getSourceType(boolean isTest) { return isTest ? JavaSourceRootType.TEST_SOURCE : JavaSourceRootType.SOURCE; } private void addSourceFolders(@NotNull IdeaAndroidProject androidProject, @NotNull Collection<ContentEntry> contentEntries, @NotNull Collection<File> folderPaths, @NotNull JpsModuleSourceRootType type, boolean generated, @NotNull List<RootSourceFolder> orphans) { for (File folderPath : folderPaths) { if (generated && !isGeneratedAtCorrectLocation(folderPath, androidProject.getDelegate())) { androidProject.registerExtraGeneratedSourceFolder(folderPath); } addSourceFolder(contentEntries, folderPath, type, generated, orphans); } } private static boolean isGeneratedAtCorrectLocation(@NotNull File folderPath, @NotNull AndroidProject project) { File generatedFolderPath = new File(project.getBuildFolder(), AndroidProject.FD_GENERATED); return FileUtil.isAncestor(generatedFolderPath, folderPath, false); } private void addExcludedOutputFolders(@NotNull Collection<ContentEntry> contentEntries, @NotNull IdeaAndroidProject androidProject) { File buildFolderPath = androidProject.getDelegate().getBuildFolder(); ContentEntry parentContentEntry = FilePaths.findParentContentEntry(buildFolderPath, contentEntries); if (parentContentEntry == null) { return; } // Explicitly exclude the output folders created by the Android Gradle plug-in for (String folderName : EXCLUDED_OUTPUT_FOLDER_NAMES) { File excludedFolderPath = new File(buildFolderPath, folderName); addExcludedFolder(parentContentEntry, excludedFolderPath); } // Iterate through the build folder's children, excluding any folders that are not "generated" and haven't been already excluded. File[] children = FileUtil.notNullize(buildFolderPath.listFiles()); for (File child : children) { if (androidProject.shouldManuallyExclude(child)) { addExcludedFolder(parentContentEntry, child); } } } }