diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/FormsParsing.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/FormsParsing.java
index 728b7a922925..f6c150290f0c 100644
--- a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/FormsParsing.java
+++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/FormsParsing.java
@@ -14,7 +14,7 @@ import java.util.Properties;
* @author Eugene Zhuravlev
* Date: 10/12/11
*/
-class FormsParsing {
+public class FormsParsing {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.java.FormsParsing");
private static final String FORM_TAG = "form";
diff --git a/plugins/android/jps-plugin/android-jps-plugin.iml b/plugins/android/jps-plugin/android-jps-plugin.iml
index a2f02ccd0c76..f88dae99cac0 100644
--- a/plugins/android/jps-plugin/android-jps-plugin.iml
+++ b/plugins/android/jps-plugin/android-jps-plugin.iml
@@ -12,6 +12,7 @@
+
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
index 81b9e6eba3de..52c990ddb6c6 100644
--- a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java
+++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidBuilderService.java
@@ -15,6 +15,7 @@ public class AndroidBuilderService extends BuilderService {
@NotNull
@Override
public List extends ModuleLevelBuilder> createModuleLevelBuilders(ExecutorService executorService) {
- return Arrays.asList(new AndroidSourceGeneratingBuilder());
+ return Arrays.asList(new AndroidSourceGeneratingBuilder(),
+ new AndroidDexBuilder());
}
}
diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidDexBuilder.java b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidDexBuilder.java
new file mode 100644
index 000000000000..7b918264c08a
--- /dev/null
+++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidDexBuilder.java
@@ -0,0 +1,215 @@
+package org.jetbrains.jps.android;
+
+import com.android.sdklib.IAndroidTarget;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.containers.HashMap;
+import com.intellij.util.containers.HashSet;
+import org.jetbrains.android.compiler.tools.AndroidDxRunner;
+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.jps.Module;
+import org.jetbrains.jps.ModuleChunk;
+import org.jetbrains.jps.ProjectPaths;
+import org.jetbrains.jps.Sdk;
+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.server.ClasspathBootstrap;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.*;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+
+// todo: change output folders
+// todo: save validity state
+
+public class AndroidDexBuilder extends ModuleLevelBuilder {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.android.AndroidDexBuilder");
+
+ @NonNls private static final String BUILDER_NAME = "android-dex";
+
+ protected AndroidDexBuilder() {
+ super(BuilderCategory.CLASS_POST_PROCESSOR);
+ }
+
+ @Override
+ public ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException {
+ if (context.isCompilingTests() || !AndroidJpsUtil.containsAndroidFacet(chunk)) {
+ return ModuleLevelBuilder.ExitCode.OK;
+ }
+ context.processMessage(new ProgressMessage("Executing DEX"));
+
+ try {
+ return doBuild(context, chunk);
+ }
+ 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 ExitCode doBuild(CompileContext context, ModuleChunk chunk) {
+ boolean success = true;
+
+ for (Module module : chunk.getModules()) {
+ final AndroidFacet facet = AndroidJpsUtil.getFacet(module);
+ if (facet == null || facet.getLibrary()) {
+ continue;
+ }
+
+ 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 = AndroidJpsUtil.parseAndroidTarget(androidSdk, context, BUILDER_NAME);
+ 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 ProjectPaths projectPaths = context.getProjectPaths();
+ final File dexOutputDir = AndroidJpsUtil.getOutputDirectoryForPackagedFiles(projectPaths, module);
+
+ if (dexOutputDir == null) {
+ context.processMessage(
+ new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Output directory is not specified for module " + module.getName()));
+ success = false;
+ continue;
+ }
+
+ // todo: support proguard
+
+ final File classesDir = projectPaths.getModuleOutputDir(module, false);
+
+ if (classesDir == null || !classesDir.isDirectory()) {
+ context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.INFO, "Dex won't be launched for module " +
+ module.getName() +
+ " because it doesn't contain compiled files"));
+ continue;
+ }
+
+ final Set fileSet = new HashSet();
+ AndroidJpsUtil.addSubdirectories(classesDir, fileSet);
+ fileSet.addAll(AndroidJpsUtil.getExternalLibraries(projectPaths, module));
+
+ for (String filePath : AndroidJpsUtil.getClassdirsOfDependentModules(projectPaths, module)) {
+ if (!classesDir.getPath().equals(filePath)) {
+ fileSet.add(filePath);
+ }
+ }
+
+ if (facet.isLibrary()) {
+ final File testsClassDir = projectPaths.getModuleOutputDir(module, true);
+
+ if (testsClassDir != null && testsClassDir.isDirectory()) {
+ AndroidJpsUtil.addSubdirectories(testsClassDir, fileSet);
+ }
+ }
+
+ final String[] files = new String[fileSet.size()];
+ int i = 0;
+ for (String filePath : fileSet) {
+ files[i++] = FileUtil.toSystemDependentName(filePath);
+ }
+
+ if (!runDex(androidSdk, target, dexOutputDir.getPath(), files, context)) {
+ success = false;
+ }
+ }
+ return success ? ExitCode.OK : ExitCode.ABORT;
+ }
+
+ @Override
+ public String getName() {
+ return BUILDER_NAME;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Android Dex Builder";
+ }
+
+ public static boolean runDex(@NotNull AndroidSdk sdk,
+ @NotNull IAndroidTarget target,
+ @NotNull String outputDir,
+ @NotNull String[] compileTargets,
+ @NotNull CompileContext context) {
+ @SuppressWarnings("deprecation")
+ final String dxJarPath = FileUtil.toSystemDependentName(target.getPath(IAndroidTarget.DX_JAR));
+
+ final File dxJar = new File(dxJarPath);
+ if (!dxJar.isFile()) {
+ context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Cannot find file " + dxJarPath));
+ return false;
+ }
+
+ final String outFilePath = outputDir + File.separatorChar + AndroidCommonUtils.CLASSES_FILE_NAME;
+
+ final List programParamList = new ArrayList();
+ programParamList.add(dxJarPath);
+ programParamList.add(outFilePath);
+ programParamList.addAll(Arrays.asList(compileTargets));
+ programParamList.add("--exclude");
+
+ final List classPath = new ArrayList();
+ classPath.add(ClasspathBootstrap.getResourcePath(AndroidDxRunner.class).getPath());
+ classPath.add(ClasspathBootstrap.getResourcePath(FileUtil.class).getPath());
+
+ if (!new File(outFilePath).delete()) {
+ context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.WARNING, "Cannot delete file " + outFilePath));
+ }
+
+ // todo: pass additional vm params and max heap size from settings
+
+ final List commandLine = ExternalProcessUtil
+ .buildJavaCommandLine(sdk.getJavaExecutable(), AndroidDxRunner.class.getName(), Collections.emptyList(), classPath,
+ Arrays.asList("-Xmx1024M"), programParamList);
+
+ LOG.info(AndroidCommonUtils.command2string(commandLine));
+
+ try {
+ final Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(commandLine));
+
+ final HashMap> messages = new HashMap>(3);
+ messages.put(AndroidCompilerMessageKind.ERROR, new ArrayList());
+ messages.put(AndroidCompilerMessageKind.WARNING, new ArrayList());
+ messages.put(AndroidCompilerMessageKind.INFORMATION, new ArrayList());
+
+ AndroidCommonUtils.handleDexCompilationResult(process, outFilePath, messages);
+
+ AndroidJpsUtil.addMessages(context, messages, null, BUILDER_NAME);
+
+ return messages.get(AndroidCompilerMessageKind.ERROR).size() == 0;
+ }
+ catch (IOException e) {
+ AndroidJpsUtil.reportExceptionError(context, null, e, BUILDER_NAME);
+ return false;
+ }
+ }
+}
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
index e9530f165ee7..eb70743a5e68 100644
--- a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy
+++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacet.groovy
@@ -5,20 +5,46 @@ import org.jetbrains.jps.Module
import org.jetbrains.jps.idea.Facet
/**
- * @author nik
+ * @author Eugene.Kudelevsky
*/
class AndroidFacet extends Facet {
final Module module
- final String resFolderRelativePath
- AndroidFacet(Module module, String name, String resFolderRelativePath) {
+ boolean library;
+
+ String resFolderRelativePath
+ String resFolderForCompilationRelativePath
+ boolean useCustomResFolderForCompilation;
+
+ String manifestRelativePath;
+ String manifestForCompilationRelativePath;
+ boolean useCustomManifestForCompilation;
+ boolean packTestCode;
+
+ AndroidFacet(Module module, String name) {
this.module = module
this.name = name;
this.resFolderRelativePath = resFolderRelativePath
}
- File getResourceDir() {
- return findFileByRelativeModulePath(resFolderRelativePath, true)
+ File getResourceDir() throws IOException {
+ def resDir = findFileByRelativeModulePath(resFolderRelativePath, true)
+ return resDir != null ? resDir.getCanonicalFile() : null;
+ }
+
+ File getResourceDirForCompilation() throws IOException {
+ def resDir = findFileByRelativeModulePath(resFolderForCompilationRelativePath, false)
+ return resDir != null ? resDir.getCanonicalFile() : null;
+ }
+
+ File getManifestFile() throws IOException {
+ def manifestFile = findFileByRelativeModulePath(manifestRelativePath, true);
+ return manifestFile != null ? manifestFile.getCanonicalFile() : null;
+ }
+
+ File getManifestFileForCompilation() throws IOException {
+ def manifestFile = findFileByRelativeModulePath(manifestForCompilationRelativePath, false);
+ return manifestFile != null ? manifestFile.getCanonicalFile() : null;
}
private File findFileByRelativeModulePath(String relativePath, boolean lookInContentRoot) {
@@ -32,7 +58,7 @@ class AndroidFacet extends Facet {
}
if (lookInContentRoot) {
- module.contentRoots.each {
+ module.contentRoots.each {String contentRoot ->
def absPath = FileUtil.toSystemIndependentName(contentRoot + relativePath)
def f = new File(absPath)
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
index ca555e24e37e..09c4e55c414e 100644
--- a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy
+++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidFacetType.groovy
@@ -17,6 +17,38 @@ class AndroidFacetType extends FacetTypeService {
@Override
Facet createFacet(Module module, String name, Node facetConfiguration, MacroExpander macroExpander) {
- return new AndroidFacet(module, name, "");
+ def facet = new AndroidFacet(module, name);
+
+ facetConfiguration.each {Node child ->
+ String value = child."@value"
+
+ switch (child."@name") {
+ case "RES_FOLDER_RELATIVE_PATH":
+ facet.resFolderRelativePath = value
+ break
+ case "USE_CUSTOM_APK_RESOURCE_FOLDER":
+ facet.useCustomResFolderForCompilation = Boolean.parseBoolean(value)
+ break
+ case "CUSTOM_APK_RESOURCE_FOLDER":
+ facet.resFolderForCompilationRelativePath = value
+ break
+ case "LIBRARY_PROJECT":
+ facet.library = Boolean.parseBoolean(value)
+ break
+ case "MANIFEST_FILE_RELATIVE_PATH":
+ facet.manifestRelativePath = value
+ break
+ case "USE_CUSTOM_COMPILER_MANIFEST":
+ facet.useCustomManifestForCompilation = Boolean.parseBoolean(value)
+ break
+ case "CUSTOM_COMPILER_MANIFEST":
+ facet.manifestForCompilationRelativePath = value
+ break
+ case "PACK_TEST_CODE":
+ facet.packTestCode = Boolean.parseBoolean(value)
+ break
+ }
+ }
+ return facet;
}
}
diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidJpsUtil.java b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidJpsUtil.java
new file mode 100644
index 000000000000..6d8ef3dc6cff
--- /dev/null
+++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidJpsUtil.java
@@ -0,0 +1,241 @@
+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.Processor;
+import com.intellij.util.containers.HashSet;
+import org.jetbrains.android.sdk.MessageBuildingSdkLog;
+import org.jetbrains.android.util.AndroidCommonUtils;
+import org.jetbrains.android.util.AndroidCompilerMessageKind;
+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.CompileContext;
+import org.jetbrains.jps.incremental.messages.BuildMessage;
+import org.jetbrains.jps.incremental.messages.CompilerMessage;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+class AndroidJpsUtil {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.android.AndroidJpsUtil");
+
+ private AndroidJpsUtil() {
+ }
+
+ @Nullable
+ public 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
+ public 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;
+ }
+
+ @Nullable
+ public static File getOutputDirectoryForPackagedFiles(@NotNull ProjectPaths paths, @NotNull Module module) {
+ // todo: return build directory for mavenized modules to place .dex and .apk files into target dir (not target/classes)
+ return paths.getModuleOutputDir(module, false);
+ }
+
+ public static void addSubdirectories(@NotNull File baseDir, @NotNull Collection result) {
+ // only include files inside packages
+ final File[] children = baseDir.listFiles();
+
+ if (children != null) {
+ for (File child : children) {
+ if (child.isDirectory()) {
+ result.add(child.getPath());
+ }
+ }
+ }
+ }
+
+ @NotNull
+ public static Set getExternalLibraries(@NotNull ProjectPaths paths, @NotNull Module module) {
+ final Set result = new HashSet();
+ fillClasspath(paths, module, null, result, new HashSet(), false);
+ return result;
+ }
+
+ @NotNull
+ public static Set getClassdirsOfDependentModules(@NotNull ProjectPaths paths, @NotNull Module module) {
+ final Set result = new HashSet();
+ fillClasspath(paths, module, result, null, new HashSet(), false);
+ return result;
+ }
+
+ private static void fillClasspath(@NotNull ProjectPaths paths,
+ @NotNull final Module module,
+ @Nullable final Set outputDirs,
+ @Nullable final Set libraries,
+ @NotNull final Set visitedModules,
+ final boolean exportedLibrariesOnly) {
+ if (!visitedModules.add(module.getName())) {
+ return;
+ }
+
+ if (libraries != null) {
+ for (ClasspathItem item : module.getClasspath(ClasspathKind.PRODUCTION_COMPILE, exportedLibrariesOnly)) {
+ if (item instanceof Library && !(item instanceof Sdk)) {
+ for (String filePath : item.getClasspathRoots(ClasspathKind.PRODUCTION_COMPILE)) {
+ final File file = new File(filePath);
+
+ if (file.exists()) {
+ collectClassFilesAndJars(filePath, libraries);
+ }
+ }
+ }
+ }
+ }
+
+ for (ClasspathItem item : module.getClasspath(ClasspathKind.PRODUCTION_COMPILE, false)) {
+ if (item instanceof Module) {
+ final Module depModule = (Module)item;
+ final AndroidFacet depFacet = getFacet(depModule);
+ final boolean depLibrary = depFacet != null && depFacet.isLibrary();
+ final File depClassDir = paths.getModuleOutputDir(depModule, false);
+
+ if (outputDirs != null && depClassDir != null) {
+ if (depLibrary) {
+ final File packagedClassesJar = new File(depClassDir, AndroidCommonUtils.CLASSES_JAR_FILE_NAME);
+
+ if (packagedClassesJar.isDirectory()) {
+ outputDirs.add(packagedClassesJar.getPath());
+ }
+ }
+ else if (depFacet == null && depClassDir.isDirectory()) {
+ // do not support android-app->android-app compile dependencies
+ outputDirs.add(depClassDir.getPath());
+ }
+ }
+ fillClasspath(paths, depModule, outputDirs, libraries, visitedModules, !depLibrary || exportedLibrariesOnly);
+ }
+ }
+ }
+
+ private static void collectClassFilesAndJars(@NotNull String root,
+ @NotNull final Set result) {
+ FileUtil.processFilesRecursively(new File(root), new Processor() {
+ @Override
+ public boolean process(File file) {
+ if (file.isFile()) {
+ final String ext = FileUtil.getExtension(file.getName());
+
+ if ("jar".equals(ext) || "class".equals(ext)) {
+ result.add(file.getPath());
+ }
+ }
+ return true;
+ }
+ });
+ }
+
+ @Nullable
+ public static IAndroidTarget parseAndroidTarget(@NotNull AndroidSdk sdk, @NotNull CompileContext context, @NotNull String builderName) {
+ final String targetHashString = sdk.getBuildTargetHashString();
+ if (targetHashString == null) {
+ context.processMessage(new CompilerMessage(builderName, 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(builderName, 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(builderName, BuildMessage.Kind.ERROR,
+ "Cannot parse SDK '" + sdk.getName() + "': unknown target " + targetHashString));
+ return null;
+ }
+ return target;
+ }
+
+ public static void addMessages(@NotNull CompileContext context,
+ @NotNull Map> messages,
+ @Nullable String sourcePath,
+ @NotNull String builderName) {
+ 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(builderName, 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;
+ }
+ }
+
+ public static void reportExceptionError(@NotNull CompileContext context,
+ @Nullable String filePath,
+ @NotNull Exception exception,
+ @NotNull String builderName) {
+ final String message = exception.getMessage();
+
+ if (message != null) {
+ context.processMessage(new CompilerMessage(builderName, BuildMessage.Kind.ERROR, message, filePath));
+ LOG.debug(exception);
+ }
+ else {
+ context.processMessage(new CompilerMessage(builderName, exception));
+ }
+ }
+
+ public static boolean containsAndroidFacet(@NotNull ModuleChunk chunk) {
+ for (Module module : chunk.getModules()) {
+ if (getFacet(module) != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidResourceNamesStateStorage.java b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidResourceNamesStateStorage.java
new file mode 100644
index 000000000000..41d8cd3d12d1
--- /dev/null
+++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidResourceNamesStateStorage.java
@@ -0,0 +1,44 @@
+package org.jetbrains.jps.android;
+
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.EnumeratorStringDescriptor;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.incremental.Paths;
+import org.jetbrains.jps.incremental.storage.AbstractStateStorage;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class AndroidResourceNamesStateStorage extends AbstractStateStorage {
+
+ @NonNls private static final String ANDROID_STORAGE_DIR = "android_resource_names";
+ @NonNls private static final String RESOURCE_NAMES_STORAGE = "resource_names";
+
+ public AndroidResourceNamesStateStorage(@NotNull String projectName) throws IOException {
+ super(getStorageFile(projectName), new EnumeratorStringDescriptor(), new MyDataExternalizer());
+ }
+
+ @NotNull
+ private static File getStorageFile(String projectName) {
+ return new File(new File(Paths.getDataStorageRoot(projectName), ANDROID_STORAGE_DIR), RESOURCE_NAMES_STORAGE);
+ }
+
+ private static class MyDataExternalizer implements DataExternalizer {
+
+ @Override
+ public void save(DataOutput out, AndroidResourceNamesValidityState value) throws IOException {
+ value.save(out);
+ }
+
+ @Override
+ public AndroidResourceNamesValidityState read(DataInput in) throws IOException {
+ return new AndroidResourceNamesValidityState(in);
+ }
+ }
+}
diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidResourceNamesValidityState.java b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidResourceNamesValidityState.java
new file mode 100644
index 000000000000..01c27ff26cc3
--- /dev/null
+++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidResourceNamesValidityState.java
@@ -0,0 +1,59 @@
+package org.jetbrains.jps.android;
+
+import com.intellij.util.containers.HashSet;
+import org.jetbrains.android.util.ResourceEntry;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.incremental.storage.ValidityState;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class AndroidResourceNamesValidityState implements ValidityState {
+ private final Set myResourcesSet;
+ private final String myPackageName;
+
+ public AndroidResourceNamesValidityState(@NotNull Collection resourcesSet, @NotNull String packageName) {
+ myResourcesSet = new HashSet(resourcesSet);
+ myPackageName = packageName;
+ }
+
+ public AndroidResourceNamesValidityState(@NotNull DataInput in) throws IOException {
+ myPackageName = in.readUTF();
+ myResourcesSet = new HashSet();
+
+ final int resourceCount = in.readInt();
+
+ for (int i = 0; i < resourceCount; i++) {
+ final String resType = in.readUTF();
+ final String resName = in.readUTF();
+ myResourcesSet.add(new ResourceEntry(resType, resName));
+ }
+ }
+
+ @Override
+ public boolean equalsTo(ValidityState otherState) {
+ if (!(otherState instanceof AndroidResourceNamesValidityState)) {
+ return false;
+ }
+ final AndroidResourceNamesValidityState otherAndroidState = (AndroidResourceNamesValidityState)otherState;
+ return otherAndroidState.myPackageName.equals(myPackageName) &&
+ otherAndroidState.myResourcesSet.equals(myResourcesSet);
+ }
+
+ @Override
+ public void save(DataOutput out) throws IOException {
+ out.writeUTF(myPackageName);
+ out.writeInt(myResourcesSet.size());
+
+ for (ResourceEntry resourceEntry : myResourcesSet) {
+ out.writeUTF(resourceEntry.getType());
+ out.writeUTF(resourceEntry.getName());
+ }
+ }
+}
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
index 77abb5082d17..06dec68230cd 100644
--- a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java
+++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidSourceGeneratingBuilder.java
@@ -1,42 +1,46 @@
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.Ref;
import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
+import org.jetbrains.android.compiler.tools.AndroidApt;
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.java.FormsParsing;
+import org.jetbrains.jps.incremental.java.JavaBuilder;
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.io.*;
import java.util.*;
/**
* @author Eugene.Kudelevsky
*/
+
+// todo: change output folders
+
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";
+ @NonNls private static final String MANIFEST_TAG = "manifest";
+ @NonNls private static final String PACKAGE_MANIFEST_ATTRIBUTE = "package";
public AndroidSourceGeneratingBuilder() {
super(BuilderCategory.SOURCE_GENERATOR);
@@ -49,16 +53,13 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
@Override
public ModuleLevelBuilder.ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException {
- if (context.isCompilingTests()) {
+ if (context.isCompilingTests() || !AndroidJpsUtil.containsAndroidFacet(chunk)) {
return ModuleLevelBuilder.ExitCode.OK;
}
try {
return doBuild(context, chunk);
}
- catch (ProjectBuildException e) {
- throw e;
- }
catch (Exception e) {
String message = e.getMessage();
@@ -73,15 +74,14 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
}
}
- 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();
+ private static ModuleLevelBuilder.ExitCode doBuild(CompileContext context, ModuleChunk chunk) throws IOException {
+ final Map idlFilesToCompile = new HashMap();
+ final Map rsFilesToCompile = new HashMap();
context.processFilesToRecompile(chunk, new FileProcessor() {
@Override
public boolean apply(Module module, File file, String sourceRoot) throws IOException {
- final AndroidFacet facet = getFacet(module);
+ final AndroidFacet facet = AndroidJpsUtil.getFacet(module);
if (facet == null) {
return true;
@@ -89,45 +89,48 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
final String ext = FileUtil.getExtension(file.getName());
if (AIDL_EXTENSION.equals(ext)) {
- idlFilesToCompile.put(file, facet);
- modules.add(facet.getModule());
+ idlFilesToCompile.put(file, facet.getModule());
}
else if (RENDERSCRIPT_EXTENSION.equals(ext)) {
- rsFilesToCompile.put(file, facet);
- modules.add(facet.getModule());
+ rsFilesToCompile.put(file, facet.getModule());
}
return true;
}
});
- final Map moduleDataMap = computeModuleDatas(modules, context);
+ final Map moduleDataMap = computeModuleDatas(chunk.getModules(), context);
- if (moduleDataMap == null) {
- return ExitCode.OK;
+ if (moduleDataMap == null || moduleDataMap.size() == 0) {
+ return ExitCode.ABORT;
}
-
+ boolean success = true;
+
if (!runAidlCompiler(context, idlFilesToCompile, moduleDataMap)) {
- return ExitCode.OK;
+ success = false;
}
if (!runRenderscriptCompiler(context, rsFilesToCompile, moduleDataMap)) {
- return ExitCode.OK;
+ success = false;
}
- return ExitCode.OK;
+ if (!runAaptCompiler(context, moduleDataMap)) {
+ success = false;
+ }
+
+ return success ? ExitCode.OK : ExitCode.ABORT;
}
private static boolean runAidlCompiler(@NotNull final CompileContext context,
- @NotNull Map files,
+ @NotNull Map files,
@NotNull Map moduleDataMap) {
context.processMessage(new ProgressMessage("Processing AIDL files..."));
-
+
boolean success = true;
- for (Map.Entry entry : files.entrySet()) {
+ for (Map.Entry entry : files.entrySet()) {
final File file = entry.getKey();
- final Module module = entry.getValue().getModule();
+ final Module module = entry.getValue();
final String filePath = file.getPath();
final MyModuleData moduleData = moduleDataMap.get(module);
@@ -143,7 +146,7 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
try {
final File[] sourceRoots = getSourceRootsForModuleAndDependencies(module);
- final String[] sourceRootPaths = toPaths(sourceRoots);
+ final String[] sourceRootPaths = AndroidJpsUtil.toPaths(sourceRoots);
final String packageName = computePackageForFile(context, file);
if (packageName == null) {
@@ -159,19 +162,21 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
final Map> messages =
AndroidIdl.execute(target, filePath, outputFilePath, sourceRootPaths);
- addMessages(context, messages, filePath);
+ AndroidJpsUtil.addMessages(context, messages, filePath, BUILDER_NAME);
if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) {
success = false;
- continue;
}
+ else {
+ final String moduleName = getCannonicalModuleName(module);
+ final SourceToOutputMapping sourceToOutputMap = context.getDataManager().getSourceToOutputMap(moduleName, false);
+ sourceToOutputMap.update(filePath, outputFilePath);
- final String moduleName = getCannonicalModuleName(module);
- final SourceToOutputMapping sourceToOutputMap = context.getDataManager().getSourceToOutputMap(moduleName, false);
- sourceToOutputMap.update(filePath, outputFilePath);
+ JavaBuilder.addTempSourcePathRoot(context, aidlOutputDirectory);
+ }
}
catch (final IOException e) {
- reportExceptionError(context, filePath, e);
+ AndroidJpsUtil.reportExceptionError(context, filePath, e, BUILDER_NAME);
success = false;
}
}
@@ -179,15 +184,15 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
}
private static boolean runRenderscriptCompiler(@NotNull final CompileContext context,
- @NotNull Map files,
+ @NotNull Map files,
@NotNull Map moduleDataMap) {
context.processMessage(new ProgressMessage("Processing Renderscript files..."));
boolean success = true;
- for (Map.Entry entry : files.entrySet()) {
+ for (Map.Entry entry : files.entrySet()) {
final File file = entry.getKey();
- final Module module = entry.getValue().getModule();
+ final Module module = entry.getValue();
final MyModuleData moduleData = moduleDataMap.get(module);
if (!LOG.assertTrue(moduleData != null)) {
@@ -214,7 +219,7 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
final Map> messages =
AndroidRenderscript.execute(sdkLocation, target, filePath, tmpOutputDirectory.getPath(), depFolderPath, rawDir.getPath());
- addMessages(context, messages, filePath);
+ AndroidJpsUtil.addMessages(context, messages, filePath, BUILDER_NAME);
if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) {
success = false;
@@ -227,15 +232,17 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
if (bcFile.exists()) {
newFiles.add(bcFile);
}
- final List newFilePaths = Arrays.asList(toPaths(newFiles.toArray(new File[newFiles.size()])));
+ final List newFilePaths = Arrays.asList(AndroidJpsUtil.toPaths(newFiles.toArray(new File[newFiles.size()])));
final String moduleName = getCannonicalModuleName(module);
final SourceToOutputMapping sourceToOutputMap = context.getDataManager().getSourceToOutputMap(moduleName, false);
sourceToOutputMap.update(filePath, newFilePaths);
+
+ JavaBuilder.addTempSourcePathRoot(context, rsOutputDirectory);
}
}
catch (IOException e) {
- reportExceptionError(context, filePath, e);
+ AndroidJpsUtil.reportExceptionError(context, filePath, e, BUILDER_NAME);
success = false;
}
finally {
@@ -247,15 +254,182 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
return success;
}
- private static void reportExceptionError(@NotNull CompileContext context, @NotNull String filePath, @NotNull Exception exception) {
- final String message = exception.getMessage();
+ // todo: save validity state
+ private static boolean runAaptCompiler(@NotNull final CompileContext context,
+ @NotNull Map moduleDataMap) {
+ context.processMessage(new ProgressMessage("Generating R.java and Manifest.java files"));
- if (message != null) {
- context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message, filePath));
- LOG.debug(exception);
+ boolean success = true;
+
+ for (Map.Entry entry : moduleDataMap.entrySet()) {
+ final Module module = entry.getKey();
+ final MyModuleData moduleData = entry.getValue();
+ final AndroidFacet facet = moduleData.getFacet();
+
+ // todo: check if we need special strategy for maven apksources
+
+ final IAndroidTarget target = moduleData.getAndroidTarget();
+
+ try {
+ final String[] resPaths = collectResourceDirs(facet);
+ if (resPaths.length == 0) {
+ // there is no resources in the module
+ continue;
+ }
+
+ final File manifestFile = getManifestFileForCompilationPath(facet);
+ if (manifestFile == null || !manifestFile.exists()) {
+ context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR,
+ "AndroidManifest.xml file not found in the module " + module.getName()));
+ success = false;
+ continue;
+ }
+ final String packageName = parsePackageNameFromManifestFile(manifestFile);
+ if (packageName == null || packageName.length() == 0) {
+ context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR,
+ "Package is not specified in AndroidManifest.xml for module " + module.getName()));
+ success = false;
+ continue;
+ }
+
+ final Set depLibPackagesSet = getDepLibPackages(module);
+ depLibPackagesSet.remove(packageName);
+
+ final File outputDirectory = moduleData.getOutputDirectory();
+ final File aptOutputDirectory = new File(outputDirectory, "generated-aapt");
+
+ if (aptOutputDirectory.exists()) {
+ // clear directory, because it may contain obsolete files (ex. if package name was changed)
+ FileUtil.delete(aptOutputDirectory);
+ }
+
+ final Map> messages =
+ AndroidApt.compile(target, -1, manifestFile.getPath(), packageName, aptOutputDirectory.getPath(), resPaths,
+ ArrayUtil.toStringArray(depLibPackagesSet), facet.getLibrary());
+
+ AndroidJpsUtil.addMessages(context, messages, null, BUILDER_NAME);
+
+ if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) {
+ success = false;
+ }
+ else {
+ JavaBuilder.addTempSourcePathRoot(context, aptOutputDirectory);
+ }
+ }
+ catch (IOException e) {
+ AndroidJpsUtil.reportExceptionError(context, null, e, BUILDER_NAME);
+ success = false;
+ }
}
- else {
- context.processMessage(new CompilerMessage(BUILDER_NAME, exception));
+ return success;
+ }
+
+ @NotNull
+ private static Set getDepLibPackages(@NotNull Module module) throws IOException {
+ final Set result = new HashSet();
+
+ for (AndroidFacet depFacet : getAllDependentAndroidLibraries(module)) {
+ final File depManifestFile = getManifestFileForCompilationPath(depFacet);
+
+ if (depManifestFile != null) {
+ final String packageName = parsePackageNameFromManifestFile(depManifestFile);
+
+ if (packageName != null) {
+ result.add(packageName);
+ }
+ }
+ }
+ return result;
+ }
+
+ private static String[] collectResourceDirs(@NotNull AndroidFacet facet) throws IOException {
+ final List result = new ArrayList();
+
+ final File resDir = getResourceDirForCompilationPath(facet);
+ if (resDir != null) {
+ result.add(resDir.getPath());
+ }
+
+ for (AndroidFacet depFacet : getAllDependentAndroidLibraries(facet.getModule())) {
+ final File depResDir = getResourceDirForCompilationPath(depFacet);
+ if (depResDir != null) {
+ result.add(depResDir.getPath());
+ }
+ }
+ return ArrayUtil.toStringArray(result);
+ }
+
+ @Nullable
+ private static File getResourceDirForCompilationPath(@NotNull AndroidFacet facet) throws IOException {
+ return facet.getUseCustomResFolderForCompilation()
+ ? facet.getResourceDirForCompilation()
+ : facet.getResourceDir();
+ }
+
+ @Nullable
+ private static File getManifestFileForCompilationPath(@NotNull AndroidFacet facet) throws IOException {
+ return facet.getUseCustomManifestForCompilation()
+ ? facet.getManifestFileForCompilation()
+ : facet.getManifestFile();
+ }
+
+ @NotNull
+ private static List getAllDependentAndroidLibraries(@NotNull Module module) {
+ final List result = new ArrayList();
+ collectDependentAndroidLibraries(module, result, new HashSet());
+ return result;
+ }
+
+ @Nullable
+ private static String parsePackageNameFromManifestFile(@NotNull File manifestFile) throws IOException {
+ final InputStream inputStream = new BufferedInputStream(new FileInputStream(manifestFile));
+ try {
+ final Ref packageName = new Ref(null);
+ FormsParsing.parse(inputStream, new FormsParsing.IXMLBuilderAdapter() {
+ boolean processingManifestTagAttrs = false;
+
+ @Override
+ public void startElement(String name, String nsPrefix, String nsURI, String systemID, int lineNr)
+ throws Exception {
+ if (MANIFEST_TAG.equals(name)) {
+ processingManifestTagAttrs = true;
+ }
+ }
+
+ @Override
+ public void addAttribute(String key, String nsPrefix, String nsURI, String value, String type)
+ throws Exception {
+ if (value != null && PACKAGE_MANIFEST_ATTRIBUTE.equals(key)) {
+ packageName.set(value.trim());
+ }
+ }
+
+ @Override
+ public void elementAttributesProcessed(String name, String nsPrefix, String nsURI) throws Exception {
+ stop();
+ }
+ });
+
+ return packageName.get();
+ }
+ finally {
+ inputStream.close();
+ }
+ }
+
+ private static void collectDependentAndroidLibraries(@NotNull Module module,
+ @NotNull List result,
+ @NotNull Set visitedSet) {
+ for (ClasspathItem item : module.getClasspath(ClasspathKind.PRODUCTION_COMPILE, false)) {
+ if (item instanceof Module) {
+ final Module depModule = (Module)item;
+ final AndroidFacet depFacet = AndroidJpsUtil.getFacet(depModule);
+
+ if (depFacet != null && depFacet.getLibrary() && visitedSet.add(module.getName())) {
+ collectDependentAndroidLibraries(depModule, result, visitedSet);
+ result.add(0, depFacet);
+ }
+ }
}
}
@@ -286,13 +460,16 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
}
@Nullable
- private static Map computeModuleDatas(@NotNull Collection modules, @NotNull CompileContext context)
- throws Exception {
+ private static Map computeModuleDatas(@NotNull Collection modules, @NotNull CompileContext context) {
final Map moduleDataMap = new HashMap();
boolean success = true;
for (Module module : modules) {
+ final AndroidFacet facet = AndroidJpsUtil.getFacet(module);
+ if (facet == null) {
+ continue;
+ }
final Sdk sdk = module.getSdk();
if (!(sdk instanceof AndroidSdk)) {
@@ -303,7 +480,7 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
}
final AndroidSdk androidSdk = (AndroidSdk)sdk;
- final IAndroidTarget target = parseAndroidTarget(androidSdk, context);
+ final IAndroidTarget target = AndroidJpsUtil.parseAndroidTarget(androidSdk, context, BUILDER_NAME);
if (target == null) {
context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR,
"Android SDK is invalid or not specified for module " + module.getName()));
@@ -319,44 +496,12 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
continue;
}
- moduleDataMap.put(module, new MyModuleData(outputDir, androidSdk.getSdkPath(), target));
+ moduleDataMap.put(module, new MyModuleData(outputDir, androidSdk.getSdkPath(), target, facet));
}
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);
@@ -372,52 +517,22 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
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);
+ final AndroidFacet facet = AndroidJpsUtil.getFacet(module);
File resDir = null;
+ File resDirForCompilation = null;
if (facet != null) {
resDir = facet.getResourceDir();
- if (resDir != null) {
- resDir = resDir.getCanonicalFile();
- }
+ resDirForCompilation = facet.getResourceDirForCompilation();
}
for (String sourceRootPath : module.getSourceRoots()) {
final File sourceRoot = new File(sourceRootPath).getCanonicalFile();
- if (!sourceRoot.equals(resDir)) {
+ if (!sourceRoot.equals(resDir) && !sourceRoot.equals(resDirForCompilation)) {
result.add(sourceRoot);
}
}
@@ -442,42 +557,23 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
@Override
public String getDescription() {
- return "Android Builder";
+ return "Android Source Generating 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 final AndroidFacet myFacet;
private MyModuleData(@NotNull File outputDirectory,
@NotNull String sdkLocation,
- @NotNull IAndroidTarget androidTarget) {
+ @NotNull IAndroidTarget androidTarget,
+ @NotNull AndroidFacet facet) {
myOutputDirectory = outputDirectory;
mySdkLocation = sdkLocation;
myAndroidTarget = androidTarget;
+ myFacet = facet;
}
@NotNull
@@ -494,5 +590,10 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder {
public String getSdkLocation() {
return mySdkLocation;
}
+
+ @NotNull
+ public AndroidFacet getFacet() {
+ return myFacet;
+ }
}
}
diff --git a/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java b/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java
index 3b54dcf9532d..e923d46677f7 100644
--- a/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java
+++ b/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java
@@ -3,6 +3,12 @@ package org.jetbrains.android.util;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
+import com.intellij.execution.process.BaseOSProcessHandler;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.execution.process.ProcessOutputTypes;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -12,13 +18,23 @@ import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidCommonUtils {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.util.AndroidCommonUtils");
+
@NonNls public static final Object MANIFEST_JAVA_FILE_NAME = "Manifest.java";
@NonNls public static final String R_JAVA_FILENAME = "R.java";
+ @NonNls public static final String CLASSES_JAR_FILE_NAME = "classes.jar";
+ @NonNls public static final String CLASSES_FILE_NAME = "classes.dex";
+ private static final Pattern WARNING_PATTERN = Pattern.compile(".*warning.*");
+ private static final Pattern ERROR_PATTERN = Pattern.compile(".*error.*");
+ private static final Pattern EXCEPTION_PATTERN = Pattern.compile(".*exception.*");
private AndroidCommonUtils() {
}
@@ -69,4 +85,52 @@ public class AndroidCommonUtils {
}
}
}
+
+ public static void handleDexCompilationResult(@NotNull Process process,
+ @NotNull String outputFilePath,
+ @NotNull final Map> messages) {
+ final BaseOSProcessHandler handler = new BaseOSProcessHandler(process, null, null);
+ handler.addProcessListener(new ProcessAdapter() {
+ private AndroidCompilerMessageKind myCategory = null;
+
+ @Override
+ public void onTextAvailable(ProcessEvent event, Key outputType) {
+ String[] msgs = event.getText().split("\\n");
+ for (String msg : msgs) {
+ msg = msg.trim();
+ String msglc = msg.toLowerCase();
+ if (outputType == ProcessOutputTypes.STDERR) {
+ if (WARNING_PATTERN.matcher(msglc).matches()) {
+ myCategory = AndroidCompilerMessageKind.WARNING;
+ }
+ if (ERROR_PATTERN.matcher(msglc).matches() || EXCEPTION_PATTERN.matcher(msglc).matches() || myCategory == null) {
+ myCategory = AndroidCompilerMessageKind.ERROR;
+ }
+ messages.get(myCategory).add(msg);
+ }
+ else if (outputType == ProcessOutputTypes.STDOUT) {
+ if (!msglc.startsWith("processing")) {
+ messages.get(AndroidCompilerMessageKind.INFORMATION).add(msg);
+ }
+ }
+
+ LOG.info(msg);
+ }
+ }
+ });
+
+ handler.startNotify();
+ handler.waitFor();
+
+ final List errors = messages.get(AndroidCompilerMessageKind.ERROR);
+
+ if (new File(outputFilePath).isFile()) {
+ // if compilation finished correctly, show all errors as warnings
+ messages.get(AndroidCompilerMessageKind.WARNING).addAll(errors);
+ errors.clear();
+ }
+ else if (errors.size() == 0) {
+ errors.add("Cannot create classes.dex file");
+ }
+ }
}
diff --git a/plugins/android/src/org/jetbrains/android/util/ResourceEntry.java b/plugins/android/rt/src/org/jetbrains/android/util/ResourceEntry.java
similarity index 100%
rename from plugins/android/src/org/jetbrains/android/util/ResourceEntry.java
rename to plugins/android/rt/src/org/jetbrains/android/util/ResourceEntry.java
diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java
index 5da6dea2b035..dee279c57de7 100644
--- a/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java
+++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java
@@ -91,12 +91,10 @@ public class AndroidCompileUtil {
@NonNls private static final String GEN_MODULE_PREFIX = "~generated_";
@NonNls private static final String PROGUARD_CFG_FILE_NAME = "proguard.cfg";
- @NonNls public static final String CLASSES_JAR_FILE_NAME = "classes.jar";
@NonNls
private static final String[] SCALA_TEST_CONFIGURATIONS =
{"ScalaTestRunConfiguration", "SpecsRunConfiguration", "Specs2RunConfiguration"};
- public static final String CLASSES_FILE_NAME = "classes.dex";
private AndroidCompileUtil() {
}
diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidDexCompiler.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidDexCompiler.java
index b8cdc423d1d7..6ba8dce9bc1a 100644
--- a/plugins/android/src/org/jetbrains/android/compiler/AndroidDexCompiler.java
+++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidDexCompiler.java
@@ -36,6 +36,7 @@ import org.jetbrains.android.maven.AndroidMavenProvider;
import org.jetbrains.android.maven.AndroidMavenUtil;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.util.AndroidBundle;
+import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -61,7 +62,7 @@ public class AndroidDexCompiler implements ClassPostProcessingCompiler {
}
if (items != null && items.length > 0) {
- context.getProgressIndicator().setText("Generating " + AndroidCompileUtil.CLASSES_FILE_NAME + "...");
+ context.getProgressIndicator().setText("Generating " + AndroidCommonUtils.CLASSES_FILE_NAME + "...");
return new ProcessAction(context, items).compute();
}
return ProcessingItem.EMPTY_ARRAY;
@@ -214,8 +215,8 @@ public class AndroidDexCompiler implements ClassPostProcessingCompiler {
files[i++] = FileUtil.toSystemDependentName(file.getPath());
}
- Map> messages = AndroidDxWrapper
- .execute(dexItem.myModule, dexItem.myAndroidTarget, outputDirPath, files);
+ Map> messages = AndroidCompileUtil.toCompilerMessageCategoryKeys(
+ AndroidDxWrapper.execute(dexItem.myModule, dexItem.myAndroidTarget, outputDirPath, files));
addMessages(messages);
if (messages.get(CompilerMessageCategory.ERROR).isEmpty()) {
diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidLibraryPackagingCompiler.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidLibraryPackagingCompiler.java
index b3030ba3df9a..b41b3a22746b 100644
--- a/plugins/android/src/org/jetbrains/android/compiler/AndroidLibraryPackagingCompiler.java
+++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidLibraryPackagingCompiler.java
@@ -13,6 +13,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.HashSet;
import org.jetbrains.android.facet.AndroidFacet;
+import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.annotations.NotNull;
import java.io.DataInput;
@@ -87,7 +88,7 @@ public class AndroidLibraryPackagingCompiler implements ClassPostProcessingCompi
final String[] classesDirOsPaths = AndroidCompileUtil.toOsPaths(processingItem.getClassDirectories());
final String outputJarOsPath = FileUtil.toSystemDependentName(processingItem.getOutputDirectory().getPath() + '/' +
- AndroidCompileUtil.CLASSES_JAR_FILE_NAME);
+ AndroidCommonUtils.CLASSES_JAR_FILE_NAME);
final File outputJarFile = new File(outputJarOsPath);
try {
diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidPackagingCompiler.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidPackagingCompiler.java
index 3480202519ff..fed97f4fb372 100644
--- a/plugins/android/src/org/jetbrains/android/compiler/AndroidPackagingCompiler.java
+++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidPackagingCompiler.java
@@ -35,6 +35,7 @@ import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.android.util.AndroidBundle;
+import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -116,7 +117,7 @@ public class AndroidPackagingCompiler implements PackagingCompiler {
File resPackage = AndroidResourcesPackagingCompiler.getOutputFile(module, outputDir);
String resPackagePath = FileUtil.toSystemDependentName(resPackage.getPath());
- File classesDexFile = new File(outputDir.getPath(), AndroidCompileUtil.CLASSES_FILE_NAME);
+ File classesDexFile = new File(outputDir.getPath(), AndroidCommonUtils.CLASSES_FILE_NAME);
String classesDexPath = FileUtil.toSystemDependentName(classesDexFile.getPath());
AndroidPlatform platform = configuration.getAndroidPlatform();
diff --git a/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidApkBuilder.java b/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidApkBuilder.java
index 9a86acd88c5d..d27208869658 100644
--- a/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidApkBuilder.java
+++ b/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidApkBuilder.java
@@ -38,6 +38,7 @@ import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.sdk.AndroidSdkUtils;
import org.jetbrains.android.util.AndroidBundle;
+import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -229,7 +230,7 @@ public class AndroidApkBuilder {
fis.close();
}
- builder.writeFile(dexEntryFile, AndroidCompileUtil.CLASSES_FILE_NAME);
+ builder.writeFile(dexEntryFile, AndroidCommonUtils.CLASSES_FILE_NAME);
final HashSet added = new HashSet();
for (VirtualFile sourceRoot : sourceRoots) {
diff --git a/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidDxWrapper.java b/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidDxWrapper.java
index d39cc22c705d..3de13461b4bc 100644
--- a/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidDxWrapper.java
+++ b/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidDxWrapper.java
@@ -21,30 +21,24 @@ import com.intellij.execution.configurations.CommandLineBuilder;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.JavaParameters;
import com.intellij.execution.configurations.ParametersList;
-import com.intellij.execution.process.OSProcessHandler;
-import com.intellij.execution.process.ProcessAdapter;
-import com.intellij.execution.process.ProcessEvent;
-import com.intellij.execution.process.ProcessOutputTypes;
-import com.intellij.openapi.compiler.CompilerMessageCategory;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
-import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.PathUtil;
import com.intellij.util.PathsList;
import com.intellij.util.containers.HashMap;
-import org.jetbrains.android.compiler.AndroidCompileUtil;
import org.jetbrains.android.compiler.AndroidDexCompilerConfiguration;
import org.jetbrains.android.util.AndroidBundle;
+import org.jetbrains.android.util.AndroidCommonUtils;
+import org.jetbrains.android.util.AndroidCompilerMessageKind;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.regex.Pattern;
/**
* @author Eugene.Kudelevsky
@@ -52,31 +46,27 @@ import java.util.regex.Pattern;
public class AndroidDxWrapper {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.tools.AndroidDx");
- private static final Pattern WARNING_PATTERN = Pattern.compile(".*warning.*");
- private static final Pattern ERROR_PATTERN = Pattern.compile(".*error.*");
- private static final Pattern EXCEPTION_PATTERN = Pattern.compile(".*exception.*");
-
private AndroidDxWrapper() {
}
@SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
- public static Map> execute(@NotNull Module module,
+ public static Map> execute(@NotNull Module module,
@NotNull IAndroidTarget target,
@NotNull String outputDir,
@NotNull String[] compileTargets) {
- String outFile = outputDir + File.separatorChar + AndroidCompileUtil.CLASSES_FILE_NAME;
+ String outFile = outputDir + File.separatorChar + AndroidCommonUtils.CLASSES_FILE_NAME;
- final Map> messages = new HashMap>(2);
- messages.put(CompilerMessageCategory.ERROR, new ArrayList());
- messages.put(CompilerMessageCategory.INFORMATION, new ArrayList());
- messages.put(CompilerMessageCategory.WARNING, new ArrayList());
+ final Map> messages = new HashMap>(2);
+ messages.put(AndroidCompilerMessageKind.ERROR, new ArrayList());
+ messages.put(AndroidCompilerMessageKind.INFORMATION, new ArrayList());
+ messages.put(AndroidCompilerMessageKind.WARNING, new ArrayList());
@SuppressWarnings("deprecation")
String dxJarPath = target.getPath(IAndroidTarget.DX_JAR);
File dxJar = new File(dxJarPath);
if (!dxJar.isFile()) {
- messages.get(CompilerMessageCategory.ERROR).add(AndroidBundle.message("android.file.not.exist.error", dxJarPath));
+ messages.get(AndroidCompilerMessageKind.ERROR).add(AndroidBundle.message("android.file.not.exist.error", dxJarPath));
return messages;
}
@@ -121,53 +111,12 @@ public class AndroidDxWrapper {
process = commandLine.createProcess();
}
catch (ExecutionException e) {
- messages.get(CompilerMessageCategory.ERROR).add("ExecutionException: " + e.getMessage());
+ messages.get(AndroidCompilerMessageKind.ERROR).add("ExecutionException: " + e.getMessage());
LOG.info(e);
return messages;
}
- final OSProcessHandler handler = new OSProcessHandler(process, "");
- handler.addProcessListener(new ProcessAdapter() {
- private CompilerMessageCategory myCategory = null;
-
- @Override
- public void onTextAvailable(ProcessEvent event, Key outputType) {
- String[] msgs = event.getText().split("\\n");
- for (String msg : msgs) {
- msg = msg.trim();
- String msglc = msg.toLowerCase();
- if (outputType == ProcessOutputTypes.STDERR) {
- if (WARNING_PATTERN.matcher(msglc).matches()) {
- myCategory = CompilerMessageCategory.WARNING;
- }
- if (ERROR_PATTERN.matcher(msglc).matches() || EXCEPTION_PATTERN.matcher(msglc).matches() || myCategory == null) {
- myCategory = CompilerMessageCategory.ERROR;
- }
- messages.get(myCategory).add(msg);
- }
- else if (outputType == ProcessOutputTypes.STDOUT) {
- if (!msglc.startsWith("processing")) {
- messages.get(CompilerMessageCategory.INFORMATION).add(msg);
- }
- }
- LOG.info(msg);
- }
- }
- });
-
- handler.startNotify();
- handler.waitFor();
-
- final List errors = messages.get(CompilerMessageCategory.ERROR);
-
- if (new File(outFile).isFile()) {
- // if compilation finished correctly, show all errors as warnings
- messages.get(CompilerMessageCategory.WARNING).addAll(errors);
- errors.clear();
- }
- else if (errors.size() == 0) {
- errors.add("Cannot create classes.dex file");
- }
+ AndroidCommonUtils.handleDexCompilationResult(process, outFile, messages);
return messages;
}
diff --git a/plugins/android/src/org/jetbrains/android/facet/AndroidRootUtil.java b/plugins/android/src/org/jetbrains/android/facet/AndroidRootUtil.java
index ceece26072be..6f7ecd72381b 100644
--- a/plugins/android/src/org/jetbrains/android/facet/AndroidRootUtil.java
+++ b/plugins/android/src/org/jetbrains/android/facet/AndroidRootUtil.java
@@ -32,6 +32,7 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.containers.OrderedSet;
import org.jetbrains.android.compiler.AndroidCompileUtil;
+import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -245,7 +246,7 @@ public class AndroidRootUtil {
if (libraryProject) {
if (classDir != null) {
- final VirtualFile packedClassesJar = classDir.findChild(AndroidCompileUtil.CLASSES_JAR_FILE_NAME);
+ final VirtualFile packedClassesJar = classDir.findChild(AndroidCommonUtils.CLASSES_JAR_FILE_NAME);
if (packedClassesJar != null) {
outputDirs.add(packedClassesJar);
}