// 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.analysis.RuleContext; 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.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 buildInfoHeaderArtifacts; private final Iterable linkerInputArtifacts; 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; @VisibleForSerialization LinkCommandLine( String actionName, String forcedToolPath, ImmutableList buildInfoHeaderArtifacts, Iterable linkerInputArtifacts, 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.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; } @Nullable public Artifact getParamFile() { return paramFile; } /** See {@link CppLinkAction#getBuildInfoHeaderArtifacts()} */ public ImmutableList getBuildInfoHeaderArtifacts() { return buildInfoHeaderArtifacts; } /** Returns the (ordered, immutable) list of paths to the linker's input files. */ public Iterable getLinkerInputArtifacts() { return linkerInputArtifacts; } /** * 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 additional linker options for this link. */ public ImmutableList getLinkopts() { if (variables.isAvailable(LinkBuildVariables.USER_LINK_FLAGS.getVariableName())) { return CcToolchainVariables.toStringList( variables, LinkBuildVariables.USER_LINK_FLAGS.getVariableName()); } else { return ImmutableList.of(); } } /** Returns the path to the linker. */ public String getLinkerPathString() { return featureConfiguration .getToolForAction(linkTargetType.getActionName()) .getToolPathFragment() .getPathString(); } /** * 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> splitCommandline() { return splitCommandline(paramFile, getRawLinkArgv(null), linkTargetType); } @VisibleForTesting final Pair, List> splitCommandline(@Nullable ArtifactExpander expander) { return splitCommandline(paramFile, getRawLinkArgv(expander), linkTargetType); } private static Pair, List> splitCommandline( Artifact paramFile, List args, LinkTargetType linkTargetType) { Preconditions.checkNotNull(paramFile); if (linkTargetType.linkerOrArchiver() == LinkerOrArchiver.ARCHIVER) { // Ar link commands can also generate huge command lines. List paramFileArgs = new ArrayList<>(); List 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 paramFileArgs = new ArrayList<>(); List commandlineArgs = new ArrayList<>(); extractArgumentsForDynamicLinkParamFile(args, commandlineArgs, paramFileArgs); 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; public 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 arguments() { List argv = getRawLinkArgv( null, forcedToolPath, featureConfiguration, actionName, linkTargetType, variables); return splitCommandline(paramsFile, argv, linkTargetType).getSecond(); } @Override public Iterable arguments(ArtifactExpander expander) { List 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); } public static void extractArgumentsForStaticLinkParamFile( List args, List commandlineArgs, List 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 args, List commandlineArgs, List 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 (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 getRawLinkArgv() { 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 getRawLinkArgv(@Nullable ArtifactExpander expander) { return getRawLinkArgv( expander, forcedToolPath, featureConfiguration, actionName, linkTargetType, variables); } private static List getRawLinkArgv( @Nullable ArtifactExpander expander, String forcedToolPath, FeatureConfiguration featureConfiguration, String actionName, LinkTargetType linkTargetType, CcToolchainVariables variables) { List 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 .getToolForAction(linkTargetType.getActionName()) .getToolPathFragment() .getPathString()); } argv.addAll(featureConfiguration.getCommandLine(actionName, variables, expander)); return argv; } List getCommandLine(@Nullable ArtifactExpander expander) { // 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> split = splitCommandline(expander); return split.first; } else { return getRawLinkArgv(expander); } } @Override public List arguments() { return getRawLinkArgv(null); } @Override public Iterable arguments(ArtifactExpander artifactExpander) { return getRawLinkArgv(artifactExpander); } /** A builder for a {@link LinkCommandLine}. */ public static final class Builder { private final RuleContext ruleContext; private String forcedToolPath; private ImmutableList buildInfoHeaderArtifacts = ImmutableList.of(); private Iterable linkerInputArtifacts = ImmutableList.of(); @Nullable private LinkTargetType linkTargetType; private Link.LinkingMode linkingMode = Link.LinkingMode.LEGACY_FULLY_STATIC; @Nullable private PathFragment toolchainLibrariesSolibDir; private boolean nativeDeps; private boolean useTestOnlyFlags; @Nullable private Artifact paramFile; private CcToolchainVariables variables; private FeatureConfiguration featureConfiguration; public Builder(RuleContext ruleContext) { this.ruleContext = ruleContext; } 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"); } // The ruleContext can be null for some tests. if (ruleContext != null) { Preconditions.checkNotNull(featureConfiguration); } if (variables == null) { variables = CcToolchainVariables.EMPTY; } String actionName = linkTargetType.getActionName(); return new LinkCommandLine( actionName, forcedToolPath, buildInfoHeaderArtifacts, linkerInputArtifacts, linkTargetType, linkingMode, toolchainLibrariesSolibDir, nativeDeps, useTestOnlyFlags, paramFile, variables, featureConfiguration); } /** 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 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 Link.LinkingMode#LEGACY_FULLY_STATIC}. The default setting is {@link * Link.LinkingMode#LEGACY_FULLY_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 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; } } }