// 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 static com.google.devtools.build.lib.packages.BuildType.LABEL;
import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.actions.ActionOwner;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
import com.google.devtools.build.lib.actions.MiddlemanFactory;
import com.google.devtools.build.lib.actions.ParamFileInfo;
import com.google.devtools.build.lib.actions.ParameterFile;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.Expander;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.PlatformConfiguration;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.StaticallyLinkedMarkerProvider;
import com.google.devtools.build.lib.analysis.ToolchainContext.ResolvedToolchainProviders;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.actions.SymlinkAction;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Options;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Options.MakeVariableSource;
import com.google.devtools.build.lib.analysis.config.CompilationMode;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.platform.ToolchainInfo;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException;
import com.google.devtools.build.lib.packages.RuleErrorConsumer;
import com.google.devtools.build.lib.rules.cpp.CcLinkParams.Linkstamp;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Tool;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
import com.google.devtools.build.lib.rules.cpp.CppConfiguration.DynamicMode;
import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.FileTypeSet;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LipoMode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

/**
 * Helper class for functionality shared by cpp related rules.
 *
 * <p>This class can be used only after the loading phase.
 */
public class CppHelper {

  static final PathFragment OBJS = PathFragment.create("_objs");
  static final PathFragment PIC_OBJS = PathFragment.create("_pic_objs");

  private static final String GREPPED_INCLUDES_SUFFIX = ".includes";

  // TODO(bazel-team): should this use Link.SHARED_LIBRARY_FILETYPES?
  public static final FileTypeSet SHARED_LIBRARY_FILETYPES = FileTypeSet.of(
      CppFileTypes.SHARED_LIBRARY,
      CppFileTypes.VERSIONED_SHARED_LIBRARY);

  private static final FileTypeSet CPP_FILETYPES = FileTypeSet.of(
      CppFileTypes.CPP_HEADER,
      CppFileTypes.CPP_SOURCE);

  private static final ImmutableList<String> LINKOPTS_PREREQUISITE_LABEL_KINDS =
      ImmutableList.of("deps", "srcs");

  /** Base label of the c++ toolchain category. */
  public static final String TOOLCHAIN_TYPE_LABEL = "//tools/cpp:toolchain_type";

  private CppHelper() {
    // prevents construction
  }

  /**
   * Merges the STL and toolchain contexts into context builder. The STL is automatically determined
   * using the ":stl" attribute.
   */
  public static void mergeToolchainDependentCcCompilationContextInfo(
      RuleContext ruleContext,
      CcToolchainProvider toolchain,
      CcCompilationContextInfo.Builder ccCompilationContextInfoBuilder) {
    if (ruleContext.getRule().getAttributeDefinition(":stl") != null) {
      TransitiveInfoCollection stl = ruleContext.getPrerequisite(":stl", Mode.TARGET);
      if (stl != null) {
        CcCompilationInfo ccCompilationInfo = stl.get(CcCompilationInfo.PROVIDER);
        CcCompilationContextInfo ccCompilationContextInfo =
            ccCompilationInfo != null ? ccCompilationInfo.getCcCompilationContextInfo() : null;
        if (ccCompilationContextInfo == null) {
          ruleContext.ruleError(
              "Unable to merge the STL '" + stl.getLabel() + "' and toolchain contexts");
          return;
        }
        ccCompilationContextInfoBuilder.mergeDependentCcCompilationContextInfo(
            ccCompilationContextInfo);
      }
    }
    if (toolchain != null) {
      ccCompilationContextInfoBuilder.mergeDependentCcCompilationContextInfo(
          toolchain.getCcCompilationContextInfo());
    }
  }

  /**
   * Returns the malloc implementation for the given target.
   */
  public static TransitiveInfoCollection mallocForTarget(
      RuleContext ruleContext, String mallocAttrName) {
    if (ruleContext.getFragment(CppConfiguration.class).customMalloc() != null) {
      return ruleContext.getPrerequisite(":default_malloc", Mode.TARGET);
    } else {
      return ruleContext.getPrerequisite(mallocAttrName, Mode.TARGET);
    }
  }

  public static TransitiveInfoCollection mallocForTarget(RuleContext ruleContext) {
    return mallocForTarget(ruleContext, "malloc");
  }

  /**
   * Returns true if this target should obtain c++ make variables from the toolchain instead of from
   * the configuration.
   */
  public static boolean shouldUseToolchainForMakeVariables(RuleContext ruleContext) {
    Label toolchainType = getToolchainTypeFromRuleClass(ruleContext);
    return (ruleContext.getConfiguration().getOptions().get(Options.class).makeVariableSource
            == MakeVariableSource.TOOLCHAIN)
        && (ruleContext
            .getFragment(PlatformConfiguration.class)
            .isToolchainTypeEnabled(toolchainType));
  }

