// Copyright 2016 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 java.nio.charset.StandardCharsets.ISO_8859_1;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
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.ParameterFile;
import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.collect.CollectionUtils;
import com.google.devtools.build.lib.collect.ImmutableIterable;
import com.google.devtools.build.lib.collect.IterablesChain;
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.RuleErrorConsumer;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariablesExtension;
import com.google.devtools.build.lib.rules.cpp.CppLinkAction.Context;
import com.google.devtools.build.lib.rules.cpp.CppLinkAction.LinkArtifactFactory;
import com.google.devtools.build.lib.rules.cpp.Link.LinkStaticness;
import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
import com.google.devtools.build.lib.rules.cpp.Link.Staticness;
import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/** Builder class to construct {@link CppLinkAction}s. */
public class CppLinkActionBuilder {

  /** A build variable for clang flags that set the root of the linker search path. */
  public static final String RUNTIME_ROOT_FLAGS_VARIABLE = "runtime_root_flags";

  /** A build variable for entries in the linker search path. */
  public static final String RUNTIME_ROOT_ENTRIES_VARIABLE = "runtime_root_entries";

  /**
   * A build variable for options applying to specific libraries in the linker invocation that
   * either identify a library to be linked or add a directory to the runtime library search path.
   */
  public static final String LIBOPTS_VARIABLE = "libopts";

  /**
   * A build variable for flags providing files to link as inputs in the linker invocation that
   * should not go in a -whole_archive block.
   */
  public static final String LINKER_INPUT_PARAMS_VARIABLE = "linker_input_params";

  /**
   * A build variable for flags providing files to link as inputs in the linker invocation that
   * should go in a -whole_archive block.
   */
  public static final String WHOLE_ARCHIVE_LINKER_INPUT_PARAMS_VARIABLE =
      "whole_archive_linker_params";

  /** A build variable whose presence indicates that whole archive flags should be applied. */
  public static final String GLOBAL_WHOLE_ARCHIVE_VARIABLE = "global_whole_archive";

  /** A build variable for the execpath of the output of the linker. */
  public static final String OUTPUT_EXECPATH_VARIABLE = "output_execpath";

  /** A build variable setting if interface library should be generated. */
  public static final String GENERATE_INTERFACE_LIBRARY_VARIABLE = "generate_interface_library";

  /** A build variable for the path to the interface library builder tool. */
  public static final String INTERFACE_LIBRARY_BUILDER_VARIABLE = "interface_library_builder_path";

  /** A build variable for the input for the interface library builder tool. */
  public static final String INTERFACE_LIBRARY_INPUT_VARIABLE = "interface_library_input_path";

  /** A build variable for the path where to generate interface library using the builder tool. */
  public static final String INTERFACE_LIBRARY_OUTPUT_VARIABLE = "interface_library_output_path";

  /**
   * A build variable that is set to indicate a mostly static linking for which the linked binary
   * should be piped to /dev/null.
   */
  public static final String SKIP_MOSTLY_STATIC_VARIABLE = "skip_mostly_static";

  /** A build variable giving a path to which to write symbol counts. */
  public static final String SYMBOL_COUNTS_OUTPUT_VARIABLE = "symbol_counts_output";

  /** A build variable giving linkstamp paths. */
  public static final String LINKSTAMP_PATHS_VARIABLE = "linkstamp_paths";

  /** A build variable whose presence indicates that PIC code should be generated. */
  public static final String FORCE_PIC_VARIABLE = "force_pic";

  // Builder-only
  // Null when invoked from tests (e.g. via createTestBuilder).
  @Nullable private final RuleContext ruleContext;
  private final AnalysisEnvironment analysisEnvironment;
  private final Artifact output;
  @Nullable private String mnemonic;

  // can be null for CppLinkAction.createTestBuilder()
  @Nullable private final CcToolchainProvider toolchain;
  private Artifact interfaceOutput;
  private Artifact symbolCounts;
  private PathFragment runtimeSolibDir;
  protected final BuildConfiguration configuration;
  private final CppConfiguration cppConfiguration;
  private FeatureConfiguration featureConfiguration;

  // Morally equivalent with {@link Context}, except these are mutable.
  // Keep these in sync with {@link Context}.
  private final Set<LinkerInput> objectFiles = new LinkedHashSet<>();
  private final Set<Artifact> nonCodeInputs = new LinkedHashSet<>();
  private final NestedSetBuilder<LibraryToLink> libraries = NestedSetBuilder.linkOrder();
  private NestedSet<Artifact> crosstoolInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
  private Artifact runtimeMiddleman;
  private ArtifactCategory runtimeType = null;
  private NestedSet<Artifact> runtimeInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
  private final NestedSetBuilder<Artifact> compilationInputs = NestedSetBuilder.stableOrder();
  private final Set<Artifact> linkstamps = new LinkedHashSet<>();
  private List<String> linkstampOptions = new ArrayList<>();
  private final List<String> linkopts = new ArrayList<>();
  private LinkTargetType linkType = LinkTargetType.STATIC_LIBRARY;
  private LinkStaticness linkStaticness = LinkStaticness.FULLY_STATIC;
  private String libraryIdentifier = null;
  private List<Artifact> ltoBitcodeFiles = new ArrayList<>();

  private boolean fake;
  private boolean isNativeDeps;
  private boolean useTestOnlyFlags;
  private boolean wholeArchive;
  private LinkArtifactFactory linkArtifactFactory = CppLinkAction.DEFAULT_ARTIFACT_FACTORY;

  private boolean isLTOIndexing = false;
  private Iterable<LTOBackendArtifacts> allLTOArtifacts = null;
  
  private final List<VariablesExtension> variablesExtensions = new ArrayList<>();
  private final NestedSetBuilder<Artifact> linkActionInputs = NestedSetBuilder.stableOrder();

  /**
   * Creates a builder that builds {@link CppLinkAction} instances.
   *
   * @param ruleContext the rule that owns the action
   * @param output the output artifact
   */
  public CppLinkActionBuilder(RuleContext ruleContext, Artifact output) {
    this(
        ruleContext,
        output,
        ruleContext.getConfiguration(),
        ruleContext.getAnalysisEnvironment(),
        CppHelper.getToolchain(ruleContext));
  }

  /**
   * Creates a builder that builds {@link CppLinkAction} instances.
   *
   * @param ruleContext the rule that owns the action
   * @param output the output artifact
   */
  public CppLinkActionBuilder(
      RuleContext ruleContext,
      Artifact output,
      BuildConfiguration configuration,
      CcToolchainProvider toolchain) {
    this(ruleContext, output, configuration, ruleContext.getAnalysisEnvironment(), toolchain);
  }

  /**
   * Creates a builder that builds {@link CppLinkAction}s.
   *
   * @param ruleContext the rule that owns the action
   * @param output the output artifact
   * @param configuration the configuration used to determine the tool chain and the default link
   *     options
   */
  private CppLinkActionBuilder(
      @Nullable RuleContext ruleContext,
      Artifact output,
      BuildConfiguration configuration,
      AnalysisEnvironment analysisEnvironment,
      CcToolchainProvider toolchain) {
    this.ruleContext = ruleContext;
    this.analysisEnvironment = Preconditions.checkNotNull(analysisEnvironment);
    this.output = Preconditions.checkNotNull(output);
    this.configuration = Preconditions.checkNotNull(configuration);
    this.cppConfiguration = configuration.getFragment(CppConfiguration.class);
    this.toolchain = toolchain;
    if (cppConfiguration.supportsEmbeddedRuntimes() && toolchain != null) {
      runtimeSolibDir = toolchain.getDynamicRuntimeSolibDir();
    }
  }

