blaze config: include "fragment -> [fragment options] map".
This is necessary to link options to the fragment(s) that contain them.
For example, if two configurations have a different --linkopt value, this belongs to the CppOptions FragmentOptions, which is included with the CppConfiguration fragment.
Serves https://github.com/bazelbuild/bazel/issues/10613.
Example output:
$ blaze config <config hash>
INFO: Displaying config with id cc19b5007
BuildConfiguration cc19b5007
Skyframe Key: BuildConfigurationValue.Key[cc19b5007]
Fragments: com.google.devtools.build.lib.analysis.PlatformConfiguration: [com.google.devtools.build.lib.analysis.PlatformOptions], com.google.devtools.build.lib.analysis.ShellConfiguration:[com.google.devtools.build.lib.analysis.ShellConfiguration$Options], ...
FragmentOptions com.google.devtools.build.lib.analysis.PlatformOptions {
enabled_toolchain_types: []
experimental_add_exec_constraints_to_targets: []
extra_toolchains: []
incompatible_auto_configure_host_platform: false
incompatible_use_toolchain_resolution_for_java_rules: true
platform_mappings: tools/platform_mappings
...
PiperOrigin-RevId: 301158932
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/ConfigCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/ConfigCommand.java
index 3365bc7..355ec3b 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/ConfigCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/ConfigCommand.java
@@ -16,6 +16,7 @@
import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
import com.google.common.base.Verify;
import com.google.common.collect.HashBasedTable;
@@ -25,7 +26,10 @@
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.cmdline.Label;
@@ -58,7 +62,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.stream.Collectors;
import javax.annotation.Nullable;
/** Handles the 'config' command on the Blaze command line. */
@@ -108,7 +111,7 @@
}
/**
- * Data structure defining a {@link BuildConfiguration} from the point of this command's output.
+ * Data structure defining a {@link BuildConfiguration} for the purpose of this command's output.
*
* <p>Includes all data representing a "configuration" and defines their relative structure and
* list order.
@@ -120,11 +123,17 @@
final String skyKey;
final String configHash;
final List<FragmentForOutput> fragments;
+ final List<FragmentOptionsForOutput> fragmentOptions;
- ConfigurationForOutput(String skyKey, String configHash, List<FragmentForOutput> fragments) {
+ ConfigurationForOutput(
+ String skyKey,
+ String configHash,
+ List<FragmentForOutput> fragments,
+ List<FragmentOptionsForOutput> fragmentOptions) {
this.skyKey = skyKey;
this.configHash = configHash;
this.fragments = fragments;
+ this.fragmentOptions = fragmentOptions;
}
@Override
@@ -133,35 +142,72 @@
ConfigurationForOutput other = (ConfigurationForOutput) o;
return other.skyKey.equals(skyKey)
&& other.configHash.equals(configHash)
- && other.fragments.equals(fragments);
+ && other.fragments.equals(fragments)
+ && other.fragmentOptions.equals(fragmentOptions);
}
return false;
}
@Override
public int hashCode() {
- return Objects.hash(skyKey, configHash, fragments);
+ return Objects.hash(skyKey, configHash, fragments, fragmentOptions);
}
}
/**
- * Data structure defining a {@link FragmentOptions} from the point of this command's output.
+ * Data structure defining a {@link Fragment} for the purpose of this command's output.
*
- * <p>See {@link ConfigurationForOutput} for further details.
+ * <p>{@link Fragment} is a Java object representation of a domain-specific "piece" of
+ * configuration (like "C++-related configuration"). It depends on one or more {@link
+ * FragmentOptions}, which are the <code>--flag=value</code> pairs that key configurations.
+ *
+ * <p>See {@link FragmentOptionsForOutput} and {@link ConfigurationForOutput} for further details.
*/
protected static class FragmentForOutput {
final String name;
- final Map<String, String> options;
+ // We store the name of the associated FragmentOptions instead of FragmentOptionsForOutput
+ // objects because multiple fragments may use the same FragmentOptions and we don't want to list
+ // it multiple times.
+ final List<String> fragmentOptions;
- FragmentForOutput(String name, Map<String, String> options) {
+ FragmentForOutput(String name, List<String> fragmentOptions) {
this.name = name;
- this.options = options;
+ this.fragmentOptions = fragmentOptions;
}
@Override
public boolean equals(Object o) {
if (o instanceof FragmentForOutput) {
FragmentForOutput other = (FragmentForOutput) o;
+ return other.name.equals(name) && other.fragmentOptions.equals(fragmentOptions);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, fragmentOptions);
+ }
+ }
+
+ /**
+ * Data structure defining a {@link FragmentOptions} from the point of this command's output.
+ *
+ * <p>See {@link FragmentForOutput} and {@link ConfigurationForOutput} for further details.
+ */
+ protected static class FragmentOptionsForOutput {
+ final String name;
+ final Map<String, String> options;
+
+ FragmentOptionsForOutput(String name, Map<String, String> options) {
+ this.name = name;
+ this.options = options;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof FragmentOptionsForOutput) {
+ FragmentOptionsForOutput other = (FragmentOptionsForOutput) o;
return other.name.equals(name) && other.options.equals(options);
}
return false;
@@ -210,7 +256,7 @@
/**
* Data structure defining the difference between two {@link BuildConfiguration}s for a given
- * {@link FragmentOptions }from the point of this command's output.
+ * {@link FragmentOptions}from the point of this command's output.
*
* <p>See {@link ConfigurationForOutput} for further details.
*/
@@ -258,17 +304,20 @@
configCommandOptions.outputType == OutputType.TEXT
? new TextOutputFormatter(writer)
: new JsonOutputFormatter(writer);
+ ImmutableSortedMap<
+ Class<? extends Fragment>, ImmutableSortedSet<Class<? extends FragmentOptions>>>
+ fragmentDefs = getFragmentDefs(env.getRuntime().getRuleClassProvider());
if (options.getResidue().isEmpty()) {
if (configCommandOptions.dumpAll) {
- return reportAllConfigurations(outputFormatter, forOutput(configurations));
+ return reportAllConfigurations(outputFormatter, forOutput(configurations, fragmentDefs));
} else {
- return reportConfigurationIds(outputFormatter, forOutput(configurations));
+ return reportConfigurationIds(outputFormatter, forOutput(configurations, fragmentDefs));
}
} else if (options.getResidue().size() == 1) {
String configHash = options.getResidue().get(0);
return reportSingleConfiguration(
- outputFormatter, env, forOutput(configurations), configHash);
+ outputFormatter, env, forOutput(configurations, fragmentDefs), configHash);
} else if (options.getResidue().size() == 2) {
String configHash1 = options.getResidue().get(0);
String configHash2 = options.getResidue().get(1);
@@ -300,41 +349,98 @@
}
/**
+ * Returns the {@link Fragment}s and the {@link FragmentOptions} they require from Blaze's
+ * runtime.
+ *
+ * <p>These are the fragments that Blaze "knows about", not necessarily the fragments in a {@link
+ * BuildConfiguration}. Trimming, in particular, strips fragments out of actual configurations.
+ * It's safe to assume untrimmed configuration have all fragments listed here.
+ */
+ private static ImmutableSortedMap<
+ Class<? extends Fragment>, ImmutableSortedSet<Class<? extends FragmentOptions>>>
+ getFragmentDefs(ConfiguredRuleClassProvider ruleClassProvider) {
+ ImmutableSortedMap.Builder<
+ Class<? extends Fragment>, ImmutableSortedSet<Class<? extends FragmentOptions>>>
+ fragments = ImmutableSortedMap.orderedBy((c1, c2) -> c1.getName().compareTo(c2.getName()));
+ for (ConfigurationFragmentFactory fragmentFactory :
+ ruleClassProvider.getConfigurationFragments()) {
+ fragments.put(
+ fragmentFactory.creates(),
+ ImmutableSortedSet.copyOf(
+ (c1, c2) -> c1.getName().compareTo(c2.getName()), fragmentFactory.requiredOptions()));
+ }
+ return fragments.build();
+ }
+
+ /**
* Converts {@link #findConfigurations}'s output into a list of {@link ConfigurationForOutput}
* instances.
*/
- private ImmutableSortedSet<ConfigurationForOutput> forOutput(
- ImmutableSortedMap<BuildConfigurationValue.Key, BuildConfiguration> asSkyKeyMap) {
+ private static ImmutableSortedSet<ConfigurationForOutput> forOutput(
+ ImmutableSortedMap<BuildConfigurationValue.Key, BuildConfiguration> asSkyKeyMap,
+ ImmutableSortedMap<
+ Class<? extends Fragment>, ImmutableSortedSet<Class<? extends FragmentOptions>>>
+ fragmentDefs) {
ImmutableSortedSet.Builder<ConfigurationForOutput> ans =
ImmutableSortedSet.orderedBy(comparing(e -> e.configHash));
for (Map.Entry<BuildConfigurationValue.Key, BuildConfiguration> entry :
asSkyKeyMap.entrySet()) {
BuildConfigurationValue.Key key = entry.getKey();
BuildConfiguration config = entry.getValue();
- ans.add(getConfigurationForOutput(key, config.checksum(), config));
+ ans.add(getConfigurationForOutput(key, config.checksum(), config, fragmentDefs));
}
return ans.build();
}
/** Constructs a {@link ConfigurationForOutput} from the given input daata. */
- ConfigurationForOutput getConfigurationForOutput(
- BuildConfigurationValue.Key skyKey, String configHash, BuildConfiguration config) {
+ private static ConfigurationForOutput getConfigurationForOutput(
+ BuildConfigurationValue.Key skyKey,
+ String configHash,
+ BuildConfiguration config,
+ ImmutableSortedMap<
+ Class<? extends Fragment>, ImmutableSortedSet<Class<? extends FragmentOptions>>>
+ fragmentDefs) {
+
ImmutableSortedSet.Builder<FragmentForOutput> fragments =
ImmutableSortedSet.orderedBy(comparing(e -> e.name));
+ for (Map.Entry<Class<? extends Fragment>, ImmutableSortedSet<Class<? extends FragmentOptions>>>
+ entry : fragmentDefs.entrySet()) {
+ fragments.add(
+ new FragmentForOutput(
+ entry.getKey().getName(),
+ entry.getValue().stream().map(clazz -> clazz.getName()).collect(toList())));
+ }
+ fragmentDefs.entrySet().stream()
+ .filter(entry -> config.hasFragment(entry.getKey()))
+ .forEach(
+ entry ->
+ fragments.add(
+ new FragmentForOutput(
+ entry.getKey().getName(),
+ entry.getValue().stream()
+ .map(clazz -> clazz.getName())
+ .collect(toList()))));
+
+ ImmutableSortedSet.Builder<FragmentOptionsForOutput> fragmentOptions =
+ ImmutableSortedSet.orderedBy(comparing(e -> e.name));
config.getOptions().getFragmentClasses().stream()
.map(optionsClass -> config.getOptions().get(optionsClass))
.forEach(
- fragmentOptions -> {
- fragments.add(
- new FragmentForOutput(
- fragmentOptions.getClass().getName(),
- getOrderedNativeOptions(fragmentOptions)));
+ fragmentOptionsInstance -> {
+ fragmentOptions.add(
+ new FragmentOptionsForOutput(
+ fragmentOptionsInstance.getClass().getName(),
+ getOrderedNativeOptions(fragmentOptionsInstance)));
});
- fragments.add(
- new FragmentForOutput(
+ fragmentOptions.add(
+ new FragmentOptionsForOutput(
UserDefinedFragment.DESCRIPTIVE_NAME, getOrderedUserDefinedOptions(config)));
+
return new ConfigurationForOutput(
- skyKey.toString(), configHash, ImmutableList.copyOf(fragments.build()));
+ skyKey.toString(),
+ configHash,
+ fragments.build().asList(),
+ fragmentOptions.build().asList());
}
/**
@@ -397,7 +503,7 @@
ConfigCommandOutputFormatter writer,
ImmutableSortedSet<ConfigurationForOutput> configurations) {
writer.writeConfigurationIDs(
- configurations.stream().map(config -> config.configHash).collect(Collectors.toList()));
+ configurations.stream().map(config -> config.configHash).collect(toList()));
return BlazeCommandResult.exitCode(ExitCode.SUCCESS);
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/ConfigCommandOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/ConfigCommandOutputFormatter.java
index 836e5c0..56ac467 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/ConfigCommandOutputFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/ConfigCommandOutputFormatter.java
@@ -18,6 +18,7 @@
import com.google.devtools.build.lib.runtime.commands.ConfigCommand.ConfigurationForOutput;
import com.google.devtools.build.lib.runtime.commands.ConfigCommand.FragmentDiffForOutput;
import com.google.devtools.build.lib.runtime.commands.ConfigCommand.FragmentForOutput;
+import com.google.devtools.build.lib.runtime.commands.ConfigCommand.FragmentOptionsForOutput;
import com.google.devtools.build.lib.util.Pair;
import com.google.gson.Gson;
import java.io.PrintWriter;
@@ -66,8 +67,19 @@
public void writeConfiguration(ConfigurationForOutput configuration) {
writer.println("BuildConfiguration " + configuration.configHash + ":");
writer.println("Skyframe Key: " + configuration.skyKey);
+
+ StringBuilder fragments = new StringBuilder();
for (FragmentForOutput fragment : configuration.fragments) {
- writer.println("Fragment " + fragment.name + " {");
+ fragments
+ .append(fragment.name)
+ .append(": [")
+ .append(String.join(",", fragment.fragmentOptions))
+ .append("], ");
+ }
+
+ writer.println("Fragments: " + fragments);
+ for (FragmentOptionsForOutput fragment : configuration.fragmentOptions) {
+ writer.println("FragmentOptions " + fragment.name + " {");
for (Map.Entry<String, String> optionSetting : fragment.options.entrySet()) {
writer.printf(" %s: %s\n", optionSetting.getKey(), optionSetting.getValue());
}
@@ -87,7 +99,7 @@
writer.printf(
"Displaying diff between configs %s and %s\n", diff.configHash1, diff.configHash2);
for (FragmentDiffForOutput fragmentDiff : diff.fragmentsDiff) {
- writer.println("Fragment " + fragmentDiff.name + " {");
+ writer.println("FragmentOptions " + fragmentDiff.name + " {");
for (Map.Entry<String, Pair<String, String>> optionDiff :
fragmentDiff.optionsDiff.entrySet()) {
writer.printf(
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/commands/ConfigCommandTest.java b/src/test/java/com/google/devtools/build/lib/runtime/commands/ConfigCommandTest.java
index b41ad28..a953f6d 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/commands/ConfigCommandTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/commands/ConfigCommandTest.java
@@ -19,6 +19,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase;
import com.google.devtools.build.lib.runtime.BlazeCommandDispatcher;
import com.google.devtools.build.lib.runtime.commands.ConfigCommand.ConfigurationDiffForOutput;
@@ -100,25 +101,25 @@
}
/**
- * Returns the given option's value for the given fragment of the given configuration.
+ * Returns the value of an option under a configuration's {@link FragmentOptions}.
*
* <p>Throws {@link NoSuchElementException} if it can't be found.
*/
private static String getOptionValue(
- ConfigurationForOutput config, String fragmentName, String optionName) {
+ ConfigurationForOutput config, String fragmentOptions, String optionName) {
List<String> ans =
- config.fragments.stream()
- .filter(fragment -> fragment.name.endsWith(fragmentName))
+ config.fragmentOptions.stream()
+ .filter(fragment -> fragment.name.endsWith(fragmentOptions))
.flatMap(fragment -> fragment.options.entrySet().stream())
.filter(setting -> setting.getKey().equals(optionName))
.map(entry -> entry.getValue())
.collect(Collectors.toList());
if (ans.size() > 1) {
throw new NoSuchElementException(
- String.format("Multple matches for fragment=%s, option=%s", fragmentName, optionName));
+ String.format("Multple matches for fragment=%s, option=%s", fragmentOptions, optionName));
} else if (ans.isEmpty()) {
throw new NoSuchElementException(
- String.format("No matches for fragment=%s, option=%s", fragmentName, optionName));
+ String.format("No matches for fragment=%s, option=%s", fragmentOptions, optionName));
}
return ans.get(0);
}
@@ -128,6 +129,16 @@
&& !Boolean.parseBoolean(getOptionValue(config, "CoreOptions", "is exec configuration"));
}
+ /** Converts {@code a.b.d} to {@code d}. * */
+ private static String getBaseName(String str) {
+ return str.substring(str.lastIndexOf(".") + 1);
+ }
+
+ /** Converts a list of {@code a.b.d} strings to {@code d} form. * */
+ private static List<String> getBaseNames(List<String> list) {
+ return list.stream().map(entry -> getBaseName(entry)).collect(Collectors.toList());
+ }
+
@Test
public void showConfigIds() throws Exception {
analyzeTarget();
@@ -157,12 +168,22 @@
// Verify the existence of a couple of expected fragments:
assertThat(
config.fragments.stream()
- .map(fragment -> fragment.name.substring(fragment.name.lastIndexOf(".") + 1))
+ .map(
+ fragment ->
+ Pair.of(getBaseName(fragment.name), getBaseNames(fragment.fragmentOptions)))
+ .collect(Collectors.toList()))
+ .containsAtLeast(
+ Pair.of("PlatformConfiguration", ImmutableList.of("PlatformOptions")),
+ Pair.of("TestConfiguration", ImmutableList.of("TestConfiguration$TestOptions")));
+ // Verify the existence of a couple of expected fragment options:
+ assertThat(
+ config.fragmentOptions.stream()
+ .map(fragment -> getBaseName(fragment.name))
.collect(Collectors.toList()))
.containsAtLeast("PlatformOptions", "CoreOptions", "user-defined");
// Verify the existence of a couple of expected option names:
assertThat(
- config.fragments.stream()
+ config.fragmentOptions.stream()
.filter(fragment -> fragment.name.endsWith("CoreOptions"))
.flatMap(fragment -> fragment.options.keySet().stream())
.collect(Collectors.toList()))
@@ -288,7 +309,7 @@
assertThat(getOptionValue(targetConfig, "user-defined", "--define:a")).isEqualTo("1");
assertThat(getOptionValue(targetConfig, "user-defined", "--define:b")).isEqualTo("2");
assertThat(
- targetConfig.fragments.stream()
+ targetConfig.fragmentOptions.stream()
.filter(fragment -> fragment.name.endsWith("CoreOptions"))
.flatMap(fragment -> fragment.options.keySet().stream())
.filter(name -> name.equals("define"))