Skip to content

Commit

Permalink
Correctly compute RPATHs for toolchain solib.
Browse files Browse the repository at this point in the history
When sibling repository layout is used, the toolchain solib directory is not co-located with the main solib (solib_<cpu>). Therefore, the RPATHs for toolchain solib need to be computed separately, in a similar manner as how they are computed for the main solib.

This change uses a private implementation of `getRelative()` function, rather than the `PathFragment.relativeTo()` method. The reason is that `PathFragment.relativeTo()` requires the base path to be a prefix which is not true in the cases here.

Fixes #16956
  • Loading branch information
yuzhy8701 committed Jan 20, 2023
1 parent 2cfda71 commit 65a023b
Showing 1 changed file with 168 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@

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;
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;
Expand All @@ -30,14 +33,19 @@
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;
Expand Down Expand Up @@ -143,7 +151,7 @@ private NestedSet<String> collectToolchainRuntimeLibrarySearchDirectories(

String toolchainLibrariesSolibName = toolchainLibrariesSolibDir.getBaseName();
if (!(isNativeDeps && cppConfiguration.shareNativeDeps())) {
for (String potentialExecRoot : potentialSolibParents) {
for (String potentialExecRoot : findToolchainSolibParents(potentialSolibParents)) {
runtimeLibrarySearchDirectories.add(potentialExecRoot + toolchainLibrariesSolibName + "/");
}
}
Expand Down Expand Up @@ -237,6 +245,164 @@ private ImmutableList<String> findPotentialSolibParents() {
return solibParents.build();
}

@VisibleForTesting
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 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".
*
* <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 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<String> fromSegments = from.splitToListOfSegments();
final ImmutableList<String> 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<String> path1, ImmutableList<String> 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
Expand Down Expand Up @@ -530,7 +696,7 @@ private void addStaticInputLinkOptions(
LinkerInputs.simpleLinkerInput(
member,
ArtifactCategory.OBJECT_FILE,
/* disableWholeArchive = */ false,
/* disableWholeArchive= */ false,
member.getRootRelativePathString()));
}
ImmutableList<Artifact> nonLtoArchiveMembers = nonLtoArchiveMembersBuilder.build();
Expand Down

0 comments on commit 65a023b

Please sign in to comment.