com.google.devtools.build.lib.pkgcache.LegacyLoadingPhaseRunner.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.pkgcache.LegacyLoadingPhaseRunner.java

Source

// Copyright 2014 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.lib.pkgcache;

import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.ResolvedTargets;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.events.DelegatingEventHandler;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.packages.TestTargetUtils;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import javax.annotation.Nullable;

/**
 * Implements the loading phase; responsible for:
 * <ul>
 *   <li>target pattern evaluation
 *   <li>test suite expansion
 *   <li>loading the labels needed to construct the build configuration
 *   <li>loading the labels needed for the analysis with the build configuration
 *   <li>loading the transitive closure of the targets and the configuration labels
 * </ul>
 *
 * <p>In order to ensure correctness of incremental loading and of full cache hits, this class is
 * very restrictive about access to its internal state and to its collaborators. In particular, none
 * of the collaborators of this class may change in incompatible ways, such as changing the relative
 * working directory for the target pattern parser, without notifying this class.
 *
 * <p>For full caching, this class tracks the exact values of all inputs to the loading phase. To
 * maximize caching, it is vital that these change as rarely as possible.
 *
 * <p>The Skyframe-based re-implementation of this class is in TargetPatternPhaseFunction.
 */
public final class LegacyLoadingPhaseRunner extends LoadingPhaseRunner {

    private static final class ParseFailureListenerImpl extends DelegatingEventHandler
            implements ParseFailureListener {
        private final EventBus eventBus;

        private ParseFailureListenerImpl(EventHandler delegate, EventBus eventBus) {
            super(delegate);
            this.eventBus = eventBus;
        }

        @Override
        public void parsingError(String targetPattern, String message) {
            if (eventBus != null) {
                eventBus.post(new ParsingFailedEvent(targetPattern, message));
            }
        }
    }

    private static final Logger LOG = Logger.getLogger(LoadingPhaseRunner.class.getName());

    private final PackageManager packageManager;
    private final TargetPatternEvaluator targetPatternEvaluator;
    private final Set<String> ruleNames;

    public LegacyLoadingPhaseRunner(PackageManager packageManager, Set<String> ruleNames) {
        this.packageManager = packageManager;
        this.targetPatternEvaluator = packageManager.newTargetPatternEvaluator();
        this.ruleNames = ruleNames;
    }

    @Override
    public TargetPatternEvaluator getTargetPatternEvaluator() {
        return targetPatternEvaluator;
    }

    @Override
    public void updatePatternEvaluator(PathFragment relativeWorkingDirectory) {
        targetPatternEvaluator.updateOffset(relativeWorkingDirectory);
    }

