// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.android; import com.android.annotations.Nullable; import com.android.builder.core.VariantType; import com.android.ide.common.internal.PngCruncher; import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.devtools.build.android.AndroidDataMerger.ContentComparingChecker; import com.google.devtools.build.android.AndroidDataMerger.PathComparingChecker; import com.google.devtools.build.android.AndroidDataMerger.SourceChecker; import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; /** Collects all the functionality for an action to merge resources. */ // TODO(bazel-team): Turn into an instance object, in order to use an external ExecutorService. public class AndroidResourceMerger { /** Thrown when there is a unexpected condition during merging. */ public static class MergingException extends UserException { private MergingException(Throwable e) { super("Error during merging", e); } private MergingException(String message) { super("Merging Error: \n" + message); } static MergingException wrapException(Throwable e) { return new MergingException(e); } static MergingException withMessage(String message) { return new MergingException(message); } } static final Logger logger = Logger.getLogger(AndroidResourceProcessor.class.getName()); /** Merges all secondary resources with the primary resources. */ public static MergedAndroidData mergeData( final ParsedAndroidData primary, final Path primaryManifest, final List direct, final List transitive, final Path resourcesOut, final Path assetsOut, @Nullable final PngCruncher cruncher, final VariantType type, @Nullable final Path symbolsOut, @Nullable AndroidResourceClassWriter rclassWriter, AndroidDataDeserializer deserializer, boolean throwOnResourceConflict, ListeningExecutorService executorService) { Stopwatch timer = Stopwatch.createStarted(); try { UnwrittenMergedAndroidData merged = mergeData( executorService, transitive, direct, primary, primaryManifest, type != VariantType.LIBRARY, deserializer, throwOnResourceConflict, ContentComparingChecker.create()); timer.reset().start(); if (symbolsOut != null) { AndroidDataSerializer serializer = AndroidDataSerializer.create(); merged.serializeTo(serializer); serializer.flushTo(symbolsOut); logger.fine( String.format( "serialize merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); timer.reset().start(); } if (rclassWriter != null) { merged.writeResourceClass(rclassWriter); logger.fine( String.format("write classes finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); timer.reset().start(); } AndroidDataWriter writer = AndroidDataWriter.createWith( resourcesOut.getParent(), resourcesOut, assetsOut, cruncher, executorService); return merged.write(writer); } catch (IOException e) { throw MergingException.wrapException(e); } finally { logger.fine( String.format("write merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); } } public static UnwrittenMergedAndroidData mergeData( ListeningExecutorService executorService, List transitive, List direct, ParsedAndroidData primary, Path primaryManifest, boolean allowPrimaryOverrideAll, AndroidDataDeserializer deserializer, boolean throwOnResourceConflict, SourceChecker checker) { Stopwatch timer = Stopwatch.createStarted(); // TODO(b/74333698): Always check the contents of conflicting resources try { AndroidDataMerger merger = AndroidDataMerger.createWithPathDeduplictor(executorService, deserializer, checker); return merger.loadAndMerge( transitive, direct, primary, primaryManifest, allowPrimaryOverrideAll, throwOnResourceConflict); } finally { logger.fine(String.format("merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); } } /** * Merges all secondary resources with the primary resources, given that the primary resources * have not yet been parsed and serialized. */ public static MergedAndroidData mergeData( final UnvalidatedAndroidData primary, final List direct, final List transitive, final Path resourcesOut, final Path assetsOut, @Nullable final PngCruncher cruncher, final VariantType type, @Nullable final Path symbolsOut, final List filteredResources, boolean throwOnResourceConflict) { try (ExecutorServiceCloser executorService = ExecutorServiceCloser.createWithFixedPoolOf(15)) { final ParsedAndroidData parsedPrimary = ParsedAndroidData.from(primary); return mergeData( parsedPrimary, primary.getManifest(), direct, transitive, resourcesOut, assetsOut, cruncher, type, symbolsOut, /* rclassWriter= */ null, AndroidParsedDataDeserializer.withFilteredResources(filteredResources), throwOnResourceConflict, executorService); } catch (IOException e) { throw MergingException.wrapException(e); } } /** * Merges all secondary resources with the primary resources, given that the primary resources * have been separately parsed and serialized. */ public static MergedAndroidData mergeData( final SerializedAndroidData primary, final Path primaryManifest, final List direct, final List transitive, final Path resourcesOut, final Path assetsOut, @Nullable final PngCruncher cruncher, final VariantType type, @Nullable final Path symbolsOut, @Nullable final AndroidResourceClassWriter rclassWriter, boolean throwOnResourceConflict, ListeningExecutorService executorService) { final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder(); final AndroidDataDeserializer deserializer = AndroidParsedDataDeserializer.create(); primary.deserialize(deserializer, primaryBuilder.consumers()); ParsedAndroidData primaryData = primaryBuilder.build(); return mergeData( primaryData, primaryManifest, direct, transitive, resourcesOut, assetsOut, cruncher, type, symbolsOut, rclassWriter, deserializer, throwOnResourceConflict, executorService); } /** * Merges all secondary compiled resources with the primary compiled resources, given that the * primary resources have been separately compiled */ public static void mergeCompiledData( final SerializedAndroidData primary, final Path primaryManifest, final List direct, final List transitive, @Nullable final AndroidResourceClassWriter rclassWriter, boolean throwOnResourceConflict, ListeningExecutorService executorService) { final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder(); final AndroidDataDeserializer deserializer = AndroidCompiledDataDeserializer.create(); primary.deserialize(deserializer, primaryBuilder.consumers()); ParsedAndroidData primaryData = primaryBuilder.build(); Stopwatch timer = Stopwatch.createStarted(); try { UnwrittenMergedAndroidData merged = mergeData( executorService, transitive, direct, primaryData, primaryManifest, false, deserializer, throwOnResourceConflict, PathComparingChecker.create()); timer.reset().start(); merged.writeResourceClass(rclassWriter); logger.fine( String.format("write classes finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); timer.reset().start(); } catch (IOException e) { throw MergingException.wrapException(e); } finally { logger.fine( String.format("write merge finished in %sms", timer.elapsed(TimeUnit.MILLISECONDS))); } } }