// 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.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.LibraryToLinkValue; import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SequenceBuilder; import com.google.devtools.build.lib.rules.cpp.Link.LinkTargetType; import com.google.devtools.build.lib.rules.cpp.Link.LinkingMode; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.HashMap; import java.util.Map; /** Class that goes over linker inputs and produces {@link LibraryToLinkValue}s */ public class LibrariesToLinkCollector { private final boolean isNativeDeps; private final PathFragment toolchainLibrariesSolibDir; private final CppConfiguration cppConfiguration; private final CcToolchainProvider ccToolchainProvider; private final Artifact outputArtifact; private final boolean isLtoIndexing; private final PathFragment solibDir; private final Iterable linkerInputs; private final Iterable allLtoArtifacts; private final boolean allowLtoIndexing; private final Artifact thinltoParamFile; private final FeatureConfiguration featureConfiguration; private final boolean needWholeArchive; private final String rpathRoot; private final boolean needToolchainLibrariesRpath; private final Map ltoMap; public LibrariesToLinkCollector( boolean isNativeDeps, CppConfiguration cppConfiguration, CcToolchainProvider toolchain, PathFragment toolchainLibrariesSolibDir, LinkTargetType linkType, Link.LinkingMode linkingMode, Artifact output, PathFragment solibDir, boolean isLtoIndexing, Iterable allLtoArtifacts, FeatureConfiguration featureConfiguration, Artifact thinltoParamFile, boolean allowLtoIndexing, Iterable linkerInputs, boolean needWholeArchive) { this.isNativeDeps = isNativeDeps; this.cppConfiguration = cppConfiguration; this.ccToolchainProvider = toolchain; this.toolchainLibrariesSolibDir = toolchainLibrariesSolibDir; this.outputArtifact = output; this.solibDir = solibDir; this.isLtoIndexing = isLtoIndexing; this.allLtoArtifacts = allLtoArtifacts; this.featureConfiguration = featureConfiguration; this.thinltoParamFile = thinltoParamFile; this.allowLtoIndexing = allowLtoIndexing; this.linkerInputs = linkerInputs; this.needWholeArchive = needWholeArchive; needToolchainLibrariesRpath = toolchainLibrariesSolibDir != null && (linkType.isDynamicLibrary() || (linkType == LinkTargetType.EXECUTABLE && linkingMode == LinkingMode.DYNAMIC)); // Calculate the correct relative value for the "-rpath" link option (which sets // the search path for finding shared libraries). if (isNativeDeps && cppConfiguration.shareNativeDeps()) { // For shared native libraries, special symlinking is applied to ensure C++ // toolchain libraries 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 = ccToolchainProvider.getSolibDirectory() + "/"; } else { rpathRoot = Strings.repeat("../", outputArtifact.getRootRelativePath().segmentCount() - 1) + ccToolchainProvider.getSolibDirectory() + "/"; } ltoMap = generateLtoMap(); } /** * Result of {@link LibrariesToLinkCollector#collectLibrariesToLink()}. Provides access to * computed sequence of {@link LibraryToLinkValue}s and accompanying library search directories. */ public static class CollectedLibrariesToLink { private final SequenceBuilder librariesToLink; private final ImmutableSet expandedLinkerInputs; private final ImmutableSet librarySearchDirectories; private final ImmutableSet runtimeLibrarySearchDirectories; public CollectedLibrariesToLink( SequenceBuilder librariesToLink, ImmutableSet expandedLinkerInputs, ImmutableSet librarySearchDirectories, ImmutableSet runtimeLibrarySearchDirectories) { this.librariesToLink = librariesToLink; this.expandedLinkerInputs = expandedLinkerInputs; this.librarySearchDirectories = librarySearchDirectories; this.runtimeLibrarySearchDirectories = runtimeLibrarySearchDirectories; } public SequenceBuilder getLibrariesToLink() { return librariesToLink; } // TODO(b/78347840): Figure out how to make these Artifacts. public ImmutableSet getExpandedLinkerInputs() { return expandedLinkerInputs; } public ImmutableSet getLibrarySearchDirectories() { return librarySearchDirectories; } public ImmutableSet getRuntimeLibrarySearchDirectories() { return runtimeLibrarySearchDirectories; } } /** * 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. * *