  /**
   * Given a Context, creates a Builder that builds {@link CppLinkAction}s. Note well: Keep the
   * Builder->Context and Context->Builder transforms consistent!
   *
   * @param ruleContext the rule that owns the action
   * @param output the output artifact
   * @param linkContext an immutable CppLinkAction.Context from the original builder
   */
  public CppLinkActionBuilder(
      RuleContext ruleContext,
      Artifact output,
      Context linkContext,
      BuildConfiguration configuration) {
    // These Builder-only fields get set in the constructor:
    //   ruleContext, analysisEnvironment, outputPath, configuration, runtimeSolibDir
    this(
        ruleContext,
        output,
        configuration,
        ruleContext.getAnalysisEnvironment(),
        CppHelper.getToolchain(ruleContext));
    Preconditions.checkNotNull(linkContext);

    // All linkContext fields should be transferred to this Builder.
    this.objectFiles.addAll(linkContext.objectFiles);
    this.nonCodeInputs.addAll(linkContext.nonCodeInputs);
    this.libraries.addTransitive(linkContext.libraries);
    this.crosstoolInputs = linkContext.crosstoolInputs;
    this.runtimeMiddleman = linkContext.runtimeMiddleman;
    this.runtimeInputs = linkContext.runtimeInputs;
    this.runtimeType = linkContext.runtimeType;
    this.compilationInputs.addTransitive(linkContext.compilationInputs);
    this.linkstamps.addAll(linkContext.linkstamps);
    this.linkopts.addAll(linkContext.linkopts);
    this.linkType = linkContext.linkType;
    this.linkStaticness = linkContext.linkStaticness;
    this.fake = linkContext.fake;
    this.isNativeDeps = linkContext.isNativeDeps;
    this.useTestOnlyFlags = linkContext.useTestOnlyFlags;
  }

  /** Returns the action name for purposes of querying the crosstool. */
  private String getActionName() {
    return linkType.getActionName();
  }
  
  /** Returns linker inputs that are not libraries. */
  public Set<LinkerInput> getObjectFiles() {
    return objectFiles;
  }

  public Set<Artifact> getNonCodeInputs() {
    return nonCodeInputs;
  }

  /**
   * Returns linker inputs that are libraries.
   */
  public NestedSetBuilder<LibraryToLink> getLibraries() {
    return libraries;
  }

  /**
   * Returns inputs arising from the crosstool.
   */
  public NestedSet<Artifact> getCrosstoolInputs() {
    return this.crosstoolInputs;
  }
  
  /**
   * Returns the runtime middleman artifact.
   */
  public Artifact getRuntimeMiddleman() {
    return this.runtimeMiddleman;
  }
  
  /**
   * Returns runtime inputs for this link action.
   */
  public NestedSet<Artifact> getRuntimeInputs() {
    return this.runtimeInputs;
  }

  public ArtifactCategory getRuntimeType() {
    return runtimeType;
  }

  /**
   * Returns compilation inputs for this link action.
   */
  public final NestedSetBuilder<Artifact> getCompilationInputs() {
    return this.compilationInputs;
  }
  
  /**
   * Returns linkstamps for this link action.
   */
  public final Set<Artifact> getLinkstamps() {
    return this.linkstamps;
  }
  /**
   * Returns linkstamp options for this link action.
   */
  public List<String> getLinkstampOptions() {
    return this.linkstampOptions;
  }

  protected Artifact getInterfaceSoBuilder() {
    return analysisEnvironment.getEmbeddedToolArtifact(CppRuleClasses.BUILD_INTERFACE_SO);
  }
  
  /**
   * Returns command line options for this link action.
   */
  public final List<String> getLinkopts() {
    return this.linkopts;
  }
  
  /**
   * Returns the type of this link action.
   */
  public LinkTargetType getLinkType() {
    return this.linkType;
  }
  /**
   * Returns the staticness of this link action.
   */
  public LinkStaticness getLinkStaticness() {
    return this.linkStaticness;
  }
  /**
   * Returns lto bitcode files for this link action.
   */
  public List<Artifact> getLtoBitcodeFiles() {
    return this.ltoBitcodeFiles;
  }

  /**
   * Returns true for a cc_fake_binary.
   */
  public boolean isFake() {
    return this.fake;
  }
  
  /**
   * Returns true for native dependencies of another language.
   */
  public boolean isNativeDeps() {
    return this.isNativeDeps;
  }
 
  public CppLinkActionBuilder setLinkArtifactFactory(LinkArtifactFactory linkArtifactFactory) {
    this.linkArtifactFactory = linkArtifactFactory;
    return this;
  }
  
  /**
   * Returns true if this link action uses test only flags.
   */
  public boolean useTestOnlyFlags() {
    return this.useTestOnlyFlags;
  }

  private Iterable<LTOBackendArtifacts> createLTOArtifacts(
      PathFragment ltoOutputRootPrefix, NestedSet<LibraryToLink> uniqueLibraries) {
    Set<Artifact> compiled = new LinkedHashSet<>();
    for (LibraryToLink lib : uniqueLibraries) {
      Iterables.addAll(compiled, lib.getLTOBitcodeFiles());
    }

    // This flattens the set of object files, so for M binaries and N .o files,
    // this is O(M*N). If we had a nested set of .o files, we could have O(M + N) instead.
    Map<PathFragment, Artifact> allBitcode = new HashMap<>();
    for (LibraryToLink lib : uniqueLibraries) {
      if (!lib.containsObjectFiles()) {
        continue;
      }
      for (Artifact a : lib.getObjectFiles()) {
        if (compiled.contains(a)) {
          allBitcode.put(a.getExecPath(), a);
        }
      }
    }
    for (LinkerInput input : objectFiles) {
      if (this.ltoBitcodeFiles.contains(input.getArtifact())) {
        allBitcode.put(input.getArtifact().getExecPath(), input.getArtifact());
      }
    }

    ImmutableList.Builder<LTOBackendArtifacts> ltoOutputs = ImmutableList.builder();
    for (Artifact a : allBitcode.values()) {
      LTOBackendArtifacts ltoArtifacts =
          new LTOBackendArtifacts(
              ltoOutputRootPrefix, a, allBitcode, ruleContext, configuration, linkArtifactFactory);
      ltoOutputs.add(ltoArtifacts);
    }
    return ltoOutputs.build();
  }

  @VisibleForTesting
  boolean canSplitCommandLine() {
    if (toolchain == null || !toolchain.supportsParamFiles()) {
      return false;
    }

    switch (linkType) {
        // We currently can't split dynamic library links if they have interface outputs. That was
        // probably an unintended side effect of the change that introduced interface outputs.
      case DYNAMIC_LIBRARY:
        return interfaceOutput == null;
      case EXECUTABLE:
      case STATIC_LIBRARY:
      case PIC_STATIC_LIBRARY:
      case ALWAYS_LINK_STATIC_LIBRARY:
      case ALWAYS_LINK_PIC_STATIC_LIBRARY:
        return true;

      default:
        return false;
    }
  }