  /**
   * Expands Make variables in a list of string and tokenizes the result. If the package feature
   * no_copts_tokenization is set, tokenize only items consisting of a single make variable.
   *
   * @param ruleContext the ruleContext to be used as the context of Make variable expansion
   * @param attributeName the name of the attribute to use in error reporting
   * @param input the list of strings to expand
   * @return a list of strings containing the expanded and tokenized values for the
   *         attribute
   */
  private static List<String> expandMakeVariables(
      RuleContext ruleContext, String attributeName, List<String> input) {
    boolean tokenization =
        !ruleContext.getFeatures().contains("no_copts_tokenization");

    List<String> tokens = new ArrayList<>();
    Expander expander = ruleContext.getExpander().withDataExecLocations();
    for (String token : input) {
      // Legacy behavior: tokenize all items.
      if (tokenization) {
        expander.tokenizeAndExpandMakeVars(tokens, attributeName, token);
      } else {
        String exp = expander.expandSingleMakeVariable(attributeName, token);
        if (exp != null) {
          try {
            ShellUtils.tokenize(tokens, exp);
          } catch (ShellUtils.TokenizationException e) {
            ruleContext.attributeError(attributeName, e.getMessage());
          }
        } else {
          tokens.add(expander.expand(attributeName, token));
        }
      }
    }
    return ImmutableList.copyOf(tokens);
  }

  /**
   * Returns the tokenized values of the copts attribute to copts.
   */
  // Called from CcCommon and CcSupport (Google's internal version of proto_library).
  public static ImmutableList<String> getAttributeCopts(RuleContext ruleContext) {
    String attr = "copts";
    Preconditions.checkArgument(ruleContext.getRule().isAttrDefined(attr, Type.STRING_LIST));
    List<String> unexpanded = ruleContext.attributes().get(attr, Type.STRING_LIST);
    return ImmutableList.copyOf(expandMakeVariables(ruleContext, attr, unexpanded));
  }

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

  /**
   * Expands attribute value either using label expansion
   * (if attemptLabelExpansion == {@code true} and it does not look like make
   * variable or flag) or tokenizes and expands make variables.
   */
  public static List<String> expandLinkopts(
      RuleContext ruleContext, String attrName, Iterable<String> values) {
    List<String> result = new ArrayList<>();
    Expander expander = ruleContext.getExpander().withDataExecLocations();
    for (String value : values) {
      if (isLinkoptLabel(value)) {
        if (!expandLabel(ruleContext, result, value)) {
          ruleContext.attributeError(attrName, "could not resolve label '" + value + "'");
        }
      } else {
        expander
            .tokenizeAndExpandMakeVars(
                result,
                attrName,
                value);
      }
    }
    return result;
  }

  /**
   * Returns the immutable list of linker options for fully statically linked outputs. Does not
   * include command-line options passed via --linkopt or --linkopts.
   *
   * @param config the CppConfiguration for this build
   * @param toolchain the c++ toolchain
   * @param sharedLib true if the output is a shared lib, false if it's an executable
   */
  public static ImmutableList<String> getFullyStaticLinkOptions(
      CppConfiguration config, CcToolchainProvider toolchain, boolean sharedLib) {
    if (sharedLib) {
      return toolchain.getSharedLibraryLinkOptions(
          toolchain.getLegacyMostlyStaticLinkFlags(
              config.getCompilationMode(), config.getLipoMode()));
    } else {
      return toolchain
          .getLegacyFullyStaticLinkFlags(config.getCompilationMode(), config.getLipoMode())
          .evaluate();
    }
  }

  /**
   * Returns the immutable list of linker options for mostly statically linked outputs. Does not
   * include command-line options passed via --linkopt or --linkopts.
   *
   * @param config the CppConfiguration for this build
   * @param toolchain the c++ toolchain
   * @param sharedLib true if the output is a shared lib, false if it's an executable
   */
  public static ImmutableList<String> getMostlyStaticLinkOptions(
      CppConfiguration config,
      CcToolchainProvider toolchain,
      boolean sharedLib,
      boolean shouldStaticallyLinkCppRuntimes) {
    if (sharedLib) {
      return toolchain.getSharedLibraryLinkOptions(
          shouldStaticallyLinkCppRuntimes
              ? toolchain.getLegacyMostlyStaticSharedLinkFlags(
                  config.getCompilationMode(), config.getLipoMode())
              : toolchain.getLegacyDynamicLinkFlags(
                  config.getCompilationMode(), config.getLipoMode()));
    } else {
      return toolchain
          .getLegacyMostlyStaticLinkFlags(config.getCompilationMode(), config.getLipoMode())
          .evaluate();
    }
  }

  /**
   * Returns the immutable list of linker options for artifacts that are not fully or mostly
   * statically linked. Does not include command-line options passed via --linkopt or --linkopts.
   *
   * @param config the CppConfiguration for this build
   * @param toolchain the c++ toolchain
   * @param sharedLib true if the output is a shared lib, false if it's an executable
   */
  public static ImmutableList<String> getDynamicLinkOptions(
      CppConfiguration config,
      CcToolchainProvider toolchain,
      Boolean sharedLib) {
    if (sharedLib) {
      return toolchain.getSharedLibraryLinkOptions(
          toolchain.getLegacyDynamicLinkFlags(config.getCompilationMode(), config.getLipoMode()));
    } else {
      return toolchain
          .getLegacyDynamicLinkFlags(config.getCompilationMode(), config.getLipoMode())
          .evaluate();
    }
  }

  /**
   * Determines if a linkopt can be a label. Linkopts come in 2 varieties:
   * literals -- flags like -Xl and makefile vars like $(LD) -- and labels,
   * which we should expand into filenames.
   *
   * @param linkopt the link option to test.
   * @return true if the linkopt is not a flag (starting with "-") or a makefile
   *         variable (starting with "$");
   */
  private static boolean isLinkoptLabel(String linkopt) {
    return !linkopt.startsWith("$") && !linkopt.startsWith("-");
  }

