diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java index 53c5fd4d46f8c7..0e33287263b759 100755 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java @@ -73,6 +73,7 @@ import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType; import com.google.devtools.build.lib.rules.cpp.Link.LinkingMode; import com.google.devtools.build.lib.starlarkbuildapi.cpp.CcModuleApi; +import com.google.devtools.build.lib.starlarkbuildapi.cpp.ExtraLinkTimeLibraryApi; import com.google.devtools.build.lib.util.FileTypeSet; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.util.StringUtil; @@ -89,6 +90,8 @@ import net.starlark.java.eval.NoneType; import net.starlark.java.eval.Sequence; import net.starlark.java.eval.Starlark; +import net.starlark.java.eval.StarlarkCallable; +import net.starlark.java.eval.StarlarkFunction; import net.starlark.java.eval.StarlarkInt; import net.starlark.java.eval.StarlarkList; import net.starlark.java.eval.StarlarkThread; @@ -2609,6 +2612,40 @@ public Sequence getBuildInfo(StarlarkRuleContext ruleContext, Starlark ruleContext.getRuleContext().getBuildInfo(CppBuildInfo.KEY)); } + @StarlarkMethod( + name = "create_extra_link_time_library", + documented = false, + doc = + "Creates a custom ExtraLinkTimeLibrary object. Extra keyword arguments are passed to the" + + " provided build function when build_libraries is called. Arguments that are" + + " depsets will be added transitively when these are combined via" + + " cc_common.merge_cc_infos. For arguments that are not depsets, only one copy will" + + " be maintained.", + parameters = { + @Param(name = "build_library_func", positional = false, named = true), + }, + extraKeywords = @Param(name = "data"), + useStarlarkThread = true) + public ExtraLinkTimeLibraryApi createExtraLinkTimeLibrary( + StarlarkCallable buildLibraryFunc, Dict dataSetsMap, StarlarkThread thread) + throws EvalException { + if (!isBuiltIn(thread)) { + throw Starlark.errorf( + "Cannot use experimental ExtraLinkTimeLibrary creation API outside of builtins"); + } + boolean nonGlobalFunc = false; + if (buildLibraryFunc instanceof StarlarkFunction) { + StarlarkFunction fn = (StarlarkFunction) buildLibraryFunc; + if (fn.getModule().getGlobal(fn.getName()) != fn) { + nonGlobalFunc = true; + } + } + if (nonGlobalFunc) { + throw Starlark.errorf("Passed function must be top-level functions."); + } + return new StarlarkDefinedLinkTimeLibrary(buildLibraryFunc, ImmutableMap.copyOf(dataSetsMap)); + } + private ImmutableList> convertSequenceTupleToPair(Sequence sequenceTuple) throws EvalException { return Sequence.cast(sequenceTuple, Tuple.class, "files").stream() diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtraLinkTimeLibraries.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtraLinkTimeLibraries.java index baf5f701ba2d2e..0e9cd752888d54 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtraLinkTimeLibraries.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtraLinkTimeLibraries.java @@ -76,8 +76,7 @@ public static final Builder builder() { * Builder for {@link ExtraLinkTimeLibraries}. */ public static final class Builder { - private Map, ExtraLinkTimeLibrary.Builder> libraries = - new LinkedHashMap<>(); + private final Map libraries = new LinkedHashMap<>(); private Builder() { // Nothing to do. @@ -98,19 +97,17 @@ public ExtraLinkTimeLibraries build() { @CanIgnoreReturnValue public final Builder addTransitive(ExtraLinkTimeLibraries dep) { for (ExtraLinkTimeLibrary depLibrary : dep.getExtraLibraries()) { - Class c = depLibrary.getClass(); - libraries.computeIfAbsent(c, k -> depLibrary.getBuilder()); - libraries.get(c).addTransitive(depLibrary); + add(depLibrary); } return this; } /** Add a single library to build. */ @CanIgnoreReturnValue - public final Builder add(ExtraLinkTimeLibrary b) { - Class c = b.getClass(); - libraries.computeIfAbsent(c, k -> b.getBuilder()); - libraries.get(c).addTransitive(b); + public final Builder add(ExtraLinkTimeLibrary depLibrary) { + Object key = depLibrary.getKey(); + libraries.computeIfAbsent(key, k -> depLibrary.getBuilder()); + libraries.get(key).addTransitive(depLibrary); return this; } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtraLinkTimeLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtraLinkTimeLibrary.java index a366ed6f317e8c..909c87ed918ff8 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtraLinkTimeLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/ExtraLinkTimeLibrary.java @@ -70,8 +70,14 @@ BuildLibraryOutput buildLibraries( Builder getBuilder(); /** - * The Builder interface builds an ExtraLinkTimeLibrary. + * Used to identify the "class" of this Library. The Java class is usually sufficient unless + * behaviour is controlled dynamically. */ + default Object getKey() { + return this.getClass(); + } + + /** The Builder interface builds an ExtraLinkTimeLibrary. */ public interface Builder { /** * Add the inputs associated with another instance of the same diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/StarlarkDefinedLinkTimeLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/StarlarkDefinedLinkTimeLibrary.java new file mode 100644 index 00000000000000..76536fdcc300ab --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/StarlarkDefinedLinkTimeLibrary.java @@ -0,0 +1,233 @@ +// 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.StarlarkList; +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 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 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 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 constantFields; + private final ImmutableList depsetFields; + + private Key( + Object builderFunction, + ImmutableList constantFields, + ImmutableList depsetFields) { + this.builderFunction = builderFunction; + this.constantFields = constantFields; + this.depsetFields = depsetFields; + } + + public static Key createKey(Object builderFunction, ImmutableMap objectMap) { + ImmutableList.Builder depsetFields = ImmutableList.builder(); + ImmutableList.Builder 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> depsetMapBuilder = new HashMap<>(); + + private final HashMap constantsMap = new HashMap<>(); + + private Builder() {} + + @Override + public ExtraLinkTimeLibrary build() { + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + for (String key : depsetMapBuilder.keySet()) { + try { + builder.put( + key, + Depset.depset( + Order.LINK_ORDER.getStarlarkName(), + StarlarkList.immutableOf(), + StarlarkList.immutableCopyOf(depsetMapBuilder.get(key).build()), + StarlarkSemantics.DEFAULT)); + } 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); + } + } + } + } +}