  /** Builds the Action as configured and returns it. */
  public CppLinkAction build() throws InterruptedException {
    // Executable links do not have library identifiers.
    boolean hasIdentifier = (libraryIdentifier != null);
    boolean isExecutable = linkType.isExecutable();
    Preconditions.checkState(hasIdentifier != isExecutable);    

    if (interfaceOutput != null && (fake || linkType != LinkTargetType.DYNAMIC_LIBRARY)) {
      throw new RuntimeException(
          "Interface output can only be used " + "with non-fake DYNAMIC_LIBRARY targets");
    }

    final ImmutableList<Artifact> buildInfoHeaderArtifacts =
        !linkstamps.isEmpty()
            ? analysisEnvironment.getBuildInfo(ruleContext, CppBuildInfo.KEY, configuration)
            : ImmutableList.<Artifact>of();

    boolean needWholeArchive =
        wholeArchive
            || needWholeArchive(linkStaticness, linkType, linkopts, isNativeDeps, cppConfiguration);

    NestedSet<LibraryToLink> uniqueLibraries = libraries.build();
    final Iterable<Artifact> objectArtifacts = LinkerInputs.toLibraryArtifacts(objectFiles);

    final Iterable<LinkerInput> linkerInputs =
        IterablesChain.<LinkerInput>builder()
            .add(ImmutableList.copyOf(objectFiles))
            .add(
                ImmutableIterable.from(
                    Link.mergeInputsCmdLine(
                        uniqueLibraries, needWholeArchive, cppConfiguration.archiveType())))
            .build();

    // ruleContext can only be null during testing. This is kind of ugly.
    final ImmutableSet<String> features =
        (ruleContext == null) ? ImmutableSet.<String>of() : ruleContext.getFeatures();

    // For backwards compatibility, and for tests, we permit the link action to be
    // instantiated without a feature configuration.
    if (featureConfiguration == null) {
      if (toolchain != null) {
        featureConfiguration =
            CcCommon.configureFeatures(ruleContext, toolchain, CcLibraryHelper.SourceCategory.CC);
      } else {
        featureConfiguration = CcCommon.configureFeatures(ruleContext);
      }
    }

    final LibraryToLink outputLibrary = linkType.isExecutable()
        ? null
        : LinkerInputs.newInputLibrary(output,
            linkType.getLinkerOutput(),
            libraryIdentifier,
            objectArtifacts, this.ltoBitcodeFiles);
    final LibraryToLink interfaceOutputLibrary =
        (interfaceOutput == null)
            ? null
            : LinkerInputs.newInputLibrary(interfaceOutput,
                ArtifactCategory.DYNAMIC_LIBRARY,
                libraryIdentifier,
                objectArtifacts, this.ltoBitcodeFiles);

    final ImmutableMap<Artifact, Artifact> linkstampMap =
        mapLinkstampsToOutputs(linkstamps, ruleContext, configuration, output, linkArtifactFactory);

    PathFragment ltoOutputRootPrefix = null;
    if (isLTOIndexing && allLTOArtifacts == null) {
      ltoOutputRootPrefix =
          FileSystemUtils.appendExtension(
              output.getRootRelativePath(), ".lto");
      allLTOArtifacts = createLTOArtifacts(ltoOutputRootPrefix, uniqueLibraries);
    }

    PathFragment linkerParamsFileRootPath = null;
    @Nullable Artifact linkerParamsFile = null;
    if (allLTOArtifacts != null) {
      // Create artifact for the file that the LTO indexing step will emit
      // object file names into for any that were included in the link as
      // determined by the linker's symbol resolution. It will be used to
      // provide the inputs for the subsequent final native object link.
      // Note that the paths emitted into this file will have their prefixes
      // replaced with the final output directory, so they will be the paths
      // of the native object files not the input bitcode files.
      linkerParamsFileRootPath =
          ParameterFile.derivePath(output.getRootRelativePath(), "lto-final");
      linkerParamsFile =
          linkArtifactFactory.create(ruleContext, configuration, linkerParamsFileRootPath);
    }

    final ImmutableList<Artifact> actionOutputs;
    if (isLTOIndexing) {
      ImmutableList.Builder<Artifact> builder = ImmutableList.builder();
      for (LTOBackendArtifacts ltoA : allLTOArtifacts) {
        ltoA.addIndexingOutputs(builder);
      }
      if (linkerParamsFile != null) {
        builder.add(linkerParamsFile);
      }
      actionOutputs = builder.build();
    } else {
      actionOutputs =
          constructOutputs(
              output,
              linkstampMap.values(),
              interfaceOutputLibrary == null ? null : interfaceOutputLibrary.getArtifact(),
              symbolCounts);
    }

    ImmutableList<LinkerInput> runtimeLinkerInputs =
        ImmutableList.copyOf(LinkerInputs.simpleLinkerInputs(runtimeInputs, runtimeType));

    // Add build variables necessary to template link args into the crosstool.
    Variables.Builder buildVariablesBuilder = new Variables.Builder();
    CppLinkVariablesExtension variablesExtension =
        isLTOIndexing
            ? new CppLinkVariablesExtension(
                configuration,
                ImmutableMap.<Artifact, Artifact>of(),
                needWholeArchive,
                linkerInputs,
                runtimeLinkerInputs,
                null,
                linkerParamsFile,
                ltoOutputRootPrefix,
                null,
                null)
            : new CppLinkVariablesExtension(
                configuration,
                linkstampMap,
                needWholeArchive,
                linkerInputs,
                runtimeLinkerInputs,
                output,
                linkerParamsFile,
                PathFragment.EMPTY_FRAGMENT,
                getInterfaceSoBuilder(),
                interfaceOutput);
    variablesExtension.addVariables(buildVariablesBuilder);
    for (VariablesExtension extraVariablesExtension : variablesExtensions) {
      extraVariablesExtension.addVariables(buildVariablesBuilder);
    }
    Variables buildVariables = buildVariablesBuilder.build();

    PathFragment paramRootPath =
        ParameterFile.derivePath(output.getRootRelativePath(), (isLTOIndexing) ? "lto-index" : "2");

    @Nullable
    final Artifact paramFile =
        canSplitCommandLine()
            ? linkArtifactFactory.create(ruleContext, configuration, paramRootPath)
            : null;

    Preconditions.checkArgument(
        linkType != LinkTargetType.INTERFACE_DYNAMIC_LIBRARY,
        "you can't link an interface dynamic library directly");
    if (linkType != LinkTargetType.DYNAMIC_LIBRARY) {
      Preconditions.checkArgument(
          interfaceOutput == null,
          "interface output may only be non-null for dynamic library links");
    }
    if (linkType.staticness() == Staticness.STATIC) {
      // solib dir must be null for static links
      runtimeSolibDir = null;

      Preconditions.checkArgument(
          linkStaticness == LinkStaticness.FULLY_STATIC, "static library link must be static");
      Preconditions.checkArgument(
          symbolCounts == null, "the symbol counts output must be null for static links");
      Preconditions.checkArgument(
          !isNativeDeps, "the native deps flag must be false for static links");
      Preconditions.checkArgument(
          !needWholeArchive, "the need whole archive flag must be false for static links");
    }

    LinkCommandLine.Builder linkCommandLineBuilder =
        new LinkCommandLine.Builder(configuration, getOwner(), ruleContext)
            .setLinkerInputs(linkerInputs)
            .setRuntimeInputs(runtimeLinkerInputs)
            .setLinkTargetType(linkType)
            .setLinkStaticness(linkStaticness)
            .setFeatures(features)
            .setRuntimeSolibDir(linkType.staticness() == Staticness.STATIC ? null : runtimeSolibDir)
            .setNativeDeps(isNativeDeps)
            .setUseTestOnlyFlags(useTestOnlyFlags)
            .setParamFile(paramFile)
            .setToolchain(toolchain)
            .setBuildVariables(buildVariables)
            .setToolPath(getToolPath())
            .setFeatureConfiguration(featureConfiguration);

    // TODO(b/30228443): Refactor noWholeArchiveInputs into action_configs, and remove this.
    if (needWholeArchive) {
      linkCommandLineBuilder.setNoWholeArchiveFlags(variablesExtension.getNoWholeArchiveInputs());
    }

    if (!isLTOIndexing) {
      linkCommandLineBuilder
          .setOutput(output)
          .setBuildInfoHeaderArtifacts(buildInfoHeaderArtifacts)
          .setLinkstamps(linkstampMap)
          .setLinkopts(ImmutableList.copyOf(linkopts))
          .addLinkstampCompileOptions(linkstampOptions);
    } else {
      List<String> opts = new ArrayList<>(linkopts);
      opts.addAll(featureConfiguration.getCommandLine("lto-indexing", buildVariables));
      linkCommandLineBuilder.setLinkopts(ImmutableList.copyOf(opts));
    }

    LinkCommandLine linkCommandLine = linkCommandLineBuilder.build();

    // Compute the set of inputs - we only need stable order here.
    NestedSetBuilder<Artifact> dependencyInputsBuilder = NestedSetBuilder.stableOrder();
    dependencyInputsBuilder.addTransitive(crosstoolInputs);
    dependencyInputsBuilder.add(toolchain.getLinkDynamicLibraryTool());
    dependencyInputsBuilder.addTransitive(linkActionInputs.build());
    if (runtimeMiddleman != null) {
      dependencyInputsBuilder.add(runtimeMiddleman);
    }
    if (!isLTOIndexing) {
      dependencyInputsBuilder.addAll(buildInfoHeaderArtifacts);
      dependencyInputsBuilder.addAll(linkstamps);
      dependencyInputsBuilder.addTransitive(compilationInputs.build());
    }

    Iterable<Artifact> expandedInputs =
        LinkerInputs.toLibraryArtifacts(
            Link.mergeInputsDependencies(
                uniqueLibraries, needWholeArchive, cppConfiguration.archiveType()));
    Iterable<Artifact> expandedNonLibraryInputs = LinkerInputs.toLibraryArtifacts(objectFiles);

    if (!isLTOIndexing && allLTOArtifacts != null) {
      // We are doing LTO, and this is the real link, so substitute
      // the LTO bitcode files with the real object files they were translated into.
      Map<Artifact, Artifact> ltoMapping = new HashMap<>();
      for (LTOBackendArtifacts a : allLTOArtifacts) {
        ltoMapping.put(a.getBitcodeFile(), a.getObjectFile());
      }

      // Handle libraries.
      List<Artifact> renamedInputs = new ArrayList<>();
      for (Artifact a : expandedInputs) {
        Artifact renamed = ltoMapping.get(a);
        renamedInputs.add(renamed == null ? a : renamed);
      }
      expandedInputs = renamedInputs;

      // Handle non-libraries.
      List<Artifact> renamedNonLibraryInputs = new ArrayList<>();
      for (Artifact a : expandedNonLibraryInputs) {
        Artifact renamed = ltoMapping.get(a);
        renamedNonLibraryInputs.add(renamed == null ? a : renamed);
      }
      expandedNonLibraryInputs = renamedNonLibraryInputs;
    } else if (isLTOIndexing && allLTOArtifacts != null) {
      for (LTOBackendArtifacts a : allLTOArtifacts) {
        List<String> argv = new ArrayList<>();
        argv.addAll(cppConfiguration.getLinkOptions());
        argv.addAll(cppConfiguration.getCompilerOptions(features));
        a.setCommandLine(argv);
      }
    }

    // getPrimaryInput returns the first element, and that is a public interface - therefore the
    // order here is important.
    IterablesChain.Builder<Artifact> inputsBuilder =
        IterablesChain.<Artifact>builder()
            .add(ImmutableList.copyOf(expandedNonLibraryInputs))
            .add(ImmutableList.copyOf(nonCodeInputs))
            .add(dependencyInputsBuilder.build())
            .add(ImmutableIterable.from(expandedInputs));

    if (linkerParamsFile != null && !isLTOIndexing) {
      inputsBuilder.add(ImmutableList.of(linkerParamsFile));
    }
    if (linkCommandLine.getParamFile() != null) {
      inputsBuilder.add(ImmutableList.of(linkCommandLine.getParamFile()));
      Action parameterFileWriteAction =
          new ParameterFileWriteAction(
              getOwner(),
              paramFile,
              linkCommandLine.paramCmdLine(),
              ParameterFile.ParameterFileType.UNQUOTED,
              ISO_8859_1);
      analysisEnvironment.registerAction(parameterFileWriteAction);
    }

    ImmutableMap<String, String> toolchainEnv =
        featureConfiguration.getEnvironmentVariables(getActionName(), buildVariables);

    // If the crosstool uses action_configs to configure cc compilation, collect execution info
    // from there, otherwise, use no execution info.
    // TODO(b/27903698): Assert that the crosstool has an action_config for this action.
    ImmutableSet.Builder<String> executionRequirements = ImmutableSet.<String>builder();
    if (featureConfiguration.actionIsConfigured(getActionName())) {
      executionRequirements.addAll(
          featureConfiguration.getToolForAction(getActionName()).getExecutionRequirements());
    }

    return new CppLinkAction(
        getOwner(),
        mnemonic,
        inputsBuilder.deduplicate().build(),
        actionOutputs,
        cppConfiguration,
        outputLibrary,
        output,
        interfaceOutputLibrary,
        fake,
        isLTOIndexing,
        allLTOArtifacts,
        linkCommandLine,
        configuration.getVariableShellEnvironment(),
        configuration.getLocalShellEnvironment(),
        toolchainEnv,
        executionRequirements.build());
  }

