com.google.devtools.build.lib.skyframe.AspectFunction.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.skyframe.AspectFunction.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.skyframe;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.analysis.CachingAnalysisEnvironment;
import com.google.devtools.build.lib.analysis.ConfiguredAspect;
import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.MergedConfiguredTarget;
import com.google.devtools.build.lib.analysis.MergedConfiguredTarget.DuplicateException;
import com.google.devtools.build.lib.analysis.TargetAndConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
import com.google.devtools.build.lib.packages.NativeAspectClass;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.NoSuchThingException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.SkylarkAspect;
import com.google.devtools.build.lib.packages.SkylarkAspectClass;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.rules.AliasProvider;
import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredTargetFunctionException;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.ConfiguredValueCreationException;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetFunction.DependencyEvaluationException;
import com.google.devtools.build.lib.skyframe.SkyframeExecutor.BuildViewProvider;
import com.google.devtools.build.lib.skyframe.SkylarkImportLookupFunction.SkylarkImportFailedException;
import com.google.devtools.build.lib.syntax.Type.ConversionException;
import com.google.devtools.build.lib.util.OrderedSetMultimap;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.ArrayList;
import java.util.Map;
import javax.annotation.Nullable;

/**
 * The Skyframe function that generates aspects.
 *
 * This class, together with {@link ConfiguredTargetFunction} drives the analysis phase. For more
 * information, see {@link com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory}.
 *
 * {@link AspectFunction} takes a SkyKey containing an {@link AspectKey} [a tuple of
 * (target label, configurations, aspect class and aspect parameters)],
 * loads an {@link Aspect} from aspect class and aspect parameters,
 * gets a {@link ConfiguredTarget} for label and configurations, and then creates
 * a {@link ConfiguredAspect} for a given {@link AspectKey}.
 *
 * See {@link com.google.devtools.build.lib.packages.AspectClass} documentation
 * for an overview of aspect-related classes
 *
 * @see com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory
 * @see com.google.devtools.build.lib.packages.AspectClass
 */
public final class AspectFunction implements SkyFunction {
    private final BuildViewProvider buildViewProvider;
    private final RuleClassProvider ruleClassProvider;

    public AspectFunction(BuildViewProvider buildViewProvider, RuleClassProvider ruleClassProvider) {
        this.buildViewProvider = buildViewProvider;
        this.ruleClassProvider = ruleClassProvider;
    }

    /**
     * Load Skylark aspect from an extension file. Is to be called from a SkyFunction.
     *
     * @return {@code null} if dependencies cannot be satisfied.
     */
    @Nullable
    static SkylarkAspect loadSkylarkAspect(Environment env, Label extensionLabel, String skylarkValueName)
            throws AspectCreationException, InterruptedException {
        SkyKey importFileKey = SkylarkImportLookupValue.key(extensionLabel, false);
        try {
            SkylarkImportLookupValue skylarkImportLookupValue = (SkylarkImportLookupValue) env
                    .getValueOrThrow(importFileKey, SkylarkImportFailedException.class);
            if (skylarkImportLookupValue == null) {
                return null;
            }

            Object skylarkValue = skylarkImportLookupValue.getEnvironmentExtension().get(skylarkValueName);
            if (!(skylarkValue instanceof SkylarkAspect)) {
                throw new ConversionException(
                        skylarkValueName + " from " + extensionLabel.toString() + " is not an aspect");
            }
            return (SkylarkAspect) skylarkValue;
        } catch (SkylarkImportFailedException | ConversionException e) {
            env.getListener().handle(Event.error(e.getMessage()));
            throw new AspectCreationException(e.getMessage());
        }
    }

