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
|
#!/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))
|