  /**
   * Returns the tool path from feature configuration, if the tool in the configuration is sane, or
   * builtin tool, if configuration has a dummy value.
   */
  private String getToolPath() {
    if (!featureConfiguration.actionIsConfigured(linkType.getActionName())) {
      return null;
    }
    String toolPath =
        featureConfiguration
            .getToolForAction(linkType.getActionName())
            .getToolPath(cppConfiguration.getCrosstoolTopPathFragment())
            .getPathString();
    if (linkType.equals(LinkTargetType.DYNAMIC_LIBRARY)
        && !featureConfiguration.hasConfiguredLinkerPathInActionConfig()) {
      toolPath = toolchain.getLinkDynamicLibraryTool().getExecPathString();
    }
    return toolPath;
  }

  /** The default heuristic on whether we need to use whole-archive for the link. */
  private static boolean needWholeArchive(
      LinkStaticness staticness,
      LinkTargetType type,
      Collection<String> linkopts,
      boolean isNativeDeps,
      CppConfiguration cppConfig) {
    boolean fullyStatic = (staticness == LinkStaticness.FULLY_STATIC);
    boolean mostlyStatic = (staticness == LinkStaticness.MOSTLY_STATIC);
    boolean sharedLinkopts =
        type == LinkTargetType.DYNAMIC_LIBRARY
            || linkopts.contains("-shared")
            || cppConfig.getLinkOptions().contains("-shared");
    return (isNativeDeps || cppConfig.legacyWholeArchive())
        && (fullyStatic || mostlyStatic)
        && sharedLinkopts;
  }

  private static ImmutableList<Artifact> constructOutputs(
      Artifact primaryOutput, Collection<Artifact> outputList, Artifact... outputs) {
    return new ImmutableList.Builder<Artifact>()
        .add(primaryOutput)
        .addAll(outputList)
        .addAll(CollectionUtils.asListWithoutNulls(outputs))
        .build();
  }

  /**
   * Translates a collection of linkstamp source files to an immutable mapping from source files to
   * object files. In other words, given a set of source files, this method determines the output
   * path to which each file should be compiled.
   *
   * @param linkstamps collection of linkstamp source files
   * @param ruleContext the rule for which this link is being performed
   * @param outputBinary the binary output path for this link
   * @return an immutable map that pairs each source file with the corresponding object file that
   *     should be fed into the link
   */
  public static ImmutableMap<Artifact, Artifact> mapLinkstampsToOutputs(
      Collection<Artifact> linkstamps,
      RuleContext ruleContext,
      BuildConfiguration configuration,
      Artifact outputBinary,
      LinkArtifactFactory linkArtifactFactory) {
    ImmutableMap.Builder<Artifact, Artifact> mapBuilder = ImmutableMap.builder();

    PathFragment outputBinaryPath = outputBinary.getRootRelativePath();
    PathFragment stampOutputDirectory =
        outputBinaryPath
            .getParentDirectory()
            .getRelative("_objs")
            .getRelative(outputBinaryPath.getBaseName());

    for (Artifact linkstamp : linkstamps) {
      PathFragment stampOutputPath =
          stampOutputDirectory.getRelative(
              FileSystemUtils.replaceExtension(linkstamp.getRootRelativePath(), ".o"));
      mapBuilder.put(
          linkstamp,
          // Note that link stamp actions can be shared between link actions that output shared
          // native dep libraries.
          linkArtifactFactory.create(ruleContext, configuration, stampOutputPath));
    }
    return mapBuilder.build();
  }

  protected ActionOwner getOwner() {
    return ruleContext.getActionOwner();
  }
  
  /** Sets the mnemonic for the link action. */
  public CppLinkActionBuilder setMnemonic(String mnemonic) {
    this.mnemonic = mnemonic;
    return this;
  }

  /** Set the crosstool inputs required for the action. */
  public CppLinkActionBuilder setCrosstoolInputs(NestedSet<Artifact> inputs) {
    this.crosstoolInputs = inputs;
    return this;
  }

