Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

autoPatchelfHook: optimize performance; better error handling #101142

Merged
merged 6 commits into from
Dec 3, 2020
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions pkgs/build-support/setup-hooks/auto-patchelf.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
declare -a autoPatchelfLibs
declare -Ag autoPatchelfFailedDeps

gatherLibraries() {
autoPatchelfLibs+=("$1/lib")
}

# wrapper around patchelf to raise proper error messages
# containing the tried file name and command
runPatchelf() {
patchelf "$@" || (echo "Command failed: patchelf $*" && exit 1)
}

addEnvHooks "$targetOffset" gatherLibraries

isExecutable() {
Expand All @@ -23,14 +30,19 @@ isExecutable() {

# We cache dependencies so that we don't need to search through all of them on
# every consecutive call to findDependency.
declare -a cachedDependencies
declare -Ag autoPatchelfCachedDepsAssoc
declare -ag autoPatchelfCachedDeps


addToDepCache() {
local existing
for existing in "${cachedDependencies[@]}"; do
if [ "$existing" = "$1" ]; then return; fi
done
cachedDependencies+=("$1")
if [[ ${autoPatchelfCachedDepsAssoc[$1]+f} ]]; then return; fi

# store deps in an assoc. array for efficient lookups
# otherwise findDependency would have quadratic complexity
autoPatchelfCachedDepsAssoc["$1"]=""

# also store deps in normal array to maintain their order
autoPatchelfCachedDeps+=("$1")
}

declare -gi depCacheInitialised=0
Expand All @@ -43,7 +55,7 @@ getDepsFromSo() {

populateCacheWithRecursiveDeps() {
local so found foundso
for so in "${cachedDependencies[@]}"; do
for so in "${autoPatchelfCachedDeps[@]}"; do
for found in $(getDepsFromSo "$so"); do
local libdir="${found%/*}"
local base="${found##*/}"
Expand Down Expand Up @@ -76,7 +88,7 @@ findDependency() {
depCacheInitialised=1
fi

for dep in "${cachedDependencies[@]}"; do
for dep in "${autoPatchelfCachedDeps[@]}"; do
if [ "$filename" = "${dep##*/}" ]; then
if [ "$(getSoArch "$dep")" = "$arch" ]; then
foundDependency="$dep"
Expand All @@ -103,7 +115,7 @@ autoPatchelfFile() {

local interpreter="$(< "$NIX_CC/nix-support/dynamic-linker")"
if isExecutable "$toPatch"; then
patchelf --set-interpreter "$interpreter" "$toPatch"
runPatchelf --set-interpreter "$interpreter" "$toPatch"
if [ -n "$runtimeDependencies" ]; then
for dep in $runtimeDependencies; do
rpath="$rpath${rpath:+:}$dep/lib"
Expand All @@ -115,7 +127,7 @@ autoPatchelfFile() {

# We're going to find all dependencies based on ldd output, so we need to
# clear the RPATH first.
patchelf --remove-rpath "$toPatch"
runPatchelf --remove-rpath "$toPatch"

local missing="$(
ldd "$toPatch" 2> /dev/null | \
Expand All @@ -134,18 +146,13 @@ autoPatchelfFile() {
echo "found: $foundDependency" >&2
else
echo "not found!" >&2
depNotFound=1
autoPatchelfFailedDeps["$dep"]="$toPatch"
fi
done

# This makes sure the builder fails if we didn't find a dependency, because
# the stdenv setup script is run with set -e. The actual error is emitted
# earlier in the previous loop.
[ $depNotFound -eq 0 -o -n "$autoPatchelfIgnoreMissingDeps" ]

if [ -n "$rpath" ]; then
echo "setting RPATH to: $rpath" >&2
patchelf --set-rpath "$rpath" "$toPatch"
runPatchelf --set-rpath "$rpath" "$toPatch"
fi
}

Expand All @@ -168,10 +175,10 @@ addAutoPatchelfSearchPath() {
esac
done

cachedDependencies+=(
$(find "$@" "${findOpts[@]}" \! -type d \
\( -name '*.so' -o -name '*.so.*' \))
)
for file in \
$(find "$@" "${findOpts[@]}" \! -type d \
\( -name '*.so' -o -name '*.so.*' \))
do addToDepCache "$file"; done
}

autoPatchelf() {
Expand All @@ -197,14 +204,9 @@ autoPatchelf() {
echo "automatically fixing dependencies for ELF files" >&2

# Add all shared objects of the current output path to the start of
# cachedDependencies so that it's choosen first in findDependency.
# autoPatchelfCachedDeps so that it's chosen first in findDependency.
addAutoPatchelfSearchPath ${norecurse:+--no-recurse} -- "$@"

# Here we actually have a subshell, which also means that
# $cachedDependencies is final at this point, so whenever we want to run
# findDependency outside of this, the dependency cache needs to be rebuilt
# from scratch, so keep this in mind if you want to run findDependency
# outside of this function.
while IFS= read -r -d $'\0' file; do
isELF "$file" || continue
segmentHeaders="$(LANG=C $READELF -l "$file")"
Expand All @@ -215,8 +217,24 @@ autoPatchelf() {
# Skip if the executable is statically linked.
[ -n "$(echo "$segmentHeaders" | grep "^ *INTERP\\>")" ] || continue
fi
# Jump file if patchelf is unable to parse it
# Some programs contain binary blobs for testing,
# which are identified as ELF but fail to be parsed by patchelf
patchelf $file || continue
DavHau marked this conversation as resolved.
Show resolved Hide resolved
autoPatchelfFile "$file"
done < <(find "$@" ${norecurse:+-maxdepth 1} -type f -print0)

# fail if any dependencies were not found and
# autoPatchelfIgnoreMissingDeps is not set
local depsMissing=0
for failedDep in "${!autoPatchelfFailedDeps[@]}"; do
echo "autoPatchelfHook could not satisfy dependency $failedDep wanted by ${autoPatchelfFailedDeps[$failedDep]}"
depsMissing=1
done
if [[ $depsMissing == 1 && -z "$autoPatchelfIgnoreMissingDeps" ]]; then
echo "Add the missing dependencies to the build inputs or set autoPatchelfIgnoreMissingDeps=true"
exit 1
fi
}

# XXX: This should ultimately use fixupOutputHooks but we currently don't have
Expand Down