summary refs log tree commit diff
path: root/pkgs/build-support/setup-hooks/auto-patchelf.sh
blob: 7c165627f72e1025d1f353044127e17cb5e03be4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
declare -a autoPatchelfLibs

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

addEnvHooks "$targetOffset" gatherLibraries

isExecutable() {
    readelf -h "$1" 2> /dev/null | grep -q '^ *Type: *EXEC\>'
}

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

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

declare -gi depCacheInitialised=0
declare -gi doneRecursiveSearch=0
declare -g foundDependency

getDepsFromSo() {
    ldd "$1" 2> /dev/null | sed -n -e 's/[^=]*=> *\(.\+\) \+([^)]*)$/\1/p'
}

populateCacheWithRecursiveDeps() {
    local so found foundso
    for so in "${cachedDependencies[@]}"; do
        for found in $(getDepsFromSo "$so"); do
            local libdir="${found%/*}"
            local base="${found##*/}"
            local soname="${base%.so*}"
            for foundso in "${found%/*}/$soname".so*; do
                addToDepCache "$foundso"
            done
        done
    done
}

getSoArch() {
    objdump -f "$1" | sed -ne 's/^architecture: *\([^,]\+\).*/\1/p'
}

# NOTE: If you want to use this function outside of the autoPatchelf function,
# keep in mind that the dependency cache is only valid inside the subshell
# spawned by the autoPatchelf function, so invoking this directly will possibly
# rebuild the dependency cache. See the autoPatchelf function below for more
# information.
findDependency() {
    local filename="$1"
    local arch="$2"
    local lib dep

    if [ $depCacheInitialised -eq 0 ]; then
        for lib in "${autoPatchelfLibs[@]}"; do
            for so in "$lib/"*.so*; do addToDepCache "$so"; done
        done
        depCacheInitialised=1
    fi

    for dep in "${cachedDependencies[@]}"; do
        if [ "$filename" = "${dep##*/}" ]; then
            if [ "$(getSoArch "$dep")" = "$arch" ]; then
                foundDependency="$dep"
                return 0
            fi
        fi
    done

    # Populate the dependency cache with recursive dependencies *only* if we
    # didn't find the right dependency so far and afterwards run findDependency
    # again, but this time with $doneRecursiveSearch set to 1 so that it won't
    # recurse again (and thus infinitely).
    if [ $doneRecursiveSearch -eq 0 ]; then
        populateCacheWithRecursiveDeps
        doneRecursiveSearch=1
        findDependency "$filename" "$arch" || return 1
        return 0
    fi
    return 1
}

autoPatchelfFile() {
    local dep rpath="" toPatch="$1"

    local interpreter="$(< "$NIX_CC/nix-support/dynamic-linker")"
    if isExecutable "$toPatch"; then
        patchelf --set-interpreter "$interpreter" "$toPatch"
        if [ -n "$runtimeDependencies" ]; then
            for dep in $runtimeDependencies; do
                rpath="$rpath${rpath:+:}$dep/lib"
            done
        fi
    fi

    echo "searching for dependencies of $toPatch" >&2

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

    local missing="$(
        ldd "$toPatch" 2> /dev/null | \
            sed -n -e 's/^[\t ]*\([^ ]\+\) => not found.*/\1/p'
    )"

    # 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.
    local -i depNotFound=0

    for dep in $missing; do
        echo -n "  $dep -> " >&2
        if findDependency "$dep" "$(getSoArch "$toPatch")"; then
            rpath="$rpath${rpath:+:}${foundDependency%/*}"
            echo "found: $foundDependency" >&2
        else
            echo "not found!" >&2
            depNotFound=1
        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 ]

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

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.
    cachedDependencies+=(
        $(find "$prefix" \! -type d \( -name '*.so' -o -name '*.so.*' \))
    )
    local elffile

    # 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
      if isExecutable "$file"; then
          # Skip if the executable is statically linked.
          readelf -l "$file" | grep -q "^ *INTERP\\>" || continue
      fi
      autoPatchelfFile "$file"
    done < <(find "$prefix" -type f -print0)
}

# XXX: This should ultimately use fixupOutputHooks but we currently don't have
# a way to enforce the order. If we have $runtimeDependencies set, the setup
# hook of patchelf is going to ruin everything and strip out those additional
# RPATHs.
#
# 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.
postFixupHooks+=(
    'for output in $outputs; do prefix="${!output}" autoPatchelf; done'
)