  /** Sets the feature configuration for the action. */
  public CppLinkActionBuilder setFeatureConfiguration(FeatureConfiguration featureConfiguration) {
    this.featureConfiguration = featureConfiguration;
    return this;
  }

  /**
   * This is the LTO indexing step, rather than the real link.
   *
   * <p>When using this, build() will store allLTOArtifacts as a side-effect so the next build()
   * call can emit the real link. Do not call addInput() between the two build() calls.
   */
  public CppLinkActionBuilder setLTOIndexing(boolean ltoIndexing) {
    this.isLTOIndexing = ltoIndexing;
    return this;
  }

  /** Sets the C++ runtime library inputs for the action. */
  public CppLinkActionBuilder setRuntimeInputs(
      ArtifactCategory runtimeType, Artifact middleman, NestedSet<Artifact> inputs) {
    Preconditions.checkArgument((middleman == null) == inputs.isEmpty());
    this.runtimeType = runtimeType;
    this.runtimeMiddleman = middleman;
    this.runtimeInputs = inputs;
    return this;
  }

  /** Adds a variables extension to template the toolchain for this link action. */
  public CppLinkActionBuilder addVariablesExtension(VariablesExtension variablesExtension) {
    this.variablesExtensions.add(variablesExtension);
    return this;
  }

  /** Adds variables extensions to template the toolchain for this link action. */
  public CppLinkActionBuilder addVariablesExtensions(List<VariablesExtension> variablesExtensions) {
    for (VariablesExtension variablesExtension : variablesExtensions) {
      addVariablesExtension(variablesExtension);
    }
     return this;
   }
  
  /**
   * Sets the interface output of the link. A non-null argument can only be provided if the link
   * type is {@code DYNAMIC_LIBRARY} and fake is false.
   */
  public CppLinkActionBuilder setInterfaceOutput(Artifact interfaceOutput) {
    this.interfaceOutput = interfaceOutput;
    return this;
  }

  public CppLinkActionBuilder setSymbolCountsOutput(Artifact symbolCounts) {
    this.symbolCounts = symbolCounts;
    return this;
  }

  /**
   * Add additional inputs needed for the linkstamp compilation that is being done as part of the
   * link.
   */
  public CppLinkActionBuilder addCompilationInputs(Iterable<Artifact> inputs) {
    this.compilationInputs.addAll(inputs);
    return this;
  }

  public CppLinkActionBuilder addTransitiveCompilationInputs(NestedSet<Artifact> inputs) {
    this.compilationInputs.addTransitive(inputs);
    return this;
  }

  private void addObjectFile(LinkerInput input) {
    String name = input.getArtifact().getFilename();
    Preconditions.checkArgument(Link.OBJECT_FILETYPES.matches(name), name);
    this.objectFiles.add(input);
  }

  public CppLinkActionBuilder addLTOBitcodeFiles(Iterable<Artifact> files) {
    for (Artifact a : files) {
      ltoBitcodeFiles.add(a);
    }
    return this;
  }

  /**
   * Adds a single object file to the set of inputs.
   */
  public CppLinkActionBuilder addObjectFile(Artifact input) {
    addObjectFile(LinkerInputs.simpleLinkerInput(input, ArtifactCategory.OBJECT_FILE));
    return this;
  }

  /**
   * Adds object files to the linker action.
   */
  public CppLinkActionBuilder addObjectFiles(Iterable<Artifact> inputs) {
    for (Artifact input : inputs) {
      addObjectFile(LinkerInputs.simpleLinkerInput(input, ArtifactCategory.OBJECT_FILE));
    }
    return this;
  }

  /**
   * Adds non-code files to the set of inputs. They will not be passed to the linker command line
   * unless that is explicitly modified, too.
   */
  public CppLinkActionBuilder addNonCodeInputs(Iterable<Artifact> inputs) {
    for (Artifact input : inputs) {
      addNonCodeInput(input);
    }

    return this;
  }

  /**
   * Adds a single non-code file to the set of inputs. It will not be passed to the linker command
   * line unless that is explicitly modified, too.
   */
  public CppLinkActionBuilder addNonCodeInput(Artifact input) {
    String basename = input.getFilename();
    Preconditions.checkArgument(!Link.ARCHIVE_LIBRARY_FILETYPES.matches(basename), basename);
    Preconditions.checkArgument(!Link.SHARED_LIBRARY_FILETYPES.matches(basename), basename);
    Preconditions.checkArgument(!Link.OBJECT_FILETYPES.matches(basename), basename);

    this.nonCodeInputs.add(input);
    return this;
  }

  public CppLinkActionBuilder addFakeObjectFiles(Iterable<Artifact> inputs) {
    for (Artifact input : inputs) {
      addObjectFile(LinkerInputs.fakeLinkerInput(input));
    }
    return this;
  }

  private void checkLibrary(LibraryToLink input) {
    String name = input.getArtifact().getFilename();
    Preconditions.checkArgument(
        Link.ARCHIVE_LIBRARY_FILETYPES.matches(name) || Link.SHARED_LIBRARY_FILETYPES.matches(name),
        "'%s' is not a library file",
        input);
  }

  /**
   * Adds a single artifact to the set of inputs. The artifact must be an archive or a shared
   * library. Note that all directly added libraries are implicitly ordered before all nested sets
   * added with {@link #addLibraries}, even if added in the opposite order.
   */
  public CppLinkActionBuilder addLibrary(LibraryToLink input) {
    checkLibrary(input);
    libraries.add(input);
    return this;
  }

  /**
   * Adds multiple artifact to the set of inputs. The artifacts must be archives or shared
   * libraries.
   */
  public CppLinkActionBuilder addLibraries(NestedSet<LibraryToLink> inputs) {
    for (LibraryToLink input : inputs) {
      checkLibrary(input);
    }
    this.libraries.addTransitive(inputs);
    return this;
  }

  /**
   * Sets the type of ELF file to be created (.a, .so, .lo, executable). The default is {@link
   * LinkTargetType#STATIC_LIBRARY}.
   */
  public CppLinkActionBuilder setLinkType(LinkTargetType linkType) {
    this.linkType = linkType;
    return this;
  }

  /**
   * Sets the degree of "staticness" of the link: fully static (static binding of all symbols),
   * mostly static (use dynamic binding only for symbols from glibc), dynamic (use dynamic binding
   * wherever possible). The default is {@link LinkStaticness#FULLY_STATIC}.
   */
  public CppLinkActionBuilder setLinkStaticness(LinkStaticness linkStaticness) {
    this.linkStaticness = linkStaticness;
    return this;
  }

  /**
   * Sets the identifier of the library produced by the action. See
   * {@link LinkerInputs.LibraryToLink#getLibraryIdentifier()}
   */
  public CppLinkActionBuilder setLibraryIdentifier(String libraryIdentifier) {
    this.libraryIdentifier = libraryIdentifier;
    return this;
  }

  /**
   * Adds a C++ source file which will be compiled at link time. This is used to embed various
   * values from the build system into binaries to identify their provenance.
   *
   * <p>Link stamps are also automatically added to the inputs.
   */
  public CppLinkActionBuilder addLinkstamps(Map<Artifact, NestedSet<Artifact>> linkstamps) {
    this.linkstamps.addAll(linkstamps.keySet());
    // Add inputs for linkstamping.
    if (!linkstamps.isEmpty()) {
      for (Map.Entry<Artifact, NestedSet<Artifact>> entry : linkstamps.entrySet()) {
        addCompilationInputs(entry.getValue());
      }
    }
    return this;
  }

  public CppLinkActionBuilder addLinkstampCompilerOptions(ImmutableList<String> linkstampOptions) {
    this.linkstampOptions = linkstampOptions;
    return this;
  }

  /** Adds an additional linker option. */
  public CppLinkActionBuilder addLinkopt(String linkopt) {
    this.linkopts.add(linkopt);
    return this;
  }

