// Copyright 2014 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.rules.cpp;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.rules.apple.Platform;
import com.google.devtools.build.lib.rules.cpp.CcLibraryHelper.SourceCategory;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
import com.google.devtools.build.lib.rules.cpp.CppConfiguration.DynamicMode;
import com.google.devtools.build.lib.rules.cpp.CppConfiguration.HeadersCheckingMode;
import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector;
import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.LocalMetadataCollector;
import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
import com.google.devtools.build.lib.rules.test.InstrumentedFilesProviderImpl;
import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * Common parts of the implementation of cc rules.
 */
public final class CcCommon {

  private static final String NO_COPTS_ATTRIBUTE = "nocopts";

  /**
   * Collects all metadata files generated by C++ compilation actions that output the .o files
   * on the input.
   */
  private static final LocalMetadataCollector CC_METADATA_COLLECTOR =
      new LocalMetadataCollector() {
    @Override
    public void collectMetadataArtifacts(Iterable<Artifact> objectFiles,
        AnalysisEnvironment analysisEnvironment, NestedSetBuilder<Artifact> metadataFilesBuilder) {
      for (Artifact artifact : objectFiles) {
        ActionAnalysisMetadata action = analysisEnvironment.getLocalGeneratingAction(artifact);
        if (action instanceof CppCompileAction) {
          addOutputs(metadataFilesBuilder, action, CppFileTypes.COVERAGE_NOTES);
        }
      }
    }
  };
    
  /**
   * Features we request to enable unless a rule explicitly doesn't support them.
   */
  private static final ImmutableSet<String> DEFAULT_FEATURES = ImmutableSet.of(
      CppRuleClasses.DEPENDENCY_FILE,
      CppRuleClasses.COMPILE_ACTION_FLAGS_IN_FLAG_SET,
      CppRuleClasses.RANDOM_SEED,
      CppRuleClasses.MODULE_MAPS,
      CppRuleClasses.MODULE_MAP_HOME_CWD,
      CppRuleClasses.INCLUDE_PATHS,
      CppRuleClasses.PIC,
      CppRuleClasses.PER_OBJECT_DEBUG_INFO,
      CppRuleClasses.PREPROCESSOR_DEFINES);

  /** C++ configuration */
  private final CppConfiguration cppConfiguration;
  
  private final RuleContext ruleContext;

  public CcCommon(RuleContext ruleContext) {
    this.ruleContext = ruleContext;
    this.cppConfiguration = ruleContext.getFragment(CppConfiguration.class);
  }

  /**
   * Returns our own linkopts from the rule attribute. This determines linker
   * options to use when building this target and anything that depends on it.
   */
  public ImmutableList<String> getLinkopts() {
    Preconditions.checkState(hasAttribute("linkopts", Type.STRING_LIST));
    List<String> ourLinkopts = ruleContext.attributes().get("linkopts", Type.STRING_LIST);
    List<String> result = new ArrayList<>();
    if (ourLinkopts != null) {
      boolean allowDashStatic = !cppConfiguration.forceIgnoreDashStatic()
          && (cppConfiguration.getDynamicMode() != DynamicMode.FULLY);
      for (String linkopt : ourLinkopts) {
        if (linkopt.equals("-static") && !allowDashStatic) {
          continue;
        }
        CppHelper.expandAttribute(ruleContext, result, "linkopts", linkopt, true);
      }
    }
    
    if (Platform.isApplePlatform(cppConfiguration.getTargetCpu()) && result.contains("-static")) {
      ruleContext.attributeError(
          "linkopts", "Apple builds do not support statically linked binaries");
    }
    
    return ImmutableList.copyOf(result);
  }

