| // Copyright 2022 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.rules.cpp; |
| |
| import com.google.common.collect.ImmutableCollection; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleContext; |
| import com.google.devtools.build.lib.collect.nestedset.Depset; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; |
| import java.util.HashMap; |
| import java.util.Objects; |
| import javax.annotation.Nullable; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.eval.Mutability; |
| import net.starlark.java.eval.Starlark; |
| import net.starlark.java.eval.StarlarkCallable; |
| import net.starlark.java.eval.StarlarkSemantics; |
| import net.starlark.java.eval.StarlarkThread; |
| import net.starlark.java.eval.StarlarkValue; |
| import net.starlark.java.eval.Structure; |
| import net.starlark.java.eval.Tuple; |
| |
| /** |
| * An implementation of ExtraLinkTimeLibrary that uses functions and data passed in from Starlark. |
| */ |
| public class StarlarkDefinedLinkTimeLibrary implements ExtraLinkTimeLibrary, Structure { |
| |
| StarlarkDefinedLinkTimeLibrary( |
| StarlarkCallable buildLibraryFunction, ImmutableMap<String, Object> objectMap) { |
| this.buildLibraryFunction = buildLibraryFunction; |
| this.objectMap = objectMap; |
| this.key = Key.createKey(buildLibraryFunction, objectMap); |
| } |
| |
| // Starlark function to create the output library. |
| private final StarlarkCallable buildLibraryFunction; |
| |
| // Map of parameter names to values. Depsets should be combined when merging libraries. |
| private final ImmutableMap<String, Object> objectMap; |
| |
| // Key object used to determine the "class" of the library implementation. |
| // The equals method is used to determine equality. |
| private final Key key; |
| |
| @Override |
| public Key getKey() { |
| return key; |
| } |
| |
| @Override |
| public ExtraLinkTimeLibrary.Builder getBuilder() { |
| return new Builder(); |
| } |
| |
| @Override |
| public BuildLibraryOutput buildLibraries( |
| RuleContext ruleContext, boolean staticMode, boolean forDynamicLibrary) |
| throws RuleErrorException, InterruptedException { |
| ruleContext.initStarlarkRuleContext(); |
| StarlarkRuleContext starlarkContext = ruleContext.getStarlarkRuleContext(); |
| StarlarkSemantics semantics = starlarkContext.getStarlarkSemantics(); |
| |
| Object response = null; |
| try (Mutability mu = Mutability.create("extra_link_time_library_build_libraries_function")) { |
| StarlarkThread thread = new StarlarkThread(mu, semantics); |
| response = |
| Starlark.call( |
| thread, |
| buildLibraryFunction, |
| ImmutableList.of(starlarkContext, staticMode, forDynamicLibrary), |
| objectMap); |
| } catch (EvalException e) { |
| throw new RuleErrorException(e); |
| } |
| String errorMsg = |
| buildLibraryFunction.getName() |
| + " in " |
| + buildLibraryFunction.getLocation() |
| + " should return (depset[CcLinkingContext], depset[File])"; |
| if (!(response instanceof Tuple)) { |
| throw new RuleErrorException(errorMsg); |
| } |
| Tuple responseTuple = (Tuple) response; |
| if (responseTuple.size() != 2) { |
| throw new RuleErrorException(errorMsg); |
| } |
| if (!(responseTuple.get(0) instanceof Depset) || !(responseTuple.get(1) instanceof Depset)) { |
| throw new RuleErrorException(errorMsg); |
| } |
| try { |
| return new BuildLibraryOutput( |
| ((Depset) responseTuple.get(0)).getSet(CcLinkingContext.LinkerInput.class), |
| ((Depset) responseTuple.get(1)).getSet(Artifact.class)); |
| } catch (Depset.TypeException e) { |
| throw new RuleErrorException(e); |
| } |
| } |
| |
| @Nullable |
| @Override |
| public StarlarkValue getValue(String key) throws EvalException { |
| return (StarlarkValue) objectMap.get(key); |
| } |
| |
| @Override |
| public ImmutableCollection<String> getFieldNames() { |
| return objectMap.keySet(); |
| } |
| |
| @Override |
| public void setField(String field, Object value) throws EvalException { |
| throw Starlark.errorf("ExtraLinkLibrary does not support field assignment"); |
| } |
| |
| @Override |
| public String getErrorMessageForUnknownField(String field) { |
| return String.format("No argument '%s' was passed to this ExtraLinkLibrary", field); |
| } |
| |
| /** |
| * Class to identify the "class" of a StarlarkDefinedLinkTimeLibrary. Uses the build function and |
| * the split between depset and non-depset parameters to determine equality. |
| */ |
| private static class Key { |
| |
| private final Object builderFunction; |
| private final ImmutableList<String> constantFields; |
| private final ImmutableList<String> depsetFields; |
| |
| private Key( |
| Object builderFunction, |
| ImmutableList<String> constantFields, |
| ImmutableList<String> depsetFields) { |
| this.builderFunction = builderFunction; |
| this.constantFields = constantFields; |
| this.depsetFields = depsetFields; |
| } |
| |
| public static Key createKey(Object builderFunction, ImmutableMap<String, Object> objectMap) { |
| ImmutableList.Builder<String> depsetFields = ImmutableList.builder(); |
| ImmutableList.Builder<String> constantFields = ImmutableList.builder(); |
| for (String key : objectMap.keySet()) { |
| if (objectMap.get(key) instanceof Depset) { |
| depsetFields.add(key); |
| } else { |
| constantFields.add(key); |
| } |
| } |
| return new Key(builderFunction, constantFields.build(), depsetFields.build()); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (!(other instanceof Key)) { |
| return false; |
| } |
| Key key = (Key) other; |
| return builderFunction.equals(key.builderFunction) |
| && constantFields.equals(key.constantFields) |
| && depsetFields.equals(key.depsetFields); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(builderFunction, constantFields, depsetFields); |
| } |
| } |
| |
| private static class Builder implements ExtraLinkTimeLibrary.Builder { |
| |
| private StarlarkCallable buildLibraryFunction; |
| |
| private final HashMap<String, ImmutableList.Builder<Depset>> depsetMapBuilder = new HashMap<>(); |
| |
| private final HashMap<String, Object> constantsMap = new HashMap<>(); |
| |
| private Builder() {} |
| |
| @Override |
| public ExtraLinkTimeLibrary build() { |
| ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>(); |
| for (String key : depsetMapBuilder.keySet()) { |
| try { |
| builder.put( |
| key, |
| Depset.fromDirectAndTransitive( |
| Order.LINK_ORDER, |
| ImmutableList.of(), |
| depsetMapBuilder.get(key).build(), |
| /* strict= */ true)); |
| } catch (EvalException e) { |
| // should never happen; exception comes from bad order argument. |
| throw new IllegalStateException(e); |
| } |
| } |
| builder.putAll(constantsMap); |
| return new StarlarkDefinedLinkTimeLibrary(buildLibraryFunction, builder.buildOrThrow()); |
| } |
| |
| @Override |
| public void addTransitive(ExtraLinkTimeLibrary dep) { |
| StarlarkDefinedLinkTimeLibrary library = (StarlarkDefinedLinkTimeLibrary) dep; |
| if (buildLibraryFunction == null) { |
| buildLibraryFunction = library.buildLibraryFunction; |
| } |
| for (String key : library.objectMap.keySet()) { |
| Object value = library.objectMap.get(key); |
| if (value instanceof Depset) { |
| depsetMapBuilder.computeIfAbsent(key, k -> ImmutableList.builder()).add((Depset) value); |
| } else { |
| constantsMap.put(key, value); |
| } |
| } |
| } |
| } |
| } |