  /**
   * Adds multiple linker options at once.
   *
   * @see #addLinkopt(String)
   */
  public CppLinkActionBuilder addLinkopts(Collection<String> linkopts) {
    this.linkopts.addAll(linkopts);
    return this;
  }

  /**
   * Merges the given link params into this builder by calling {@link #addLinkopts}, {@link
   * #addLibraries}, and {@link #addLinkstamps}.
   */
  public CppLinkActionBuilder addLinkParams(
      CcLinkParams linkParams, RuleErrorConsumer errorListener) throws InterruptedException {
    addLinkopts(linkParams.flattenedLinkopts());
    addLibraries(linkParams.getLibraries());
    ExtraLinkTimeLibraries extraLinkTimeLibraries = linkParams.getExtraLinkTimeLibraries();
    if (extraLinkTimeLibraries != null) {
      for (ExtraLinkTimeLibrary extraLibrary : extraLinkTimeLibraries.getExtraLibraries()) {
        addLibraries(extraLibrary.buildLibraries(ruleContext));
      }
    }
    addLinkstamps(CppHelper.resolveLinkstamps(errorListener, linkParams));
    return this;
  }

  /** Sets whether this link action will be used for a cc_fake_binary; false by default. */
  public CppLinkActionBuilder setFake(boolean fake) {
    this.fake = fake;
    return this;
  }

  /** Sets whether this link action is used for a native dependency library. */
  public CppLinkActionBuilder setNativeDeps(boolean isNativeDeps) {
    this.isNativeDeps = isNativeDeps;
    return this;
  }

  /**
   * Setting this to true overrides the default whole-archive computation and force-enables whole
   * archives for every archive in the link. This is only necessary for linking executable binaries
   * that are supposed to export symbols.
   *
   * <p>Usually, the link action while use whole archives for dynamic libraries that are native deps
   * (or the legacy whole archive flag is enabled), and that are not dynamically linked.
   *
   * <p>(Note that it is possible to build dynamic libraries with cc_binary rules by specifying
   * linkshared = 1, and giving the rule a name that matches the pattern {@code
   * lib&lt;name&gt;.so}.)
   */
  public CppLinkActionBuilder setWholeArchive(boolean wholeArchive) {
    this.wholeArchive = wholeArchive;
    return this;
  }

  /**
   * Sets whether this link action should use test-specific flags (e.g. $EXEC_ORIGIN instead of
   * $ORIGIN for the solib search path or lazy binding); false by default.
   */
  public CppLinkActionBuilder setUseTestOnlyFlags(boolean useTestOnlyFlags) {
    this.useTestOnlyFlags = useTestOnlyFlags;
    return this;
  }

  /**
   * Sets the name of the directory where the solib symlinks for the dynamic runtime libraries live.
   * This is usually automatically set from the cc_toolchain.
   */
  public CppLinkActionBuilder setRuntimeSolibDir(PathFragment runtimeSolibDir) {
    this.runtimeSolibDir = runtimeSolibDir;
    return this;
  }
  
  /**
   * Adds an extra input artifact to the link action.
   */
  public CppLinkActionBuilder addActionInput(Artifact input) {
    this.linkActionInputs.add(input);
    return this;
  }
  
  /**
   * Adds extra input artifacts to the link action.
   */
  public CppLinkActionBuilder addActionInputs(Iterable<Artifact> inputs) {
    this.linkActionInputs.addAll(inputs);
    return this;
  }
  
  /**
   * Adds extra input artifacts to the link actions.
   */
  public CppLinkActionBuilder addTransitiveActionInputs(NestedSet<Artifact> inputs) {
    this.linkActionInputs.addTransitive(inputs);
    return this;
  }

  private static class LinkArgCollector {
    String rpathRoot;
    ImmutableList<String> rpathEntries;
    ImmutableSet<String> libopts;
    ImmutableList<String> linkerInputParams;
    ImmutableList<String> wholeArchiveLinkerInputParams;
    ImmutableList<String> noWholeArchiveInputs;

    public void setRpathRoot(String rPathRoot) {
      this.rpathRoot = rPathRoot;
    }

    public void setRpathEntries(ImmutableList<String> rpathEntries) {
      this.rpathEntries = rpathEntries;
    }

    public void setLibopts(ImmutableSet<String> libopts) {
      this.libopts = libopts;
    }

    public void setLinkerInputParams(ImmutableList<String> linkerInputParams) {
      this.linkerInputParams = linkerInputParams;
    }

    public void setWholeArchiveLinkerInputParams(ImmutableList<String> wholeArchiveInputParams) {
      this.wholeArchiveLinkerInputParams = wholeArchiveInputParams;
    }

    public void setNoWholeArchiveInputs(ImmutableList<String> noWholeArchiveInputs) {
      this.noWholeArchiveInputs = noWholeArchiveInputs;
    }

    public String getRpathRoot() {
      return rpathRoot;
    }

    public ImmutableList<String> getRpathEntries() {
      return rpathEntries;
    }

    public ImmutableSet<String> getLibopts() {
      return libopts;
    }

    public ImmutableList<String> getLinkerInputParams() {
      return linkerInputParams;
    }

    public ImmutableList<String> getWholeArchiveLinkerInputParams() {
      return wholeArchiveLinkerInputParams;
    }

    public ImmutableList<String> getNoWholeArchiveInputs() {
      return noWholeArchiveInputs;
    }
  }

  private class CppLinkVariablesExtension implements VariablesExtension {

    private final BuildConfiguration configuration;
    private final ImmutableMap<Artifact, Artifact> linkstampMap;
    private final boolean needWholeArchive;
    private final Iterable<LinkerInput> linkerInputs;
    private final ImmutableList<LinkerInput> runtimeLinkerInputs;
    private final Artifact outputArtifact;
    private final Artifact interfaceLibraryBuilder;
    private final Artifact interfaceLibraryOutput;
    private final Artifact linkerParamsFile;
    private final PathFragment ltoOutputRootPrefix;

    private final LinkArgCollector linkArgCollector = new LinkArgCollector();

    public CppLinkVariablesExtension(
        BuildConfiguration configuration,
        ImmutableMap<Artifact, Artifact> linkstampMap,
        boolean needWholeArchive,
        Iterable<LinkerInput> linkerInputs,
        ImmutableList<LinkerInput> runtimeLinkerInputs,
        Artifact output,
        Artifact linkerParamsFile,
        PathFragment ltoOutputRootPrefix,
        Artifact interfaceLibraryBuilder,
        Artifact interfaceLibraryOutput) {
      this.configuration = configuration;
      this.linkstampMap = linkstampMap;
      this.needWholeArchive = needWholeArchive;
      this.linkerInputs = linkerInputs;
      this.runtimeLinkerInputs = runtimeLinkerInputs;
      this.outputArtifact = output;
      this.interfaceLibraryBuilder = interfaceLibraryBuilder;
      this.interfaceLibraryOutput = interfaceLibraryOutput;
      this.linkerParamsFile = linkerParamsFile;
      this.ltoOutputRootPrefix = ltoOutputRootPrefix;

      addInputFileLinkOptions(linkArgCollector);
    }

    /**
     * Returns linker parameters indicating libraries that should not be linked inside a
     * --whole_archive block.
     *
     * <p>TODO(b/30228443): Refactor into action configs
     */
    public List<String> getNoWholeArchiveInputs() {
      return linkArgCollector.getNoWholeArchiveInputs();
    }

