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