com.google.devtools.build.lib.analysis.util.BuildViewForTesting.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.analysis.util.BuildViewForTesting.java

Source

// Copyright 2018 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.analysis.util;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.actions.ArtifactFactory;
import com.google.devtools.build.lib.actions.PackageRoots;
import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
import com.google.devtools.build.lib.analysis.AnalysisOptions;
import com.google.devtools.build.lib.analysis.AnalysisResult;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.AspectCollection;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.BuildView;
import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.Dependency;
import com.google.devtools.build.lib.analysis.DependencyResolver;
import com.google.devtools.build.lib.analysis.DependencyResolver.InconsistentAspectOrderException;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
import com.google.devtools.build.lib.analysis.ToolchainContext;
import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
import com.google.devtools.build.lib.analysis.WorkspaceStatusAction;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationCollection;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
import com.google.devtools.build.lib.analysis.config.ConfigurationResolver;
import com.google.devtools.build.lib.analysis.config.FragmentClassSet;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.analysis.config.TransitionResolver;
import com.google.devtools.build.lib.analysis.config.transitions.ConfigurationTransition;
import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
import com.google.devtools.build.lib.analysis.test.CoverageReportActionFactory;
import com.google.devtools.build.lib.causes.Cause;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.PackageSpecification;
import com.google.devtools.build.lib.packages.PackageSpecification.PackageGroupContents;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.pkgcache.LoadingResult;
import com.google.devtools.build.lib.skyframe.BuildConfigurationValue;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
import com.google.devtools.build.lib.skyframe.SkyframeBuildView;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
import com.google.devtools.build.lib.skyframe.ToolchainUtil.ToolchainContextException;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.util.OrderedSetMultimap;
import com.google.devtools.build.skyframe.SkyKey;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A util class that contains all the helper stuff previously in BuildView that only exists to give
 * tests access to Skyframe internals. The code largely predates the introduction of Skyframe, and
 * mostly exists to avoid having to rewrite our tests to work with Skyframe natively.
 */
@VisibleForTesting
public class BuildViewForTesting {
    private final BuildView buildView;
    private final SkyframeExecutor skyframeExecutor;
    private final SkyframeBuildView skyframeBuildView;

    private final ConfiguredRuleClassProvider ruleClassProvider;

    public BuildViewForTesting(BlazeDirectories directories, ConfiguredRuleClassProvider ruleClassProvider,
            SkyframeExecutor skyframeExecutor, CoverageReportActionFactory coverageReportActionFactory) {
        this.buildView = new BuildView(directories, ruleClassProvider, skyframeExecutor,
                coverageReportActionFactory);
        this.ruleClassProvider = ruleClassProvider;
        this.skyframeExecutor = Preconditions.checkNotNull(skyframeExecutor);
        this.skyframeBuildView = skyframeExecutor.getSkyframeBuildView();
    }

    @VisibleForTesting
    public Set<SkyKey> getSkyframeEvaluatedTargetKeysForTesting() {
        return skyframeBuildView.getEvaluatedTargetKeys();
    }

    /** The number of targets freshly evaluated in the last analysis run. */
    public int getTargetsVisited() {
        return buildView.getTargetsVisited();
    }

    /**
     * Returns whether the given configured target has errors.
     */
    @VisibleForTesting
    public boolean hasErrors(ConfiguredTarget configuredTarget) {
        return configuredTarget == null;
    }

    @ThreadCompatible
    public AnalysisResult update(LoadingResult loadingResult, BuildConfigurationCollection configurations,
            List<String> aspects, AnalysisOptions viewOptions, boolean keepGoing, int loadingPhaseThreads,
            TopLevelArtifactContext topLevelOptions, ExtendedEventHandler eventHandler, EventBus eventBus)
            throws ViewCreationFailedException, InterruptedException {
        return buildView.update(loadingResult, configurations, aspects, viewOptions, keepGoing, loadingPhaseThreads,
                topLevelOptions, eventHandler, eventBus);
    }

