blob: 3917534afedc510afbb7630e51095974fde2becf [file] [log] [blame]
// 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.common.collect.ListMultimap;
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.RuleConfiguredTarget;
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.packages.TargetUtils;
import com.google.devtools.build.lib.skyframe.AspectValue.AspectKey;
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.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 javax.annotation.Nullable;
/**
* The Skyframe function that generates aspects.
*
* {@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
*/
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
public static SkylarkAspect loadSkylarkAspect(
Environment env, Label extensionLabel, String skylarkValueName)
throws AspectCreationException {
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);
}
Label aliasLabel = TargetUtils.getAliasTarget(target);
if (aliasLabel != null) {
return createAliasAspect(env, target, aliasLabel, aspect, key);
}
if (!(target instanceof Rule)) {
throw new AspectFunctionException(new AspectCreationException(
"aspects must be attached to rules"));
}
final 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;
}
RuleConfiguredTarget associatedTarget =
(RuleConfiguredTarget) configuredTargetValue.getConfiguredTarget();
if (associatedTarget == null) {
return null;
}
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());
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;
}
ListMultimap<Attribute, ConfiguredTarget> depValueMap =
ConfiguredTargetFunction.computeDependencies(
env,
resolver,
originalTargetAndAspectConfiguration,
aspect,
configConditions,
ruleClassProvider,
view.getHostConfiguration(originalTargetAndAspectConfiguration.getConfiguration()),
transitivePackages,
transitiveRootCauses);
if (depValueMap == null) {
return null;
}
if (!transitiveRootCauses.isEmpty()) {
throw new AspectFunctionException(
new AspectCreationException("Loading failed", transitiveRootCauses.build()));
}
return createAspect(
env,
key,
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);
}
}
private SkyValue createAliasAspect(Environment env, Target originalTarget, Label aliasLabel,
Aspect aspect, AspectKey originalKey) {
SkyKey depKey = AspectValue.key(aliasLabel,
originalKey.getAspectConfiguration(),
originalKey.getBaseConfiguration(),
originalKey.getAspectClass(),
originalKey.getParameters());
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,
Aspect aspect,
ConfiguredAspectFactory aspectFactory,
RuleConfiguredTarget associatedTarget,
BuildConfiguration aspectConfiguration,
ImmutableMap<Label, ConfigMatchingProvider> configConditions,
ListMultimap<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,
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());
}
@Nullable
@Override
public String extractTag(SkyKey skyKey) {
return null;
}
/**
* 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);
}
}
}