From f244529d6b5e0ca94e0f454f145e8fff549c6d7f Mon Sep 17 00:00:00 2001 From: Googler Date: Thu, 4 Apr 2024 11:06:53 -0700 Subject: [PATCH] New sky function that does the following: 1) call BzlLoadFunction to load the content of PROJECT.scl file given the provided scl_config name 2) call ParsedFlagsFunction to parse the list of options 3) define a patch transition and applies the transition to the targetOptions which was used for creating topLevel configuration 4) the returned adjusted BuildOptions are then used in AnalysisPhaseRunner and BuildTool instead of the original BuildOptions. PiperOrigin-RevId: 621913271 Change-Id: If0620fdaca6f292a96150faa34f1b3145ec54a23 --- .../google/devtools/build/lib/analysis/BUILD | 6 + .../devtools/build/lib/analysis/Project.java | 37 +++++ .../lib/analysis/config/CoreOptions.java | 11 ++ .../lib/buildtool/AnalysisPhaseRunner.java | 42 +++-- .../build/lib/buildtool/BuildTool.java | 54 +++++- .../build/lib/skyframe/SkyFunctions.java | 2 + .../build/lib/skyframe/SkyframeExecutor.java | 3 + .../devtools/build/lib/skyframe/config/BUILD | 3 + .../lib/skyframe/config/FlagSetFunction.java | 157 ++++++++++++++++++ .../lib/skyframe/config/FlagSetValue.java | 105 ++++++++++++ .../skyframe/config/FlagSetsFunctionTest.java | 115 +++++++++++++ 11 files changed, 515 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/skyframe/config/FlagSetFunction.java create mode 100644 src/main/java/com/google/devtools/build/lib/skyframe/config/FlagSetValue.java create mode 100644 src/test/java/com/google/devtools/build/lib/skyframe/config/FlagSetsFunctionTest.java diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD index daeb9fe01288bb..d4ddc627c2d5e4 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD +++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD @@ -602,13 +602,19 @@ java_library( name = "projects", srcs = ["Project.java"], deps = [ + ":config/build_options", + ":config/core_options", + ":config/invalid_configuration_exception", "//src/main/java/com/google/devtools/build/lib/cmdline", "//src/main/java/com/google/devtools/build/lib/events", "//src/main/java/com/google/devtools/build/lib/skyframe:containing_package_lookup_value", "//src/main/java/com/google/devtools/build/lib/skyframe:package_lookup_function", "//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster", + "//src/main/java/com/google/devtools/build/lib/skyframe/config", "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment", + "//src/main/java/com/google/devtools/build/skyframe", "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects", + "//src/main/protobuf:failure_details_java_proto", "//third_party:guava", ], ) diff --git a/src/main/java/com/google/devtools/build/lib/analysis/Project.java b/src/main/java/com/google/devtools/build/lib/analysis/Project.java index 19fecbf424d11a..f7579f4cc92e16 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/Project.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/Project.java @@ -24,14 +24,21 @@ import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.analysis.config.CoreOptions; +import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.events.ExtendedEventHandler; +import com.google.devtools.build.lib.server.FailureDetails.BuildConfiguration.Code; import com.google.devtools.build.lib.skyframe.ContainingPackageLookupValue; import com.google.devtools.build.lib.skyframe.PackageLookupFunction; import com.google.devtools.build.lib.skyframe.SkyframeExecutor; +import com.google.devtools.build.lib.skyframe.config.FlagSetValue; import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.skyframe.EvaluationResult; import com.google.devtools.build.skyframe.SkyKey; +import com.google.devtools.build.skyframe.SkyValue; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -180,4 +187,34 @@ public static ImmutableMultimap findProjectFiles( } return ans.build(); } + + /** + * applies {@link CoreOptions.sclConfig} to the top-level {@link BuildOptions} + * + *

given an existing PROJECT.scl file and an {@link CoreOptions.sclConfig}, the method creates + * a {@link SkyKey} containing the {@link PathFragment} of the scl file and the config name which + * is evaluated by the {@link FlagSetFunction} + * + * @return {@link FlagSetValue} which has the effective top-level {@link BuildOptions} after + * project file resolution. + */ + public static FlagSetValue modifyBuildOptionsWithFlagSets( + PathFragment projectFile, + BuildOptions targetOptions, + ExtendedEventHandler eventHandler, + SkyframeExecutor skyframeExecutor) + throws InvalidConfigurationException { + + FlagSetValue.Key flagSetKey = + FlagSetValue.Key.create( + projectFile, targetOptions.get(CoreOptions.class).sclConfig, targetOptions); + + EvaluationResult result = + skyframeExecutor.evaluateSkyKeys( + eventHandler, ImmutableList.of(flagSetKey), /* keepGoing= */ false); + if (result.hasError()) { + throw new InvalidConfigurationException("Cannot parse options", Code.INVALID_BUILD_OPTIONS); + } + return (FlagSetValue) result.get(flagSetKey); + } } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptions.java index 2dc55914ac06b3..a906c5528e2a20 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptions.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptions.java @@ -66,6 +66,17 @@ public class CoreOptions extends FragmentOptions implements Cloneable { public static final OptionDefinition CPU = OptionsParser.getOptionDefinitionByName(CoreOptions.class, "cpu"); + @Option( + name = "scl_config", + defaultValue = "null", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + metadataTags = {OptionMetadataTag.EXPERIMENTAL}, + help = + "Name of the scl config defined in PROJECT.scl. Note that this feature is still under" + + " development b/324119879.") + public String sclConfig; + @Option( name = "incompatible_merge_genfiles_directory", defaultValue = "true", diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/AnalysisPhaseRunner.java b/src/main/java/com/google/devtools/build/lib/buildtool/AnalysisPhaseRunner.java index 1337b0b4b7e53d..39b163b2b3f70a 100644 --- a/src/main/java/com/google/devtools/build/lib/buildtool/AnalysisPhaseRunner.java +++ b/src/main/java/com/google/devtools/build/lib/buildtool/AnalysisPhaseRunner.java @@ -83,11 +83,16 @@ private AnalysisPhaseRunner() {} public static AnalysisResult execute( CommandEnvironment env, BuildRequest request, - BuildOptions buildOptions, + BuildOptions buildOptionsBeforeFlagSets, TargetValidator validator) - throws BuildFailedException, InterruptedException, ViewCreationFailedException, - TargetParsingException, LoadingFailedException, AbruptExitException, - InvalidConfigurationException, RepositoryMappingResolutionException { + throws BuildFailedException, + InterruptedException, + ViewCreationFailedException, + TargetParsingException, + LoadingFailedException, + AbruptExitException, + InvalidConfigurationException, + RepositoryMappingResolutionException { // Target pattern evaluation. TargetPatternPhaseValue loadingResult; @@ -97,10 +102,25 @@ public static AnalysisResult execute( } env.setWorkspaceName(loadingResult.getWorkspaceName()); - // TODO: b/324127375 - Use with incoming --scl_config flag. - PathFragment unusedProjectFile = - BuildTool.getProjectFile( - loadingResult.getTargetLabels(), env.getSkyframeExecutor(), env.getReporter()); + BuildOptions postFlagsetsBuildOptions; + String sclConfig = buildOptionsBeforeFlagSets.get(CoreOptions.class).sclConfig; + if (sclConfig != null && !sclConfig.isEmpty()) { + PathFragment projectFile = + BuildTool.getProjectFile( + loadingResult.getTargetLabels(), env.getSkyframeExecutor(), env.getReporter()); + if (projectFile != null) { + postFlagsetsBuildOptions = + BuildTool.applySclConfigs( + buildOptionsBeforeFlagSets, + projectFile, + env.getSkyframeExecutor(), + env.getReporter()); + } else { + postFlagsetsBuildOptions = buildOptionsBeforeFlagSets; + } + } else { + postFlagsetsBuildOptions = buildOptionsBeforeFlagSets; + } // Compute the heuristic instrumentation filter if needed. if (request.needsInstrumentationFilter()) { @@ -115,7 +135,7 @@ public static AnalysisResult execute( // We're modifying the buildOptions in place, which is not ideal, but we also don't want // to pay the price for making a copy. Maybe reconsider later if this turns out to be a // problem (and the performance loss may not be a big deal). - buildOptions.get(CoreOptions.class).instrumentationFilter = + postFlagsetsBuildOptions.get(CoreOptions.class).instrumentationFilter = new RegexFilter.RegexFilterConverter().convert(instrumentationFilter); } catch (OptionsParsingException e) { throw new InvalidConfigurationException(Code.HEURISTIC_INSTRUMENTATION_FILTER_INVALID, e); @@ -131,11 +151,11 @@ public static AnalysisResult execute( Profiler.instance().markPhase(ProfilePhase.ANALYZE); try (SilentCloseable c = Profiler.instance().profile("runAnalysisPhase")) { - analysisResult = runAnalysisPhase(env, request, loadingResult, buildOptions); + analysisResult = runAnalysisPhase(env, request, loadingResult, postFlagsetsBuildOptions); } for (BlazeModule module : env.getRuntime().getBlazeModules()) { - module.afterAnalysis(env, request, buildOptions, analysisResult); + module.afterAnalysis(env, request, postFlagsetsBuildOptions, analysisResult); } if (request.shouldRunTests()) { diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java index f05159be80fa39..066be9bda82bf6 100644 --- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java +++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java @@ -38,6 +38,7 @@ import com.google.devtools.build.lib.analysis.ViewCreationFailedException; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionException; import com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.analysis.config.CoreOptions; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.buildeventstream.BuildEvent.LocalFile.LocalFileType; import com.google.devtools.build.lib.buildeventstream.BuildEventArtifactUploader.UploadContext; @@ -77,6 +78,7 @@ import com.google.devtools.build.lib.skyframe.actiongraph.v2.AqueryOutputHandler; import com.google.devtools.build.lib.skyframe.actiongraph.v2.AqueryOutputHandler.OutputType; import com.google.devtools.build.lib.skyframe.actiongraph.v2.InvalidAqueryOutputFormatException; +import com.google.devtools.build.lib.skyframe.config.FlagSetValue; import com.google.devtools.build.lib.util.AbruptExitException; import com.google.devtools.build.lib.util.CrashFailureDetails; import com.google.devtools.build.lib.util.DetailedExitCode; @@ -292,10 +294,16 @@ private void buildTargetsWithMergedAnalysisExecution( BuildRequest request, BuildResult result, TargetValidator validator, - BuildOptions buildOptions) - throws InterruptedException, TargetParsingException, LoadingFailedException, - AbruptExitException, ViewCreationFailedException, BuildFailedException, TestExecException, - InvalidConfigurationException, RepositoryMappingResolutionException { + BuildOptions buildOptionsBeforeFlagSets) + throws InterruptedException, + TargetParsingException, + LoadingFailedException, + AbruptExitException, + ViewCreationFailedException, + BuildFailedException, + TestExecException, + InvalidConfigurationException, + RepositoryMappingResolutionException { // Target pattern evaluation. TargetPatternPhaseValue loadingResult; Profiler.instance().markPhase(ProfilePhase.TARGET_PATTERN_EVAL); @@ -305,9 +313,21 @@ private void buildTargetsWithMergedAnalysisExecution( } env.setWorkspaceName(loadingResult.getWorkspaceName()); - // TODO: b/324127375 - Use with incoming --scl_config flag. - PathFragment unusedProjectFile = - getProjectFile(loadingResult.getTargetLabels(), env.getSkyframeExecutor(), getReporter()); + BuildOptions postFlagSetsBuildOptions; + String sclConfig = buildOptionsBeforeFlagSets.get(CoreOptions.class).sclConfig; + if (sclConfig != null && !sclConfig.isEmpty()) { + PathFragment projectFile = + getProjectFile(loadingResult.getTargetLabels(), env.getSkyframeExecutor(), getReporter()); + if (projectFile != null) { + postFlagSetsBuildOptions = + applySclConfigs( + buildOptionsBeforeFlagSets, projectFile, env.getSkyframeExecutor(), getReporter()); + } else { + postFlagSetsBuildOptions = buildOptionsBeforeFlagSets; + } + } else { + postFlagSetsBuildOptions = buildOptionsBeforeFlagSets; + } // See https://github.com/bazelbuild/rules_nodejs/issues/3693. env.getSkyframeExecutor().clearSyscallCache(); @@ -326,7 +346,7 @@ private void buildTargetsWithMergedAnalysisExecution( AnalysisAndExecutionPhaseRunner.execute( env, request, - buildOptions, + postFlagSetsBuildOptions, loadingResult, () -> executionTool.prepareForExecution(executionTimer), result::setBuildConfiguration, @@ -461,7 +481,7 @@ private void dumpSkyframeStateAfterBuild( /* includeSchedulingDependencies= */ true, /* actionFilters= */ null, /* includeParamFiles= */ false, - /* includeFileWriteContents */ false, + /* includeFileWriteContents= */ false, aqueryOutputHandler, getReporter()); AqueryProcessor.dumpActionGraph(env, aqueryOutputHandler, actionGraphDump); @@ -809,6 +829,22 @@ static PathFragment getProjectFile( } } + /** Creates a BuildOptions class for the given options taken from an {@link OptionsProvider}. */ + public static BuildOptions applySclConfigs( + BuildOptions buildOptionsBeforeFlagSets, + PathFragment projectFile, + SkyframeExecutor skyframeExecutor, + ExtendedEventHandler eventHandler) + throws InvalidConfigurationException { + + FlagSetValue flagSetValue = + Project.modifyBuildOptionsWithFlagSets( + projectFile, buildOptionsBeforeFlagSets, eventHandler, skyframeExecutor); + + // BuildOptions after Flagsets + return flagSetValue.getTopLevelBuildOptions(); + } + private Reporter getReporter() { return env.getReporter(); } 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 1d7152f0930329..790bbe4390f2b7 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 @@ -167,6 +167,8 @@ public final class SkyFunctions { public static final SkyFunctionName MODULE_EXTENSION_REPO_MAPPING_ENTRIES = SkyFunctionName.createHermetic("MODULE_EXTENSION_REPO_MAPPING_ENTRIES"); + public static final SkyFunctionName FLAG_SET = SkyFunctionName.createHermetic("FLAG_SET"); + public static Predicate isSkyFunction(SkyFunctionName functionName) { return key -> key.functionName().equals(functionName); } diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java index 13ef47086db145..9c1c72ae439ef3 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java @@ -197,6 +197,7 @@ import com.google.devtools.build.lib.skyframe.config.BuildConfigurationKey; import com.google.devtools.build.lib.skyframe.config.BuildConfigurationKeyFunction; import com.google.devtools.build.lib.skyframe.config.BuildConfigurationKeyValue; +import com.google.devtools.build.lib.skyframe.config.FlagSetFunction; import com.google.devtools.build.lib.skyframe.config.NativeAndStarlarkFlags; import com.google.devtools.build.lib.skyframe.config.ParsedFlagsFunction; import com.google.devtools.build.lib.skyframe.config.ParsedFlagsValue; @@ -766,6 +767,8 @@ public SkyValue compute(SkyKey skyKey, Environment env) { new ArtifactNestedSetFunction(this::getConsumedArtifactsTracker)); BuildDriverFunction buildDriverFunction = newBuildDriverFunction(); map.put(SkyFunctions.BUILD_DRIVER, buildDriverFunction); + FlagSetFunction flagSetFunction = new FlagSetFunction(); + map.put(SkyFunctions.FLAG_SET, flagSetFunction); this.buildDriverFunction = buildDriverFunction; map.putAll(extraSkyFunctions); diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/config/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/config/BUILD index ce3af3009b8565..99f807a3373b67 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/config/BUILD +++ b/src/main/java/com/google/devtools/build/lib/skyframe/config/BUILD @@ -17,6 +17,7 @@ java_library( "BaselineOptionsFunction.java", "BuildConfigurationFunction.java", "BuildConfigurationKeyFunction.java", + "FlagSetFunction.java", "ParsedFlagsFunction.java", "PlatformMappingFunction.java", ], @@ -43,6 +44,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/analysis:platform_options", "//src/main/java/com/google/devtools/build/lib/analysis/producers:build_configuration_key_producer", "//src/main/java/com/google/devtools/build/lib/cmdline", + "//src/main/java/com/google/devtools/build/lib/events", "//src/main/java/com/google/devtools/build/lib/packages", "//src/main/java/com/google/devtools/build/lib/packages/semantics", "//src/main/java/com/google/devtools/build/lib/pkgcache", @@ -84,6 +86,7 @@ java_library( srcs = [ "BuildConfigurationKey.java", "BuildConfigurationKeyValue.java", + "FlagSetValue.java", "NativeAndStarlarkFlags.java", "ParsedFlagsValue.java", "PlatformMappingValue.java", diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/config/FlagSetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/config/FlagSetFunction.java new file mode 100644 index 00000000000000..5a7d1478e7f929 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/config/FlagSetFunction.java @@ -0,0 +1,157 @@ +// 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 com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.analysis.config.BuildOptionsView; +import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.Label.RepoContext; +import com.google.devtools.build.lib.cmdline.LabelSyntaxException; +import com.google.devtools.build.lib.cmdline.RepositoryName; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.skyframe.BzlLoadFailedException; +import com.google.devtools.build.lib.skyframe.BzlLoadValue; +import com.google.devtools.build.lib.skyframe.RepositoryMappingValue; +import com.google.devtools.build.lib.skyframe.config.ParsedFlagsFunction.ParsedFlagsFunctionException; +import com.google.devtools.build.skyframe.SkyFunction; +import com.google.devtools.build.skyframe.SkyFunctionException; +import com.google.devtools.build.skyframe.SkyFunctionException.Transience; +import com.google.devtools.build.skyframe.SkyKey; +import com.google.devtools.build.skyframe.SkyValue; +import com.google.devtools.common.options.OptionsParsingException; +import com.google.devtools.common.options.OptionsParsingResult; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nullable; + +/** + * A SkyFunction that, given an scl file path and the name of scl configs, does the following: 1) + * call {@link BzlLoadFunction} to load the content of scl files given the provided scl config name + * 2) call {@link ParsedFlagsFunction} to parse the list of options 3) define a patch transition and + * applies the transition to the targetOptions which was used for creating topLevel configuration. + * + *

If given an unknown {@link CoreOptions.sclConfig}, {@link FlagSetFunction} will return the + * original {@link BuildOptions} and will not error out. + */ +public class FlagSetFunction implements SkyFunction { + + @Override + @SuppressWarnings("unchecked") + @Nullable + public SkyValue compute(SkyKey skyKey, Environment env) + throws FlagSetFunctionException, ParsedFlagsFunctionException, InterruptedException { + FlagSetValue.Key key = (FlagSetValue.Key) skyKey.argument(); + + if (key.getProjectFile() == null || key.getSclConfig().isEmpty()) { + return FlagSetValue.create(key.getTargetOptions()); + } + + String parentDirectoryString = key.getProjectFile().getParentDirectory().getPathString(); + String baseName = key.getProjectFile().getBaseName(); + String projectFileLabelString = parentDirectoryString + ":" + baseName; + + BzlLoadValue sclLoadValue = loadSclFile(projectFileLabelString, env); + + if (sclLoadValue == null) { + return null; + } + + RepositoryMappingValue mainRepositoryMappingValue = + (RepositoryMappingValue) env.getValue(RepositoryMappingValue.key(RepositoryName.MAIN)); + if (mainRepositoryMappingValue == null) { + return null; + } + + RepoContext mainRepoContext = + RepoContext.of(RepositoryName.MAIN, mainRepositoryMappingValue.getRepositoryMapping()); + + List rawFlags = new ArrayList<>(); + if (sclLoadValue.getModule().getGlobal(key.getSclConfig()) != null) { + rawFlags.addAll( + (Collection) sclLoadValue.getModule().getGlobal(key.getSclConfig())); + } else { + return FlagSetValue.create(key.getTargetOptions()); + } + + ParsedFlagsValue parsedFlagsValue; + try { + parsedFlagsValue = + (ParsedFlagsValue) + env.getValueOrThrow( + ParsedFlagsValue.Key.create( + ImmutableList.copyOf(rawFlags), mainRepoContext.rootPackage()), + ParsedFlagsFunctionException.class); + } catch (ParsedFlagsFunctionException e) { + throw new FlagSetFunctionException(e, Transience.PERSISTENT); + } + + if (parsedFlagsValue == null) { + return null; + } + + BuildOptions adjustedBuildOptions; + try { + OptionsParsingResult optionsParsingResult = parsedFlagsValue.flags().parse(); + FlagSetTransition transition = new FlagSetTransition(optionsParsingResult); + BuildOptionsView buildOptionsView = + new BuildOptionsView(key.getTargetOptions(), parsedFlagsValue.flags().optionsClasses()); + adjustedBuildOptions = + Iterables.getOnlyElement(transition.apply(buildOptionsView, env.getListener()).values()); + } catch (OptionsParsingException e) { + throw new FlagSetFunctionException(e, Transience.PERSISTENT); + } + + return FlagSetValue.create(adjustedBuildOptions); + } + + private BzlLoadValue loadSclFile(String sclFile, Environment env) + throws FlagSetFunctionException, InterruptedException { + BzlLoadValue bzlLoadValue; + try { + Label sclFileLabel = Label.parseCanonical(sclFile); + bzlLoadValue = + (BzlLoadValue) + env.getValueOrThrow( + BzlLoadValue.keyForBuild(sclFileLabel), BzlLoadFailedException.class); + } catch (BzlLoadFailedException | LabelSyntaxException e) { + throw new FlagSetFunctionException(e, Transience.PERSISTENT); + } + return bzlLoadValue; + } + + private static final class FlagSetFunctionException extends SkyFunctionException { + FlagSetFunctionException(Exception cause, Transience transience) { + super(cause, transience); + } + } + + /** Transition that applies the config defines in PROJECT.scl to existing buildOptions */ + private static class FlagSetTransition implements PatchTransition { + public final OptionsParsingResult parsingResult; + + public FlagSetTransition(OptionsParsingResult parsingResult) { + this.parsingResult = parsingResult; + } + + @Override + public BuildOptions patch(BuildOptionsView originalOptions, EventHandler eventHandler) { + BuildOptions toOptions = originalOptions.underlying().clone(); + return toOptions.applyParsingResult(parsingResult); + } + } +} diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/config/FlagSetValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/config/FlagSetValue.java new file mode 100644 index 00000000000000..4a3d00234d3f94 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/config/FlagSetValue.java @@ -0,0 +1,105 @@ +// 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 com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.concurrent.ThreadSafety; +import com.google.devtools.build.lib.skyframe.SkyFunctions; +import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.skyframe.SkyFunctionName; +import com.google.devtools.build.skyframe.SkyKey; +import com.google.devtools.build.skyframe.SkyValue; +import java.util.Objects; + +/** A return value of {@link FlagSetFunction} */ +public class FlagSetValue implements SkyValue { + + private final BuildOptions topLevelBuildOptions; + + /** Key for {@link FlagSetValue} based on the raw flags. */ + @ThreadSafety.Immutable + @AutoCodec + public static final class Key implements SkyKey { + private static final SkyKeyInterner interner = SkyKey.newInterner(); + // private final String sclFile; + private final PathFragment projectFile; + private final String sclConfig; + private final BuildOptions targetOptions; + + public Key(PathFragment projectFile, String sclConfig, BuildOptions targetOptions) { + this.projectFile = projectFile; + this.sclConfig = sclConfig; + this.targetOptions = targetOptions; + } + + public static Key create( + PathFragment projectFile, String sclConfig, BuildOptions targetOptions) { + return interner.intern(new Key(projectFile, sclConfig, targetOptions)); + } + + public PathFragment getProjectFile() { + return projectFile; + } + + public String getSclConfig() { + return sclConfig; + } + + public BuildOptions getTargetOptions() { + return targetOptions; + } + + @Override + public SkyKeyInterner getSkyKeyInterner() { + return interner; + } + + @Override + public SkyFunctionName functionName() { + return SkyFunctions.FLAG_SET; + } + + @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(projectFile, key.projectFile) + && Objects.equals(sclConfig, key.sclConfig) + && Objects.equals(targetOptions, key.targetOptions); + } + + @Override + public int hashCode() { + return Objects.hash(projectFile, sclConfig, targetOptions); + } + } + + public static FlagSetValue create(BuildOptions buildOptions) { + return new FlagSetValue(buildOptions); + } + + public FlagSetValue(BuildOptions buildOptions) { + this.topLevelBuildOptions = buildOptions; + } + + public BuildOptions getTopLevelBuildOptions() { + return topLevelBuildOptions; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/config/FlagSetsFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/config/FlagSetsFunctionTest.java new file mode 100644 index 00000000000000..4d5f44892fb55a --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/skyframe/config/FlagSetsFunctionTest.java @@ -0,0 +1,115 @@ +// 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.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; +import com.google.devtools.build.lib.analysis.PlatformOptions; +import com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; +import com.google.devtools.build.lib.analysis.util.DummyTestFragment; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction; +import com.google.devtools.build.lib.skyframe.PrecomputedValue; +import com.google.devtools.build.lib.skyframe.SkyframeExecutor; +import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils; +import com.google.devtools.build.lib.testutil.TestRuleClassProvider; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.skyframe.EvaluationResult; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class FlagSetsFunctionTest extends BuildViewTestCase { + + @Override + protected ConfiguredRuleClassProvider createRuleClassProvider() { + ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder(); + TestRuleClassProvider.addStandardRules(builder); + builder.addConfigurationFragment(DummyTestFragment.class); + return builder.build(); + } + + @Test + public void flagSetsFunction_returns_modified_buildOptions() throws Exception { + rewriteWorkspace("workspace(name = 'my_workspace')"); + scratch.file( + "test/PROJECT.scl", "test_config = ['--platforms=//buildenv/platforms/android:x86']"); + scratch.file("test/BUILD"); + setBuildLanguageOptions("--experimental_enable_scl_dialect=true"); + // given original BuildOptions and a valid key + BuildOptions buildOptions = + BuildOptions.getDefaultBuildOptionsForFragments( + ruleClassProvider.getFragmentRegistry().getOptionsClasses()); + PathFragment projectFile = PathFragment.createAlreadyNormalized("//test/PROJECT.scl"); + FlagSetValue.Key key = FlagSetValue.Key.create(projectFile, "test_config", buildOptions); + FlagSetValue flagSetsValue = executeFunction(key); + + // expects the modified BuildOptions + assertThat(flagSetsValue.getTopLevelBuildOptions().get(PlatformOptions.class).platforms) + .containsExactly(Label.parseCanonical("//buildenv/platforms/android:x86")); + } + + @Test + public void given_unknown_sclConfig_flagSetsFunction_returns_original_buildOptions() + throws Exception { + rewriteWorkspace("workspace(name = 'my_workspace')"); + scratch.file( + "test/PROJECT.scl", "test_config = ['--platforms=//buildenv/platforms/android:x86']"); + scratch.file("test/BUILD"); + setBuildLanguageOptions("--experimental_enable_scl_dialect=true"); + // given valid project file but a nonexistent scl config + BuildOptions buildOptions = + BuildOptions.getDefaultBuildOptionsForFragments( + ruleClassProvider.getFragmentRegistry().getOptionsClasses()); + PathFragment projectFile = PathFragment.createAlreadyNormalized("//test/PROJECT.scl"); + FlagSetValue.Key key = FlagSetValue.Key.create(projectFile, "unknown_config", buildOptions); + FlagSetValue flagSetsValue = executeFunction(key); + + // expects the original BuildOptions + assertThat(flagSetsValue.getTopLevelBuildOptions()).isEqualTo(buildOptions); + } + + @Test + public void flagSetsFunction_returns_origional_buildOptions() throws Exception { + // given original BuildOptions and an empty scl config name + BuildOptions buildOptions = + BuildOptions.getDefaultBuildOptionsForFragments( + ruleClassProvider.getFragmentRegistry().getOptionsClasses()); + PathFragment projectFile = PathFragment.create("test/PROJECT.scl"); + FlagSetValue.Key key = FlagSetValue.Key.create(projectFile, "", buildOptions); + FlagSetValue flagSetsValue = executeFunction(key); + + // expects the original BuildOptions + assertThat(flagSetsValue.getTopLevelBuildOptions()).isEqualTo(buildOptions); + } + + private FlagSetValue executeFunction(FlagSetValue.Key key) throws Exception { + SkyframeExecutor skyframeExecutor = getSkyframeExecutor(); + EvaluationResult result = + SkyframeExecutorTestUtils.evaluate(skyframeExecutor, key, /* keepGoing= */ false, reporter); + skyframeExecutor.injectExtraPrecomputedValues( + ImmutableList.of( + PrecomputedValue.injected( + RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE, Optional.empty()))); + if (result.hasError()) { + throw result.getError(key).getException(); + } + return result.get(key); + } +}