    @VisibleForTesting
    WorkspaceStatusAction getLastWorkspaceBuildInfoActionForTesting() throws InterruptedException {
        return skyframeExecutor.getLastWorkspaceStatusAction();
    }

    /** Sets the configurations. Not thread-safe. DO NOT CALL except from tests! */
    @VisibleForTesting
    public void setConfigurationsForTesting(EventHandler eventHandler,
            BuildConfigurationCollection configurations) {
        skyframeBuildView.setConfigurations(eventHandler, configurations);
    }

    public ArtifactFactory getArtifactFactory() {
        return skyframeBuildView.getArtifactFactory();
    }

    /**
     * Gets a configuration for the given target.
     *
     * <p>If {@link BuildConfiguration.Options#trimConfigurations()} is true, the configuration only
     * includes the fragments needed by the fragment and its transitive closure. Else unconditionally
     * includes all fragments.
     */
    @VisibleForTesting
    public BuildConfiguration getConfigurationForTesting(Target target, BuildConfiguration config,
            ExtendedEventHandler eventHandler) throws InterruptedException {
        List<TargetAndConfiguration> node = ImmutableList
                .<TargetAndConfiguration>of(new TargetAndConfiguration(target, config));
        LinkedHashSet<TargetAndConfiguration> configs = ConfigurationResolver.getConfigurationsFromExecutor(node,
                AnalysisUtils.targetsToDeps(new LinkedHashSet<TargetAndConfiguration>(node), ruleClassProvider),
                eventHandler, skyframeExecutor);
        return configs.iterator().next().getConfiguration();
    }

    /**
     * Sets the possible artifact roots in the artifact factory. This allows the factory to resolve
     * paths with unknown roots to artifacts.
     */
    @VisibleForTesting // for BuildViewTestCase
    public void setArtifactRoots(PackageRoots packageRoots) {
        getArtifactFactory().setPackageRoots(packageRoots.getPackageRootLookup());
    }

    @VisibleForTesting
    public Collection<ConfiguredTarget> getDirectPrerequisitesForTesting(ExtendedEventHandler eventHandler,
            ConfiguredTarget ct, BuildConfigurationCollection configurations) throws EvalException,
            InvalidConfigurationException, InterruptedException, InconsistentAspectOrderException {
        return Collections2.transform(
                getConfiguredTargetAndDataDirectPrerequisitesForTesting(eventHandler, ct, configurations),
                ConfiguredTargetAndData::getConfiguredTarget);
    }

    // TODO(janakr): pass the configuration in as a parameter here and above.
    private Collection<ConfiguredTargetAndData> getConfiguredTargetAndDataDirectPrerequisitesForTesting(
            ExtendedEventHandler eventHandler, ConfiguredTarget ct, BuildConfigurationCollection configurations)
            throws EvalException, InvalidConfigurationException, InterruptedException,
            InconsistentAspectOrderException {
        return getConfiguredTargetAndDataDirectPrerequisitesForTesting(eventHandler, ct, ct.getConfigurationKey(),
                configurations);
    }

    @VisibleForTesting
    public Collection<ConfiguredTargetAndData> getConfiguredTargetAndDataDirectPrerequisitesForTesting(
            ExtendedEventHandler eventHandler, ConfiguredTargetAndData ct,
            BuildConfigurationCollection configurations) throws EvalException, InvalidConfigurationException,
            InterruptedException, InconsistentAspectOrderException {
        return getConfiguredTargetAndDataDirectPrerequisitesForTesting(eventHandler, ct.getConfiguredTarget(),
                ct.getConfiguredTarget().getConfigurationKey(), configurations);
    }

    private Collection<ConfiguredTargetAndData> getConfiguredTargetAndDataDirectPrerequisitesForTesting(
            ExtendedEventHandler eventHandler, ConfiguredTarget ct, BuildConfigurationValue.Key configuration,
            BuildConfigurationCollection configurations) throws EvalException, InvalidConfigurationException,
            InterruptedException, InconsistentAspectOrderException {
        return skyframeExecutor.getConfiguredTargetsForTesting(eventHandler, configuration,
                ImmutableSet.copyOf(getDirectPrerequisiteDependenciesForTesting(eventHandler, ct, configurations,
                        /*toolchainContext=*/ null).values()));
    }