    @Nullable
    @Override
    public SkyValue compute(SkyKey skyKey, Environment env) throws AspectFunctionException, InterruptedException {
        SkyframeBuildView view = buildViewProvider.getSkyframeBuildView();
        NestedSetBuilder<Package> transitivePackages = NestedSetBuilder.stableOrder();
        NestedSetBuilder<Label> transitiveRootCauses = NestedSetBuilder.stableOrder();
        AspectKey key = (AspectKey) skyKey.argument();
        ConfiguredAspectFactory aspectFactory;
        Aspect aspect;
        if (key.getAspectClass() instanceof NativeAspectClass) {
            NativeAspectClass nativeAspectClass = (NativeAspectClass) key.getAspectClass();
            aspectFactory = (ConfiguredAspectFactory) nativeAspectClass;
            aspect = Aspect.forNative(nativeAspectClass, key.getParameters());
        } else if (key.getAspectClass() instanceof SkylarkAspectClass) {
            SkylarkAspectClass skylarkAspectClass = (SkylarkAspectClass) key.getAspectClass();
            SkylarkAspect skylarkAspect;
            try {
                skylarkAspect = loadSkylarkAspect(env, skylarkAspectClass.getExtensionLabel(),
                        skylarkAspectClass.getExportedName());
            } catch (AspectCreationException e) {
                throw new AspectFunctionException(e);
            }
            if (skylarkAspect == null) {
                return null;
            }

            aspectFactory = new SkylarkAspectFactory(skylarkAspect);
            aspect = Aspect.forSkylark(skylarkAspect.getAspectClass(),
                    skylarkAspect.getDefinition(key.getParameters()), key.getParameters());
        } else {
            throw new IllegalStateException();
        }

        // Keep this in sync with the same code in ConfiguredTargetFunction.
        PackageValue packageValue = (PackageValue) env
                .getValue(PackageValue.key(key.getLabel().getPackageIdentifier()));
        if (packageValue == null) {
            return null;
        }
        Package pkg = packageValue.getPackage();
        if (pkg.containsErrors()) {
            throw new AspectFunctionException(
                    new BuildFileContainsErrorsException(key.getLabel().getPackageIdentifier()));
        }

        Target target;
        try {
            target = pkg.getTarget(key.getLabel().getName());
        } catch (NoSuchTargetException e) {
            throw new AspectFunctionException(e);
        }

        if (!(target instanceof Rule)) {
            env.getListener()
                    .handle(Event.error(target.getLocation(),
                            String.format("%s is attached to %s %s but aspects must be attached to rules",
                                    aspect.getAspectClass().getName(), target.getTargetKind(), target.getName())));
            throw new AspectFunctionException(new AspectCreationException("aspects must be attached to rules"));
        }

        ConfiguredTargetValue configuredTargetValue;
        try {
            configuredTargetValue = (ConfiguredTargetValue) env.getValueOrThrow(
                    ConfiguredTargetValue.key(key.getLabel(), key.getBaseConfiguration()),
                    ConfiguredValueCreationException.class);
        } catch (ConfiguredValueCreationException e) {
            throw new AspectFunctionException(new AspectCreationException(e.getRootCauses()));
        }
        if (configuredTargetValue == null) {
            // TODO(bazel-team): remove this check when top-level targets also use dynamic configurations.
            // Right now the key configuration may be dynamic while the original target's configuration
            // is static, resulting in a Skyframe cache miss even though the original target is, in fact,
            // precomputed.
            return null;
        }

        if (configuredTargetValue.getConfiguredTarget() == null) {
            return null;
        }

        if (configuredTargetValue.getConfiguredTarget().getProvider(AliasProvider.class) != null) {
            return createAliasAspect(env, target, aspect, key, configuredTargetValue.getConfiguredTarget());
        }

        ConfiguredTarget associatedTarget = configuredTargetValue.getConfiguredTarget();

        ImmutableList.Builder<Aspect> aspectPathBuilder = ImmutableList.builder();

        if (key.getBaseKey() != null) {
            ImmutableList<SkyKey> aspectKeys = getSkyKeysForAspects(key.getBaseKey());

            Map<SkyKey, SkyValue> values = env.getValues(aspectKeys);
            if (env.valuesMissing()) {
                return null;
            }
            try {
                associatedTarget = getBaseTargetAndCollectPath(associatedTarget, aspectKeys, values,
                        aspectPathBuilder);
            } catch (DuplicateException e) {
                env.getListener().handle(Event.error(associatedTarget.getTarget().getLocation(), e.getMessage()));

                throw new AspectFunctionException(
                        new AspectCreationException(e.getMessage(), associatedTarget.getLabel()));

            }
        }
        aspectPathBuilder.add(aspect);

        SkyframeDependencyResolver resolver = view.createDependencyResolver(env);

        // When getting the dependencies of this hybrid aspect+base target, use the aspect's
        // configuration. The configuration of the aspect will always be a superset of the target's
        // (dynamic configuration mode: target is part of the aspect's config fragment requirements;
        // static configuration mode: target is the same configuration as the aspect), so the fragments
        // required by all dependencies (both those of the aspect and those of the base target)
        // will be present this way.
        TargetAndConfiguration originalTargetAndAspectConfiguration = new TargetAndConfiguration(target,
                key.getAspectConfiguration());
        ImmutableList<Aspect> aspectPath = aspectPathBuilder.build();
        try {
            // Get the configuration targets that trigger this rule's configurable attributes.
            ImmutableMap<Label, ConfigMatchingProvider> configConditions = ConfiguredTargetFunction
                    .getConfigConditions(target, env, resolver, originalTargetAndAspectConfiguration,
                            transitivePackages, transitiveRootCauses);
            if (configConditions == null) {
                // Those targets haven't yet been resolved.
                return null;
            }

            OrderedSetMultimap<Attribute, ConfiguredTarget> depValueMap;
            try {
                depValueMap = ConfiguredTargetFunction.computeDependencies(env, resolver,
                        originalTargetAndAspectConfiguration, aspectPath, configConditions, ruleClassProvider,
                        view.getHostConfiguration(originalTargetAndAspectConfiguration.getConfiguration()),
                        transitivePackages, transitiveRootCauses);
            } catch (ConfiguredTargetFunctionException e) {
                throw new AspectCreationException(e.getMessage());
            }
            if (depValueMap == null) {
                return null;
            }
            if (!transitiveRootCauses.isEmpty()) {
                throw new AspectFunctionException(
                        new AspectCreationException("Loading failed", transitiveRootCauses.build()));
            }

            return createAspect(env, key, aspectPath, aspect, aspectFactory, associatedTarget,
                    key.getAspectConfiguration(), configConditions, depValueMap, transitivePackages);
        } catch (DependencyEvaluationException e) {
            if (e.getCause() instanceof ConfiguredValueCreationException) {
                ConfiguredValueCreationException cause = (ConfiguredValueCreationException) e.getCause();
                throw new AspectFunctionException(
                        new AspectCreationException(cause.getMessage(), cause.getAnalysisRootCause()));
            } else {
                // Cast to InvalidConfigurationException as a consistency check. If you add any
                // DependencyEvaluationException constructors, you may need to change this code, too.
                InvalidConfigurationException cause = (InvalidConfigurationException) e.getCause();
                throw new AspectFunctionException(new AspectCreationException(cause.getMessage()));
            }
        } catch (AspectCreationException e) {
            throw new AspectFunctionException(e);
        }
    }

