| // 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. |
| } |
| } |