    @VisibleForTesting
    public OrderedSetMultimap<Attribute, Dependency> getDirectPrerequisiteDependenciesForTesting(
            final ExtendedEventHandler eventHandler, final ConfiguredTarget ct,
            BuildConfigurationCollection configurations, ToolchainContext toolchainContext) throws EvalException,
            InvalidConfigurationException, InterruptedException, InconsistentAspectOrderException {

        Target target = null;
        try {
            target = skyframeExecutor.getPackageManager().getTarget(eventHandler, ct.getLabel());
        } catch (NoSuchPackageException | NoSuchTargetException | InterruptedException e) {
            eventHandler.handle(Event.error("Failed to get target from package during prerequisite analysis." + e));
            return OrderedSetMultimap.create();
        }

        if (!(target instanceof Rule)) {
            return OrderedSetMultimap.create();
        }

        class SilentDependencyResolver extends DependencyResolver {
            private SilentDependencyResolver() {
            }

            @Override
            protected void invalidVisibilityReferenceHook(TargetAndConfiguration node, Label label) {
                throw new RuntimeException("bad visibility on " + label + " during testing unexpected");
            }

            @Override
            protected void invalidPackageGroupReferenceHook(TargetAndConfiguration node, Label label) {
                throw new RuntimeException("bad package group on " + label + " during testing unexpected");
            }

            @Override
            protected void missingEdgeHook(Target from, Label to, NoSuchThingException e) {
                throw new RuntimeException(
                        "missing dependency from " + from.getLabel() + " to " + to + ": " + e.getMessage(), e);
            }

            @Override
            protected Target getTarget(Target from, Label label, NestedSetBuilder<Cause> rootCauses)
                    throws InterruptedException {
                try {
                    return skyframeExecutor.getPackageManager().getTarget(eventHandler, label);
                } catch (NoSuchThingException e) {
                    throw new IllegalStateException(e);
                }
            }

            @Override
            protected List<BuildConfiguration> getConfigurations(FragmentClassSet fragments,
                    Iterable<BuildOptions> buildOptions, BuildOptions defaultBuildOptions) {
                Preconditions.checkArgument(
                        fragments.fragmentClasses().equals(ct.getConfigurationKey().getFragments()),
                        "Mismatch: %s %s", ct, fragments);
                Dependency asDep = Dependency.withTransitionAndAspects(ct.getLabel(), NoTransition.INSTANCE,
                        AspectCollection.EMPTY);
                ImmutableList.Builder<BuildConfiguration> builder = ImmutableList.builder();
                for (BuildOptions options : buildOptions) {
                    builder.add(Iterables.getOnlyElement(skyframeExecutor
                            .getConfigurations(eventHandler, options, ImmutableList.<Dependency>of(asDep))
                            .values()));
                }
                return builder.build();
            }
        }

        DependencyResolver dependencyResolver = new SilentDependencyResolver();
        TargetAndConfiguration ctgNode = new TargetAndConfiguration(target,
                skyframeExecutor.getConfiguration(eventHandler, ct.getConfigurationKey()));
        return dependencyResolver.dependentNodeMap(ctgNode, configurations.getHostConfiguration(), /*aspect=*/ null,
                getConfigurableAttributeKeysForTesting(eventHandler, ctgNode),
                toolchainContext == null ? ImmutableSet.of() : toolchainContext.resolvedToolchainLabels(),
                skyframeExecutor.getDefaultBuildOptions(), ruleClassProvider.getTrimmingTransitionFactory());
    }

