1# -*- coding: utf-8 -*-
2# Copyright 2016 Christoph Reiter
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8
9"""
10Deletes unneeded DLLs and checks DLL dependencies.
11Execute with the build python, will figure out the rest.
12"""
13
14import subprocess
15import os
16import sys
17
18import gi
19gi.require_version("GIRepository", "2.0")
20from gi.repository import GIRepository
21
22
23def get_required_by_typelibs():
24    deps = set()
25    repo = GIRepository.Repository()
26    for tl in os.listdir(repo.get_search_path()[0]):
27        namespace, version = os.path.splitext(tl)[0].split("-", 1)
28        repo.require(namespace, version, 0)
29        lib = repo.get_shared_library(namespace)
30        if lib:
31            deps.update(lib.split(","))
32    return deps
33
34
35EXTENSIONS = [".exe", ".pyd", ".dll"]
36SYSTEM_LIBS = ['advapi32.dll',
37    "cabinet.dll", "comctl32.dll", "comdlg32.dll", "crypt32.dll", "d3d9.dll",
38    "dnsapi.dll", "dsound.dll", "dwmapi.dll", "gdi32.dll", "imm32.dll",
39    "iphlpapi.dll", "kernel32.dll", "ksuser.dll", "msi.dll", "msimg32.dll",
40    "msvcr71.dll", "msvcr80.dll", "msvcrt.dll", "ole32.dll", "oleaut32.dll",
41    "opengl32.dll", "rpcrt4.dll", "setupapi.dll", "shell32.dll", "user32.dll",
42    "usp10.dll", "winmm.dll", "winspool.drv", "wldap32.dll", "ws2_32.dll",
43    "wsock32.dll", "shlwapi.dll"
44]
45
46
47def get_dependencies(filename):
48    deps = []
49    try:
50        data = subprocess.getoutput("objdump -p %s" % filename)
51    except Exception as error:
52        print(error)
53        return deps
54
55    for line in data.splitlines():
56        line = line.strip()
57        if line.startswith("DLL Name:"):
58            deps.append(line.split(":", 1)[-1].strip().lower())
59    return deps
60
61
62def find_lib(root, name):
63    search_path = os.path.join(root, "bin")
64    if os.path.exists(os.path.join(search_path, name)):
65        return os.path.join(search_path, name)
66    elif name in SYSTEM_LIBS:
67        return name
68
69
70def get_things_to_delete(root):
71    all_libs = set()
72    needed = set()
73    for base, dirs, files in os.walk(root):
74        for f in files:
75            path = os.path.join(base, f)
76            if os.path.splitext(path)[-1].lower() in EXTENSIONS:
77                all_libs.add(f.lower())
78                for lib in get_dependencies(path):
79                    all_libs.add(lib)
80                    needed.add(lib)
81                    if not find_lib(root, lib):
82                        print("MISSING:", path, lib)
83
84    for lib in get_required_by_typelibs():
85        needed.add(lib)
86        if not find_lib(root, lib):
87            print("MISSING:", path, lib)
88
89    # get rid of things not in the search path,
90    # maybe loaded through other means?
91    not_needed = filter(
92        lambda l: find_lib(root, l) and \
93            os.path.splitext(l)[-1].lower() != ".exe", all_libs - needed)
94
95    return [find_lib(root, l) for l in not_needed]
96
97
98def main():
99    libs = get_things_to_delete(sys.prefix)
100    while libs:
101        for l in libs:
102            print("DELETE:", l)
103            os.unlink(l)
104        libs = get_things_to_delete(sys.prefix)
105
106
107if __name__ == "__main__":
108    main()
109