    /**
     * Performs target pattern evaluation, test suite expansion (if requested), and loads the
     * transitive closure of the resulting targets as well as of the targets needed to use the
     * given build configuration provider.
     */
    @Override
    public LoadingResult execute(EventHandler eventHandler, EventBus eventBus, List<String> targetPatterns,
            LoadingOptions options, ListMultimap<String, Label> labelsToLoadUnconditionally, boolean keepGoing,
            boolean enableLoading, boolean determineTests, @Nullable LoadingCallback callback)
            throws TargetParsingException, LoadingFailedException, InterruptedException {
        LOG.info("Starting pattern evaluation");
        Stopwatch timer = Stopwatch.createStarted();
        if (options.buildTestsOnly && options.compileOneDependency) {
            throw new LoadingFailedException("--compile_one_dependency cannot be used together with "
                    + "the --build_tests_only option or the 'bazel test' command ");
        }

        EventHandler parseFailureListener = new ParseFailureListenerImpl(eventHandler, eventBus);
        // Determine targets to build:
        ResolvedTargets<Target> targets = getTargetsToBuild(parseFailureListener, targetPatterns,
                options.compileOneDependency, keepGoing);

        ImmutableSet<Target> filteredTargets = targets.getFilteredTargets();

        boolean buildTestsOnly = options.buildTestsOnly;
        ImmutableSet<Target> testsToRun = null;
        ImmutableSet<Target> testFilteredTargets = ImmutableSet.of();

        // Now we have a list of targets to build. If the --build_tests_only option was specified or we
        // want to run tests, we need to determine the list of targets to test. For that, we remove
        // manual tests and apply the command line filters. Also, if --build_tests_only is specified,
        // then the list of filtered targets will be set as build list as well.
        if (determineTests || buildTestsOnly) {
            // Parse the targets to get the tests.
            ResolvedTargets<Target> testTargets = determineTests(parseFailureListener, targetPatterns, options,
                    keepGoing);
            if (testTargets.getTargets().isEmpty() && !testTargets.getFilteredTargets().isEmpty()) {
                eventHandler.handle(Event.warn("All specified test targets were excluded by filters"));
            }

            if (buildTestsOnly) {
                // Replace original targets to build with test targets, so that only targets that are
                // actually going to be built are loaded in the loading phase. Note that this has a side
                // effect that any test_suite target requested to be built is replaced by the set of *_test
                // targets it represents; for example, this affects the status and the summary reports.
                Set<Target> allFilteredTargets = new HashSet<>();
                allFilteredTargets.addAll(targets.getTargets());
                allFilteredTargets.addAll(targets.getFilteredTargets());
                allFilteredTargets.removeAll(testTargets.getTargets());
                allFilteredTargets.addAll(testTargets.getFilteredTargets());
                testFilteredTargets = ImmutableSet.copyOf(allFilteredTargets);
                filteredTargets = ImmutableSet.of();

                targets = ResolvedTargets.<Target>builder().merge(testTargets).mergeError(targets.hasError())
                        .build();
                if (determineTests) {
                    testsToRun = testTargets.getTargets();
                }
            } else /*if (determineTests)*/ {
                testsToRun = testTargets.getTargets();
                targets = ResolvedTargets.<Target>builder().merge(targets)
                        // Avoid merge() here which would remove the filteredTargets from the targets.
                        .addAll(testsToRun).mergeError(testTargets.hasError()).build();
                // filteredTargets is correct in this case - it cannot contain tests that got back in
                // through test_suite expansion, because the test determination would also filter those out.
                // However, that's not obvious, and it might be better to explicitly recompute it.
            }
            if (testsToRun != null) {
                // Note that testsToRun can still be null here, if buildTestsOnly && !shouldRunTests.
                Preconditions.checkState(targets.getTargets().containsAll(testsToRun));
            }
        }

        eventBus.post(new TargetParsingCompleteEvent(targets.getTargets(), filteredTargets, testFilteredTargets,
                timer.stop().elapsed(TimeUnit.MILLISECONDS)));

        if (targets.hasError()) {
            eventHandler.handle(Event.warn("Target pattern parsing failed. Continuing anyway"));
        }
        if (callback != null) {
            callback.notifyTargets(targets.getTargets());
        }
        LoadingPhaseRunner.maybeReportDeprecation(eventHandler, targets.getTargets());

        if (enableLoading) {
            return doLoadingPhase(eventHandler, eventBus, targets, testsToRun, labelsToLoadUnconditionally,
                    keepGoing, options.loadingPhaseThreads, callback);
        } else {
            return doSimpleLoadingPhase(eventHandler, eventBus, targets, testsToRun, keepGoing);
        }
    }

    private void freeMemoryAfterLoading(LoadingCallback callback, Set<PackageIdentifier> visitedPackages) {
        if (callback != null) {
            callback.notifyVisitedPackages(visitedPackages);
        }
        // Clear some targets from the cache to free memory.
        packageManager.partiallyClear();
    }

