|
| 1 | +// Copyright 2019 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.skyframe; |
| 16 | + |
| 17 | +import com.google.common.base.Preconditions; |
| 18 | +import com.google.common.collect.ImmutableList; |
| 19 | +import com.google.common.collect.Interner; |
| 20 | +import com.google.common.collect.Iterables; |
| 21 | +import com.google.devtools.build.lib.analysis.PlatformOptions; |
| 22 | +import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| 23 | +import com.google.devtools.build.lib.cmdline.Label; |
| 24 | +import com.google.devtools.build.lib.concurrent.BlazeInterners; |
| 25 | +import com.google.devtools.build.lib.concurrent.ThreadSafety; |
| 26 | +import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; |
| 27 | +import com.google.devtools.build.lib.vfs.RootedPath; |
| 28 | +import com.google.devtools.build.skyframe.SkyFunctionName; |
| 29 | +import com.google.devtools.build.skyframe.SkyKey; |
| 30 | +import com.google.devtools.build.skyframe.SkyValue; |
| 31 | +import com.google.devtools.common.options.OptionsParser; |
| 32 | +import com.google.devtools.common.options.OptionsParsingException; |
| 33 | +import com.google.devtools.common.options.OptionsParsingResult; |
| 34 | +import java.util.Collection; |
| 35 | +import java.util.List; |
| 36 | +import java.util.Map; |
| 37 | +import java.util.Objects; |
| 38 | + |
| 39 | +/** |
| 40 | + * Stores contents of a platforms/flags mapping file for transforming one {@link |
| 41 | + * BuildConfigurationValue.Key} into another. |
| 42 | + * |
| 43 | + * <p>See <a href=https://docs.google.com/document/d/1Vg_tPgiZbSrvXcJ403vZVAGlsWhH9BUDrAxMOYnO0Ls> |
| 44 | + * the design</a> for more details on how the mapping can be defined and the desired logic on how it |
| 45 | + * is applied to configuration keys. |
| 46 | + */ |
| 47 | +public final class PlatformMappingValue implements SkyValue { |
| 48 | + |
| 49 | + /** Key for {@link PlatformMappingValue} based on the location of the mapping file. */ |
| 50 | + @ThreadSafety.Immutable |
| 51 | + @AutoCodec |
| 52 | + public static final class Key implements SkyKey { |
| 53 | + private static final Interner<Key> interner = BlazeInterners.newWeakInterner(); |
| 54 | + |
| 55 | + private final RootedPath path; |
| 56 | + |
| 57 | + private Key(RootedPath path) { |
| 58 | + this.path = path; |
| 59 | + } |
| 60 | + |
| 61 | + @AutoCodec.VisibleForSerialization |
| 62 | + @AutoCodec.Instantiator |
| 63 | + static Key create(RootedPath path) { |
| 64 | + return interner.intern(new Key(path)); |
| 65 | + } |
| 66 | + |
| 67 | + @Override |
| 68 | + public SkyFunctionName functionName() { |
| 69 | + return SkyFunctions.PLATFORM_MAPPING; |
| 70 | + } |
| 71 | + |
| 72 | + @Override |
| 73 | + public boolean equals(Object o) { |
| 74 | + if (this == o) { |
| 75 | + return true; |
| 76 | + } |
| 77 | + if (o == null || getClass() != o.getClass()) { |
| 78 | + return false; |
| 79 | + } |
| 80 | + Key key = (Key) o; |
| 81 | + return Objects.equals(path, key.path); |
| 82 | + } |
| 83 | + |
| 84 | + @Override |
| 85 | + public int hashCode() { |
| 86 | + return Objects.hash(path); |
| 87 | + } |
| 88 | + |
| 89 | + @Override |
| 90 | + public String toString() { |
| 91 | + return "PlatformMappingValue.Key{" + "path=" + path + '}'; |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + private final Map<Label, Collection<String>> platformsToFlags; |
| 96 | + private final Map<Collection<String>, Label> flagsToPlatforms; |
| 97 | + |
| 98 | + /** |
| 99 | + * Creates a new mapping value which will match on the given platforms (if a target platform is |
| 100 | + * set on the key to be mapped), otherwise on the set of flags. |
| 101 | + * |
| 102 | + * @param platformsToFlags mapping from target platform label to the command line style flags that |
| 103 | + * should be parsed & modified if that platform is set |
| 104 | + * @param flagsToPlatforms mapping from a collection of command line style flags to a target |
| 105 | + * platform that should be set if the flags match the mapped options |
| 106 | + */ |
| 107 | + PlatformMappingValue( |
| 108 | + Map<Label, Collection<String>> platformsToFlags, |
| 109 | + Map<Collection<String>, Label> flagsToPlatforms) { |
| 110 | + this.platformsToFlags = platformsToFlags; |
| 111 | + this.flagsToPlatforms = flagsToPlatforms; |
| 112 | + } |
| 113 | + |
| 114 | + /** |
| 115 | + * Maps one {@link BuildConfigurationValue.Key} to another by way of mappings provided in a file. |
| 116 | + * |
| 117 | + * <p>The <a href=https://docs.google.com/document/d/1Vg_tPgiZbSrvXcJ403vZVAGlsWhH9BUDrAxMOYnO0Ls> |
| 118 | + * full design</a> contains the details for the mapping logic but in short: |
| 119 | + * |
| 120 | + * <ol> |
| 121 | + * <li>If a target platform is set on the original then mappings from platform to flags will be |
| 122 | + * applied. |
| 123 | + * <li>If no target platform is set then mappings from flags to platforms will be applied. |
| 124 | + * <li>If no matching flags to platforms mapping was found, the default target platform will be |
| 125 | + * used. |
| 126 | + * </ol> |
| 127 | + * |
| 128 | + * @param original the key representing the configuration to be mapped |
| 129 | + * @param defaultBuildOptions build options as set by default in this server |
| 130 | + * @return the mapped key if any mapping matched the original or else the original |
| 131 | + * @throws OptionsParsingException if any of the user configured flags cannot be parsed |
| 132 | + * @throws IllegalArgumentException if the original does not contain a {@link PlatformOptions} |
| 133 | + * fragment |
| 134 | + */ |
| 135 | + public BuildConfigurationValue.Key map( |
| 136 | + BuildConfigurationValue.Key original, BuildOptions defaultBuildOptions) |
| 137 | + throws OptionsParsingException { |
| 138 | + BuildOptions.OptionsDiffForReconstruction originalDiff = original.getOptionsDiff(); |
| 139 | + BuildOptions originalOptions = defaultBuildOptions.applyDiff(originalDiff); |
| 140 | + |
| 141 | + Preconditions.checkArgument( |
| 142 | + originalOptions.contains(PlatformOptions.class), |
| 143 | + "When using platform mappings, all configurations must contain platform options"); |
| 144 | + |
| 145 | + BuildOptions modifiedOptions = null; |
| 146 | + |
| 147 | + if (!originalOptions.get(PlatformOptions.class).platforms.isEmpty()) { |
| 148 | + List<Label> platforms = originalOptions.get(PlatformOptions.class).platforms; |
| 149 | + |
| 150 | + Preconditions.checkArgument( |
| 151 | + platforms.size() == 1, |
| 152 | + "Platform mapping only supports a single target platform but found %s", |
| 153 | + platforms); |
| 154 | + |
| 155 | + Label targetPlatform = Iterables.getOnlyElement(platforms); |
| 156 | + if (!platformsToFlags.containsKey(targetPlatform)) { |
| 157 | + // This can happen if the user has set the platform and any other flags that would normally |
| 158 | + // be mapped from it on the command line instead of relying on the mapping. |
| 159 | + return original; |
| 160 | + } |
| 161 | + |
| 162 | + OptionsParsingResult parsingResult = |
| 163 | + parse(platformsToFlags.get(targetPlatform), defaultBuildOptions); |
| 164 | + modifiedOptions = originalOptions.applyParsingResult(parsingResult); |
| 165 | + } else { |
| 166 | + boolean mappingFound = false; |
| 167 | + for (Map.Entry<Collection<String>, Label> flagsToPlatform : flagsToPlatforms.entrySet()) { |
| 168 | + if (originalOptions.matches(parse(flagsToPlatform.getKey(), defaultBuildOptions))) { |
| 169 | + modifiedOptions = originalOptions.clone(); |
| 170 | + modifiedOptions.get(PlatformOptions.class).platforms = |
| 171 | + ImmutableList.of(flagsToPlatform.getValue()); |
| 172 | + mappingFound = true; |
| 173 | + break; |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + if (!mappingFound) { |
| 178 | + Label targetPlatform = originalOptions.get(PlatformOptions.class).computeTargetPlatform(); |
| 179 | + modifiedOptions = originalOptions.clone(); |
| 180 | + modifiedOptions.get(PlatformOptions.class).platforms = ImmutableList.of(targetPlatform); |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + return BuildConfigurationValue.key( |
| 185 | + original.getFragments(), |
| 186 | + BuildOptions.diffForReconstruction(defaultBuildOptions, modifiedOptions)); |
| 187 | + } |
| 188 | + |
| 189 | + private OptionsParsingResult parse(Iterable<String> args, BuildOptions defaultBuildOptions) |
| 190 | + throws OptionsParsingException { |
| 191 | + OptionsParser parser = OptionsParser.newOptionsParser(defaultBuildOptions.getFragmentClasses()); |
| 192 | + parser.parse(ImmutableList.copyOf(args)); |
| 193 | + // TODO(schmitt): Parse starlark options as well. |
| 194 | + return parser; |
| 195 | + } |
| 196 | +} |
0 commit comments