Transformation for build configurations based on a platform/flags mapping. Introduces a new SkyValue which stores the information obtained from a mapping file (parser yet to be written) and provides logic to transform a build configuration (key) based on that. Step 3/N towards the platforms mapping functionality for https://github.com/bazelbuild/bazel/issues/6426 RELNOTES: None. PiperOrigin-RevId: 238298127
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfigurationLoader.java b/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfigurationLoader.java index ce34005..adad85a 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfigurationLoader.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/PlatformConfigurationLoader.java
@@ -16,13 +16,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory; import com.google.devtools.build.lib.analysis.config.FragmentOptions; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; -import com.google.devtools.build.lib.cmdline.Label; /** A loader that creates {@link PlatformConfiguration} instances based on command-line options. */ public class PlatformConfigurationLoader implements ConfigurationFragmentFactory { @@ -35,36 +33,10 @@ public PlatformConfiguration create(BuildOptions buildOptions) throws InvalidConfigurationException { PlatformOptions platformOptions = buildOptions.get(PlatformOptions.class); - - // Handle default values for the host and target platform. - // TODO(https://github.com/bazelbuild/bazel/issues/6849): After migration, set the defaults - // directly. - Label hostPlatform; - if (platformOptions.hostPlatform != null) { - hostPlatform = platformOptions.hostPlatform; - } else if (platformOptions.autoConfigureHostPlatform) { - // Use the auto-configured host platform. - hostPlatform = PlatformOptions.DEFAULT_HOST_PLATFORM; - } else { - // Use the legacy host platform. - hostPlatform = PlatformOptions.LEGACY_DEFAULT_HOST_PLATFORM; - } - - Label targetPlatform; - if (!platformOptions.platforms.isEmpty()) { - targetPlatform = Iterables.getFirst(platformOptions.platforms, null); - } else if (platformOptions.autoConfigureHostPlatform) { - // Default to the host platform, whatever it is. - targetPlatform = hostPlatform; - } else { - // Use the legacy target platform - targetPlatform = PlatformOptions.LEGACY_DEFAULT_TARGET_PLATFORM; - } - return new PlatformConfiguration( - hostPlatform, + platformOptions.computeHostPlatform(), ImmutableList.copyOf(platformOptions.extraExecutionPlatforms), - targetPlatform, + platformOptions.computeTargetPlatform(), ImmutableList.copyOf(platformOptions.extraToolchains), ImmutableList.copyOf(platformOptions.enabledToolchainTypes)); }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java index 7681871..788dc3a 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/PlatformOptions.java
@@ -15,6 +15,7 @@ package com.google.devtools.build.lib.analysis; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildConfiguration.LabelListConverter; import com.google.devtools.build.lib.analysis.config.FragmentOptions; @@ -101,23 +102,22 @@ public List<String> extraToolchains; @Option( - name = "toolchain_resolution_override", - allowMultiple = true, - defaultValue = "", - documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, - effectTags = { - OptionEffectTag.AFFECTS_OUTPUTS, - OptionEffectTag.CHANGES_INPUTS, - OptionEffectTag.LOADING_AND_ANALYSIS - }, - deprecationWarning = - "toolchain_resolution_override is now a no-op and will be removed in" - + " an upcoming release", - help = - "Override toolchain resolution for a toolchain type with a specific toolchain. " - + "Example: --toolchain_resolution_override=@io_bazel_rules_go//:toolchain=" - + "@io_bazel_rules_go//:linux-arm64-toolchain" - ) + name = "toolchain_resolution_override", + allowMultiple = true, + defaultValue = "", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = { + OptionEffectTag.AFFECTS_OUTPUTS, + OptionEffectTag.CHANGES_INPUTS, + OptionEffectTag.LOADING_AND_ANALYSIS + }, + deprecationWarning = + "toolchain_resolution_override is now a no-op and will be removed in" + + " an upcoming release", + help = + "Override toolchain resolution for a toolchain type with a specific toolchain. " + + "Example: --toolchain_resolution_override=@io_bazel_rules_go//:toolchain=" + + "@io_bazel_rules_go//:linux-arm64-toolchain") public List<String> toolchainResolutionOverrides; @Option( @@ -184,4 +184,39 @@ host.useToolchainResolutionForJavaRules = this.useToolchainResolutionForJavaRules; return host; } + + /** Returns the intended target platform value based on options defined in this fragment. */ + public Label computeTargetPlatform() { + // Handle default values for the host and target platform. + // TODO(https://github.com/bazelbuild/bazel/issues/6849): After migration, set the defaults + // directly. + + if (!platforms.isEmpty()) { + return Iterables.getFirst(platforms, null); + } else if (autoConfigureHostPlatform) { + // Default to the host platform, whatever it is. + return computeHostPlatform(); + } else { + // Use the legacy target platform + return LEGACY_DEFAULT_TARGET_PLATFORM; + } + } + + /** Returns the intended host platform value based on options defined in this fragment. */ + public Label computeHostPlatform() { + // Handle default values for the host and target platform. + // TODO(https://github.com/bazelbuild/bazel/issues/6849): After migration, set the defaults + // directly. + + Label hostPlatform; + if (this.hostPlatform != null) { + return this.hostPlatform; + } else if (autoConfigureHostPlatform) { + // Use the auto-configured host platform. + return DEFAULT_HOST_PLATFORM; + } else { + // Use the legacy host platform. + return LEGACY_DEFAULT_HOST_PLATFORM; + } + } }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java index 7775d33..a47b2d6 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
@@ -192,6 +192,11 @@ return fragmentOptionsMap.values(); } + /** Returns the set of fragment classes contained in these options. */ + public Set<Class<? extends FragmentOptions>> getFragmentClasses() { + return fragmentOptionsMap.keySet(); + } + public ImmutableMap<Label, Object> getStarlarkOptions() { return skylarkOptionsMap; }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java new file mode 100644 index 0000000..4ad16cb --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/PlatformMappingValue.java
@@ -0,0 +1,196 @@ +// Copyright 2019 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.skyframe; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Interner; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.analysis.PlatformOptions; +import com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.concurrent.BlazeInterners; +import com.google.devtools.build.lib.concurrent.ThreadSafety; +import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; +import com.google.devtools.build.lib.vfs.RootedPath; +import com.google.devtools.build.skyframe.SkyFunctionName; +import com.google.devtools.build.skyframe.SkyKey; +import com.google.devtools.build.skyframe.SkyValue; +import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.OptionsParsingException; +import com.google.devtools.common.options.OptionsParsingResult; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Stores contents of a platforms/flags mapping file for transforming one {@link + * BuildConfigurationValue.Key} into another. + * + * <p>See <a href=https://docs.google.com/document/d/1Vg_tPgiZbSrvXcJ403vZVAGlsWhH9BUDrAxMOYnO0Ls> + * the design</a> for more details on how the mapping can be defined and the desired logic on how it + * is applied to configuration keys. + */ +public final class PlatformMappingValue implements SkyValue { + + /** Key for {@link PlatformMappingValue} based on the location of the mapping file. */ + @ThreadSafety.Immutable + @AutoCodec + public static final class Key implements SkyKey { + private static final Interner<Key> interner = BlazeInterners.newWeakInterner(); + + private final RootedPath path; + + private Key(RootedPath path) { + this.path = path; + } + + @AutoCodec.VisibleForSerialization + @AutoCodec.Instantiator + static Key create(RootedPath path) { + return interner.intern(new Key(path)); + } + + @Override + public SkyFunctionName functionName() { + return SkyFunctions.PLATFORM_MAPPING; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Key key = (Key) o; + return Objects.equals(path, key.path); + } + + @Override + public int hashCode() { + return Objects.hash(path); + } + + @Override + public String toString() { + return "PlatformMappingValue.Key{" + "path=" + path + '}'; + } + } + + private final Map<Label, Collection<String>> platformsToFlags; + private final Map<Collection<String>, Label> flagsToPlatforms; + + /** + * Creates a new mapping value which will match on the given platforms (if a target platform is + * set on the key to be mapped), otherwise on the set of flags. + * + * @param platformsToFlags mapping from target platform label to the command line style flags that + * should be parsed & modified if that platform is set + * @param flagsToPlatforms mapping from a collection of command line style flags to a target + * platform that should be set if the flags match the mapped options + */ + PlatformMappingValue( + Map<Label, Collection<String>> platformsToFlags, + Map<Collection<String>, Label> flagsToPlatforms) { + this.platformsToFlags = platformsToFlags; + this.flagsToPlatforms = flagsToPlatforms; + } + + /** + * Maps one {@link BuildConfigurationValue.Key} to another by way of mappings provided in a file. + * + * <p>The <a href=https://docs.google.com/document/d/1Vg_tPgiZbSrvXcJ403vZVAGlsWhH9BUDrAxMOYnO0Ls> + * full design</a> contains the details for the mapping logic but in short: + * + * <ol> + * <li>If a target platform is set on the original then mappings from platform to flags will be + * applied. + * <li>If no target platform is set then mappings from flags to platforms will be applied. + * <li>If no matching flags to platforms mapping was found, the default target platform will be + * used. + * </ol> + * + * @param original the key representing the configuration to be mapped + * @param defaultBuildOptions build options as set by default in this server + * @return the mapped key if any mapping matched the original or else the original + * @throws OptionsParsingException if any of the user configured flags cannot be parsed + * @throws IllegalArgumentException if the original does not contain a {@link PlatformOptions} + * fragment + */ + public BuildConfigurationValue.Key map( + BuildConfigurationValue.Key original, BuildOptions defaultBuildOptions) + throws OptionsParsingException { + BuildOptions.OptionsDiffForReconstruction originalDiff = original.getOptionsDiff(); + BuildOptions originalOptions = defaultBuildOptions.applyDiff(originalDiff); + + Preconditions.checkArgument( + originalOptions.contains(PlatformOptions.class), + "When using platform mappings, all configurations must contain platform options"); + + BuildOptions modifiedOptions = null; + + if (!originalOptions.get(PlatformOptions.class).platforms.isEmpty()) { + List<Label> platforms = originalOptions.get(PlatformOptions.class).platforms; + + Preconditions.checkArgument( + platforms.size() == 1, + "Platform mapping only supports a single target platform but found %s", + platforms); + + Label targetPlatform = Iterables.getOnlyElement(platforms); + if (!platformsToFlags.containsKey(targetPlatform)) { + // This can happen if the user has set the platform and any other flags that would normally + // be mapped from it on the command line instead of relying on the mapping. + return original; + } + + OptionsParsingResult parsingResult = + parse(platformsToFlags.get(targetPlatform), defaultBuildOptions); + modifiedOptions = originalOptions.applyParsingResult(parsingResult); + } else { + boolean mappingFound = false; + for (Map.Entry<Collection<String>, Label> flagsToPlatform : flagsToPlatforms.entrySet()) { + if (originalOptions.matches(parse(flagsToPlatform.getKey(), defaultBuildOptions))) { + modifiedOptions = originalOptions.clone(); + modifiedOptions.get(PlatformOptions.class).platforms = + ImmutableList.of(flagsToPlatform.getValue()); + mappingFound = true; + break; + } + } + + if (!mappingFound) { + Label targetPlatform = originalOptions.get(PlatformOptions.class).computeTargetPlatform(); + modifiedOptions = originalOptions.clone(); + modifiedOptions.get(PlatformOptions.class).platforms = ImmutableList.of(targetPlatform); + } + } + + return BuildConfigurationValue.key( + original.getFragments(), + BuildOptions.diffForReconstruction(defaultBuildOptions, modifiedOptions)); + } + + private OptionsParsingResult parse(Iterable<String> args, BuildOptions defaultBuildOptions) + throws OptionsParsingException { + OptionsParser parser = OptionsParser.newOptionsParser(defaultBuildOptions.getFragmentClasses()); + parser.parse(ImmutableList.copyOf(args)); + // TODO(schmitt): Parse starlark options as well. + return parser; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java index ea7724a..8bfa5df 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
@@ -21,9 +21,7 @@ import com.google.devtools.build.skyframe.SkyFunctionName; import com.google.devtools.build.skyframe.SkyKey; -/** - * Value types in Skyframe. - */ +/** Value types in Skyframe. */ public final class SkyFunctions { public static final SkyFunctionName PRECOMPUTED = SkyFunctionName.createNonHermetic("PRECOMPUTED"); @@ -120,6 +118,8 @@ public static final SkyFunctionName BUILD_INFO = SkyFunctionName.createHermetic("BUILD_INFO"); public static final SkyFunctionName WORKSPACE_NAME = SkyFunctionName.createHermetic("WORKSPACE_NAME"); + static final SkyFunctionName PLATFORM_MAPPING = + SkyFunctionName.createHermetic("PLATFORM_MAPPING"); static final SkyFunctionName COVERAGE_REPORT = SkyFunctionName.createHermetic("COVERAGE_REPORT"); public static final SkyFunctionName REPOSITORY = SkyFunctionName.createHermetic("REPOSITORY"); public static final SkyFunctionName REPOSITORY_DIRECTORY =