  public ImmutableList<String> getCopts() {
    Preconditions.checkState(hasAttribute("copts", Type.STRING_LIST));
    // TODO(bazel-team): getAttributeCopts should not tokenize the strings. Make a warning for now.
    List<String> tokens = new ArrayList<>();
    for (String str : ruleContext.attributes().get("copts", Type.STRING_LIST)) {
      tokens.clear();
      try {
        ShellUtils.tokenize(tokens, str);
        if (tokens.size() > 1) {
          ruleContext.attributeWarning("copts",
              "each item in the list should contain only one option");
        }
      } catch (ShellUtils.TokenizationException e) {
        // ignore, the error is reported in the getAttributeCopts call
      }
    }

    Pattern nocopts = getNoCopts(ruleContext);
    if (nocopts != null && nocopts.matcher("-Wno-future-warnings").matches()) {
      ruleContext.attributeWarning("nocopts",
          "Regular expression '" + nocopts.pattern() + "' is too general; for example, it matches "
          + "'-Wno-future-warnings'.  Thus it might *re-enable* compiler warnings we wish to "
          + "disable globally.  To disable all compiler warnings, add '-w' to copts instead");
    }

    return ImmutableList.<String>builder()
        .addAll(getPackageCopts(ruleContext))
        .addAll(CppHelper.getAttributeCopts(ruleContext, "copts"))
        .build();
  }

  private boolean hasAttribute(String name, Type<?> type) {
    return ruleContext.attributes().has(name, type);
  }

  /** Collects all .dwo artifacts in this target's transitive closure. */
  public static DwoArtifactsCollector collectTransitiveDwoArtifacts(
      RuleContext ruleContext,
      CcCompilationOutputs compilationOutputs,
      boolean generateDwo,
      boolean ltoBackendArtifactsUsePic,
      Iterable<LTOBackendArtifacts> ltoBackendArtifacts) {
    ImmutableList.Builder<TransitiveInfoCollection> deps =
        ImmutableList.<TransitiveInfoCollection>builder();

    deps.addAll(ruleContext.getPrerequisites("deps", Mode.TARGET));

    if (ruleContext.attributes().has("malloc", BuildType.LABEL)) {
      deps.add(CppHelper.mallocForTarget(ruleContext));
    }

    return compilationOutputs == null // Possible in LIPO collection mode (see initializationHook).
        ? DwoArtifactsCollector.emptyCollector()
        : DwoArtifactsCollector.transitiveCollector(
            ruleContext,
            compilationOutputs,
            deps.build(),
            generateDwo,
            ltoBackendArtifactsUsePic,
            ltoBackendArtifacts);
  }

  public TransitiveLipoInfoProvider collectTransitiveLipoLabels(CcCompilationOutputs outputs) {
    if (CppHelper.getFdoSupport(ruleContext).getFdoRoot() == null
        || !cppConfiguration.isLipoContextCollector()) {
      return TransitiveLipoInfoProvider.EMPTY;
    }

    NestedSetBuilder<IncludeScannable> scannableBuilder = NestedSetBuilder.stableOrder();
    CppHelper.addTransitiveLipoInfoForCommonAttributes(ruleContext, outputs, scannableBuilder);
    return new TransitiveLipoInfoProvider(scannableBuilder.build());
  }

  /**
   * Returns a list of ({@link Artifact}, {@link Label}) pairs. Each pair represents an input
   * source file and the label of the rule that generates it (or the label of the source file
   * itself if it is an input file).
   */
  List<Pair<Artifact, Label>> getSources() {
    Map<Artifact, Label> map = Maps.newLinkedHashMap();
    Iterable<? extends TransitiveInfoCollection> providers =
        ruleContext.getPrerequisitesIf("srcs", Mode.TARGET, FileProvider.class);
    for (TransitiveInfoCollection provider : providers) {
      for (Artifact artifact : provider.getProvider(FileProvider.class).getFilesToBuild()) {
        // TODO(bazel-team): We currently do not produce an error for duplicate headers and other
        // non-source artifacts with different labels, as that would require cleaning up the code
        // base without significant benefit; we should eventually make this consistent one way or
        // the other.
        Label oldLabel = map.put(artifact, provider.getLabel());
        boolean isHeader = CppFileTypes.CPP_HEADER.matches(artifact.getExecPath());
        if (!isHeader
            && SourceCategory.CC_AND_OBJC.getSourceTypes().matches(artifact.getExecPathString())
            && oldLabel != null
            && !oldLabel.equals(provider.getLabel())) {
          ruleContext.attributeError("srcs", String.format(
              "Artifact '%s' is duplicated (through '%s' and '%s')",
              artifact.getExecPathString(), oldLabel, provider.getLabel()));
        }
      }
    }

    ImmutableList.Builder<Pair<Artifact, Label>> result = ImmutableList.builder();
    for (Map.Entry<Artifact, Label> entry : map.entrySet()) {
      result.add(Pair.of(entry.getKey(), entry.getValue()));
    }
    return result.build();
  }