  /**
   * Expands a label against the target's deps, adding the expanded path strings
   * to the linkopts.
   *
   * @param linkopts the linkopts to add the expanded label to
   * @param labelName the name of the label to expand
   * @return true if the label was expanded successfully, false otherwise
   */
  private static boolean expandLabel(
      RuleContext ruleContext, List<String> linkopts, String labelName) {
    try {
      Label label = ruleContext.getLabel().getRelative(labelName);
      for (String prereqKind : LINKOPTS_PREREQUISITE_LABEL_KINDS) {
        for (TransitiveInfoCollection target : ruleContext
            .getPrerequisitesIf(prereqKind, Mode.TARGET, FileProvider.class)) {
          if (target.getLabel().equals(label)) {
            for (Artifact artifact : target.getProvider(FileProvider.class).getFilesToBuild()) {
              linkopts.add(artifact.getExecPathString());
            }
            return true;
          }
        }
      }
    } catch (LabelSyntaxException e) {
      // Quietly ignore and fall through.
    }
    linkopts.add(labelName);
    return false;
  }

  /**
   * Returns the dynamic linking mode (full, off, or default) implied by the given configuration and
   * toolchain.
   */
  public static DynamicMode getDynamicMode(CppConfiguration config, CcToolchainProvider toolchain) {
    // With LLVM, ThinLTO is automatically used in place of LIPO. ThinLTO works fine with dynamic
    // linking (and in fact creates a lot more work when dynamic linking is off).
    if (config.getLipoMode() == LipoMode.BINARY && !toolchain.isLLVMCompiler()) {
      // TODO(bazel-team): implement dynamic linking with LIPO
      return DynamicMode.OFF;
    } else {
      return config.getDynamicModeFlag();
    }
  }

  /** Returns true if LIPO optimization should be applied for this configuration and toolchain. */
  public static boolean isLipoOptimization(CppConfiguration config, CcToolchainProvider toolchain) {
    // The LIPO optimization bits are set in the LIPO context collector configuration, too.
    // If compiler is LLVM, then LIPO gets auto-converted to ThinLTO.
    return config.lipoOptimizationIsActivated() && !toolchain.isLLVMCompiler();
  }

  /**
   * Return {@link FdoSupportProvider} using default cc_toolchain attribute name.
   *
   * <p>Be careful to provide explicit attribute name if the rule doesn't store cc_toolchain under
   * the default name.
   */
  @Nullable
  public static FdoSupportProvider getFdoSupportUsingDefaultCcToolchainAttribute(
      RuleContext ruleContext) {
    return getFdoSupport(ruleContext, CcToolchain.CC_TOOLCHAIN_DEFAULT_ATTRIBUTE_NAME);
  }

  @Nullable public static FdoSupportProvider getFdoSupport(RuleContext ruleContext,
      String ccToolchainAttribute) {
    return ruleContext
        .getPrerequisite(ccToolchainAttribute, Mode.TARGET)
        .getProvider(FdoSupportProvider.class);
  }

  public static NestedSet<Pair<String, String>> getCoverageEnvironmentIfNeeded(
      RuleContext ruleContext, CcToolchainProvider toolchain) {
    if (ruleContext.getConfiguration().isCodeCoverageEnabled()) {
      return toolchain.getCoverageEnvironment();
    } else {
      return NestedSetBuilder.emptySet(Order.COMPILE_ORDER);
    }
  }

  public static NestedSet<Artifact> getGcovFilesIfNeeded(
      RuleContext ruleContext, CcToolchainProvider toolchain) {
    if (ruleContext.getConfiguration().isCodeCoverageEnabled()) {
      return toolchain.getCoverage();
    } else {
      return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
    }
  }

  /**
   * This almost trivial method looks up the default cc toolchain attribute on the rule context,
   * makes sure that it refers to a rule that has a {@link CcToolchainProvider} (gives an error
   * otherwise), and returns a reference to that {@link CcToolchainProvider}. The method only
   * returns {@code null} if there is no such attribute (this is currently not an error).
   *
   * <p>Be careful to provide explicit attribute name if the rule doesn't store cc_toolchain under
   * the default name.
   */
  @Nullable
  public static CcToolchainProvider getToolchainUsingDefaultCcToolchainAttribute(
      RuleContext ruleContext) {
    return getToolchain(ruleContext, CcToolchain.CC_TOOLCHAIN_DEFAULT_ATTRIBUTE_NAME);
  }

  /**
   * Convenience function for finding the dynamic runtime inputs for the current toolchain. Useful
   * for non C++ rules that link against the C++ runtime.
   */
  public static NestedSet<Artifact> getDefaultCcToolchainDynamicRuntimeInputs(
      RuleContext ruleContext) {
    CcToolchainProvider defaultToolchain =
        getToolchain(ruleContext, CcToolchain.CC_TOOLCHAIN_DEFAULT_ATTRIBUTE_NAME);
    if (defaultToolchain == null) {
      return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
    }
    FeatureConfiguration featureConfiguration =
        CcCommon.configureFeaturesOrReportRuleError(ruleContext, defaultToolchain);
    return defaultToolchain.getDynamicRuntimeLinkInputs(featureConfiguration);
  }

