Skip to content

Commit

Permalink
Refactor logics to find runtime library search directories
Browse files Browse the repository at this point in the history
* Extracts logic to find execroots as a separate method and moves it into the `collectLibrariesToLink()` method from the constructor.
* Extracts logic to create rpaths for toolchain libraries as a separate method.
* Reorganizes the logic for better readability.

This debloats the constructor and makes further changes easier.
  • Loading branch information
yuzhy8701 committed Jan 20, 2023
1 parent ab71a10 commit 2cfda71
Showing 1 changed file with 140 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,10 @@ public class LibrariesToLinkCollector {
private final Artifact thinltoParamFile;
private final FeatureConfiguration featureConfiguration;
private final boolean needWholeArchive;
private final ImmutableList<String> potentialExecRoots;
private final ImmutableList<String> rpathRoots;
private final boolean needToolchainLibrariesRpath;
private final Map<Artifact, Artifact> ltoMap;
private final RuleErrorConsumer ruleErrorConsumer;
private final Artifact output;
private final String workspaceName;

public LibrariesToLinkCollector(
boolean isNativeDeps,
Expand Down Expand Up @@ -87,114 +86,13 @@ public LibrariesToLinkCollector(
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));

// 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.
potentialExecRoots = ImmutableList.of();
rpathRoots = ImmutableList.of(ccToolchainProvider.getSolibDirectory() + "/");
} else {
// 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> execRoots = ImmutableList.builder();
// Handles cases 1, 3, 4, 5, and 7.
execRoots.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;
}
execRoots.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.
execRoots.add(
"../".repeat(output.getRunfilesPath().segmentCount() - 2) + workspaceName + "/");
}

potentialExecRoots = execRoots.build();
rpathRoots =
potentialExecRoots.stream()
.map((execRoot) -> execRoot + ccToolchainProvider.getSolibDirectory() + "/")
.collect(toImmutableList());
}

ltoMap = generateLtoMap();
}

/**
Expand Down Expand Up @@ -236,6 +134,109 @@ public NestedSet<String> getRuntimeLibrarySearchDirectories() {
}
}

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 : 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();
}

/**
* 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 All @@ -247,48 +248,43 @@ public NestedSet<String> getRuntimeLibrarySearchDirectories() {
*/
public CollectedLibrariesToLink collectLibrariesToLink() {
NestedSetBuilder<String> librarySearchDirectories = NestedSetBuilder.linkOrder();
NestedSetBuilder<String> runtimeLibrarySearchDirectories = 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();

String toolchainLibrariesSolibName =
toolchainLibrariesSolibDir != null ? toolchainLibrariesSolibDir.getBaseName() : null;
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()) {
if (needToolchainLibrariesRpath) {
runtimeLibrarySearchDirectories.add("../" + toolchainLibrariesSolibName + "/");
}
// 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 {
// 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) {
for (String potentialExecRoot : potentialExecRoots) {
runtimeLibrarySearchDirectories.add(
potentialExecRoot + toolchainLibrariesSolibName + "/");
}
}
if (isNativeDeps) {
// We also retain the $ORIGIN/ path to solibs that are in _solib_<arch>, as opposed to
// the package directory)
if (needToolchainLibrariesRpath) {
runtimeLibrarySearchDirectories.add("../" + toolchainLibrariesSolibName + "/");
}
}
}

if (needToolchainLibrariesRpath) {
if (isNativeDeps) {
runtimeLibrarySearchDirectories.add(".");
}
runtimeLibrarySearchDirectories.add(toolchainLibrariesSolibName + "/");
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,
Expand All @@ -307,7 +303,8 @@ public CollectedLibrariesToLink collectLibrariesToLink() {
}
allRuntimeLibrarySearchDirectories.addAll(rpathRootsForExplicitSoDeps.build());
if (includeToolchainLibrariesSolibDir) {
allRuntimeLibrarySearchDirectories.addTransitive(runtimeLibrarySearchDirectories.build());
allRuntimeLibrarySearchDirectories.addTransitive(
collectToolchainRuntimeLibrarySearchDirectories(potentialSolibParents));
}

return new CollectedLibrariesToLink(
Expand All @@ -318,6 +315,8 @@ public CollectedLibrariesToLink collectLibrariesToLink() {
}

private Pair<Boolean, Boolean> addLinkerInputs(
ImmutableList<String> rpathRoots,
Map<Artifact, Artifact> ltoMap,
NestedSetBuilder<String> librarySearchDirectories,
ImmutableSet.Builder<String> rpathEntries,
SequenceBuilder librariesToLink,
Expand Down Expand Up @@ -366,9 +365,10 @@ private Pair<Boolean, Boolean> addLinkerInputs(
librariesToLink,
expandedLinkerInputsBuilder,
librarySearchDirectories,
rpathRoots,
rpathEntries);
} else {
addStaticInputLinkOptions(input, librariesToLink, expandedLinkerInputsBuilder);
addStaticInputLinkOptions(input, ltoMap, librariesToLink, expandedLinkerInputsBuilder);
}
}
return Pair.of(includeSolibDir, includeToolchainLibrariesSolibDir);
Expand All @@ -384,6 +384,7 @@ private void addDynamicInputLinkOptions(
SequenceBuilder librariesToLink,
NestedSetBuilder<LinkerInput> expandedLinkerInputsBuilder,
NestedSetBuilder<String> librarySearchDirectories,
ImmutableList<String> rpathRoots,
ImmutableSet.Builder<String> rpathRootsForExplicitSoDeps) {
Preconditions.checkState(
input.getArtifactCategory() == ArtifactCategory.DYNAMIC_LIBRARY
Expand Down Expand Up @@ -470,6 +471,7 @@ private void addDynamicInputLinkOptions(
*/
private void addStaticInputLinkOptions(
LinkerInput input,
Map<Artifact, Artifact> ltoMap,
SequenceBuilder librariesToLink,
NestedSetBuilder<LinkerInput> expandedLinkerInputsBuilder) {
ArtifactCategory artifactCategory = input.getArtifactCategory();
Expand Down

0 comments on commit 2cfda71

Please sign in to comment.