  /**
   * Returns the files from headers and does some sanity checks. Note that this method reports
   * warnings to the {@link RuleContext} as a side effect, and so should only be called once for any
   * given rule.
   */
  public static List<Pair<Artifact, Label>> getHeaders(RuleContext ruleContext) {
    Map<Artifact, Label> map = Maps.newLinkedHashMap();
    for (TransitiveInfoCollection target :
        ruleContext.getPrerequisitesIf("hdrs", Mode.TARGET, FileProvider.class)) {
      FileProvider provider = target.getProvider(FileProvider.class);
      for (Artifact artifact : provider.getFilesToBuild()) {
        if (CppRuleClasses.DISALLOWED_HDRS_FILES.matches(artifact.getFilename())) {
          ruleContext.attributeWarning("hdrs", "file '" + artifact.getFilename()
              + "' from target '" + target.getLabel() + "' is not allowed in hdrs");
          continue;
        }
        Label oldLabel = map.put(artifact, target.getLabel());
        if (oldLabel != null && !oldLabel.equals(target.getLabel())) {
          ruleContext.attributeWarning(
              "hdrs",
              String.format(
                  "Artifact '%s' is duplicated (through '%s' and '%s')",
                  artifact.getExecPathString(),
                  oldLabel,
                  target.getLabel()));
        }
      }
    }
    
    ImmutableList.Builder<Pair<Artifact, Label>> result = ImmutableList.builder();
    for (Map.Entry<Artifact, Label> entry : map.entrySet()) {
      result.add(Pair.of(entry.getKey(), entry.getValue()));
    }
    return result.build();
  }

  /**
   * Returns the files from headers and does some sanity checks. Note that this method reports
   * warnings to the {@link RuleContext} as a side effect, and so should only be called once for any
   * given rule.
   */
  public List<Pair<Artifact, Label>> getHeaders() {
    return getHeaders(ruleContext);
  }

  private static ImmutableList<String> getPackageCopts(RuleContext ruleContext) {
    List<String> unexpanded = ruleContext.getRule().getPackage().getDefaultCopts();
    return ImmutableList.copyOf(CppHelper.expandMakeVariables(ruleContext, "copts", unexpanded));
  }

  Pattern getNoCopts() {
    return getNoCopts(ruleContext);
  }

  /**
   * Returns nocopts pattern built from the make variable expanded nocopts
   * attribute.
   */
  private static Pattern getNoCopts(RuleContext ruleContext) {
    Pattern nocopts = null;
    if (ruleContext.getRule().isAttrDefined(NO_COPTS_ATTRIBUTE, Type.STRING)) {
      String nocoptsAttr = ruleContext.expandMakeVariables(NO_COPTS_ATTRIBUTE,
          ruleContext.attributes().get(NO_COPTS_ATTRIBUTE, Type.STRING));
      try {
        nocopts = Pattern.compile(nocoptsAttr);
      } catch (PatternSyntaxException e) {
        ruleContext.attributeError(NO_COPTS_ATTRIBUTE,
            "invalid regular expression '" + nocoptsAttr + "': " + e.getMessage());
      }
    }
    return nocopts;
  }

  // TODO(bazel-team): calculating nocopts every time is not very efficient,
  // fix this after the rule migration. The problem is that in some cases we call this after
  // the RCT is created (so RuleContext is not accessible), in some cases during the creation.
  // It would probably make more sense to use TransitiveInfoProviders.
  /**
   * Returns true if the rule context has a nocopts regex that matches the given value, false
   * otherwise.
   */
  static boolean noCoptsMatches(String option, RuleContext ruleContext) {
    Pattern nocopts = getNoCopts(ruleContext);
    return nocopts == null ? false : nocopts.matcher(option).matches();
  }

