aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java1074
1 files changed, 1074 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
new file mode 100644
index 0000000000..ecf3431b4f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkAction.java
@@ -0,0 +1,1074 @@
+// Copyright 2014 Google Inc. 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.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+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.AbstractAction;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionException;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.EnvironmentalExecException;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
+import com.google.devtools.build.lib.actions.ParameterFile;
+import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.extra.CppLinkInfo;
+import com.google.devtools.build.lib.actions.extra.ExtraActionInfo;
+import com.google.devtools.build.lib.analysis.AnalysisEnvironment;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+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.concurrent.ThreadSafety.Immutable;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+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.LinkerInputs.LibraryToLink;
+import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.Pair;
+import com.google.devtools.build.lib.util.ShellEscaper;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Action that represents an ELF linking step.
+ */
+@ThreadCompatible
+public final class CppLinkAction extends AbstractAction {
+ private static final String LINK_GUID = "58ec78bd-1176-4e36-8143-439f656b181d";
+ private static final String FAKE_LINK_GUID = "da36f819-5a15-43a9-8a45-e01b60e10c8b";
+
+ private final CppConfiguration cppConfiguration;
+ private final LibraryToLink outputLibrary;
+ private final LibraryToLink interfaceOutputLibrary;
+
+ private final LinkCommandLine linkCommandLine;
+
+ /** True for cc_fake_binary targets. */
+ private final boolean fake;
+
+ private final Iterable<Artifact> mandatoryInputs;
+
+ // Linking uses a lot of memory; estimate 1 MB per input file, min 1.5 Gib.
+ // It is vital to not underestimate too much here,
+ // because running too many concurrent links can
+ // thrash the machine to the point where it stops
+ // responding to keystrokes or mouse clicks.
+ // CPU and IO do not scale similarly and still use the static minimum estimate.
+ public static final ResourceSet LINK_RESOURCES_PER_INPUT = new ResourceSet(1, 0, 0);
+
+ // This defines the minimum of each resource that will be reserved.
+ public static final ResourceSet MIN_STATIC_LINK_RESOURCES = new ResourceSet(1536, 1, 0.3);
+
+ // Dynamic linking should be cheaper than static linking.
+ public static final ResourceSet MIN_DYNAMIC_LINK_RESOURCES = new ResourceSet(1024, 0.3, 0.2);
+
+ /**
+ * Use {@link Builder} to create instances of this class. Also see there for
+ * the documentation of all parameters.
+ *
+ * <p>This constructor is intentionally private and is only to be called from
+ * {@link Builder#build()}.
+ */
+ private CppLinkAction(ActionOwner owner,
+ Iterable<Artifact> inputs,
+ ImmutableList<Artifact> outputs,
+ CppConfiguration cppConfiguration,
+ LibraryToLink outputLibrary,
+ LibraryToLink interfaceOutputLibrary,
+ boolean fake,
+ LinkCommandLine linkCommandLine) {
+ super(owner, inputs, outputs);
+ this.mandatoryInputs = inputs;
+ this.cppConfiguration = cppConfiguration;
+ this.outputLibrary = outputLibrary;
+ this.interfaceOutputLibrary = interfaceOutputLibrary;
+ this.fake = fake;
+
+ this.linkCommandLine = linkCommandLine;
+ }
+
+ private static Iterable<LinkerInput> filterLinkerInputs(Iterable<LinkerInput> inputs) {
+ return Iterables.filter(inputs, new Predicate<LinkerInput>() {
+ @Override
+ public boolean apply(LinkerInput input) {
+ return Link.VALID_LINKER_INPUTS.matches(input.getArtifact().getFilename());
+ }
+ });
+ }
+
+ private static Iterable<Artifact> filterLinkerInputArtifacts(Iterable<Artifact> inputs) {
+ return Iterables.filter(inputs, new Predicate<Artifact>() {
+ @Override
+ public boolean apply(Artifact input) {
+ return Link.VALID_LINKER_INPUTS.matches(input.getFilename());
+ }
+ });
+ }
+
+ private CppConfiguration getCppConfiguration() {
+ return cppConfiguration;
+ }
+
+ @VisibleForTesting
+ public String getTargetCpu() {
+ return getCppConfiguration().getTargetCpu();
+ }
+
+ public String getHostSystemName() {
+ return getCppConfiguration().getHostSystemName();
+ }
+
+ /**
+ * Returns the link configuration; for correctness you should not call this method during
+ * execution - only the argv is part of the action cache key, and we therefore don't guarantee
+ * that the action will be re-executed if the contents change in a way that does not affect the
+ * argv.
+ */
+ @VisibleForTesting
+ public LinkCommandLine getLinkCommandLine() {
+ return linkCommandLine;
+ }
+
+ public LibraryToLink getOutputLibrary() {
+ return outputLibrary;
+ }
+
+ public LibraryToLink getInterfaceOutputLibrary() {
+ return interfaceOutputLibrary;
+ }
+
+ /**
+ * Returns the path to the output artifact produced by the linker.
+ */
+ public Path getOutputFile() {
+ return outputLibrary.getArtifact().getPath();
+ }
+
+ @VisibleForTesting
+ public List<String> getRawLinkArgv() {
+ return linkCommandLine.getRawLinkArgv();
+ }
+
+ @VisibleForTesting
+ public List<String> getArgv() {
+ return linkCommandLine.arguments();
+ }
+
+ /**
+ * Prepares and returns the command line specification for this link.
+ * Splits appropriate parts into a .params file and adds any required
+ * linkstamp compilation steps.
+ *
+ * @return a finalized command line suitable for execution
+ */
+ public final List<String> prepareCommandLine(Path execRoot, List<String> inputFiles)
+ throws ExecException {
+ List<String> commandlineArgs;
+ // Try to shorten the command line by use of a parameter file.
+ // This makes the output with --subcommands (et al) more readable.
+ if (linkCommandLine.canBeSplit()) {
+ PathFragment paramExecPath = ParameterFile.derivePath(
+ outputLibrary.getArtifact().getExecPath());
+ Pair<List<String>, List<String>> split = linkCommandLine.splitCommandline(paramExecPath);
+ commandlineArgs = split.first;
+ writeToParamFile(execRoot, paramExecPath, split.second);
+ if (inputFiles != null) {
+ inputFiles.add(paramExecPath.getPathString());
+ }
+ } else {
+ commandlineArgs = linkCommandLine.getRawLinkArgv();
+ }
+ return linkCommandLine.finalizeWithLinkstampCommands(commandlineArgs);
+ }
+
+ private static void writeToParamFile(Path workingDir, PathFragment paramExecPath,
+ List<String> paramFileArgs) throws ExecException {
+ // Create parameter file.
+ ParameterFile paramFile = new ParameterFile(workingDir, paramExecPath, ISO_8859_1,
+ ParameterFileType.UNQUOTED);
+ Path paramFilePath = paramFile.getPath();
+ try {
+ // writeContent() fails for existing files that are marked readonly.
+ paramFilePath.delete();
+ } catch (IOException e) {
+ throw new EnvironmentalExecException("could not delete file '" + paramFilePath + "'", e);
+ }
+ paramFile.writeContent(paramFileArgs);
+
+ // Normally Blaze chmods all output files automatically (see
+ // SkyframeActionExecutor#setOutputsReadOnlyAndExecutable), but this params file is created
+ // out-of-band and is not declared as an output. By chmodding the file, other processes
+ // can observe this file being created.
+ try {
+ paramFilePath.setWritable(false);
+ paramFilePath.setExecutable(true); // for consistency with other action outputs
+ } catch (IOException e) {
+ throw new EnvironmentalExecException("could not chmod param file '" + paramFilePath + "'", e);
+ }
+ }
+
+ @Override
+ @ThreadCompatible
+ public void execute(
+ ActionExecutionContext actionExecutionContext)
+ throws ActionExecutionException, InterruptedException {
+ if (fake) {
+ executeFake();
+ } else {
+ Executor executor = actionExecutionContext.getExecutor();
+
+ try {
+ executor.getContext(CppLinkActionContext.class).exec(
+ this, actionExecutionContext);
+ } catch (ExecException e) {
+ throw e.toActionExecutionException("Linking of rule '" + getOwner().getLabel() + "'",
+ executor.getVerboseFailures(), this);
+ }
+ }
+ }
+
+ @Override
+ public String describeStrategy(Executor executor) {
+ return fake
+ ? "fake,local"
+ : executor.getContext(CppLinkActionContext.class).strategyLocality(this);
+ }
+
+ // Don't forget to update FAKE_LINK_GUID if you modify this method.
+ @ThreadCompatible
+ private void executeFake()
+ throws ActionExecutionException {
+ // The uses of getLinkConfiguration in this method may not be consistent with the computed key.
+ // I.e., this may be incrementally incorrect.
+ final Collection<Artifact> linkstampOutputs = getLinkCommandLine().getLinkstamps().values();
+
+ // Prefix all fake output files in the command line with $TEST_TMPDIR/.
+ final String outputPrefix = "$TEST_TMPDIR/";
+ List<String> escapedLinkArgv = escapeLinkArgv(linkCommandLine.getRawLinkArgv(),
+ linkstampOutputs, outputPrefix);
+ // Write the commands needed to build the real target to the fake target
+ // file.
+ StringBuilder s = new StringBuilder();
+ Joiner.on('\n').appendTo(s,
+ "# This is a fake target file, automatically generated.",
+ "# Do not edit by hand!",
+ "echo $0 is a fake target file and not meant to be executed.",
+ "exit 0",
+ "EOS",
+ "",
+ "makefile_dir=.",
+ "");
+
+ try {
+ // Concatenate all the (fake) .o files into the result.
+ for (LinkerInput linkerInput : getLinkCommandLine().getLinkerInputs()) {
+ Artifact objectFile = linkerInput.getArtifact();
+ if (CppFileTypes.OBJECT_FILE.matches(objectFile.getFilename())
+ && linkerInput.isFake()) {
+ s.append(FileSystemUtils.readContentAsLatin1(objectFile.getPath())); // (IOException)
+ }
+ }
+
+ s.append(getOutputFile().getBaseName()).append(": ");
+ for (Artifact linkstamp : linkstampOutputs) {
+ s.append("mkdir -p " + outputPrefix +
+ linkstamp.getExecPath().getParentDirectory() + " && ");
+ }
+ Joiner.on(' ').appendTo(s,
+ ShellEscaper.escapeAll(linkCommandLine.finalizeAlreadyEscapedWithLinkstampCommands(
+ escapedLinkArgv, outputPrefix)));
+ s.append('\n');
+ if (getOutputFile().exists()) {
+ getOutputFile().setWritable(true); // (IOException)
+ }
+ FileSystemUtils.writeContent(getOutputFile(), ISO_8859_1, s.toString());
+ getOutputFile().setExecutable(true); // (IOException)
+ for (Artifact linkstamp : linkstampOutputs) {
+ FileSystemUtils.touchFile(linkstamp.getPath());
+ }
+ } catch (IOException e) {
+ throw new ActionExecutionException("failed to create fake link command for rule '" +
+ getOwner().getLabel() + ": " + e.getMessage(),
+ this, false);
+ }
+ }
+
+ /**
+ * Shell-escapes the raw link command line.
+ *
+ * @param rawLinkArgv raw link command line
+ * @param linkstampOutputs linkstamp artifacts
+ * @param outputPrefix to be prepended to any outputs
+ * @return escaped link command line
+ */
+ private List<String> escapeLinkArgv(List<String> rawLinkArgv,
+ final Collection<Artifact> linkstampOutputs, final String outputPrefix) {
+ final List<String> linkstampExecPaths = Artifact.asExecPaths(linkstampOutputs);
+ ImmutableList.Builder<String> escapedArgs = ImmutableList.builder();
+ for (String rawArg : rawLinkArgv) {
+ String escapedArg;
+ if (rawArg.equals(getPrimaryOutput().getExecPathString())
+ || linkstampExecPaths.contains(rawArg)) {
+ escapedArg = outputPrefix + ShellEscaper.escapeString(rawArg);
+ } else if (rawArg.startsWith(Link.FAKE_OBJECT_PREFIX)) {
+ escapedArg = outputPrefix + ShellEscaper.escapeString(
+ rawArg.substring(Link.FAKE_OBJECT_PREFIX.length()));
+ } else {
+ escapedArg = ShellEscaper.escapeString(rawArg);
+ }
+ escapedArgs.add(escapedArg);
+ }
+ return escapedArgs.build();
+ }
+
+ @Override
+ public ExtraActionInfo.Builder getExtraActionInfo() {
+ // The uses of getLinkConfiguration in this method may not be consistent with the computed key.
+ // I.e., this may be incrementally incorrect.
+ CppLinkInfo.Builder info = CppLinkInfo.newBuilder();
+ info.addAllInputFile(Artifact.toExecPaths(
+ LinkerInputs.toLibraryArtifacts(getLinkCommandLine().getLinkerInputs())));
+ info.addAllInputFile(Artifact.toExecPaths(
+ LinkerInputs.toLibraryArtifacts(getLinkCommandLine().getRuntimeInputs())));
+ info.setOutputFile(getPrimaryOutput().getExecPathString());
+ if (interfaceOutputLibrary != null) {
+ info.setInterfaceOutputFile(interfaceOutputLibrary.getArtifact().getExecPathString());
+ }
+ info.setLinkTargetType(getLinkCommandLine().getLinkTargetType().name());
+ info.setLinkStaticness(getLinkCommandLine().getLinkStaticness().name());
+ info.addAllLinkStamp(Artifact.toExecPaths(getLinkCommandLine().getLinkstamps().values()));
+ info.addAllBuildInfoHeaderArtifact(
+ Artifact.toExecPaths(getLinkCommandLine().getBuildInfoHeaderArtifacts()));
+ info.addAllLinkOpt(getLinkCommandLine().getLinkopts());
+
+ return super.getExtraActionInfo()
+ .setExtension(CppLinkInfo.cppLinkInfo, info.build());
+ }
+
+ @Override
+ protected String computeKey() {
+ Fingerprint f = new Fingerprint();
+ f.addString(fake ? FAKE_LINK_GUID : LINK_GUID);
+ f.addString(getCppConfiguration().getLdExecutable().getPathString());
+ f.addStrings(linkCommandLine.arguments());
+ // TODO(bazel-team): For correctness, we need to ensure the invariant that all values accessed
+ // during the execution phase are also covered by the key. Above, we add the argv to the key,
+ // which covers most cases. Unfortunately, the extra action and fake support methods above also
+ // sometimes directly access settings from the link configuration that may or may not affect the
+ // key. We either need to change the code to cover them in the key computation, or change the
+ // LinkConfiguration to disallow the combinations where the value of a setting does not affect
+ // the argv.
+ f.addBoolean(linkCommandLine.isNativeDeps());
+ f.addBoolean(linkCommandLine.useTestOnlyFlags());
+ if (linkCommandLine.getRuntimeSolibDir() != null) {
+ f.addPath(linkCommandLine.getRuntimeSolibDir());
+ }
+ return f.hexDigestAndReset();
+ }
+
+ @Override
+ public String describeKey() {
+ StringBuilder message = new StringBuilder();
+ if (fake) {
+ message.append("Fake ");
+ }
+ message.append(getProgressMessage());
+ message.append('\n');
+ message.append(" Command: ");
+ message.append(ShellEscaper.escapeString(
+ getCppConfiguration().getLdExecutable().getPathString()));
+ message.append('\n');
+ // Outputting one argument per line makes it easier to diff the results.
+ for (String argument : ShellEscaper.escapeAll(linkCommandLine.arguments())) {
+ message.append(" Argument: ");
+ message.append(argument);
+ message.append('\n');
+ }
+ return message.toString();
+ }
+
+ @Override
+ public String getMnemonic() { return "CppLink"; }
+
+ @Override
+ protected String getRawProgressMessage() {
+ return "Linking " + outputLibrary.getArtifact().prettyPrint();
+ }
+
+ @Override
+ public ResourceSet estimateResourceConsumption(Executor executor) {
+ return executor.getContext(CppLinkActionContext.class).estimateResourceConsumption(this);
+ }
+
+ /**
+ * Estimate the resources consumed when this action is run locally.
+ */
+ public ResourceSet estimateResourceConsumptionLocal() {
+ // It's ok if this behaves differently even if the key is identical.
+ ResourceSet minLinkResources =
+ getLinkCommandLine().getLinkStaticness() == Link.LinkStaticness.DYNAMIC
+ ? MIN_DYNAMIC_LINK_RESOURCES
+ : MIN_STATIC_LINK_RESOURCES;
+
+ final int inputSize = Iterables.size(getLinkCommandLine().getLinkerInputs())
+ + Iterables.size(getLinkCommandLine().getRuntimeInputs());
+
+ return new ResourceSet(
+ Math.max(inputSize * LINK_RESOURCES_PER_INPUT.getMemoryMb(),
+ minLinkResources.getMemoryMb()),
+ Math.max(inputSize * LINK_RESOURCES_PER_INPUT.getCpuUsage(),
+ minLinkResources.getCpuUsage()),
+ Math.max(inputSize * LINK_RESOURCES_PER_INPUT.getIoUsage(),
+ minLinkResources.getIoUsage())
+ );
+ }
+
+ @Override
+ public Iterable<Artifact> getMandatoryInputs() {
+ return mandatoryInputs;
+ }
+
+ /**
+ * Determines whether or not this link should output a symbol counts file.
+ */
+ private static boolean enableSymbolsCounts(CppConfiguration cppConfiguration, boolean fake,
+ LinkTargetType linkType) {
+ return cppConfiguration.getSymbolCounts()
+ && cppConfiguration.supportsGoldLinker()
+ && linkType == LinkTargetType.EXECUTABLE
+ && !fake;
+ }
+
+ /**
+ * Builder class to construct {@link CppLinkAction}s.
+ */
+ public static class Builder {
+ // Builder-only
+ private final RuleContext ruleContext;
+ private final AnalysisEnvironment analysisEnvironment;
+ private final PathFragment outputPath;
+ private final CcToolchainProvider toolchain;
+ private PathFragment interfaceOutputPath;
+ private PathFragment runtimeSolibDir;
+ protected final BuildConfiguration configuration;
+ private final CppConfiguration cppConfiguration;
+
+ // Morally equivalent with {@link Context}, except these are mutable.
+ // Keep these in sync with {@link Context}.
+ private final Set<LinkerInput> nonLibraries = new LinkedHashSet<>();
+ private final NestedSetBuilder<LibraryToLink> libraries = NestedSetBuilder.linkOrder();
+ private NestedSet<Artifact> crosstoolInputs = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
+ private Artifact runtimeMiddleman;
+ 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 boolean fake;
+ private boolean isNativeDeps;
+ private boolean useTestOnlyFlags;
+ private boolean wholeArchive;
+ private boolean supportsParamFiles = true;
+
+ /**
+ * Creates a builder that builds {@link CppLinkAction} instances.
+ *
+ * @param ruleContext the rule that owns the action
+ * @param outputPath the path of the ELF file to be created, relative to the
+ * 'bin' directory
+ */
+ public Builder(RuleContext ruleContext, PathFragment outputPath) {
+ this(ruleContext, outputPath, ruleContext.getConfiguration(),
+ ruleContext.getAnalysisEnvironment(), CppHelper.getToolchain(ruleContext));
+ }
+
+ /**
+ * Creates a builder that builds {@link CppLinkAction} instances.
+ *
+ * @param ruleContext the rule that owns the action
+ * @param outputPath the path of the ELF file to be created, relative to the
+ * 'bin' directory
+ */
+ public Builder(RuleContext ruleContext, PathFragment outputPath,
+ BuildConfiguration configuration, CcToolchainProvider toolchain) {
+ this(ruleContext, outputPath, configuration,
+ ruleContext.getAnalysisEnvironment(), toolchain);
+ }
+
+ /**
+ * Creates a builder that builds {@link CppLinkAction}s.
+ *
+ * @param ruleContext the rule that owns the action
+ * @param outputPath the path of the ELF file to be created, relative to the
+ * 'bin' directory
+ * @param configuration the configuration used to determine the tool chain
+ * and the default link options
+ */
+ private Builder(RuleContext ruleContext, PathFragment outputPath,
+ BuildConfiguration configuration, AnalysisEnvironment analysisEnvironment,
+ CcToolchainProvider toolchain) {
+ this.ruleContext = ruleContext;
+ this.analysisEnvironment = Preconditions.checkNotNull(analysisEnvironment);
+ this.outputPath = Preconditions.checkNotNull(outputPath);
+ this.configuration = Preconditions.checkNotNull(configuration);
+ this.cppConfiguration = configuration.getFragment(CppConfiguration.class);
+ this.toolchain = toolchain;
+
+ // The toolchain != null is here for CppLinkAction.createTestBuilder(). Meh.
+ if (cppConfiguration.supportsEmbeddedRuntimes() && toolchain != null) {
+ runtimeSolibDir = toolchain.getDynamicRuntimeSolibDir();
+ }
+ if (toolchain != null) {
+ supportsParamFiles = toolchain.supportsParamFiles();
+ }
+ }
+
+ /**
+ * 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 outputPath the path of the ELF file to be created, relative to the
+ * 'bin' directory
+ * @param linkContext an immutable CppLinkAction.Context from the original builder
+ */
+ public Builder(RuleContext ruleContext, PathFragment outputPath, Context linkContext,
+ BuildConfiguration configuration) {
+ // These Builder-only fields get set in the constructor:
+ // ruleContext, analysisEnvironment, outputPath, configuration, runtimeSolibDir
+ this(ruleContext, outputPath, configuration, ruleContext.getAnalysisEnvironment(),
+ CppHelper.getToolchain(ruleContext));
+ Preconditions.checkNotNull(linkContext);
+
+ // All linkContext fields should be transferred to this Builder.
+ this.nonLibraries.addAll(linkContext.nonLibraries);
+ this.libraries.addTransitive(linkContext.libraries);
+ this.crosstoolInputs = linkContext.crosstoolInputs;
+ this.runtimeMiddleman = linkContext.runtimeMiddleman;
+ this.runtimeInputs = linkContext.runtimeInputs;
+ 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;
+ }
+
+ /**
+ * Builds the Action as configured and returns it.
+ *
+ * <p>This method may only be called once.
+ */
+ public CppLinkAction build() {
+ if (interfaceOutputPath != null && (fake || linkType != LinkTargetType.DYNAMIC_LIBRARY)) {
+ throw new RuntimeException("Interface output can only be used "
+ + "with non-fake DYNAMIC_LIBRARY targets");
+ }
+
+ final Artifact output = createArtifact(outputPath);
+ final Artifact interfaceOutput = (interfaceOutputPath != null)
+ ? createArtifact(interfaceOutputPath)
+ : null;
+
+ final ImmutableList<Artifact> buildInfoHeaderArtifacts = !linkstamps.isEmpty()
+ ? ruleContext.getAnalysisEnvironment().getBuildInfo(ruleContext, CppBuildInfo.KEY)
+ : ImmutableList.<Artifact>of();
+
+ final Artifact symbolCountOutput = enableSymbolsCounts(cppConfiguration, fake, linkType)
+ ? createArtifact(output.getRootRelativePath().replaceName(
+ output.getExecPath().getBaseName() + ".sc"))
+ : null;
+
+ boolean needWholeArchive = wholeArchive || needWholeArchive(
+ linkStaticness, linkType, linkopts, isNativeDeps, cppConfiguration);
+
+ NestedSet<LibraryToLink> uniqueLibraries = libraries.build();
+ final Iterable<Artifact> filteredNonLibraryArtifacts = filterLinkerInputArtifacts(
+ LinkerInputs.toLibraryArtifacts(nonLibraries));
+ final Iterable<LinkerInput> linkerInputs = IterablesChain.<LinkerInput>builder()
+ .add(ImmutableList.copyOf(filterLinkerInputs(nonLibraries)))
+ .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();
+
+ final LibraryToLink outputLibrary =
+ LinkerInputs.newInputLibrary(output, filteredNonLibraryArtifacts);
+ final LibraryToLink interfaceOutputLibrary = interfaceOutput == null ? null :
+ LinkerInputs.newInputLibrary(interfaceOutput, filteredNonLibraryArtifacts);
+
+ final ImmutableMap<Artifact, Artifact> linkstampMap =
+ mapLinkstampsToOutputs(linkstamps, ruleContext, output);
+
+ final ImmutableList<Artifact> actionOutputs = constructOutputs(
+ outputLibrary.getArtifact(),
+ linkstampMap.values(),
+ interfaceOutputLibrary == null ? null : interfaceOutputLibrary.getArtifact(),
+ symbolCountOutput);
+
+ LinkCommandLine linkCommandLine = new LinkCommandLine.Builder(configuration, getOwner())
+ .setOutput(outputLibrary.getArtifact())
+ .setInterfaceOutput(interfaceOutput)
+ .setSymbolCountsOutput(symbolCountOutput)
+ .setBuildInfoHeaderArtifacts(buildInfoHeaderArtifacts)
+ .setLinkerInputs(linkerInputs)
+ .setRuntimeInputs(ImmutableList.copyOf(LinkerInputs.simpleLinkerInputs(runtimeInputs)))
+ .setLinkTargetType(linkType)
+ .setLinkStaticness(linkStaticness)
+ .setLinkopts(ImmutableList.copyOf(linkopts))
+ .setFeatures(features)
+ .setLinkstamps(linkstampMap)
+ .addLinkstampCompileOptions(linkstampOptions)
+ .setRuntimeSolibDir(linkType.isStaticLibraryLink() ? null : runtimeSolibDir)
+ .setNativeDeps(isNativeDeps)
+ .setUseTestOnlyFlags(useTestOnlyFlags)
+ .setNeedWholeArchive(needWholeArchive)
+ .setInterfaceSoBuilder(getInterfaceSoBuilder())
+ .setSupportsParamFiles(supportsParamFiles)
+ .build();
+
+ // Compute the set of inputs - we only need stable order here.
+ NestedSetBuilder<Artifact> dependencyInputsBuilder = NestedSetBuilder.stableOrder();
+ dependencyInputsBuilder.addAll(buildInfoHeaderArtifacts);
+ dependencyInputsBuilder.addAll(linkstamps);
+ dependencyInputsBuilder.addTransitive(crosstoolInputs);
+ if (runtimeMiddleman != null) {
+ dependencyInputsBuilder.add(runtimeMiddleman);
+ }
+ dependencyInputsBuilder.addTransitive(compilationInputs.build());
+
+ Iterable<Artifact> expandedInputs =
+ LinkerInputs.toLibraryArtifacts(Link.mergeInputsDependencies(uniqueLibraries,
+ needWholeArchive, cppConfiguration.archiveType()));
+ // getPrimaryInput returns the first element, and that is a public interface - therefore the
+ // order here is important.
+ Iterable<Artifact> inputs = IterablesChain.<Artifact>builder()
+ .add(ImmutableList.copyOf(LinkerInputs.toLibraryArtifacts(nonLibraries)))
+ .add(dependencyInputsBuilder.build())
+ .add(ImmutableIterable.from(expandedInputs))
+ .deduplicate()
+ .build();
+
+ return new CppLinkAction(
+ getOwner(),
+ inputs,
+ actionOutputs,
+ cppConfiguration,
+ outputLibrary,
+ interfaceOutputLibrary,
+ fake,
+ linkCommandLine);
+ }
+
+ /**
+ * 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, Artifact outputBinary) {
+ 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,
+ ruleContext.getAnalysisEnvironment().getDerivedArtifact(
+ stampOutputPath, outputBinary.getRoot()));
+ }
+ return mapBuilder.build();
+ }
+
+ protected ActionOwner getOwner() {
+ return ruleContext.getActionOwner();
+ }
+
+ protected Artifact createArtifact(PathFragment path) {
+ return analysisEnvironment.getDerivedArtifact(path, configuration.getBinDirectory());
+ }
+
+ protected Artifact getInterfaceSoBuilder() {
+ return analysisEnvironment.getEmbeddedToolArtifact(CppRuleClasses.BUILD_INTERFACE_SO);
+ }
+
+ /**
+ * Set the crosstool inputs required for the action.
+ */
+ public Builder setCrosstoolInputs(NestedSet<Artifact> inputs) {
+ this.crosstoolInputs = inputs;
+ return this;
+ }
+
+ /**
+ * Sets the C++ runtime library inputs for the action.
+ */
+ public Builder setRuntimeInputs(Artifact middleman, NestedSet<Artifact> inputs) {
+ Preconditions.checkArgument((middleman == null) == inputs.isEmpty());
+ this.runtimeMiddleman = middleman;
+ this.runtimeInputs = inputs;
+ 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 Builder setInterfaceOutputPath(PathFragment path) {
+ this.interfaceOutputPath = path;
+ return this;
+ }
+
+ /**
+ * Add additional inputs needed for the linkstamp compilation that is being done as part of the
+ * link.
+ */
+ public Builder addCompilationInputs(Iterable<Artifact> inputs) {
+ this.compilationInputs.addAll(inputs);
+ return this;
+ }
+
+ public Builder addTransitiveCompilationInputs(NestedSet<Artifact> inputs) {
+ this.compilationInputs.addTransitive(inputs);
+ return this;
+ }
+
+ private void addNonLibraryInput(LinkerInput input) {
+ String name = input.getArtifact().getFilename();
+ Preconditions.checkArgument(
+ !Link.ARCHIVE_LIBRARY_FILETYPES.matches(name)
+ && !Link.SHARED_LIBRARY_FILETYPES.matches(name),
+ "'%s' is a library file", input);
+ this.nonLibraries.add(input);
+ }
+ /**
+ * Adds a single artifact to the set of inputs (C++ source files, header files, etc). Artifacts
+ * that are not of recognized types will be used for dependency checking but will not be passed
+ * to the linker. The artifact must not be an archive or a shared library.
+ */
+ public Builder addNonLibraryInput(Artifact input) {
+ addNonLibraryInput(LinkerInputs.simpleLinkerInput(input));
+ return this;
+ }
+
+ /**
+ * Adds multiple artifacts to the set of inputs (C++ source files, header files, etc).
+ * Artifacts that are not of recognized types will be used for dependency checking but will
+ * not be passed to the linker. The artifacts must not be archives or shared libraries.
+ */
+ public Builder addNonLibraryInputs(Iterable<Artifact> inputs) {
+ for (Artifact input : inputs) {
+ addNonLibraryInput(LinkerInputs.simpleLinkerInput(input));
+ }
+ return this;
+ }
+
+ public Builder addFakeNonLibraryInputs(Iterable<Artifact> inputs) {
+ for (Artifact input : inputs) {
+ addNonLibraryInput(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 Builder 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 Builder 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 Builder 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 Builder setLinkStaticness(LinkStaticness linkStaticness) {
+ this.linkStaticness = linkStaticness;
+ 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 Builder addLinkstamps(Map<Artifact, ImmutableList<Artifact>> linkstamps) {
+ this.linkstamps.addAll(linkstamps.keySet());
+ // Add inputs for linkstamping.
+ if (!linkstamps.isEmpty()) {
+ // This will just be the compiler unless include scanning is disabled, in which case it will
+ // include all header files. Since we insist that linkstamps declare all their headers, all
+ // header files would be overkill, but that only happens when include scanning is disabled.
+ addTransitiveCompilationInputs(toolchain.getCompile());
+ for (Map.Entry<Artifact, ImmutableList<Artifact>> entry : linkstamps.entrySet()) {
+ addCompilationInputs(entry.getValue());
+ }
+ }
+ return this;
+ }
+
+ public Builder addLinkstampCompilerOptions(ImmutableList<String> linkstampOptions) {
+ this.linkstampOptions = linkstampOptions;
+ return this;
+ }
+
+ /**
+ * Adds an additional linker option.
+ */
+ public Builder addLinkopt(String linkopt) {
+ this.linkopts.add(linkopt);
+ return this;
+ }
+
+ /**
+ * Adds multiple linker options at once.
+ *
+ * @see #addLinkopt(String)
+ */
+ public Builder addLinkopts(Collection<String> linkopts) {
+ this.linkopts.addAll(linkopts);
+ return this;
+ }
+
+ /**
+ * Sets whether this link action will be used for a cc_fake_binary; false by
+ * default.
+ */
+ public Builder setFake(boolean fake) {
+ this.fake = fake;
+ return this;
+ }
+
+ /**
+ * Sets whether this link action is used for a native dependency library.
+ */
+ public Builder 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 Builder 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 Builder 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 Builder setRuntimeSolibDir(PathFragment runtimeSolibDir) {
+ this.runtimeSolibDir = runtimeSolibDir;
+ return this;
+ }
+
+ /**
+ * Creates a builder without the need for a {@link RuleContext}.
+ * This is to be used exclusively for testing purposes.
+ *
+ * <p>Link stamping is not supported if using this method.
+ */
+ @VisibleForTesting
+ public static Builder createTestBuilder(
+ final ActionOwner owner, final AnalysisEnvironment analysisEnvironment,
+ final PathFragment outputPath, BuildConfiguration config) {
+ return new Builder(null, outputPath, config, analysisEnvironment, null) {
+ @Override
+ protected Artifact createArtifact(PathFragment path) {
+ return new Artifact(configuration.getBinDirectory().getPath().getRelative(path),
+ configuration.getBinDirectory(), configuration.getBinFragment().getRelative(path),
+ analysisEnvironment.getOwner());
+ }
+ @Override
+ protected ActionOwner getOwner() {
+ return owner;
+ }
+ };
+ }
+ }
+
+ /**
+ * Immutable ELF linker context, suitable for serialization.
+ */
+ @Immutable @ThreadSafe
+ public static final class Context implements TransitiveInfoProvider {
+ // Morally equivalent with {@link Builder}, except these are immutable.
+ // Keep these in sync with {@link Builder}.
+ private final ImmutableSet<LinkerInput> nonLibraries;
+ private final NestedSet<LibraryToLink> libraries;
+ private final NestedSet<Artifact> crosstoolInputs;
+ private final Artifact runtimeMiddleman;
+ private final NestedSet<Artifact> runtimeInputs;
+ private final NestedSet<Artifact> compilationInputs;
+ private final ImmutableSet<Artifact> linkstamps;
+ private final ImmutableList<String> linkopts;
+ private final LinkTargetType linkType;
+ private final LinkStaticness linkStaticness;
+ private final boolean fake;
+ private final boolean isNativeDeps;
+ private final boolean useTestOnlyFlags;
+
+ /**
+ * Given a {@link Builder}, creates a {@code Context} to pass to another target.
+ * Note well: Keep the Builder->Context and Context->Builder transforms consistent!
+ * @param builder a mutable {@link CppLinkAction.Builder} to clone from
+ */
+ public Context(Builder builder) {
+ this.nonLibraries = ImmutableSet.copyOf(builder.nonLibraries);
+ this.libraries = NestedSetBuilder.<LibraryToLink>linkOrder()
+ .addTransitive(builder.libraries.build()).build();
+ this.crosstoolInputs =
+ NestedSetBuilder.<Artifact>stableOrder().addTransitive(builder.crosstoolInputs).build();
+ this.runtimeMiddleman = builder.runtimeMiddleman;
+ this.runtimeInputs =
+ NestedSetBuilder.<Artifact>stableOrder().addTransitive(builder.runtimeInputs).build();
+ this.compilationInputs = NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(builder.compilationInputs.build()).build();
+ this.linkstamps = ImmutableSet.copyOf(builder.linkstamps);
+ this.linkopts = ImmutableList.copyOf(builder.linkopts);
+ this.linkType = builder.linkType;
+ this.linkStaticness = builder.linkStaticness;
+ this.fake = builder.fake;
+ this.isNativeDeps = builder.isNativeDeps;
+ this.useTestOnlyFlags = builder.useTestOnlyFlags;
+ }
+ }
+}