  /**
   * Convenience function for finding the static runtime inputs for the current toolchain. Useful
   * for non C++ rules that link against the C++ runtime.
   */
  public static NestedSet<Artifact> getDefaultCcToolchainStaticRuntimeInputs(
      RuleContext ruleContext) {
    CcToolchainProvider defaultToolchain =
        getToolchain(ruleContext, CcToolchain.CC_TOOLCHAIN_DEFAULT_ATTRIBUTE_NAME);
    if (defaultToolchain == null) {
      return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
    }
    FeatureConfiguration featureConfiguration =
        CcCommon.configureFeaturesOrReportRuleError(ruleContext, defaultToolchain);
    return defaultToolchain.getStaticRuntimeLinkInputs(featureConfiguration);
  }

  /**
   * Makes sure that the given info collection has a {@link CcToolchainProvider} (gives an error
   * otherwise), and returns a reference to that {@link CcToolchainProvider}. The method will only
   * return {@code null}, if the toolchain attribute is undefined for the rule class.
   */
  @Nullable
  public static CcToolchainProvider getToolchain(
      RuleContext ruleContext, String toolchainAttribute) {
     if (!ruleContext.isAttrDefined(toolchainAttribute, LABEL)) {
       // TODO(bazel-team): Report an error or throw an exception in this case.
       return null;
     }
     TransitiveInfoCollection dep = ruleContext.getPrerequisite(toolchainAttribute, Mode.TARGET);
     return getToolchain(ruleContext, dep);
   }

  /** Returns the c++ toolchain type, or null if it is not specified on the rule class. */
  public static Label getToolchainTypeFromRuleClass(RuleContext ruleContext) {
    Label toolchainType;
    // TODO(b/65835260): Remove this conditional once j2objc can learn the toolchain type.
    if (ruleContext.attributes().has(CcToolchain.CC_TOOLCHAIN_TYPE_ATTRIBUTE_NAME)) {
      toolchainType =
          ruleContext.attributes().get(CcToolchain.CC_TOOLCHAIN_TYPE_ATTRIBUTE_NAME, NODEP_LABEL);
    } else {
      toolchainType = null;
    }
    return toolchainType;
  }

  /**
   * Makes sure that the given info collection has a {@link CcToolchainProvider} (gives an error
   * otherwise), and returns a reference to that {@link CcToolchainProvider}. The method never
   * returns {@code null}, even if there is no toolchain.
   */
  public static CcToolchainProvider getToolchain(
      RuleContext ruleContext, TransitiveInfoCollection dep) {

    Label toolchainType = getToolchainTypeFromRuleClass(ruleContext);
    if (toolchainType != null
        && ruleContext
            .getFragment(PlatformConfiguration.class)
            .isToolchainTypeEnabled(toolchainType)) {
      return getToolchainFromPlatformConstraints(ruleContext, toolchainType);
    }
    return getToolchainFromCrosstoolTop(ruleContext, dep);
  }

  private static CcToolchainProvider getToolchainFromPlatformConstraints(
      RuleContext ruleContext, Label toolchainType) {
    ResolvedToolchainProviders providers =
        (ResolvedToolchainProviders)
            ruleContext.getToolchainContext().getResolvedToolchainProviders();
    return (CcToolchainProvider) providers.getForToolchainType(toolchainType);
  }

  private static CcToolchainProvider getToolchainFromCrosstoolTop(
      RuleContext ruleContext, TransitiveInfoCollection dep) {
    // TODO(bazel-team): Consider checking this generally at the attribute level.
    if ((dep == null) || (dep.get(ToolchainInfo.PROVIDER) == null)) {
      ruleContext.ruleError("The selected C++ toolchain is not a cc_toolchain rule");
      return CcToolchainProvider.EMPTY_TOOLCHAIN_IS_ERROR;
    }
    return (CcToolchainProvider) dep.get(ToolchainInfo.PROVIDER);
  }

  /** Returns the directory where object files are created. */
  public static PathFragment getObjDirectory(Label ruleLabel, boolean usePic) {
    if (usePic) {
      return AnalysisUtils.getUniqueDirectory(ruleLabel, PIC_OBJS);
    } else {
      return AnalysisUtils.getUniqueDirectory(ruleLabel, OBJS);
    }
  }

  /** Returns the directory where object files are created. */
  public static PathFragment getObjDirectory(Label ruleLabel) {
    return getObjDirectory(ruleLabel, false);
  }

  /**
   * A header that has been grepped for #include statements. Includes the original header as well as
   * a stripped version of that header that contains only #include statements.
   */
  @AutoCodec
  @AutoValue
  abstract static class PregreppedHeader {
    @AutoCodec.Instantiator
    static PregreppedHeader create(Artifact originalHeader, Artifact greppedHeader) {
      return new AutoValue_CppHelper_PregreppedHeader(originalHeader, greppedHeader);
    }

    /** Returns the original header, before grepping. */
    abstract Artifact originalHeader();

    /** Returns the grepped header, which contains only #include statements. */
    abstract Artifact greppedHeader();
  }

