1#!/usr/bin/env python3
2import os
3import sys
4import shutil
5import subprocess
6
7strip = False
8rpath = False
9no_copy = False
10# steam_runtime = os.getenv("STEAM_RUNTIME", "")
11steam_runtime = os.getenv("STEAMOS", "")
12
13
14def fix_binary(path):
15    changes = 0
16    if os.path.exists(path + ".standalone"):
17        return changes
18    if not os.path.exists(path):
19        raise Exception("could not find " + repr(path))
20
21    # find library locations
22    args = ["ldd", path]
23    p = subprocess.Popen(args, stdout=subprocess.PIPE)
24    # noinspection PyUnresolvedReferences
25    data = p.stdout.read().decode("UTF-8")
26    if p.wait() != 0:
27        return 0
28    print("fixing", path, "no_copy =", no_copy)
29    library_locations = {}
30    for line in data.split("\n"):
31        line = line.strip()
32        if "=>" not in line:
33            continue
34        library_locations[line.split(" ")[0]] = line.split(" ")[2]
35
36    # find direct dependencies
37    args = ["objdump", "-p", path]
38    p = subprocess.Popen(args, stdout=subprocess.PIPE)
39    # noinspection PyUnresolvedReferences
40    data = p.stdout.read().decode("UTF-8")
41    if p.wait() != 0:
42        return 0
43    for line in data.split("\n"):
44        line = line.strip()
45        # print(line)
46        if not line.startswith("NEEDED"):
47            continue
48        print(line)
49        library = line.split(" ")[-1]
50        print(library)
51        if ignore_library(library):
52            continue
53        library_source = library_locations[library]
54        library_source = os.path.normpath(library_source)
55        print(library, library_source)
56        # if steam_runtime and library_source.startswith(steam_runtime):
57        # if steam_runtime and not library_source.startswith("/usr/local"):
58        if steam_runtime and not library_source.startswith("/home"):
59            print("skipping steam runtime library")
60            continue
61        if no_copy:
62            print("no_copy is set")
63            continue
64        dst = os.path.join(os.path.dirname(path), library)
65        if not os.path.exists(dst):
66            print("copying", library)
67            shutil.copy(library_source, dst)
68            os.chmod(dst, 0o644)
69            changes += 1
70    if strip:
71        # strip does not work after patchelf has been run
72        command = "strip '{}'".format(path)
73        print(command)
74        os.system(command)
75    if rpath:
76        command = "patchelf --set-rpath '{}' '{}'".format(rpath, path)
77        print(command)
78        assert os.system(command) == 0
79    # to make sure strip is not run again
80    os.system("touch '{}.standalone'".format(path))
81    return changes
82
83
84def ignore_library(name):
85    if name.startswith("libgpg-error.so"):
86        raise Exception(
87            "Bundling libgpg-error (libgcrypt?) breaks Intel GL driver")
88
89    if name.startswith("linux-gate.so"):
90        return True
91    if name.startswith("linux-vdso.so"):
92        return True
93    if name.startswith("ld-linux.so.2"):
94        return True
95    if name.startswith("ld-linux-x86-64.so"):
96        return True
97
98    if name.startswith("libc.so"):
99        return True
100    if name.startswith("libstdc++.so"):
101        # Including libstdc++.sp breaks libGL loading with Intel on Ubuntu 16.10
102        # libGL error: unable to load driver: i965_dri.so
103        return True
104    if name.startswith("libgcc_s.so"):
105        # Might as well skip this one also, to avoid potential similar problems.
106        return True
107    if name.startswith("libpthread.so"):
108        return True
109    if name.startswith("libm.so"):
110        return True
111    if name.startswith("libdl.so"):
112        return True
113    if name.startswith("libresolv.so"):
114        return True
115    if name.startswith("librt.so"):
116        return True
117    if name.startswith("libutil.so"):
118        return True
119    # if name.startswith("libpcre.so"):
120    #     # Problem with OpenAL on Ubuntu 16.04 if this is included.
121    #     return True
122
123    if name.startswith("libGL.so"):
124        return True
125    if name.startswith("libGLU.so"):
126        return True
127    if name.startswith("libEGL.so"):
128        return True
129
130    if name.startswith("libasound.so"):
131        # Alsa library is in LSB, looks like only "old" interfaces are used
132        # by SDL2.
133        return True
134
135    if name.startswith("libX11.so"):
136        return True
137    if name.startswith("libXext.so"):
138        return True
139    if name.startswith("libXcursor.so"):
140        return True
141    if name.startswith("libXinerama.so"):
142        return True
143    if name.startswith("libXi.so"):
144        return True
145    if name.startswith("libXrandr.so"):
146        return True
147    if name.startswith("libXss.so"):
148        return True
149    if name.startswith("libXxf86vm.so"):
150        return True
151    # if name.startswith("libxkbcommon.so"):
152    #     return True
153    if name.startswith("libxcb.so"):
154        return True
155
156    return False
157
158
159def fix_iteration(app):
160    binaries = []
161    binaries_dir = app
162    for name in os.listdir(binaries_dir):
163        binaries.append(os.path.join(binaries_dir, name))
164    changes = 0
165    for binary in binaries:
166        changes += fix_binary(binary)
167    return changes
168
169
170def main():
171    global no_copy, strip, rpath
172    for arg in list(sys.argv):
173        if arg.startswith("--rpath="):
174            sys.argv.remove(arg)
175            rpath = arg[8:]
176        elif arg == "--no-copy":
177            sys.argv.remove("--no-copy")
178            no_copy = True
179        elif arg == "--strip":
180            sys.argv.remove("--strip")
181            strip = True
182    app = sys.argv[1]
183    while True:
184        changes = fix_iteration(app)
185        if changes == 0:
186            break
187    for name in os.listdir(app):
188        if name.endswith(".standalone"):
189            os.remove(os.path.join(app, name))
190
191
192if __name__ == "__main__":
193    main()
194