Skip to content

Commit

Permalink
Refactor and fix issues with StarlarkTransition BuildSettingsPackages
Browse files Browse the repository at this point in the history
Two main goals of this CL:
* Properly include 'metadata about Starlark build settings' in the transition application cache.
* Better isolate where and why the transition infrastructure reads BUILD file information. This unifies previously duplicated logic in SkyframeExecutor.

First, the transition application cache, on a miss, would also read defaults, type, aliasing information, etc. from BUILD files but failed to include any of this info in the cache key. This lead to failure to re-evaluate a transition when that build setting information in BUILD files changes.

This CL fixes that by introducing a new type StarlarkBuildSettingsDetailsValue, which contains all that information for a given set of Starlark build settings. That value is now included in the cache key.

Second, rather than having ConfiguredTargetFunction (via machinery in StarlarkTransition) itself do the Skyframe calls and subsequent computations necessary to compute StarlarkBuildSettingsDetailsValue, a new SkyFunction StarlarkBuildSettingsDetailsFunction was created to do so.

This provides a few benefits:
1. ConfiguredTargetFunction now is only registering an edge onto a single StarlarkBuildSettingsDetailsFunction, rather than all the PackageValue needed to resolve the StarlarkBuildSettings.

StarlarkBuildSettingsDetailsValue more narrowly describes how sensitive the transition is to change. This means irrelevant changes to a package involved in build setting resolution will not cause as much invalidation.

This information is now cached so StarlarkTransition (called via ConfiguredTargetFunction) can more quickly compute the cache key. (Beforehand, it was always unconditionally following any build setting alias chains).

2. SkyframeExecutor can just evaluate StarlarkBuildSettingsDetailsValue rather than having a slightly different implementation getBuildSettingsPackages. (The new getStarlarkBuildSettingsDetailsValue functions that must be synced are much simpler and easier to keep in sync.)

As part of this unification, the error messages are now consistent regardless of whether the problematic build settings are asked for when transitioning the top-level target or an underlying target.

3. The split between reading Packages to compute StarlarkBuildSettingsDetailsValue and then later consuming it in StarlarkTransition should be easier to read and maintain.

PS: As part of the refactor, significant issues were found in PrepareAnalysisPhaseFunction. Warnings have been added as comments and upcoming work will be deleting that file entirely.

This fixes caching issue described at bazelbuild#15732

PiperOrigin-RevId: 462741847
Change-Id: I517a9a841735e6cff26faea5373f50a164334f86
  • Loading branch information
Googler authored and copybara-github committed Jul 23, 2022
1 parent 41cf353 commit 2f7d965
Show file tree
Hide file tree
Showing 10 changed files with 647 additions and 377 deletions.
19 changes: 18 additions & 1 deletion src/main/java/com/google/devtools/build/lib/analysis/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ java_library(
":starlark/bazel_build_api_globals",
":starlark/function_transition_util",
":starlark/starlark_api_provider",
":starlark/starlark_build_settings_details_value",
":starlark/starlark_command_line",
":starlark/starlark_exec_group_collection",
":starlark/starlark_late_bound_default",
Expand Down Expand Up @@ -418,7 +419,6 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_key",
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_value_creation_exception",
"//src/main/java/com/google/devtools/build/lib/skyframe:package_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:starlark_builtins_value",
"//src/main/java/com/google/devtools/build/lib/skyframe:workspace_status_value",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
Expand Down Expand Up @@ -1259,6 +1259,23 @@ java_library(
],
)

java_library(
name = "starlark/starlark_build_settings_details_value",
srcs = [
"starlark/StarlarkBuildSettingsDetailsValue.java",
],
deps = [
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/concurrent:thread_safety",
"//src/main/java/com/google/devtools/build/lib/packages",
"//src/main/java/com/google/devtools/build/lib/skyframe:sky_functions",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//third_party:auto_value",
"//third_party:error_prone_annotations",
"//third_party:guava",
],
)