  /**
   * Creates a grep-includes ExtractInclusions action for generated sources/headers in the
   * needsIncludeScanning() BuildConfiguration case. Returns a map from original header Artifact to
   * the output Artifact of grepping over it. The return value only includes entries for generated
   * sources or headers when --extract_generated_inclusions is enabled.
   *
   * <p>Previously, incremental rebuilds redid all include scanning work for a given .cc source in
   * serial. For high-latency file systems, this could cause performance problems if many headers
   * are generated.
   */
  @Nullable
  public static final List<PregreppedHeader> createExtractInclusions(
      RuleContext ruleContext, CppSemantics semantics, Iterable<Artifact> prerequisites) {
    List<PregreppedHeader> extractions = new ArrayList<>();
    HashSet<Artifact> alreadyProcessedHeaders = new HashSet<>();
    for (Artifact prerequisite : prerequisites) {
      if (alreadyProcessedHeaders.contains(prerequisite)) {
        // Don't create duplicate actions just because user specified same header file twice.
        continue;
      }
      alreadyProcessedHeaders.add(prerequisite);

      Artifact scanned = createExtractInclusions(ruleContext, semantics, prerequisite);
      if (scanned != null) {
        extractions.add(PregreppedHeader.create(prerequisite, scanned));
      }
    }
    return extractions;
  }

  /**
   * Creates a grep-includes ExtractInclusions action for generated  sources/headers in the
   * needsIncludeScanning() BuildConfiguration case.
   *
   * <p>Previously, incremental rebuilds redid all include scanning work for a given
   * .cc source in serial. For high-latency file systems, this could cause
   * performance problems if many headers are generated.
   */
  private static final Artifact createExtractInclusions(
      RuleContext ruleContext, CppSemantics semantics, Artifact prerequisite) {
    if (ruleContext != null
        && semantics.needsIncludeScanning(ruleContext)
        && !prerequisite.isSourceArtifact()
        && CPP_FILETYPES.matches(prerequisite.getFilename())) {
      Artifact scanned = getIncludesOutput(ruleContext, prerequisite);
      Artifact grepIncludes = ruleContext.getPrerequisiteArtifact("$grep_includes", Mode.HOST);
      ruleContext.registerAction(
          new ExtractInclusionAction(
              ruleContext.getActionOwner(), prerequisite, scanned, grepIncludes));
      return scanned;
    }
    return null;
  }

  private static Artifact getIncludesOutput(RuleContext ruleContext, Artifact src) {
    Preconditions.checkArgument(!src.isSourceArtifact(), src);
    return ruleContext.getGenfilesArtifact(
        src.getRootRelativePath().replaceName(src.getFilename() + GREPPED_INCLUDES_SUFFIX));
  }

  /**
   * Returns the linked artifact for linux.
   *
   * @param ruleContext the ruleContext to be used to scope the artifact
   * @param config the configuration to be used to scope the artifact
   * @param linkType the type of artifact, used to determine extension
   */
  public static Artifact getLinuxLinkedArtifact(
      RuleContext ruleContext, BuildConfiguration config, LinkTargetType linkType) {
    return getLinuxLinkedArtifact(ruleContext, config, linkType, "");
  }

  /** Returns the linked artifact with the given suffix for linux. */
  public static Artifact getLinuxLinkedArtifact(
      RuleContext ruleContext,
      BuildConfiguration config,
      LinkTargetType linkType,
      String linkedArtifactNameSuffix) {
    PathFragment name = PathFragment.create(ruleContext.getLabel().getName());
    if (linkType != LinkTargetType.EXECUTABLE) {
      name = name.replaceName(
          "lib" + name.getBaseName() + linkedArtifactNameSuffix  + linkType.getExtension());
    }

    return ruleContext.getPackageRelativeArtifact(
        name, config.getBinDirectory(ruleContext.getRule().getRepository()));
  }

  /**
   * Emits a warning on the rule if there are identical linkstamp artifacts with different {@code
   * CcCompilationContextInfo}s.
   */
  public static void checkLinkstampsUnique(RuleErrorConsumer listener, CcLinkParams linkParams) {
    Map<Artifact, NestedSet<Artifact>> result = new LinkedHashMap<>();
    for (Linkstamp pair : linkParams.getLinkstamps()) {
      Artifact artifact = pair.getArtifact();
      if (result.containsKey(artifact)) {
        listener.ruleWarning("rule inherits the '" + artifact.toDetailString()
            + "' linkstamp file from more than one cc_library rule");
      }
      result.put(artifact, pair.getDeclaredIncludeSrcs());
    }
  }

  public static void addTransitiveLipoInfoForCommonAttributes(
      RuleContext ruleContext,
      CcCompilationOutputs outputs,
      NestedSetBuilder<IncludeScannable> scannableBuilder) {

    TransitiveLipoInfoProvider stl = null;
    if (ruleContext.getRule().getAttributeDefinition(":stl") != null
        && ruleContext.getPrerequisite(":stl", Mode.TARGET) != null) {
      // If the attribute is defined, it is never null.
      stl = ruleContext.getPrerequisite(":stl", Mode.TARGET)
          .getProvider(TransitiveLipoInfoProvider.class);
    }
    if (stl != null) {
      scannableBuilder.addTransitive(stl.getTransitiveIncludeScannables());
    }

    for (TransitiveLipoInfoProvider dep :
        ruleContext.getPrerequisites("deps", Mode.TARGET, TransitiveLipoInfoProvider.class)) {
      scannableBuilder.addTransitive(dep.getTransitiveIncludeScannables());
    }

    if (ruleContext.attributes().has("malloc", LABEL)) {
      TransitiveInfoCollection malloc = mallocForTarget(ruleContext);
      TransitiveLipoInfoProvider provider = malloc.getProvider(TransitiveLipoInfoProvider.class);
      if (provider != null) {
        scannableBuilder.addTransitive(provider.getTransitiveIncludeScannables());
      }
    }

    for (IncludeScannable scannable : outputs.getLipoScannables()) {
      Preconditions.checkState(scannable.getIncludeScannerSources().size() == 1);
      scannableBuilder.add(scannable);
    }
  }

