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 52c990ddb6c6..333dd623a783 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 @@ -16,6 +16,7 @@ public class AndroidBuilderService extends BuilderService { @Override public List createModuleLevelBuilders(ExecutorService executorService) { return Arrays.asList(new AndroidSourceGeneratingBuilder(), + new AndroidLibraryPackagingBuilder(), 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 index c9b4a0ea5fa2..bd5fce8c6b3a 100644 --- a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidDexBuilder.java +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidDexBuilder.java @@ -21,16 +21,15 @@ 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: save validity state +// todo: support light builds (for tests) public class AndroidDexBuilder extends ModuleLevelBuilder { private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.android.AndroidDexBuilder"); @@ -52,16 +51,7 @@ public class AndroidDexBuilder extends ModuleLevelBuilder { 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); + return AndroidJpsUtil.handleException(context, e, BUILDER_NAME); } } 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 index 6d8ef3dc6cff..8d0e982244c6 100644 --- a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidJpsUtil.java +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidJpsUtil.java @@ -14,10 +14,15 @@ 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.ModuleLevelBuilder; +import org.jetbrains.jps.incremental.ProjectBuildException; import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.io.PrintStream; import java.util.Collection; import java.util.List; import java.util.Map; @@ -238,4 +243,25 @@ class AndroidJpsUtil { } return false; } + + public static ModuleLevelBuilder.ExitCode handleException(@NotNull CompileContext context, @NotNull Exception e, @NotNull String builderName) + throws ProjectBuildException { + 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(builderName, BuildMessage.Kind.ERROR, message)); + throw new ProjectBuildException(message, e); + } + + @Nullable + public static File getManifestFileForCompilationPath(@NotNull AndroidFacet facet) throws IOException { + return facet.getUseCustomManifestForCompilation() + ? facet.getManifestFileForCompilation() + : facet.getManifestFile(); + } } diff --git a/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidLibraryPackagingBuilder.java b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidLibraryPackagingBuilder.java new file mode 100644 index 000000000000..34f866b4ca64 --- /dev/null +++ b/plugins/android/jps-plugin/src/org/jetbrains/jps/android/AndroidLibraryPackagingBuilder.java @@ -0,0 +1,102 @@ +package org.jetbrains.jps.android; + +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.HashSet; +import org.jetbrains.android.util.AndroidCommonUtils; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.jps.Module; +import org.jetbrains.jps.ModuleChunk; +import org.jetbrains.jps.ProjectPaths; +import org.jetbrains.jps.incremental.BuilderCategory; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.ModuleLevelBuilder; +import org.jetbrains.jps.incremental.ProjectBuildException; +import org.jetbrains.jps.incremental.messages.BuildMessage; +import org.jetbrains.jps.incremental.messages.CompilerMessage; +import org.jetbrains.jps.incremental.messages.ProgressMessage; + +import java.io.File; +import java.io.IOException; +import java.util.Set; + +/** + * @author Eugene.Kudelevsky + */ + +// todo: save validity states +// todo: support light builds + +public class AndroidLibraryPackagingBuilder extends ModuleLevelBuilder { + @NonNls private static final String BUILDER_NAME = "android-library-packager"; + + protected AndroidLibraryPackagingBuilder() { + super(BuilderCategory.CLASS_POST_PROCESSOR); + } + + @Override + public ModuleLevelBuilder.ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException { + if (context.isCompilingTests() || !AndroidJpsUtil.containsAndroidFacet(chunk)) { + return ModuleLevelBuilder.ExitCode.OK; + } + context.processMessage(new ProgressMessage("Packaging Android libraries")); + + try { + return doBuild(context, chunk); + } + catch (Exception e) { + return AndroidJpsUtil.handleException(context, e, BUILDER_NAME); + } + } + + private static ModuleLevelBuilder.ExitCode doBuild(CompileContext context, ModuleChunk chunk) { + boolean success = true; + + for (Module module : chunk.getModules()) { + final AndroidFacet facet = AndroidJpsUtil.getFacet(module); + if (facet == null || !facet.isLibrary()) { + continue; + } + + final ProjectPaths projectPaths = context.getProjectPaths(); + final File outputDirectoryForPackagedFiles = AndroidJpsUtil.getOutputDirectoryForPackagedFiles(projectPaths, module); + + if (outputDirectoryForPackagedFiles == null) { + context.processMessage( + new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Output directory is not specified for module " + module.getName())); + success = false; + continue; + } + + final File classesDir = projectPaths.getModuleOutputDir(module, false); + if (classesDir == null || !classesDir.isDirectory()) { + continue; + } + + final Set subdirs = new HashSet(); + AndroidJpsUtil.addSubdirectories(classesDir, subdirs); + + if (subdirs.size() > 0) { + final File outputJarFile = new File(outputDirectoryForPackagedFiles, AndroidCommonUtils.CLASSES_JAR_FILE_NAME); + + try { + AndroidCommonUtils.packClassFilesIntoJar(ArrayUtil.EMPTY_STRING_ARRAY, ArrayUtil.toStringArray(subdirs), outputJarFile); + } + catch (IOException e) { + AndroidJpsUtil.reportExceptionError(context, null, e, BUILDER_NAME); + success = false; + } + } + } + return success ? ModuleLevelBuilder.ExitCode.OK : ModuleLevelBuilder.ExitCode.ABORT; + } + + @Override + public String getName() { + return BUILDER_NAME; + } + + @Override + public String getDescription() { + return "Android Library Packaging Builder"; + } +} 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 06dec68230cd..25ff3801c66f 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 @@ -61,16 +61,7 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder { 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); + return AndroidJpsUtil.handleException(context, e, BUILDER_NAME); } } @@ -277,7 +268,7 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder { continue; } - final File manifestFile = getManifestFileForCompilationPath(facet); + final File manifestFile = AndroidJpsUtil.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())); @@ -329,7 +320,7 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder { final Set result = new HashSet(); for (AndroidFacet depFacet : getAllDependentAndroidLibraries(module)) { - final File depManifestFile = getManifestFileForCompilationPath(depFacet); + final File depManifestFile = AndroidJpsUtil.getManifestFileForCompilationPath(depFacet); if (depManifestFile != null) { final String packageName = parsePackageNameFromManifestFile(depManifestFile); @@ -366,13 +357,6 @@ public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder { : 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(); 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 e923d46677f7..3decb454d04e 100644 --- a/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java +++ b/plugins/android/rt/src/org/jetbrains/android/util/AndroidCommonUtils.java @@ -9,17 +9,16 @@ 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.Pair; import com.intellij.openapi.util.io.FileUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -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.io.*; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; import java.util.regex.Pattern; /** @@ -35,6 +34,7 @@ public class AndroidCommonUtils { 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 static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); private AndroidCommonUtils() { } @@ -133,4 +133,95 @@ public class AndroidCommonUtils { errors.add("Cannot create classes.dex file"); } } + + public static void packClassFilesIntoJar(@NotNull String[] firstPackageDirPaths, + @NotNull String[] libFirstPackageDirPaths, + @NotNull File jarFile) throws IOException { + final List> files = new ArrayList>(); + for (String path : firstPackageDirPaths) { + final File firstPackageDir = new File(path); + if (firstPackageDir.exists()) { + addFileToJar(firstPackageDir, firstPackageDir.getParentFile(), true, files); + } + } + + for (String path : libFirstPackageDirPaths) { + final File firstPackageDir = new File(path); + if (firstPackageDir.exists()) { + addFileToJar(firstPackageDir, firstPackageDir.getParentFile(), false, files); + } + } + + if (files.size() > 0) { + final JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile)); + try { + for (Pair pair : files) { + packIntoJar(jos, pair.getFirst(), pair.getSecond()); + } + } + finally { + jos.close(); + } + } + else if (jarFile.isFile()) { + if (!jarFile.delete()) { + throw new IOException("Cannot delete file " + FileUtil.toSystemDependentName(jarFile.getPath())); + } + } + } + + private static void addFileToJar(@NotNull File file, + @NotNull File rootDirectory, + boolean packRClasses, + @NotNull List> files) + throws IOException { + + if (file.isDirectory()) { + final File[] children = file.listFiles(); + + if (children != null) { + for (File child : children) { + addFileToJar(child, rootDirectory, packRClasses, files); + } + } + } + else if (file.isFile()) { + if (!FileUtil.getExtension(file.getName()).equals("class")) { + return; + } + + if (!packRClasses && R_PATTERN.matcher(file.getName()).matches()) { + return; + } + + final String rootPath = rootDirectory.getAbsolutePath(); + + String path = file.getAbsolutePath(); + path = FileUtil.toSystemIndependentName(path.substring(rootPath.length())); + if (path.charAt(0) == '/') { + path = path.substring(1); + } + + files.add(new Pair(file, path)); + } + } + + private static void packIntoJar(@NotNull JarOutputStream jar, @NotNull File file, @NotNull String path) throws IOException { + final JarEntry entry = new JarEntry(path); + entry.setTime(file.lastModified()); + jar.putNextEntry(entry); + + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); + try { + final byte[] buffer = new byte[1024]; + int count; + while ((count = bis.read(buffer)) != -1) { + jar.write(buffer, 0, count); + } + jar.closeEntry(); + } + finally { + bis.close(); + } + } } diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java index dee279c57de7..eea48e727a18 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java @@ -40,7 +40,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.*; import com.intellij.psi.JavaPsiFacade; @@ -70,10 +69,9 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.util.*; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -82,7 +80,6 @@ import java.util.regex.Pattern; */ public class AndroidCompileUtil { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.AndroidCompileUtil"); - private static Pattern R_PATTERN = Pattern.compile("R(\\$.*)?\\.class"); private static final Pattern ourMessagePattern = Pattern.compile("(.+):(\\d+):.+"); @@ -750,97 +747,6 @@ public class AndroidCompileUtil { } } - public static void packClassFilesIntoJar(@NotNull String[] firstPackageDirPaths, - @NotNull String[] libFirstPackageDirPaths, - @NotNull File jarFile) throws IOException { - final List> files = new ArrayList>(); - for (String path : firstPackageDirPaths) { - final File firstPackageDir = new File(path); - if (firstPackageDir.exists()) { - addFileToJar(firstPackageDir, firstPackageDir.getParentFile(), true, files); - } - } - - for (String path : libFirstPackageDirPaths) { - final File firstPackageDir = new File(path); - if (firstPackageDir.exists()) { - addFileToJar(firstPackageDir, firstPackageDir.getParentFile(), false, files); - } - } - - if (files.size() > 0) { - final JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile)); - try { - for (Pair pair : files) { - packIntoJar(jos, pair.getFirst(), pair.getSecond()); - } - } - finally { - jos.close(); - } - } - else if (jarFile.isFile()) { - if (!jarFile.delete()) { - throw new IOException("Cannot delete file " + FileUtil.toSystemDependentName(jarFile.getPath())); - } - } - } - - private static void addFileToJar(@NotNull File file, - @NotNull File rootDirectory, - boolean packRClasses, - @NotNull List> files) - throws IOException { - - if (file.isDirectory()) { - final File[] children = file.listFiles(); - - if (children != null) { - for (File child : children) { - addFileToJar(child, rootDirectory, packRClasses, files); - } - } - } - else if (file.isFile()) { - if (!FileUtil.getExtension(file.getName()).equals("class")) { - return; - } - - if (!packRClasses && R_PATTERN.matcher(file.getName()).matches()) { - return; - } - - final String rootPath = rootDirectory.getAbsolutePath(); - - String path = file.getAbsolutePath(); - path = FileUtil.toSystemIndependentName(path.substring(rootPath.length())); - if (path.charAt(0) == '/') { - path = path.substring(1); - } - - files.add(new Pair(file, path)); - } - } - - private static void packIntoJar(@NotNull JarOutputStream jar, @NotNull File file, @NotNull String path) throws IOException { - final JarEntry entry = new JarEntry(path); - entry.setTime(file.lastModified()); - jar.putNextEntry(entry); - - BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); - try { - final byte[] buffer = new byte[1024]; - int count; - while ((count = bis.read(buffer)) != -1) { - jar.write(buffer, 0, count); - } - jar.closeEntry(); - } - finally { - bis.close(); - } - } - @NotNull public static String[] toOsPaths(@NotNull VirtualFile[] classFilesDirs) { final String[] classFilesDirOsPaths = new String[classFilesDirs.length]; diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidLibraryPackagingCompiler.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidLibraryPackagingCompiler.java index b41b3a22746b..b442f022b8a8 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/AndroidLibraryPackagingCompiler.java +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidLibraryPackagingCompiler.java @@ -92,7 +92,7 @@ public class AndroidLibraryPackagingCompiler implements ClassPostProcessingCompi final File outputJarFile = new File(outputJarOsPath); try { - AndroidCompileUtil.packClassFilesIntoJar(ArrayUtil.EMPTY_STRING_ARRAY, classesDirOsPaths, outputJarFile); + AndroidCommonUtils.packClassFilesIntoJar(ArrayUtil.EMPTY_STRING_ARRAY, classesDirOsPaths, outputJarFile); CompilerUtil.refreshIOFile(outputJarFile); result.add(processingItem); } diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidProguardCompiler.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidProguardCompiler.java index fb3ed1174edb..ffe8300de363 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/AndroidProguardCompiler.java +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidProguardCompiler.java @@ -195,7 +195,7 @@ public class AndroidProguardCompiler implements ClassPostProcessingCompiler { throws IOException { final File inputJar = FileUtil.createTempFile("proguard_input", ".jar"); - AndroidCompileUtil.packClassFilesIntoJar(classFilesDirOsPaths, libClassFilesDirOsPaths, inputJar); + AndroidCommonUtils.packClassFilesIntoJar(classFilesDirOsPaths, libClassFilesDirOsPaths, inputJar); return FileUtil.toSystemDependentName(inputJar.getPath()); }