From d08b27fa9701fecfdb69e1b0d1ac2459efc2129b Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Wed, 25 Feb 2015 16:45:20 +0100 Subject: Update from Google. -- MOE_MIGRATED_REVID=85702957 --- .../build/lib/rules/cpp/LinkCommandLine.java | 1121 ++++++++++++++++++++ 1 file changed, 1121 insertions(+) create mode 100644 src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java (limited to 'src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java') diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java new file mode 100644 index 0000000000..1dccafa8cc --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkCommandLine.java @@ -0,0 +1,1121 @@ +// 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 com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.actions.CommandLine; +import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +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.Link.LinkStaticness; +import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType; +import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.util.Pair; +import com.google.devtools.build.lib.util.ShellEscaper; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +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 BuildConfiguration configuration; + private final CppConfiguration cppConfiguration; + private final ActionOwner owner; + private final Artifact output; + @Nullable private final Artifact interfaceOutput; + @Nullable private final Artifact symbolCountsOutput; + private final ImmutableList buildInfoHeaderArtifacts; + private final Iterable linkerInputs; + private final Iterable runtimeInputs; + private final LinkTargetType linkTargetType; + private final LinkStaticness linkStaticness; + private final ImmutableList linkopts; + private final ImmutableSet features; + private final ImmutableMap linkstamps; + private final ImmutableList linkstampCompileOptions; + @Nullable private final PathFragment runtimeSolibDir; + private final boolean nativeDeps; + private final boolean useTestOnlyFlags; + private final boolean needWholeArchive; + private final boolean supportsParamFiles; + @Nullable private final Artifact interfaceSoBuilder; + + private LinkCommandLine( + BuildConfiguration configuration, + ActionOwner owner, + Artifact output, + @Nullable Artifact interfaceOutput, + @Nullable Artifact symbolCountsOutput, + ImmutableList buildInfoHeaderArtifacts, + Iterable linkerInputs, + Iterable runtimeInputs, + LinkTargetType linkTargetType, + LinkStaticness linkStaticness, + ImmutableList linkopts, + ImmutableSet features, + ImmutableMap linkstamps, + ImmutableList linkstampCompileOptions, + @Nullable PathFragment runtimeSolibDir, + boolean nativeDeps, + boolean useTestOnlyFlags, + boolean needWholeArchive, + boolean supportsParamFiles, + Artifact interfaceSoBuilder) { + Preconditions.checkArgument(linkTargetType != LinkTargetType.INTERFACE_DYNAMIC_LIBRARY, + "you can't link an interface dynamic library directly"); + if (linkTargetType != LinkTargetType.DYNAMIC_LIBRARY) { + Preconditions.checkArgument(interfaceOutput == null, + "interface output may only be non-null for dynamic library links"); + } + if (linkTargetType.isStaticLibraryLink()) { + Preconditions.checkArgument(linkstamps.isEmpty(), + "linkstamps may only be present on dynamic library or executable links"); + Preconditions.checkArgument(linkStaticness == LinkStaticness.FULLY_STATIC, + "static library link must be static"); + Preconditions.checkArgument(buildInfoHeaderArtifacts.isEmpty(), + "build info headers may only be present on dynamic library or executable links"); + Preconditions.checkArgument(symbolCountsOutput == null, + "the symbol counts output must be null for static links"); + Preconditions.checkArgument(runtimeSolibDir == null, + "the runtime solib directory must be null for static links"); + Preconditions.checkArgument(!nativeDeps, + "the native deps flag must be false for static links"); + Preconditions.checkArgument(!needWholeArchive, + "the need whole archive flag must be false for static links"); + } + + this.configuration = Preconditions.checkNotNull(configuration); + this.cppConfiguration = configuration.getFragment(CppConfiguration.class); + this.owner = Preconditions.checkNotNull(owner); + this.output = Preconditions.checkNotNull(output); + this.interfaceOutput = interfaceOutput; + this.symbolCountsOutput = symbolCountsOutput; + this.buildInfoHeaderArtifacts = Preconditions.checkNotNull(buildInfoHeaderArtifacts); + this.linkerInputs = Preconditions.checkNotNull(linkerInputs); + this.runtimeInputs = Preconditions.checkNotNull(runtimeInputs); + this.linkTargetType = Preconditions.checkNotNull(linkTargetType); + this.linkStaticness = Preconditions.checkNotNull(linkStaticness); + // For now, silently ignore linkopts if this is a static library link. + this.linkopts = linkTargetType.isStaticLibraryLink() + ? ImmutableList.of() + : Preconditions.checkNotNull(linkopts); + this.features = Preconditions.checkNotNull(features); + this.linkstamps = Preconditions.checkNotNull(linkstamps); + this.linkstampCompileOptions = linkstampCompileOptions; + this.runtimeSolibDir = runtimeSolibDir; + this.nativeDeps = nativeDeps; + this.useTestOnlyFlags = useTestOnlyFlags; + this.needWholeArchive = needWholeArchive; + this.supportsParamFiles = supportsParamFiles; + // For now, silently ignore interfaceSoBuilder if we don't build an interface dynamic library. + this.interfaceSoBuilder = + ((linkTargetType == LinkTargetType.DYNAMIC_LIBRARY) && (interfaceOutput != null)) + ? Preconditions.checkNotNull(interfaceSoBuilder, + "cannot build interface dynamic library without builder") + : null; + } + + /** + * Returns an interface shared object output artifact produced during linking. This only returns + * non-null if {@link #getLinkTargetType} is {@code DYNAMIC_LIBRARY} and an interface shared + * object was requested. + */ + @Nullable public Artifact getInterfaceOutput() { + return interfaceOutput; + } + + /** + * Returns an artifact containing the number of symbols used per object file passed to the linker. + * This is currently a gold only feature, and is only produced for executables. If another target + * is being linked, or if symbol counts output is disabled, this will be null. + */ + @Nullable public Artifact getSymbolCountsOutput() { + return symbolCountsOutput; + } + + /** + * Returns the (ordered, immutable) list of header files that contain build info. + */ + public ImmutableList getBuildInfoHeaderArtifacts() { + return buildInfoHeaderArtifacts; + } + + /** + * Returns the (ordered, immutable) list of paths to the linker's input files. + */ + public Iterable getLinkerInputs() { + return linkerInputs; + } + + /** + * Returns the runtime inputs to the linker. + */ + public Iterable getRuntimeInputs() { + return runtimeInputs; + } + + /** + * Returns the current type of link target set. + */ + public LinkTargetType getLinkTargetType() { + return linkTargetType; + } + + /** + * Returns the "staticness" of the link. + */ + public LinkStaticness getLinkStaticness() { + return linkStaticness; + } + + /** + * Returns the additional linker options for this link. + */ + public ImmutableList getLinkopts() { + return linkopts; + } + + /** + * Returns a (possibly empty) mapping of (C++ source file, .o output file) pairs for source files + * that need to be compiled at link time. + * + *

