-
Notifications
You must be signed in to change notification settings - Fork 7
/
make_portable.sh
executable file
·112 lines (98 loc) · 3.38 KB
/
make_portable.sh
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
#!/usr/bin/env bash
# Example input:
# ./make_portable.sh mycoolbinary
# Or:
# USE_RPATH=1 ./make_portable.sh mycoolbinary
#
# where mycoolbinary is a mach-o object file
# (for example an executable binary or a .dylib)
#
# this script rewrites your file's every environment-specific
# dynamic link (recursively!)
# such that they point to local .dylibs.
# these .dylibs are then copied to a folder lib, next to your binary
#
# by "environment-specific" I mean any link to a .dylib under /usr/local
set -o pipefail
error() {
local parent_lineno="$1"
local message="$2"
local code="${3:-1}"
if [[ -n "$message" ]] ; then
echo "Error on or near line ${parent_lineno}: ${message}; exiting with status ${code}"
else
echo "Error on or near line ${parent_lineno}; exiting with status ${code}"
fi
exit "${code}"
}
trap 'error ${LINENO}' ERR
BINARY="$1"
BINARYDIR=$(dirname "$BINARY")
LIBDIRNAME=${LIBDIRNAME:-lib}
LIBOUTPUTDIR="$BINARYDIR/$LIBDIRNAME"
# assume that our binary is running from:
# CoolBinary.app/Contents/MacOS/coolbinary
# and wants to navigate to:
# CoolBinary.app/Contents/lib/coollibrary.dylib
RPATH_FALLBACK="@loader_path/../$LIBDIRNAME"
if [ -z ${USE_RPATH+x} ]; then
# don't use rpath
OBJLOADREL="$RPATH_FALLBACK"
else
OBJLOADREL="@rpath"
# define in our binary what it should expand the
# runtime search path @rpath to
install_name_tool -add_rpath "$RPATH_FALLBACK" "$BINARY"
fi
# make a lib folder
mkdir -p "$LIBOUTPUTDIR"
# find every LC_LOAD_DYLIB command in the obj file
# filter to just loads under /usr/local
# print the absolute path of each such dylib
get_env_specific_direct_dependencies () {
# otool -L shows us every LC_LOAD_DYLIB plus LC_ID_DYLIB
# otool -D shows us just LC_ID_DYLIB
ALL_DYLIBS=$(otool -L "$1" | awk 'NR>1')
DYLIB_ID=$(otool -D "$1" | awk 'NR>1')
if [ -z "$DYLIB_ID" ]; then
DIRECT_DEPS="$ALL_DYLIBS"
else
DIRECT_DEPS=$(echo "$ALL_DYLIBS" | grep -v "$DYLIB_ID")
fi
echo "$DIRECT_DEPS" \
| awk '/\/usr\/local\//,/.dylib/ {print $1}'
}
# lookup LC_LOAD_DYLIB commands in an obj file,
# then follow those loads and ask the same of each
# of its dylibs, recursively
get_env_specific_dependencies_recursive () {
while read -r obj; do
[ -z "$obj" ] && continue
echo "$obj"
get_env_specific_dependencies_recursive "$obj"
done < <(get_env_specific_direct_dependencies "$1")
}
DEP_PATHS=$(get_env_specific_dependencies_recursive "$BINARY")
#mkdir -p "$LIBOUTPUTDIR"
# copy each distinct dylib in the dependency tree into our lib folder
echo "$DEP_PATHS" \
| xargs -n1 realpath \
| sort \
| uniq \
| xargs -I'{}' cp {} "$LIBOUTPUTDIR/"
chmod +w "$BINARY" "$LIBOUTPUTDIR"/*.dylib
while read -r obj; do
[ -z "$obj" ] && continue
OBJ_LEAF_NAME=$(echo "$obj" | awk -F'/' '{print $NF}')
# rewrite the install name of this obj file. completely optional.
# provides good default for future people who link to it.
install_name_tool -id "$OBJLOADREL/$OBJ_LEAF_NAME" "$obj"
# iterate over every LC_LOAD_DYLIB command in the objfile
while read -r load; do
[ -z "$load" ] && continue
LOAD_LEAF_NAME=$(echo "$load" | awk -F'/' '{print $NF}')
# rewrite a LC_LOAD_DYLIB command in this obj file
# to point relative to @rpath
install_name_tool -change "$load" "$OBJLOADREL/$LOAD_LEAF_NAME" "$obj"
done < <(get_env_specific_direct_dependencies "$obj")
done < <(cat <(echo "$BINARY") <(echo "$DEP_PATHS" | awk -F'/' -v l="$LIBOUTPUTDIR" -v OFS='/' '{print l,$NF}'))