    /**
     * Merges aspects defined by {@code aspectKeys} into the {@code target} using
     * previously computed {@code values}.
     *
     * Also populates {@code aspectPath}.
     *
     * @return A {@link ConfiguredTarget} that is a result of a merge.
     * @throws DuplicateException if there is a duplicate provider provided by aspects.
     */
    private ConfiguredTarget getBaseTargetAndCollectPath(ConfiguredTarget target, ImmutableList<SkyKey> aspectKeys,
            Map<SkyKey, SkyValue> values, ImmutableList.Builder<Aspect> aspectPath) throws DuplicateException {
        ArrayList<ConfiguredAspect> aspectValues = new ArrayList<>();
        for (SkyKey skyAspectKey : aspectKeys) {
            AspectValue aspectValue = (AspectValue) values.get(skyAspectKey);
            ConfiguredAspect configuredAspect = aspectValue.getConfiguredAspect();
            aspectValues.add(configuredAspect);
            aspectPath.add(aspectValue.getAspect());
        }
        return MergedConfiguredTarget.of(target, aspectValues);
    }

    /**
     *  Returns a list of SkyKeys for all aspects the given aspect key depends on.
     *  The order corresponds to the order the aspects should be merged into a configured target.
     */
    private ImmutableList<SkyKey> getSkyKeysForAspects(AspectKey key) {
        ImmutableList.Builder<SkyKey> aspectKeysBuilder = ImmutableList.builder();
        AspectKey baseKey = key;
        while (baseKey != null) {
            aspectKeysBuilder.add(baseKey.getSkyKey());
            baseKey = baseKey.getBaseKey();
        }
        return aspectKeysBuilder.build().reverse();
    }

    private static SkyValue createAliasAspect(Environment env, Target originalTarget, Aspect aspect,
            AspectKey originalKey, ConfiguredTarget configuredTarget) throws InterruptedException {
        ImmutableList<Label> aliasChain = configuredTarget.getProvider(AliasProvider.class).getAliasChain();
        // Find the next alias in the chain: either the next alias (if there are two) or the name of
        // the real configured target.
        Label aliasLabel = aliasChain.size() > 1 ? aliasChain.get(1) : configuredTarget.getLabel();

        SkyKey depKey = ActionLookupValue.key(originalKey.withLabel(aliasLabel));

        // Compute the AspectValue of the target the alias refers to (which can itself be either an
        // alias or a real target)
        AspectValue real = (AspectValue) env.getValue(depKey);
        if (env.valuesMissing()) {
            return null;
        }

        NestedSet<Package> transitivePackages = NestedSetBuilder.<Package>stableOrder()
                .addTransitive(real.getTransitivePackages()).add(originalTarget.getPackage()).build();

        return new AspectValue(originalKey, aspect, originalTarget.getLabel(), originalTarget.getLocation(),
                ConfiguredAspect.forAlias(real.getConfiguredAspect()), ImmutableList.<ActionAnalysisMetadata>of(),
                transitivePackages);
    }

