static BuildConfiguration setupTransitions(
      BuildConfiguration targetConfiguration,
      BuildConfiguration dataConfiguration,
      BuildConfiguration hostConfiguration,
      ListMultimap<SplitTransition<?>, BuildConfiguration> splitTransitionsTable) {
    Set<BuildConfiguration> allConfigurations = new LinkedHashSet<>();
    allConfigurations.add(targetConfiguration);
    allConfigurations.add(dataConfiguration);
    allConfigurations.add(hostConfiguration);
    allConfigurations.addAll(splitTransitionsTable.values());

    Table<BuildConfiguration, Transition, ConfigurationHolder> transitionBuilder =
        HashBasedTable.create();
    for (BuildConfiguration from : allConfigurations) {
      for (ConfigurationTransition transition : ConfigurationTransition.values()) {
        BuildConfiguration to;
        if (transition == ConfigurationTransition.HOST) {
          to = hostConfiguration;
        } else if (transition == ConfigurationTransition.DATA && from == targetConfiguration) {
          to = dataConfiguration;
        } else {
          to = from;
        }
        transitionBuilder.put(from, transition, new ConfigurationHolder(to));
      }
    }

    // TODO(bazel-team): This makes LIPO totally not work. Just a band-aid until we get around to
    // implementing a way for the C++ rules to contribute this transition to the configuration
    // collection.
    for (BuildConfiguration config : allConfigurations) {
      transitionBuilder.put(config, CppTransition.LIPO_COLLECTOR, new ConfigurationHolder(config));
      transitionBuilder.put(
          config,
          CppTransition.TARGET_CONFIG_FOR_LIPO,
          new ConfigurationHolder(config.isHostConfiguration() ? null : config));
    }

    for (BuildConfiguration config : allConfigurations) {
      Transitions outgoingTransitions =
          new BazelTransitions(
              config,
              transitionBuilder.row(config),
              // Split transitions must not have their own split transitions because then they
              // would be applied twice due to a quirk in DependencyResolver. See the comment in
              // DependencyResolver.resolveLateBoundAttributes().
              splitTransitionsTable.values().contains(config)
                  ? ImmutableListMultimap.<SplitTransition<?>, BuildConfiguration>of()
                  : splitTransitionsTable);
      // We allow host configurations to be shared between target configurations. In that case, the
      // transitions may already be set.
      // TODO(bazel-team): Check that the transitions are identical, or even better, change the
      // code to set the host configuration transitions before we even create the target
      // configuration.
      if (config.isHostConfiguration() && config.getTransitions() != null) {
        continue;
      }
      config.setConfigurationTransitions(outgoingTransitions);
    }

    return targetConfiguration;
  }