    /** 
     * Simplified version of {@code doLoadingPhase} method. This method does not load targets.
     * It only does test_suite expansion and emits necessary events and logging messages for legacy
     * support.
     */
    private LoadingResult doSimpleLoadingPhase(EventHandler eventHandler, EventBus eventBus,
            ResolvedTargets<Target> targets, ImmutableSet<Target> testsToRun, boolean keepGoing)
            throws LoadingFailedException {
        Stopwatch timer = preLoadingLogging(eventHandler);

        ImmutableSet<Target> targetsToLoad = targets.getTargets();
        ResolvedTargets<Target> expandedResult;
        try {
            expandedResult = expandTestSuites(eventHandler, targetsToLoad, keepGoing);
        } catch (TargetParsingException e) {
            throw new LoadingFailedException("Loading failed; build aborted", e);
        }

        postLoadingLogging(eventBus, targetsToLoad, expandedResult.getTargets(), timer);
        return new LoadingResult(targets.hasError(), expandedResult.hasError(), expandedResult.getTargets(),
                testsToRun, ImmutableMap.<PackageIdentifier, Path>of());
    }

    /**
     * Visit the transitive closure of the targets, populating the package cache
     * and ensuring that all labels can be resolved and all rules were free from
     * errors.
     */
    private LoadingResult doLoadingPhase(EventHandler eventHandler, EventBus eventBus,
            ResolvedTargets<Target> targets, ImmutableSet<Target> testsToRun,
            ListMultimap<String, Label> labelsToLoadUnconditionally, boolean keepGoing, int loadingPhaseThreads,
            @Nullable LoadingCallback callback) throws InterruptedException, LoadingFailedException {
        Stopwatch timer = preLoadingLogging(eventHandler);

        TransitivePackageLoader pkgLoader = packageManager.newTransitiveLoader();
        BaseLoadingResult baseResult = performLoadingOfTargets(eventHandler, eventBus, pkgLoader,
                targets.getTargets(), labelsToLoadUnconditionally, keepGoing, loadingPhaseThreads);
        ResolvedTargets<Target> expandedResult;
        try {
            expandedResult = expandTestSuites(eventHandler, baseResult.getTargets(), keepGoing);
        } catch (TargetParsingException e) {
            // This shouldn't happen, because we've already loaded the targets successfully.
            throw (AssertionError) (new AssertionError("Unexpected target failure").initCause(e));
        }
        freeMemoryAfterLoading(callback, pkgLoader.getVisitedPackageNames());

        postLoadingLogging(eventBus, baseResult.getTargets(), expandedResult.getTargets(), timer);
        LoadingResult loadingResult = new LoadingResult(targets.hasError(),
                !baseResult.isSuccesful() || expandedResult.hasError(), expandedResult.getTargets(), testsToRun,
                baseResult.roots);
        return loadingResult;
    }

    private Stopwatch preLoadingLogging(EventHandler eventHandler) {
        eventHandler.handle(Event.progress("Loading..."));
        LOG.info("Starting loading phase");
        return Stopwatch.createStarted();
    }

    private void postLoadingLogging(EventBus eventBus, ImmutableSet<Target> originalTargetsToLoad,
            ImmutableSet<Target> expandedTargetsToLoad, Stopwatch timer) {
        Set<Target> testSuiteTargets = Sets.difference(originalTargetsToLoad, expandedTargetsToLoad);
        eventBus.post(new LoadingPhaseCompleteEvent(expandedTargetsToLoad, testSuiteTargets,
                packageManager.getStatistics(), timer.stop().elapsed(TimeUnit.MILLISECONDS)));
        LOG.info("Loading phase finished");
    }

