blob: 3167cae9fc30cc4703bdf60bfd6a1218d611d70a [file] [log] [blame]
// Copyright 2023 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.config;
import com.google.auto.value.AutoValue;
import com.google.common.base.Splitter;
import com.google.common.base.Verify;
import com.google.devtools.build.lib.analysis.starlark.StarlarkAttributeTransitionProvider;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.skyframe.BzlLoadFailedException;
import com.google.devtools.build.lib.skyframe.BzlLoadValue;
import com.google.devtools.build.lib.skyframe.StarlarkBuiltinsValue;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nullable;
/**
* Utility class for loading a Starlark exec transition from source and making it available as an
* {@link StarlarkAttributeTransitionProvider}.
*/
public final class StarlarkExecTransitionLoader {
/** Thrown when the Starlark transition failed to load. */
public static class StarlarkExecTransitionLoadingException extends Exception {
public StarlarkExecTransitionLoadingException(String context, String ref, String message) {
super(
String.format(
"Bad Starlark transition reference from %s: %s. %s.", context, ref, message));
}
}
/** Caller-provided logic for Skyframe-evaluating {@link BzlLoadValue.Key}s. */
public interface BzlFileLoader {
/**
* Loads the given {@link BzlLoadValue.Key}. Returns null if not all Skyframe deps are ready.
*/
@Nullable
BzlLoadValue getValue(BzlLoadValue.Key key) throws BzlLoadFailedException, InterruptedException;
}
/**
* Loads the Starlark transition that implements execution transition logic according to {@link
* CoreOptions#starlarkExecConfig}.
*
* @param options the current configured target's {@link BuildOptions}. This is used to find the
* value for {@link CoreOptions#starlarkExecConfig}.
* @param bzlFileLoader caller-provided logic for loading {@link BzlLoadValue.Key} skyvalues.
* @return null if Skyframe deps need loading. A filled {@link Optional} if this build implements
* the exec transition with a Starlark transition. An empty {@link Optional} if this build
* implements the exec transition with native logic.
* @throws StarlarkExecTransitionLoadingException if the desired transition isn't a valid Starlark
* exec transition.
*/
@Nullable
public static Optional<StarlarkAttributeTransitionProvider> loadStarlarkExecTransition(
@Nullable BuildOptions options, BzlFileLoader bzlFileLoader)
throws StarlarkExecTransitionLoadingException, InterruptedException {
if (options == null || options.equals(CommonOptions.EMPTY_OPTIONS)) {
return Optional.empty();
}
String userRef =
Verify.verifyNotNull(
options.get(CoreOptions.class).starlarkExecConfig,
"Cannot apply the exec transition since no transition is defined for this build.");
final String flagName = "--experimental_exec_config";
TransitionReference parsedRef = TransitionReference.create(userRef, flagName);
BzlLoadValue bzlValue;
try {
bzlValue =
bzlFileLoader.getValue(
Objects.equals(
parsedRef.bzlFile().getRepository(), StarlarkBuiltinsValue.BUILTINS_REPO)
? BzlLoadValue.keyForBuiltins(parsedRef.bzlFile())
: BzlLoadValue.keyForBuild(parsedRef.bzlFile()));
} catch (BzlLoadFailedException e) {
throw new StarlarkExecTransitionLoadingException(flagName, userRef, e.getMessage());
}
if (bzlValue == null) {
return null;
}
Object transition = bzlValue.getModule().getGlobal(parsedRef.starlarkSymbolName());
if (transition == null) {
throw new StarlarkExecTransitionLoadingException(
flagName,
userRef,
String.format("%s not found in %s", parsedRef.starlarkSymbolName(), parsedRef.bzlFile()));
} else if (!(transition instanceof StarlarkDefinedConfigTransition)) {
throw new StarlarkExecTransitionLoadingException(
flagName, userRef, parsedRef.starlarkSymbolName() + " is not a Starlark transition");
}
return Optional.of(
new StarlarkExecTransitionProvider((StarlarkDefinedConfigTransition) transition));
}
/** A marker class to distinguish the exec transition from other starlark transitions. */
static class StarlarkExecTransitionProvider extends StarlarkAttributeTransitionProvider {
StarlarkExecTransitionProvider(StarlarkDefinedConfigTransition execTransition) {
super(execTransition);
}
@Override
public boolean allowImmutableFlagChanges() {
// The exec transition must be allowed to change otherwise immutable flags.
return true;
}
}
/**
* Structured form of a Starlark transition reference.
*
* <p>In other words, structured form of <code>//pkg:def.bzl%transition_name</code>
*/
@AutoValue
abstract static class TransitionReference {
/** The .bzl file where this transition is defined. */
abstract Label bzlFile();
/** The transition's Starlark symbol name. */
abstract String starlarkSymbolName();
/**
* Returns a structured form of a user-specified Starlark transition reference.
*
* @throws StarlarkExecTransitionLoadingException on parsing errors.
*/
static TransitionReference create(String userRef, String context)
throws StarlarkExecTransitionLoadingException {
List<String> splitval = Splitter.on('%').splitToList(userRef);
if (splitval.size() < 2 || splitval.get(1).isEmpty()) {
throw new StarlarkExecTransitionLoadingException(
context, userRef, "Doesn't match expected form //pkg:file.bzl%%symbol");
}
try {
return new AutoValue_StarlarkExecTransitionLoader_TransitionReference(
Label.parseCanonical(splitval.get(0)), splitval.get(1));
} catch (LabelSyntaxException e) {
throw new StarlarkExecTransitionLoadingException(
context, userRef, String.format("Bad label %s: %s", splitval.get(0), e.getMessage()));
}
}
}
private StarlarkExecTransitionLoader() {}
}