  // TODO(bazel-team): figure out a way to merge these 2 methods. See the Todo in
  // CcCommonConfiguredTarget.noCoptsMatches().
  /**
   * Determines if we should apply -fPIC for this rule's C++ compilations. This determination is
   * generally made by the global C++ configuration settings "needsPic" and "usePicForBinaries".
   * However, an individual rule may override these settings by applying -fPIC" to its "nocopts"
   * attribute. This allows incompatible rules to "opt out" of global PIC settings (see bug:
   * "Provide a way to turn off -fPIC for targets that can't be built that way").
   *
   * @param ruleContext the context of the rule to check
   * @return true if this rule's compilations should apply -fPIC, false otherwise
   */
  public static boolean usePicForDynamicLibraries(
      RuleContext ruleContext, CcToolchainProvider toolchain) {
    return ruleContext.getFragment(CppConfiguration.class).forcePic()
        || toolchain.toolchainNeedsPic();
  }

  /** Returns whether binaries must be compiled with position independent code. */
  public static boolean usePicForBinaries(RuleContext ruleContext, CcToolchainProvider toolchain) {
    CppConfiguration config = ruleContext.getFragment(CppConfiguration.class);
    if (CcCommon.noCoptsMatches("-fPIC", ruleContext)) {
      return false;
    }
    return config.forcePic()
        || (toolchain.toolchainNeedsPic() && config.getCompilationMode() != CompilationMode.OPT);
  }

  /**
   * Returns true if shared libraries must be compiled with position independent code for the build
   * implied by the given config and toolchain.
   */

  /**
   * Returns the LIPO context provider for configured target,
   * or null if such a provider doesn't exist.
   */
  public static LipoContextProvider getLipoContextProvider(RuleContext ruleContext) {
    if (ruleContext
            .getRule()
            .getAttributeDefinition(TransitiveLipoInfoProvider.LIPO_CONTEXT_COLLECTOR)
        == null) {
      return null;
    }

    TransitiveInfoCollection dep =
        ruleContext.getPrerequisite(
            TransitiveLipoInfoProvider.LIPO_CONTEXT_COLLECTOR, Mode.DONT_CHECK);
    return (dep != null) ? dep.getProvider(LipoContextProvider.class) : null;
  }

  /**
   * Creates a CppModuleMap object for pure c++ builds. The module map artifact becomes a candidate
   * input to a CppCompileAction.
   */
  public static CppModuleMap createDefaultCppModuleMap(RuleContext ruleContext, String suffix) {
    // Create the module map artifact as a genfile.
    Artifact mapFile =
        ruleContext.getPackageRelativeArtifact(
            ruleContext.getLabel().getName()
                + suffix
                + Iterables.getOnlyElement(CppFileTypes.CPP_MODULE_MAP.getExtensions()),
            ruleContext
                .getConfiguration()
                .getGenfilesDirectory(ruleContext.getRule().getRepository()));
    return new CppModuleMap(mapFile, ruleContext.getLabel().toString());
  }

  /**
   * Returns a middleman for all files to build for the given configured target, substituting shared
   * library artifacts with corresponding solib symlinks. If multiple calls are made, then it
   * returns the same artifact for configurations with the same internal directory.
   *
   * <p>The resulting middleman only aggregates the inputs and must be expanded before populating
   * the set of files necessary to execute an action.
   */
  static List<Artifact> getAggregatingMiddlemanForCppRuntimes(
      RuleContext ruleContext,
      String purpose,
      Iterable<Artifact> artifacts,
      String solibDir,
      String solibDirOverride,
      BuildConfiguration configuration) {
    return getMiddlemanInternal(
        ruleContext,
        ruleContext.getActionOwner(),
        purpose,
        artifacts,
        true,
        true,
        solibDir,
        solibDirOverride,
        configuration);
  }

  @VisibleForTesting
  public static List<Artifact> getAggregatingMiddlemanForTesting(
      RuleContext ruleContext,
      ActionOwner owner,
      String purpose,
      Iterable<Artifact> artifacts,
      boolean useSolibSymlinks,
      String solibDir,
      BuildConfiguration configuration) {
    return getMiddlemanInternal(
        ruleContext,
        owner,
        purpose,
        artifacts,
        useSolibSymlinks,
        false,
        solibDir,
        null,
        configuration);
  }