    @Override
    public void addVariables(Variables.Builder buildVariables) {

      // symbol counting
      if (symbolCounts != null) {
        buildVariables.addStringVariable(
            SYMBOL_COUNTS_OUTPUT_VARIABLE, symbolCounts.getExecPathString());
      }

      // linkstamp
      ImmutableSet.Builder<String> linkstampPaths = ImmutableSet.<String>builder();
      for (Artifact linkstampOutput : linkstampMap.values()) {
        linkstampPaths.add(linkstampOutput.getExecPathString());
      }

      buildVariables.addStringSequenceVariable(
          LINKSTAMP_PATHS_VARIABLE, linkstampPaths.build());

      // pic
      boolean forcePic = cppConfiguration.forcePic();
      if (forcePic) {
        buildVariables.addStringVariable(FORCE_PIC_VARIABLE, "");
      }

      // rpath
      if (linkArgCollector.getRpathRoot() != null) {
        buildVariables.addStringVariable(
            RUNTIME_ROOT_FLAGS_VARIABLE, linkArgCollector.getRpathRoot());
      }

      if (linkArgCollector.getRpathEntries() != null) {
        buildVariables.addStringSequenceVariable(
            RUNTIME_ROOT_ENTRIES_VARIABLE, linkArgCollector.getRpathEntries());
      }

      buildVariables.addStringSequenceVariable(LIBOPTS_VARIABLE, linkArgCollector.getLibopts());
      buildVariables.addStringSequenceVariable(
          LINKER_INPUT_PARAMS_VARIABLE, linkArgCollector.getLinkerInputParams());
      buildVariables.addStringSequenceVariable(
          WHOLE_ARCHIVE_LINKER_INPUT_PARAMS_VARIABLE,
          linkArgCollector.getWholeArchiveLinkerInputParams());

      // global archive
      if (needWholeArchive) {
        buildVariables.addStringVariable(GLOBAL_WHOLE_ARCHIVE_VARIABLE, "");
      }

      // mostly static
      if (linkStaticness == LinkStaticness.MOSTLY_STATIC && cppConfiguration.skipStaticOutputs()) {
        buildVariables.addStringVariable(SKIP_MOSTLY_STATIC_VARIABLE, "");
      }

      // output exec path
      if (outputArtifact != null) {
        buildVariables.addStringVariable(
            OUTPUT_EXECPATH_VARIABLE, outputArtifact.getExecPathString());
      }

      if (!ltoOutputRootPrefix.equals(PathFragment.EMPTY_FRAGMENT)) {
        if (linkerParamsFile != null) {
          buildVariables.addStringVariable(
              "thinlto_optional_params_file", "=" + linkerParamsFile.getExecPathString());
        } else {
          buildVariables.addStringVariable("thinlto_optional_params_file", "");
        }
        buildVariables.addStringVariable(
            "thinlto_prefix_replace",
            configuration.getBinDirectory().getExecPathString()
                + ";"
                + configuration.getBinDirectory().getExecPath().getRelative(ltoOutputRootPrefix));
      }
      boolean shouldGenerateInterfaceLibrary =
          outputArtifact != null
              && interfaceLibraryBuilder != null
              && interfaceLibraryOutput != null;
      buildVariables.addStringVariable(
          GENERATE_INTERFACE_LIBRARY_VARIABLE, shouldGenerateInterfaceLibrary ? "yes" : "no");
      buildVariables.addStringVariable(
          INTERFACE_LIBRARY_BUILDER_VARIABLE,
          shouldGenerateInterfaceLibrary ? interfaceLibraryBuilder.getExecPathString() : "ignored");
      buildVariables.addStringVariable(
          INTERFACE_LIBRARY_INPUT_VARIABLE,
          shouldGenerateInterfaceLibrary ? outputArtifact.getExecPathString() : "ignored");
      buildVariables.addStringVariable(
          INTERFACE_LIBRARY_OUTPUT_VARIABLE,
          shouldGenerateInterfaceLibrary ? interfaceLibraryOutput.getExecPathString() : "ignored");

      // Variables arising from the toolchain
      buildVariables
          .addAllStringVariables(CppHelper.getToolchain(ruleContext).getBuildVariables())
          .build();
      CppHelper.getFdoSupport(ruleContext).getLinkOptions(featureConfiguration, buildVariables);
    }

    private boolean isSharedNativeLibrary() {
      return isNativeDeps && cppConfiguration.shareNativeDeps();
    }

    private boolean inputNeedsWholeArchive(LinkerInput input) {
      if (Link.useStartEndLib(input, cppConfiguration.archiveType())) {
        return false;
      }

      return input.getArtifactCategory() == ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY
           && !wholeArchive && !needWholeArchive;
    }

    /**
     * When linking a shared library fully or mostly static then we need to link in *all* dependent
     * files, not just what the shared library needs for its own code. This is done by wrapping all
     * objects/libraries with -Wl,-whole-archive and -Wl,-no-whole-archive. For this case the
     * globalNeedWholeArchive parameter must be set to true. Otherwise only library objects (.lo)
     * need to be wrapped with -Wl,-whole-archive and -Wl,-no-whole-archive.
     *
     * <p>TODO: Factor out of the bazel binary into build variables for crosstool action_configs.
     */
    private void addInputFileLinkOptions(LinkArgCollector linkArgCollector) {

      // Used to collect -L and -Wl,-rpath options, ensuring that each used only once.
      ImmutableSet.Builder<String> libOpts = ImmutableSet.builder();

      // List of command line parameters that need to be placed *outside* of
      // --whole-archive ... --no-whole-archive.
      ImmutableList.Builder<String> noWholeArchiveInputs = ImmutableList.builder();

      PathFragment solibDir =
          configuration
              .getBinDirectory(ruleContext.getRule().getRepository())
              .getExecPath()
              .getRelative(cppConfiguration.getSolibDirectory());
      String runtimeSolibName = runtimeSolibDir != null ? runtimeSolibDir.getBaseName() : null;
      boolean runtimeRpath =
          runtimeSolibDir != null
              && (linkType == LinkTargetType.DYNAMIC_LIBRARY
                  || (linkType == LinkTargetType.EXECUTABLE
                      && linkStaticness == LinkStaticness.DYNAMIC));

      String rpathRoot = null;
      ImmutableList.Builder<String> runtimeRpathEntries = ImmutableList.builder();

      if (output != null) {
        String origin =
            useTestOnlyFlags && cppConfiguration.supportsExecOrigin()
                ? "$EXEC_ORIGIN/"
                : "$ORIGIN/";
        if (runtimeRpath) {
          runtimeRpathEntries.add("-Wl,-rpath," + origin + runtimeSolibName + "/");
        }

        // Calculate the correct relative value for the "-rpath" link option (which sets
        // the search path for finding shared libraries).
        if (isSharedNativeLibrary()) {
          // For shared native libraries, special symlinking is applied to ensure C++
          // runtimes are available under $ORIGIN/_solib_[arch]. So we set the RPATH to find
          // them.
          //
          // Note that we have to do this because $ORIGIN points to different paths for
          // different targets. In other words, blaze-bin/d1/d2/d3/a_shareddeps.so and
          // blaze-bin/d4/b_shareddeps.so have different path depths. The first could
          // reference a standard blaze-bin/_solib_[arch] via $ORIGIN/../../../_solib[arch],
          // and the second could use $ORIGIN/../_solib_[arch]. But since this is a shared
          // artifact, both are symlinks to the same place, so
          // there's no *one* RPATH setting that fits all targets involved in the sharing.
          rpathRoot =
              "-Wl,-rpath," + origin + ":" + origin + cppConfiguration.getSolibDirectory() + "/";
          if (runtimeRpath) {
            runtimeRpathEntries.add("-Wl,-rpath," + origin + "../" + runtimeSolibName + "/");
          }
        } else {
          // For all other links, calculate the relative path from the output file to _solib_[arch]
          // (the directory where all shared libraries are stored, which resides under the blaze-bin
          // directory. In other words, given blaze-bin/my/package/binary, rpathRoot would be
          // "../../_solib_[arch]".
          if (runtimeRpath) {
            runtimeRpathEntries.add(
                "-Wl,-rpath,"
                    + origin
                    + Strings.repeat("../", output.getRootRelativePath().segmentCount() - 1)
                    + runtimeSolibName
                    + "/");
          }

          rpathRoot =
              "-Wl,-rpath,"
                  + origin
                  + Strings.repeat("../", output.getRootRelativePath().segmentCount() - 1)
                  + cppConfiguration.getSolibDirectory()
                  + "/";

          if (isNativeDeps) {
            // We also retain the $ORIGIN/ path to solibs that are in _solib_<arch>, as opposed to
            // the package directory)
            if (runtimeRpath) {
              runtimeRpathEntries.add("-Wl,-rpath," + origin + "../" + runtimeSolibName + "/");
            }
            rpathRoot += ":" + origin;
          }
        }
      }

      boolean includeSolibDir = false;

      Map<Artifact, Artifact> ltoMap = null;
      if (!isLTOIndexing && (allLTOArtifacts != null)) {
        // TODO(bazel-team): The LTO final link can only work if there are individual .o files on
        // the command line. Rather than crashing, this should issue a nice error. We will get
        // this by
        // 1) moving supports_start_end_lib to a toolchain feature
        // 2) having thin_lto require start_end_lib
        // As a bonus, we can rephrase --nostart_end_lib as --features=-start_end_lib and get rid
        // of a command line option.

        Preconditions.checkState(cppConfiguration.useStartEndLib());
        ltoMap = new HashMap<>();
        for (LTOBackendArtifacts l : allLTOArtifacts) {
          ltoMap.put(l.getBitcodeFile(), l.getObjectFile());
        }
      }

      ImmutableList.Builder<String> wholeArchiveInputParams = ImmutableList.builder();
      ImmutableList.Builder<String> standardArchiveInputParams = ImmutableList.builder();

      for (LinkerInput input : linkerInputs) {
        if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY) {
          PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory();
          Preconditions.checkState(
              libDir.startsWith(solibDir),
              "Artifact '%s' is not under directory '%s'.",
              input.getArtifact(),
              solibDir);
          if (libDir.equals(solibDir)) {
            includeSolibDir = true;
          }
          addDynamicInputLinkOptions(
              input, standardArchiveInputParams, libOpts, solibDir, rpathRoot);
        } else {
          addStaticInputLinkOptions(
              input, wholeArchiveInputParams, standardArchiveInputParams, ltoMap);
        }
      }