This is used to embed various values from the build system into binaries to identify their + * provenance. + */ + public ImmutableMap getLinkstamps() { + return linkstamps; + } + + /** + * 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 getRuntimeSolibDir() { + return runtimeSolibDir; + } + + /** + * 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; + } + + /** + * 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). Call {@link + * #canBeSplit} first to check if the command-line can be split. + * + * @throws IllegalStateException if the command-line cannot be split + */ + @VisibleForTesting + final Pair, List> splitCommandline(PathFragment paramExecPath) { + Preconditions.checkState(canBeSplit()); + List args = getRawLinkArgv(); + if (linkTargetType.isStaticLibraryLink()) { + // Ar link commands can also generate huge command lines. + List paramFileArgs = args.subList(1, args.size()); + List commandlineArgs = new ArrayList<>(); + commandlineArgs.add(args.get(0)); + + commandlineArgs.add("@" + paramExecPath.getPathString()); + 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<>(); + extractArgumentsForParamFile(args, commandlineArgs, paramFileArgs); + + commandlineArgs.add("-Wl,@" + paramExecPath.getPathString()); + return Pair.of(commandlineArgs, paramFileArgs); + } + } + + boolean canBeSplit() { + if (!supportsParamFiles) { + return false; + } + switch (linkTargetType) { + // We currently can't split dynamic library links if they have interface outputs. That was + // probably an unintended side effect of the change that introduced interface outputs. + case DYNAMIC_LIBRARY: + return interfaceOutput == null; + case EXECUTABLE: + case STATIC_LIBRARY: + case PIC_STATIC_LIBRARY: + case ALWAYS_LINK_STATIC_LIBRARY: + case ALWAYS_LINK_PIC_STATIC_LIBRARY: + return true; + default: + return false; + } + } + + private static void extractArgumentsForParamFile(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.equals("--incremental-unchanged")) { + paramFileArgs.add(arg); + } else if (arg.equals("--incremental-changed")) { + paramFileArgs.add(arg); + } 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 (arg.endsWith(".a") || arg.endsWith(".lo") || arg.endsWith(".so") + || arg.endsWith(".ifso") || arg.endsWith(".o") + || CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(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). After any further usage-specific processing, this can be passed to + * {@link #finalizeWithLinkstampCommands} to give the final command line. + * + * @return raw link command line. + */ + public List getRawLinkArgv() { + List argv = new ArrayList<>(); + switch (linkTargetType) { + case EXECUTABLE: + addCppArgv(argv); + break; + + case DYNAMIC_LIBRARY: + if (interfaceOutput != null) { + argv.add(configuration.getShExecutable().getPathString()); + argv.add("-c"); + argv.add("build_iface_so=\"$0\"; impl=\"$1\"; iface=\"$2\"; cmd=\"$3\"; shift 3; " + + "\"$cmd\" \"$@\" && \"$build_iface_so\" \"$impl\" \"$iface\""); + argv.add(interfaceSoBuilder.getExecPathString()); + argv.add(output.getExecPathString()); + argv.add(interfaceOutput.getExecPathString()); + } + addCppArgv(argv); + // -pie is not compatible with -shared and should be + // removed when the latter is part of the link command. Should we need to further + // distinguish between shared libraries and executables, we could add additional + // command line / CROSSTOOL flags that distinguish them. But as long as this is + // the only relevant use case we're just special-casing it here. + Iterables.removeIf(argv, Predicates.equalTo("-pie")); + break; + + case STATIC_LIBRARY: + case PIC_STATIC_LIBRARY: + case ALWAYS_LINK_STATIC_LIBRARY: + case ALWAYS_LINK_PIC_STATIC_LIBRARY: + // The static library link command follows this template: + // ar + argv.add(cppConfiguration.getArExecutable().getPathString()); + argv.addAll( + cppConfiguration.getArFlags(cppConfiguration.archiveType() == Link.ArchiveType.THIN)); + argv.add(output.getExecPathString()); + addInputFileLinkOptions(argv, /*needWholeArchive=*/false, + /*includeLinkopts=*/false); + break; + + default: + throw new IllegalArgumentException(); + } + + // Fission mode: debug info is in .dwo files instead of .o files. Inform the linker of this. + if (!linkTargetType.isStaticLibraryLink() && cppConfiguration.useFission()) { + argv.add("-Wl,--gdb-index"); + } + + return argv; + } + + @Override + public List arguments() { + return finalizeWithLinkstampCommands(getRawLinkArgv()); + } + + /** + * Takes a raw link command line and gives the final link command that will + * also first compile any linkstamps necessary. Elements of rawLinkArgv are + * shell-escaped. + * + * @param rawLinkArgv raw link command line + * + * @return final link command line suitable for execution + */ + public List finalizeWithLinkstampCommands(List rawLinkArgv) { + return addLinkstampingToCommand(getLinkstampCompileCommands(""), rawLinkArgv, true); + } + + /** + * Takes a raw link command line and gives the final link command that will also first compile any + * linkstamps necessary. Elements of rawLinkArgv are not shell-escaped. + * + * @param rawLinkArgv raw link command line + * @param outputPrefix prefix to add before the linkstamp outputs' exec paths + * + * @return final link command line suitable for execution + */ + public List finalizeAlreadyEscapedWithLinkstampCommands( + List rawLinkArgv, String outputPrefix) { + return addLinkstampingToCommand(getLinkstampCompileCommands(outputPrefix), rawLinkArgv, false); + } + + /** + * Adds linkstamp compilation to the (otherwise) fully specified link + * command if {@link #getLinkstamps} is non-empty. + * + *

Linkstamps were historically compiled implicitly as part of the link + * command, but implicit compilation doesn't guarantee consistent outputs. + * For example, the command "gcc input.o input.o foo/linkstamp.cc -o myapp" + * causes gcc to implicitly run "gcc foo/linkstamp.cc -o /tmp/ccEtJHDB.o", + * for some internally decided output path /tmp/ccEtJHDB.o, then add that path + * to the linker's command line options. The name of this path can change + * even between equivalently specified gcc invocations. + * + *

So now we explicitly compile these files in their own command + * invocations before running the link command, thus giving us direct + * control over the naming of their outputs. This method adds those extra + * steps as necessary. + * @param linkstampCommands individual linkstamp compilation commands + * @param linkCommand the complete list of link command arguments (after + * .params file compacting) for an invocation + * @param escapeArgs if true, linkCommand arguments are shell escaped. if + * false, arguments are returned as-is + * + * @return The original argument list if no linkstamps compilation commands + * are given, otherwise an expanded list that adds the linkstamp + * compilation commands and funnels their outputs into the link step. + * Note that these outputs only need to persist for the duration of + * the link step. + */ + private static List addLinkstampingToCommand( + List linkstampCommands, + List linkCommand, + boolean escapeArgs) { + if (linkstampCommands.isEmpty()) { + return linkCommand; + } else { + List batchCommand = Lists.newArrayListWithCapacity(3); + batchCommand.add("/bin/bash"); + batchCommand.add("-c"); + batchCommand.add( + Joiner.on(" && ").join(linkstampCommands) + " && " + + (escapeArgs + ? ShellEscaper.escapeJoinAll(linkCommand) + : Joiner.on(" ").join(linkCommand))); + return ImmutableList.copyOf(batchCommand); + } + } + + /** + * Computes, for each C++ source file in + * {@link #getLinkstamps}, the command necessary to compile + * that file such that the output is correctly fed into the link command. + * + *

As these options (as well as all others) are taken into account when + * computing the action key, they do not directly contain volatile build + * information to avoid unnecessary relinking. Instead this information is + * passed as an additional header generated by + * {@link com.google.devtools.build.lib.rules.cpp.WriteBuildInfoHeaderAction}. + * + * @param outputPrefix prefix to add before the linkstamp outputs' exec paths + * @return a list of shell-escaped compiler commmands, one for each entry + * in {@link #getLinkstamps} + */ + public List getLinkstampCompileCommands(String outputPrefix) { + if (linkstamps.isEmpty()) { + return ImmutableList.of(); + } + + String compilerCommand = cppConfiguration.getCppExecutable().getPathString(); + List commands = Lists.newArrayListWithCapacity(linkstamps.size()); + + for (Map.Entry linkstamp : linkstamps.entrySet()) { + List optionList = new ArrayList<>(); + + // Defines related to the build info are read from generated headers. + for (Artifact header : buildInfoHeaderArtifacts) { + optionList.add("-include"); + optionList.add(header.getExecPathString()); + } + + String labelReplacement = Matcher.quoteReplacement( + isSharedNativeLibrary() ? output.getExecPathString() : Label.print(owner.getLabel())); + String outputPathReplacement = Matcher.quoteReplacement( + output.getExecPathString()); + for (String option : linkstampCompileOptions) { + optionList.add(option + .replaceAll(Pattern.quote("${LABEL}"), labelReplacement) + .replaceAll(Pattern.quote("${OUTPUT_PATH}"), outputPathReplacement)); + } + + optionList.add("-DGPLATFORM=\"" + cppConfiguration + "\""); + + // Needed to find headers included from linkstamps. + optionList.add("-I."); + + // Add sysroot. + PathFragment sysroot = cppConfiguration.getSysroot(); + if (sysroot != null) { + optionList.add("--sysroot=" + sysroot.getPathString()); + } + + // Add toolchain compiler options. + optionList.addAll(cppConfiguration.getCompilerOptions(features)); + optionList.addAll(cppConfiguration.getCOptions()); + optionList.addAll(cppConfiguration.getUnfilteredCompilerOptions(features)); + + // For dynamic libraries, produce position independent code. + if (linkTargetType == LinkTargetType.DYNAMIC_LIBRARY + && cppConfiguration.toolchainNeedsPic()) { + optionList.add("-fPIC"); + } + + // Stamp FDO builds with FDO subtype string + String fdoBuildStamp = CppHelper.getFdoBuildStamp(cppConfiguration); + if (fdoBuildStamp != null) { + optionList.add("-D" + CppConfiguration.FDO_STAMP_MACRO + "=\"" + fdoBuildStamp + "\""); + } + + // Add the compilation target. + optionList.add("-c"); + optionList.add(linkstamp.getKey().getExecPathString()); + + // Assemble the final command, exempting outputPrefix from shell escaping. + commands.add(compilerCommand + " " + + ShellEscaper.escapeJoinAll(optionList) + + " -o " + + outputPrefix + + ShellEscaper.escapeString(linkstamp.getValue().getExecPathString())); + } + + return commands; + } + + /** + * Determine the arguments to pass to the C++ compiler when linking. + * Add them to the {@code argv} parameter. + */ + private void addCppArgv(List argv) { + argv.add(cppConfiguration.getCppExecutable().getPathString()); + + // When using gold to link an executable, output the number of used and unused symbols. + if (symbolCountsOutput != null) { + argv.add("-Wl,--print-symbol-counts=" + symbolCountsOutput.getExecPathString()); + } + + if (linkTargetType == LinkTargetType.DYNAMIC_LIBRARY) { + argv.add("-shared"); + } + + // Add the outputs of any associated linkstamp compilations. + for (Artifact linkstampOutput : linkstamps.values()) { + argv.add(linkstampOutput.getExecPathString()); + } + + boolean fullyStatic = (linkStaticness == LinkStaticness.FULLY_STATIC); + boolean mostlyStatic = (linkStaticness == LinkStaticness.MOSTLY_STATIC); + boolean sharedLinkopts = + linkTargetType == LinkTargetType.DYNAMIC_LIBRARY + || linkopts.contains("-shared") + || cppConfiguration.getLinkOptions().contains("-shared"); + + if (output != null) { + argv.add("-o"); + String execpath = output.getExecPathString(); + if (mostlyStatic + && linkTargetType == LinkTargetType.EXECUTABLE + && cppConfiguration.skipStaticOutputs()) { + // Linked binary goes to /dev/null; bogus dependency info in its place. + Collections.addAll(argv, "/dev/null", "-MMD", "-MF", execpath); // thanks Ambrose + } else { + argv.add(execpath); + } + } + + addInputFileLinkOptions(argv, needWholeArchive, /*includeLinkopts=*/true); + + // Extra toolchain link options based on the output's link staticness. + if (fullyStatic) { + argv.addAll(cppConfiguration.getFullyStaticLinkOptions(features, sharedLinkopts)); + } else if (mostlyStatic) { + argv.addAll(cppConfiguration.getMostlyStaticLinkOptions(features, sharedLinkopts)); + } else { + argv.addAll(cppConfiguration.getDynamicLinkOptions(features, sharedLinkopts)); + } + + // Extra test-specific link options. + if (useTestOnlyFlags) { + argv.addAll(cppConfiguration.getTestOnlyLinkOptions()); + } + + if (configuration.isCodeCoverageEnabled()) { + argv.add("-lgcov"); + } + + if (linkTargetType == LinkTargetType.EXECUTABLE && cppConfiguration.forcePic()) { + argv.add("-pie"); + } + + argv.addAll(cppConfiguration.getLinkOptions()); + argv.addAll(cppConfiguration.getFdoSupport().getLinkOptions()); + } + + private static boolean isDynamicLibrary(LinkerInput linkInput) { + Artifact libraryArtifact = linkInput.getArtifact(); + String name = libraryArtifact.getFilename(); + return Link.SHARED_LIBRARY_FILETYPES.matches(name) && name.startsWith("lib"); + } + + private boolean isSharedNativeLibrary() { + return nativeDeps && cppConfiguration.shareNativeDeps(); + } + + /** + * When linking a shared library fully or mostly static then we need to link in + * *all* dependent files, not just what the shared library needs for its own + * code. This is done by wrapping all objects/libraries with + * -Wl,-whole-archive and -Wl,-no-whole-archive. For this case the + * globalNeedWholeArchive parameter must be set to true. Otherwise only + * library objects (.lo) need to be wrapped with -Wl,-whole-archive and + * -Wl,-no-whole-archive. + */ + private void addInputFileLinkOptions(List argv, boolean globalNeedWholeArchive, + boolean includeLinkopts) { + // The Apple ld doesn't support -whole-archive/-no-whole-archive. It + // does have -all_load/-noall_load, but -all_load is a global setting + // that affects all subsequent files, and -noall_load is simply ignored. + // TODO(bazel-team): Not sure what the implications of this are, other than + // bloated binaries. + boolean macosx = cppConfiguration.getTargetLibc().equals("macosx"); + if (globalNeedWholeArchive) { + argv.add(macosx ? "-Wl,-all_load" : "-Wl,-whole-archive"); + } + + // Used to collect -L and -Wl,-rpath options, ensuring that each used only once. + Set libOpts = new LinkedHashSet<>(); + + // List of command line parameters to link input files (either directly or using -l). + List linkerInputs = new ArrayList<>(); + + // List of command line parameters that need to be placed *outside* of + // --whole-archive ... --no-whole-archive. + List noWholeArchiveInputs = new ArrayList<>(); + + PathFragment solibDir = configuration.getBinDirectory().getExecPath() + .getRelative(cppConfiguration.getSolibDirectory()); + String runtimeSolibName = runtimeSolibDir != null ? runtimeSolibDir.getBaseName() : null; + boolean runtimeRpath = runtimeSolibDir != null + && (linkTargetType == LinkTargetType.DYNAMIC_LIBRARY + || (linkTargetType == LinkTargetType.EXECUTABLE + && linkStaticness == LinkStaticness.DYNAMIC)); + + String rpathRoot = null; + List runtimeRpathEntries = new ArrayList<>(); + + if (output != null) { + String origin = + useTestOnlyFlags && cppConfiguration.supportsExecOrigin() ? "$EXEC_ORIGIN/" : "$ORIGIN/"; + if (runtimeRpath) { + runtimeRpathEntries.add("-Wl,-rpath," + origin + runtimeSolibName + "/"); + } + + // Calculate the correct relative value for the "-rpath" link option (which sets + // the search path for finding shared libraries). + if (isSharedNativeLibrary()) { + // For shared native libraries, special symlinking is applied to ensure C++ + // runtimes are available under $ORIGIN/_solib_[arch]. So we set the RPATH to find + // them. + // + // Note that we have to do this because $ORIGIN points to different paths for + // different targets. In other words, blaze-bin/d1/d2/d3/a_shareddeps.so and + // blaze-bin/d4/b_shareddeps.so have different path depths. The first could + // reference a standard blaze-bin/_solib_[arch] via $ORIGIN/../../../_solib[arch], + // and the second could use $ORIGIN/../_solib_[arch]. But since this is a shared + // artifact, both are symlinks to the same place, so + // there's no *one* RPATH setting that fits all targets involved in the sharing. + rpathRoot = "-Wl,-rpath," + origin + ":" + + origin + cppConfiguration.getSolibDirectory() + "/"; + if (runtimeRpath) { + runtimeRpathEntries.add("-Wl,-rpath," + origin + "../" + runtimeSolibName + "/"); + } + } else { + // For all other links, calculate the relative path from the output file to _solib_[arch] + // (the directory where all shared libraries are stored, which resides under the blaze-bin + // directory. In other words, given blaze-bin/my/package/binary, rpathRoot would be + // "../../_solib_[arch]". + if (runtimeRpath) { + runtimeRpathEntries.add("-Wl,-rpath," + origin + + Strings.repeat("../", output.getRootRelativePath().segmentCount() - 1) + + runtimeSolibName + "/"); + } + + rpathRoot = "-Wl,-rpath," + + origin + Strings.repeat("../", output.getRootRelativePath().segmentCount() - 1) + + cppConfiguration.getSolibDirectory() + "/"; + + if (nativeDeps) { + // We also retain the $ORIGIN/ path to solibs that are in _solib_, as opposed to + // the package directory) + if (runtimeRpath) { + runtimeRpathEntries.add("-Wl,-rpath," + origin + "../" + runtimeSolibName + "/"); + } + rpathRoot += ":" + origin; + } + } + } + + boolean includeSolibDir = false; + + for (LinkerInput input : getLinkerInputs()) { + if (isDynamicLibrary(input)) { + PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory(); + Preconditions.checkState( + libDir.startsWith(solibDir), + "Artifact '%s' is not under directory '%s'.", input.getArtifact(), solibDir); + if (libDir.equals(solibDir)) { + includeSolibDir = true; + } + addDynamicInputLinkOptions(input, linkerInputs, libOpts, solibDir, rpathRoot); + } else { + addStaticInputLinkOptions(input, linkerInputs); + } + } + + boolean includeRuntimeSolibDir = false; + + for (LinkerInput input : runtimeInputs) { + List optionsList = globalNeedWholeArchive + ? noWholeArchiveInputs + : linkerInputs; + + if (isDynamicLibrary(input)) { + PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory(); + Preconditions.checkState(runtimeSolibDir != null && libDir.equals(runtimeSolibDir), + "Artifact '%s' is not under directory '%s'.", input.getArtifact(), solibDir); + includeRuntimeSolibDir = true; + addDynamicInputLinkOptions(input, optionsList, libOpts, solibDir, rpathRoot); + } else { + addStaticInputLinkOptions(input, optionsList); + } + } + + // rpath ordering matters for performance; first add the one where most libraries are found. + if (includeSolibDir && rpathRoot != null) { + argv.add(rpathRoot); + } + if (includeRuntimeSolibDir) { + argv.addAll(runtimeRpathEntries); + } + argv.addAll(libOpts); + + // Need to wrap static libraries with whole-archive option + for (String option : linkerInputs) { + if (!globalNeedWholeArchive && Link.LINK_LIBRARY_FILETYPES.matches(option)) { + argv.add(macosx ? "-Wl,-all_load" : "-Wl,-whole-archive"); + argv.add(option); + argv.add(macosx ? "-Wl,-noall_load" : "-Wl,-no-whole-archive"); + } else { + argv.add(option); + } + } + + if (globalNeedWholeArchive) { + argv.add(macosx ? "-Wl,-noall_load" : "-Wl,-no-whole-archive"); + argv.addAll(noWholeArchiveInputs); + } + + if (includeLinkopts) { + /* + * For backwards compatibility, linkopts come _after_ inputFiles. + * This is needed to allow linkopts to contain libraries and + * positional library-related options such as + * -Wl,--begin-group -lfoo -lbar -Wl,--end-group + * or + * -Wl,--as-needed -lfoo -Wl,--no-as-needed + * + * As for the relative order of the three different flavours of linkopts + * (global defaults, per-target linkopts, and command-line linkopts), + * we have no idea what the right order should be, or if anyone cares. + */ + argv.addAll(linkopts); + } + } + + /** + * Adds command-line options for a dynamic library input file into + * options and libOpts. + */ + private void addDynamicInputLinkOptions(LinkerInput input, List options, + Set libOpts, PathFragment solibDir, String rpathRoot) { + Preconditions.checkState(isDynamicLibrary(input)); + Preconditions.checkState( + !Link.useStartEndLib(input, cppConfiguration.archiveType())); + + Artifact inputArtifact = input.getArtifact(); + PathFragment libDir = inputArtifact.getExecPath().getParentDirectory(); + if (rpathRoot != null + && !libDir.equals(solibDir) + && (runtimeSolibDir == null || !runtimeSolibDir.equals(libDir))) { + String dotdots = ""; + PathFragment commonParent = solibDir; + while (!libDir.startsWith(commonParent)) { + dotdots += "../"; + commonParent = commonParent.getParentDirectory(); + } + + libOpts.add(rpathRoot + dotdots + libDir.relativeTo(commonParent).getPathString()); + } + + libOpts.add("-L" + inputArtifact.getExecPath().getParentDirectory().getPathString()); + + String name = inputArtifact.getFilename(); + if (CppFileTypes.SHARED_LIBRARY.matches(name)) { + String libName = name.replaceAll("(^lib|\\.so$)", ""); + options.add("-l" + libName); + } else { + // Interface shared objects have a non-standard extension + // that the linker won't be able to find. So use the + // filename directly rather than a -l option. Since the + // library has an SONAME attribute, this will work fine. + options.add(inputArtifact.getExecPathString()); + } + } + + /** + * Adds command-line options for a static library or non-library input + * into options. + */ + private void addStaticInputLinkOptions(LinkerInput input, List options) { + Preconditions.checkState(!isDynamicLibrary(input)); + + // start-lib/end-lib library: adds its input object files. + if (Link.useStartEndLib(input, cppConfiguration.archiveType())) { + Iterable archiveMembers = input.getObjectFiles(); + if (!Iterables.isEmpty(archiveMembers)) { + options.add("-Wl,--start-lib"); + for (Artifact member : archiveMembers) { + options.add(member.getExecPathString()); + } + options.add("-Wl,--end-lib"); + } + // For anything else, add the input directly. + } else { + Artifact inputArtifact = input.getArtifact(); + if (input.isFake()) { + options.add(Link.FAKE_OBJECT_PREFIX + inputArtifact.getExecPathString()); + } else { + options.add(inputArtifact.getExecPathString()); + } + } + } + + /** + * A builder for a {@link LinkCommandLine}. + */ + public static final class Builder { + // TODO(bazel-team): Pass this in instead of having it here. Maybe move to cc_toolchain. + private static final ImmutableList DEFAULT_LINKSTAMP_OPTIONS = ImmutableList.of( + // G3_VERSION_INFO and G3_TARGET_NAME are C string literals that normally + // contain the label of the target being linked. However, they are set + // differently when using shared native deps. In that case, a single .so file + // is shared by multiple targets, and its contents cannot depend on which + // target(s) were specified on the command line. So in that case we have + // to use the (obscure) name of the .so file instead, or more precisely + // the path of the .so file relative to the workspace root. + "-DG3_VERSION_INFO=\"${LABEL}\"", + "-DG3_TARGET_NAME=\"${LABEL}\"", + + // G3_BUILD_TARGET is a C string literal containing the output of this + // link. (An undocumented and untested invariant is that G3_BUILD_TARGET is the location of + // the executable, either absolutely, or relative to the directory part of BUILD_INFO.) + "-DG3_BUILD_TARGET=\"${OUTPUT_PATH}\""); + + private final BuildConfiguration configuration; + private final ActionOwner owner; + + @Nullable private Artifact output; + @Nullable private Artifact interfaceOutput; + @Nullable private Artifact symbolCountsOutput; + private ImmutableList buildInfoHeaderArtifacts = ImmutableList.of(); + private Iterable linkerInputs = ImmutableList.of(); + private Iterable runtimeInputs = ImmutableList.of(); + @Nullable private LinkTargetType linkTargetType; + private LinkStaticness linkStaticness = LinkStaticness.FULLY_STATIC; + private ImmutableList linkopts = ImmutableList.of(); + private ImmutableSet features = ImmutableSet.of(); + private ImmutableMap linkstamps = ImmutableMap.of(); + private List linkstampCompileOptions = new ArrayList<>(); + @Nullable private PathFragment runtimeSolibDir; + private boolean nativeDeps; + private boolean useTestOnlyFlags; + private boolean needWholeArchive; + private boolean supportsParamFiles; + @Nullable private Artifact interfaceSoBuilder; + + public Builder(BuildConfiguration configuration, ActionOwner owner) { + this.configuration = configuration; + this.owner = owner; + } + + public Builder(RuleContext ruleContext) { + this(ruleContext.getConfiguration(), ruleContext.getActionOwner()); + } + + public LinkCommandLine build() { + ImmutableList actualLinkstampCompileOptions; + if (linkstampCompileOptions.isEmpty()) { + actualLinkstampCompileOptions = DEFAULT_LINKSTAMP_OPTIONS; + } else { + actualLinkstampCompileOptions = ImmutableList.copyOf( + Iterables.concat(DEFAULT_LINKSTAMP_OPTIONS, linkstampCompileOptions)); + } + return new LinkCommandLine(configuration, owner, output, interfaceOutput, + symbolCountsOutput, buildInfoHeaderArtifacts, linkerInputs, runtimeInputs, linkTargetType, + linkStaticness, linkopts, features, linkstamps, actualLinkstampCompileOptions, + runtimeSolibDir, nativeDeps, useTestOnlyFlags, needWholeArchive, supportsParamFiles, + interfaceSoBuilder); + } + + /** + * 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#isStaticLibraryLink}) 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 the primary output artifact. This must be called before calling {@link #build}. + */ + public Builder setOutput(Artifact output) { + this.output = output; + return this; + } + + /** + * Sets a list of linker inputs. 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 setLinkerInputs(Iterable linkerInputs) { + this.linkerInputs = CollectionUtils.makeImmutable(linkerInputs); + return this; + } + + public Builder setRuntimeInputs(ImmutableList runtimeInputs) { + this.runtimeInputs = runtimeInputs; + return this; + } + + /** + * Sets the additional interface output artifact, which is only used for dynamic libraries. The + * {@link #build} method throws an exception if the target type is not {@link + * LinkTargetType#DYNAMIC_LIBRARY}. + */ + public Builder setInterfaceOutput(Artifact interfaceOutput) { + this.interfaceOutput = interfaceOutput; + return this; + } + + /** + * Sets an additional output artifact that contains symbol counts. The {@link #build} method + * throws an exception if this is non-null for a static link (see + * {@link LinkTargetType#isStaticLibraryLink}). + */ + public Builder setSymbolCountsOutput(Artifact symbolCountsOutput) { + this.symbolCountsOutput = symbolCountsOutput; + return this; + } + + /** + * Sets the linker options. These are passed to the linker in addition to the other linker + * options like linker inputs, symbol count options, etc. The {@link #build} method + * throws an exception if the linker options are non-empty for a static link (see {@link + * LinkTargetType#isStaticLibraryLink}). + */ + public Builder setLinkopts(ImmutableList linkopts) { + this.linkopts = linkopts; + return this; + } + + /** + * Sets how static the link is supposed to be. For static target types (see {@link + * LinkTargetType#isStaticLibraryLink}), the {@link #build} method throws an exception if this + * is not {@link LinkStaticness#FULLY_STATIC}. The default setting is {@link + * LinkStaticness#FULLY_STATIC}. + */ + public Builder setLinkStaticness(LinkStaticness linkStaticness) { + this.linkStaticness = linkStaticness; + return this; + } + + /** + * Sets the binary that should be used to create the interface output for a dynamic library. + * This is ignored unless the target type is {@link LinkTargetType#DYNAMIC_LIBRARY} and an + * interface output artifact is specified. + */ + public Builder setInterfaceSoBuilder(Artifact interfaceSoBuilder) { + this.interfaceSoBuilder = interfaceSoBuilder; + return this; + } + + /** + * Sets the linkstamps. Linkstamps are additional C++ source files that are compiled as part of + * the link command. The {@link #build} method throws an exception if the linkstamps are + * non-empty for a static link (see {@link LinkTargetType#isStaticLibraryLink}). + */ + public Builder setLinkstamps(ImmutableMap linkstamps) { + this.linkstamps = linkstamps; + return this; + } + + /** + * Adds the given C++ compiler options to the list of options passed to the linkstamp + * compilation. + */ + public Builder addLinkstampCompileOptions(List linkstampCompileOptions) { + this.linkstampCompileOptions.addAll(linkstampCompileOptions); + 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#isStaticLibraryLink}). + */ + public Builder setBuildInfoHeaderArtifacts(ImmutableList buildInfoHeaderArtifacts) { + this.buildInfoHeaderArtifacts = buildInfoHeaderArtifacts; + return this; + } + + /** + * Sets the features enabled for the rule. + */ + public Builder setFeatures(ImmutableSet features) { + this.features = features; + return this; + } + + /** + * Sets the directory of the dynamic runtime libraries, which is added to the rpath. The {@link + * #build} method throws an exception if the runtime dir is non-null for a static link (see + * {@link LinkTargetType#isStaticLibraryLink}). + */ + public Builder setRuntimeSolibDir(PathFragment runtimeSolibDir) { + this.runtimeSolibDir = runtimeSolibDir; + 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#isStaticLibraryLink}). + */ + 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 setNeedWholeArchive(boolean needWholeArchive) { + this.needWholeArchive = needWholeArchive; + return this; + } + + public Builder setSupportsParamFiles(boolean supportsParamFiles) { + this.supportsParamFiles = supportsParamFiles; + return this; + } + } +} -- cgit v1.2.3