com.facebook.buck.cli.AuditMbrIsolationCommand.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.cli.AuditMbrIsolationCommand.java

Source

/*
 * Copyright 2018-present Facebook, Inc.
 *
 * 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.facebook.buck.cli;

import com.facebook.buck.command.config.BuildBuckConfig;
import com.facebook.buck.core.model.BuildTarget;
import com.facebook.buck.core.model.actiongraph.computation.ActionGraphCache;
import com.facebook.buck.core.model.actiongraph.computation.ActionGraphFactory;
import com.facebook.buck.core.model.actiongraph.computation.ActionGraphProvider;
import com.facebook.buck.core.model.targetgraph.TargetGraph;
import com.facebook.buck.core.model.targetgraph.TargetGraphAndBuildTargets;
import com.facebook.buck.core.rules.ActionGraphBuilder;
import com.facebook.buck.core.rules.BuildRule;
import com.facebook.buck.core.rules.SourcePathRuleFinder;
import com.facebook.buck.core.rules.attr.HasRuntimeDeps;
import com.facebook.buck.core.util.graph.AbstractBreadthFirstTraversal;
import com.facebook.buck.event.ConsoleEvent;
import com.facebook.buck.parser.SpeculativeParsing;
import com.facebook.buck.parser.exceptions.BuildFileParseException;
import com.facebook.buck.rules.modern.tools.IsolationChecker;
import com.facebook.buck.rules.modern.tools.IsolationChecker.FailureReporter;
import com.facebook.buck.util.CommandLineException;
import com.facebook.buck.util.ExitCode;
import com.facebook.buck.util.MoreExceptions;
import com.facebook.buck.util.exceptions.BuckUncheckedExecutionException;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.TreeMultimap;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
import org.kohsuke.args4j.Argument;

/**
 * Generates an isolation report. This report includes information indicating which nodes in the
 * action graph are isolateable (e.g. able to run in remote execution) and the requirements to do
 * that (absolute paths needed, toolchains needed, etc).
 */
public class AuditMbrIsolationCommand extends AbstractCommand {
    @Argument
    private List<String> arguments = new ArrayList<>();

    public List<String> getArguments() {
        return arguments;
    }

    @Override
    public ExitCode runWithoutHelp(CommandRunnerParams params) {
        try {
            // Create a TargetGraph that is composed of the transitive closure of all of the dependent
            // BuildRules for the specified BuildTargetPaths.
            ImmutableSet<BuildTarget> targets = convertArgumentsToBuildTargets(params, getArguments());

            if (targets.isEmpty()) {
                throw new CommandLineException("must specify at least one build target");
            }

            TargetGraph targetGraph;
            try (CommandThreadManager pool = new CommandThreadManager("Audit",
                    getConcurrencyLimit(params.getBuckConfig()))) {
                targetGraph = params.getParser()
                        .buildTargetGraph(createParsingContext(params.getCell(), pool.getListeningExecutorService())
                                .withSpeculativeParsing(SpeculativeParsing.ENABLED)
                                .withExcludeUnsupportedTargets(false), targets);
            } catch (BuildFileParseException e) {
                params.getBuckEventBus()
                        .post(ConsoleEvent.severe(MoreExceptions.getHumanReadableOrLocalizedMessage(e)));
                return ExitCode.PARSE_ERROR;
            }
            if (params.getBuckConfig().getView(BuildBuckConfig.class).getBuildVersions()) {
                targetGraph = toVersionedTargetGraph(params, TargetGraphAndBuildTargets.of(targetGraph, targets))
                        .getTargetGraph();
            }

            ActionGraphBuilder graphBuilder = Objects.requireNonNull(new ActionGraphProvider(
                    params.getBuckEventBus(),
                    ActionGraphFactory.create(params.getBuckEventBus(), params.getCell().getCellProvider(),
                            params.getPoolSupplier(), params.getBuckConfig()),
                    new ActionGraphCache(
                            params.getBuckConfig().getView(BuildBuckConfig.class).getMaxActionGraphCacheEntries()),
                    params.getRuleKeyConfiguration(), params.getBuckConfig()).getFreshActionGraph(targetGraph))
                    .getActionGraphBuilder();
            graphBuilder.requireAllRules(targets);

            SerializationReportGenerator reportGenerator = new SerializationReportGenerator();

            SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(graphBuilder);
            IsolationChecker isolationChecker = new IsolationChecker(ruleFinder,
                    params.getCell().getCellPathResolver(), reportGenerator.getFailureReporter());
            AbstractBreadthFirstTraversal.<BuildRule>traverse(
                    targets.stream().map(graphBuilder::getRule).collect(Collectors.toList()), rule -> {
                        isolationChecker.check(rule);
                        ImmutableList.Builder<BuildRule> depsBuilder = ImmutableList.builder();
                        depsBuilder.addAll(rule.getBuildDeps());
                        if (rule instanceof HasRuntimeDeps) {
                            depsBuilder.addAll(graphBuilder.getAllRules(((HasRuntimeDeps) rule)
                                    .getRuntimeDeps(ruleFinder).collect(Collectors.toList())));
                        }
                        return depsBuilder.build();
                    });

            String report = Joiner.on("\n").join(reportGenerator.generate());
            params.getConsole().getStdOut().println(report);
        } catch (Exception e) {
            throw new BuckUncheckedExecutionException(e,
                    "When inspecting serialization state of the action graph.");
        }

        return ExitCode.SUCCESS;
    }

