| // 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 static com.google.common.collect.ImmutableList.toImmutableList; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| 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.analysis.RuleErrorConsumer; |
| import com.google.devtools.build.lib.cmdline.LabelConstants; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| 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.OsPathPolicy; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| |
| /** Class that goes over linker inputs and produces {@link LibraryToLinkValue}s */ |
| public class LibrariesToLinkCollector { |
| |
| private static final OsPathPolicy OS = OsPathPolicy.getFilePathOs(); |
| private static final Joiner PATH_JOINER = Joiner.on(PathFragment.SEPARATOR_CHAR); |
| |
| private final boolean isNativeDeps; |
| private final PathFragment toolchainLibrariesSolibDir; |
| private final CppConfiguration cppConfiguration; |
| private final CcToolchainProvider ccToolchainProvider; |
| private final boolean isLtoIndexing; |
| private final PathFragment solibDir; |
| private final Iterable<? extends LinkerInput> linkerInputs; |
| private final Iterable<LtoBackendArtifacts> allLtoArtifacts; |
| private final boolean allowLtoIndexing; |
| private final Artifact thinltoParamFile; |
| private final FeatureConfiguration featureConfiguration; |
| private final boolean needWholeArchive; |
| private final boolean needToolchainLibrariesRpath; |
| private final RuleErrorConsumer ruleErrorConsumer; |
| private final Artifact output; |
| private final String workspaceName; |
| |
| public LibrariesToLinkCollector( |
| boolean isNativeDeps, |
| CppConfiguration cppConfiguration, |
| CcToolchainProvider toolchain, |
| PathFragment toolchainLibrariesSolibDir, |
| LinkTargetType linkType, |
| Link.LinkingMode linkingMode, |
| Artifact output, |
| PathFragment solibDir, |
| boolean isLtoIndexing, |
| Iterable<LtoBackendArtifacts> allLtoArtifacts, |
| FeatureConfiguration featureConfiguration, |
| Artifact thinltoParamFile, |
| boolean allowLtoIndexing, |
| Iterable<LinkerInput> linkerInputs, |
| boolean needWholeArchive, |
| RuleErrorConsumer ruleErrorConsumer, |
| String workspaceName) { |
| this.isNativeDeps = isNativeDeps; |
| this.cppConfiguration = cppConfiguration; |
| this.ccToolchainProvider = toolchain; |
| this.toolchainLibrariesSolibDir = toolchainLibrariesSolibDir; |
| this.solibDir = solibDir; |
| this.isLtoIndexing = isLtoIndexing; |
| this.allLtoArtifacts = allLtoArtifacts; |
| this.featureConfiguration = featureConfiguration; |
| this.thinltoParamFile = thinltoParamFile; |
| this.allowLtoIndexing = allowLtoIndexing; |
| this.linkerInputs = linkerInputs; |
| this.needWholeArchive = needWholeArchive; |
| this.ruleErrorConsumer = ruleErrorConsumer; |
| this.output = output; |
| this.workspaceName = workspaceName; |
| |
| needToolchainLibrariesRpath = |
| toolchainLibrariesSolibDir != null |
| && (linkType.isDynamicLibrary() |
| || (linkType == LinkTargetType.EXECUTABLE && linkingMode == LinkingMode.DYNAMIC)); |
| } |
| |
| /** |
| * 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 NestedSet<LinkerInput> expandedLinkerInputs; |
| private final NestedSet<String> librarySearchDirectories; |
| private final NestedSet<String> runtimeLibrarySearchDirectories; |
| |
| private CollectedLibrariesToLink( |
| SequenceBuilder librariesToLink, |
| NestedSet<LinkerInput> expandedLinkerInputs, |
| NestedSet<String> librarySearchDirectories, |
| NestedSet<String> 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 NestedSet<LinkerInput> getExpandedLinkerInputs() { |
| return expandedLinkerInputs; |
| } |
| |
| public NestedSet<String> getLibrarySearchDirectories() { |
| return librarySearchDirectories; |
| } |
| |
| public NestedSet<String> getRuntimeLibrarySearchDirectories() { |
| return runtimeLibrarySearchDirectories; |
| } |
| } |
| |
| private NestedSet<String> collectToolchainRuntimeLibrarySearchDirectories( |
| ImmutableList<String> potentialSolibParents) { |
| NestedSetBuilder<String> runtimeLibrarySearchDirectories = NestedSetBuilder.linkOrder(); |
| if (!needToolchainLibrariesRpath) { |
| return runtimeLibrarySearchDirectories.build(); |
| } |
| |
| String toolchainLibrariesSolibName = toolchainLibrariesSolibDir.getBaseName(); |
| if (!(isNativeDeps && cppConfiguration.shareNativeDeps())) { |
| for (String potentialExecRoot : findToolchainSolibParents(potentialSolibParents)) { |
| runtimeLibrarySearchDirectories.add(potentialExecRoot + toolchainLibrariesSolibName + "/"); |
| } |
| } |
| if (isNativeDeps) { |
| runtimeLibrarySearchDirectories.add("../" + toolchainLibrariesSolibName + "/"); |
| runtimeLibrarySearchDirectories.add("."); |
| } |
| runtimeLibrarySearchDirectories.add(toolchainLibrariesSolibName + "/"); |
| |
| return runtimeLibrarySearchDirectories.build(); |
| } |
| |
| private ImmutableList<String> findPotentialSolibParents() { |
| // The runtime location of the solib directory relative to the binary depends on four factors: |
| // |
| // * whether the binary is contained in the main repository or an external repository; |
| // * whether the binary is executed directly or from a runfiles tree; |
| // * whether the binary is staged as a symlink (sandboxed execution; local execution if the |
| // binary is in the runfiles of another target) or a regular file (remote execution) - the |
| // dynamic linker follows sandbox and runfiles symlinks into its location under the |
| // unsandboxed execroot, which thus becomes the effective $ORIGIN; |
| // * whether --experimental_sibling_repository_layout is enabled or not. |
| // |
| // The rpaths emitted into the binary thus have to cover the following cases (assuming that |
| // the binary target is located in the pkg `pkg` and has name `file`) for the directory used |
| // as $ORIGIN by the dynamic linker and the directory containing the solib directories: |
| // |
| // 1. main, direct, symlink: |
| // $ORIGIN: $EXECROOT/pkg |
| // solib root: $EXECROOT |
| // 2. main, direct, regular file: |
| // $ORIGIN: $EXECROOT/pkg |
| // solib root: $EXECROOT/pkg/file.runfiles/main_repo |
| // 3. main, runfiles, symlink: |
| // $ORIGIN: $EXECROOT/pkg |
| // solib root: $EXECROOT |
| // 4. main, runfiles, regular file: |
| // $ORIGIN: other_target.runfiles/main_repo/pkg |
| // solib root: other_target.runfiles/main_repo |
| // 5a. external, direct, symlink: |
| // $ORIGIN: $EXECROOT/external/other_repo/pkg |
| // solib root: $EXECROOT |
| // 5b. external, direct, symlink, with --experimental_sibling_repository_layout: |
| // $ORIGIN: $EXECROOT/../other_repo/pkg |
| // solib root: $EXECROOT/../other_repo |
| // 6a. external, direct, regular file: |
| // $ORIGIN: $EXECROOT/external/other_repo/pkg |
| // solib root: $EXECROOT/external/other_repo/pkg/file.runfiles/main_repo |
| // 6b. external, direct, regular file, with --experimental_sibling_repository_layout: |
| // $ORIGIN: $EXECROOT/../other_repo/pkg |
| // solib root: $EXECROOT/../other_repo/pkg/file.runfiles/other_repo |
| // 7a. external, runfiles, symlink: |
| // $ORIGIN: $EXECROOT/external/other_repo/pkg |
| // solib root: $EXECROOT |
| // 7b. external, runfiles, symlink, with --experimental_sibling_repository_layout: |
| // $ORIGIN: $EXECROOT/../other_repo/pkg |
| // solib root: $EXECROOT/../other_repo |
| // 8a. external, runfiles, regular file: |
| // $ORIGIN: other_target.runfiles/some_repo/pkg |
| // solib root: other_target.runfiles/main_repo |
| // 8b. external, runfiles, regular file, with --experimental_sibling_repository_layout: |
| // $ORIGIN: other_target.runfiles/some_repo/pkg |
| // solib root: other_target.runfiles/some_repo |
| // |
| // Cases 1, 3, 4, 5, 7, and 8b are covered by an rpath that walks up the root relative path. |
| // Cases 2 and 6 covered by walking into file.runfiles/main_repo. |
| // Case 8a is covered by walking up some_repo/pkg and then into main_repo. |
| boolean isExternal = |
| output.getRunfilesPath().startsWith(LabelConstants.EXTERNAL_RUNFILES_PATH_PREFIX); |
| boolean usesLegacyRepositoryLayout = output.getRoot().isLegacy(); |
| ImmutableList.Builder<String> solibParents = ImmutableList.builder(); |
| // Handles cases 1, 3, 4, 5, and 7. |
| solibParents.add("../".repeat(output.getRootRelativePath().segmentCount() - 1)); |
| // Handle cases 2 and 6. |
| String solibRepositoryName; |
| if (isExternal && !usesLegacyRepositoryLayout) { |
| // Case 6b |
| solibRepositoryName = output.getRunfilesPath().getSegment(1); |
| } else { |
| // Cases 2 and 6a |
| solibRepositoryName = workspaceName; |
| } |
| solibParents.add(output.getFilename() + ".runfiles/" + solibRepositoryName + "/"); |
| if (isExternal && usesLegacyRepositoryLayout) { |
| // Handles case 8a. The runfiles path is of the form ../some_repo/pkg/file and we need to |
| // walk up some_repo/pkg and then down into main_repo. |
| solibParents.add( |
| "../".repeat(output.getRunfilesPath().segmentCount() - 2) + workspaceName + "/"); |
| } |
| |
| return solibParents.build(); |
| } |
| |
| private ImmutableList<String> findToolchainSolibParents( |
| ImmutableList<String> potentialSolibParents) { |
| boolean usesLegacyRepositoryLayout = output.getRoot().isLegacy(); |
| // When -experimental_sibling_repository_layout is not enabled, the toolchain solib sits next to |
| // the solib_<cpu> directory - so that it shares the same parents. |
| if (usesLegacyRepositoryLayout) { |
| return potentialSolibParents; |
| } |
| |
| // When -experimental_sibling_repository_layout is enabled, the toolchain solib is located in |
| // these 2 places: |
| // 1. The `bin` directory of the repository where the toolchain target is declared (this is the |
| // parent directory of `toolchainLibrariesSolibDir`). |
| // 2. In `target.runfiles/<toolchain repo>` |
| // |
| // And the following factors affect what $ORIGIN is resolved to: |
| // * whether the binary is contained in the main repository or an external repository; |
| // * whether the binary is executed directly or from a runfiles tree; |
| // * whether the binary is staged as a symlink (sandboxed execution; local execution if the |
| // binary is in the runfiles of another target) or a regular file (remote execution) - the |
| // dynamic linker follows sandbox and runfiles symlinks into its location under the |
| // unsandboxed execroot, which thus becomes the effective $ORIGIN; |
| // |
| // The rpaths emitted into the binary thus have to cover the following cases (assuming that |
| // the binary target is located in the pkg `pkg` and has name `file`) for the directory used |
| // as $ORIGIN by the dynamic linker and the directory containing the solib directories: |
| // 1. main, direct, symlink: |
| // $ORIGIN: $EXECROOT/pkg |
| // solib root: <toolchain repo bin> |
| // 2. main, direct, regular file: |
| // $ORIGIN: $EXECROOT/pkg |
| // solib root: $EXECROOT/pkg/file.runfiles/<toolchain repo> |
| // 3. main, runfiles, symlink: |
| // $ORIGIN: $EXECROOT/pkg |
| // solib root: <toolchain repo bin> |
| // 4. main, runfiles, regular file: |
| // $ORIGIN: other_target.runfiles/main_repo/pkg |
| // solib root: other_target.runfiles/<toolchain repo> |
| // 5. external, direct, symlink: |
| // $ORIGIN: $EXECROOT/../other_repo/pkg |
| // solib root: <toolchain repo bin> |
| // 6. external, direct, regular file: |
| // $ORIGIN: $EXECROOT/../other_repo/pkg |
| // solib root: $EXECROOT/../other_repo/pkg/file.runfiles/<toolchain repo> |
| // 7. external, runfiles, symlink: |
| // $ORIGIN: $EXECROOT/../other_repo/pkg |
| // solib root: <toolchain repo bin> |
| // 8. external, runfiles, regular file: |
| // $ORIGIN: other_target.runfiles/some_repo/pkg |
| // solib root: other_target.runfiles/<toolchain repo> |
| // |
| // For cases 1, 3, 5, 7, we need to compute the relative path from the output artifact to |
| // toolchain repo's bin directory. For 2 and 6, we walk down into `file.runfiles/<toolchain |
| // repo>`. For 4 and 8, we need to compute the relative path from the output runfile to |
| // <toolchain repo> under runfiles. |
| ImmutableList.Builder<String> solibParents = ImmutableList.builder(); |
| |
| // Cases 1, 3, 5, 7 |
| PathFragment toolchainBinExecPath = toolchainLibrariesSolibDir.getParentDirectory(); |
| PathFragment binaryOriginExecPath = output.getExecPath().getParentDirectory(); |
| solibParents.add( |
| getRelative(binaryOriginExecPath, toolchainBinExecPath).getPathString() |
| + PathFragment.SEPARATOR_CHAR); |
| |
| // Cases 2 and 6 |
| String toolchainRunfilesRepoName = |
| getRunfilesRepoName(ccToolchainProvider.getCcToolchainLabel().getRepository()); |
| solibParents.add( |
| PATH_JOINER.join(output.getFilename() + ".runfiles", toolchainRunfilesRepoName) |
| + PathFragment.SEPARATOR_CHAR); |
| |
| // Cases 4 and 8 |
| String binaryRepoName = getRunfilesRepoName(output.getOwnerLabel().getRepository()); |
| PathFragment toolchainBinRunfilesPath = PathFragment.create(toolchainRunfilesRepoName); |
| PathFragment binaryOriginRunfilesPath = |
| PathFragment.create(binaryRepoName) |
| .getRelative(output.getRepositoryRelativePath()) |
| .getParentDirectory(); |
| solibParents.add( |
| getRelative(binaryOriginRunfilesPath, toolchainBinRunfilesPath).getPathString() |
| + PathFragment.SEPARATOR_CHAR); |
| |
| return solibParents.build(); |
| } |
| |
| private String getRunfilesRepoName(RepositoryName repo) { |
| if (repo.isMain()) { |
| return workspaceName; |
| } |
| return repo.getName(); |
| } |
| |
| /** |
| * Returns the relative {@link PathFragment} from "from" to "to". |
| * |
| * <p>Example 1: <code> |
| * getRelative({@link PathFragment}.create("foo"), {@link PathFragment}.create("foo/bar/wiz")) |
| * </code> returns <code>"bar/wiz"</code>. |
| * |
| * <p>Example 2: <code> |
| * getRelative({@link PathFragment}.create("foo/bar/wiz"), |
| * {@link PathFragment}.create("foo/wiz")) |
| * </code> returns <code>"../../wiz"</code>. |
| * |
| * <p>The following requirements / assumptions are made: 1) paths must be both relative; 2) they |
| * are assumed to be relative to the same location; 3) when the {@code from} path starts with |
| * {@code ..} prefixes, the prefix length must not exceed {@code ..} prefixes of the {@code to} |
| * path. |
| */ |
| static PathFragment getRelative(PathFragment from, PathFragment to) { |
| if (from.isAbsolute() || to.isAbsolute()) { |
| throw new IllegalArgumentException("Paths must be both relative."); |
| } |
| |
| final ImmutableList<String> fromSegments = from.splitToListOfSegments(); |
| final ImmutableList<String> toSegments = to.splitToListOfSegments(); |
| final int fromSegCount = fromSegments.size(); |
| final int toSegCount = toSegments.size(); |
| |
| int commonSegCount = 0; |
| while (commonSegCount < fromSegCount |
| && commonSegCount < toSegCount |
| && OS.equals(fromSegments.get(commonSegCount), toSegments.get(commonSegCount))) { |
| commonSegCount++; |
| } |
| |
| if (commonSegCount < fromSegCount && fromSegments.get(commonSegCount).equals("..")) { |
| throw new IllegalArgumentException( |
| "Unable to compute relative path from \"" |
| + from.getPathString() |
| + "\" to \"" |
| + to.getPathString() |
| + "\": too many leading \"..\" segments in from path."); |
| } |
| PathFragment relativePath = |
| PathFragment.create( |
| PATH_JOINER.join(Collections.nCopies(fromSegCount - commonSegCount, ".."))); |
| if (commonSegCount < toSegCount) { |
| relativePath = relativePath.getRelative(to.subFragment(commonSegCount, toSegCount)); |
| } |
| return relativePath; |
| } |
| |
| /** |
| * 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. |
| * |
| * <p>TODO: Factor out of the bazel binary into build variables for crosstool action_configs. |
| */ |
| public CollectedLibrariesToLink collectLibrariesToLink() { |
| NestedSetBuilder<String> librarySearchDirectories = NestedSetBuilder.linkOrder(); |
| ImmutableSet.Builder<String> rpathRootsForExplicitSoDeps = ImmutableSet.builder(); |
| NestedSetBuilder<LinkerInput> expandedLinkerInputsBuilder = NestedSetBuilder.linkOrder(); |
| // List of command line parameters that need to be placed *outside* of |
| // --whole-archive ... --no-whole-archive. |
| SequenceBuilder librariesToLink = new SequenceBuilder(); |
| |
| ImmutableList<String> potentialSolibParents; |
| ImmutableList<String> rpathRoots; |
| // 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. |
| potentialSolibParents = ImmutableList.of(); |
| rpathRoots = ImmutableList.of(ccToolchainProvider.getSolibDirectory() + "/"); |
| } else { |
| potentialSolibParents = findPotentialSolibParents(); |
| rpathRoots = |
| potentialSolibParents.stream() |
| .map((execRoot) -> execRoot + ccToolchainProvider.getSolibDirectory() + "/") |
| .collect(toImmutableList()); |
| } |
| |
| Map<Artifact, Artifact> ltoMap = generateLtoMap(); |
| Pair<Boolean, Boolean> includeSolibsPair = |
| addLinkerInputs( |
| rpathRoots, |
| ltoMap, |
| 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); |
| |
| NestedSetBuilder<String> allRuntimeLibrarySearchDirectories = NestedSetBuilder.linkOrder(); |
| // rpath ordering matters for performance; first add the one where most libraries are found. |
| if (includeSolibDir) { |
| for (String rpathRoot : rpathRoots) { |
| allRuntimeLibrarySearchDirectories.add(rpathRoot); |
| } |
| } |
| allRuntimeLibrarySearchDirectories.addAll(rpathRootsForExplicitSoDeps.build()); |
| if (includeToolchainLibrariesSolibDir) { |
| allRuntimeLibrarySearchDirectories.addTransitive( |
| collectToolchainRuntimeLibrarySearchDirectories(potentialSolibParents)); |
| } |
| |
| return new CollectedLibrariesToLink( |
| librariesToLink, |
| expandedLinkerInputsBuilder.build(), |
| librarySearchDirectories.build(), |
| allRuntimeLibrarySearchDirectories.build()); |
| } |
| |
| private Pair<Boolean, Boolean> addLinkerInputs( |
| ImmutableList<String> rpathRoots, |
| Map<Artifact, Artifact> ltoMap, |
| NestedSetBuilder<String> librarySearchDirectories, |
| ImmutableSet.Builder<String> rpathEntries, |
| SequenceBuilder librariesToLink, |
| NestedSetBuilder<LinkerInput> expandedLinkerInputsBuilder) { |
| boolean includeSolibDir = false; |
| boolean includeToolchainLibrariesSolibDir = false; |
| Map<String, PathFragment> linkedLibrariesPaths = new HashMap<>(); |
| |
| for (LinkerInput input : linkerInputs) { |
| if (input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY |
| || input.getArtifactCategory() == ArtifactCategory.INTERFACE_LIBRARY) { |
| PathFragment originalLibDir = |
| input.getOriginalLibraryArtifact().getExecPath().getParentDirectory(); |
| Preconditions.checkNotNull(originalLibDir); |
| String libraryIdentifier = input.getLibraryIdentifier(); |
| PathFragment previousLibDir = linkedLibrariesPaths.get(libraryIdentifier); |
| |
| if (previousLibDir == null) { |
| linkedLibrariesPaths.put(libraryIdentifier, originalLibDir); |
| } else if (!previousLibDir.equals(originalLibDir)) { |
| ruleErrorConsumer.ruleError( |
| String.format( |
| "You are trying to link the same dynamic library %s built in a different" |
| + " configuration. Previously registered instance had path %s, current one" |
| + " has path %s", |
| libraryIdentifier, previousLibDir, originalLibDir)); |
| } |
| |
| 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)) { |
| // The first fragment is bazel-out, and the second may contain a configuration mnemonic. |
| // We should always add the default solib dir because that's where libraries will be found |
| // e.g. in remote execution, so we ignore the first two fragments. |
| if (libDir.subFragment(2).equals(solibDir.subFragment(2))) { |
| includeSolibDir = true; |
| } |
| if (libDir.equals(toolchainLibrariesSolibDir)) { |
| includeToolchainLibrariesSolibDir = true; |
| } |
| } |
| addDynamicInputLinkOptions( |
| input, |
| librariesToLink, |
| expandedLinkerInputsBuilder, |
| librarySearchDirectories, |
| rpathRoots, |
| rpathEntries); |
| } else { |
| addStaticInputLinkOptions(input, ltoMap, 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, |
| NestedSetBuilder<LinkerInput> expandedLinkerInputsBuilder, |
| NestedSetBuilder<String> librarySearchDirectories, |
| ImmutableList<String> rpathRoots, |
| ImmutableSet.Builder<String> rpathRootsForExplicitSoDeps) { |
| Preconditions.checkState( |
| input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY |
| || input.getArtifactCategory() == ArtifactCategory.INTERFACE_LIBRARY); |
| Preconditions.checkState( |
| !Link.useStartEndLib( |
| input, |
| CppHelper.getArchiveType(cppConfiguration, ccToolchainProvider, featureConfiguration))); |
| |
| expandedLinkerInputsBuilder.add(input); |
| if (featureConfiguration.isEnabled(CppRuleClasses.TARGETS_WINDOWS) |
| && ccToolchainProvider.supportsInterfaceSharedLibraries(featureConfiguration)) { |
| // On Windows, dynamic library (dll) cannot be linked directly when using toolchains that |
| // support interface library (eg. MSVC). If the user is doing so, it is only to be referenced |
| // in other places (such as copy_dynamic_libraries_to_binary); skip adding it. |
| if (CppFileTypes.SHARED_LIBRARY.matches(input.getArtifact().getFilename())) { |
| return; |
| } |
| } |
| |
| 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(); |
| } |
| |
| // When all dynamic deps are built in transitioned configurations, the default solib dir is |
| // not created. While resolving paths, the dynamic linker stops at the first directory that |
| // does not exist, even when followed by "../". We thus have to normalize the relative path. |
| for (String rpathRoot : rpathRoots) { |
| String relativePathToRoot = |
| rpathRoot + dotdots + libDir.relativeTo(commonParent).getPathString(); |
| String normalizedPathToRoot = PathFragment.create(relativePathToRoot).getPathString(); |
| rpathRootsForExplicitSoDeps.add(normalizedPathToRoot); |
| } |
| |
| // Unless running locally, libraries will be available under the root relative path, so we |
| // should add that to the rpath as well. |
| if (inputArtifact.getRootRelativePathString().startsWith("_solib_")) { |
| PathFragment artifactPathUnderSolib = inputArtifact.getRootRelativePath().subFragment(1); |
| for (String rpathRoot : rpathRoots) { |
| rpathRootsForExplicitSoDeps.add( |
| rpathRoot + artifactPathUnderSolib.getParentDirectory().getPathString()); |
| } |
| } |
| } |
| |
| librarySearchDirectories.add(inputArtifact.getExecPath().getParentDirectory().getPathString()); |
| |
| String name = inputArtifact.getFilename(); |
| |
| // Use the normal shared library resolution rules if possible, otherwise treat as a versioned |
| // library that must use the exact name. e.g.: |
| // -lfoo -> libfoo.so |
| // -l:foo -> foo.so |
| // -l:libfoo.so.1 -> libfoo.so.1 |
| boolean hasCompatibleName = |
| name.startsWith("lib") || (!name.endsWith(".so") && !name.endsWith(".dylib")); |
| if (CppFileTypes.SHARED_LIBRARY.matches(name) && hasCompatibleName) { |
| String libName = name.replaceAll("(^lib|\\.(so|dylib)$)", ""); |
| librariesToLink.addValue(LibraryToLinkValue.forDynamicLibrary(libName)); |
| } else if (CppFileTypes.SHARED_LIBRARY.matches(name) |
| || CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(name)) { |
| 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())); |
| } |
| } |
| |
| /** |
| * 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, |
| Map<Artifact, Artifact> ltoMap, |
| SequenceBuilder librariesToLink, |
| NestedSetBuilder<LinkerInput> 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, featureConfiguration))) { |
| Iterable<Artifact> archiveMembers = input.getObjectFiles(); |
| if (!Iterables.isEmpty(archiveMembers)) { |
| ImmutableList.Builder<Artifact> 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, |
| a.getRootRelativePathString())); |
| 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, |
| member.getRootRelativePathString())); |
| } |
| ImmutableList<Artifact> 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.<Artifact>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( |
| member.getExecPathString(), /* 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, |
| a.getRootRelativePathString())); |
| 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.<Artifact>of(inputArtifact), inputIsWholeArchive)); |
| } else { |
| librariesToLink.addValue( |
| LibraryToLinkValue.forObjectFile( |
| inputArtifact.getExecPathString(), inputIsWholeArchive)); |
| } |
| if (!input.isLinkstamp()) { |
| expandedLinkerInputsBuilder.add(input); |
| } |
| } else { |
| librariesToLink.addValue( |
| LibraryToLinkValue.forStaticLibrary( |
| inputArtifact.getExecPathString(), 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)); |
| } |
| |
| @Nullable |
| private Map<Artifact, Artifact> 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, featureConfiguration)); |
| Map<Artifact, Artifact> ltoMap = new HashMap<>(); |
| for (LtoBackendArtifacts l : allLtoArtifacts) { |
| ltoMap.put(l.getBitcodeFile(), l.getObjectFile()); |
| } |
| return ltoMap; |
| } |
| } |