| // Copyright 2025 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.packages; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.cmdline.LabelSyntaxException; | |
| import com.google.devtools.build.lib.cmdline.StarlarkThreadContext; | |
| import com.google.devtools.build.lib.events.Event; | |
| import com.google.devtools.build.lib.events.ExtendedEventHandler; | |
| import com.google.devtools.build.lib.starlarkbuildapi.StarlarkAspectPropagationContextApi; | |
| import java.util.Objects; | |
| import net.starlark.java.eval.EvalException; | |
| import net.starlark.java.eval.Mutability; | |
| import net.starlark.java.eval.Sequence; | |
| import net.starlark.java.eval.Starlark; | |
| import net.starlark.java.eval.StarlarkFunction; | |
| import net.starlark.java.eval.StarlarkList; | |
| import net.starlark.java.eval.StarlarkSemantics; | |
| import net.starlark.java.eval.StarlarkThread; | |
| /** | |
| * Supplies the list of edges (attribute names or toolchain types) that should be propagated by an | |
| * aspect. It is extended by 2 classes: | |
| * | |
| * <ul> | |
| * <li>FixedListSupplier: for the case when the list is fixed and known at the aspect definition | |
| * time. | |
| * <li>FunctionSupplier: for the case when the list is computed for each target that aspect | |
| * visits. | |
| * </ul> | |
| * | |
| * The type <T> is String for {@code attr_aspects} and {@link Label} for {@code toolchains_aspects}. | |
| */ | |
| public sealed interface AspectPropagationEdgesSupplier<T> { | |
| Label ALL_TOOLCHAINS = Label.parseCanonicalUnchecked("//__toolchains_aspects__:all"); | |
| public static final AspectPropagationEdgesSupplier<String> DEFAULT_ATTR_ASPECTS_SUPPLIER = | |
| new FixedListSupplier<>(ImmutableSet.of()); | |
| public static final FixedListSupplier<Label> DEFAULT_TOOLCHAINS_ASPECTS_SUPPLIER = | |
| new FixedListSupplier<>(ImmutableSet.of()); | |
| /** A supplier of the edges that is fixed and known at the aspect definition time. */ | |
| public static final class FixedListSupplier<T> implements AspectPropagationEdgesSupplier<T> { | |
| private final ImmutableSet<T> edges; | |
| private FixedListSupplier(ImmutableSet<T> edges) { | |
| this.edges = edges; | |
| } | |
| public ImmutableSet<T> getList() { | |
| return edges; | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (this == o) { | |
| return true; | |
| } | |
| if (o == null || !(o instanceof FixedListSupplier<?> that)) { | |
| return false; | |
| } | |
| return Objects.equals(edges, that.edges); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hashCode(edges); | |
| } | |
| } | |
| /** A supplier of the edges that is computed for each target that aspect visits. */ | |
| public abstract static sealed class FunctionSupplier<T> | |
| implements AspectPropagationEdgesSupplier<T> { | |
| private final StarlarkFunction function; | |
| private final StarlarkSemantics semantics; | |
| private FunctionSupplier(StarlarkFunction function, StarlarkSemantics semantics) { | |
| this.function = function; | |
| this.semantics = semantics; | |
| } | |
| public abstract ImmutableSet<T> computeList( | |
| StarlarkAspectPropagationContextApi context, ExtendedEventHandler eventHandler) | |
| throws InterruptedException, EvalException; | |
| @Override | |
| public boolean equals(Object o) { | |
| if (this == o) { | |
| return true; | |
| } | |
| if (o == null || !(o instanceof FunctionSupplier<?> that)) { | |
| return false; | |
| } | |
| return Objects.equals(function, that.function) && Objects.equals(semantics, that.semantics); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(function, semantics); | |
| } | |
| private static final class AspectPropagationEdgesThreadContext extends StarlarkThreadContext { | |
| private AspectPropagationEdgesThreadContext() { | |
| super(null); | |
| } | |
| } | |
| protected StarlarkList<?> runFunction( | |
| StarlarkAspectPropagationContextApi context, ExtendedEventHandler eventHandler) | |
| throws InterruptedException, EvalException { | |
| try (Mutability mu = Mutability.create("aspect_propagation_edges")) { | |
| StarlarkThread thread = StarlarkThread.createTransient(mu, semantics); | |
| thread.setPrintHandler(Event.makeDebugPrintHandler(eventHandler)); | |
| new AspectPropagationEdgesThreadContext().storeInThread(thread); | |
| Object starlarkResult = Starlark.positionalOnlyCall(thread, function, context); | |
| if (starlarkResult instanceof StarlarkList<?> listResult) { | |
| return listResult; | |
| } | |
| throw new EvalException("Expected a list"); | |
| } | |
| } | |
| } | |
| /** A function supplier for {@code attr_aspects}. */ | |
| public static final class AttrAspectsFunctionSupplier extends FunctionSupplier<String> { | |
| private AttrAspectsFunctionSupplier(StarlarkFunction function, StarlarkSemantics semantics) { | |
| super(function, semantics); | |
| } | |
| @Override | |
| public ImmutableSet<String> computeList( | |
| StarlarkAspectPropagationContextApi context, ExtendedEventHandler eventHandler) | |
| throws InterruptedException, EvalException { | |
| return parseAttrAspects(runFunction(context, eventHandler), /* allowAll= */ false); | |
| } | |
| } | |
| /** A function supplier for {@code toolchains_aspects}. */ | |
| public static final class ToolchainsAspectsFunctionSupplier extends FunctionSupplier<Label> { | |
| private ToolchainsAspectsFunctionSupplier( | |
| StarlarkFunction function, StarlarkSemantics semantics) { | |
| super(function, semantics); | |
| } | |
| @Override | |
| public ImmutableSet<Label> computeList( | |
| StarlarkAspectPropagationContextApi context, ExtendedEventHandler eventHandler) | |
| throws InterruptedException, EvalException { | |
| StarlarkList<?> listResult = runFunction(context, eventHandler); | |
| if (listResult == null || listResult.isEmpty()) { | |
| return ImmutableSet.of(); | |
| } | |
| return ImmutableSet.copyOf(Sequence.cast(listResult, Label.class, "toolchains_aspects")); | |
| } | |
| } | |
| public static AspectPropagationEdgesSupplier<String> createForAttrAspects( | |
| Object rawAttrAspects, StarlarkThread thread) throws EvalException { | |
| if (rawAttrAspects instanceof StarlarkFunction attrAspectsFunction) { | |
| return new AttrAspectsFunctionSupplier(attrAspectsFunction, thread.getSemantics()); | |
| } else { | |
| return new FixedListSupplier<>(parseAttrAspects(rawAttrAspects, /* allowAll= */ true)); | |
| } | |
| } | |
| public static AspectPropagationEdgesSupplier<Label> createForToolchainsAspects( | |
| Object rawToolchainsAspects, StarlarkThread thread, LabelConverter labelConverter) | |
| throws EvalException { | |
| if (rawToolchainsAspects instanceof StarlarkFunction toolchainsAspectsFunction) { | |
| return new ToolchainsAspectsFunctionSupplier( | |
| toolchainsAspectsFunction, thread.getSemantics()); | |
| } else { | |
| return new FixedListSupplier<>(parseToolchainsAspects(rawToolchainsAspects, labelConverter)); | |
| } | |
| } | |
| private static ImmutableSet<String> parseAttrAspects(Object rawAttrAspects, boolean allowAll) | |
| throws EvalException { | |
| Sequence<String> attrAspects = Sequence.cast(rawAttrAspects, String.class, "attr_aspects"); | |
| ImmutableSet.Builder<String> attrAspectsBuilder = ImmutableSet.builder(); | |
| for (String attrName : attrAspects) { | |
| if (attrName.equals("*")) { | |
| if (!allowAll) { | |
| throw new EvalException("'*' is not allowed in 'attr_aspects' list"); | |
| } else if (attrAspects.size() != 1) { | |
| throw new EvalException("'*' must be the only string in 'attr_aspects' list"); | |
| } | |
| } | |
| if (!attrName.startsWith("_")) { | |
| attrAspectsBuilder.add(attrName); | |
| } else { | |
| // Implicit attribute names mean either implicit or late-bound attributes | |
| // (``$attr`` or ``:attr``). Depend on both. | |
| attrAspectsBuilder | |
| .add(AttributeValueSource.COMPUTED_DEFAULT.convertToNativeName(attrName)) | |
| .add(AttributeValueSource.LATE_BOUND.convertToNativeName(attrName)); | |
| } | |
| } | |
| return attrAspectsBuilder.build(); | |
| } | |
| private static ImmutableSet<Label> parseToolchainsAspects( | |
| Object rawToolchainsAspects, LabelConverter labelConverter) throws EvalException { | |
| Sequence<String> toolchainsAspects = | |
| Sequence.cast(rawToolchainsAspects, String.class, "toolchains_aspects"); | |
| if (toolchainsAspects.size() == 1 && Objects.equals(toolchainsAspects.get(0), "*")) { | |
| return ImmutableSet.of(ALL_TOOLCHAINS); | |
| } | |
| ImmutableSet.Builder<Label> parsedLabels = new ImmutableSet.Builder<>(); | |
| for (String input : toolchainsAspects) { | |
| if (Objects.equals(input, "*")) { | |
| // This is already handled if the list has a single '*' item in it | |
| throw new EvalException("'*' must be the only item in 'toolchains_aspects' list"); | |
| } | |
| try { | |
| Label label = labelConverter.convert(input); | |
| parsedLabels.add(label); | |
| } catch (LabelSyntaxException e) { | |
| throw Starlark.errorf( | |
| "Unable to parse label '%s' in attribute '%s': %s", | |
| input, "toolchains_aspects", e.getMessage()); | |
| } | |
| } | |
| return parsedLabels.build(); | |
| } | |
| } |