com.google.devtools.build.lib.rules.cpp.CppLinkActionBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.rules.cpp.CppLinkActionBuilder.java

Source

// 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.LibraryToLinkValue;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.SequenceBuilder;
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";

    public static final String LIBRARY_SEARCH_DIRECTORIES_VARIABLE = "library_search_directories";

    /** A build variable for flags providing files to link as inputs in the linker invocation */
    public static final String LIBRARIES_TO_LINK_VARIABLE = "libraries_to_link";

    /**
     * A build variable for thinlto param file produced by thinlto-indexing action and consumed by
     * normal linking actions.
     */
    public static final String THINLTO_PARAM_FILE_VARIABLE = "thinlto_param_file";

    public static final String THINLTO_PREFIX_REPLACE_VARIABLE = "thinlto_prefix_replace";

    /**
     * A build variable for linker param file created by Bazel to overcome the command line length
     * limit.
     */
    public static final String LINKER_PARAM_FILE_VARIABLE = "linker_param_file";

    /** 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 for hard-coded linker flags currently only known by bazel. */
    public static final String LEGACY_LINK_FLAGS_VARIABLE = "legacy_link_flags";
    /**
     * 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";

    /** A build variable whose presence indicates that this action is a cc_test linking action. */
    public static final String IS_CC_TEST_LINK_ACTION_VARIABLE = "is_cc_test_link_action";

    // 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 (fake) {
            return false;
        }

        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 linkerParamFileRootPath = null;
        @Nullable
        Artifact thinltoParamFile = 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.
            linkerParamFileRootPath = ParameterFile.derivePath(output.getRootRelativePath(), "lto-final");
            thinltoParamFile = linkArtifactFactory.create(ruleContext, configuration, linkerParamFileRootPath);
        }

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

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

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

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

        // 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, paramFile, thinltoParamFile,
                        ltoOutputRootPrefix, null, null)
                : new CppLinkVariablesExtension(configuration, linkstampMap, needWholeArchive, linkerInputs,
                        runtimeLinkerInputs, output, paramFile, thinltoParamFile, PathFragment.EMPTY_FRAGMENT,
                        getInterfaceSoBuilder(), interfaceOutput);
        variablesExtension.addVariables(buildVariablesBuilder);
        for (VariablesExtension extraVariablesExtension : variablesExtensions) {
            extraVariablesExtension.addVariables(buildVariablesBuilder);
        }
        Variables buildVariables = buildVariablesBuilder.build();

        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);

        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));
            opts.addAll(cppConfiguration.getLTOIndexOptions());
            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 (thinltoParamFile != null && !isLTOIndexing) {
            inputsBuilder.add(ImmutableList.of(thinltoParamFile));
        }
        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;
        ImmutableSet<String> librarySearchDirectories;
        SequenceBuilder librariesToLink;

        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 setLibrariesToLink(SequenceBuilder librariesToLink) {
            this.librariesToLink = librariesToLink;
        }

        public void setLibrarySearchDirectories(ImmutableSet<String> librarySearchDirectories) {
            this.librarySearchDirectories = librarySearchDirectories;
        }

        public String getRpathRoot() {
            return rpathRoot;
        }

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

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

        public SequenceBuilder getLibrariesToLink() {
            return librariesToLink;
        }

        public ImmutableSet<String> getLibrarySearchDirectories() {
            return librarySearchDirectories;
        }

    }

    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 paramFile;
        private final Artifact thinltoParamFile;
        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 paramFile, Artifact thinltoParamFile, 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.paramFile = paramFile;
            this.thinltoParamFile = thinltoParamFile;
            this.ltoOutputRootPrefix = ltoOutputRootPrefix;

            addInputFileLinkOptions(linkArgCollector);
        }

        @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, "");
            }

            if (useTestOnlyFlags()) {
                buildVariables.addStringVariable(IS_CC_TEST_LINK_ACTION_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.addCustomBuiltVariable(LIBRARIES_TO_LINK_VARIABLE,
                    linkArgCollector.getLibrariesToLink());

            buildVariables.addStringSequenceVariable(LIBRARY_SEARCH_DIRECTORIES_VARIABLE,
                    linkArgCollector.getLibrarySearchDirectories());

            if (paramFile != null) {
                buildVariables.addStringVariable(LINKER_PARAM_FILE_VARIABLE, paramFile.getExecPathString());
            }

            // 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 (isLTOIndexing()) {
                if (thinltoParamFile != null) {
                    // This is a lto-indexing action and we want it to populate param file.
                    buildVariables.addStringVariable(THINLTO_PARAM_FILE_VARIABLE,
                            thinltoParamFile.getExecPathString());
                    // TODO(b/33846234): Remove once all the relevant crosstools don't depend on the variable.
                    buildVariables.addStringVariable("thinlto_optional_params_file",
                            "=" + thinltoParamFile.getExecPathString());
                } else {
                    buildVariables.addStringVariable(THINLTO_PARAM_FILE_VARIABLE, "");
                    // TODO(b/33846234): Remove once all the relevant crosstools don't depend on the variable.
                    buildVariables.addStringVariable("thinlto_optional_params_file", "");
                }
                buildVariables.addStringVariable(THINLTO_PREFIX_REPLACE_VARIABLE,
                        configuration.getBinDirectory().getExecPathString() + ";"
                                + configuration.getBinDirectory().getExecPath().getRelative(ltoOutputRootPrefix));
            } else {
                if (thinltoParamFile != null) {
                    // This is a normal link action and we need to use param file created by lto-indexing.
                    buildVariables.addStringVariable(THINLTO_PARAM_FILE_VARIABLE,
                            thinltoParamFile.getExecPathString());
                }
            }
            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 isLTOIndexing() {
            return !ltoOutputRootPrefix.equals(PathFragment.EMPTY_FRAGMENT);
        }

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

        /**
         * 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();
            ImmutableSet.Builder<String> librarySearchDirectories = ImmutableSet.builder();

            // List of command line parameters that need to be placed *outside* of
            // --whole-archive ... --no-whole-archive.
            SequenceBuilder librariesToLink = new SequenceBuilder();

            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));

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

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

            String rpathRoot;
            // 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());
                }
            }

            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, librariesToLink, false, libOpts, librarySearchDirectories,
                            solibDir, rpathRoot);
                } else {
                    addStaticInputLinkOptions(input, librariesToLink, false, ltoMap);
                }
            }

            boolean includeRuntimeSolibDir = false;

            for (LinkerInput input : runtimeLinkerInputs) {
                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, librariesToLink, true, libOpts, librarySearchDirectories,
                            solibDir, rpathRoot);
                } else {
                    addStaticInputLinkOptions(input, librariesToLink, true, ltoMap);
                }
            }

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

            linkArgCollector.setLibopts(libOpts.build());
            linkArgCollector.setLibrarySearchDirectories(librarySearchDirectories.build());

            linkArgCollector.setLibrariesToLink(librariesToLink);

            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.
         *
         * @param librariesToLink - a collection that will be exposed as a build variable.
         */
        private void addDynamicInputLinkOptions(LinkerInput input, SequenceBuilder librariesToLink,
                boolean isRuntimeLinkerInput, ImmutableSet.Builder<String> libOpts,
                ImmutableSet.Builder<String> librarySearchDirectories, 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 (!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());
            }

            librarySearchDirectories.add(inputArtifact.getExecPath().getParentDirectory().getPathString());

            String name = inputArtifact.getFilename();
            boolean inputIsWholeArchive = !isRuntimeLinkerInput && needWholeArchive;
            if (CppFileTypes.SHARED_LIBRARY.matches(name)) {
                // Use normal shared library resolution rules for shared libraries.
                String libName = name.replaceAll("(^lib|\\.(so|dylib)$)", "");
                librariesToLink.addValue(LibraryToLinkValue.forDynamicLibrary(libName, inputIsWholeArchive));
            } else if (CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(name)) {
                // Versioned shared libraries require the exact library filename, e.g.:
                // -lfoo -> libfoo.so
                // -l:libfoo.so.1 -> libfoo.so.1
                librariesToLink.addValue(LibraryToLinkValue.forVersionedDynamicLibrary(name, inputIsWholeArchive));
            } 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.
                librariesToLink.addValue(LibraryToLinkValue.forInterfaceLibrary(inputArtifact.getExecPathString(),
                        inputIsWholeArchive));
            }
        }

        /**
         * Adds command-line options for a static library or non-library input into options.
         *
         * @param librariesToLink - a collection that will be exposed as a build variable.
         * @param ltoMap is a mutable list of exec paths that should be on the command-line, which must
         */
        private void addStaticInputLinkOptions(LinkerInput input, SequenceBuilder librariesToLink,
                boolean isRuntimeLinkerInput, @Nullable Map<Artifact, Artifact> ltoMap) {
            ArtifactCategory artifactCategory = input.getArtifactCategory();
            Preconditions.checkState(artifactCategory != ArtifactCategory.DYNAMIC_LIBRARY);
            // If we had any LTO artifacts, ltoMap whould be non-null. In that case,
            // we should have created a thinltoParamFile 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 || thinltoParamFile != 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)) {
                    ImmutableList.Builder<String> nonLTOArchiveMembersBuilder = ImmutableList.builder();
                    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 thinltoParamFile. 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;
                        }
                        nonLTOArchiveMembersBuilder.add(member.getExecPathString());
                    }
                    ImmutableList<String> nonLTOArchiveMembers = nonLTOArchiveMembersBuilder.build();
                    if (!nonLTOArchiveMembers.isEmpty()) {
                        boolean inputIsWholeArchive = !isRuntimeLinkerInput && needWholeArchive;
                        librariesToLink.addValue(
                                LibraryToLinkValue.forObjectFileGroup(nonLTOArchiveMembers, inputIsWholeArchive));
                    }
                }
            } else {
                Preconditions.checkArgument(artifactCategory.equals(ArtifactCategory.OBJECT_FILE)
                        || artifactCategory.equals(ArtifactCategory.STATIC_LIBRARY)
                        || artifactCategory.equals(ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY));
                boolean isAlwaysLinkStaticLibrary = artifactCategory == ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY;
                boolean inputIsWholeArchive = (!isRuntimeLinkerInput
                        && (isAlwaysLinkStaticLibrary || needWholeArchive))
                        || (isRuntimeLinkerInput && isAlwaysLinkStaticLibrary && !needWholeArchive);

                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 thinltoParamFile.
                    return;
                }

                String name;
                if (input.isFake()) {
                    name = Link.FAKE_OBJECT_PREFIX + inputArtifact.getExecPathString();
                } else {
                    name = inputArtifact.getExecPathString();
                }

                librariesToLink.addValue(artifactCategory.equals(ArtifactCategory.OBJECT_FILE)
                        ? LibraryToLinkValue.forObjectFile(name, inputIsWholeArchive)
                        : LibraryToLinkValue.forStaticLibrary(name, inputIsWholeArchive));
            }
        }
    }
}