Allow starlark rules to be build settings.
For skylark rules that are declared build settings, we auto-add a nonconfigurable build_setting_default attribute which must be set on a per target basis. We also add access to the build setting value via the rule context object. All of this functionality is hidden behind the --experimental_build_setting_api flag which by default is flipped off.
This includes:
- Add a build_setting parameter to starlark rule construction.
- If a starlark rule sets the build_setting parameter, auto-add a mandatory 'build_setting_default' attribute of the same type.
- Allow the value of a build setting skylark rules to be accessed via ctx.build_setting_value.
- Get rid of BuildSettingDescriptor since we don't need both it and BuildSetting.
- Add the --experimental_build_setting_api flag to SkylarkSemantics flags.
Work towards issue #5577
PiperOrigin-RevId: 219537101
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkConfig.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkConfig.java
index bc4d6e4..061e0b5 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkConfig.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkConfig.java
@@ -21,69 +21,48 @@
import static com.google.devtools.build.lib.syntax.Type.STRING;
import static com.google.devtools.build.lib.syntax.Type.STRING_LIST;
+import com.google.devtools.build.lib.packages.BuildSetting;
import com.google.devtools.build.lib.skylarkbuildapi.SkylarkConfigApi;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
-import com.google.devtools.build.lib.syntax.Type;
/**
- * Skylark namespace for creating build setting descriptors.
+ * Skylark namespace for creating build settings.
+ * TODO(juliexxia): Consider adding more types of build settings, specifically other label types.
*/
public class SkylarkConfig implements SkylarkConfigApi {
@Override
- public BuildSettingDescriptor intSetting(Boolean flag) {
- return new BuildSettingDescriptor(flag, INTEGER);
+ public BuildSetting intSetting(Boolean flag) {
+ return new BuildSetting(flag, INTEGER);
}
@Override
- public BuildSettingDescriptor boolSetting(Boolean flag) {
- return new BuildSettingDescriptor(flag, BOOLEAN);
+ public BuildSetting boolSetting(Boolean flag) {
+ return new BuildSetting(flag, BOOLEAN);
}
@Override
- public BuildSettingDescriptor stringSetting(Boolean flag) {
- return new BuildSettingDescriptor(flag, STRING);
+ public BuildSetting stringSetting(Boolean flag) {
+ return new BuildSetting(flag, STRING);
}
@Override
- public BuildSettingDescriptor stringListSetting(Boolean flag) {
- return new BuildSettingDescriptor(flag, STRING_LIST);
+ public BuildSetting stringListSetting(Boolean flag) {
+ return new BuildSetting(flag, STRING_LIST);
}
@Override
- public BuildSettingDescriptor labelSetting(Boolean flag) {
- return new BuildSettingDescriptor(flag, LABEL);
+ public BuildSetting labelSetting(Boolean flag) {
+ return new BuildSetting(flag, LABEL);
}
@Override
- public BuildSettingDescriptor labelListSetting(Boolean flag) {
- return new BuildSettingDescriptor(flag, LABEL_LIST);
+ public BuildSetting labelListSetting(Boolean flag) {
+ return new BuildSetting(flag, LABEL_LIST);
}
@Override
public void repr(SkylarkPrinter printer) {
printer.append("<config>");
}
-
- /**
- * An object that describes what time of build setting a skylark rule is (if any type).
- */
- private static final class BuildSettingDescriptor implements SkylarkConfigApi.BuildSettingApi {
- private final boolean isFlag;
- private final Type<?> type;
-
- BuildSettingDescriptor(boolean isFlag, Type<?> type) {
- this.isFlag = isFlag;
- this.type = type;
- }
-
- public Type<?> getType() {
- return type;
- }
-
- @Override
- public void repr(SkylarkPrinter printer) {
- printer.append("<build_setting." + type.toString() + ">");
- }
- }
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
index 264ae49..814cf5a 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
@@ -46,6 +46,7 @@
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.AttributeValueSource;
+import com.google.devtools.build.lib.packages.BuildSetting;
import com.google.devtools.build.lib.packages.FunctionSplitTransitionWhitelist;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SkylarkImplicitOutputsFunctionWithCallback;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SkylarkImplicitOutputsFunctionWithMap;
@@ -272,6 +273,7 @@
Boolean executionPlatformConstraintsAllowed,
SkylarkList<?> execCompatibleWith,
Object analysisTest,
+ Object buildSetting,
FuncallExpression ast,
Environment funcallEnv)
throws EvalException, ConversionException {
@@ -352,6 +354,17 @@
builder.addRequiredToolchains(
collectToolchainLabels(
toolchains.getContents(String.class, "toolchains"), ast.getLocation()));
+ if (!buildSetting.equals(Runtime.NONE)) {
+ if (funcallEnv.getSemantics().experimentalBuildSettingApi()) {
+ builder.setBuildSetting((BuildSetting) buildSetting);
+ } else {
+ throw new EvalException(
+ ast.getLocation(),
+ "build_setting parameter is experimental and not available for "
+ + "general use. It is subject to change at any time. It may be enabled by "
+ + "specifying --experimental_build_setting_api");
+ }
+ }
for (Object o : providesArg) {
if (!SkylarkAttr.isProvider(o)) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
index b311dfb..136ab55 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
@@ -14,6 +14,8 @@
package com.google.devtools.build.lib.analysis.skylark;
+import static com.google.devtools.build.lib.packages.RuleClass.Builder.SKYLARK_BUILD_SETTING_DEFAULT_ATTR_NAME;
+
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
@@ -575,6 +577,30 @@
}
@Override
+ @Nullable
+ public Object getBuildSettingValue() throws EvalException {
+ if (ruleContext.getRule().getRuleClassObject().getBuildSetting() == null) {
+ throw new EvalException(
+ Location.BUILTIN,
+ String.format(
+ "attempting to access 'build_setting_value' of non-build setting %s",
+ ruleLabelCanonicalName));
+ }
+ ImmutableMap<String, Object> skylarkFlagSettings =
+ ruleContext.getConfiguration().getOptions().getSkylarkOptions();
+
+ Type<?> buildSettingType =
+ ruleContext.getRule().getRuleClassObject().getBuildSetting().getType();
+ if (skylarkFlagSettings.containsKey(ruleLabelCanonicalName)) {
+ return skylarkFlagSettings.get(ruleLabelCanonicalName);
+ } else {
+ return ruleContext
+ .attributes()
+ .get(SKYLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, buildSettingType);
+ }
+ }
+
+ @Override
public boolean instrumentCoverage(Object targetUnchecked) throws EvalException {
checkMutable("coverage_instrumented");
BuildConfiguration config = ruleContext.getConfiguration();
diff --git a/src/main/java/com/google/devtools/build/lib/packages/BuildSetting.java b/src/main/java/com/google/devtools/build/lib/packages/BuildSetting.java
new file mode 100644
index 0000000..506aaab
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/BuildSetting.java
@@ -0,0 +1,47 @@
+// Copyright 2018 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.annotations.VisibleForTesting;
+import com.google.devtools.build.lib.skylarkbuildapi.SkylarkConfigApi.BuildSettingApi;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
+import com.google.devtools.build.lib.syntax.Type;
+
+/**
+ * Metadata of a build setting rule's properties. This describes the build setting's type (for
+ * example, 'int' or 'string'), and whether the build setting corresponds to a command line flag.
+ */
+public class BuildSetting implements BuildSettingApi {
+ private final boolean isFlag;
+ private final Type<?> type;
+
+ public BuildSetting(boolean isFlag, Type<?> type) {
+ this.isFlag = isFlag;
+ this.type = type;
+ }
+
+ public Type<?> getType() {
+ return type;
+ }
+
+ @VisibleForTesting
+ public boolean isFlag() {
+ return isFlag;
+ }
+
+ @Override
+ public void repr(SkylarkPrinter printer) {
+ printer.append("<build_setting." + type + ">");
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
index fba2b88..56ceb24 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleClass.java
@@ -57,6 +57,7 @@
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.syntax.Type.ConversionException;
+import com.google.devtools.build.lib.util.FileTypeSet;
import com.google.devtools.build.lib.util.StringUtil;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
@@ -111,6 +112,8 @@
* tree. All targets appearing in these attributes appears beneath the ".runfiles" tree; in
* addition, "deps" may have rule-specific semantics.
* </ul>
+ *
+ * TODO(bazel-team): Consider breaking up this class in more manageable subclasses.
*/
// Non-final only for mocking in tests. Do not subclass!
@Immutable
@@ -607,6 +610,12 @@
}
}
+ /**
+ * Name of default attribute implicitly added to all Skylark RuleClasses that are {@code
+ * build_setting}s.
+ */
+ public static final String SKYLARK_BUILD_SETTING_DEFAULT_ATTR_NAME = "build_setting_default";
+
/** List of required attributes for normal rules, name and type. */
public static final ImmutableList<Attribute> REQUIRED_ATTRIBUTES_FOR_NORMAL_RULES =
ImmutableList.of(attr("tags", Type.STRING_LIST).build());
@@ -641,6 +650,7 @@
private Predicate<String> preferredDependencyPredicate = Predicates.alwaysFalse();
private AdvertisedProviderSet.Builder advertisedProviders = AdvertisedProviderSet.builder();
private BaseFunction configuredTargetFunction = null;
+ private BuildSetting buildSetting = null;
private Function<? super Rule, Map<String, Label>> externalBindingsFunction =
NO_EXTERNAL_BINDINGS;
private Function<? super Rule, ? extends Set<String>> optionReferenceFunction =
@@ -770,6 +780,17 @@
if (skylark) {
assertRuleClassProperStarlarkDefinedTransitionUsage();
}
+ if (buildSetting != null) {
+ Type<?> type = buildSetting.getType();
+ Attribute.Builder<?> attrBuilder =
+ attr(SKYLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, type)
+ .nonconfigurable("Build setting defaults are referenced during analysis.")
+ .mandatory();
+ if (BuildType.isLabelType(type)) {
+ attrBuilder.allowedFileTypes(FileTypeSet.ANY_FILE);
+ }
+ this.add(attrBuilder);
+ }
return new RuleClass(
name,
@@ -803,7 +824,8 @@
executionPlatformConstraintsAllowed,
executionPlatformConstraints,
outputFileKind,
- attributes.values());
+ attributes.values(),
+ buildSetting);
}
private void assertSkylarkRuleClassHasImplementationFunction() {
@@ -1142,6 +1164,11 @@
return this;
}
+ public Builder setBuildSetting(BuildSetting buildSetting) {
+ this.buildSetting = buildSetting;
+ return this;
+ }
+
public Builder setExternalBindingsFunction(Function<? super Rule, Map<String, Label>> func) {
this.externalBindingsFunction = func;
return this;
@@ -1418,6 +1445,12 @@
@Nullable private final BaseFunction configuredTargetFunction;
/**
+ * The BuildSetting associated with this rule. Null for all RuleClasses except Skylark-defined
+ * rules that pass {@code build_setting} to their {@code rule()} declaration.
+ */
+ @Nullable private final BuildSetting buildSetting;
+
+ /**
* Returns the extra bindings a workspace function adds to the WORKSPACE file.
*/
private final Function<? super Rule, Map<String, Label>> externalBindingsFunction;
@@ -1506,7 +1539,8 @@
ExecutionPlatformConstraintsAllowed executionPlatformConstraintsAllowed,
Set<Label> executionPlatformConstraints,
OutputFile.Kind outputFileKind,
- Collection<Attribute> attributes) {
+ Collection<Attribute> attributes,
+ @Nullable BuildSetting buildSetting) {
this.name = name;
this.key = key;
this.type = type;
@@ -1541,6 +1575,7 @@
this.supportsPlatforms = supportsPlatforms;
this.executionPlatformConstraintsAllowed = executionPlatformConstraintsAllowed;
this.executionPlatformConstraints = ImmutableSet.copyOf(executionPlatformConstraints);
+ this.buildSetting = buildSetting;
// Create the index and collect non-configurable attributes.
int index = 0;
@@ -2307,6 +2342,11 @@
return configuredTargetFunction;
}
+ @Nullable
+ public BuildSetting getBuildSetting() {
+ return buildSetting;
+ }
+
/**
* Returns a function that computes the external bindings a repository function contributes to
* the WORKSPACE file.
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java
index c57e823..55ae637 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkSemanticsOptions.java
@@ -72,6 +72,16 @@
public boolean experimentalAnalysisTestingImprovements;
@Option(
+ name = "experimental_build_setting_api",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = OptionEffectTag.BUILD_FILE_SEMANTICS,
+ help =
+ "If set to true, allows access to value of build setting rules via "
+ + "ctx.build_setting_value.")
+ public boolean experimentalBuildSettingApi;
+
+ @Option(
name = "experimental_cc_skylark_api_enabled_packages",
converter = CommaSeparatedOptionListConverter.class,
defaultValue = "",
@@ -520,6 +530,7 @@
return SkylarkSemantics.builder()
// <== Add new options here in alphabetic order ==>
.experimentalAnalysisTestingImprovements(experimentalAnalysisTestingImprovements)
+ .experimentalBuildSettingApi(experimentalBuildSettingApi)
.experimentalCcSkylarkApiEnabledPackages(experimentalCcSkylarkApiEnabledPackages)
.experimentalEnableAndroidMigrationApis(experimentalEnableAndroidMigrationApis)
.experimentalEnableRepoMapping(experimentalEnableRepoMapping)
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleContextApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleContextApi.java
index 979a447..20e04fa 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleContextApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleContextApi.java
@@ -34,6 +34,7 @@
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
+import com.google.devtools.build.lib.syntax.SkylarkSemantics.FlagIdentifier;
import java.util.Map;
import javax.annotation.Nullable;
@@ -229,6 +230,17 @@
public BuildConfigurationApi getHostConfiguration() throws EvalException;
@SkylarkCallable(
+ name = "build_setting_value",
+ structField = true,
+ enableOnlyWithFlag = FlagIdentifier.EXPERIMENTAL_BUILD_SETTING_API,
+ doc =
+ "<b>Experimental. This field is experimental and subject to change at any time. Do not "
+ + "depend on it.</b> <p>Returns the value of the build setting that is represented "
+ + "by the current target. It is an error to access this field for rules that do not "
+ + "set the <code>build_setting</code> attribute in their rule definition.")
+ public Object getBuildSettingValue() throws EvalException;
+
+ @SkylarkCallable(
name = "coverage_instrumented",
doc =
"Returns whether code coverage instrumentation should be generated when performing "
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
index 247f01d..32d73f1 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
@@ -16,6 +16,7 @@
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.skylarkbuildapi.SkylarkConfigApi.BuildSettingApi;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.ParamType;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
@@ -307,6 +308,20 @@
doc =
"<b>Experimental: This parameter is experimental and subject to change at any "
+ "time.</b><p> If true, then this rule is treated as an analysis test."),
+ @Param(
+ name = "build_setting",
+ type = BuildSettingApi.class,
+ noneable = true,
+ defaultValue = "None",
+ named = true,
+ positional = false,
+ // TODO(juliexxia): Link to in-build testing documentation when it is available.
+ doc =
+ "<b>Experimental: This parameter is experimental and subject to change at any "
+ + "time.</b><p> If set, describes what kind of build setting this rule is. "
+ + "See the <a href='config.html'><code>config</code></a> module. If this is "
+ + "set, a mandatory attribute named \"build_setting_default\" is automatically"
+ + "added to this rule, with a type corresponding to the value passed in here."),
},
useAst = true,
useEnvironment = true)
@@ -326,6 +341,7 @@
Boolean executionPlatformConstraintsAllowed,
SkylarkList<?> execCompatibleWith,
Object analysisTest,
+ Object buildSetting,
FuncallExpression ast,
Environment funcallEnv)
throws EvalException;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
index db78fb1..4580e2e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
@@ -43,6 +43,7 @@
SkylarkSemantics::experimentalAnalysisTestingImprovements),
EXPERIMENTAL_ENABLE_ANDROID_MIGRATION_APIS(
SkylarkSemantics::experimentalEnableAndroidMigrationApis),
+ EXPERIMENTAL_BUILD_SETTING_API(SkylarkSemantics::experimentalBuildSettingApi),
EXPERIMENTAL_PLATFORM_API(SkylarkSemantics::experimentalPlatformsApi),
INCOMPATIBLE_DISABLE_OBJC_PROVIDER_RESOURCES(
SkylarkSemantics::incompatibleDisableObjcProviderResources),
@@ -109,6 +110,8 @@
// <== Add new options here in alphabetic order ==>
public abstract boolean experimentalAnalysisTestingImprovements();
+ public abstract boolean experimentalBuildSettingApi();
+
public abstract List<String> experimentalCcSkylarkApiEnabledPackages();
public abstract boolean experimentalEnableAndroidMigrationApis();
@@ -193,6 +196,7 @@
builder()
// <== Add new options here in alphabetic order ==>
.experimentalAnalysisTestingImprovements(false)
+ .experimentalBuildSettingApi(false)
.experimentalCcSkylarkApiEnabledPackages(ImmutableList.of())
.experimentalEnableAndroidMigrationApis(false)
.experimentalEnableRepoMapping(false)
@@ -236,6 +240,8 @@
// <== Add new options here in alphabetic order ==>
public abstract Builder experimentalAnalysisTestingImprovements(boolean value);
+ public abstract Builder experimentalBuildSettingApi(boolean value);
+
public abstract Builder experimentalCcSkylarkApiEnabledPackages(List<String> value);
public abstract Builder experimentalEnableAndroidMigrationApis(boolean value);
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
index 29a7cb2..8b62d92 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
@@ -117,6 +117,7 @@
Boolean executionPlatformConstraintsAllowed,
SkylarkList<?> execCompatibleWith,
Object analysisTest,
+ Object buildSetting,
FuncallExpression ast,
Environment funcallEnv)
throws EvalException {
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParser.java b/src/main/java/com/google/devtools/common/options/OptionsParser.java
index 9810988..331f2df 100644
--- a/src/main/java/com/google/devtools/common/options/OptionsParser.java
+++ b/src/main/java/com/google/devtools/common/options/OptionsParser.java
@@ -14,6 +14,7 @@
package com.google.devtools.common.options;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
@@ -197,7 +198,8 @@
return skylarkOptions;
}
- void setSkylarkOptions(Map<String, Object> skylarkOptions) {
+ @VisibleForTesting
+ public void setSkylarkOptionsForTesting(Map<String, Object> skylarkOptions) {
this.skylarkOptions = skylarkOptions;
}