| // 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.collect.ImmutableList; | |
| 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 { | |
| /** Performs a merge of compiled android data. */ | |
| static Path mergeDataToSymbols( | |
| ParsedAndroidData primary, | |
| Path manifest, | |
| ImmutableList<SerializedAndroidData> direct, | |
| ImmutableList<SerializedAndroidData> transitive, | |
| VariantType packageType, | |
| Path symbolsOut, | |
| AndroidDataDeserializer deserializer, | |
| boolean throwOnResourceConflict, | |
| ExecutorServiceCloser executorService) | |
| throws IOException { | |
| AndroidDataMerger merger = | |
| AndroidDataMerger.createWithPathDeduplictor( | |
| executorService, deserializer, new PathComparingChecker()); | |
| final UnwrittenMergedAndroidData merged = | |
| merger.loadAndMerge( | |
| transitive, | |
| direct, | |
| primary, | |
| manifest, | |
| packageType.equals(VariantType.DEFAULT), | |
| throwOnResourceConflict); | |
| AndroidDataSerializer serializer = AndroidDataSerializer.create(); | |
| merged.serializeTo(serializer); | |
| serializer.flushTo(symbolsOut); | |
| return symbolsOut; | |
| } | |
| /** 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 mergeDataAndWrite( | |
| final ParsedAndroidData primary, | |
| final Path primaryManifest, | |
| final List<? extends SerializedAndroidData> direct, | |
| final List<? extends SerializedAndroidData> 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<? extends SerializedAndroidData> transitive, | |
| List<? extends SerializedAndroidData> 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 mergeDataAndWrite( | |
| final UnvalidatedAndroidData primary, | |
| final List<? extends SerializedAndroidData> direct, | |
| final List<? extends SerializedAndroidData> transitive, | |
| final Path resourcesOut, | |
| final Path assetsOut, | |
| @Nullable final PngCruncher cruncher, | |
| final VariantType type, | |
| @Nullable final Path symbolsOut, | |
| final List<String> filteredResources, | |
| boolean throwOnResourceConflict) { | |
| try (ExecutorServiceCloser executorService = ExecutorServiceCloser.createWithFixedPoolOf(15)) { | |
| final ParsedAndroidData parsedPrimary = ParsedAndroidData.from(primary); | |
| return mergeDataAndWrite( | |
| 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 mergeDataAndWrite( | |
| final SerializedAndroidData primary, | |
| final Path primaryManifest, | |
| final List<? extends SerializedAndroidData> direct, | |
| final List<? extends SerializedAndroidData> 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 mergeDataAndWrite( | |
| 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<? extends SerializedAndroidData> direct, | |
| final List<? extends SerializedAndroidData> 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))); | |
| } | |
| } | |
| } |