blob: 744904978308e5e1a298f10c481f601aec3ba19b [file] [log] [blame]
package org.jetbrains.jps.android;
import com.android.tools.idea.jps.AndroidTargetBuilder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import com.intellij.util.containers.HashSet;
import org.jetbrains.android.compiler.tools.AndroidApkBuilder;
import org.jetbrains.android.util.AndroidCompilerMessageKind;
import org.jetbrains.android.util.AndroidNativeLibData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.ProjectPaths;
import org.jetbrains.jps.android.builder.AndroidDexBuildTarget;
import org.jetbrains.jps.android.builder.AndroidPackagingBuildTarget;
import org.jetbrains.jps.android.builder.AndroidResourcePackagingBuildTarget;
import org.jetbrains.jps.android.model.JpsAndroidModuleExtension;
import org.jetbrains.jps.builders.BuildOutputConsumer;
import org.jetbrains.jps.builders.BuildRootDescriptor;
import org.jetbrains.jps.builders.BuildTarget;
import org.jetbrains.jps.builders.DirtyFilesHolder;
import org.jetbrains.jps.builders.storage.BuildDataPaths;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.ProjectBuildException;
import org.jetbrains.jps.incremental.StopBuildException;
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.BuildDataManager;
import org.jetbrains.jps.model.JpsProject;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import org.jetbrains.jps.model.java.compiler.JpsCompilerExcludes;
import org.jetbrains.jps.model.module.JpsModule;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidPackagingBuilder extends AndroidTargetBuilder<BuildRootDescriptor, AndroidPackagingBuildTarget> {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.android.AndroidPackagingBuilder");
private static final String BUILDER_NAME = "Android Packager";
public AndroidPackagingBuilder() {
super(Collections.singletonList(AndroidPackagingBuildTarget.MyTargetType.INSTANCE));
}
@NotNull
@Override
public String getPresentableName() {
return BUILDER_NAME;
}
@Override
protected void buildTarget(@NotNull AndroidPackagingBuildTarget target,
@NotNull DirtyFilesHolder<BuildRootDescriptor, AndroidPackagingBuildTarget> holder,
@NotNull BuildOutputConsumer outputConsumer,
@NotNull CompileContext context) throws ProjectBuildException, IOException {
if (AndroidJpsUtil.isLightBuild(context)) {
return;
}
final boolean hasDirtyFiles = holder.hasDirtyFiles() || holder.hasRemovedFiles();
try {
if (!doPackaging(target, context, target.getModule(), hasDirtyFiles, outputConsumer)) {
throw new StopBuildException();
}
}
catch (ProjectBuildException e) {
throw e;
}
catch (Exception e) {
AndroidJpsUtil.handleException(context, e, BUILDER_NAME, LOG);
}
}
private static boolean doPackaging(@NotNull BuildTarget<?> target,
@NotNull CompileContext context,
@NotNull JpsModule module,
boolean hasDirtyFiles,
@NotNull BuildOutputConsumer outputConsumer) throws IOException {
final boolean release = AndroidJpsUtil.isReleaseBuild(context);
final BuildDataManager dataManager = context.getProjectDescriptor().dataManager;
boolean success = true;
final AndroidApkBuilderConfigStateStorage.Provider builderStateStoragetProvider =
new AndroidApkBuilderConfigStateStorage.Provider("apk_builder_config");
final AndroidApkBuilderConfigStateStorage apkBuilderConfigStateStorage =
dataManager.getStorage(target, builderStateStoragetProvider);
final AndroidPackagingStateStorage packagingStateStorage =
dataManager.getStorage(target, AndroidPackagingStateStorage.Provider.INSTANCE);
try {
if (!doPackagingForModule(context, module, apkBuilderConfigStateStorage, packagingStateStorage,
release, hasDirtyFiles, outputConsumer)) {
success = false;
}
}
catch (IOException e) {
AndroidJpsUtil.reportExceptionError(context, null, e, BUILDER_NAME);
success = false;
}
return success;
}
private static boolean doPackagingForModule(@NotNull CompileContext context,
@NotNull JpsModule module,
@NotNull AndroidApkBuilderConfigStateStorage apkBuilderConfigStateStorage,
@NotNull AndroidPackagingStateStorage packagingStateStorage,
boolean release,
boolean hasDirtyFiles,
@NotNull BuildOutputConsumer outputConsumer) throws IOException {
final JpsAndroidModuleExtension extension = AndroidJpsUtil.getExtension(module);
if (extension == null || extension.isLibrary()) {
return true;
}
final String[] resourceRoots = AndroidJpsUtil.toPaths(AndroidJpsUtil.getJavaOutputRootsForModuleAndDependencies(module));
Arrays.sort(resourceRoots);
final File moduleOutputDir = ProjectPaths.getModuleOutputDir(module, false);
if (moduleOutputDir == null) {
context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, AndroidJpsBundle
.message("android.jps.errors.output.dir.not.specified", module.getName())));
return false;
}
final AndroidPlatform platform = AndroidJpsUtil.getAndroidPlatform(module, context, BUILDER_NAME);
if (platform == null) {
return false;
}
final Set<String> externalJarsSet = new HashSet<String>();
for (String jarPath : AndroidJpsUtil.getExternalLibraries(context, module, platform)) {
if (new File(jarPath).exists()) {
externalJarsSet.add(jarPath);
}
}
final BuildDataPaths dataPaths = context.getProjectDescriptor().dataManager.getDataPaths();
final File resPackage = AndroidResourcePackagingBuildTarget.getOutputFile(dataPaths, module);
final File classesDexFile = AndroidDexBuildTarget.getOutputFile(dataPaths, module);
final String sdkPath = platform.getSdk().getHomePath();
final String outputPath = AndroidJpsUtil.getApkPath(extension, moduleOutputDir);
if (outputPath == null) {
context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, AndroidJpsBundle
.message("android.jps.errors.cannot.compute.output.apk", module.getName())));
return false;
}
final String customKeyStorePath = FileUtil.toSystemDependentName(extension.getCustomDebugKeyStorePath());
final String[] nativeLibDirs = AndroidPackagingBuildTarget.collectNativeLibsFolders(extension, true);
final String resPackagePath = resPackage.getPath();
final String classesDexFilePath = classesDexFile.getPath();
final String[] externalJars = ArrayUtil.toStringArray(externalJarsSet);
Arrays.sort(externalJars);
final List<AndroidNativeLibData> additionalNativeLibs = extension.getAdditionalNativeLibs();
final AndroidApkBuilderConfigState currentApkBuilderConfigState =
new AndroidApkBuilderConfigState(outputPath, customKeyStorePath, additionalNativeLibs);
if (!hasDirtyFiles) {
final AndroidApkBuilderConfigState savedApkBuilderConfigState = apkBuilderConfigStateStorage.getState(module.getName());
final AndroidPackagingStateStorage.MyState packagingState = packagingStateStorage.read();
if (currentApkBuilderConfigState.equalsTo(savedApkBuilderConfigState) &&
packagingState != null && packagingState.isRelease() == release) {
return true;
}
}
context.processMessage(new ProgressMessage(
AndroidJpsBundle.message("android.jps.progress.packaging", AndroidJpsUtil.getApkName(module))));
final Map<AndroidCompilerMessageKind, List<String>> messages = AndroidApkBuilder
.execute(resPackagePath, classesDexFilePath, resourceRoots, externalJars,
nativeLibDirs, additionalNativeLibs, outputPath, release, sdkPath, platform.getTarget(),
customKeyStorePath, new MyExcludedSourcesFilter(context.getProjectDescriptor().getProject()));
if (messages.get(AndroidCompilerMessageKind.ERROR).size() == 0) {
final List<String> srcFiles = new ArrayList<String>();
srcFiles.add(resPackagePath);
srcFiles.add(classesDexFilePath);
for (String resourceRoot : resourceRoots) {
FileUtil.processFilesRecursively(new File(resourceRoot), new Processor<File>() {
@Override
public boolean process(File file) {
if (file.isFile() && AndroidApkBuilder.checkFileForPackaging(file)) {
srcFiles.add(file.getPath());
}
return true;
}
});
}
Collections.addAll(srcFiles, externalJars);
for (String nativeLibDir : nativeLibDirs) {
FileUtil.processFilesRecursively(new File(nativeLibDir), new Processor<File>() {
@Override
public boolean process(File file) {
if (file.isFile()) {
srcFiles.add(file.getPath());
}
return true;
}
});
}
outputConsumer.registerOutputFile(new File(outputPath), srcFiles);
}
AndroidJpsUtil.addMessages(context, messages, BUILDER_NAME, module.getName());
final boolean success = messages.get(AndroidCompilerMessageKind.ERROR).isEmpty();
apkBuilderConfigStateStorage.update(module.getName(), success ? currentApkBuilderConfigState : null);
packagingStateStorage.saveState(new AndroidPackagingStateStorage.MyState(release));
return success;
}
private static class MyExcludedSourcesFilter implements Condition<File> {
private final JpsCompilerExcludes myExcludes;
public MyExcludedSourcesFilter(@NotNull JpsProject project) {
myExcludes = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project).getCompilerExcludes();
}
@Override
public boolean value(File file) {
return !myExcludes.isExcluded(file);
}
}
}