    private static List<Entry<String, Collection<String>>> asSortedEntries(Multimap<String, String> failure) {
        return failure.asMap().entrySet().stream().sorted(Comparator.comparing(e -> -e.getValue().size()))
                .collect(Collectors.toList());
    }

    @Override
    public boolean isReadOnly() {
        return true;
    }

    @Override
    public String getShortDescription() {
        return "provides facilities to audit build targets' classpaths";
    }

    private static class SerializationReportGenerator {
        // Maps a rule type to a set of failures for that rule type. The set of failures in turn is a
        // map of failure message to a set of targets that failed in that way.
        Map<String, Multimap<String, String>> failuresByRuleType = new HashMap<>();
        Map<String, Multimap<String, String>> absolutePathsRequired = new HashMap<>();

        Multimap<String, String> successByType = ArrayListMultimap.create();
        Multimap<String, String> notMigratedByType = ArrayListMultimap.create();
        private FailureReporter failureReporter = new FailureReporter() {
            @Override
            public void reportNotMbr(BuildRule instance) {
                if (instance.hasBuildSteps()) {
                    notMigratedByType.put(getRuleTypeString(instance), instance.getFullyQualifiedName());
                }
            }

            @Override
            public void reportSerializationFailure(BuildRule instance, String crumbs, String message) {
                String error = String.format("%s %s", crumbs, message);
                Multimap<String, String> failedTargetsByMessage = failuresByRuleType
                        .computeIfAbsent(getRuleTypeString(instance), ignored -> TreeMultimap.create());
                failedTargetsByMessage.put(error, instance.getFullyQualifiedName());
            }

            @Override
            public void reportAbsolutePath(BuildRule instance, String crumbs, Path path) {
                Multimap<String, String> inner = absolutePathsRequired.computeIfAbsent(path.toString(),
                        ignored -> TreeMultimap.create());
                inner.put(crumbs, instance.getFullyQualifiedName());
            }

            @Override
            public void reportSuccess(BuildRule instance) {
                successByType.put(getRuleTypeString(instance), instance.getFullyQualifiedName());
            }

            public String getRuleTypeString(BuildRule instance) {
                return instance.getType();
            }
        };

        public FailureReporter getFailureReporter() {
            return failureReporter;
        }

        public List<String> generate() {
            ReportBuilder builder = new ReportBuilder();
            if (successByType.isEmpty()) {
                builder.addLine("No rules are serializable.");
            } else {
                for (Entry<String, Collection<String>> instance : asSortedEntries(successByType)) {
                    builder.addLine("%s instances of %s are serializable.", instance.getValue().size(),
                            instance.getKey());
                }
            }

            builder.addSeparator();
            if (notMigratedByType.isEmpty()) {
                builder.addLine("All used rules are migrated to ModernBuildRule.");
            } else {
                for (Entry<String, Collection<String>> instance : asSortedEntries(notMigratedByType)) {
                    builder.addLine("%s instances of %s which is not yet migrated to ModernBuildRule.",
                            instance.getValue().size(), instance.getKey());
                }
            }

            builder.addSeparator();
            if (failuresByRuleType.isEmpty()) {
                builder.addLine("There's no failures for rules migrated to ModernBuildRule.");
            } else {
                for (Map.Entry<String, Multimap<String, String>> failure : failuresByRuleType.entrySet().stream()
                        .sorted(Comparator.comparing(entry -> -entry.getValue().size()))
                        .collect(Collectors.toList())) {
                    builder.addLine("%s failures for rules of type %s.", failure.getValue().size(),
                            failure.getKey());
                    for (Entry<String, Collection<String>> instance : asSortedEntries(failure.getValue())) {
                        builder.addLine(" %s: %s", instance.getValue().size(), instance.getKey());

                        int count = 0;
                        int max = 3;
                        for (String target : instance.getValue()) {
                            if (count >= max) {
                                builder.addLine("    ...");
                                break;
                            }
                            builder.addLine("    %s", target);
                            count++;
                        }
                    }
                }
            }

            builder.addSeparator();

            if (absolutePathsRequired.isEmpty()) {
                builder.addLine("Didn't find any references to absolute paths.");
            } else {
                for (Map.Entry<String, Multimap<String, String>> requiredPath : absolutePathsRequired.entrySet()
                        .stream().sorted(Comparator.comparing(entry -> -entry.getValue().size()))
                        .collect(Collectors.toList())) {
                    builder.addLine("%s referenced by %s rules.", requiredPath.getKey(),
                            requiredPath.getValue().size());
                    for (Entry<String, Collection<String>> instance : asSortedEntries(requiredPath.getValue())) {
                        builder.addLine("  %s: %s", instance.getValue().size(), instance.getKey());

                        int count = 0;
                        int max = 3;
                        for (String target : instance.getValue()) {
                            if (count >= max) {
                                builder.addLine("    ...");
                                break;
                            }
                            builder.addLine("    %s", target);
                            count++;
                        }
                    }
                }
            }

            return builder.reportLines;
        }

        private static class ReportBuilder {
            List<String> reportLines = new ArrayList<>();

            private void addSeparator() {
                reportLines.add("-------------------------------------------------------------");
                reportLines.add("-------------------------------------------------------------");
                reportLines.add("-------------------------------------------------------------");
            }

            private void addLine(String message) {
                reportLines.add(message);
            }

            private void addLine(String format, Object... args) {
                reportLines.add(String.format(format, args));
            }
        }
    }
}