com.google.devtools.build.android.GenerateRobolectricResourceSymbolsAction.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.android.GenerateRobolectricResourceSymbolsAction.java

Source

// 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.builder.dependency.SymbolFileProvider;
import com.android.resources.ResourceType;
import com.google.common.base.Optional;
import com.google.common.base.Stopwatch;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.devtools.build.android.AndroidResourceProcessor.AaptConfigOptions;
import com.google.devtools.build.android.Converters.DependencyAndroidDataListConverter;
import com.google.devtools.build.android.Converters.PathConverter;
import com.google.devtools.build.android.resources.RClassGenerator;
import com.google.devtools.build.android.resources.ResourceSymbols;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;
import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
import java.io.Closeable;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This action generates consistent ids R.class files for use in robolectric tests.
 */
public class GenerateRobolectricResourceSymbolsAction {

    private static final Logger logger = Logger.getLogger(GenerateRobolectricResourceSymbolsAction.class.getName());

    private static final class WriteLibraryRClass implements Callable<Boolean> {
        private final Entry<String, Collection<ListenableFuture<ResourceSymbols>>> librarySymbolEntry;
        private final RClassGenerator generator;

        private WriteLibraryRClass(Entry<String, Collection<ListenableFuture<ResourceSymbols>>> librarySymbolEntry,
                RClassGenerator generator) {
            this.librarySymbolEntry = librarySymbolEntry;
            this.generator = generator;
        }

        @Override
        public Boolean call() throws Exception {
            List<ResourceSymbols> resourceSymbolsList = new ArrayList<>();
            for (final ListenableFuture<ResourceSymbols> resourceSymbolsReader : librarySymbolEntry.getValue()) {
                resourceSymbolsList.add(resourceSymbolsReader.get());
            }

            generator.write(librarySymbolEntry.getKey(),
                    ResourceSymbols.merge(resourceSymbolsList).asInitializers());
            return true;
        }
    }

    /** Flag specifications for this action. */
    public static final class Options extends OptionsBase {

        @Option(name = "data", defaultValue = "", converter = DependencyAndroidDataListConverter.class, category = "input", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {
                OptionEffectTag.UNKNOWN }, help = "Data dependencies. The expected format is "
                        + DependencyAndroidData.EXPECTED_FORMAT + "[&...]")
        public List<DependencyAndroidData> data;

        @Option(name = "classJarOutput", defaultValue = "null", converter = PathConverter.class, category = "output", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {
                OptionEffectTag.UNKNOWN }, help = "Path for the generated java class jar.")
        public Path classJarOutput;
    }

    public static void main(String[] args) throws Exception {

        final Stopwatch timer = Stopwatch.createStarted();
        OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class, AaptConfigOptions.class);
        optionsParser.enableParamsFileSupport(new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault()));
        optionsParser.parseAndExitUponError(args);
        AaptConfigOptions aaptConfigOptions = optionsParser.getOptions(AaptConfigOptions.class);
        Options options = optionsParser.getOptions(Options.class);

        try (ScopedTemporaryDirectory scopedTmp = new ScopedTemporaryDirectory("robolectric_resources_tmp")) {
            Path tmp = scopedTmp.getPath();
            Path generatedSources = Files.createDirectories(tmp.resolve("generated_resources"));
            // The reported availableProcessors may be higher than the actual resources
            // (on a shared system). On the other hand, a lot of the work is I/O, so it's not completely
            // CPU bound. As a compromise, divide by 2 the reported availableProcessors.
            int numThreads = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
            ListeningExecutorService executorService = MoreExecutors
                    .listeningDecorator(Executors.newFixedThreadPool(numThreads));
            try (Closeable closeable = ExecutorServiceCloser.createWith(executorService)) {

                logger.fine(String.format("Setup finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));

                final PlaceholderIdFieldInitializerBuilder robolectricIds = PlaceholderIdFieldInitializerBuilder
                        .from(aaptConfigOptions.androidJar);
                ParsedAndroidData.loadedFrom(options.data, executorService, AndroidDataDeserializer.create())
                        .writeResourcesTo(new AndroidResourceSymbolSink() {

                            @Override
                            public void acceptSimpleResource(ResourceType type, String name) {
                                robolectricIds.addSimpleResource(type, name);
                            }

                            @Override
                            public void acceptPublicResource(ResourceType type, String name,
                                    Optional<Integer> value) {
                                robolectricIds.addPublicResource(type, name, value);
                            }

                            @Override
                            public void acceptStyleableResource(FullyQualifiedName key,
                                    Map<FullyQualifiedName, Boolean> attrs) {
                                robolectricIds.addStyleableResource(key, attrs);
                            }
                        });

                final RClassGenerator generator = RClassGenerator.with(generatedSources, robolectricIds.build(),
                        false);

                List<SymbolFileProvider> libraries = new ArrayList<>();
                for (DependencyAndroidData dataDep : options.data) {
                    SymbolFileProvider library = dataDep.asSymbolFileProvider();
                    libraries.add(library);
                }
                List<ListenableFuture<Boolean>> writeSymbolsTask = new ArrayList<>();
                for (final Entry<String, Collection<ListenableFuture<ResourceSymbols>>> librarySymbolEntry : ResourceSymbols
                        .loadFrom(libraries, executorService, null).asMap().entrySet()) {
                    writeSymbolsTask
                            .add(executorService.submit(new WriteLibraryRClass(librarySymbolEntry, generator)));
                }
                FailedFutureAggregator.forIOExceptionsWithMessage("Errors writing symbols.")
                        .aggregateAndMaybeThrow(writeSymbolsTask);
            }

            logger.fine(String.format("Merging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));

            AndroidResourceOutputs.createClassJar(generatedSources, options.classJarOutput);
            logger.fine(String.format("Create classJar finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));

        } catch (Exception e) {
            logger.log(Level.SEVERE, "Unexpected", e);
            throw e;
        }
        logger.fine(String.format("Resources merged in %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
    }
}