blob: 72a38ddbd270b0e02f5b1533873ad81ce68954a6 [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.actions.PathMapper;
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.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
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.
*/
@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 LinkTargetType linkTargetType;
private final Link.LinkingMode linkingMode;
@Nullable private final PathFragment toolchainLibrariesSolibDir;
private final boolean nativeDeps;
private final boolean useTestOnlyFlags;
@Nullable private final Artifact paramFile;
private LinkCommandLine(
String actionName,
String forcedToolPath,
LinkTargetType linkTargetType,
Link.LinkingMode linkingMode,
@Nullable PathFragment toolchainLibrariesSolibDir,
boolean nativeDeps,
boolean useTestOnlyFlags,
@Nullable Artifact paramFile,
CcToolchainVariables variables,
@Nullable FeatureConfiguration featureConfiguration) {
this.actionName = actionName;
this.forcedToolPath = forcedToolPath;
this.variables = variables;
this.featureConfiguration = featureConfiguration;
this.linkTargetType = Preconditions.checkNotNull(linkTargetType);
this.linkingMode = Preconditions.checkNotNull(linkingMode);
this.toolchainLibrariesSolibDir = toolchainLibrariesSolibDir;
this.nativeDeps = nativeDeps;
this.useTestOnlyFlags = useTestOnlyFlags;
this.paramFile = paramFile;
}
@Nullable
public Artifact getParamFile() {
return paramFile;
}
public String getActionName() {
return actionName;
}
/** 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);
}
@VisibleForTesting
final Pair<List<String>, List<String>> splitCommandline(@Nullable ArtifactExpander expander)
throws CommandLineExpansionException {
return splitCommandline(paramFile, getRawLinkArgv(expander), linkTargetType);
}
private static Pair<List<String>, List<String>> splitCommandline(
Artifact paramFile, List<String> args, LinkTargetType linkTargetType) {
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);
return Pair.of(commandlineArgs, paramFileArgs);
}
}
public CommandLine getCommandLineForStarlark() {
return new CommandLine() {
@Override
public Iterable<String> arguments() throws CommandLineExpansionException {
return arguments(/* artifactExpander= */ null, PathMapper.NOOP);
}
@Override
public ImmutableList<String> arguments(
ArtifactExpander artifactExpander, PathMapper pathMapper)
throws CommandLineExpansionException {
if (paramFile == null) {
return ImmutableList.copyOf(getRawLinkArgv(artifactExpander));
} else {
return ImmutableList.<String>builder()
.add(getLinkerPathString())
.addAll(splitCommandline(artifactExpander).getSecond())
.build();
}
}
};
}
/**
* A {@link CommandLine} implementation that returns the command line args pertaining to the
* .params file.
*/
private 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;
ParamFileCommandLine(
Artifact paramsFile,
LinkTargetType linkTargetType,
String forcedToolPath,
FeatureConfiguration featureConfiguration,
String actionName,
CcToolchainVariables variables) {
this.paramsFile = paramsFile;
this.linkTargetType = linkTargetType;
this.forcedToolPath = forcedToolPath;
this.featureConfiguration = featureConfiguration;
this.actionName = actionName;
this.variables = variables;
}
@Override
public Iterable<String> arguments() throws CommandLineExpansionException {
List<String> argv =
getRawLinkArgv(
null, forcedToolPath, featureConfiguration, actionName, linkTargetType, variables);
return splitCommandline(paramsFile, argv, linkTargetType).getSecond();
}
@Override
public Iterable<String> arguments(ArtifactExpander expander, PathMapper pathMapper)
throws CommandLineExpansionException {
List<String> argv =
getRawLinkArgv(
expander,
forcedToolPath,
featureConfiguration,
actionName,
linkTargetType,
variables);
return splitCommandline(paramsFile, argv, linkTargetType).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);
}
private 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 (isLikelyParamFile(arg)) {
commandlineArgs.add(arg); // params file, keep it in the command line
} else {
paramFileArgs.add(arg); // the rest goes to the params file
}
}
}
private static void extractArgumentsForDynamicLinkParamFile(
List<String> args, List<String> commandlineArgs, List<String> paramFileArgs) {
// 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();
for (int i = 1; i < argsSize; i++) {
String arg = args.get(i);
if (isLikelyParamFile(arg)) {
commandlineArgs.add(arg); // params file, keep it in the command line
} else {
paramFileArgs.add(arg); // the rest goes to the params file
}
}
}
private static boolean isLikelyParamFile(String arg) {
return arg.startsWith("@")
&& !arg.startsWith("@rpath")
&& !arg.startsWith("@loader_path")
&& !arg.startsWith("@executable_path");
}
/**
* 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 List<String> arguments(ArtifactExpander artifactExpander, PathMapper pathMapper)
throws CommandLineExpansionException {
return getRawLinkArgv(artifactExpander);
}
/** A builder for a {@link LinkCommandLine}. */
public static final class Builder {
private String forcedToolPath;
@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 String actionName;
public LinkCommandLine build() {
if (variables == null) {
variables = CcToolchainVariables.EMPTY;
}
return new LinkCommandLine(
actionName,
forcedToolPath,
linkTargetType,
linkingMode,
toolchainLibrariesSolibDir,
nativeDeps,
useTestOnlyFlags,
paramFile,
variables,
featureConfiguration);
}
/** Use given tool path instead of the one from feature configuration */
@CanIgnoreReturnValue
public Builder forceToolPath(String forcedToolPath) {
this.forcedToolPath = forcedToolPath;
return this;
}
/** Sets the feature configuration for this link action. */
@CanIgnoreReturnValue
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.
*/
@CanIgnoreReturnValue
public Builder setLinkTargetType(LinkTargetType linkTargetType) {
Preconditions.checkArgument(linkTargetType != LinkTargetType.INTERFACE_DYNAMIC_LIBRARY);
this.linkTargetType = linkTargetType;
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}.
*/
@CanIgnoreReturnValue
public Builder setLinkingMode(Link.LinkingMode linkingMode) {
this.linkingMode = linkingMode;
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()}}).
*/
@CanIgnoreReturnValue
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.
*/
@CanIgnoreReturnValue
public Builder setUseTestOnlyFlags(boolean useTestOnlyFlags) {
this.useTestOnlyFlags = useTestOnlyFlags;
return this;
}
@CanIgnoreReturnValue
public Builder setParamFile(Artifact paramFile) {
this.paramFile = paramFile;
return this;
}
@CanIgnoreReturnValue
public Builder setBuildVariables(CcToolchainVariables variables) {
this.variables = variables;
return this;
}
@CanIgnoreReturnValue
public Builder setToolchainLibrariesSolibDir(PathFragment toolchainLibrariesSolibDir) {
this.toolchainLibrariesSolibDir = toolchainLibrariesSolibDir;
return this;
}
@CanIgnoreReturnValue
public Builder setActionName(String actionName) {
this.actionName = actionName;
return this;
}
}
}