android-jps: refactor, support library packaging

This commit is contained in:
Eugene Kudelevsky
2012-02-16 21:21:10 +04:00
parent c687ab5e35
commit fb30f6914b
9 changed files with 235 additions and 135 deletions
@@ -16,6 +16,7 @@ public class AndroidBuilderService extends BuilderService {
@Override
public List<? extends ModuleLevelBuilder> createModuleLevelBuilders(ExecutorService executorService) {
return Arrays.asList(new AndroidSourceGeneratingBuilder(),
new AndroidLibraryPackagingBuilder(),
new AndroidDexBuilder());
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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<String> subdirs = new HashSet<String>();
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";
}
}
@@ -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<String> result = new HashSet<String>();
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<AndroidFacet> getAllDependentAndroidLibraries(@NotNull Module module) {
final List<AndroidFacet> result = new ArrayList<AndroidFacet>();
@@ -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<Pair<File, String>> files = new ArrayList<Pair<File, String>>();
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<File, String> 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<Pair<File, String>> 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, String>(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();
}
}
}
@@ -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<Pair<File, String>> files = new ArrayList<Pair<File, String>>();
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<File, String> 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<Pair<File, String>> 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, String>(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];
@@ -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);
}
@@ -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());
}