blob: ee9b6f8131bdd9e4dfe7d45a470fe9a10e9b20d8 [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.analysis.config.output;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.FragmentClassSet;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.skyframe.config.BuildConfigurationKey;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedSet;
import javax.annotation.Nullable;
/**
* Data structure defining a {@link BuildConfigurationValue} for the purpose of returning user
* output about the configuration.
*
* <p>Includes all data representing a "configuration" and defines their relative structure and list
* order.
*
* <p>A {@link com.google.devtools.build.lib.runtime.commands.ConfigCommandOutputFormatter} uses
* this to lightly format output from a logically consistent core structure.
*/
public class ConfigurationForOutput {
private final String skyKey;
private final String configHash;
private final String mnemonic;
private final boolean isExec;
private final List<FragmentForOutput> fragments;
private final List<FragmentOptionsForOutput> fragmentOptions;
public ConfigurationForOutput(
String skyKey,
String configHash,
String mnemonic,
boolean isExec,
List<FragmentForOutput> fragments,
List<FragmentOptionsForOutput> fragmentOptions) {
this.skyKey = skyKey;
this.configHash = configHash;
this.mnemonic = mnemonic;
this.isExec = isExec;
this.fragments = fragments;
this.fragmentOptions = fragmentOptions;
}
public String getSkyKey() {
return skyKey;
}
public String getConfigHash() {
return configHash;
}
public String getMnemonic() {
return mnemonic;
}
public boolean isExec() {
return isExec;
}
public List<FragmentForOutput> getFragments() {
return fragments;
}
/**
* The union of {@link FragmentOptionsForOutput} used by the Fragments associated with this
* configuration, sorted by FragmentOptionsForOutput name.
*/
public List<FragmentOptionsForOutput> getFragmentOptions() {
return fragmentOptions;
}
@Nullable
public FragmentOptionsForOutput fragment(String fragmentName) {
return this.fragmentOptions.stream()
.filter(fo -> fo.getName().equals(fragmentName))
.findFirst()
.orElse(null);
}
public SortedSet<String> fragmentOptionNames() {
return this.fragmentOptions.stream()
.map(FragmentOptionsForOutput::getName)
.collect(toImmutableSortedSet(Ordering.natural()));
}
@Override
public boolean equals(Object o) {
if (o instanceof ConfigurationForOutput other) {
return other.skyKey.equals(skyKey)
&& other.configHash.equals(configHash)
&& other.fragments.equals(fragments)
&& other.fragmentOptions.equals(fragmentOptions);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(skyKey, configHash, fragments, fragmentOptions);
}
/** Constructs a {@link ConfigurationForOutput} from the given {@link BuildConfigurationValue}. */
public static ConfigurationForOutput getConfigurationForOutput(
BuildConfigurationValue buildConfigurationValue) {
ImmutableSortedMap<
Class<? extends Fragment>, ImmutableSortedSet<Class<? extends FragmentOptions>>>
fragmentDefs =
buildConfigurationValue.getFragments().keySet().stream()
.collect(
toImmutableSortedMap(
FragmentClassSet.LEXICAL_FRAGMENT_SORTER,
fragment -> fragment,
fragment ->
ImmutableSortedSet.copyOf(
comparing(Class::getName), Fragment.requiredOptions(fragment))));
return getConfigurationForOutput(
buildConfigurationValue.getKey(),
buildConfigurationValue.checksum(),
buildConfigurationValue,
fragmentDefs);
}
/** Constructs a {@link ConfigurationForOutput} from the given input data. */
public static ConfigurationForOutput getConfigurationForOutput(
BuildConfigurationKey skyKey,
String configHash,
BuildConfigurationValue config,
ImmutableSortedMap<
Class<? extends Fragment>, ImmutableSortedSet<Class<? extends FragmentOptions>>>
fragmentDefs) {
ImmutableSortedSet.Builder<FragmentForOutput> fragments =
ImmutableSortedSet.orderedBy(comparing(e -> e.getName()));
for (Map.Entry<Class<? extends Fragment>, ImmutableSortedSet<Class<? extends FragmentOptions>>>
entry : fragmentDefs.entrySet()) {
fragments.add(
new FragmentForOutput(
entry.getKey().getName(),
entry.getValue().stream().map(Class::getName).collect(toImmutableList())));
}
fragmentDefs.entrySet().stream()
.filter(entry -> config.hasFragment(entry.getKey()))
.forEach(
entry ->
fragments.add(
new FragmentForOutput(
entry.getKey().getName(),
entry.getValue().stream().map(Class::getName).collect(toList()))));
ImmutableSortedSet.Builder<FragmentOptionsForOutput> fragmentOptions =
ImmutableSortedSet.orderedBy(comparing(e -> e.getName()));
config.getOptions().getFragmentClasses().stream()
.map(optionsClass -> config.getOptions().get(optionsClass))
.forEach(
fragmentOptionsInstance ->
fragmentOptions.add(
new FragmentOptionsForOutput(
fragmentOptionsInstance.getClass().getName(),
getOrderedNativeOptions(fragmentOptionsInstance))));
fragmentOptions.add(
new FragmentOptionsForOutput(
UserDefinedFragment.DESCRIPTIVE_NAME, getOrderedUserDefinedOptions(config)));
return new ConfigurationForOutput(
skyKey.toString(),
configHash,
config.getMnemonic(),
config.isExecConfiguration(),
fragments.build().asList(),
fragmentOptions.build().asList());
}
/**
* Returns a {@link FragmentOptions}'s native option settings in canonical order.
*
* <p>While actual option values are objects, we serialize them to strings to prevent command
* output from interpreting them more deeply than we want for simple "name=value" output.
*/
private static ImmutableSortedMap<String, String> getOrderedNativeOptions(
FragmentOptions options) {
return options.asMap().entrySet().stream()
// While technically part of CoreOptions, --define is practically a user-definable flag so
// we include it in the user-defined fragment for clarity. See getOrderedUserDefinedOptions.
.filter(
entry ->
!(options.getClass().equals(CoreOptions.class) && entry.getKey().equals("define")))
.collect(
toImmutableSortedMap(
Ordering.natural(), Map.Entry::getKey, e -> String.valueOf(e.getValue())));
}
/**
* Returns a configuration's user-definable settings in canonical order.
*
* <p>While actual option values are objects, we serialize them to strings to prevent command
* output from interpreting them more deeply than we want for simple "name=value" output.
*/
private static ImmutableSortedMap<String, String> getOrderedUserDefinedOptions(
BuildConfigurationValue config) {
ImmutableSortedMap.Builder<String, String> ans = ImmutableSortedMap.naturalOrder();
// Starlark-defined options:
for (Map.Entry<Label, Object> entry : config.getOptions().getStarlarkOptions().entrySet()) {
ans.put(entry.getKey().toString(), String.valueOf(entry.getValue()));
}
// --define:
for (Map.Entry<String, String> entry :
config
.getOptions()
.get(CoreOptions.class)
.getNormalizedCommandLineBuildVariables()
.entrySet()) {
ans.put("--define:" + entry.getKey(), Verify.verifyNotNull(entry.getValue()));
}
return ans.buildOrThrow();
}
/**
* Starlark options don't have configuration fragments. This is just to keep their output
* consistent with native options, i.e. to include "user-defined" section in the output list.
*/
static class UserDefinedFragment extends FragmentOptions {
static final String DESCRIPTIVE_NAME = "user-defined";
// Intentionally empty: we read the actual options directly from BuildOptions.
}
}