blob: 3faff2a51461320c6893cdff8380a54955b644b5 [file] [log] [blame]
// Copyright 2021 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.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.analysis.AspectCollection;
import com.google.devtools.build.lib.analysis.AspectCollection.AspectCycleOnPathException;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.AspectClass;
import com.google.devtools.build.lib.packages.AspectDescriptor;
import com.google.devtools.build.lib.packages.AspectsList;
import com.google.devtools.build.lib.packages.NativeAspectClass;
import com.google.devtools.build.lib.packages.StarlarkAspect;
import com.google.devtools.build.lib.packages.StarlarkAspectClass;
import com.google.devtools.build.lib.packages.StarlarkDefinedAspect;
import com.google.devtools.build.lib.server.FailureDetails.Analysis;
import com.google.devtools.build.lib.server.FailureDetails.Analysis.Code;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey;
import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.util.DetailedExitCode;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
/**
* {@link SkyFunction} to load top level aspects, build the dependency relation between them based
* on the aspects required by the top level aspects and the aspect providers they require and
* advertise using {@link AspectCollection}.
*
* <p>This is needed to compute the relationship between top-level aspects once for all top-level
* targets in the command. The {@link SkyValue} of this function contains a list of {@link
* AspectDetails} objects which contain the aspect descriptor and a list of the used aspects this
* aspect depends on. Then {@link ToplevelStarlarkAspectFunction} adds the target information to
* create {@link AspectKey}.
*/
final class BuildTopLevelAspectsDetailsFunction implements SkyFunction {
@Nullable
@Override
public SkyValue compute(SkyKey skyKey, Environment env)
throws BuildTopLevelAspectsDetailsFunctionException, InterruptedException {
BuildTopLevelAspectsDetailsKey topLevelAspectsDetailsKey =
(BuildTopLevelAspectsDetailsKey) skyKey.argument();
ImmutableList<Aspect> topLevelAspects =
getTopLevelAspects(
env,
topLevelAspectsDetailsKey.getTopLevelAspectsClasses(),
topLevelAspectsDetailsKey.getTopLevelAspectsParameters());
if (topLevelAspects == null) {
return null; // some aspects are not loaded
}
AspectCollection aspectCollection;
try {
aspectCollection = AspectCollection.create(topLevelAspects);
} catch (AspectCycleOnPathException e) {
// This exception should never happen because aspects duplicates are not allowed in top-level
// aspects and their existence should have been caught and reported by `getTopLevelAspects()`.
env.getListener().handle(Event.error(e.getMessage()));
throw new BuildTopLevelAspectsDetailsFunctionException(
new TopLevelAspectsDetailsBuildFailedException(
e.getMessage(), Code.ASPECT_CREATION_FAILED));
}
return new BuildTopLevelAspectsDetailsValue(getTopLevelAspectsDetails(aspectCollection));
}
@Nullable
private static StarlarkDefinedAspect loadStarlarkAspect(
Environment env, StarlarkAspectClass aspectClass)
throws InterruptedException, BuildTopLevelAspectsDetailsFunctionException {
StarlarkDefinedAspect starlarkAspect;
try {
BzlLoadValue bzlLoadValue =
(BzlLoadValue)
env.getValueOrThrow(
AspectFunction.bzlLoadKeyForStarlarkAspect(aspectClass),
BzlLoadFailedException.class);
if (bzlLoadValue == null) {
return null;
}
starlarkAspect = AspectFunction.loadAspectFromBzl(aspectClass, bzlLoadValue);
} catch (BzlLoadFailedException | AspectCreationException e) {
env.getListener().handle(Event.error(e.getMessage()));
throw new BuildTopLevelAspectsDetailsFunctionException(
new TopLevelAspectsDetailsBuildFailedException(
e.getMessage(), Code.ASPECT_CREATION_FAILED));
}
return starlarkAspect;
}
@Nullable
private static ImmutableList<Aspect> getTopLevelAspects(
Environment env,
ImmutableList<AspectClass> topLevelAspectsClasses,
ImmutableMap<String, String> topLevelAspectsParameters)
throws InterruptedException, BuildTopLevelAspectsDetailsFunctionException {
AspectsList.Builder builder = new AspectsList.Builder();
for (AspectClass aspectClass : topLevelAspectsClasses) {
if (aspectClass instanceof StarlarkAspectClass) {
StarlarkAspect starlarkAspect = loadStarlarkAspect(env, (StarlarkAspectClass) aspectClass);
if (starlarkAspect == null) {
return null;
}
try {
builder.addAspect(starlarkAspect);
} catch (EvalException e) {
env.getListener().handle(Event.error(e.getMessage()));
throw new BuildTopLevelAspectsDetailsFunctionException(
new TopLevelAspectsDetailsBuildFailedException(
e.getMessage(), Code.ASPECT_CREATION_FAILED));
}
} else {
try {
builder.addAspect((NativeAspectClass) aspectClass);
} catch (AssertionError e) {
env.getListener().handle(Event.error(e.getMessage()));
throw new BuildTopLevelAspectsDetailsFunctionException(
new TopLevelAspectsDetailsBuildFailedException(
e.getMessage(), Code.ASPECT_CREATION_FAILED));
}
}
}
AspectsList aspectsList = builder.build();
try {
aspectsList.validateTopLevelAspectsParameters(topLevelAspectsParameters);
return aspectsList.buildAspects(topLevelAspectsParameters);
} catch (EvalException e) {
env.getListener().handle(Event.error(e.getMessage()));
throw new BuildTopLevelAspectsDetailsFunctionException(
new TopLevelAspectsDetailsBuildFailedException(
e.getMessage(), Code.ASPECT_CREATION_FAILED));
}
}
private static Collection<AspectDetails> getTopLevelAspectsDetails(
AspectCollection aspectCollection) {
Map<AspectDescriptor, AspectDetails> result = new HashMap<>();
for (AspectCollection.AspectDeps aspectDeps : aspectCollection.getUsedAspects()) {
buildAspectDetails(aspectDeps, result);
}
return result.values();
}
private static AspectDetails buildAspectDetails(
AspectCollection.AspectDeps aspectDeps, Map<AspectDescriptor, AspectDetails> result) {
if (result.containsKey(aspectDeps.getAspect())) {
return result.get(aspectDeps.getAspect());
}
ImmutableList.Builder<AspectDetails> dependentAspects = ImmutableList.builder();
for (AspectCollection.AspectDeps path : aspectDeps.getUsedAspects()) {
dependentAspects.add(buildAspectDetails(path, result));
}
AspectDetails aspectDetails =
new AspectDetails(dependentAspects.build(), aspectDeps.getAspect());
result.put(aspectDetails.getAspectDescriptor(), aspectDetails);
return aspectDetails;
}
private static final class BuildTopLevelAspectsDetailsFunctionException
extends SkyFunctionException {
BuildTopLevelAspectsDetailsFunctionException(TopLevelAspectsDetailsBuildFailedException cause) {
super(cause, Transience.PERSISTENT);
}
}
private static final class TopLevelAspectsDetailsBuildFailedException extends Exception
implements SaneAnalysisException {
private final DetailedExitCode detailedExitCode;
private TopLevelAspectsDetailsBuildFailedException(String errorMessage, Code code) {
super(errorMessage);
this.detailedExitCode =
DetailedExitCode.of(
FailureDetail.newBuilder()
.setMessage(errorMessage)
.setAnalysis(Analysis.newBuilder().setCode(code))
.build());
}
@Override
public DetailedExitCode getDetailedExitCode() {
return detailedExitCode;
}
}
/**
* Details of the top-level aspects including the {@link AspectDescriptor} and a list of the
* aspects it depends on. This is used to build the {@link AspectKey} when combined with
* configured target details.
*/
static final class AspectDetails {
private final ImmutableList<AspectDetails> usedAspects;
private final AspectDescriptor aspectDescriptor;
private AspectDetails(
ImmutableList<AspectDetails> usedAspects, AspectDescriptor aspectDescriptor) {
this.usedAspects = usedAspects;
this.aspectDescriptor = aspectDescriptor;
}
public AspectDescriptor getAspectDescriptor() {
return aspectDescriptor;
}
public ImmutableList<AspectDetails> getUsedAspects() {
return usedAspects;
}
}
/** {@link SkyKey} for building top-level aspects details. */
@AutoCodec
static final class BuildTopLevelAspectsDetailsKey implements SkyKey {
private static final SkyKeyInterner<BuildTopLevelAspectsDetailsKey> interner =
SkyKey.newInterner();
private final ImmutableList<AspectClass> topLevelAspectsClasses;
private final ImmutableMap<String, String> topLevelAspectsParameters;
private final int hashCode;
static BuildTopLevelAspectsDetailsKey create(
ImmutableList<AspectClass> topLevelAspectsClasses,
ImmutableMap<String, String> topLevelAspectsParameters) {
return interner.intern(
new BuildTopLevelAspectsDetailsKey(
topLevelAspectsClasses,
topLevelAspectsParameters,
Objects.hashCode(topLevelAspectsClasses, topLevelAspectsParameters)));
}
@VisibleForSerialization
@AutoCodec.Interner
static BuildTopLevelAspectsDetailsKey intern(BuildTopLevelAspectsDetailsKey key) {
return interner.intern(key);
}
private BuildTopLevelAspectsDetailsKey(
ImmutableList<AspectClass> topLevelAspectsClasses,
@Nullable ImmutableMap<String, String> topLevelAspectsParameters,
int hashCode) {
Preconditions.checkArgument(!topLevelAspectsClasses.isEmpty(), "No aspects");
this.topLevelAspectsClasses = topLevelAspectsClasses;
this.topLevelAspectsParameters = topLevelAspectsParameters;
this.hashCode = hashCode;
}
@Override
public SkyFunctionName functionName() {
return SkyFunctions.BUILD_TOP_LEVEL_ASPECTS_DETAILS;
}
ImmutableList<AspectClass> getTopLevelAspectsClasses() {
return topLevelAspectsClasses;
}
ImmutableMap<String, String> getTopLevelAspectsParameters() {
return topLevelAspectsParameters;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof BuildTopLevelAspectsDetailsKey)) {
return false;
}
BuildTopLevelAspectsDetailsKey that = (BuildTopLevelAspectsDetailsKey) o;
return hashCode == that.hashCode
&& topLevelAspectsClasses.equals(that.topLevelAspectsClasses)
&& topLevelAspectsParameters.equals(that.topLevelAspectsParameters);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("topLevelAspectsClasses", topLevelAspectsClasses)
.add("topLevelAspectsParameters", topLevelAspectsParameters)
.toString();
}
@Override
public SkyKeyInterner<BuildTopLevelAspectsDetailsKey> getSkyKeyInterner() {
return interner;
}
}
/**
* {@link SkyValue} for {@code BuildTopLevelAspectsDetailsKey} wraps a list of the {@code
* AspectDetails} of the top level aspects.
*/
static final class BuildTopLevelAspectsDetailsValue implements SkyValue {
private final ImmutableList<AspectDetails> aspectsDetails;
private BuildTopLevelAspectsDetailsValue(Collection<AspectDetails> aspectsDetails) {
this.aspectsDetails = ImmutableList.copyOf(aspectsDetails);
}
public ImmutableList<AspectDetails> getAspectsDetails() {
return aspectsDetails;
}
}
}