blob: 8703cfbbd045bfd979fe08c3fce38d0504323736 [file] [log] [blame]
package org.jetbrains.jps.android;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.manifmerger.ICallback;
import com.android.manifmerger.IMergerLog;
import com.android.manifmerger.ManifestMerger;
import com.android.sdklib.AndroidTargetHash;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.repository.local.LocalSdk;
import com.android.tools.idea.jps.AndroidTargetBuilder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.android.util.AndroidBuildTestingManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.android.model.JpsAndroidModuleExtension;
import org.jetbrains.jps.builders.BuildOutputConsumer;
import org.jetbrains.jps.builders.DirtyFilesHolder;
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.model.module.JpsModule;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IllegalFormatException;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidManifestMergingBuilder
extends AndroidTargetBuilder<AndroidManifestMergingTarget.MyRootDescriptor, AndroidManifestMergingTarget> {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.android.AndroidManifestMergingBuilder");
private static final String BUILDER_NAME = "Android Manifest Merger";
public AndroidManifestMergingBuilder() {
super(Collections.singletonList(AndroidManifestMergingTarget.MyTargetType.INSTANCE));
}
@Override
protected void buildTarget(@NotNull AndroidManifestMergingTarget target,
@NotNull DirtyFilesHolder<AndroidManifestMergingTarget.MyRootDescriptor, AndroidManifestMergingTarget> holder,
@NotNull BuildOutputConsumer outputConsumer,
@NotNull CompileContext context) throws ProjectBuildException, IOException {
if (!holder.hasDirtyFiles() && !holder.hasRemovedFiles()) {
return;
}
try {
if (!doManifestMerging(target, context, outputConsumer)) {
throw new StopBuildException();
}
}
catch (ProjectBuildException e) {
throw e;
}
catch (Exception e) {
AndroidJpsUtil.handleException(context, e, BUILDER_NAME, LOG);
}
}
private static boolean doManifestMerging(AndroidManifestMergingTarget target,
CompileContext context,
BuildOutputConsumer outputConsumer) throws IOException {
final JpsModule module = target.getModule();
final JpsAndroidModuleExtension extension = AndroidJpsUtil.getExtension(module);
assert extension != null;
assert !extension.isLibrary();
assert extension.isManifestMergingEnabled();
final File outputDir = target.getOutputDirectory(context);
if (!outputDir.exists() && !outputDir.mkdirs()) {
context.processMessage(new CompilerMessage(
BUILDER_NAME, BuildMessage.Kind.ERROR, AndroidJpsBundle.message(
"android.jps.cannot.create.directory", outputDir.getPath())));
return false;
}
File manifestFile = null;
final List<File> libManifests = new ArrayList<File>();
final List<AndroidManifestMergingTarget.MyRootDescriptor> roots =
context.getProjectDescriptor().getBuildRootIndex().getTargetRoots(target, context);
for (AndroidManifestMergingTarget.MyRootDescriptor root : roots) {
if (root.isLibManifestRoot()) {
libManifests.add(root.getRootFile());
}
else {
manifestFile = root.getRootFile();
}
}
if (manifestFile == null) {
context.processMessage(new CompilerMessage(
BUILDER_NAME, BuildMessage.Kind.ERROR, AndroidJpsBundle.message(
"android.jps.errors.manifest.not.found", module.getName())));
return false;
}
final File outputFile = new File(outputDir, SdkConstants.FN_ANDROID_MANIFEST_XML);
final AndroidPlatform platform = AndroidJpsUtil.getAndroidPlatform(module, context, BUILDER_NAME);
if (platform == null) {
return false;
}
if (!doMergeManifests(context, platform.getLocalSdk(), manifestFile, libManifests, outputFile)) {
context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR,
"[" + module.getName() + "] Cannot perform manifest merging"));
return false;
}
final List<String> srcPaths = new ArrayList<String>();
srcPaths.add(manifestFile.getPath());
for (File libManifest : libManifests) {
srcPaths.add(libManifest.getPath());
}
outputConsumer.registerOutputFile(outputFile, srcPaths);
return true;
}
@NotNull
@Override
public String getPresentableName() {
return BUILDER_NAME;
}
private static boolean doMergeManifests(final CompileContext context,
final LocalSdk localSdk,
File manifestFile,
List<File> libManifests,
File outputFile)
throws IOException {
final AndroidBuildTestingManager testingManager = AndroidBuildTestingManager.getTestingManager();
if (testingManager != null) {
final StringBuilder messageBuilder = new StringBuilder("manifest_merging\n");
messageBuilder.append(manifestFile.getPath()).append('\n');
Collections.sort(libManifests);
for (File libManifest : libManifests) {
messageBuilder.append(libManifest.getPath()).append('\n');
}
messageBuilder.append(outputFile.getPath());
testingManager.getCommandExecutor().log(messageBuilder.toString());
}
final ManifestMerger manifestMerger = new ManifestMerger(new IMergerLog() {
@Override
public void error(@NonNull Severity severity,
@NonNull FileAndLine location,
@NonNull String message,
Object... msgParams) {
context.processMessage(new CompilerMessage(BUILDER_NAME, toBuildMessageKind(severity), formatMessage(message, msgParams),
location.getFileName(), -1L, -1L, -1L, location.getLine(), -1L));
}
@Override
public void conflict(@NonNull Severity severity,
@NonNull FileAndLine location1,
@NonNull FileAndLine location2,
@NonNull String message,
Object... msgParams) {
final StringBuilder builder = new StringBuilder("Conflicts:\n");
final String filePath1 = location1.getFileName();
if (filePath1 != null) {
builder.append(FileUtil.toSystemDependentName(filePath1)).append(": line ").append(location1.getLine());
}
else {
builder.append("unknown");
}
builder.append('\n');
final String filePath2 = location2.getFileName();
if (filePath2 != null) {
builder.append(FileUtil.toSystemDependentName(filePath2)).append(": line ").append(location1.getLine());
}
else {
builder.append("unknown");
}
builder.append('\n').append(formatMessage(message, msgParams));
context.processMessage(new CompilerMessage(BUILDER_NAME, toBuildMessageKind(severity),
builder.toString(), filePath1, -1L, -1L, -1L, location1.getLine(), -1L));
}
private String formatMessage(String message, Object... msgParams) {
try {
return String.format(message, msgParams);
}
catch (IllegalFormatException e) {
LOG.debug(e);
return message;
}
}
}, new ICallback() {
@Override
public int queryCodenameApiLevel(@NonNull String codename) {
try {
AndroidVersion version = new AndroidVersion(codename);
String hashString = AndroidTargetHash.getPlatformHashString(version);
IAndroidTarget t = localSdk.getTargetFromHashString(hashString);
if (t != null) {
return t.getVersion().getApiLevel();
}
}
catch (AndroidVersion.AndroidVersionException ignore) {
}
return ICallback.UNKNOWN_CODENAME;
}
});
return manifestMerger.process(outputFile, manifestFile, libManifests.toArray(new File[libManifests.size()]), null, null);
}
private static BuildMessage.Kind toBuildMessageKind(IMergerLog.Severity severity) {
if (severity == null) {
return BuildMessage.Kind.INFO;
}
switch (severity) {
case INFO:
return BuildMessage.Kind.INFO;
case WARNING:
return BuildMessage.Kind.WARNING;
case ERROR:
return BuildMessage.Kind.ERROR;
default:
return BuildMessage.Kind.INFO;
}
}
}