TODO: Factor out of the bazel binary into build variables for crosstool action_configs. */ public CollectedLibrariesToLink collectLibrariesToLink() { ImmutableSet.Builder librarySearchDirectories = ImmutableSet.builder(); ImmutableSet.Builder runtimeLibrarySearchDirectories = ImmutableSet.builder(); ImmutableSet.Builder rpathRootsForExplicitSoDeps = ImmutableSet.builder(); ImmutableSet.Builder expandedLinkerInputsBuilder = ImmutableSet.builder(); // List of command line parameters that need to be placed *outside* of // --whole-archive ... --no-whole-archive. SequenceBuilder librariesToLink = new SequenceBuilder(); String toolchainLibrariesSolibName = toolchainLibrariesSolibDir != null ? toolchainLibrariesSolibDir.getBaseName() : null; if (isNativeDeps && cppConfiguration.shareNativeDeps()) { if (needToolchainLibrariesRpath) { runtimeLibrarySearchDirectories.add("../" + toolchainLibrariesSolibName + "/"); } } 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 (needToolchainLibrariesRpath) { runtimeLibrarySearchDirectories.add( Strings.repeat("../", outputArtifact.getRootRelativePath().segmentCount() - 1) + toolchainLibrariesSolibName + "/"); } if (isNativeDeps) { // We also retain the $ORIGIN/ path to solibs that are in _solib_, as opposed to // the package directory) if (needToolchainLibrariesRpath) { runtimeLibrarySearchDirectories.add("../" + toolchainLibrariesSolibName + "/"); } } } if (needToolchainLibrariesRpath) { if (isNativeDeps) { runtimeLibrarySearchDirectories.add("."); } runtimeLibrarySearchDirectories.add(toolchainLibrariesSolibName + "/"); } Pair includeSolibsPair = addLinkerInputs( librarySearchDirectories, rpathRootsForExplicitSoDeps, librariesToLink, expandedLinkerInputsBuilder); boolean includeSolibDir = includeSolibsPair.first; boolean includeToolchainLibrariesSolibDir = includeSolibsPair.second; Preconditions.checkState( ltoMap == null || ltoMap.isEmpty(), "Still have LTO objects left: %s", ltoMap); ImmutableSet.Builder allRuntimeLibrarySearchDirectories = ImmutableSet.builder(); // rpath ordering matters for performance; first add the one where most libraries are found. if (includeSolibDir) { allRuntimeLibrarySearchDirectories.add(rpathRoot); } allRuntimeLibrarySearchDirectories.addAll(rpathRootsForExplicitSoDeps.build()); if (includeToolchainLibrariesSolibDir) { allRuntimeLibrarySearchDirectories.addAll(runtimeLibrarySearchDirectories.build()); } return new CollectedLibrariesToLink( librariesToLink, expandedLinkerInputsBuilder.build(), librarySearchDirectories.build(), allRuntimeLibrarySearchDirectories.build()); } private Pair addLinkerInputs( ImmutableSet.Builder librarySearchDirectories, ImmutableSet.Builder rpathEntries, SequenceBuilder librariesToLink, ImmutableSet.Builder expandedLinkerInputsBuilder) { boolean includeSolibDir = false; boolean includeToolchainLibrariesSolibDir = false; for (LinkerInput input : linkerInputs) { if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY || input.getArtifactCategory() == ArtifactCategory.INTERFACE_LIBRARY) { PathFragment libDir = input.getArtifact().getExecPath().getParentDirectory(); // When COPY_DYNAMIC_LIBRARIES_TO_BINARY is enabled, dynamic libraries are not symlinked // under solibDir, so don't check it and don't include solibDir. if (!featureConfiguration.isEnabled(CppRuleClasses.COPY_DYNAMIC_LIBRARIES_TO_BINARY)) { Preconditions.checkState( libDir.startsWith(solibDir) || libDir.startsWith(toolchainLibrariesSolibDir), "Artifact '%s' is not under directory expected '%s'," + " neither it is in directory for toolchain libraries '%'.", input.getArtifact(), solibDir, toolchainLibrariesSolibDir); if (libDir.equals(solibDir)) { includeSolibDir = true; } if (libDir.equals(toolchainLibrariesSolibDir)) { includeToolchainLibrariesSolibDir = true; } } addDynamicInputLinkOptions( input, librariesToLink, expandedLinkerInputsBuilder, librarySearchDirectories, rpathEntries); } else { addStaticInputLinkOptions(input, librariesToLink, expandedLinkerInputsBuilder); } } return Pair.of(includeSolibDir, includeToolchainLibrariesSolibDir); } /** * Adds command-line options for a dynamic library input file into options and libOpts. * * @param librariesToLink - a collection that will be exposed as a build variable. */ private void addDynamicInputLinkOptions( LinkerInput input, SequenceBuilder librariesToLink, ImmutableSet.Builder expandedLinkerInputsBuilder, ImmutableSet.Builder librarySearchDirectories, ImmutableSet.Builder rpathRootsForExplicitSoDeps) { Preconditions.checkState( input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY || input.getArtifactCategory() == ArtifactCategory.INTERFACE_LIBRARY); Preconditions.checkState( !Link.useStartEndLib( input, CppHelper.getArchiveType(cppConfiguration, ccToolchainProvider))); expandedLinkerInputsBuilder.add(input); Artifact inputArtifact = input.getArtifact(); PathFragment libDir = inputArtifact.getExecPath().getParentDirectory(); if (!libDir.equals(solibDir) && (toolchainLibrariesSolibDir == null || !toolchainLibrariesSolibDir.equals(libDir))) { String dotdots = ""; PathFragment commonParent = solibDir; while (!libDir.startsWith(commonParent)) { dotdots += "../"; commonParent = commonParent.getParentDirectory(); } rpathRootsForExplicitSoDeps.add( rpathRoot + dotdots + libDir.relativeTo(commonParent).getPathString()); } librarySearchDirectories.add(inputArtifact.getExecPath().getParentDirectory().getPathString()); String name = inputArtifact.getFilename(); if (CppFileTypes.SHARED_LIBRARY.matches(name)) { // Use normal shared library resolution rules for shared libraries. String libName = name.replaceAll("(^lib|\\.(so|dylib)$)", ""); librariesToLink.addValue(LibraryToLinkValue.forDynamicLibrary(libName)); } else if (CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(name)) { // Versioned shared libraries require the exact library filename, e.g.: // -lfoo -> libfoo.so // -l:libfoo.so.1 -> libfoo.so.1 librariesToLink.addValue(LibraryToLinkValue.forVersionedDynamicLibrary(name)); } 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. librariesToLink.addValue( LibraryToLinkValue.forInterfaceLibrary(inputArtifact.getExecPathString())); } } /** * Get the effective path for the object artifact, being careful not to create new strings if * possible. * * @param: objectFile - Artifact representing an object file. * @param -inputIsFake - Is this object being used for a cc_fake_binary. */ private static String effectiveObjectFilePath(Artifact objectFile, boolean inputIsFake) { // Avoid making new strings as much as possible! This called for every object file used in the // build. if (inputIsFake) { return Link.FAKE_OBJECT_PREFIX + objectFile.getExecPathString(); } return objectFile.getExecPathString(); } /** * Adds command-line options for a static library or non-library input into options. * * @param librariesToLink - a collection that will be exposed as a build variable. */ private void addStaticInputLinkOptions( LinkerInput input, SequenceBuilder librariesToLink, ImmutableSet.Builder expandedLinkerInputsBuilder) { ArtifactCategory artifactCategory = input.getArtifactCategory(); Preconditions.checkArgument( artifactCategory.equals(ArtifactCategory.OBJECT_FILE) || artifactCategory.equals(ArtifactCategory.STATIC_LIBRARY) || artifactCategory.equals(ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY)); boolean isAlwaysLinkStaticLibrary = artifactCategory == ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY; // input.disableWholeArchive() should only be true for libstdc++/libc++ etc. boolean inputIsWholeArchive = !input.disableWholeArchive() && (isAlwaysLinkStaticLibrary || needWholeArchive); // If we had any LTO artifacts, ltoMap whould be non-null. In that case, // we should have created a thinltoParamFile which the LTO indexing // step will populate with the exec paths that correspond to the LTO // artifacts that the linker decided to include based on symbol resolution. // Those files will be included directly in the link (and not wrapped // in --start-lib/--end-lib) to ensure consistency between the two link // steps. Preconditions.checkState(ltoMap == null || thinltoParamFile != null || !allowLtoIndexing); // start-lib/end-lib library: adds its input object files. if (Link.useStartEndLib( input, CppHelper.getArchiveType(cppConfiguration, ccToolchainProvider))) { Iterable archiveMembers = input.getObjectFiles(); if (!Iterables.isEmpty(archiveMembers)) { ImmutableList.Builder nonLtoArchiveMembersBuilder = ImmutableList.builder(); for (Artifact member : archiveMembers) { Artifact a; if (ltoMap != null && (a = ltoMap.remove(member)) != null) { // When ltoMap is non-null the backend artifact may be missing due to libraries that // list .o files explicitly, or generate .o files from assembler. if (handledByLtoIndexing(a, allowLtoIndexing)) { // The LTO artifacts that should be included in the final link // are listed in the thinltoParamFile, generated by the LTO indexing. // Even if this object file is being skipped for exposure as a Build variable, it's // still an input to this action. expandedLinkerInputsBuilder.add( LinkerInputs.simpleLinkerInput( a, ArtifactCategory.OBJECT_FILE, /* disableWholeArchive= */ false)); continue; } // No LTO indexing step, so use the LTO backend's generated artifact directly // instead of the bitcode object. member = a; } nonLtoArchiveMembersBuilder.add(member); expandedLinkerInputsBuilder.add( LinkerInputs.simpleLinkerInput( member, ArtifactCategory.OBJECT_FILE, /* disableWholeArchive = */ false)); } ImmutableList nonLtoArchiveMembers = nonLtoArchiveMembersBuilder.build(); if (!nonLtoArchiveMembers.isEmpty()) { if (inputIsWholeArchive) { for (Artifact member : nonLtoArchiveMembers) { if (member.isTreeArtifact()) { // TODO(b/78189629): This object filegroup is expanded at action time but wrapped // with --start/--end-lib. There's currently no way to force these objects to be // linked in. librariesToLink.addValue( LibraryToLinkValue.forObjectFileGroup( ImmutableList.of(member), /* isWholeArchive= */ true)); } else { // TODO(b/78189629): These each need to be their own LibraryToLinkValue so they're // not wrapped in --start/--end-lib (which lets the linker leave out objects with // unreferenced code). librariesToLink.addValue( LibraryToLinkValue.forObjectFile( effectiveObjectFilePath(member, input.isFake()), /* isWholeArchive= */ true)); } } } else { librariesToLink.addValue( LibraryToLinkValue.forObjectFileGroup( nonLtoArchiveMembers, /* isWholeArchive= */ false)); } } } } else { Artifact inputArtifact = input.getArtifact(); Artifact a; if (ltoMap != null && (a = ltoMap.remove(inputArtifact)) != null) { if (handledByLtoIndexing(a, allowLtoIndexing)) { // The LTO artifacts that should be included in the final link // are listed in the thinltoParamFile, generated by the LTO indexing. // Even if this object file is being skipped for exposure as a build variable, it's // still an input to this action. expandedLinkerInputsBuilder.add( LinkerInputs.simpleLinkerInput( a, ArtifactCategory.OBJECT_FILE, /* disableWholeArchive= */ false)); return; } // No LTO indexing step, so use the LTO backend's generated artifact directly // instead of the bitcode object. inputArtifact = a; } if (artifactCategory.equals(ArtifactCategory.OBJECT_FILE)) { if (inputArtifact.isTreeArtifact()) { librariesToLink.addValue( LibraryToLinkValue.forObjectFileGroup( ImmutableList.of(inputArtifact), inputIsWholeArchive)); } else { librariesToLink.addValue( LibraryToLinkValue.forObjectFile( effectiveObjectFilePath(inputArtifact, input.isFake()), inputIsWholeArchive)); } expandedLinkerInputsBuilder.add(input); } else { librariesToLink.addValue( LibraryToLinkValue.forStaticLibrary( effectiveObjectFilePath(inputArtifact, input.isFake()), inputIsWholeArchive)); expandedLinkerInputsBuilder.add(input); } } } /** * Returns true if this artifact is produced from a bitcode file that will be input to the LTO * indexing step, in which case that step will add it to the generated thinltoParamFile for * inclusion in the final link step if the linker decides to include it. * * @param a is an artifact produced by an LTO backend. * @param allowLtoIndexing */ private static boolean handledByLtoIndexing(Artifact a, boolean allowLtoIndexing) { // If no LTO indexing is allowed for this link, then none are handled by LTO indexing. // Otherwise, this may be from a linkstatic library that we decided not to include in // LTO indexing because we are linking a test, to improve scalability when linking many tests. return allowLtoIndexing && !a.getRootRelativePath() .startsWith( PathFragment.create(CppLinkActionBuilder.SHARED_NONLTO_BACKEND_ROOT_PREFIX)); } private Map generateLtoMap() { if (isLtoIndexing || allLtoArtifacts == null) { return null; } // TODO(bazel-team): The LTO final link can only work if there are individual .o files on // the command line. Rather than crashing, this should issue a nice error. We will get // this by // 1) moving supports_start_end_lib to a toolchain feature // 2) having thin_lto require start_end_lib // As a bonus, we can rephrase --nostart_end_lib as --features=-start_end_lib and get rid // of a command line option. Preconditions.checkState(CppHelper.useStartEndLib(cppConfiguration, ccToolchainProvider)); Map ltoMap = new HashMap<>(); for (LtoBackendArtifacts l : allLtoArtifacts) { ltoMap.put(l.getBitcodeFile(), l.getObjectFile()); } return ltoMap; } }