File: appbundledeps.py

package info (click to toggle)
dosbox-x 2025.12.01%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 53,224 kB
  • sloc: cpp: 339,768; ansic: 165,257; sh: 1,455; makefile: 963; perl: 385; python: 106; asm: 57
file content (134 lines) | stat: -rwxr-xr-x 4,053 bytes parent folder | download | duplicates (2)
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))