    private BaseLoadingResult performLoadingOfTargets(EventHandler eventHandler, EventBus eventBus,
            TransitivePackageLoader pkgLoader, ImmutableSet<Target> targetsToLoad,
            ListMultimap<String, Label> labelsToLoadUnconditionally, boolean keepGoing, int loadingPhaseThreads)
            throws InterruptedException, LoadingFailedException {
        Set<Label> labelsToLoad = ImmutableSet.copyOf(labelsToLoadUnconditionally.values());

        // For each label in {@code targetsToLoad}, ensure that the target to which
        // it refers exists, and also every target in its transitive closure of label
        // dependencies. Success guarantees that a call to
        // {@code getConfiguredTarget} for the same targets will not fail; the
        // configuration process is intolerant of missing packages/targets. Before
        // calling getConfiguredTarget(), clients must ensure that all necessary
        // packages/targets have been visited since the last sync/clear.
        boolean loadingSuccessful = pkgLoader.sync(eventHandler, targetsToLoad, labelsToLoad, keepGoing,
                loadingPhaseThreads, Integer.MAX_VALUE);
        Set<Package> errorFreePackages = pkgLoader.getErrorFreeVisitedPackages(eventHandler);

        ImmutableSet<Target> targetsToAnalyze;
        if (loadingSuccessful) {
            // Success: all loaded targets will be analyzed.
            targetsToAnalyze = targetsToLoad;
        } else if (keepGoing) {
            // Keep going: filter out the error-free targets and only continue with those.
            targetsToAnalyze = filterErrorFreeTargets(eventHandler, eventBus, pkgLoader, targetsToLoad,
                    labelsToLoadUnconditionally);
            reportAboutPartiallySuccesfulLoading(targetsToLoad, targetsToAnalyze, eventHandler);
        } else {
            throw new LoadingFailedException("Loading failed; build aborted");
        }
        return new BaseLoadingResult(targetsToAnalyze, loadingSuccessful,
                LoadingPhaseRunner.collectPackageRoots(errorFreePackages));
    }

    private void reportAboutPartiallySuccesfulLoading(ImmutableSet<Target> requestedTargets,
            ImmutableSet<Target> loadedTargets, EventHandler eventHandler) {
        // Tell the user about the subset of successful targets.
        int requested = requestedTargets.size();
        int loaded = loadedTargets.size();
        if (0 < loaded) {
            String message = String.format("Loading succeeded for only %d of %d targets", loaded, requested);
            eventHandler.handle(Event.info(message));
            LOG.info(message);
        }
    }

    private ResolvedTargets<Target> expandTestSuites(EventHandler eventHandler, ImmutableSet<Target> targets,
            boolean keepGoing) throws LoadingFailedException, TargetParsingException {
        // We use strict test_suite expansion here to match the analysis-time checks.
        ResolvedTargets<Target> expandedResult = TestTargetUtils.expandTestSuites(packageManager, eventHandler,
                targets, /*strict=*/true, /*keepGoing=*/true);
        if (expandedResult.hasError() && !keepGoing) {
            throw new LoadingFailedException("Could not expand test suite target");
        }
        return expandedResult;
    }

    private static class BaseLoadingResult {
        private final ImmutableSet<Target> targets;
        private final boolean succesful;
        private final ImmutableMap<PackageIdentifier, Path> roots;

        BaseLoadingResult(ImmutableSet<Target> targets, boolean succesful,
                ImmutableMap<PackageIdentifier, Path> roots) {
            this.targets = targets;
            this.succesful = succesful;
            this.roots = roots;
        }

        ImmutableSet<Target> getTargets() {
            return targets;
        }

        boolean isSuccesful() {
            return succesful;
        }
    }

    private Set<Target> getTargetsForLabels(LoadedPackageProvider loadedPackageProvider, Collection<Label> labels) {
        Set<Target> result = new HashSet<>();
        for (Label label : labels) {
            try {
                result.add(loadedPackageProvider.getLoadedTarget(label));
            } catch (NoSuchThingException e) {
                throw new IllegalStateException(e); // The target should have been loaded
            }
        }
        return result;
    }