      boolean includeRuntimeSolibDir = false;

      for (LinkerInput input : runtimeLinkerInputs) {
        ImmutableList.Builder<String> optionsList =
            needWholeArchive ? noWholeArchiveInputs : standardArchiveInputParams;

        if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY) {
          PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory();
          Preconditions.checkState(
              runtimeSolibDir != null && libDir.equals(runtimeSolibDir),
              "Artifact '%s' is not under directory '%s'.",
              input.getArtifact(),
              solibDir);
          includeRuntimeSolibDir = true;
          addDynamicInputLinkOptions(input, optionsList, libOpts, solibDir, rpathRoot);
        } else {
          addStaticInputLinkOptions(input,
              needWholeArchive ? noWholeArchiveInputs : wholeArchiveInputParams,
              needWholeArchive ? noWholeArchiveInputs : standardArchiveInputParams,
              ltoMap);
        }
      }
      if (linkerParamsFile != null && ltoOutputRootPrefix.equals(PathFragment.EMPTY_FRAGMENT)) {
        standardArchiveInputParams.add("-Wl,@" + linkerParamsFile.getExecPathString());
      }

      // rpath ordering matters for performance; first add the one where most libraries are found.
      if (includeSolibDir && rpathRoot != null) {
        linkArgCollector.setRpathRoot(rpathRoot);
      }
      if (includeRuntimeSolibDir) {
        linkArgCollector.setRpathEntries(runtimeRpathEntries.build());
      }

      linkArgCollector.setLibopts(libOpts.build());

      linkArgCollector.setLinkerInputParams(standardArchiveInputParams.build());
      linkArgCollector.setWholeArchiveLinkerInputParams(wholeArchiveInputParams.build());
      linkArgCollector.setNoWholeArchiveInputs(noWholeArchiveInputs.build());

      if (ltoMap != null) {
        Preconditions.checkState(ltoMap.isEmpty(), "Still have LTO objects left: %s", ltoMap);
      }
    }

    /** Adds command-line options for a dynamic library input file into options and libOpts. */
    private void addDynamicInputLinkOptions(
        LinkerInput input,
        ImmutableList.Builder<String> options,
        ImmutableSet.Builder<String> libOpts,
        PathFragment solibDir,
        String rpathRoot) {
      Preconditions.checkState(input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY);
      Preconditions.checkState(!Link.useStartEndLib(input, cppConfiguration.archiveType()));

      Artifact inputArtifact = input.getArtifact();
      PathFragment libDir = inputArtifact.getExecPath().getParentDirectory();
      if (rpathRoot != null
          && !libDir.equals(solibDir)
          && (runtimeSolibDir == null || !runtimeSolibDir.equals(libDir))) {
        String dotdots = "";
        PathFragment commonParent = solibDir;
        while (!libDir.startsWith(commonParent)) {
          dotdots += "../";
          commonParent = commonParent.getParentDirectory();
        }

        libOpts.add(rpathRoot + dotdots + libDir.relativeTo(commonParent).getPathString());
      }

      libOpts.add("-L" + inputArtifact.getExecPath().getParentDirectory().getPathString());

      String name = inputArtifact.getFilename();
      if (CppFileTypes.SHARED_LIBRARY.matches(name)) {
        String libName = name.replaceAll("(^lib|\\.(so|dylib)$)", "");
        options.add("-l" + libName);
      } else {
        // Interface shared objects have a non-standard extension
        // that the linker won't be able to find.  So use the
        // filename directly rather than a -l option.  Since the
        // library has an SONAME attribute, this will work fine.
        options.add(inputArtifact.getExecPathString());
      }
    }

    /**
     * Adds command-line options for a static library or non-library input into options.
     *
     * @param ltoMap is a mutable list of exec paths that should be on the command-line, which must
     *     be supplied for LTO final links.
     */
    private void addStaticInputLinkOptions(
        LinkerInput input,
        ImmutableList.Builder<String> wholeArchiveOptions,
        ImmutableList.Builder<String> standardOptions,
        @Nullable Map<Artifact, Artifact> ltoMap) {
      Preconditions.checkState(!(input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY));
      // If we had any LTO artifacts, ltoMap whould be non-null. In that case,
      // we should have created a linkerParamsFile which the LTO indexing
      // step will populate with the exec paths that correspond to the LTO
      // artifacts that the linker decided to include based on symbol resolution.
      // Those files will be included directly in the link (and not wrapped
      // in --start-lib/--end-lib) to ensure consistency between the two link
      // steps.
      Preconditions.checkState(ltoMap == null || linkerParamsFile != null);

      // start-lib/end-lib library: adds its input object files.
      if (Link.useStartEndLib(input, cppConfiguration.archiveType())) {
        Iterable<Artifact> archiveMembers = input.getObjectFiles();
        if (!Iterables.isEmpty(archiveMembers)) {
          List<String> nonLTOArchiveMembers = new ArrayList<>();
          for (Artifact member : archiveMembers) {
            if (ltoMap != null && ltoMap.remove(member) != null) {
              // The LTO artifacts that should be included in the final link
              // are listed in the linkerParamsFile. When ltoMap is non-null
              // the backend artifact may be missing due to libraries that list .o
              // files explicitly, or generate .o files from assembler.
              continue;
            }
            nonLTOArchiveMembers.add(member.getExecPathString());
          }
          if (!nonLTOArchiveMembers.isEmpty()) {
            standardOptions.add("-Wl,--start-lib");
            standardOptions.addAll(nonLTOArchiveMembers);
            standardOptions.add("-Wl,--end-lib");
          }
        }
      } else {
        ImmutableList.Builder<String> options =
            inputNeedsWholeArchive(input) ? wholeArchiveOptions : standardOptions;
        // For anything else, add the input directly.
        Artifact inputArtifact = input.getArtifact();

        if (ltoMap != null && ltoMap.remove(inputArtifact) != null) {
          // The LTO artifacts that should be included in the final link
          // are listed in the linkerParamsFile.
          return;
        }

        if (input.isFake()) {
          options.add(Link.FAKE_OBJECT_PREFIX + inputArtifact.getExecPathString());
        } else {
          options.add(inputArtifact.getExecPathString());
        }
      }
    }
  }
}
