Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2015 The Bazel Authors. All rights reserved. |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 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 | package com.google.devtools.build.lib.skyframe; |
| 15 | |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 16 | import static com.google.common.collect.ImmutableSet.toImmutableSet; |
| 17 | import static com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition.COMMAND_LINE_OPTION_PREFIX; |
| 18 | |
| 19 | import com.google.common.annotations.VisibleForTesting; |
| 20 | import com.google.common.base.VerifyException; |
| 21 | import com.google.common.collect.ImmutableList; |
| 22 | import com.google.common.collect.ImmutableMap; |
| 23 | import com.google.common.collect.ImmutableSet; |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 24 | import com.google.devtools.build.lib.analysis.BlazeDirectories; |
Greg Estren | 0004943 | 2015-08-25 16:43:47 +0000 | [diff] [blame] | 25 | import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 26 | import com.google.devtools.build.lib.analysis.PlatformOptions; |
jhorvitz | 33f7648 | 2021-10-28 10:13:26 -0700 | [diff] [blame] | 27 | import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
twigg | 50dc446 | 2022-03-10 14:59:52 -0800 | [diff] [blame] | 28 | import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| 29 | import com.google.devtools.build.lib.analysis.config.CoreOptions; |
twigg | af15543 | 2021-11-29 13:08:24 -0800 | [diff] [blame] | 30 | import com.google.devtools.build.lib.analysis.config.FragmentFactory; |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 31 | import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 32 | import com.google.devtools.build.lib.analysis.config.OptionInfo; |
| 33 | import com.google.devtools.build.lib.cmdline.Label; |
jhorvitz | 6380c289 | 2021-05-03 10:13:52 -0700 | [diff] [blame] | 34 | import com.google.devtools.build.lib.cmdline.RepositoryName; |
Greg Estren | 0004943 | 2015-08-25 16:43:47 +0000 | [diff] [blame] | 35 | import com.google.devtools.build.lib.packages.RuleClassProvider; |
Googler | 4e7b1e5 | 2020-10-09 11:58:45 -0700 | [diff] [blame] | 36 | import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 37 | import com.google.devtools.build.lib.util.Fingerprint; |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 38 | import com.google.devtools.build.skyframe.SkyFunction; |
| 39 | import com.google.devtools.build.skyframe.SkyFunctionException; |
| 40 | import com.google.devtools.build.skyframe.SkyKey; |
| 41 | import com.google.devtools.build.skyframe.SkyValue; |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 42 | import com.google.devtools.common.options.OptionDefinition; |
| 43 | import com.google.devtools.common.options.OptionMetadataTag; |
| 44 | import com.google.devtools.common.options.OptionsParsingException; |
| 45 | import java.util.Map; |
| 46 | import java.util.TreeMap; |
Googler | 4e7b1e5 | 2020-10-09 11:58:45 -0700 | [diff] [blame] | 47 | import net.starlark.java.eval.StarlarkSemantics; |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 48 | |
jhorvitz | 9ad5aed | 2021-04-23 14:32:33 -0700 | [diff] [blame] | 49 | /** A builder for {@link BuildConfigurationValue} instances. */ |
| 50 | public final class BuildConfigurationFunction implements SkyFunction { |
| 51 | |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 52 | // 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 Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 56 | private final BlazeDirectories directories; |
gregce | 490b095 | 2017-07-06 18:44:38 -0400 | [diff] [blame] | 57 | private final ConfiguredRuleClassProvider ruleClassProvider; |
twigg | af15543 | 2021-11-29 13:08:24 -0800 | [diff] [blame] | 58 | private final FragmentFactory fragmentFactory = new FragmentFactory(); |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 59 | |
mjhalupka | 5d7fa7b | 2018-03-22 13:37:38 -0700 | [diff] [blame] | 60 | public BuildConfigurationFunction( |
jhorvitz | 9ad5aed | 2021-04-23 14:32:33 -0700 | [diff] [blame] | 61 | BlazeDirectories directories, RuleClassProvider ruleClassProvider) { |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 62 | this.directories = directories; |
gregce | 490b095 | 2017-07-06 18:44:38 -0400 | [diff] [blame] | 63 | this.ruleClassProvider = (ConfiguredRuleClassProvider) ruleClassProvider; |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 64 | } |
| 65 | |
| 66 | @Override |
Janak Ramakrishnan | 3c0adb2 | 2016-08-15 21:54:55 +0000 | [diff] [blame] | 67 | public SkyValue compute(SkyKey skyKey, Environment env) |
| 68 | throws InterruptedException, BuildConfigurationFunctionException { |
kchodorow | 85ae190 | 2017-04-22 15:07:22 -0400 | [diff] [blame] | 69 | WorkspaceNameValue workspaceNameValue = (WorkspaceNameValue) env |
| 70 | .getValue(WorkspaceNameValue.key()); |
| 71 | if (workspaceNameValue == null) { |
| 72 | return null; |
| 73 | } |
jhorvitz | 6380c289 | 2021-05-03 10:13:52 -0700 | [diff] [blame] | 74 | |
Googler | 4e7b1e5 | 2020-10-09 11:58:45 -0700 | [diff] [blame] | 75 | StarlarkSemantics starlarkSemantics = PrecomputedValue.STARLARK_SEMANTICS.get(env); |
| 76 | if (starlarkSemantics == null) { |
| 77 | return null; |
| 78 | } |
twigg | 44c30ac | 2021-12-08 15:57:11 -0800 | [diff] [blame] | 79 | BuildConfigurationKey key = (BuildConfigurationKey) skyKey.argument(); |
twigg | 50dc446 | 2022-03-10 14:59:52 -0800 | [diff] [blame] | 80 | |
| 81 | BuildOptions targetOptions = key.getOptions(); |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 82 | |
| 83 | String transitionDirectoryNameFragment; |
twigg | 50dc446 | 2022-03-10 14:59:52 -0800 | [diff] [blame] | 84 | if (targetOptions |
| 85 | .get(CoreOptions.class) |
| 86 | .outputDirectoryNamingScheme |
| 87 | .equals(CoreOptions.OutputDirectoryNamingScheme.DIFF_AGAINST_BASELINE)) { |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 88 | // 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); |
twigg | 50dc446 | 2022-03-10 14:59:52 -0800 | [diff] [blame] | 112 | } |
| 113 | |
janakr | ca6209f | 2020-11-13 19:17:27 -0800 | [diff] [blame] | 114 | try { |
twigg | 44c30ac | 2021-12-08 15:57:11 -0800 | [diff] [blame] | 115 | return BuildConfigurationValue.create( |
twigg | 50dc446 | 2022-03-10 14:59:52 -0800 | [diff] [blame] | 116 | targetOptions, |
Googler | 80ada0f | 2021-12-22 07:15:59 -0800 | [diff] [blame] | 117 | RepositoryName.createFromValidStrippedName(workspaceNameValue.getName()), |
twigg | 44c30ac | 2021-12-08 15:57:11 -0800 | [diff] [blame] | 118 | starlarkSemantics.getBool(BuildLanguageOptions.EXPERIMENTAL_SIBLING_REPOSITORY_LAYOUT), |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 119 | transitionDirectoryNameFragment, |
twigg | 44c30ac | 2021-12-08 15:57:11 -0800 | [diff] [blame] | 120 | // Arguments below this are server-global. |
| 121 | directories, |
| 122 | ruleClassProvider, |
| 123 | fragmentFactory); |
| 124 | } catch (InvalidConfigurationException e) { |
janakr | ca6209f | 2020-11-13 19:17:27 -0800 | [diff] [blame] | 125 | throw new BuildConfigurationFunctionException(e); |
| 126 | } |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 127 | } |
| 128 | |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 129 | private static final class BuildConfigurationFunctionException extends SkyFunctionException { |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 130 | BuildConfigurationFunctionException(Exception e) { |
Greg Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 131 | super(e, Transience.PERSISTENT); |
| 132 | } |
| 133 | } |
twigg | 52d1d4a | 2022-03-10 16:41:11 -0800 | [diff] [blame] | 134 | |
| 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 Estren | 531fc04 | 2015-05-26 22:37:44 +0000 | [diff] [blame] | 278 | } |