# TODO(b/144899336): This should be analysis/actions/BUILD
java_library(
name = "actions/abstract_file_write_action",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.devtools.build.lib.analysis.ConfigurationsCollector;
import com.google.devtools.build.lib.analysis.ConfigurationsResult;
Expand All @@ -35,6 +36,7 @@
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionUtil;
import com.google.devtools.build.lib.analysis.starlark.FunctionTransitionUtil;
import com.google.devtools.build.lib.analysis.starlark.StarlarkBuildSettingsDetailsValue;
import com.google.devtools.build.lib.analysis.starlark.StarlarkTransition;
import com.google.devtools.build.lib.analysis.starlark.StarlarkTransition.TransitionException;
import com.google.devtools.build.lib.cmdline.Label;
Expand All @@ -46,7 +48,6 @@
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.skyframe.BuildConfigurationKey;
import com.google.devtools.build.lib.skyframe.ConfiguredValueCreationException;
import com.google.devtools.build.lib.skyframe.PackageValue;
import com.google.devtools.build.lib.skyframe.PlatformMappingValue;
import com.google.devtools.build.lib.util.OrderedSetMultimap;
import com.google.devtools.build.lib.vfs.PathFragment;
Expand Down Expand Up @@ -99,9 +100,13 @@ public final class ConfigurationResolver {
private static class StarlarkTransitionCacheKey {
private final ConfigurationTransition transition;
private final BuildOptions fromOptions;
private final StarlarkBuildSettingsDetailsValue details;
private final int hashCode;

StarlarkTransitionCacheKey(ConfigurationTransition transition, BuildOptions fromOptions) {
StarlarkTransitionCacheKey(
ConfigurationTransition transition,
BuildOptions fromOptions,
StarlarkBuildSettingsDetailsValue details) {
// For rule self-transitions, the transition instance encapsulates both the transition logic
// and attributes of the target it's attached to. This is important: the same transition in
// the same configuration applied to distinct targets may produce different outputs. See
Expand All @@ -112,7 +117,8 @@ private static class StarlarkTransitionCacheKey {
// of an interning contract there is.
this.transition = transition;
this.fromOptions = fromOptions;
this.hashCode = Objects.hash(transition, fromOptions);
this.details = details;
this.hashCode = Objects.hash(transition, fromOptions, details);
}

@Override
Expand All @@ -124,7 +130,8 @@ public boolean equals(Object other) {
return false;
}
return (this.transition.equals(((StarlarkTransitionCacheKey) other).transition)
&& this.fromOptions.equals(((StarlarkTransitionCacheKey) other).fromOptions));
&& this.fromOptions.equals(((StarlarkTransitionCacheKey) other).fromOptions)
&& this.details.equals(((StarlarkTransitionCacheKey) other).details));
}

@Override
Expand Down Expand Up @@ -423,8 +430,8 @@ private ImmutableList<String> collectTransitionKeys(
* Applies a configuration transition over a set of build options.
*
* <p>This is only for callers that can't use {@link #applyTransitionWithSkyframe}. The difference
* is {@link #applyTransitionWithSkyframe} internally computes {@code buildSettingPackages} with
* Skyframe, while this version requires it as a precomputed input.
* is {@link #applyTransitionWithSkyframe} internally computes {@code details} with Skyframe,
* while this version requires it as a precomputed input.
*
* <p>prework - load all default values for reading build settings in Starlark transitions (by
* design, {@link BuildOptions} never holds default values of build settings)
Expand All @@ -437,11 +444,11 @@ private ImmutableList<String> collectTransitionKeys(
public static Map<String, BuildOptions> applyTransitionWithoutSkyframe(
BuildOptions fromOptions,
ConfigurationTransition transition,
Map<PackageValue.Key, PackageValue> buildSettingPackages,
StarlarkBuildSettingsDetailsValue details,
ExtendedEventHandler eventHandler)
throws TransitionException, InterruptedException {
if (StarlarkTransition.doesStarlarkTransition(transition)) {
return applyStarlarkTransition(fromOptions, transition, buildSettingPackages, eventHandler);
return applyStarlarkTransition(fromOptions, transition, details, eventHandler);
}
return transition.apply(TransitionUtil.restrict(transition, fromOptions), eventHandler);
}
Expand All @@ -467,42 +474,63 @@ public static Map<String, BuildOptions> applyTransitionWithSkyframe(
ExtendedEventHandler eventHandler)
throws TransitionException, InterruptedException {
if (StarlarkTransition.doesStarlarkTransition(transition)) {
// TODO(blaze-team): find a way to dedupe this with SkyframeExecutor.getBuildSettingPackages.
Map<PackageValue.Key, PackageValue> buildSettingPackages =
StarlarkTransition.getBuildSettingPackages(env, transition);
return buildSettingPackages == null
? null
: applyStarlarkTransition(fromOptions, transition, buildSettingPackages, eventHandler);
StarlarkBuildSettingsDetailsValue details =
getStarlarkBuildSettingsDetailsValue(transition, env);
if (details == null) {
return null;
}
return applyStarlarkTransition(fromOptions, transition, details, eventHandler);
}
return transition.apply(TransitionUtil.restrict(transition, fromOptions), eventHandler);
}

/** Must be in sync with {@link SkyframeExecutor.getStarlarkBuildSettingsDetailsValue} */
@Nullable
private static StarlarkBuildSettingsDetailsValue getStarlarkBuildSettingsDetailsValue(
ConfigurationTransition transition, SkyFunction.Environment env)
throws TransitionException, InterruptedException {
ImmutableSet<Label> starlarkBuildSettings =
StarlarkTransition.getAllStarlarkBuildSettings(transition);
// Quick escape if transition doesn't use any Starlark build settings
if (starlarkBuildSettings.isEmpty()) {
return StarlarkBuildSettingsDetailsValue.EMPTY;
}
// Evaluate the key into StarlarkBuildSettingsDetailsValue
return (StarlarkBuildSettingsDetailsValue)
env.getValueOrThrow(
StarlarkBuildSettingsDetailsValue.key(starlarkBuildSettings),
TransitionException.class);
}

/**
* Applies a Starlark transition.
*
* @param fromOptions source options before the transition
* @param transition the transition itself
* @param buildSettingPackages packages for build_settings read by the transition. This is used to
* read default values for build_settings that aren't explicitly set on the build.
* @param details information from packages about Starlark build settings needed by transition
* @param eventHandler handler for errors evaluating the transition.
* @return transition output
*/
private static Map<String, BuildOptions> applyStarlarkTransition(
BuildOptions fromOptions,
ConfigurationTransition transition,
Map<PackageValue.Key, PackageValue> buildSettingPackages,
StarlarkBuildSettingsDetailsValue details,
ExtendedEventHandler eventHandler)
throws TransitionException, InterruptedException {
StarlarkTransitionCacheKey cacheKey = new StarlarkTransitionCacheKey(transition, fromOptions);
StarlarkTransitionCacheKey cacheKey =
new StarlarkTransitionCacheKey(transition, fromOptions, details);
StarlarkTransitionCacheValue cachedResult = starlarkTransitionCache.getIfPresent(cacheKey);
if (cachedResult != null) {
if (cachedResult.nonErrorEvents != null) {
cachedResult.nonErrorEvents.replayOn(eventHandler);
}
return cachedResult.result;
}
// All code below here only executes on a cache miss and thus should rely only on values that
// are part of the above cache key or constants that exist throughout the lifetime of the
// Blaze server instance.
BuildOptions adjustedOptions =
StarlarkTransition.addDefaultStarlarkOptions(fromOptions, transition, buildSettingPackages);
StarlarkTransition.addDefaultStarlarkOptions(fromOptions, transition, details);
// TODO(bazel-team): Add safety-check that this never mutates fromOptions.
StoredEventHandler handlerWithErrorStatus = new StoredEventHandler();
Map<String, BuildOptions> result =
Expand All @@ -520,7 +548,7 @@ private static Map<String, BuildOptions> applyStarlarkTransition(
if (handlerWithErrorStatus.hasErrors()) {
throw new TransitionException("Errors encountered while applying Starlark transition");
}
result = StarlarkTransition.validate(transition, buildSettingPackages, result);
result = StarlarkTransition.validate(transition, details, result);
// If the transition errored (like bad Starlark code), this method already exited with an
// exception so the results won't go into the cache. We still want to collect non-error events
// like print() output.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2022 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.analysis.starlark;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.skyframe.SkyFunctions;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.errorprone.annotations.CheckReturnValue;
import java.util.Map;
import java.util.Set;

/**
* This contains information about a list of given Starlark build options, specifically their
* defaults and the (final) actual values of alias {@link Label}.
*
* <p>For memory-efficiency reasons, aliasToActual contains only aliases in keys. Other attributes
* contain only actual build setting as keys.
*
* <p>Potentially aliased targets can be unaliased with aliasToActual().getWithDefault(raw, raw);
*/
@CheckReturnValue
@Immutable
@ThreadSafe
@AutoValue
public abstract class StarlarkBuildSettingsDetailsValue implements SkyValue {
/**
* Create a single StarlarkBuildSettingsDetailsValue that can be quickly returned for transitions
* that use no Starlark build settings
*/
public static final StarlarkBuildSettingsDetailsValue EMPTY =
create(ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of(), ImmutableMap.of());

/** Map from each build option to its default value. Does not include aliases. */
public abstract ImmutableMap<Label, Object> buildSettingToDefault();

/** Map from each build option to its type information. Does not include aliases. */
public abstract ImmutableMap<Label, Type<?>> buildSettingToType();

/** If build option is in this set, is an allows_multiple option. Does not include aliases. */
public abstract ImmutableSet<Label> buildSettingIsAllowsMultiple();

/** Map from an alias Label to actual Label it points to. */
public abstract ImmutableMap<Label, Label> aliasToActual();

public static StarlarkBuildSettingsDetailsValue create(
Map<Label, Object> buildSettingDefaults,
Map<Label, Type<?>> buildSettingToType,
Set<Label> buildSettingIsAllowsMultiple,
Map<Label, Label> aliasToActual) {
return new AutoValue_StarlarkBuildSettingsDetailsValue(
ImmutableMap.copyOf(buildSettingDefaults),
ImmutableMap.copyOf(buildSettingToType),
ImmutableSet.copyOf(buildSettingIsAllowsMultiple),
ImmutableMap.copyOf(aliasToActual));
}

public static Key key(Set<Label> buildSettings) {
return Key.create(ImmutableSet.copyOf(buildSettings));
}

/** {@link SkyKey} implementation used for {@link StarlarkBuildSettingsDetailsValue}. */
@CheckReturnValue
@Immutable
@ThreadSafe
@AutoValue
public abstract static class Key implements SkyKey {
public abstract ImmutableSet<Label> buildSettings();

@Override
public SkyFunctionName functionName() {
return SkyFunctions.STARLARK_BUILD_SETTINGS_DETAILS;
}

static Key create(ImmutableSet<Label> buildSettings) {
return new AutoValue_StarlarkBuildSettingsDetailsValue_Key(buildSettings);
}
}
}
Loading

0 comments on commit 2f7d965

Please sign in to comment.