    /**
     * Returns ConfigMatchingProvider instances corresponding to the configurable attribute keys
     * present in this rule's attributes.
     */
    private ImmutableMap<Label, ConfigMatchingProvider> getConfigurableAttributeKeysForTesting(
            ExtendedEventHandler eventHandler, TargetAndConfiguration ctg) {
        if (!(ctg.getTarget() instanceof Rule)) {
            return ImmutableMap.of();
        }
        Rule rule = (Rule) ctg.getTarget();
        Map<Label, ConfigMatchingProvider> keys = new LinkedHashMap<>();
        RawAttributeMapper mapper = RawAttributeMapper.of(rule);
        for (Attribute attribute : rule.getAttributes()) {
            for (Label label : mapper.getConfigurabilityKeys(attribute.getName(), attribute.getType())) {
                if (BuildType.Selector.isReservedLabel(label)) {
                    continue;
                }
                ConfiguredTarget ct = getConfiguredTargetForTesting(eventHandler, label, ctg.getConfiguration());
                keys.put(label, Preconditions.checkNotNull(ct.getProvider(ConfigMatchingProvider.class)));
            }
        }
        return ImmutableMap.copyOf(keys);
    }

    private OrderedSetMultimap<Attribute, ConfiguredTargetAndData> getPrerequisiteMapForTesting(
            final ExtendedEventHandler eventHandler, ConfiguredTarget target,
            BuildConfigurationCollection configurations, ToolchainContext toolchainContext) throws EvalException,
            InvalidConfigurationException, InterruptedException, InconsistentAspectOrderException {
        OrderedSetMultimap<Attribute, Dependency> depNodeNames = getDirectPrerequisiteDependenciesForTesting(
                eventHandler, target, configurations, toolchainContext);

        ImmutableMultimap<Dependency, ConfiguredTargetAndData> cts = skyframeExecutor
                .getConfiguredTargetMapForTesting(eventHandler, target.getConfigurationKey(),
                        ImmutableSet.copyOf(depNodeNames.values()));

        OrderedSetMultimap<Attribute, ConfiguredTargetAndData> result = OrderedSetMultimap.create();
        for (Map.Entry<Attribute, Dependency> entry : depNodeNames.entries()) {
            result.putAll(entry.getKey(), cts.get(entry.getValue()));
        }
        return result;
    }

    private ConfigurationTransition getTopLevelTransitionForTarget(Label label, BuildConfiguration config,
            ExtendedEventHandler handler) {
        Target target;
        try {
            target = skyframeExecutor.getPackageManager().getTarget(handler, label);
        } catch (NoSuchPackageException | NoSuchTargetException e) {
            return NoTransition.INSTANCE;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new AssertionError("Configuration of " + label + " interrupted");
        }
        return TransitionResolver.evaluateTopLevelTransition(new TargetAndConfiguration(target, config),
                ruleClassProvider.getTrimmingTransitionFactory());
    }

    /**
     * Returns a configured target for the specified target and configuration. If the target in
     * question has a top-level rule class transition, that transition is applied in the returned
     * ConfiguredTarget.
     *
     * <p>Returns {@code null} if something goes wrong.
     */
    @VisibleForTesting
    public ConfiguredTarget getConfiguredTargetForTesting(ExtendedEventHandler eventHandler, Label label,
            BuildConfiguration config) {
        return skyframeExecutor.getConfiguredTargetForTesting(eventHandler, label, config,
                getTopLevelTransitionForTarget(label, config, eventHandler));
    }

    @VisibleForTesting
    public ConfiguredTargetAndData getConfiguredTargetAndDataForTesting(ExtendedEventHandler eventHandler,
            Label label, BuildConfiguration config) {
        return skyframeExecutor.getConfiguredTargetAndDataForTesting(eventHandler, label, config,
                getTopLevelTransitionForTarget(label, config, eventHandler));
    }