  /** Internal implementation for getAggregatingMiddlemanForCppRuntimes. */
  private static List<Artifact> getMiddlemanInternal(
      RuleContext ruleContext,
      ActionOwner actionOwner,
      String purpose,
      Iterable<Artifact> artifacts,
      boolean useSolibSymlinks,
      boolean isCppRuntime,
      String solibDir,
      String solibDirOverride,
      BuildConfiguration configuration) {
    MiddlemanFactory factory = ruleContext.getAnalysisEnvironment().getMiddlemanFactory();
    if (useSolibSymlinks) {
      List<Artifact> symlinkedArtifacts = new ArrayList<>();
      for (Artifact artifact : artifacts) {
        Preconditions.checkState(Link.SHARED_LIBRARY_FILETYPES.matches(artifact.getFilename()));
        symlinkedArtifacts.add(
            isCppRuntime
                ? SolibSymlinkAction.getCppRuntimeSymlink(
                    ruleContext, artifact, solibDir, solibDirOverride, configuration)
                : SolibSymlinkAction.getDynamicLibrarySymlink(
                    ruleContext, solibDir, artifact, false, true, configuration));
      }
      artifacts = symlinkedArtifacts;
      purpose += "_with_solib";
    }
    return ImmutableList.of(
        factory.createMiddlemanAllowMultiple(ruleContext.getAnalysisEnvironment(), actionOwner,
            ruleContext.getPackageDirectory(), purpose, artifacts,
            configuration.getMiddlemanDirectory(ruleContext.getRule().getRepository())));
  }

  /**
   * Returns the FDO build subtype.
   */
  public static String getFdoBuildStamp(RuleContext ruleContext, FdoSupport fdoSupport) {
    CppConfiguration cppConfiguration = ruleContext.getFragment(CppConfiguration.class);
    if (fdoSupport.isAutoFdoEnabled()) {
      return (cppConfiguration.getLipoMode() == LipoMode.BINARY) ? "ALIPO" : "AFDO";
    }
    if (cppConfiguration.isFdo()) {
      return (cppConfiguration.getLipoMode() == LipoMode.BINARY) ? "LIPO" : "FDO";
    }
    return null;
  }

  /**
   * Returns a relative path to the bin directory for data in AutoFDO LIPO mode.
   */
  public static PathFragment getLipoDataBinFragment(BuildConfiguration configuration) {
    PathFragment parent = configuration.getBinFragment().getParentDirectory();
    return parent.replaceName(parent.getBaseName() + "-lipodata")
        .getChild(configuration.getBinFragment().getBaseName());
  }

  /**
   * Returns a relative path to the genfiles directory for data in AutoFDO LIPO mode.
   */
  public static PathFragment getLipoDataGenfilesFragment(BuildConfiguration configuration) {
    PathFragment parent = configuration.getGenfilesFragment().getParentDirectory();
    return parent.replaceName(parent.getBaseName() + "-lipodata")
        .getChild(configuration.getGenfilesFragment().getBaseName());
  }

  /** Creates an action to strip an executable. */
  public static void createStripAction(
      RuleContext context,
      CcToolchainProvider toolchain,
      CppConfiguration cppConfiguration,
      Artifact input,
      Artifact output,
      FeatureConfiguration featureConfiguration) {
    if (featureConfiguration.isEnabled(CppRuleClasses.NO_STRIPPING)) {
      context.registerAction(
          new SymlinkAction(
              context.getActionOwner(),
              input,
              output,
              "Symlinking original binary as stripped binary"));
      return;
    }

    if (!featureConfiguration.actionIsConfigured(CppCompileAction.STRIP_ACTION_NAME)) {
      context.ruleError("Expected action_config for 'strip' to be configured.");
      return;
    }

    Tool stripTool =
        Preconditions.checkNotNull(
            featureConfiguration.getToolForAction(CppCompileAction.STRIP_ACTION_NAME));
    Variables variables =
        new Variables.Builder(toolchain.getBuildVariables())
            .addStringVariable(
                StripBuildVariables.OUTPUT_FILE.getVariableName(), output.getExecPathString())
            .addStringSequenceVariable(
                StripBuildVariables.STRIPOPTS.getVariableName(), cppConfiguration.getStripOpts())
            .addStringVariable(CcCommon.INPUT_FILE_VARIABLE_NAME, input.getExecPathString())
            .build();
    ImmutableList<String> commandLine =
        ImmutableList.copyOf(
            featureConfiguration.getCommandLine(CppCompileAction.STRIP_ACTION_NAME, variables));
    ImmutableMap.Builder<String, String> executionInfoBuilder = ImmutableMap.builder();
    for (String executionRequirement : stripTool.getExecutionRequirements()) {
      executionInfoBuilder.put(executionRequirement, "");
    }
    Action[] stripAction =
        new SpawnAction.Builder()
            .addInput(input)
            .addTransitiveInputs(toolchain.getStrip())
            .addOutput(output)
            .useDefaultShellEnvironment()
            .setExecutable(stripTool.getToolPathFragment())
            .setExecutionInfo(executionInfoBuilder.build())
            .setProgressMessage("Stripping %s for %s", output.prettyPrint(), context.getLabel())
            .setMnemonic("CcStrip")
            .addCommandLine(CustomCommandLine.builder().addAll(commandLine).build())
            .build(context);
    context.registerAction(stripAction);
  }

  public static void maybeAddStaticLinkMarkerProvider(RuleConfiguredTargetBuilder builder,
      RuleContext ruleContext) {
    boolean staticallyLinked = false;
    CppConfiguration cppConfiguration = ruleContext.getFragment(CppConfiguration.class);
    if (ruleContext.getFeatures().contains("fully_static_link")) {
      staticallyLinked = true;
    } else if (cppConfiguration.hasStaticLinkOption()) {
      staticallyLinked = true;
    } else if (ruleContext.attributes().has("linkopts", Type.STRING_LIST)
        && ruleContext.attributes().get("linkopts", Type.STRING_LIST).contains("-static")) {
      staticallyLinked = true;
    }

    if (staticallyLinked) {
      builder.add(StaticallyLinkedMarkerProvider.class, new StaticallyLinkedMarkerProvider(true));
    }
  }

