File indexing completed on 2025-01-05 04:00:17
0001 #!/usr/bin/python 0002 0003 # Python script that takes an EXE file, automatically figures out all the DLL dependencies, 0004 # and copies them next to the EXE. 0005 # 0006 # SPDX-FileCopyrightText: 2015 by Martin Preisler <martin at preisler dot me> 0007 # SPDX-FileCopyrightText: 2016-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0008 # 0009 # Blog post : https://martin.preisler.me/2015/03/mingw-bundledlls-automatically-bundle-dlls/ 0010 # Github repository : https://github.com/mpreisler/mingw-bundledlls 0011 # 0012 # SPDX-License-Identifier: BSD-3-Clause 0013 0014 import subprocess 0015 import os.path 0016 import argparse 0017 import shutil 0018 import string 0019 0020 # ----------------------------------------------- 0021 0022 # Blacklist of native Windows dlls (may need extending) 0023 # Note : Lowercase file names. 0024 blacklist = [ 0025 "advapi32.dll", 0026 "kernel32.dll", 0027 "msvcrt.dll", 0028 "ole32.dll", 0029 "user32.dll", 0030 "ws2_32.dll", 0031 "comdlg32.dll", 0032 "gdi32.dll", 0033 "imm32.dll", 0034 "oleaut32.dll", 0035 "shell32.dll", 0036 "winmm.dll", 0037 "winspool.drv", 0038 "wldap32.dll", 0039 "ntdll.dll", 0040 "d3d9.dll", 0041 "mpr.dll", 0042 "crypt32.dll", 0043 "dnsapi.dll", 0044 "shlwapi.dll", 0045 "version.dll", 0046 "iphlpapi.dll", 0047 "msimg32.dll", 0048 "netapi32.dll", 0049 "userenv.dll", 0050 "opengl32.dll", 0051 "secur32.dll", 0052 "psapi.dll", 0053 "wsock32.dll", 0054 "setupapi.dll", 0055 "avicap32.dll", 0056 "avifil32.dll", 0057 "comctl32.dll", 0058 "msvfw32.dll", 0059 "shfolder.dll", 0060 "odbc32.dll", 0061 "dxva2.dll", 0062 "evr.dll", 0063 "mf.dll", 0064 "mfplat.dll", 0065 "glu32.dll", 0066 "dwmapi.dll", 0067 "uxtheme.dll", 0068 "bcrypt.dll", 0069 "wtsapi32.dll", 0070 "d2d1.dll", 0071 "d3d11.dll", 0072 "dxgi.dll", 0073 "dwrite.dll", 0074 "ncrypt.dll", 0075 "dbghelp.dll", # blacklisted dll from DrMinGW as it use MSVC dll to show debug dialog. 0076 "dbgcore.dll", # blacklisted dll from DrMinGW. 0077 "gdiplus.dll", 0078 "urlmon.dll", 0079 ] 0080 0081 # ----------------------------------------------- 0082 0083 def find_full_path(filename, path_prefixes): 0084 0085 path = None 0086 #print(filename) 0087 0088 for path_prefix in path_prefixes: 0089 path_candidate1 = os.path.join(path_prefix, filename) 0090 #print(path_candidate1) 0091 0092 if os.path.exists(path_candidate1): 0093 path = path_candidate1 0094 #print("Found") 0095 break 0096 0097 path_candidate2 = os.path.join(path_prefix, filename.lower()) 0098 #print(path_candidate2) 0099 0100 if os.path.exists(path_candidate2): 0101 path = path_candidate2 0102 #print("Found") 0103 break 0104 0105 if path is None: 0106 raise RuntimeError( 0107 "Can't find " + filename + ". If it is an inbuilt Windows DLL, " 0108 "please add it to the blacklist variable in this script.") 0109 0110 return path 0111 0112 # ----------------------------------------------- 0113 0114 def gather_deps(path, path_prefixes, seen): 0115 0116 ret = [path] 0117 output = subprocess.check_output(["objdump", "-p", path]).decode('utf-8').split("\n") 0118 0119 for line in output: 0120 0121 if not line.startswith("\tDLL Name: "): 0122 continue 0123 0124 dep = line.split("DLL Name: ")[1].strip() 0125 ldep = dep.lower() 0126 0127 #print("Searching: " + ldep) 0128 0129 if ldep in blacklist: 0130 continue 0131 0132 if ldep in seen: 0133 continue 0134 0135 dep_path = find_full_path(dep, path_prefixes) 0136 seen.extend([ldep]) 0137 subdeps = gather_deps(dep_path, path_prefixes, seen) 0138 0139 ret.extend(subdeps) 0140 0141 return ret 0142 0143 # ----------------------------------------------- 0144 0145 def main(): 0146 0147 parser = argparse.ArgumentParser() 0148 0149 parser.add_argument( 0150 "--installprefix", 0151 type = str, 0152 action = "store", 0153 help = "Install prefix path in build directory." 0154 ) 0155 0156 parser.add_argument( 0157 "--efile", 0158 type = str, 0159 action = "store", 0160 help = "EXE or DLL file that you need to bundle dependencies for" 0161 ) 0162 0163 parser.add_argument( 0164 "--odir", 0165 type = str, 0166 action = "store", 0167 help = "Directory to store found dlls" 0168 ) 0169 0170 parser.add_argument( 0171 "--copy", 0172 action = "store_true", 0173 help = "In addition to printing out the dependencies, also copy them next to the exe_file" 0174 ) 0175 0176 parser.add_argument( 0177 "--upx", 0178 action = "store_true", 0179 help = "Only valid if --copy is provided. Run UPX on all the DLLs and EXE. See https://en.wikipedia.org/wiki/UPX for details" 0180 ) 0181 0182 args = parser.parse_args() 0183 0184 if args.upx and not args.copy: 0185 raise RuntimeError("Can't run UPX if --copy hasn't been provided.") 0186 0187 print("Scan dependencies for " + args.efile) 0188 0189 # The mingw paths matches in MXE build directory 0190 default_path_prefixes = [ 0191 args.installprefix + "/qt5/bin/", 0192 args.installprefix + "/bin/", 0193 ] 0194 0195 all_deps = set(gather_deps(args.efile, default_path_prefixes, [])) 0196 all_deps.remove(args.efile) 0197 0198 #print("\n".join(all_deps)) 0199 0200 if args.copy: 0201 0202 #print("Copying enabled, will now copy recursively all dependencies near to the exe file.\n") 0203 0204 for dep in all_deps: 0205 target = os.path.join(args.odir, os.path.basename(dep)) 0206 0207 # Only copy target file to bundle only if it do not exists yet. 0208 if not os.path.exists(target): 0209 print("Copying '%s' to '%s'" % (dep, target)) 0210 shutil.copy(dep, args.odir) 0211 0212 if args.upx: 0213 subprocess.call(["upx", target]) 0214 0215 # ----------------------------------------------- 0216 0217 if __name__ == "__main__": 0218 main()