Merge pull request #101142 from DavHau/improve-autopatchelf

autoPatchelfHook: optimize performance; better error handling
This commit is contained in:
Frederik Rietdijk 2020-12-03 12:34:05 +01:00 committed by GitHub
commit c7843cf6a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,9 +1,16 @@
declare -a autoPatchelfLibs declare -a autoPatchelfLibs
declare -Ag autoPatchelfFailedDeps
gatherLibraries() { gatherLibraries() {
autoPatchelfLibs+=("$1/lib") 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 addEnvHooks "$targetOffset" gatherLibraries
isExecutable() { isExecutable() {
@ -23,14 +30,19 @@ isExecutable() {
# We cache dependencies so that we don't need to search through all of them on # We cache dependencies so that we don't need to search through all of them on
# every consecutive call to findDependency. # every consecutive call to findDependency.
declare -a cachedDependencies declare -Ag autoPatchelfCachedDepsAssoc
declare -ag autoPatchelfCachedDeps
addToDepCache() { addToDepCache() {
local existing if [[ ${autoPatchelfCachedDepsAssoc[$1]+f} ]]; then return; fi
for existing in "${cachedDependencies[@]}"; do
if [ "$existing" = "$1" ]; then return; fi # store deps in an assoc. array for efficient lookups
done # otherwise findDependency would have quadratic complexity
cachedDependencies+=("$1") autoPatchelfCachedDepsAssoc["$1"]=""
# also store deps in normal array to maintain their order
autoPatchelfCachedDeps+=("$1")
} }
declare -gi depCacheInitialised=0 declare -gi depCacheInitialised=0
@ -43,9 +55,8 @@ getDepsFromSo() {
populateCacheWithRecursiveDeps() { populateCacheWithRecursiveDeps() {
local so found foundso local so found foundso
for so in "${cachedDependencies[@]}"; do for so in "${autoPatchelfCachedDeps[@]}"; do
for found in $(getDepsFromSo "$so"); do for found in $(getDepsFromSo "$so"); do
local libdir="${found%/*}"
local base="${found##*/}" local base="${found##*/}"
local soname="${base%.so*}" local soname="${base%.so*}"
for foundso in "${found%/*}/$soname".so*; do for foundso in "${found%/*}/$soname".so*; do
@ -76,7 +87,7 @@ findDependency() {
depCacheInitialised=1 depCacheInitialised=1
fi fi
for dep in "${cachedDependencies[@]}"; do for dep in "${autoPatchelfCachedDeps[@]}"; do
if [ "$filename" = "${dep##*/}" ]; then if [ "$filename" = "${dep##*/}" ]; then
if [ "$(getSoArch "$dep")" = "$arch" ]; then if [ "$(getSoArch "$dep")" = "$arch" ]; then
foundDependency="$dep" foundDependency="$dep"
@ -101,9 +112,10 @@ findDependency() {
autoPatchelfFile() { autoPatchelfFile() {
local dep rpath="" toPatch="$1" local dep rpath="" toPatch="$1"
local interpreter="$(< "$NIX_CC/nix-support/dynamic-linker")" local interpreter
interpreter="$(< "$NIX_CC/nix-support/dynamic-linker")"
if isExecutable "$toPatch"; then if isExecutable "$toPatch"; then
patchelf --set-interpreter "$interpreter" "$toPatch" runPatchelf --set-interpreter "$interpreter" "$toPatch"
if [ -n "$runtimeDependencies" ]; then if [ -n "$runtimeDependencies" ]; then
for dep in $runtimeDependencies; do for dep in $runtimeDependencies; do
rpath="$rpath${rpath:+:}$dep/lib" rpath="$rpath${rpath:+:}$dep/lib"
@ -115,9 +127,10 @@ autoPatchelfFile() {
# We're going to find all dependencies based on ldd output, so we need to # We're going to find all dependencies based on ldd output, so we need to
# clear the RPATH first. # clear the RPATH first.
patchelf --remove-rpath "$toPatch" runPatchelf --remove-rpath "$toPatch"
local missing="$( local missing
missing="$(
ldd "$toPatch" 2> /dev/null | \ ldd "$toPatch" 2> /dev/null | \
sed -n -e 's/^[\t ]*\([^ ]\+\) => not found.*/\1/p' sed -n -e 's/^[\t ]*\([^ ]\+\) => not found.*/\1/p'
)" )"
@ -125,7 +138,6 @@ autoPatchelfFile() {
# This ensures that we get the output of all missing dependencies instead # This ensures that we get the output of all missing dependencies instead
# of failing at the first one, because it's more useful when working on a # of failing at the first one, because it's more useful when working on a
# new package where you don't yet know its dependencies. # new package where you don't yet know its dependencies.
local -i depNotFound=0
for dep in $missing; do for dep in $missing; do
echo -n " $dep -> " >&2 echo -n " $dep -> " >&2
@ -134,18 +146,13 @@ autoPatchelfFile() {
echo "found: $foundDependency" >&2 echo "found: $foundDependency" >&2
else else
echo "not found!" >&2 echo "not found!" >&2
depNotFound=1 autoPatchelfFailedDeps["$dep"]="$toPatch"
fi fi
done 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 if [ -n "$rpath" ]; then
echo "setting RPATH to: $rpath" >&2 echo "setting RPATH to: $rpath" >&2
patchelf --set-rpath "$rpath" "$toPatch" runPatchelf --set-rpath "$rpath" "$toPatch"
fi fi
} }
@ -168,10 +175,10 @@ addAutoPatchelfSearchPath() {
esac esac
done done
cachedDependencies+=( for file in \
$(find "$@" "${findOpts[@]}" \! -type d \ $(find "$@" "${findOpts[@]}" \! -type d \
\( -name '*.so' -o -name '*.so.*' \)) \( -name '*.so' -o -name '*.so.*' \))
) do addToDepCache "$file"; done
} }
autoPatchelf() { autoPatchelf() {
@ -197,14 +204,9 @@ autoPatchelf() {
echo "automatically fixing dependencies for ELF files" >&2 echo "automatically fixing dependencies for ELF files" >&2
# Add all shared objects of the current output path to the start of # 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} -- "$@" 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 while IFS= read -r -d $'\0' file; do
isELF "$file" || continue isELF "$file" || continue
segmentHeaders="$(LANG=C $READELF -l "$file")" segmentHeaders="$(LANG=C $READELF -l "$file")"
@ -215,8 +217,24 @@ autoPatchelf() {
# Skip if the executable is statically linked. # Skip if the executable is statically linked.
[ -n "$(echo "$segmentHeaders" | grep "^ *INTERP\\>")" ] || continue [ -n "$(echo "$segmentHeaders" | grep "^ *INTERP\\>")" ] || continue
fi 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
autoPatchelfFile "$file" autoPatchelfFile "$file"
done < <(find "$@" ${norecurse:+-maxdepth 1} -type f -print0) 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 # XXX: This should ultimately use fixupOutputHooks but we currently don't have