Skip to content

Commit 98d376f

Browse files
juliexxiacopybara-github
authored andcommitted
bazel starlark transitions: grab build setting label from Starlark call stack in order to canonicalize.
There are 5 locations a user might write the label string of a build setting: (1) on the command line (2) in the transition definition inputs (3) in the transition definition output (4) in the transition impl function while reading the settings dict (5) in the transition impl function return dict We know that, depending on the situation, there are multiple ways to write "a build setting in the main repo of this project" - //myflag, @//myflag, @main_repo//my:flag. In all 5 places listed above, bazel needs to recognize that the three different versions of //myflag above all map back to a single target. We need this for deduping purposes and consistency. 1 is taken care of in a grandparent CL to this CL. This Cl addresses locations 2-5. To do this, whenever we are interpreting a user's input at one of the locations above, we covert that input string to the canonical string form of that build setting for our back end book keeping purposes. The logic to do this was modeled off of the `Label()` function logic. We also keep a map of canonicalized form to the user-given form in order to display messages (or key the dict in 4) to the user using the forms of the flags they used themselves. work towards #11128 PiperOrigin-RevId: 351426279
1 parent 6d8f067 commit 98d376f

File tree

7 files changed

+471
-96
lines changed

7 files changed

+471
-96
lines changed

src/main/java/com/google/devtools/build/lib/analysis/BUILD

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,7 @@ java_library(
17621762
srcs = ["config/StarlarkDefinedConfigTransition.java"],
17631763
deps = [
17641764
":config/transitions/configuration_transition",
1765+
"//src/main/java/com/google/devtools/build/lib/cmdline",
17651766
"//src/main/java/com/google/devtools/build/lib/events",
17661767
"//src/main/java/com/google/devtools/build/lib/packages",
17671768
"//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config",
@@ -2096,8 +2097,6 @@ java_library(
20962097
"//src/main/java/com/google/devtools/build/lib/util",
20972098
"//src/main/java/com/google/devtools/common/options",
20982099
"//src/main/java/net/starlark/java/eval",
2099-
"//src/main/java/net/starlark/java/syntax",
2100-
"//third_party:error_prone_annotations",
21012100
"//third_party:guava",
21022101
"//third_party:jsr305",
21032102
],

src/main/java/com/google/devtools/build/lib/analysis/config/StarlarkDefinedConfigTransition.java

Lines changed: 267 additions & 43 deletions
Large diffs are not rendered by default.

src/main/java/com/google/devtools/build/lib/analysis/starlark/FunctionTransitionUtil.java

Lines changed: 11 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.devtools.build.lib.analysis.config.CoreOptions;
2727
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
2828
import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
29+
import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition.ValidationException;
2930
import com.google.devtools.build.lib.cmdline.Label;
3031
import com.google.devtools.build.lib.events.Event;
3132
import com.google.devtools.build.lib.events.EventHandler;
@@ -34,9 +35,7 @@
3435
import com.google.devtools.common.options.OptionDefinition;
3536
import com.google.devtools.common.options.OptionsParser;
3637
import com.google.devtools.common.options.OptionsParsingException;
37-
import com.google.errorprone.annotations.FormatMethod;
3838
import java.lang.reflect.Field;
39-
import java.util.Collection;
4039
import java.util.HashSet;
4140
import java.util.LinkedHashSet;
4241
import java.util.List;
@@ -96,7 +95,6 @@ static Map<String, BuildOptions> applyAndValidate(
9695
if (transitions == null) {
9796
return null; // errors reported to handler
9897
}
99-
validateFunctionOutputsMatchesDeclaredOutputs(transitions.values(), starlarkTransition);
10098

10199
for (Map.Entry<String, Map<String, Object>> entry : transitions.entrySet()) {
102100
Map<String, Object> newValues = handleImplicitPlatformChange(entry.getValue());
@@ -113,17 +111,6 @@ static Map<String, BuildOptions> applyAndValidate(
113111
}
114112
}
115113

116-
private static final class ValidationException extends Exception {
117-
ValidationException(String message) {
118-
super(message);
119-
}
120-
121-
@FormatMethod
122-
static ValidationException format(String format, Object... args) {
123-
return new ValidationException(String.format(format, args));
124-
}
125-
}
126-
127114
/**
128115
* If the transition changes --cpu but not --platforms, clear out --platforms.
129116
*
@@ -166,33 +153,6 @@ private static void checkForDenylistedOptions(StarlarkDefinedConfigTransition tr
166153
}
167154
}
168155

169-
/**
170-
* Validates that function outputs exactly the set of outputs it declares. More thorough checking
171-
* (like type checking of output values) is done elsewhere because it requires loading. see {@link
172-
* StarlarkTransition#validate}
173-
*/
174-
private static void validateFunctionOutputsMatchesDeclaredOutputs(
175-
Collection<Map<String, Object>> transitions,
176-
StarlarkDefinedConfigTransition starlarkTransition)
177-
throws ValidationException {
178-
for (Map<String, Object> transition : transitions) {
179-
LinkedHashSet<String> remainingOutputs =
180-
Sets.newLinkedHashSet(starlarkTransition.getOutputs());
181-
for (String outputKey : transition.keySet()) {
182-
if (!remainingOutputs.remove(outputKey)) {
183-
throw ValidationException.format(
184-
"transition function returned undeclared output '%s'", outputKey);
185-
}
186-
}
187-
188-
if (!remainingOutputs.isEmpty()) {
189-
throw ValidationException.format(
190-
"transition outputs [%s] were not defined by transition function",
191-
Joiner.on(", ").join(remainingOutputs));
192-
}
193-
}
194-
}
195-
196156
/** For all the options in the BuildOptions, build a map from option name to its information. */
197157
static ImmutableMap<String, OptionInfo> buildOptionInfo(BuildOptions buildOptions) {
198158
ImmutableMap.Builder<String, OptionInfo> builder = new ImmutableMap.Builder<>();
@@ -230,7 +190,10 @@ static Dict<String, Object> buildSettings(
230190
Map<String, OptionInfo> optionInfoMap,
231191
StarlarkDefinedConfigTransition starlarkTransition)
232192
throws ValidationException {
233-
LinkedHashSet<String> remainingInputs = Sets.newLinkedHashSet(starlarkTransition.getInputs());
193+
Map<String, String> inputsCanonicalizedToGiven =
194+
starlarkTransition.getInputsCanonicalizedToGiven();
195+
LinkedHashSet<String> remainingInputs =
196+
Sets.newLinkedHashSet(inputsCanonicalizedToGiven.keySet());
234197

235198
try (Mutability mutability = Mutability.create("build_settings")) {
236199
Dict<String, Object> dict = Dict.of(mutability);
@@ -261,11 +224,15 @@ static Dict<String, Object> buildSettings(
261224

262225
// Add Starlark options
263226
for (Map.Entry<Label, Object> starlarkOption : buildOptions.getStarlarkOptions().entrySet()) {
264-
if (!remainingInputs.remove(starlarkOption.getKey().toString())) {
227+
String canonicalLabelForm = starlarkOption.getKey().getUnambiguousCanonicalForm();
228+
if (!remainingInputs.remove(canonicalLabelForm)) {
265229
continue;
266230
}
231+
// Convert the canonical form to the user requested form that they expect to see in this
232+
// dict.
233+
String userRequestedLabelForm = inputsCanonicalizedToGiven.get(canonicalLabelForm);
267234
try {
268-
dict.putEntry(starlarkOption.getKey().toString(), starlarkOption.getValue());
235+
dict.putEntry(userRequestedLabelForm, starlarkOption.getValue());
269236
} catch (EvalException ex) {
270237
throw new IllegalStateException(ex); // can't happen
271238
}

src/main/java/com/google/devtools/build/lib/packages/BazelStarlarkContext.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ public void storeInThread(StarlarkThread thread) {
4848
}
4949

5050
private final Phase phase;
51-
private final String toolsRepository;
51+
// Only necessary for loading phase threads.
52+
@Nullable private final String toolsRepository;
53+
// Only necessary for loading phase threads to construct configuration_field.
5254
@Nullable private final ImmutableMap<String, Class<?>> fragmentNameToClass;
5355
private final ImmutableMap<RepositoryName, RepositoryName> repoMapping;
5456
private final HashMap<String, Label> convertedLabelsInPackage;
@@ -57,9 +59,11 @@ public void storeInThread(StarlarkThread thread) {
5759

5860
/**
5961
* @param phase the phase to which this Starlark thread belongs
60-
* @param toolsRepository the name of the tools repository, such as "@bazel_tools"
62+
* @param toolsRepository the name of the tools repository, such as "@bazel_tools" for loading
63+
* phase threads, null for other threads.
6164
* @param fragmentNameToClass a map from configuration fragment name to configuration fragment
62-
* class, such as "apple" to AppleConfiguration.class
65+
* class, such as "apple" to AppleConfiguration.class for loading phase threads, null for
66+
* other threads.
6367
* @param repoMapping a map from RepositoryName to RepositoryName to be used for external
6468
* @param convertedLabelsInPackage a mutable map from String to Label, used during package loading
6569
* of a single package.
@@ -78,7 +82,7 @@ public void storeInThread(StarlarkThread thread) {
7882
// analysis threads?
7983
public BazelStarlarkContext(
8084
Phase phase,
81-
String toolsRepository,
85+
@Nullable String toolsRepository,
8286
@Nullable ImmutableMap<String, Class<?>> fragmentNameToClass,
8387
ImmutableMap<RepositoryName, RepositoryName> repoMapping,
8488
HashMap<String, Label> convertedLabelsInPackage,
@@ -99,6 +103,7 @@ public Phase getPhase() {
99103
}
100104

101105
/** Returns the name of the tools repository, such as "@bazel_tools". */
106+
@Nullable
102107
@Override
103108
public String getToolsRepository() {
104109
return toolsRepository;

src/main/java/com/google/devtools/build/lib/rules/config/ConfigGlobalLibrary.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616

1717
import static com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition.COMMAND_LINE_OPTION_PREFIX;
1818

19+
import com.google.common.collect.ImmutableMap;
1920
import com.google.common.collect.Sets;
2021
import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
2122
import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition.Settings;
2223
import com.google.devtools.build.lib.cmdline.Label;
24+
import com.google.devtools.build.lib.cmdline.RepositoryName;
25+
import com.google.devtools.build.lib.packages.BazelModuleContext;
26+
import com.google.devtools.build.lib.packages.BazelStarlarkContext;
2327
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
2428
import com.google.devtools.build.lib.starlarkbuildapi.config.ConfigGlobalLibraryApi;
2529
import com.google.devtools.build.lib.starlarkbuildapi.config.ConfigurationTransitionApi;
@@ -28,11 +32,13 @@
2832
import java.util.Map;
2933
import net.starlark.java.eval.Dict;
3034
import net.starlark.java.eval.EvalException;
35+
import net.starlark.java.eval.Module;
3136
import net.starlark.java.eval.Sequence;
3237
import net.starlark.java.eval.Starlark;
3338
import net.starlark.java.eval.StarlarkCallable;
3439
import net.starlark.java.eval.StarlarkSemantics;
3540
import net.starlark.java.eval.StarlarkThread;
41+
import net.starlark.java.syntax.Location;
3642

3743
/**
3844
* Implementation of {@link ConfigGlobalLibraryApi}.
@@ -59,8 +65,12 @@ public ConfigurationTransitionApi transition(
5965
outputsList,
6066
Settings.OUTPUTS,
6167
semantics.getBool(BuildLanguageOptions.EXPERIMENTAL_STARLARK_CONFIG_TRANSITIONS));
68+
Label parentLabel =
69+
BazelModuleContext.of(Module.ofInnermostEnclosingStarlarkFunction(thread)).label();
70+
Location location = thread.getCallerLocation();
71+
BazelStarlarkContext context = BazelStarlarkContext.from(thread);
6272
return StarlarkDefinedConfigTransition.newRegularTransition(
63-
implementation, inputsList, outputsList, semantics, thread);
73+
implementation, inputsList, outputsList, semantics, parentLabel, location, context);
6474
}
6575

6676
@Override
@@ -71,8 +81,13 @@ public ConfigurationTransitionApi analysisTestTransition(
7181
Map<String, Object> changedSettingsMap =
7282
Dict.cast(changedSettings, String.class, Object.class, "changed_settings dict");
7383
validateBuildSettingKeys(changedSettingsMap.keySet(), Settings.OUTPUTS, true);
84+
ImmutableMap<RepositoryName, RepositoryName> repoMapping =
85+
BazelStarlarkContext.from(thread).getRepoMapping();
86+
Label parentLabel =
87+
BazelModuleContext.of(Module.ofInnermostEnclosingStarlarkFunction(thread)).label();
88+
Location location = thread.getCallerLocation();
7489
return StarlarkDefinedConfigTransition.newAnalysisTestTransition(
75-
changedSettingsMap, thread.getCallerLocation());
90+
changedSettingsMap, repoMapping, parentLabel, location);
7691
}
7792

7893
private void validateBuildSettingKeys(

src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ public void testInvalidNativeOptionInput() throws Exception {
765765
reporter.removeHandler(failFastHandler);
766766
getConfiguredTarget("//test/starlark:test");
767767
assertContainsEvent(
768-
"transition inputs [//command_line_option:foop, //command_line_option:barp] "
768+
"transition inputs [//command_line_option:barp, //command_line_option:foop] "
769769
+ "do not correspond to valid settings");
770770
}
771771

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright 2020 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.analysis;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
19+
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
20+
import com.google.devtools.build.lib.cmdline.Label;
21+
import com.google.devtools.build.lib.testutil.Scratch;
22+
import java.util.Map;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
import org.junit.runners.JUnit4;
26+
27+
/**
28+
* Test of common logic between Starlark-defined transitions. Rule-transition- or
29+
* attr-transition-specific logic should be tested in {@link StarlarkRuleTransitionProviderTest} and
30+
* {@link StarlarkAttrTransitionProviderTest}.
31+
*/
32+
@RunWith(JUnit4.class)
33+
public class StarlarkTransitionTest extends BuildViewTestCase {
34+
static void writeAllowlistFile(Scratch scratch) throws Exception {
35+
scratch.file(
36+
"tools/allowlists/function_transition_allowlist/BUILD",
37+
"package_group(",
38+
" name = 'function_transition_allowlist',",
39+
" packages = [",
40+
" '//test/...',",
41+
" ],",
42+
")");
43+
}
44+
45+
@Test
46+
public void testDupeSettingsInInputsThrowsError() throws Exception {
47+
scratch.file(
48+
"test/defs.bzl",
49+
"def _setting_impl(ctx):",
50+
" return []",
51+
"string_flag = rule(",
52+
" implementation = _setting_impl,",
53+
" build_setting = config.string(flag=True),",
54+
")",
55+
"def _transition_impl(settings, attr):",
56+
" return {'//test:formation': 'mesa'}",
57+
"formation_transition = transition(",
58+
" implementation = _transition_impl,",
59+
" inputs = ['@//test:formation', '//test:formation'],", // duplicates here
60+
" outputs = ['//test:formation'],",
61+
")",
62+
"def _impl(ctx):",
63+
" return []",
64+
"state = rule(",
65+
" implementation = _impl,",
66+
" cfg = formation_transition,",
67+
" attrs = {",
68+
" '_allowlist_function_transition': attr.label(",
69+
" default = '//tools/allowlists/function_transition_allowlist',",
70+
" ),",
71+
" })");
72+
scratch.file(
73+
"test/BUILD",
74+
"load('//test:defs.bzl', 'state', 'string_flag')",
75+
"state(name = 'arizona')",
76+
"string_flag(name = 'formation', build_setting_default = 'canyon')");
77+
78+
reporter.removeHandler(failFastHandler);
79+
getConfiguredTarget("//test:arizona");
80+
assertContainsEvent(
81+
"Transition declares duplicate build setting '@//test:formation' in INPUTS");
82+
}
83+
84+
@Test
85+
public void testDupeSettingsInOutputsThrowsError() throws Exception {
86+
scratch.file(
87+
"test/defs.bzl",
88+
"def _setting_impl(ctx):",
89+
" return []",
90+
"string_flag = rule(",
91+
" implementation = _setting_impl,",
92+
" build_setting = config.string(flag=True),",
93+
")",
94+
"def _transition_impl(settings, attr):",
95+
" return {'//test:formation': 'mesa'}",
96+
"formation_transition = transition(",
97+
" implementation = _transition_impl,",
98+
" inputs = ['//test:formation'],",
99+
" outputs = ['@//test:formation', '//test:formation'],", // duplicates here
100+
")",
101+
"def _impl(ctx):",
102+
" return []",
103+
"state = rule(",
104+
" implementation = _impl,",
105+
" cfg = formation_transition,",
106+
" attrs = {",
107+
" '_allowlist_function_transition': attr.label(",
108+
" default = '//tools/allowlists/function_transition_allowlist',",
109+
" ),",
110+
" })");
111+
scratch.file(
112+
"test/BUILD",
113+
"load('//test:defs.bzl', 'state', 'string_flag')",
114+
"state(name = 'arizona')",
115+
"string_flag(name = 'formation', build_setting_default = 'canyon')");
116+
117+
reporter.removeHandler(failFastHandler);
118+
getConfiguredTarget("//test:arizona");
119+
assertContainsEvent(
120+
"Transition declares duplicate build setting '@//test:formation' in OUTPUTS");
121+
}
122+
123+
@Test
124+
public void testDifferentFormsOfFlagInInputsAndOutputs() throws Exception {
125+
writeAllowlistFile(scratch);
126+
scratch.file(
127+
"test/defs.bzl",
128+
"def _setting_impl(ctx):",
129+
" return []",
130+
"string_flag = rule(",
131+
" implementation = _setting_impl,",
132+
" build_setting = config.string(flag=True),",
133+
")",
134+
"def _transition_impl(settings, attr):",
135+
" return {",
136+
" '//test:formation': settings['@//test:formation']+'-transitioned',",
137+
" }",
138+
"formation_transition = transition(",
139+
" implementation = _transition_impl,",
140+
" inputs = ['@//test:formation'],",
141+
" outputs = ['//test:formation'],",
142+
")",
143+
"def _impl(ctx):",
144+
" return []",
145+
"state = rule(",
146+
" implementation = _impl,",
147+
" cfg = formation_transition,",
148+
" attrs = {",
149+
" '_allowlist_function_transition': attr.label(",
150+
" default = '//tools/allowlists/function_transition_allowlist',",
151+
" ),",
152+
" })");
153+
scratch.file(
154+
"test/BUILD",
155+
"load('//test:defs.bzl', 'state', 'string_flag')",
156+
"state(name = 'arizona')",
157+
"string_flag(name = 'formation', build_setting_default = 'canyon')");
158+
159+
Map<Label, Object> starlarkOptions =
160+
getConfiguration(getConfiguredTarget("//test:arizona")).getOptions().getStarlarkOptions();
161+
assertThat(starlarkOptions).hasSize(1);
162+
assertThat(starlarkOptions.get(Label.parseAbsoluteUnchecked("//test:formation")))
163+
.isEqualTo("canyon-transitioned");
164+
}
165+
}

0 commit comments

Comments
 (0)