  private static final String DEFINES_ATTRIBUTE = "defines";

  /**
   * Returns a list of define tokens from "defines" attribute.
   *
   * <p>We tokenize the "defines" attribute, to ensure that the handling of
   * quotes and backslash escapes is consistent Bazel's treatment of the "copts" attribute.
   *
   * <p>But we require that the "defines" attribute consists of a single token.
   */
  public List<String> getDefines() {
    List<String> defines = new ArrayList<>();
    for (String define :
      ruleContext.attributes().get(DEFINES_ATTRIBUTE, Type.STRING_LIST)) {
      List<String> tokens = new ArrayList<>();
      try {
        ShellUtils.tokenize(tokens, ruleContext.expandMakeVariables(DEFINES_ATTRIBUTE, define));
        if (tokens.size() == 1) {
          defines.add(tokens.get(0));
        } else if (tokens.isEmpty()) {
          ruleContext.attributeError(DEFINES_ATTRIBUTE, "empty definition not allowed");
        } else {
          ruleContext.attributeError(DEFINES_ATTRIBUTE,
              "definition contains too many tokens (found " + tokens.size()
              + ", expecting exactly one)");
        }
      } catch (ShellUtils.TokenizationException e) {
        ruleContext.attributeError(DEFINES_ATTRIBUTE, e.getMessage());
      }
    }
    return defines;
  }

  /**
   * Determines a list of loose include directories that are only allowed to be referenced when
   * headers checking is {@link HeadersCheckingMode#LOOSE} or {@link HeadersCheckingMode#WARN}.
   */
  List<PathFragment> getLooseIncludeDirs() {
    List<PathFragment> result = new ArrayList<>();
    // The package directory of the rule contributes includes. Note that this also covers all
    // non-subpackage sub-directories.
    PathFragment rulePackage = ruleContext.getLabel().getPackageIdentifier()
        .getPathUnderExecRoot();
    result.add(rulePackage);

    // Gather up all the dirs from the rule's srcs as well as any of the srcs outputs.
    if (hasAttribute("srcs", BuildType.LABEL_LIST)) {
      for (TransitiveInfoCollection src :
          ruleContext.getPrerequisitesIf("srcs", Mode.TARGET, FileProvider.class)) {
        PathFragment packageDir = src.getLabel().getPackageIdentifier().getPathUnderExecRoot();
        for (Artifact a : src.getProvider(FileProvider.class).getFilesToBuild()) {
          result.add(packageDir);
          // Attempt to gather subdirectories that might contain include files.
          result.add(a.getRootRelativePath().getParentDirectory());
        }
      }
    }

    // Add in any 'includes' attribute values as relative path fragments
    if (ruleContext.getRule().isAttributeValueExplicitlySpecified("includes")) {
      PathFragment packageFragment = ruleContext.getLabel().getPackageIdentifier()
          .getPathUnderExecRoot();
      // For now, anything with an 'includes' needs a blanket declaration
      result.add(packageFragment.getRelative("**"));
    }
    return result;
  }

  List<PathFragment> getSystemIncludeDirs() {
    List<PathFragment> result = new ArrayList<>();
    PackageIdentifier packageIdentifier = ruleContext.getLabel().getPackageIdentifier();
    PathFragment packageFragment = packageIdentifier.getPathUnderExecRoot();
    for (String includesAttr : ruleContext.attributes().get("includes", Type.STRING_LIST)) {
      includesAttr = ruleContext.expandMakeVariables("includes", includesAttr);
      if (includesAttr.startsWith("/")) {
        ruleContext.attributeWarning("includes",
            "ignoring invalid absolute path '" + includesAttr + "'");
        continue;
      }
      PathFragment includesPath = packageFragment.getRelative(includesAttr).normalize();
      if (!includesPath.isNormalized()) {
        ruleContext.attributeError("includes",
            "Path references a path above the execution root.");
      }
      if (includesPath.segmentCount() == 0) {
        ruleContext.attributeError(
            "includes",
            "'"
                + includesAttr
                + "' resolves to the workspace root, which would allow this rule and all of its "
                + "transitive dependents to include any file in your workspace. Please include only"
                + " what you need");
      } else if (!includesPath.startsWith(packageFragment)) {
        ruleContext.attributeWarning(
            "includes",
            "'"
                + includesAttr
                + "' resolves to '"
                + includesPath
                + "' not below the relative path of its package '"
                + packageFragment
                + "'. This will be an error in the future");
      }
      result.add(includesPath);
      result.add(ruleContext.getConfiguration().getGenfilesFragment().getRelative(includesPath));
    }
    return result;
  }

