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 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); }