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 extends ModuleLevelBuilder> 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 @@
+
+
+
+
+
+