    private ImmutableSet<Target> filterErrorFreeTargets(EventHandler eventHandler, EventBus eventBus,
            TransitivePackageLoader pkgLoader, Collection<Target> targetsToLoad,
            ListMultimap<String, Label> labelsToLoadUnconditionally) throws LoadingFailedException {
        // Error out if any of the labels needed for the configuration could not be loaded.
        Multimap<Label, Label> rootCauses = pkgLoader.getRootCauses();
        for (Map.Entry<String, Label> entry : labelsToLoadUnconditionally.entries()) {
            if (rootCauses.containsKey(entry.getValue())) {
                throw new LoadingFailedException(
                        "Failed to load required " + entry.getKey() + " target: '" + entry.getValue() + "'");
            }
        }

        // Post root causes for command-line targets that could not be loaded.
        for (Map.Entry<Label, Label> entry : rootCauses.entries()) {
            eventBus.post(new LoadingFailureEvent(entry.getKey(), entry.getValue()));
        }

        LoadedPackageProvider.Bridge bridge = new LoadedPackageProvider.Bridge(packageManager, eventHandler);
        return ImmutableSet.copyOf(Sets.difference(ImmutableSet.copyOf(targetsToLoad),
                getTargetsForLabels(bridge, rootCauses.keySet())));
    }

    /**
     * Interpret the command-line arguments.
     *
     * @param targetPatterns the list of command-line target patterns specified by the user
     * @param compileOneDependency if true, enables alternative interpretation of targetPatterns; see
     *     {@link LoadingOptions#compileOneDependency}
     * @throws TargetParsingException if parsing failed and !keepGoing
     */
    private ResolvedTargets<Target> getTargetsToBuild(EventHandler eventHandler, List<String> targetPatterns,
            boolean compileOneDependency, boolean keepGoing) throws TargetParsingException, InterruptedException {
        ResolvedTargets<Target> result = targetPatternEvaluator.parseTargetPatternList(eventHandler, targetPatterns,
                FilteringPolicies.FILTER_MANUAL, keepGoing);
        if (compileOneDependency) {
            return new CompileOneDependencyTransformer(packageManager).transformCompileOneDependency(eventHandler,
                    result);
        }
        return result;
    }

    /**
     * Interpret test target labels from the command-line arguments and return the corresponding set
     * of targets, handling the filter flags, and expanding test suites.
     *
     * @param eventHandler the error event eventHandler
     * @param targetPatterns the list of command-line target patterns specified by the user
     * @param options the loading phase options
     * @param keepGoing value of the --keep_going flag
     */
    private ResolvedTargets<Target> determineTests(EventHandler eventHandler, List<String> targetPatterns,
            LoadingOptions options, boolean keepGoing) throws TargetParsingException, InterruptedException {
        // Parse the targets to get the tests.
        ResolvedTargets<Target> testTargetsBuilder = targetPatternEvaluator.parseTargetPatternList(eventHandler,
                targetPatterns, FilteringPolicies.FILTER_TESTS, keepGoing);

        ResolvedTargets.Builder<Target> finalBuilder = ResolvedTargets.builder();
        finalBuilder.merge(testTargetsBuilder);
        finalBuilder.filter(TestFilter.forOptions(options, eventHandler, ruleNames));
        return finalBuilder.build();
    }

    /**
     * Emit a warning when a deprecated target is mentioned on the command line.
     *
     * <p>Note that this does not stop us from emitting "target X depends on deprecated target Y"
     * style warnings for the same target and it is a good thing; <i>depending</i> on a target and
     * <i>wanting</i> to build it are different things.
     */
    // Public for use by skyframe.TargetPatternPhaseFunction until this class goes away.
    public static void maybeReportDeprecation(EventHandler eventHandler, Collection<Target> targets) {
        for (Rule rule : Iterables.filter(targets, Rule.class)) {
            if (rule.isAttributeValueExplicitlySpecified("deprecation")) {
                eventHandler.handle(Event.warn(rule.getLocation(), String.format("target '%s' is deprecated: %s",
                        rule.getLabel(), NonconfigurableAttributeMapper.of(rule).get("deprecation", Type.STRING))));
            }
        }
    }
}