    /**
     * Returns a RuleContext which is the same as the original RuleContext of the target parameter.
     */
    @VisibleForTesting
    public RuleContext getRuleContextForTesting(ConfiguredTarget target, StoredEventHandler eventHandler,
            BuildConfigurationCollection configurations) throws EvalException, InvalidConfigurationException,
            InterruptedException, InconsistentAspectOrderException, ToolchainContextException {
        BuildConfiguration targetConfig = skyframeExecutor.getConfiguration(eventHandler,
                target.getConfigurationKey());
        CachingAnalysisEnvironment env = new CachingAnalysisEnvironment(getArtifactFactory(),
                skyframeExecutor.getActionKeyContext(), ConfiguredTargetKey.of(target.getLabel(), targetConfig),
                /*isSystemEnv=*/ false, targetConfig.extendedSanityChecks(), eventHandler, /*env=*/ null);
        return getRuleContextForTesting(eventHandler, target, env, configurations);
    }

    /**
     * Creates and returns a rule context that is equivalent to the one that was used to create the
     * given configured target.
     */
    @VisibleForTesting
    public RuleContext getRuleContextForTesting(ExtendedEventHandler eventHandler,
            ConfiguredTarget configuredTarget, AnalysisEnvironment env, BuildConfigurationCollection configurations)
            throws EvalException, InvalidConfigurationException, InterruptedException,
            InconsistentAspectOrderException, ToolchainContextException {
        BuildConfiguration targetConfig = skyframeExecutor.getConfiguration(eventHandler,
                configuredTarget.getConfigurationKey());
        Target target = null;
        try {
            target = skyframeExecutor.getPackageManager().getTarget(eventHandler, configuredTarget.getLabel());
        } catch (NoSuchPackageException | NoSuchTargetException e) {
            eventHandler.handle(Event.error("Failed to get target when trying to get rule context for testing"));
            throw new IllegalStateException(e);
        }
        Set<Label> requiredToolchains = target.getAssociatedRule().getRuleClassObject().getRequiredToolchains();
        ToolchainContext toolchainContext = skyframeExecutor.getToolchainContextForTesting(requiredToolchains,
                targetConfig, eventHandler);
        OrderedSetMultimap<Attribute, ConfiguredTargetAndData> prerequisiteMap = getPrerequisiteMapForTesting(
                eventHandler, configuredTarget, configurations, toolchainContext);
        toolchainContext.resolveToolchains(prerequisiteMap);

        return new RuleContext.Builder(env, (Rule) target, ImmutableList.of(), targetConfig,
                configurations.getHostConfiguration(), ruleClassProvider.getPrerequisiteValidator(),
                ((Rule) target).getRuleClassObject().getConfigurationFragmentPolicy())
                        .setVisibility(NestedSetBuilder.create(Order.STABLE_ORDER,
                                PackageGroupContents.create(ImmutableList.of(PackageSpecification.everything()))))
                        .setPrerequisites(getPrerequisiteMapForTesting(eventHandler, configuredTarget,
                                configurations, toolchainContext))
                        .setConfigConditions(ImmutableMap.<Label, ConfigMatchingProvider>of())
                        .setUniversalFragments(ruleClassProvider.getUniversalFragments())
                        .setToolchainContext(toolchainContext)
                        .setConstraintSemantics(ruleClassProvider.getConstraintSemantics()).build();
    }

    /**
     * For a configured target dependentTarget, returns the desired configured target that is depended
     * upon. Useful for obtaining the a target with aspects required by the dependent.
     */
    @VisibleForTesting
    public ConfiguredTarget getPrerequisiteConfiguredTargetForTesting(ExtendedEventHandler eventHandler,
            ConfiguredTarget dependentTarget, Label desiredTarget, BuildConfigurationCollection configurations)
            throws EvalException, InvalidConfigurationException, InterruptedException,
            InconsistentAspectOrderException {
        Collection<ConfiguredTargetAndData> configuredTargets = getPrerequisiteMapForTesting(eventHandler,
                dependentTarget, configurations, /*toolchainContext=*/ null).values();
        for (ConfiguredTargetAndData ct : configuredTargets) {
            if (ct.getTarget().getLabel().equals(desiredTarget)) {
                return ct.getConfiguredTarget();
            }
        }
        return null;
    }
}