Skip to content

Commit

Permalink
Transformation for build configurations based on a platform/flags map…
Browse files Browse the repository at this point in the history
…ping.

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 #6426

RELNOTES: None.
PiperOrigin-RevId: 238298127
  • Loading branch information
aragos authored and copybara-github committed Mar 13, 2019
1 parent 09c6b7a commit 9c230f1
Show file tree
Hide file tree
Showing 6 changed files with 482 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -35,36 +33,10 @@ public ImmutableSet<Class<? extends FragmentOptions>> requiredOptions() {
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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -101,23 +102,22 @@ public class PlatformOptions extends FragmentOptions {
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(
Expand Down Expand Up @@ -184,4 +184,39 @@ public PlatformOptions getHost() {
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ public Collection<FragmentOptions> getNativeOptions() {
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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -120,6 +118,8 @@ public final class SkyFunctions {
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 =
Expand Down
Loading

0 comments on commit 9c230f1

Please sign in to comment.