package org.jetbrains.android.compiler; import com.android.SdkConstants; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.internal.build.BuildConfigGenerator; import com.android.tools.idea.fileTypes.AndroidRenderscriptFileType; import com.intellij.compiler.impl.CompilerUtil; import com.intellij.compiler.impl.ModuleCompileScope; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.*; import com.intellij.psi.search.FilenameIndex; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashSet; import org.jetbrains.android.compiler.tools.AndroidIdl; import org.jetbrains.android.compiler.tools.AndroidRenderscript; import org.jetbrains.android.dom.manifest.Manifest; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidRootUtil; import org.jetbrains.android.fileTypes.AndroidIdlFileType; import org.jetbrains.android.sdk.AndroidPlatform; import org.jetbrains.android.util.AndroidBundle; import org.jetbrains.android.util.AndroidCommonUtils; import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.*; /** * @author Eugene.Kudelevsky */ public class AndroidAutogenerator { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.AndroidAutogenerator"); private AndroidAutogenerator() { } private static boolean toRun(@NotNull AndroidAutogeneratorMode mode, @NotNull AndroidFacet facet, boolean force) { if (!supportsAutogeneration(facet)) { return false; } if (!force && !facet.getProperties().ENABLE_SOURCES_AUTOGENERATION) { return false; } switch (mode) { case AAPT: case AIDL: case RENDERSCRIPT: case BUILDCONFIG: return true; default: LOG.error("Unknown autogenerator mode " + mode); return false; } } public static boolean supportsAutogeneration(@NotNull AndroidFacet facet) { // This is a cheap way to figure out that a module has the Android-Gradle facet. // Don't generate anything if a module has an Android-Gradle facet. return !facet.isGradleProject(); } public static void run(@NotNull AndroidAutogeneratorMode mode, @NotNull AndroidFacet facet, @NotNull CompileContext context, boolean force) { if (!toRun(mode, facet, force)) { return; } final Set<String> obsoleteFiles = new HashSet<String>(facet.getAutogeneratedFiles(mode)); switch (mode) { case AAPT: runAapt(facet, context, force); break; case AIDL: runAidl(facet, context); break; case RENDERSCRIPT: runRenderscript(facet, context); break; case BUILDCONFIG: runBuildConfigGenerator(facet, context); break; default: LOG.error("Unknown mode" + mode); } obsoleteFiles.removeAll(facet.getAutogeneratedFiles(mode)); for (String path : obsoleteFiles) { final File file = new File(path); if (file.isFile()) { FileUtil.delete(file); CompilerUtil.refreshIOFile(file); } } } private static void runBuildConfigGenerator(@NotNull final AndroidFacet facet, @NotNull final CompileContext context) { final Module module = facet.getModule(); final BuildconfigAutogenerationItem item = ApplicationManager.getApplication().runReadAction( new Computable<BuildconfigAutogenerationItem>() { @Nullable @Override public BuildconfigAutogenerationItem compute() { if (module.isDisposed() || module.getProject().isDisposed()) { return null; } final String sourceRootPath = AndroidRootUtil.getBuildconfigGenSourceRootPath(facet); if (sourceRootPath == null) { return null; } final VirtualFile manifestFile = AndroidRootUtil.getManifestFileForCompiler(facet); if (manifestFile == null) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("android.compilation.error.manifest.not.found", module.getName()), null, -1, -1); return null; } final Manifest manifest = AndroidUtils.loadDomElement(module, manifestFile, Manifest.class); if (manifest == null) { context.addMessage(CompilerMessageCategory.ERROR, "Cannot parse file", manifestFile.getUrl(), -1, -1); return null; } String packageName = manifest.getPackage().getValue(); if (packageName != null) { packageName = packageName.trim(); } if (packageName == null || packageName.length() <= 0) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("package.not.found.error"), manifestFile.getUrl(), -1, -1); return null; } if (!AndroidUtils.isValidAndroidPackageName(packageName)) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("not.valid.package.name.error", packageName), manifestFile.getUrl(), -1, -1); return null; } return new BuildconfigAutogenerationItem(packageName, FileUtil.toSystemDependentName(sourceRootPath)); } }); if (item == null) { return; } try { // hack for IDEA-100046: we need to avoid reporting "condition is always 'true' // from data flow inspection, so use non-constant value here generateStubClass(item.myPackage, new File(item.mySourceRootOsPath), "BuildConfig", " public final static boolean DEBUG = Boolean.parseBoolean(null);\n"); final VirtualFile genSourceRoot = LocalFileSystem.getInstance().findFileByPath(item.mySourceRootOsPath); if (genSourceRoot != null) { genSourceRoot.refresh(false, true); } facet.clearAutogeneratedFiles(AndroidAutogeneratorMode.BUILDCONFIG); final VirtualFile genFile = LocalFileSystem.getInstance().refreshAndFindFileByPath( item.mySourceRootOsPath + '/' + item.myPackage.replace('.', '/') + '/' + BuildConfigGenerator.BUILD_CONFIG_NAME); if (genFile != null && genFile.exists()) { facet.markFileAutogenerated(AndroidAutogeneratorMode.BUILDCONFIG, genFile); } } catch (final IOException e) { LOG.info(e); ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { if (module.getProject().isDisposed()) return; context.addMessage(CompilerMessageCategory.ERROR, "I/O error: " + e.getMessage(), null, -1, -1); } }); } } private static void runAapt(@NotNull final AndroidFacet facet, @NotNull final CompileContext context, boolean force) { final Module module = facet.getModule(); final AptAutogenerationItem item = ApplicationManager.getApplication().runReadAction(new Computable<AptAutogenerationItem>() { @Nullable @Override public AptAutogenerationItem compute() { if (module.isDisposed() || module.getProject().isDisposed()) { return null; } final VirtualFile manifestFile = AndroidRootUtil.getManifestFileForCompiler(facet); if (manifestFile == null) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("android.compilation.error.manifest.not.found", module.getName()), null, -1, -1); return null; } final Manifest manifest = AndroidUtils.loadDomElement(module, manifestFile, Manifest.class); if (manifest == null) { context.addMessage(CompilerMessageCategory.ERROR, "Cannot parse file", manifestFile.getUrl(), -1, -1); return null; } String packageName = manifest.getPackage().getValue(); if (packageName != null) { packageName = packageName.trim(); } if (packageName == null || packageName.length() <= 0) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("package.not.found.error"), manifestFile.getUrl(), -1, -1); return null; } if (!AndroidUtils.isValidAndroidPackageName(packageName)) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("not.valid.package.name.error", packageName), manifestFile.getUrl(), -1, -1); return null; } final String sourceRootPath = AndroidRootUtil.getAptGenSourceRootPath(facet); if (sourceRootPath == null) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("android.compilation.error.apt.gen.not.specified", module.getName()), null, -1, -1); return null; } final Map<String, String> genFilePath2Package = new HashMap<String, String>(); final String packageDir = packageName.replace('.', '/') + '/'; genFilePath2Package.put(packageDir + AndroidCommonUtils.MANIFEST_JAVA_FILE_NAME, packageName); genFilePath2Package.put(packageDir + AndroidCommonUtils.R_JAVA_FILENAME, packageName); return new AptAutogenerationItem(packageName, sourceRootPath, genFilePath2Package); } }); if (item == null) { return; } if (force) { final Set<VirtualFile> filesToCheck = new HashSet<VirtualFile>(); for (String genFileRelPath : item.myGenFileRelPath2package.keySet()) { final String genFileFullPath = item.myOutputDirOsPath + '/' + genFileRelPath; if (new File(genFileFullPath).exists()) { final VirtualFile genFile = LocalFileSystem.getInstance().findFileByPath(genFileFullPath); if (genFile != null) { filesToCheck.add(genFile); } } } if (!ensureFilesWritable(module.getProject(), filesToCheck)) { return; } } File tempOutDir = null; try { // Aapt generation can be very long, so we generate it in temp directory first tempOutDir = FileUtil.createTempDirectory("android_apt_autogeneration", "tmp"); generateStubClasses(item.myPackage, tempOutDir, AndroidUtils.R_CLASS_NAME, AndroidUtils.MANIFEST_CLASS_NAME); for (String genFileRelPath : item.myGenFileRelPath2package.keySet()) { final File srcFile = new File(tempOutDir.getPath() + '/' + genFileRelPath); if (srcFile.isFile()) { final File dstFile = new File(item.myOutputDirOsPath + '/' + genFileRelPath); if (dstFile.exists()) { if (!force) { continue; } if (!FileUtil.delete(dstFile)) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { if (module.isDisposed() || module.getProject().isDisposed()) { return; } context.addMessage(CompilerMessageCategory.ERROR, "Cannot delete " + FileUtil.toSystemDependentName(dstFile.getPath()), null, -1, -1); } }); } } FileUtil.rename(srcFile, dstFile); } } for (Map.Entry<String, String> entry : item.myGenFileRelPath2package.entrySet()) { final String path = item.myOutputDirOsPath + '/' + entry.getKey(); final String aPackage = entry.getValue(); final File file = new File(path); CompilerUtil.refreshIOFile(file); removeAllFilesWithSameName(module, file, item.myOutputDirOsPath); removeDuplicateClasses(module, aPackage, file, item.myOutputDirOsPath); } final VirtualFile genSourceRoot = LocalFileSystem.getInstance().findFileByPath(item.myOutputDirOsPath); if (genSourceRoot != null) { genSourceRoot.refresh(false, true); } facet.clearAutogeneratedFiles(AndroidAutogeneratorMode.AAPT); for (String relPath : item.myGenFileRelPath2package.keySet()) { final VirtualFile genFile = LocalFileSystem.getInstance().findFileByPath(item.myOutputDirOsPath + '/' + relPath); if (genFile != null && genFile.exists()) { facet.markFileAutogenerated(AndroidAutogeneratorMode.AAPT, genFile); } } } catch (final IOException e) { LOG.info(e); ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { if (module.getProject().isDisposed()) return; context.addMessage(CompilerMessageCategory.ERROR, "I/O error: " + e.getMessage(), null, -1, -1); } }); } finally { if (tempOutDir != null) { FileUtil.delete(tempOutDir); } } } private static void generateStubClasses(@NotNull String aPackage, @NotNull File outputDir, @NotNull String... classNames) throws IOException { assert aPackage.length() > 0; for (String className : classNames) { generateStubClass(aPackage, outputDir, className, ""); } } private static void generateStubClass(String aPackage, File outputDir, String className, String content) throws IOException { final File packageDir = new File(outputDir.getPath() + '/' + aPackage.replace('.', '/')); if (!packageDir.exists() && !packageDir.mkdirs()) { throw new IOException("Cannot create directory " + FileUtil.toSystemDependentName(packageDir.getPath())); } final BufferedWriter writer = new BufferedWriter(new FileWriter(new File(packageDir, className + ".java"))); try { writer.write( AndroidCommonUtils.AUTOGENERATED_JAVA_FILE_HEADER + "\n\npackage " + aPackage + ";\n\n" + "/* This stub is only used by the IDE. It is NOT the " + className + " class actually packed into the APK */\n" + "public final class " + className + " {\n" + content + "}" ); } finally { writer.close(); } } private static void patchAndMarkGeneratedFile(@NotNull AndroidFacet facet, @NotNull AndroidAutogeneratorMode mode, @NotNull VirtualFile vFile) throws IOException { final File file = new File(vFile.getPath()); final String fileText = FileUtil.loadFile(file); FileUtil.writeToFile(file, AndroidCommonUtils.AUTOGENERATED_JAVA_FILE_HEADER + "\n\n" + fileText); facet.markFileAutogenerated(mode, vFile); } private static void removeAllFilesWithSameName(@NotNull final Module module, @NotNull final File file, @NotNull String directoryPath) { final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(file); final VirtualFile genDir = LocalFileSystem.getInstance().findFileByPath(directoryPath); if (vFile == null || genDir == null) { return; } final Collection<VirtualFile> files = DumbService.getInstance(module.getProject()).runReadActionInSmartMode(new Computable<Collection<VirtualFile>>() { @Nullable @Override public Collection<VirtualFile> compute() { if (module.isDisposed() || module.getProject().isDisposed()) { return null; } return FilenameIndex.getVirtualFilesByName(module.getProject(), file.getName(), module.getModuleScope(false)); } }); if (files == null) { return; } final List<VirtualFile> filesToDelete = new ArrayList<VirtualFile>(); for (final VirtualFile f : files) { if (!Comparing.equal(f, vFile) && VfsUtilCore.isAncestor(genDir, f, true)) { filesToDelete.add(f); } } if (filesToDelete.size() == 0) { return; } ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { for (VirtualFile f : filesToDelete) { if (f.isValid() && f.exists()) { try { f.delete(module.getProject()); } catch (IOException e) { LOG.debug(e); } } } } }); } }); } private static void runAidl(@NotNull final AndroidFacet facet, @NotNull final CompileContext context) { final Module module = facet.getModule(); final ModuleCompileScope moduleCompileScope = new ModuleCompileScope(module, false); final VirtualFile[] files = moduleCompileScope.getFiles(AndroidIdlFileType.ourFileType, true); final List<IdlAutogenerationItem> items = new ArrayList<IdlAutogenerationItem>(); for (final VirtualFile file : files) { final IdlAutogenerationItem item = ApplicationManager.getApplication().runReadAction(new Computable<IdlAutogenerationItem>() { @Nullable @Override public IdlAutogenerationItem compute() { if (module.isDisposed() || module.getProject().isDisposed()) { return null; } final IAndroidTarget target = facet.getConfiguration().getAndroidTarget(); if (target == null) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("android.compilation.error.specify.platform", module.getName()), null, -1, -1); return null; } final String packageName = AndroidUtils.computePackageName(module, file); if (packageName == null) { context.addMessage(CompilerMessageCategory.ERROR, "Cannot compute package for file", file.getUrl(), -1, -1); return null; } final String sourceRootPath = AndroidRootUtil.getAidlGenSourceRootPath(facet); if (sourceRootPath == null) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("android.compilation.error.apt.gen.not.specified", module.getName()), null, -1, -1); return null; } final VirtualFile[] sourceRoots = getSourceRootsForModuleAndDependencies(module, false); final String[] sourceRootOsPaths = AndroidCompileUtil.toOsPaths(sourceRoots); final String outFileOsPath = FileUtil.toSystemDependentName( sourceRootPath + '/' + packageName.replace('.', '/') + '/' + file.getNameWithoutExtension() + ".java"); return new IdlAutogenerationItem(file, target, outFileOsPath, sourceRootOsPaths, sourceRootPath, packageName); } }); if (item != null) { items.add(item); } } final Set<VirtualFile> filesToCheck = new HashSet<VirtualFile>(); for (IdlAutogenerationItem item : items) { if (new File(FileUtil.toSystemDependentName(item.myFile.getPath())).exists()) { filesToCheck.add(item.myFile); } } if (!ensureFilesWritable(module.getProject(), filesToCheck)) { return; } facet.clearAutogeneratedFiles(AndroidAutogeneratorMode.AIDL); for (IdlAutogenerationItem item : items) { final VirtualFile file = item.myFile; final String fileOsPath = FileUtil.toSystemDependentName(file.getPath()); try { final Map<CompilerMessageCategory, List<String>> messages = AndroidCompileUtil.toCompilerMessageCategoryKeys( AndroidIdl.execute(item.myTarget, fileOsPath, item.myOutFileOsPath, item.mySourceRootOsPaths)); ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { if (module.getProject().isDisposed()) return; for (CompilerMessageCategory category : messages.keySet()) { List<String> messageList = messages.get(category); for (String message : messageList) { context.addMessage(category, message, file.getUrl(), -1, -1); } } } }); removeDuplicateClasses(module, item.myPackage, new File(item.myOutFileOsPath), item.myOutDirOsPath); final VirtualFile genDir = LocalFileSystem.getInstance().findFileByPath(item.myOutDirOsPath); if (genDir != null) { genDir.refresh(false, true); } final VirtualFile outFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(item.myOutFileOsPath); if (outFile != null && outFile.exists()) { patchAndMarkGeneratedFile(facet, AndroidAutogeneratorMode.AIDL, outFile); } } catch (final IOException e) { LOG.info(e); ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { if (module.getProject().isDisposed()) return; context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), file.getUrl(), -1, -1); } }); } } } private static void runRenderscript(@NotNull final AndroidFacet facet, @NotNull final CompileContext context) { final Module module = facet.getModule(); final ModuleCompileScope moduleCompileScope = new ModuleCompileScope(module, false); final VirtualFile[] files = moduleCompileScope.getFiles(AndroidRenderscriptFileType.INSTANCE, true); facet.clearAutogeneratedFiles(AndroidAutogeneratorMode.RENDERSCRIPT); for (final VirtualFile file : files) { final RenderscriptAutogenerationItem item = ApplicationManager.getApplication().runReadAction(new Computable<RenderscriptAutogenerationItem>() { @Nullable @Override public RenderscriptAutogenerationItem compute() { final AndroidPlatform platform = facet.getConfiguration().getAndroidPlatform(); if (platform == null) { context.addMessage(CompilerMessageCategory.ERROR, AndroidBundle.message("android.compilation.error.specify.platform", module.getName()), null, -1, -1); return null; } final IAndroidTarget target = platform.getTarget(); final String sdkLocation = platform.getSdkData().getPath(); final String packageName = AndroidUtils.computePackageName(module, file); if (packageName == null) { context.addMessage(CompilerMessageCategory.ERROR, "Cannot compute package for file", file.getUrl(), -1, -1); return null; } final String resourceDirPath = AndroidRootUtil.getResourceDirPath(facet); assert resourceDirPath != null; final String sourceRootPath = AndroidRootUtil.getRenderscriptGenSourceRootPath(facet); if (sourceRootPath == null) { return null; } final String rawDirPath = resourceDirPath + '/' + SdkConstants.FD_RES_RAW; return new RenderscriptAutogenerationItem(sdkLocation, target, sourceRootPath, rawDirPath); } }); if (item == null) { continue; } File tempOutDir = null; try { tempOutDir = FileUtil.createTempDirectory("android_renderscript_autogeneration", "tmp"); final VirtualFile vTempOutDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempOutDir); final String depFolderPath = vTempOutDir != null ? getDependencyFolder(context.getProject(), file, vTempOutDir) : null; final Map<CompilerMessageCategory, List<String>> messages = AndroidCompileUtil.toCompilerMessageCategoryKeys( AndroidRenderscript .execute(item.mySdkLocation, item.myTarget, file.getPath(), tempOutDir.getPath(), depFolderPath, item.myRawDirPath)); if (messages.get(CompilerMessageCategory.ERROR).size() == 0) { final List<File> newFiles = new ArrayList<File>(); AndroidCommonUtils.moveAllFiles(tempOutDir, new File(item.myGenDirPath), newFiles); for (File newFile : newFiles) { final VirtualFile newVFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(newFile); if (newVFile != null) { patchAndMarkGeneratedFile(facet, AndroidAutogeneratorMode.RENDERSCRIPT, newVFile); } } final File bcFile = new File(item.myRawDirPath, FileUtil.getNameWithoutExtension(file.getName()) + ".bc"); final VirtualFile vBcFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(bcFile); if (vBcFile != null) { facet.markFileAutogenerated(AndroidAutogeneratorMode.RENDERSCRIPT, vBcFile); } } ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { if (module.getProject().isDisposed()) { return; } for (final CompilerMessageCategory category : messages.keySet()) { final List<String> messageList = messages.get(category); for (final String message : messageList) { context.addMessage(category, message, file.getUrl(), -1, -1); } } } }); final VirtualFile genDir = LocalFileSystem.getInstance().findFileByPath(item.myGenDirPath); if (genDir != null) { genDir.refresh(false, true); } } catch (final IOException e) { LOG.info(e); ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { if (module.getProject().isDisposed()) return; context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), file.getUrl(), -1, -1); } }); } finally { if (tempOutDir != null) { FileUtil.delete(tempOutDir); } } } } private static boolean ensureFilesWritable(@NotNull final Project project, @NotNull final Collection<VirtualFile> filesToCheck) { if (filesToCheck.size() == 0) { return true; } final boolean[] run = {false}; ApplicationManager.getApplication().invokeAndWait(new Runnable() { @Override public void run() { run[0] = !project.isDisposed() && ReadonlyStatusHandler.ensureFilesWritable(project, filesToCheck.toArray(new VirtualFile[filesToCheck.size()])); } }, ModalityState.defaultModalityState()); return run[0]; } private static void removeDuplicateClasses(@NotNull final Module module, @NotNull final String aPackage, @NotNull final File generatedFile, @NotNull final String sourceRootPath) { if (generatedFile.exists()) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { if (module.getProject().isDisposed() || module.isDisposed()) { return; } String className = FileUtil.getNameWithoutExtension(generatedFile); AndroidCompileUtil.removeDuplicatingClasses(module, aPackage, className, generatedFile, sourceRootPath); } }); } } private static void fillSourceRoots(@NotNull Module module, @NotNull Set<Module> visited, @NotNull Set<VirtualFile> result, boolean includingTests) { visited.add(module); final AndroidFacet facet = AndroidFacet.getInstance(module); VirtualFile resDir = facet != null ? AndroidRootUtil.getResourceDir(facet) : null; ModuleRootManager manager = ModuleRootManager.getInstance(module); for (VirtualFile sourceRoot : manager.getSourceRoots(includingTests)) { if (!Comparing.equal(resDir, sourceRoot)) { result.add(sourceRoot); } } for (OrderEntry entry : manager.getOrderEntries()) { if (entry instanceof ModuleOrderEntry) { ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry)entry; DependencyScope scope = moduleOrderEntry.getScope(); if (scope == DependencyScope.COMPILE) { Module depModule = moduleOrderEntry.getModule(); if (depModule != null && !visited.contains(depModule)) { fillSourceRoots(depModule, visited, result, false); } } } } } @NotNull public static VirtualFile[] getSourceRootsForModuleAndDependencies(@NotNull Module module, boolean includingTests) { Set<VirtualFile> result = new HashSet<VirtualFile>(); fillSourceRoots(module, new HashSet<Module>(), result, includingTests); return VfsUtil.toVirtualFileArray(result); } @Nullable static String getDependencyFolder(@NotNull final Project project, @NotNull final VirtualFile sourceFile, @NotNull final VirtualFile genFolder) { final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex(); final VirtualFile sourceRoot = index.getSourceRootForFile(sourceFile); if (sourceRoot == null) { return null; } final VirtualFile parent = sourceFile.getParent(); if (Comparing.equal(parent, sourceRoot)) { return genFolder.getPath(); } final String relativePath = VfsUtilCore.getRelativePath(sourceFile.getParent(), sourceRoot, '/'); assert relativePath != null; return genFolder.getPath() + '/' + relativePath; } private static class AptAutogenerationItem { final String myPackage; final String myOutputDirOsPath; final Map<String, String> myGenFileRelPath2package; private AptAutogenerationItem(@NotNull String aPackage, @NotNull String outputDirOsPath, @NotNull Map<String, String> genFileRelPath2package) { myPackage = aPackage; myOutputDirOsPath = outputDirOsPath; myGenFileRelPath2package = genFileRelPath2package; } } private static class IdlAutogenerationItem { final VirtualFile myFile; final IAndroidTarget myTarget; final String myOutFileOsPath; final String[] mySourceRootOsPaths; final String myOutDirOsPath; final String myPackage; private IdlAutogenerationItem(@NotNull VirtualFile file, @NotNull IAndroidTarget target, @NotNull String outFileOsPath, @NotNull String[] sourceRootOsPaths, @NotNull String outDirOsPath, @NotNull String aPackage) { myFile = file; myTarget = target; myOutFileOsPath = outFileOsPath; mySourceRootOsPaths = sourceRootOsPaths; myOutDirOsPath = outDirOsPath; myPackage = aPackage; } } private static class RenderscriptAutogenerationItem { final String mySdkLocation; final IAndroidTarget myTarget; final String myGenDirPath; final String myRawDirPath; private RenderscriptAutogenerationItem(@NotNull String sdkLocation, @NotNull IAndroidTarget target, @NotNull String genDirPath, @NotNull String rawDirPath) { mySdkLocation = sdkLocation; myTarget = target; myGenDirPath = genDirPath; myRawDirPath = rawDirPath; } } private static class BuildconfigAutogenerationItem { final String myPackage; final String mySourceRootOsPath; private BuildconfigAutogenerationItem(@NotNull String aPackage, @NotNull String sourceRootOsPath) { myPackage = aPackage; mySourceRootOsPath = sourceRootOsPath; } } }