-
Notifications
You must be signed in to change notification settings - Fork 383
/
appbundledeps.py
executable file
·134 lines (122 loc) · 3.96 KB
/
appbundledeps.py
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
#!/usr/bin/env python3
import re
import os
import sys
import subprocess
# EXEs to scan for dependencies
exepaths = [ ]
deps = { }
# look for argv
sit = iter(sys.argv)
next(sit) # toss argv[0]
class DepInfo:
slname = None
modpath = None
exepath = None
rpath = None
loaderpath = None # @loader_path
dependencies = None
def __init__(self,modpath=None,exepath=None,slname=None):
self.modpath = str(modpath)
self.slname = slname
self.loaderpath = None
self.rpath = [ ]
self.exepath = exepath;
if not modpath == None:
self.loaderpath = os.path.basename(modpath)
self.dependencies = [ ]
def __str__(self):
return "[modpath="+str(self.modpath)+",loaderpath="+str(self.loaderpath)+",exepath="+str(self.exepath)+"]"
def help():
print("appbundledeps.py --exe <exe>")
def GetDepList(exe,modpath=None,exepath=None):
rl = [ ]
rpath = [ ]
#
p = subprocess.Popen(["otool","-l",exe],stdout=subprocess.PIPE,encoding="utf8")
ldcmd = None
for lin in p.stdout:
lin = lin.strip().split(' ')
if len(lin) == 0:
continue
if lin[0] == "cmd" and len(lin) > 1:
ldcmd = lin[1]
if lin[0] == "path" and len(lin) > 1 and not lin[1] == "" and ldcmd == "LC_RPATH":
rpath.append(lin[1])
#
p = subprocess.Popen(["otool","-L",exe],stdout=subprocess.PIPE,encoding="utf8")
for lin in p.stdout:
if lin == None or lin == "":
continue
# we're looking for anything where the first char is tab
if not lin[0] == '\t':
continue
#
lin = lin.strip().split(' ')
if len(lin) == 0:
continue
deppath = lin[0].split('/')
#
if deppath[0] == "@rpath":
if len(rpath) > 0:
deppath = rpath[0].split('/') + deppath[1:]
#
if deppath[0] == "@loader_path":
deppath = os.path.dirname(modpath).split('/') + deppath[1:]
# dosbox-x refers to the name of the dylib symlink not the raw name, store that name!
slname = deppath[-1]
# NTS: Realpath is needed because Brew uses symlinks and .. rel path resolution will fail trying to access /opt/opt/...
if len(deppath[0]) > 0 and deppath[0][0] == "@":
deppath = '/'.join(deppath)
else:
deppath = os.path.realpath('/'.join(deppath))
if deppath == None:
raise Exception("Unable to resolve")
#
di = DepInfo(modpath=deppath,exepath=exepath,slname=slname)
di.rpath = rpath
rl.append(di)
#
p.terminate()
return rl
while True:
try:
n = next(sit)
if n == '--exe':
exepaths.append(os.path.realpath(next(sit)))
elif n == '-h' or n == '--help':
help()
sys.exit(1)
else:
print("Unknown switch "+n)
sys.exit(1)
except StopIteration:
break
if len(exepaths) == 0:
print("Must specify EXE")
sys.exit(1)
for exe in exepaths:
dl = GetDepList(exe,modpath=exe,exepath=exe)
for dep in dl:
if not dep.modpath in deps:
dep.dependencies = GetDepList(dep.modpath,modpath=dep.modpath,exepath=exe)
deps[dep.modpath] = dep
while True:
newdeps = 0
tdeps = deps.copy()
for deppath in tdeps:
depobj = deps[deppath]
for dep in depobj.dependencies:
if not dep.modpath in deps:
newdeps += 1
dep.dependencies = GetDepList(dep.modpath,modpath=dep.modpath,exepath=exe)
deps[dep.modpath] = dep
#
if newdeps == 0:
break
for deppath in deps:
# do not list /usr/lib or /System libraries, only /opt (Brew) dependencies
# TODO: Make an option to list them if wanted
if re.match(r"^/opt/",deppath) or re.match(r"^/usr/local/Cellar/",deppath):
depobj = deps[deppath]
print(str(deppath)+"\t"+str(depobj.slname))