blob: fde86b813f749c10bceef8cc233582dac4f428a2 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Greg Estren531fc042015-05-26 22:37:44 +00002//
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.
14package com.google.devtools.build.lib.skyframe;
15
twigg52d1d4a2022-03-10 16:41:11 -080016import static com.google.common.collect.ImmutableSet.toImmutableSet;
17import static com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition.COMMAND_LINE_OPTION_PREFIX;
18
19import com.google.common.annotations.VisibleForTesting;
20import com.google.common.base.VerifyException;
21import com.google.common.collect.ImmutableList;
22import com.google.common.collect.ImmutableMap;
23import com.google.common.collect.ImmutableSet;
Greg Estren531fc042015-05-26 22:37:44 +000024import com.google.devtools.build.lib.analysis.BlazeDirectories;
Greg Estren00049432015-08-25 16:43:47 +000025import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
twigg52d1d4a2022-03-10 16:41:11 -080026import com.google.devtools.build.lib.analysis.PlatformOptions;
jhorvitz33f76482021-10-28 10:13:26 -070027import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
twigg50dc4462022-03-10 14:59:52 -080028import com.google.devtools.build.lib.analysis.config.BuildOptions;
29import com.google.devtools.build.lib.analysis.config.CoreOptions;
twiggaf155432021-11-29 13:08:24 -080030import com.google.devtools.build.lib.analysis.config.FragmentFactory;
Greg Estren531fc042015-05-26 22:37:44 +000031import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
twigg52d1d4a2022-03-10 16:41:11 -080032import com.google.devtools.build.lib.analysis.config.OptionInfo;
33import com.google.devtools.build.lib.cmdline.Label;
jhorvitz6380c2892021-05-03 10:13:52 -070034import com.google.devtools.build.lib.cmdline.RepositoryName;
Greg Estren00049432015-08-25 16:43:47 +000035import com.google.devtools.build.lib.packages.RuleClassProvider;
Googler4e7b1e52020-10-09 11:58:45 -070036import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
twigg52d1d4a2022-03-10 16:41:11 -080037import com.google.devtools.build.lib.util.Fingerprint;
Greg Estren531fc042015-05-26 22:37:44 +000038import com.google.devtools.build.skyframe.SkyFunction;
39import com.google.devtools.build.skyframe.SkyFunctionException;
40import com.google.devtools.build.skyframe.SkyKey;
41import com.google.devtools.build.skyframe.SkyValue;
twigg52d1d4a2022-03-10 16:41:11 -080042import com.google.devtools.common.options.OptionDefinition;
43import com.google.devtools.common.options.OptionMetadataTag;
44import com.google.devtools.common.options.OptionsParsingException;
45import java.util.Map;
46import java.util.TreeMap;
Googler4e7b1e52020-10-09 11:58:45 -070047import net.starlark.java.eval.StarlarkSemantics;
Greg Estren531fc042015-05-26 22:37:44 +000048
jhorvitz9ad5aed2021-04-23 14:32:33 -070049/** A builder for {@link BuildConfigurationValue} instances. */
50public final class BuildConfigurationFunction implements SkyFunction {
51
twigg52d1d4a2022-03-10 16:41:11 -080052 // The length of the hash of the config tacked onto the end of the output path.
53 // Limited for ergonomics and MAX_PATH reasons.
54 private static final int HASH_LENGTH = 12;
55
Greg Estren531fc042015-05-26 22:37:44 +000056 private final BlazeDirectories directories;
gregce490b0952017-07-06 18:44:38 -040057 private final ConfiguredRuleClassProvider ruleClassProvider;
twiggaf155432021-11-29 13:08:24 -080058 private final FragmentFactory fragmentFactory = new FragmentFactory();
Greg Estren531fc042015-05-26 22:37:44 +000059
mjhalupka5d7fa7b2018-03-22 13:37:38 -070060 public BuildConfigurationFunction(
jhorvitz9ad5aed2021-04-23 14:32:33 -070061 BlazeDirectories directories, RuleClassProvider ruleClassProvider) {
Greg Estren531fc042015-05-26 22:37:44 +000062 this.directories = directories;
gregce490b0952017-07-06 18:44:38 -040063 this.ruleClassProvider = (ConfiguredRuleClassProvider) ruleClassProvider;
Greg Estren531fc042015-05-26 22:37:44 +000064 }
65
66 @Override
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +000067 public SkyValue compute(SkyKey skyKey, Environment env)
68 throws InterruptedException, BuildConfigurationFunctionException {
kchodorow85ae1902017-04-22 15:07:22 -040069 WorkspaceNameValue workspaceNameValue = (WorkspaceNameValue) env
70 .getValue(WorkspaceNameValue.key());
71 if (workspaceNameValue == null) {
72 return null;
73 }
jhorvitz6380c2892021-05-03 10:13:52 -070074
Googler4e7b1e52020-10-09 11:58:45 -070075 StarlarkSemantics starlarkSemantics = PrecomputedValue.STARLARK_SEMANTICS.get(env);
76 if (starlarkSemantics == null) {
77 return null;
78 }
twigg44c30ac2021-12-08 15:57:11 -080079 BuildConfigurationKey key = (BuildConfigurationKey) skyKey.argument();
twigg50dc4462022-03-10 14:59:52 -080080
81 BuildOptions targetOptions = key.getOptions();
twigg52d1d4a2022-03-10 16:41:11 -080082
83 String transitionDirectoryNameFragment;
twigg50dc4462022-03-10 14:59:52 -080084 if (targetOptions
85 .get(CoreOptions.class)
86 .outputDirectoryNamingScheme
87 .equals(CoreOptions.OutputDirectoryNamingScheme.DIFF_AGAINST_BASELINE)) {
twigg52d1d4a2022-03-10 16:41:11 -080088 // Herein lies a hack to apply platform mappings to the baseline options.
89 // TODO(blaze-configurability-team): this should become unnecessary once --platforms is marked
90 // as EXPLICIT_IN_OUTPUT_PATH
91 PlatformMappingValue platformMappingValue =
92 (PlatformMappingValue)
93 env.getValue(
94 PlatformMappingValue.Key.create(
95 targetOptions.get(PlatformOptions.class).platformMappings));
96 if (platformMappingValue == null) {
97 return null;
98 }
99 BuildOptions baselineOptions = PrecomputedValue.BASELINE_CONFIGURATION.get(env);
100 try {
101 BuildOptions mappedBaselineOptions =
102 BuildConfigurationKey.withPlatformMapping(platformMappingValue, baselineOptions)
103 .getOptions();
104 transitionDirectoryNameFragment =
105 computeNameFragmentWithDiff(targetOptions, mappedBaselineOptions);
106 } catch (OptionsParsingException e) {
107 throw new BuildConfigurationFunctionException(e);
108 }
109 } else {
110 transitionDirectoryNameFragment =
111 computeNameFragmentWithAffectedByStarlarkTransition(targetOptions);
twigg50dc4462022-03-10 14:59:52 -0800112 }
113
janakrca6209f2020-11-13 19:17:27 -0800114 try {
twigg44c30ac2021-12-08 15:57:11 -0800115 return BuildConfigurationValue.create(
twigg50dc4462022-03-10 14:59:52 -0800116 targetOptions,
Googler80ada0f2021-12-22 07:15:59 -0800117 RepositoryName.createFromValidStrippedName(workspaceNameValue.getName()),
twigg44c30ac2021-12-08 15:57:11 -0800118 starlarkSemantics.getBool(BuildLanguageOptions.EXPERIMENTAL_SIBLING_REPOSITORY_LAYOUT),
twigg52d1d4a2022-03-10 16:41:11 -0800119 transitionDirectoryNameFragment,
twigg44c30ac2021-12-08 15:57:11 -0800120 // Arguments below this are server-global.
121 directories,
122 ruleClassProvider,
123 fragmentFactory);
124 } catch (InvalidConfigurationException e) {
janakrca6209f2020-11-13 19:17:27 -0800125 throw new BuildConfigurationFunctionException(e);
126 }
Greg Estren531fc042015-05-26 22:37:44 +0000127 }
128
Greg Estren531fc042015-05-26 22:37:44 +0000129 private static final class BuildConfigurationFunctionException extends SkyFunctionException {
twigg52d1d4a2022-03-10 16:41:11 -0800130 BuildConfigurationFunctionException(Exception e) {
Greg Estren531fc042015-05-26 22:37:44 +0000131 super(e, Transience.PERSISTENT);
132 }
133 }
twigg52d1d4a2022-03-10 16:41:11 -0800134
135 /**
136 * Compute the hash for the new BuildOptions based on the names and values of all options (both
137 * native and Starlark) that are different from some supplied baseline configuration.
138 */
139 private static String computeNameFragmentWithDiff(
140 BuildOptions toOptions, BuildOptions baselineOptions) {
141 // Quick short-circuit for trivial case.
142 if (toOptions.equals(baselineOptions)) {
143 return "";
144 }
145
146 // TODO(blaze-configurability-team): As a mild performance update, getFirst already includes
147 // details of the corresponding option. Could incorporate this instead of hashChosenOptions
148 // regenerating the OptionDefinitions and values.
149 BuildOptions.OptionsDiff diff = BuildOptions.diff(toOptions, baselineOptions);
150 // Note: getFirst only excludes options trimmed between baselineOptions to toOptions and this is
151 // considered OK as a given Rule should not be being built with options of different
152 // trimmings. See longform note in {@link ConfiguredTargetKey} for details.
153 ImmutableSet<String> chosenNativeOptions =
154 diff.getFirst().keySet().stream()
155 .filter(
156 optionDef ->
157 !optionDef.hasOptionMetadataTag(OptionMetadataTag.EXPLICIT_IN_OUTPUT_PATH))
158 .map(OptionDefinition::getOptionName)
159 .collect(toImmutableSet());
160 // Note: getChangedStarlarkOptions includes all changed options, added options and removed
161 // options between baselineOptions and toOptions. This is necessary since there is no current
162 // notion of trimming a Starlark option: 'null' or non-existent justs means set to default.
163 ImmutableSet<String> chosenStarlarkOptions =
164 diff.getChangedStarlarkOptions().stream().map(Label::toString).collect(toImmutableSet());
165 return hashChosenOptions(toOptions, chosenNativeOptions, chosenStarlarkOptions);
166 }
167
168 /**
169 * Compute the output directory name fragment corresponding to the new BuildOptions based on the
170 * names and values of all options (both native and Starlark) previously transitioned anywhere in
171 * the build by Starlark transitions. Options only set on command line are not affecting the
172 * computation.
173 *
174 * @param toOptions the {@link BuildOptions} to use to calculate which we need to compute {@code
175 * transitionDirectoryNameFragment}.
176 */
177 private static String computeNameFragmentWithAffectedByStarlarkTransition(
178 BuildOptions toOptions) {
179 CoreOptions buildConfigOptions = toOptions.get(CoreOptions.class);
180 if (buildConfigOptions.affectedByStarlarkTransition.isEmpty()) {
181 return "";
182 }
183
184 ImmutableList.Builder<String> affectedNativeOptions = ImmutableList.builder();
185 ImmutableList.Builder<String> affectedStarlarkOptions = ImmutableList.builder();
186
187 for (String optionName : buildConfigOptions.affectedByStarlarkTransition) {
188 if (optionName.startsWith(COMMAND_LINE_OPTION_PREFIX)) {
189 String nativeOptionName = optionName.substring(COMMAND_LINE_OPTION_PREFIX.length());
190 affectedNativeOptions.add(nativeOptionName);
191 } else {
192 affectedStarlarkOptions.add(optionName);
193 }
194 }
195
196 return hashChosenOptions(
197 toOptions, affectedNativeOptions.build(), affectedStarlarkOptions.build());
198 }
199
200 /**
201 * Compute a hash of the given BuildOptions by hashing only the options referenced in both
202 * chosenNative and chosenStarlark. The order of the chosen order does not matter (as this
203 * function will effectively sort them into a canonical order) and the pre-hash for each option
204 * will be of the form (//command_line_option:[native option]|[Starlark option label])=[value].
205 *
206 * <p>If a supplied native option does not exist, it is skipped (as it is presumed non-existence
207 * is due to trimming).
208 *
209 * <p>If a supplied Starlark option does exist, the pre-hash will be [Starlark option label]@null
210 * (as it is presumed non-existence is due to being set to default value).
211 */
212 private static String hashChosenOptions(
213 BuildOptions toOptions, Iterable<String> chosenNative, Iterable<String> chosenStarlark) {
214 // TODO(blaze-configurability-team): A mild performance optimization would have this be global.
215 ImmutableMap<String, OptionInfo> optionInfoMap = OptionInfo.buildMapFrom(toOptions);
216
217 // Note that the TreeMap guarantees a stable ordering of keys and thus
218 // it is okay if chosenNative or chosenStarlark do not have a stable iteration order
219 TreeMap<String, Object> toHash = new TreeMap<>();
220 for (String nativeOptionName : chosenNative) {
221 Object value;
222 try {
223 OptionInfo optionInfo = optionInfoMap.get(nativeOptionName);
224 if (optionInfo == null) {
225 // This can occur if toOptions has been trimmed but the supplied chosen native options
226 // includes that trimmed options.
227 // (e.g. legacy naming mode, using --trim_test_configuration and --test_arg transition).
228 continue;
229 }
230 value =
231 optionInfo
232 .getDefinition()
233 .getField()
234 .get(toOptions.get(optionInfoMap.get(nativeOptionName).getOptionClass()));
235 } catch (IllegalAccessException e) {
236 throw new VerifyException(
237 "IllegalAccess for option " + nativeOptionName + ": " + e.getMessage());
238 }
239 // TODO(blaze-configurability-team): The commandline option is legacy and can be removed
240 // after fixing up all the associated tests.
241 toHash.put("//command_line_option:" + nativeOptionName, value);
242 }
243 for (String starlarkOptionName : chosenStarlark) {
244 Object value =
245 toOptions.getStarlarkOptions().get(Label.parseAbsoluteUnchecked(starlarkOptionName));
246 toHash.put(starlarkOptionName, value);
247 }
248
249 if (toHash.isEmpty()) {
250 return "";
251 } else {
252 ImmutableList.Builder<String> hashStrs = ImmutableList.builderWithExpectedSize(toHash.size());
253 for (Map.Entry<String, Object> singleOptionAndValue : toHash.entrySet()) {
254 Object value = singleOptionAndValue.getValue();
255 if (value != null) {
256 hashStrs.add(singleOptionAndValue.getKey() + "=" + value);
257 } else {
258 // Avoid using =null to different from value being the non-null String "null"
259 hashStrs.add(singleOptionAndValue.getKey() + "@null");
260 }
261 }
262 return transitionDirectoryNameFragment(hashStrs.build());
263 }
264 }
265
266 @VisibleForTesting
267 public static String transitionDirectoryNameFragment(Iterable<String> opts) {
268 Fingerprint fp = new Fingerprint();
269 for (String opt : opts) {
270 fp.addString(opt);
271 }
272 // Shorten the hash to 48 bits. This should provide sufficient collision avoidance
273 // (that is, we don't expect anyone to experience a collision ever).
274 // Shortening the hash is important for Windows paths that tend to be short.
275 String suffix = fp.hexDigestAndReset().substring(0, HASH_LENGTH);
276 return "ST-" + suffix;
277 }
Greg Estren531fc042015-05-26 22:37:44 +0000278}