blob: d03d99b6cb296412bcc72379bf1787076c027f64 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.rules.cpp;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.CommandLine;
import com.google.devtools.build.lib.actions.CommandLineExpansionException;
import com.google.devtools.build.lib.collect.CollectionUtils;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ExpansionException;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType;
import com.google.devtools.build.lib.rules.cpp.Link.LinkerOrArchiver;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
/**
* Represents the command line of a linker invocation. It supports executables and dynamic libraries
* as well as static libraries.
*/
@AutoCodec
@Immutable
public final class LinkCommandLine extends CommandLine {
private final String actionName;
private final String forcedToolPath;
private final CcToolchainVariables variables;
// The feature config can be null for tests.
@Nullable private final FeatureConfiguration featureConfiguration;
private final ImmutableList<Artifact> buildInfoHeaderArtifacts;
private final Iterable<Artifact> linkerInputArtifacts;
private final LinkTargetType linkTargetType;
private final Link.LinkingMode linkingMode;
@Nullable private final PathFragment toolchainLibrariesSolibDir;
private final boolean nativeDeps;
private final boolean useTestOnlyFlags;
private final boolean doNotSplitLinkingCmdLine;
@Nullable private final Artifact paramFile;
@VisibleForSerialization
LinkCommandLine(
String actionName,
String forcedToolPath,
ImmutableList<Artifact> buildInfoHeaderArtifacts,
Iterable<Artifact> linkerInputArtifacts,
LinkTargetType linkTargetType,
Link.LinkingMode linkingMode,
@Nullable PathFragment toolchainLibrariesSolibDir,
boolean nativeDeps,
boolean useTestOnlyFlags,
@Nullable Artifact paramFile,
CcToolchainVariables variables,
@Nullable FeatureConfiguration featureConfiguration,
boolean doNotSplitLinkingCmdLine) {
this.actionName = actionName;
this.forcedToolPath = forcedToolPath;
this.variables = variables;
this.featureConfiguration = featureConfiguration;
this.buildInfoHeaderArtifacts = Preconditions.checkNotNull(buildInfoHeaderArtifacts);
this.linkerInputArtifacts = Preconditions.checkNotNull(linkerInputArtifacts);
this.linkTargetType = Preconditions.checkNotNull(linkTargetType);
this.linkingMode = Preconditions.checkNotNull(linkingMode);
this.toolchainLibrariesSolibDir = toolchainLibrariesSolibDir;
this.nativeDeps = nativeDeps;
this.useTestOnlyFlags = useTestOnlyFlags;
this.paramFile = paramFile;
this.doNotSplitLinkingCmdLine = doNotSplitLinkingCmdLine;
}
@Nullable
public Artifact getParamFile() {
return paramFile;
}
/** See {@link CppLinkAction#getBuildInfoHeaderArtifacts()} */
public ImmutableList<Artifact> getBuildInfoHeaderArtifacts() {
return buildInfoHeaderArtifacts;
}
/** Returns the (ordered, immutable) list of paths to the linker's input files. */
public Iterable<Artifact> getLinkerInputArtifacts() {
return linkerInputArtifacts;
}
@Nullable
@VisibleForTesting
public FeatureConfiguration getFeatureConfiguration() {
return featureConfiguration;
}
/** Returns the current type of link target set. */
public LinkTargetType getLinkTargetType() {
return linkTargetType;
}
/** Returns the "staticness" of the link. */
public Link.LinkingMode getLinkingMode() {
return linkingMode;
}
/** Returns the path to the linker. */
public String getLinkerPathString() {
return featureConfiguration.getToolPathForAction(linkTargetType.getActionName());
}
/**
* Returns the location of the C++ runtime solib symlinks. If null, the C++ dynamic runtime
* libraries either do not exist (because they do not come from the depot) or they are in the
* regular solib directory.
*/
@Nullable
public PathFragment getToolchainLibrariesSolibDir() {
return toolchainLibrariesSolibDir;
}
/** Returns true for libraries linked as native dependencies for other languages. */
public boolean isNativeDeps() {
return nativeDeps;
}
/**
* Returns true if this link should use test-specific flags (e.g. $EXEC_ORIGIN as the root for
* finding shared libraries or lazy binding); false by default. See bug "Please use $EXEC_ORIGIN
* instead of $ORIGIN when linking cc_tests" for further context.
*/
public boolean useTestOnlyFlags() {
return useTestOnlyFlags;
}
/** Returns the build variables used to template the crosstool for this linker invocation. */
@VisibleForTesting
public CcToolchainVariables getBuildVariables() {
return this.variables;
}
/**
* Splits the link command-line into a part to be written to a parameter file, and the remaining
* actual command line to be executed (which references the parameter file). Should only be used
* if getParamFile() is not null.
*/
@VisibleForTesting
final Pair<List<String>, List<String>> splitCommandline() throws CommandLineExpansionException {
return splitCommandline(
paramFile, getRawLinkArgv(null), linkTargetType, doNotSplitLinkingCmdLine);
}
@VisibleForTesting
final Pair<List<String>, List<String>> splitCommandline(@Nullable ArtifactExpander expander)
throws CommandLineExpansionException {
return splitCommandline(
paramFile, getRawLinkArgv(expander), linkTargetType, doNotSplitLinkingCmdLine);
}
private static Pair<List<String>, List<String>> splitCommandline(
Artifact paramFile,
List<String> args,
LinkTargetType linkTargetType,
boolean doNotSplitLinkingCmdline) {
Preconditions.checkNotNull(paramFile);
if (linkTargetType.linkerOrArchiver() == LinkerOrArchiver.ARCHIVER) {
// Ar link commands can also generate huge command lines.
List<String> paramFileArgs = new ArrayList<>();
List<String> commandlineArgs = new ArrayList<>();
extractArgumentsForStaticLinkParamFile(args, commandlineArgs, paramFileArgs);
return Pair.of(commandlineArgs, paramFileArgs);
} else {
// Gcc link commands tend to generate humongous commandlines for some targets, which may
// not fit on some remote execution machines. To work around this we will employ the help of
// a parameter file and pass any linker options through it.
List<String> paramFileArgs = new ArrayList<>();
List<String> commandlineArgs = new ArrayList<>();
extractArgumentsForDynamicLinkParamFile(
args, commandlineArgs, paramFileArgs, doNotSplitLinkingCmdline);
return Pair.of(commandlineArgs, paramFileArgs);
}
}
/**
* A {@link CommandLine} implementation that returns the command line args pertaining to the
* .params file.
*/
@AutoCodec
@VisibleForSerialization
static class ParamFileCommandLine extends CommandLine {
private final Artifact paramsFile;
private final LinkTargetType linkTargetType;
private final String forcedToolPath;
private final FeatureConfiguration featureConfiguration;
private final String actionName;
private final CcToolchainVariables variables;
private final boolean doNotSplitLinkingCmdLine;
public ParamFileCommandLine(
Artifact paramsFile,
LinkTargetType linkTargetType,
String forcedToolPath,
FeatureConfiguration featureConfiguration,
String actionName,
CcToolchainVariables variables,
boolean doNotSplitLinkingCmdLine) {
this.paramsFile = paramsFile;
this.linkTargetType = linkTargetType;
this.forcedToolPath = forcedToolPath;
this.featureConfiguration = featureConfiguration;
this.actionName = actionName;
this.variables = variables;
this.doNotSplitLinkingCmdLine = doNotSplitLinkingCmdLine;
}
@Override
public Iterable<String> arguments() throws CommandLineExpansionException {
List<String> argv =
getRawLinkArgv(
null, forcedToolPath, featureConfiguration, actionName, linkTargetType, variables);
return splitCommandline(paramsFile, argv, linkTargetType, doNotSplitLinkingCmdLine)
.getSecond();
}
@Override
public Iterable<String> arguments(ArtifactExpander expander)
throws CommandLineExpansionException {
List<String> argv =
getRawLinkArgv(
expander,
forcedToolPath,
featureConfiguration,
actionName,
linkTargetType,
variables);
return splitCommandline(paramsFile, argv, linkTargetType, doNotSplitLinkingCmdLine)
.getSecond();
}
}
/** Returns just the .params file portion of the command-line as a {@link CommandLine}. */
CommandLine paramCmdLine() {
Preconditions.checkNotNull(paramFile);
return new ParamFileCommandLine(
paramFile,
linkTargetType,
forcedToolPath,
featureConfiguration,
actionName,
variables,
doNotSplitLinkingCmdLine);
}
public static void extractArgumentsForStaticLinkParamFile(
List<String> args, List<String> commandlineArgs, List<String> paramFileArgs) {
commandlineArgs.add(args.get(0)); // ar command, must not be moved!
int argsSize = args.size();
for (int i = 1; i < argsSize; i++) {
String arg = args.get(i);
if (arg.startsWith("@")) {
commandlineArgs.add(arg); // params file, keep it in the command line
} else {
paramFileArgs.add(arg); // the rest goes to the params file
}
}
}
public static void extractArgumentsForDynamicLinkParamFile(
List<String> args,
List<String> commandlineArgs,
List<String> paramFileArgs,
boolean doNotSplitLinkingCmdline) {
// Note, that it is not important that all linker arguments are extracted so that
// they can be moved into a parameter file, but the vast majority should.
commandlineArgs.add(args.get(0)); // gcc command, must not be moved!
int argsSize = args.size();
if (doNotSplitLinkingCmdline) {
for (int i = 1; i < argsSize; i++) {
String arg = args.get(i);
if (arg.startsWith("@")) {
commandlineArgs.add(arg); // params file, keep it in the command line
} else {
paramFileArgs.add(arg); // the rest goes to the params file
}
}
} else {
for (int i = 1; i < argsSize; i++) {
String arg = args.get(i);
if (arg.isEmpty()) {
continue;
}
if (arg.equals("-Wl,-no-whole-archive")) {
paramFileArgs.add("-no-whole-archive");
} else if (arg.equals("-Wl,-whole-archive")) {
paramFileArgs.add("-whole-archive");
} else if (arg.equals("-Wl,--start-group")) {
paramFileArgs.add("--start-group");
} else if (arg.equals("-Wl,--end-group")) {
paramFileArgs.add("--end-group");
} else if (arg.equals("-Wl,--start-lib")) {
paramFileArgs.add("--start-lib");
} else if (arg.equals("-Wl,--end-lib")) {
paramFileArgs.add("--end-lib");
} else if (arg.charAt(0) == '-') {
if (arg.startsWith("-l")) {
paramFileArgs.add(arg);
} else {
// Anything else starting with a '-' can stay on the commandline.
commandlineArgs.add(arg);
if (arg.equals("-o")) {
// Special case for '-o': add the following argument as well - it is the output file!
commandlineArgs.add(args.get(++i));
}
}
} else if (CppFileTypes.OBJECT_FILE.apply(arg)
|| CppFileTypes.PIC_OBJECT_FILE.apply(arg)
|| CppFileTypes.ARCHIVE.apply(arg)
|| CppFileTypes.PIC_ARCHIVE.apply(arg)
|| CppFileTypes.ALWAYS_LINK_LIBRARY.apply(arg)
|| CppFileTypes.ALWAYS_LINK_PIC_LIBRARY.apply(arg)
|| CppFileTypes.SHARED_LIBRARY.apply(arg)
|| CppFileTypes.INTERFACE_SHARED_LIBRARY.apply(arg)
|| CppFileTypes.VERSIONED_SHARED_LIBRARY.apply(arg)) {
// All objects of any kind go into the linker parameters.
paramFileArgs.add(arg);
} else {
// Everything that's left stays conservatively on the commandline.
commandlineArgs.add(arg);
}
}
}
}
/**
* Returns a raw link command for the given link invocation, including both command and arguments
* (argv). The version that uses the expander is preferred, but that one can't be used during
* analysis.
*
* @return raw link command line.
*/
public List<String> getRawLinkArgv() throws CommandLineExpansionException {
return getRawLinkArgv(null);
}
/**
* Returns a raw link command for the given link invocation, including both command and arguments
* (argv).
*
* @param expander ArtifactExpander for expanding TreeArtifacts.
* @return raw link command line.
*/
public List<String> getRawLinkArgv(@Nullable ArtifactExpander expander)
throws CommandLineExpansionException {
return getRawLinkArgv(
expander, forcedToolPath, featureConfiguration, actionName, linkTargetType, variables);
}
private static List<String> getRawLinkArgv(
@Nullable ArtifactExpander expander,
String forcedToolPath,
FeatureConfiguration featureConfiguration,
String actionName,
LinkTargetType linkTargetType,
CcToolchainVariables variables)
throws CommandLineExpansionException {
List<String> argv = new ArrayList<>();
if (forcedToolPath != null) {
argv.add(forcedToolPath);
} else {
Preconditions.checkArgument(
featureConfiguration.actionIsConfigured(actionName),
String.format("Expected action_config for '%s' to be configured", actionName));
argv.add(featureConfiguration.getToolPathForAction(linkTargetType.getActionName()));
}
try {
argv.addAll(featureConfiguration.getCommandLine(actionName, variables, expander));
} catch (ExpansionException e) {
throw new CommandLineExpansionException(e.getMessage());
}
return argv;
}
List<String> getCommandLine(@Nullable ArtifactExpander expander)
throws CommandLineExpansionException {
// Try to shorten the command line by use of a parameter file.
// This makes the output with --subcommands (et al) more readable.
if (paramFile != null) {
Pair<List<String>, List<String>> split = splitCommandline(expander);
return split.first;
} else {
return getRawLinkArgv(expander);
}
}
@Override
public List<String> arguments() throws CommandLineExpansionException {
return getRawLinkArgv(null);
}
@Override
public Iterable<String> arguments(ArtifactExpander artifactExpander)
throws CommandLineExpansionException {
return getRawLinkArgv(artifactExpander);
}
/** A builder for a {@link LinkCommandLine}. */
public static final class Builder {
private String forcedToolPath;
private ImmutableList<Artifact> buildInfoHeaderArtifacts = ImmutableList.of();
private Iterable<Artifact> linkerInputArtifacts = ImmutableList.of();
@Nullable private LinkTargetType linkTargetType;
private Link.LinkingMode linkingMode = Link.LinkingMode.STATIC;
@Nullable private PathFragment toolchainLibrariesSolibDir;
private boolean nativeDeps;
private boolean useTestOnlyFlags;
@Nullable private Artifact paramFile;
private CcToolchainVariables variables;
private FeatureConfiguration featureConfiguration;
private boolean doNotSplitLinkingCmdLine;
private String actionName;
public LinkCommandLine build() {
if (linkTargetType.linkerOrArchiver() == LinkerOrArchiver.ARCHIVER) {
Preconditions.checkArgument(
buildInfoHeaderArtifacts.isEmpty(),
"build info headers may only be present on dynamic library or executable links");
}
if (variables == null) {
variables = CcToolchainVariables.EMPTY;
}
return new LinkCommandLine(
actionName,
forcedToolPath,
buildInfoHeaderArtifacts,
linkerInputArtifacts,
linkTargetType,
linkingMode,
toolchainLibrariesSolibDir,
nativeDeps,
useTestOnlyFlags,
paramFile,
variables,
featureConfiguration,
doNotSplitLinkingCmdLine);
}
/** Use given tool path instead of the one from feature configuration */
public Builder forceToolPath(String forcedToolPath) {
this.forcedToolPath = forcedToolPath;
return this;
}
/** Sets the feature configuration for this link action. */
public Builder setFeatureConfiguration(FeatureConfiguration featureConfiguration) {
this.featureConfiguration = featureConfiguration;
return this;
}
/**
* Sets the type of the link. It is an error to try to set this to {@link
* LinkTargetType#INTERFACE_DYNAMIC_LIBRARY}. Note that all the static target types (see {@link
* LinkTargetType#linkerOrArchiver}) are equivalent, and there is no check that the output
* artifact matches the target type extension.
*/
public Builder setLinkTargetType(LinkTargetType linkTargetType) {
Preconditions.checkArgument(linkTargetType != LinkTargetType.INTERFACE_DYNAMIC_LIBRARY);
this.linkTargetType = linkTargetType;
return this;
}
/**
* Sets a list of linker input artifacts. These get turned into linker options depending on the
* staticness and the target type. This call makes an immutable copy of the inputs, if the
* provided Iterable isn't already immutable (see {@link CollectionUtils#makeImmutable}).
*/
public Builder setLinkerInputArtifacts(Iterable<Artifact> linkerInputArtifacts) {
this.linkerInputArtifacts = CollectionUtils.makeImmutable(linkerInputArtifacts);
return this;
}
/**
* Sets how static the link is supposed to be. For static target types (see {@link
* LinkTargetType#linkerOrArchiver()}}), the {@link #build} method throws an exception if this
* is not {@link LinkingMode#STATIC}. The default setting is {@link LinkingMode#STATIC}.
*/
public Builder setLinkingMode(Link.LinkingMode linkingMode) {
this.linkingMode = linkingMode;
return this;
}
/**
* The build info header artifacts are generated header files that are used for link stamping.
* The {@link #build} method throws an exception if the build info header artifacts are
* non-empty for a static link (see {@link LinkTargetType#linkerOrArchiver()}}).
*/
public Builder setBuildInfoHeaderArtifacts(ImmutableList<Artifact> buildInfoHeaderArtifacts) {
this.buildInfoHeaderArtifacts = buildInfoHeaderArtifacts;
return this;
}
/**
* Whether the resulting library is intended to be used as a native library from another
* programming language. This influences the rpath. The {@link #build} method throws an
* exception if this is true for a static link (see {@link LinkTargetType#linkerOrArchiver()}}).
*/
public Builder setNativeDeps(boolean nativeDeps) {
this.nativeDeps = nativeDeps;
return this;
}
/**
* Sets whether to use test-specific linker flags, e.g. {@code $EXEC_ORIGIN} instead of {@code
* $ORIGIN} in the rpath or lazy binding.
*/
public Builder setUseTestOnlyFlags(boolean useTestOnlyFlags) {
this.useTestOnlyFlags = useTestOnlyFlags;
return this;
}
public Builder setParamFile(Artifact paramFile) {
this.paramFile = paramFile;
return this;
}
public Builder setBuildVariables(CcToolchainVariables variables) {
this.variables = variables;
return this;
}
public Builder setToolchainLibrariesSolibDir(PathFragment toolchainLibrariesSolibDir) {
this.toolchainLibrariesSolibDir = toolchainLibrariesSolibDir;
return this;
}
public Builder doNotSplitLinkingCmdLine() {
this.doNotSplitLinkingCmdLine = true;
return this;
}
public Builder setActionName(String actionName) {
this.actionName = actionName;
return this;
}
}
}