  /**
   * Collects compilation prerequisite artifacts.
   */
  static NestedSet<Artifact> collectCompilationPrerequisites(
      RuleContext ruleContext, CppCompilationContext context) {
    // TODO(bazel-team): Use context.getCompilationPrerequisites() instead; note that this will
    // need cleaning up the prerequisites, as the compilation context currently collects them
    // transitively (to get transitive headers), but source files are not transitive compilation
    // prerequisites.
    NestedSetBuilder<Artifact> prerequisites = NestedSetBuilder.stableOrder();
    if (ruleContext.attributes().has("srcs", BuildType.LABEL_LIST)) {
      for (FileProvider provider :
          ruleContext.getPrerequisites("srcs", Mode.TARGET, FileProvider.class)) {
        prerequisites.addAll(
            FileType.filter(
                provider.getFilesToBuild(), SourceCategory.CC_AND_OBJC.getSourceTypes()));
      }
    }
    prerequisites.addTransitive(context.getDeclaredIncludeSrcs());
    prerequisites.addTransitive(context.getAdditionalInputs());
    prerequisites.addTransitive(context.getTransitiveModules(true));
    prerequisites.addTransitive(context.getTransitiveModules(false));
    return prerequisites.build();
  }

  /**
   * Replaces shared library artifact with mangled symlink and creates related
   * symlink action. For artifacts that should retain filename (e.g. libraries
   * with SONAME tag), link is created to the parent directory instead.
   *
   * This action is performed to minimize number of -rpath entries used during
   * linking process (by essentially "collecting" as many shared libraries as
   * possible in the single directory), since we will be paying quadratic price
   * for each additional entry on the -rpath.
   *
   * @param library Shared library artifact that needs to be mangled
   * @param preserveName true if filename should be preserved, false - mangled.
   * @return mangled symlink artifact.
   */
  public Artifact getDynamicLibrarySymlink(Artifact library, boolean preserveName) {
    return  SolibSymlinkAction.getDynamicLibrarySymlink(
        ruleContext, library, preserveName, true, ruleContext.getConfiguration());
  }

  /**
   * Returns any linker scripts found in the dependencies of the rule.
   */
  Iterable<Artifact> getLinkerScripts() {
    return FileType.filter(ruleContext.getPrerequisiteArtifacts("deps", Mode.TARGET).list(),
        CppFileTypes.LINKER_SCRIPT);
  }

  /**
   * Provides support for instrumentation.
   */
  public InstrumentedFilesProvider getInstrumentedFilesProvider(Iterable<Artifact> files,
      boolean withBaselineCoverage) {
    return cppConfiguration.isLipoContextCollector()
        ? InstrumentedFilesProviderImpl.EMPTY
        : InstrumentedFilesCollector.collect(
            ruleContext, CppRuleClasses.INSTRUMENTATION_SPEC, CC_METADATA_COLLECTOR, files,
            CppHelper.getGcovFilesIfNeeded(ruleContext),
            CppHelper.getCoverageEnvironmentIfNeeded(ruleContext),
            withBaselineCoverage);
  }

  private static String getHostOrNonHostFeature(RuleContext ruleContext) {
    if (ruleContext.getConfiguration().isHostConfiguration()) {
      return "host";
    } else {
      return "nonhost";
    }
  }

