blob: 996001f01cb7b5f9531f3d48d6cea20219827062 [file] [log] [blame]
// Copyright 2024 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.config;
import static com.google.devtools.build.lib.server.FailureDetails.TargetPatterns.Code.DEPENDENCY_NOT_FOUND;
import static com.google.devtools.common.options.OptionsParser.STARLARK_SKIPPED_PREFIXES;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.Label.PackageContext;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.TargetParsingException;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.runtime.StarlarkOptionsParser;
import com.google.devtools.build.lib.skyframe.PackageValue;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionException;
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 javax.annotation.Nullable;
/**
* Converts a list of command-line flags (like {@code --compilation_mode=dbg} or {@code
* --//custom/starlark:flag=foo}) into a {@link NativeAndStarlarkFlags} instance. This is intended
* as preparation for using the flags to create or update a build configuration in Bazel.
*/
public class ParsedFlagsFunction implements SkyFunction {
private final ImmutableSet<Class<? extends FragmentOptions>> optionsClasses;
public ParsedFlagsFunction(ImmutableSet<Class<? extends FragmentOptions>> optionsClasses) {
this.optionsClasses = optionsClasses;
}
@Nullable
@Override
public SkyValue compute(SkyKey skyKey, Environment env)
throws InterruptedException, ParsedFlagsFunctionException {
ParsedFlagsValue.Key key = (ParsedFlagsValue.Key) skyKey.argument();
ImmutableList.Builder<String> nativeFlags = ImmutableList.builder();
ImmutableList.Builder<String> starlarkFlags = ImmutableList.builder();
for (String flagSetting : key.rawFlags()) {
if (STARLARK_SKIPPED_PREFIXES.stream().noneMatch(flagSetting::startsWith)) {
nativeFlags.add(flagSetting);
} else {
starlarkFlags.add(flagSetting);
}
}
// The StarlarkOptionsParser needs a native options parser just to inject its Starlark flag
// values. It doesn't actually parse anything with the native parser.
OptionsParser fakeNativeParser = OptionsParser.builder().build();
StarlarkOptionsParser starlarkFlagParser =
StarlarkOptionsParser.newStarlarkOptionsParser(
new SkyframeTargetLoader(env, key.packageContext()), fakeNativeParser);
try {
if (!starlarkFlagParser.parseGivenArgs(starlarkFlags.build())) {
return null;
}
} catch (OptionsParsingException e) {
throw new ParsedFlagsFunctionException(e);
}
NativeAndStarlarkFlags flags =
NativeAndStarlarkFlags.builder()
.nativeFlags(nativeFlags.build())
.starlarkFlags(fakeNativeParser.getStarlarkOptions())
.optionsClasses(optionsClasses)
.repoMapping(key.packageContext().repoMapping())
.build();
return ParsedFlagsValue.create(flags);
}
/**
* Lets {@link StarlarkOptionsParser} convert flag names to {@link Target}s through a Skyframe
* {@link PackageValue} lookup.
*/
private static class SkyframeTargetLoader implements StarlarkOptionsParser.BuildSettingLoader {
private final Environment env;
private final PackageContext packageContext;
public SkyframeTargetLoader(Environment env, PackageContext packageContext) {
this.env = env;
this.packageContext = packageContext;
}
@Nullable
@Override
public Target loadBuildSetting(String name)
throws InterruptedException, TargetParsingException {
Label asLabel;
try {
asLabel = Label.parseWithPackageContext(name, packageContext);
} catch (LabelSyntaxException e) {
throw new IllegalArgumentException(e);
}
try {
SkyKey pkgKey = asLabel.getPackageIdentifier();
PackageValue pkg = (PackageValue) env.getValueOrThrow(pkgKey, NoSuchPackageException.class);
if (pkg == null) {
return null;
}
return pkg.getPackage().getTarget(asLabel.getName());
} catch (NoSuchPackageException | NoSuchTargetException e) {
throw new TargetParsingException(
String.format("Failed to load %s", name), e, DEPENDENCY_NOT_FOUND);
}
}
}
/** Exception class for errors during flag parsing. */
public static class ParsedFlagsFunctionException extends SkyFunctionException {
ParsedFlagsFunctionException(OptionsParsingException e) {
super(e, Transience.PERSISTENT);
}
}
}