diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java index 3e919ad22cb29f..d5abfbd85709f9 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java @@ -15,6 +15,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -22,6 +24,7 @@ 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; @@ -30,7 +33,9 @@ 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; @@ -38,6 +43,9 @@ /** 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; @@ -143,7 +151,7 @@ private NestedSet collectToolchainRuntimeLibrarySearchDirectories( String toolchainLibrariesSolibName = toolchainLibrariesSolibDir.getBaseName(); if (!(isNativeDeps && cppConfiguration.shareNativeDeps())) { - for (String potentialExecRoot : potentialSolibParents) { + for (String potentialExecRoot : findToolchainSolibParents(potentialSolibParents)) { runtimeLibrarySearchDirectories.add(potentialExecRoot + toolchainLibrariesSolibName + "/"); } } @@ -237,6 +245,164 @@ private ImmutableList findPotentialSolibParents() { return solibParents.build(); } + @VisibleForTesting + private ImmutableList findToolchainSolibParents( + ImmutableList potentialSolibParents) { + boolean usesLegacyRepositoryLayout = output.getRoot().isLegacy(); + // When -experimental_sibling_repository_layout is not enabled, the toolchain solib sits next to + // the solib_ 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/` + // + // 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: + // 2. main, direct, regular file: + // $ORIGIN: $EXECROOT/pkg + // solib root: $EXECROOT/pkg/file.runfiles/ + // 3. main, runfiles, symlink: + // $ORIGIN: $EXECROOT/pkg + // solib root: + // 4. main, runfiles, regular file: + // $ORIGIN: other_target.runfiles/main_repo/pkg + // solib root: other_target.runfiles/ + // 5. external, direct, symlink: + // $ORIGIN: $EXECROOT/../other_repo/pkg + // solib root: + // 6. external, direct, regular file: + // $ORIGIN: $EXECROOT/../other_repo/pkg + // solib root: $EXECROOT/../other_repo/pkg/file.runfiles/ + // 7. external, runfiles, symlink: + // $ORIGIN: $EXECROOT/../other_repo/pkg + // solib root: + // 8. external, runfiles, regular file: + // $ORIGIN: other_target.runfiles/some_repo/pkg + // solib root: other_target.runfiles/ + // + // 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/`. For 4 and 8, we need to compute the relative path from the output runfile to + // under runfiles. + ImmutableList.Builder solibParents = ImmutableList.builder(); + + // Cases 1, 3, 5, 7 + PathFragment toolchainBin = + toolchainLibrariesSolibDir.getParentDirectory(); // relative to execroot + PathFragment binaryOrigin = output.getExecPath().getParentDirectory(); // relative to execroot + solibParents.add( + toolchainBin.relativeTo(binaryOrigin).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()); + toolchainBin = PathFragment.create(toolchainRunfilesRepoName); // relative to runfiles + binaryOrigin = + PathFragment.create(binaryRepoName) + .getRelative(output.getRepositoryRelativePath()) + .getParentDirectory(); + solibParents.add( + toolchainBin.relativeTo(binaryOrigin).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". + * + *

Example 1: + * getRelative({@link PathFragment}.create("foo"), {@link PathFragment}.create("foo/bar/wiz")) + * returns "bar/wiz". + * + *

Example 2: + * getRelative({@link PathFragment}.create("foo/bar/wiz"), {@link PathFragment}.create("foo/wiz")) + * returns "../../wiz". + * + *

The following requirements / assumptions are made: 1) paths must be both absolute or both + * relative; 2) for absolute windows paths, they must be under the same drive letter; 3) when + * paths are relative, they are assumed to be relative to the same location; 4) when the {@code + * from} path starts with {@code ..} prefixes, the prefix length must not excceed {@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 either both absolute or both relative."); + } + if (from.isAbsolute() && !OS.equals(from.getDriveStr(), to.getDriveStr())) { + throw new IllegalArgumentException("Paths must be under the same drive letter."); + } + if (from.getPathString().length() == 0) { + return to; + } + + final ImmutableList fromSegments = from.splitToListOfSegments(); + final ImmutableList toSegments = to.splitToListOfSegments(); + final int fromSegCount = fromSegments.size(); + final int toSegCount = toSegments.size(); + + int commonSegCount = getCommonSegmentCount(fromSegments, toSegments); + if (commonSegCount == fromSegCount && commonSegCount == toSegCount) { + return PathFragment.EMPTY_FRAGMENT; + } + + StringBuilder resultBuilder = new StringBuilder(); + if (commonSegCount < fromSegCount) { + if (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."); + } + // For each remaining segment in base path, go one level up. + PATH_JOINER.appendTo(resultBuilder, Collections.nCopies(fromSegCount - commonSegCount, "..")); + } + if (commonSegCount < toSegCount) { + PATH_JOINER.appendTo(resultBuilder, toSegments.subList(commonSegCount, toSegCount)); + } + return PathFragment.createAlreadyNormalized(resultBuilder.toString()); + } + + private static int getCommonSegmentCount( + ImmutableList path1, ImmutableList path2) { + int i; + for (i = 0; + i < path1.size() && i < path2.size() && OS.equals(path1.get(i), path2.get(i)); + i++) {} + return i; + } + /** * 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 @@ -530,7 +696,7 @@ private void addStaticInputLinkOptions( LinkerInputs.simpleLinkerInput( member, ArtifactCategory.OBJECT_FILE, - /* disableWholeArchive = */ false, + /* disableWholeArchive= */ false, member.getRootRelativePathString())); } ImmutableList nonLtoArchiveMembers = nonLtoArchiveMembersBuilder.build();