  /**
   * Creates the feature configuration for a given rule.
   *
   * @param ruleContext the context of the rule we want the feature configuration for.
   * @param ruleSpecificRequestedFeatures features that will be requested, and thus be always
   * enabled if the toolchain supports them.
   * @param ruleSpecificUnsupportedFeatures features that are not supported in the current context.
   * @param sourceCategory the source category for this build.
   * @return the feature configuration for the given {@code ruleContext}.
   */
  public static FeatureConfiguration configureFeatures(
      RuleContext ruleContext,
      Set<String> ruleSpecificRequestedFeatures,
      Set<String> ruleSpecificUnsupportedFeatures,
      SourceCategory sourceCategory,
      CcToolchainProvider toolchain) {
    ImmutableSet.Builder<String> unsupportedFeaturesBuilder = ImmutableSet.builder();
    unsupportedFeaturesBuilder.addAll(ruleSpecificUnsupportedFeatures);
    if (!toolchain.supportsHeaderParsing()) {
      // TODO(bazel-team): Remove once supports_header_parsing has been removed from the
      // cc_toolchain rule.
      unsupportedFeaturesBuilder.add(CppRuleClasses.PARSE_HEADERS);
      unsupportedFeaturesBuilder.add(CppRuleClasses.PREPROCESS_HEADERS);
    }
    if (toolchain.getCppCompilationContext().getCppModuleMap() == null) {
      unsupportedFeaturesBuilder.add(CppRuleClasses.MODULE_MAPS);
    }
    Set<String> unsupportedFeatures = unsupportedFeaturesBuilder.build();
    ImmutableSet.Builder<String> requestedFeatures = ImmutableSet.builder();
    for (String feature :
        Iterables.concat(
            ImmutableSet.of(toolchain.getCompilationMode().toString()),
            ImmutableSet.of(getHostOrNonHostFeature(ruleContext)),
            DEFAULT_FEATURES,
            ruleContext.getFeatures())) {
      if (!unsupportedFeatures.contains(feature)) {
        requestedFeatures.add(feature);
      }
    }
    requestedFeatures.addAll(ruleSpecificRequestedFeatures);

    requestedFeatures.addAll(sourceCategory.getActionConfigSet());

    FeatureConfiguration configuration =
        toolchain.getFeatures().getFeatureConfiguration(requestedFeatures.build());
    for (String feature : unsupportedFeatures) {
      if (configuration.isEnabled(feature)) {
        ruleContext.ruleError("The C++ toolchain '"
            + ruleContext.getPrerequisite(":cc_toolchain", Mode.TARGET).getLabel()
            + "' unconditionally implies feature '" + feature
            + "', which is unsupported by this rule. "
            + "This is most likely a misconfiguration in the C++ toolchain.");
      }
    }
    return configuration; 
  }
 
  /**
   * Creates a feature configuration for a given rule.
   *
   * @param ruleContext the context of the rule we want the feature configuration for.
   * @param toolchain the toolchain we want the feature configuration for.
   * @param sourceCategory the category of sources to be used in this build.
   * @return the feature configuration for the given {@code ruleContext}.
   */
  public static FeatureConfiguration configureFeatures(
      RuleContext ruleContext, CcToolchainProvider toolchain, SourceCategory sourceCategory) {
    return configureFeatures(
        ruleContext,
        ImmutableSet.<String>of(),
        ImmutableSet.<String>of(),
        sourceCategory,
        toolchain);
  }

  /**
   * Creates a feature configuration for a given rule.
   *
   * @param ruleContext the context of the rule we want the feature configuraiton for.
   * @param sourceCategory the category of sources to be used in this build.
   * @return the feature configuration for the given {@code ruleContext}.
   */
  public static FeatureConfiguration configureFeatures(
      RuleContext ruleContext, SourceCategory sourceCategory) {
    return configureFeatures(ruleContext, CppHelper.getToolchain(ruleContext), sourceCategory);
  }

  /**
   * Creates a feature configuration for a given rule.  Assumes strictly cc sources.
   *
   * @param ruleContext the context of the rule we want the feature configuration for.
   * @return the feature configuration for the given {@code ruleContext}.
   */
  public static FeatureConfiguration configureFeatures(RuleContext ruleContext) {
    return configureFeatures(ruleContext, SourceCategory.CC);
  }
}