  static Artifact getCompileOutputArtifact(RuleContext ruleContext, String outputName,
      BuildConfiguration config) {
    PathFragment objectDir = getObjDirectory(ruleContext.getLabel());
    return ruleContext.getDerivedArtifact(objectDir.getRelative(outputName),
        config.getBinDirectory(ruleContext.getRule().getRepository()));
  }

  /** Returns the corresponding compiled TreeArtifact given the source TreeArtifact. */
  public static SpecialArtifact getCompileOutputTreeArtifact(
      RuleContext ruleContext, Artifact sourceTreeArtifact, String outputName, boolean usePic) {
    PathFragment objectDir = getObjDirectory(ruleContext.getLabel(), usePic);

    return ruleContext.getTreeArtifact(
        objectDir.getRelative(outputName), sourceTreeArtifact.getRoot());
  }

  static String getArtifactNameForCategory(
      RuleContext ruleContext,
      CcToolchainProvider toolchain,
      ArtifactCategory category,
      String outputName)
      throws RuleErrorException {
    try {
      return toolchain.getFeatures().getArtifactNameForCategory(category, outputName);
    } catch (InvalidConfigurationException e) {
      ruleContext.throwWithRuleError(e.getMessage());
      throw new IllegalStateException("Should not be reached");
    }
  }

  static String getDotdFileName(
      RuleContext ruleContext,
      CcToolchainProvider toolchain,
      ArtifactCategory outputCategory,
      String outputName)
      throws RuleErrorException {
    String baseName = outputCategory == ArtifactCategory.OBJECT_FILE
        || outputCategory == ArtifactCategory.PROCESSED_HEADER
        ? outputName
        : getArtifactNameForCategory(ruleContext, toolchain, outputCategory, outputName);

    return getArtifactNameForCategory(
        ruleContext, toolchain, ArtifactCategory.INCLUDED_FILE_LIST, baseName);
  }

  /**
   * Returns true when {@link CppRuleClasses#WINDOWS_EXPORT_ALL_SYMBOLS} feature is enabled and
   * {@link CppRuleClasses#NO_WINDOWS_EXPORT_ALL_SYMBOLS} feature is not enabled and no custom DEF
   * file is specified in win_def_file attribute.
   */
  public static boolean shouldUseGeneratedDefFile(
      RuleContext ruleContext, FeatureConfiguration featureConfiguration) {
    return featureConfiguration.isEnabled(CppRuleClasses.WINDOWS_EXPORT_ALL_SYMBOLS)
        && !featureConfiguration.isEnabled(CppRuleClasses.NO_WINDOWS_EXPORT_ALL_SYMBOLS)
        && ruleContext.getPrerequisiteArtifact("win_def_file", Mode.TARGET) == null;
  }

  /**
   * Create actions for parsing object files to generate a DEF file, should only be used when
   * targeting Windows.
   *
   * @param defParser The tool we use to parse object files for generating the DEF file.
   * @param objectFiles A list of object files to parse
   * @param dllName The DLL name to be written into the DEF file, it specifies which DLL is required
   *     at runtime
   * @return The DEF file artifact.
   */
  public static Artifact createDefFileActions(
      RuleContext ruleContext,
      Artifact defParser,
      ImmutableList<Artifact> objectFiles,
      String dllName) {
    Artifact defFile = ruleContext.getBinArtifact(ruleContext.getLabel().getName() + ".def");
    CustomCommandLine.Builder argv = new CustomCommandLine.Builder();
    for (Artifact objectFile : objectFiles) {
      argv.addDynamicString(objectFile.getExecPathString());
    }

    ruleContext.registerAction(
        new SpawnAction.Builder()
            .addInputs(objectFiles)
            .addOutput(defFile)
            .setExecutable(defParser)
            .useDefaultShellEnvironment()
            .addCommandLine(
                CustomCommandLine.builder().addExecPath(defFile).addDynamicString(dllName).build())
            .addCommandLine(
                argv.build(),
                ParamFileInfo.builder(ParameterFile.ParameterFileType.SHELL_QUOTED)
                    .setCharset(UTF_8)
                    .setUseAlways(true)
                    .build())
            .setMnemonic("DefParser")
            .build(ruleContext));
    return defFile;
  }

  /**
   * Returns true if the build implied by the given config and toolchain uses --start-lib/--end-lib
   * ld options.
   */
  public static boolean useStartEndLib(CppConfiguration config, CcToolchainProvider toolchain) {
    return config.startEndLibIsRequested() && toolchain.supportsStartEndLib();
  }

  /**
   * Returns the type of archives being used by the build implied by the given config and toolchain.
   */
  public static Link.ArchiveType getArchiveType(
      CppConfiguration config, CcToolchainProvider toolchain) {
    return useStartEndLib(config, toolchain)
        ? Link.ArchiveType.START_END_LIB
        : Link.ArchiveType.REGULAR;
  }

  /**
   * Returns true if interface shared objects should be used in the build implied by the given
   * config and toolchain.
   */
  public static boolean useInterfaceSharedObjects(
      CppConfiguration config, CcToolchainProvider toolchain) {
    return toolchain.supportsInterfaceSharedObjects() && config.getUseInterfaceSharedObjects();
  }

}
