From c098f1dec72ed3559cec7f9bfef0ab45e8839791 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Thu, 9 Feb 2012 19:58:53 +0400 Subject: [PATCH] android-jps-plugin initial --- .idea/modules.xml | 1 + build/scripts/layouts.gant | 4 + .../android/jps-plugin/android-jps-plugin.iml | 17 + .../org.jetbrains.jps.idea.FacetTypeService | 1 + .../org.jetbrains.jps.idea.SdkTypeService | 1 + ...g.jetbrains.jps.incremental.BuilderService | 1 + .../jps/android/AndroidBuilderService.java | 20 + .../jetbrains/jps/android/AndroidFacet.groovy | 46 ++ .../jps/android/AndroidFacetType.groovy | 22 + .../jetbrains/jps/android/AndroidSdk.groovy | 34 ++ .../jps/android/AndroidSdkType.groovy | 30 ++ .../AndroidSourceGeneratingBuilder.java | 498 ++++++++++++++++++ plugins/android/src/META-INF/plugin.xml | 6 + 13 files changed, 681 insertions(+) create mode 100644 plugins/android/jps-plugin/android-jps-plugin.iml create mode 100644 plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.FacetTypeService create mode 100644 plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.SdkTypeService create mode 100644 plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdk.groovy create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdkType.groovy create mode 100644 plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java diff --git a/.idea/modules.xml b/.idea/modules.xml index ce8d7ca005f3..c7736ee6aa66 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -11,6 +11,7 @@ + diff --git a/build/scripts/layouts.gant b/build/scripts/layouts.gant index 6dc01096ba64..a092e36f46eb 100644 --- a/build/scripts/layouts.gant +++ b/build/scripts/layouts.gant @@ -366,6 +366,10 @@ public def layoutCommunityPlugins(String home) { } jar("android_rt.jar") {module("android-rt")} + + dir("jps") { + jar("android-jps-plugin.jar") { module("android-jps-plugin") } + } } } } diff --git a/plugins/android/jps-plugin/android-jps-plugin.iml b/plugins/android/jps-plugin/android-jps-plugin.iml new file mode 100644 index 000000000000..a2f02ccd0c76 --- /dev/null +++ b/plugins/android/jps-plugin/android-jps-plugin.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.FacetTypeService b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.FacetTypeService new file mode 100644 index 000000000000..a75fa45b2aa4 --- /dev/null +++ b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.FacetTypeService @@ -0,0 +1 @@ +org.jetbrains.jps.android.AndroidFacetType \ No newline at end of file diff --git a/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.SdkTypeService b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.SdkTypeService new file mode 100644 index 000000000000..f3084a2b366a --- /dev/null +++ b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.idea.SdkTypeService @@ -0,0 +1 @@ +org.jetbrains.jps.android.AndroidSdkType \ No newline at end of file diff --git a/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService new file mode 100644 index 000000000000..59103ac8ba86 --- /dev/null +++ b/plugins/android/jps-plugin/src/META-INF/services/org.jetbrains.jps.incremental.BuilderService @@ -0,0 +1 @@ +org.jetbrains.jps.android.AndroidBuilderService \ No newline at end of file diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java new file mode 100644 index 000000000000..81b9e6eba3de --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java @@ -0,0 +1,20 @@ +package org.jetbrains.jps.android; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.incremental.BuilderService; +import org.jetbrains.jps.incremental.ModuleLevelBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutorService; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidBuilderService extends BuilderService { + @NotNull + @Override + public List createModuleLevelBuilders(ExecutorService executorService) { + return Arrays.asList(new AndroidSourceGeneratingBuilder()); + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy new file mode 100644 index 000000000000..e9530f165ee7 --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy @@ -0,0 +1,46 @@ +package org.jetbrains.jps.android + +import com.intellij.openapi.util.io.FileUtil +import org.jetbrains.jps.Module +import org.jetbrains.jps.idea.Facet + +/** + * @author nik + */ +class AndroidFacet extends Facet { + final Module module + final String resFolderRelativePath + + AndroidFacet(Module module, String name, String resFolderRelativePath) { + this.module = module + this.name = name; + this.resFolderRelativePath = resFolderRelativePath + } + + File getResourceDir() { + return findFileByRelativeModulePath(resFolderRelativePath, true) + } + + private File findFileByRelativeModulePath(String relativePath, boolean lookInContentRoot) { + if (module.basePath != null) { + def absPath = FileUtil.toSystemIndependentName(module.basePath + relativePath) + def f = new File(absPath) + + if (f.exists()) { + return f + } + } + + if (lookInContentRoot) { + module.contentRoots.each { + def absPath = FileUtil.toSystemIndependentName(contentRoot + relativePath) + def f = new File(absPath) + + if (f.exists()) { + return f + } + } + } + return null; + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy new file mode 100644 index 000000000000..ca555e24e37e --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy @@ -0,0 +1,22 @@ +package org.jetbrains.jps.android + +import org.jetbrains.jps.MacroExpander +import org.jetbrains.jps.Module +import org.jetbrains.jps.idea.Facet +import org.jetbrains.jps.idea.FacetTypeService + +/** + * @author Eugene.Kudelevsky + */ +class AndroidFacetType extends FacetTypeService { + public static final String ID = "android" + + AndroidFacetType() { + super(ID) + } + + @Override + Facet createFacet(Module module, String name, Node facetConfiguration, MacroExpander macroExpander) { + return new AndroidFacet(module, name, ""); + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdk.groovy b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdk.groovy new file mode 100644 index 000000000000..b4ce00b91c41 --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdk.groovy @@ -0,0 +1,34 @@ +package org.jetbrains.jps.android + +import org.jetbrains.jps.JavaSdk +import org.jetbrains.jps.Project +import org.jetbrains.jps.Sdk +import org.jetbrains.jps.JavaSdkImpl + +/** + * @author Eugene.Kudelevsky + */ +class AndroidSdk extends Sdk implements JavaSdk { + final String sdkPath; + final String buildTargetHashString; + final String javaSdkName; + + AndroidSdk(Project project, String name, String sdkPath, String javaSdkName, String buildTargetHashString) { + super(project, name, {}) + this.sdkPath = sdkPath + this.buildTargetHashString = buildTargetHashString + this.javaSdkName = javaSdkName; + } + + @Override + String getJavacExecutable() { + def javaSdk = project.sdks[javaSdkName] + return javaSdk instanceof JavaSdkImpl ? javaSdk.getJavacExecutable() : null + } + + @Override + String getJavaExecutable() { + def javaSdk = project.sdks[javaSdkName] + return javaSdk instanceof JavaSdkImpl ? javaSdk.getJavaExecutable() : null + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdkType.groovy b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdkType.groovy new file mode 100644 index 000000000000..90d47d29c07c --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSdkType.groovy @@ -0,0 +1,30 @@ +package org.jetbrains.jps.android + +import org.jetbrains.jps.idea.SdkTypeService +import org.jetbrains.jps.Sdk +import org.jetbrains.jps.Project + +/** + * @author Eugene.Kudelevsky + */ +class AndroidSdkType extends SdkTypeService { + AndroidSdkType() { + super("Android SDK"); + } + + @Override + Sdk createSdk(Project project, String name, String homePath, Node additionalData) { + def attributes = additionalData.attributes() + if (attributes == null) { + return null; + } + + def buildTargetHashString = (String)attributes.get("sdk") + def internalJdkName = (String)attributes.get("jdk") + + if (internalJdkName == null || buildTargetHashString == null) { + return null; + } + return new AndroidSdk(project, name, homePath, internalJdkName, buildTargetHashString) + } +} diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java new file mode 100644 index 000000000000..77abb5082d17 --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java @@ -0,0 +1,498 @@ +package org.jetbrains.jps.android; + +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +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.sdk.MessageBuildingSdkLog; +import org.jetbrains.android.util.AndroidCommonUtils; +import org.jetbrains.android.util.AndroidCompilerMessageKind; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.*; +import org.jetbrains.jps.idea.Facet; +import org.jetbrains.jps.incremental.*; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; +import org.jetbrains.jps.incremental.messages.ProgressMessage; +import org.jetbrains.jps.incremental.storage.SourceToOutputMapping; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.*; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.android.AndroidSourceGeneratingBuilder"); + + @NonNls private static final String BUILDER_NAME = "android-source-generator"; + @NonNls private static final String AIDL_EXTENSION = "aidl"; + @NonNls private static final String RENDERSCRIPT_EXTENSION = "rs"; + + public AndroidSourceGeneratingBuilder() { + super(BuilderCategory.SOURCE_GENERATOR); + } + + @Override + public String getName() { + return BUILDER_NAME; + } + + @Override + public ModuleLevelBuilder.ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException { + if (context.isCompilingTests()) { + return ModuleLevelBuilder.ExitCode.OK; + } + + try { + return doBuild(context, chunk); + } + catch (ProjectBuildException e) { + throw e; + } + catch (Exception e) { + String message = e.getMessage(); + + if (message == null) { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + //noinspection IOResourceOpenedButNotSafelyClosed + e.printStackTrace(new PrintStream(out)); + message = "Internal error: \n" + out.toString(); + } + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message)); + throw new ProjectBuildException(message, e); + } + } + + private static ModuleLevelBuilder.ExitCode doBuild(CompileContext context, ModuleChunk chunk) throws Exception { + final Map idlFilesToCompile = new HashMap(); + final Map rsFilesToCompile = new HashMap(); + final Set modules = new HashSet(); + + context.processFilesToRecompile(chunk, new FileProcessor() { + @Override + public boolean apply(Module module, File file, String sourceRoot) throws IOException { + final AndroidFacet facet = getFacet(module); + + if (facet == null) { + return true; + } + final String ext = FileUtil.getExtension(file.getName()); + + if (AIDL_EXTENSION.equals(ext)) { + idlFilesToCompile.put(file, facet); + modules.add(facet.getModule()); + } + else if (RENDERSCRIPT_EXTENSION.equals(ext)) { + rsFilesToCompile.put(file, facet); + modules.add(facet.getModule()); + } + + return true; + } + }); + + final Map moduleDataMap = computeModuleDatas(modules, context); + + if (moduleDataMap == null) { + return ExitCode.OK; + } + + if (!runAidlCompiler(context, idlFilesToCompile, moduleDataMap)) { + return ExitCode.OK; + } + + if (!runRenderscriptCompiler(context, rsFilesToCompile, moduleDataMap)) { + return ExitCode.OK; + } + + return ExitCode.OK; + } + + private static boolean runAidlCompiler(@NotNull final CompileContext context, + @NotNull Map files, + @NotNull Map moduleDataMap) { + context.processMessage(new ProgressMessage("Processing AIDL files...")); + + boolean success = true; + + for (Map.Entry entry : files.entrySet()) { + final File file = entry.getKey(); + final Module module = entry.getValue().getModule(); + final String filePath = file.getPath(); + + final MyModuleData moduleData = moduleDataMap.get(module); + + if (!LOG.assertTrue(moduleData != null)) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Internal error")); + success = false; + continue; + } + final File outputDirectory = moduleData.getOutputDirectory(); + final File aidlOutputDirectory = new File(outputDirectory, "generated-aidl"); + final IAndroidTarget target = moduleData.getAndroidTarget(); + + try { + final File[] sourceRoots = getSourceRootsForModuleAndDependencies(module); + final String[] sourceRootPaths = toPaths(sourceRoots); + final String packageName = computePackageForFile(context, file); + + if (packageName == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Cannot compute package for file", + filePath)); + success = false; + continue; + } + + final File outputFile = new File(aidlOutputDirectory, packageName.replace('.', File.separatorChar) + + File.separator + FileUtil.getNameWithoutExtension(file) + ".java"); + final String outputFilePath = outputFile.getPath(); + final Map> messages = + AndroidIdl.execute(target, filePath, outputFilePath, sourceRootPaths); + + addMessages(context, messages, filePath); + + if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) { + success = false; + continue; + } + + final String moduleName = getCannonicalModuleName(module); + final SourceToOutputMapping sourceToOutputMap = context.getDataManager().getSourceToOutputMap(moduleName, false); + sourceToOutputMap.update(filePath, outputFilePath); + } + catch (final IOException e) { + reportExceptionError(context, filePath, e); + success = false; + } + } + return success; + } + + private static boolean runRenderscriptCompiler(@NotNull final CompileContext context, + @NotNull Map files, + @NotNull Map moduleDataMap) { + context.processMessage(new ProgressMessage("Processing Renderscript files...")); + + boolean success = true; + + for (Map.Entry entry : files.entrySet()) { + final File file = entry.getKey(); + final Module module = entry.getValue().getModule(); + + final MyModuleData moduleData = moduleDataMap.get(module); + if (!LOG.assertTrue(moduleData != null)) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Internal error")); + success = false; + continue; + } + + final File outputDirectory = moduleData.getOutputDirectory(); + final File rsOutputDirectory = new File(outputDirectory, "generated-rs"); + final File generatedResourcesDir = new File(outputDirectory, "generated-resources"); + + final IAndroidTarget target = moduleData.getAndroidTarget(); + final String sdkLocation = moduleData.getSdkLocation(); + final String filePath = file.getPath(); + + File tmpOutputDirectory = null; + + try { + tmpOutputDirectory = FileUtil.createTempDirectory("generated-rs-temp", null); + final String depFolderPath = getDependencyFolder(context, file, tmpOutputDirectory); + final File rawDir = new File(generatedResourcesDir, "raw"); + + final Map> messages = + AndroidRenderscript.execute(sdkLocation, target, filePath, tmpOutputDirectory.getPath(), depFolderPath, rawDir.getPath()); + + addMessages(context, messages, filePath); + + if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) { + success = false; + } + else { + final List newFiles = new ArrayList(); + AndroidCommonUtils.moveAllFiles(tmpOutputDirectory, rsOutputDirectory, newFiles); + + final File bcFile = new File(rawDir, FileUtil.getNameWithoutExtension(file) + ".bc"); + if (bcFile.exists()) { + newFiles.add(bcFile); + } + final List newFilePaths = Arrays.asList(toPaths(newFiles.toArray(new File[newFiles.size()]))); + + final String moduleName = getCannonicalModuleName(module); + final SourceToOutputMapping sourceToOutputMap = context.getDataManager().getSourceToOutputMap(moduleName, false); + sourceToOutputMap.update(filePath, newFilePaths); + } + } + catch (IOException e) { + reportExceptionError(context, filePath, e); + success = false; + } + finally { + if (tmpOutputDirectory != null) { + FileUtil.delete(tmpOutputDirectory); + } + } + } + return success; + } + + private static void reportExceptionError(@NotNull CompileContext context, @NotNull String filePath, @NotNull Exception exception) { + final String message = exception.getMessage(); + + if (message != null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message, filePath)); + LOG.debug(exception); + } + else { + context.processMessage(new CompilerMessage(BUILDER_NAME, exception)); + } + } + + @NotNull + private static String getCannonicalModuleName(@NotNull Module module) { + return module.getName().toLowerCase(Locale.US); + } + + @Nullable + private static String getDependencyFolder(@NotNull CompileContext context, @NotNull File sourceFile, @NotNull File genFolder) { + final RootDescriptor descriptor = context.getRootsIndex().getModuleAndRoot(sourceFile); + if (descriptor == null) { + return null; + } + final File sourceRoot = descriptor.root; + + final File parent = FileUtil.getParentFile(sourceFile); + if (parent == null) { + return null; + } + + if (parent.equals(sourceRoot)) { + return genFolder.getPath(); + } + final String relativePath = FileUtil.getRelativePath(sourceRoot, parent); + assert relativePath != null; + return genFolder.getPath() + '/' + relativePath; + } + + @Nullable + private static Map computeModuleDatas(@NotNull Collection modules, @NotNull CompileContext context) + throws Exception { + final Map moduleDataMap = new HashMap(); + + boolean success = true; + + for (Module module : modules) { + + final Sdk sdk = module.getSdk(); + if (!(sdk instanceof AndroidSdk)) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Android SDK is not specified for module " + module.getName())); + success = false; + continue; + } + final AndroidSdk androidSdk = (AndroidSdk)sdk; + + final IAndroidTarget target = parseAndroidTarget(androidSdk, context); + if (target == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Android SDK is invalid or not specified for module " + module.getName())); + success = false; + continue; + } + + final File outputDir = context.getProjectPaths().getModuleOutputDir(module, false); + if (outputDir == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Cannot find output directory for module " + module.getName())); + success = false; + continue; + } + + moduleDataMap.put(module, new MyModuleData(outputDir, androidSdk.getSdkPath(), target)); + } + + return success ? moduleDataMap : null; + } + + private static void addMessages(@NotNull CompileContext context, + @NotNull Map> messages, + @Nullable String sourcePath) { + for (Map.Entry> entry : messages.entrySet()) { + final AndroidCompilerMessageKind kind = entry.getKey(); + final BuildMessage.Kind buildMessageKind = toBuildMessageKind(kind); + + if (buildMessageKind == null) { + continue; + } + + for (String message : entry.getValue()) { + context.processMessage(new CompilerMessage(BUILDER_NAME, buildMessageKind, message, sourcePath)); + } + } + } + + @Nullable + private static BuildMessage.Kind toBuildMessageKind(@NotNull AndroidCompilerMessageKind kind) { + switch (kind) { + case ERROR: + return BuildMessage.Kind.ERROR; + case INFORMATION: + return BuildMessage.Kind.INFO; + case WARNING: + return BuildMessage.Kind.WARNING; + default: + LOG.error("unknown AndroidCompilerMessageKind object " + kind); + return null; + } + } + + @Nullable + private static String computePackageForFile(@NotNull CompileContext context, @NotNull File file) throws IOException { + final RootDescriptor descriptor = context.getRootsIndex().getModuleAndRoot(file); + if (descriptor == null) { + return null; + } + + final String relPath = FileUtil.getRelativePath(descriptor.root, FileUtil.getParentFile(file)); + if (relPath == null) { + return null; + } + + return FileUtil.toSystemIndependentName(relPath).replace('/', '.'); + } + + @Nullable + private static IAndroidTarget parseAndroidTarget(@NotNull AndroidSdk sdk, @NotNull CompileContext context) { + final String targetHashString = sdk.getBuildTargetHashString(); + if (targetHashString == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Cannot parse SDK " + sdk.getName() + ": build target is not specified")); + return null; + } + + final MessageBuildingSdkLog log = new MessageBuildingSdkLog(); + final SdkManager manager = AndroidCommonUtils.createSdkManager(sdk.getSdkPath(), log); + + if (manager == null) { + final String message = log.getErrorMessage(); + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Android SDK is parsed incorrectly." + + (message.length() > 0 ? " Parsing log:\n" + message : ""))); + return null; + } + + final IAndroidTarget target = manager.getTargetFromHashString(targetHashString); + if (target == null) { + context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, + "Cannot parse SDK '" + sdk.getName() + "': unknown target " + targetHashString)); + return null; + } + return target; + } + + private static void fillSourceRoots(@NotNull Module module, @NotNull Set visited, @NotNull Set result) + throws IOException { + visited.add(module); + final AndroidFacet facet = getFacet(module); + File resDir = null; + + if (facet != null) { + resDir = facet.getResourceDir(); + if (resDir != null) { + resDir = resDir.getCanonicalFile(); + } + } + + for (String sourceRootPath : module.getSourceRoots()) { + final File sourceRoot = new File(sourceRootPath).getCanonicalFile(); + + if (!sourceRoot.equals(resDir)) { + result.add(sourceRoot); + } + } + + for (ClasspathItem classpathItem : module.getClasspath(ClasspathKind.PRODUCTION_COMPILE)) { + if (classpathItem instanceof Module) { + final Module depModule = (Module)classpathItem; + + if (!visited.contains(depModule)) { + fillSourceRoots(depModule, visited, result); + } + } + } + } + + @NotNull + public static File[] getSourceRootsForModuleAndDependencies(@NotNull Module module) throws IOException { + Set result = new HashSet(); + fillSourceRoots(module, new HashSet(), result); + return result.toArray(new File[result.size()]); + } + + @Override + public String getDescription() { + return "Android Builder"; + } + + @Nullable + private static AndroidFacet getFacet(@NotNull Module module) { + AndroidFacet androidFacet = null; + + for (Facet facet : module.getFacets().values()) { + if (facet instanceof AndroidFacet) { + androidFacet = (AndroidFacet)facet; + } + } + return androidFacet; + } + + @NotNull + private static String[] toPaths(@NotNull File[] files) { + final String[] result = new String[files.length]; + + for (int i = 0; i < result.length; i++) { + result[i] = files[i].getPath(); + } + return result; + } + + private static class MyModuleData { + private final File myOutputDirectory; + private final String mySdkLocation; + private final IAndroidTarget myAndroidTarget; + + private MyModuleData(@NotNull File outputDirectory, + @NotNull String sdkLocation, + @NotNull IAndroidTarget androidTarget) { + myOutputDirectory = outputDirectory; + mySdkLocation = sdkLocation; + myAndroidTarget = androidTarget; + } + + @NotNull + public File getOutputDirectory() { + return myOutputDirectory; + } + + @NotNull + public IAndroidTarget getAndroidTarget() { + return myAndroidTarget; + } + + @NotNull + public String getSdkLocation() { + return mySdkLocation; + } + } +} diff --git a/plugins/android/src/META-INF/plugin.xml b/plugins/android/src/META-INF/plugin.xml index e6e625de60e8..67a616094d46 100644 --- a/plugins/android/src/META-INF/plugin.xml +++ b/plugins/android/src/META-INF/plugin.xml @@ -189,6 +189,12 @@ + + + + + +