    @Nullable
    private AspectValue createAspect(Environment env, AspectKey key, ImmutableList<Aspect> aspectPath,
            Aspect aspect, ConfiguredAspectFactory aspectFactory, ConfiguredTarget associatedTarget,
            BuildConfiguration aspectConfiguration, ImmutableMap<Label, ConfigMatchingProvider> configConditions,
            OrderedSetMultimap<Attribute, ConfiguredTarget> directDeps,
            NestedSetBuilder<Package> transitivePackages) throws AspectFunctionException, InterruptedException {

        SkyframeBuildView view = buildViewProvider.getSkyframeBuildView();

        StoredEventHandler events = new StoredEventHandler();
        CachingAnalysisEnvironment analysisEnvironment = view.createAnalysisEnvironment(key, false, events, env,
                aspectConfiguration);
        if (env.valuesMissing()) {
            return null;
        }

        ConfiguredAspect configuredAspect = view.getConfiguredTargetFactory().createAspect(analysisEnvironment,
                associatedTarget, aspectPath, aspectFactory, aspect, directDeps, configConditions,
                aspectConfiguration, view.getHostConfiguration(aspectConfiguration));

        events.replayOn(env.getListener());
        if (events.hasErrors()) {
            analysisEnvironment.disable(associatedTarget.getTarget());
            throw new AspectFunctionException(new AspectCreationException(
                    "Analysis of target '" + associatedTarget.getLabel() + "' failed; build aborted"));
        }
        Preconditions.checkState(!analysisEnvironment.hasErrors(),
                "Analysis environment hasError() but no errors reported");

        if (env.valuesMissing()) {
            return null;
        }

        analysisEnvironment.disable(associatedTarget.getTarget());
        Preconditions.checkNotNull(configuredAspect);

        return new AspectValue(key, aspect, associatedTarget.getLabel(), associatedTarget.getTarget().getLocation(),
                configuredAspect, ImmutableList.copyOf(analysisEnvironment.getRegisteredActions()),
                transitivePackages.build());
    }

    @Override
    public String extractTag(SkyKey skyKey) {
        AspectKey aspectKey = (AspectKey) skyKey.argument();
        return Label.print(aspectKey.getLabel());
    }

    /**
     * An exception indicating that there was a problem creating an aspect.
     */
    public static final class AspectCreationException extends Exception {
        /** Targets in the transitive closure that failed to load. May be empty. */
        private final NestedSet<Label> loadingRootCauses;

        /**
         * The target for which analysis failed, if any. We can't represent aspects with labels, so if
         * the aspect analysis fails, this will be {@code null}.
         */
        @Nullable
        private final Label analysisRootCause;

        public AspectCreationException(String message, Label analysisRootCause) {
            super(message);
            this.loadingRootCauses = NestedSetBuilder.<Label>emptySet(Order.STABLE_ORDER);
            this.analysisRootCause = analysisRootCause;
        }

        public AspectCreationException(String message, NestedSet<Label> loadingRootCauses) {
            super(message);
            this.loadingRootCauses = loadingRootCauses;
            this.analysisRootCause = null;
        }

        public AspectCreationException(NestedSet<Label> loadingRootCauses) {
            this("Loading failed", loadingRootCauses);
        }

        public AspectCreationException(String message) {
            this(message, (Label) null);
        }

        public NestedSet<Label> getRootCauses() {
            return loadingRootCauses;
        }

        @Nullable
        public Label getAnalysisRootCause() {
            return analysisRootCause;
        }
    }

    /**
     * Used to indicate errors during the computation of an {@link AspectValue}.
     */
    private static final class AspectFunctionException extends SkyFunctionException {
        public AspectFunctionException(NoSuchThingException e) {
            super(e, Transience.PERSISTENT);
        }

        public AspectFunctionException(AspectCreationException e) {
            super(e, Transience.PERSISTENT);
        }
    }
}