diff --git a/pkgs/build-support/setup-hooks/auto-patchelf.sh b/pkgs/build-support/setup-hooks/auto-patchelf.sh index 70b1fc802b565..d310f8255224f 100644 --- a/pkgs/build-support/setup-hooks/auto-patchelf.sh +++ b/pkgs/build-support/setup-hooks/auto-patchelf.sh @@ -38,7 +38,6 @@ isExecutable() { declare -Ag autoPatchelfCachedDepsAssoc declare -ag autoPatchelfCachedDeps - addToDepCache() { if [[ ${autoPatchelfCachedDepsAssoc[$1]+f} ]]; then return; fi @@ -54,25 +53,70 @@ declare -gi depCacheInitialised=0 declare -gi doneRecursiveSearch=0 declare -g foundDependency -getDepsFromSo() { - ldd "$1" 2> /dev/null | sed -n -e 's/[^=]*=> *\(.\+\) \+([^)]*)$/\1/p' +getDepsFromElfBinary() { + # NOTE: This does not use runPatchelf because it may encounter non-ELF + # files. Caller is expected to check the return code if needed. + patchelf --print-needed "$1" 2> /dev/null } -populateCacheWithRecursiveDeps() { - local so found foundso - for so in "${autoPatchelfCachedDeps[@]}"; do - for found in $(getDepsFromSo "$so"); do - local base="${found##*/}" - local soname="${base%.so*}" - for foundso in "${found%/*}/$soname".so*; do +getRpathFromElfBinary() { + # NOTE: This does not use runPatchelf because it may encounter non-ELF + # files. Caller is expected to check the return code if needed. + local rpath + rpath="$(patchelf --print-rpath "$1" 2> /dev/null)" || return $? + + local IFS=':' + printf "%s\n" $rpath +} + +populateCacheForDep() { + local so="$1" + local rpath found + rpath="$(getRpathFromElfBinary "$so")" || return 1 + + for found in $(getDepsFromElfBinary "$so"); do + local rpathElem + for rpathElem in $rpath; do + # Ignore empty element or $ORIGIN magic variable which should be + # deterministically resolved by adding this package's library + # files early anyway. + # + # shellcheck disable=SC2016 + # (Expressions don't expand in single quotes, use double quotes for + # that.) + if [[ -z "$rpathElem" || "$rpathElem" == *'$ORIGIN'* ]]; then + continue + fi + + local soname="${found%.so*}" + local foundso= + for foundso in "$rpathElem/$soname".so*; do addToDepCache "$foundso" done + + # Found in this element of the rpath, no need to check others. + if [ -n "$foundso" ]; then + break + fi done done + + # Not found in any rpath element. + return 1 +} + +populateCacheWithRecursiveDeps() { + # Dependencies may add more to the end of this array, so we use a counter + # with while instead of a regular for loop here. + local -i i=0 + while [ $i -lt ${#autoPatchelfCachedDeps[@]} ]; do + populateCacheForDep "${autoPatchelfCachedDeps[$i]}" + i=$i+1 + done } getSoArch() { - objdump -f "$1" | sed -ne 's/^architecture: *\([^,]\+\).*/\1/p' + $OBJDUMP -f "$1" | sed -ne 's/^architecture: *\([^,]\+\).*/\1/p' } # NOTE: If you want to use this function outside of the autoPatchelf function, @@ -130,25 +174,25 @@ autoPatchelfFile() { fi fi - echo "searching for dependencies of $toPatch" >&2 + local libcLib + libcLib="$(< "$NIX_CC/nix-support/orig-libc")/lib" - # We're going to find all dependencies based on ldd output, so we need to - # clear the RPATH first. - runPatchelf --remove-rpath "$toPatch" + echo "searching for dependencies of $toPatch" >&2 - # If the file is not a dynamic executable, ldd/sed will fail, - # in which case we return, since there is nothing left to do. local missing - missing="$( - ldd "$toPatch" 2> /dev/null | \ - sed -n -e 's/^[\t ]*\([^ ]\+\) => not found.*/\1/p' - )" || return 0 + missing="$(getDepsFromElfBinary "$toPatch")" || return 0 # 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 # new package where you don't yet know its dependencies. for dep in $missing; do + # Check whether this library exists in libc. If so, we don't need to do + # any futher searching -- it will be resolved correctly by the linker. + if [ -f "$libcLib/$dep" ]; then + continue + fi + echo -n " $dep -> " >&2 if findDependency "$dep" "$(getSoArch "$toPatch")"; then rpath="$rpath${rpath:+:}${foundDependency%/*}" @@ -185,7 +229,7 @@ addAutoPatchelfSearchPath() { done while IFS= read -r -d '' file; do - addToDepCache "$file" + addToDepCache "$file" done < <(find "$@" "${findOpts[@]}" \! -type d \ \( -name '*.so' -o -name '*.so.*' \) -print0) } @@ -221,10 +265,10 @@ autoPatchelf() { segmentHeaders="$(LANG=C $READELF -l "$file")" # Skip if the ELF file doesn't have segment headers (eg. object files). # not using grep -q, because it can cause Broken pipe - [ -n "$(echo "$segmentHeaders" | grep '^Program Headers:')" ] || continue + grep -q '^Program Headers:' <<<"$segmentHeaders" || continue if isExecutable "$file"; then # Skip if the executable is statically linked. - [ -n "$(echo "$segmentHeaders" | grep "^ *INTERP\\>")" ] || continue + grep -q "^ *INTERP\\>" <<<"$segmentHeaders" || continue fi # Jump file if patchelf is unable to parse it # Some programs contain binary blobs for testing, @@ -256,6 +300,9 @@ autoPatchelf() { # So what we do here is basically run in postFixup and emulate the same # behaviour as fixupOutputHooks because the setup hook for patchelf is run in # fixupOutput and the postFixup hook runs later. +# +# shellcheck disable=SC2016 +# (Expressions don't expand in single quotes, use double quotes for that.) postFixupHooks+=(' if [ -z "${dontAutoPatchelf-}" ]; then autoPatchelf -- $(for output in $outputs; do