// 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.common.util.concurrent.MoreExecutors; import java.io.Closeable; import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.concurrent.Executors; 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 RuntimeException { private MergingException(Throwable e) { super(e); } private MergingException(String message) { super(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<? 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) throws MergingException { Stopwatch timer = Stopwatch.createStarted(); final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(15)); try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) { AndroidDataMerger merger = AndroidDataMerger.createWithPathDeduplictor(executorService, deserializer); UnwrittenMergedAndroidData merged = mergeData( executorService, transitive, direct, primary, primaryManifest, type != VariantType.LIBRARY, deserializer); 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) throws MergingException { Stopwatch timer = Stopwatch.createStarted(); try { AndroidDataMerger merger = AndroidDataMerger.createWithPathDeduplictor(executorService, deserializer); return merger.loadAndMerge( transitive, direct, primary, primaryManifest, allowPrimaryOverrideAll); } 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<? 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) throws MergingException { try { final ParsedAndroidData parsedPrimary = ParsedAndroidData.from(primary); return mergeData( parsedPrimary, primary.getManifest(), direct, transitive, resourcesOut, assetsOut, cruncher, type, symbolsOut, null /* rclassWriter */, AndroidDataDeserializer.withFilteredResources(filteredResources)); } 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<? 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) throws MergingException { final ParsedAndroidData.Builder primaryBuilder = ParsedAndroidData.Builder.newBuilder(); final AndroidDataDeserializer deserializer = AndroidDataDeserializer.create(); primary.deserialize(deserializer, primaryBuilder.consumers()); ParsedAndroidData primaryData = primaryBuilder.build(); return mergeData( primaryData, primaryManifest, direct, transitive, resourcesOut, assetsOut, cruncher, type, symbolsOut, rclassWriter, deserializer); } }