LATER... ei_kerberos_kdc_session_key ...
[wireshark-sm.git] / tools / msys2checkdeps.py
blobf46eb503b47c25dcd6f03f003523425da90cf041
1 #!/usr/bin/env python
2 # ------------------------------------------------------------------------------------------------------------------
3 # list or check dependencies for binary distributions based on MSYS2 (requires the package mingw-w64-ntldd)
5 # run './msys2checkdeps.py --help' for usage information
6 # ------------------------------------------------------------------------------------------------------------------
8 # SPDX-License-Identifier: GPL-2.0-or-later
11 from __future__ import print_function
14 import argparse
15 import os
16 import subprocess
17 import sys
20 SYSTEMROOT = os.environ['SYSTEMROOT']
23 class Dependency:
24 def __init__(self):
25 self.location = None
26 self.dependents = set()
29 def warning(msg):
30 print("Warning: " + msg, file=sys.stderr)
33 def error(msg):
34 print("Error: " + msg, file=sys.stderr)
35 exit(1)
38 def call_ntldd(filename):
39 try:
40 output = subprocess.check_output(['ntldd', '-R', filename], stderr=subprocess.STDOUT)
41 except subprocess.CalledProcessError as e:
42 error("'ntldd' failed with '" + str(e) + "'")
43 except WindowsError as e:
44 error("Calling 'ntldd' failed with '" + str(e) + "' (have you installed 'mingw-w64-ntldd-git'?)")
45 except Exception as e:
46 error("Calling 'ntldd' failed with '" + str(e) + "'")
47 return output.decode('utf-8')
50 def get_dependencies(filename, deps):
51 raw_list = call_ntldd(filename)
53 skip_indent = float('Inf')
54 parents = {}
55 parents[0] = os.path.basename(filename)
56 for line in raw_list.splitlines():
57 line = line[1:]
58 indent = len(line) - len(line.lstrip())
59 if indent > skip_indent:
60 continue
61 else:
62 skip_indent = float('Inf')
64 # if the dependency is not found in the working directory ntldd tries to find it on the search path
65 # which is indicated by the string '=>' followed by the determined location or 'not found'
66 if ('=>' in line):
67 (lib, location) = line.lstrip().split(' => ')
68 if location == 'not found':
69 location = None
70 else:
71 location = location.rsplit('(', 1)[0].strip()
72 else:
73 lib = line.rsplit('(', 1)[0].strip()
74 location = os.getcwd()
76 parents[indent+1] = lib
78 # we don't care about Microsoft libraries and their dependencies
79 if location and SYSTEMROOT in location:
80 skip_indent = indent
81 continue
83 if lib not in deps:
84 deps[lib] = Dependency()
85 deps[lib].location = location
86 deps[lib].dependents.add(parents[indent])
87 return deps
90 def collect_dependencies(path):
91 # collect dependencies
92 # - each key in 'deps' will be the filename of a dependency
93 # - the corresponding value is an instance of class Dependency (containing full path and dependents)
94 deps = {}
95 if os.path.isfile(path):
96 deps = get_dependencies(path, deps)
97 elif os.path.isdir(path):
98 extensions = ['.exe', '.pyd', '.dll']
99 exclusions = ['distutils/command/wininst'] # python
100 for base, dirs, files in os.walk(path):
101 for f in files:
102 filepath = os.path.join(base, f)
103 (_, ext) = os.path.splitext(f)
104 if (ext.lower() not in extensions) or any(exclusion in filepath for exclusion in exclusions):
105 continue
106 deps = get_dependencies(filepath, deps)
107 return deps
110 if __name__ == '__main__':
111 modes = ['list', 'list-compact', 'check', 'check-missing', 'check-unused']
113 # parse arguments from command line
114 parser = argparse.ArgumentParser(description="List or check dependencies for binary distributions based on MSYS2.\n"
115 "(requires the package 'mingw-w64-ntldd')",
116 formatter_class=argparse.RawTextHelpFormatter)
117 parser.add_argument('mode', metavar="MODE", choices=modes,
118 help="One of the following:\n"
119 " list - list dependencies in human-readable form\n"
120 " with full path and list of dependents\n"
121 " list-compact - list dependencies in compact form (as a plain list of filenames)\n"
122 " check - check for missing or unused dependencies (see below for details)\n"
123 " check-missing - check if all required dependencies are present in PATH\n"
124 " exits with error code 2 if missing dependencies are found\n"
125 " and prints the list to stderr\n"
126 " check-unused - check if any of the libraries in the root of PATH are unused\n"
127 " and prints the list to stderr")
128 parser.add_argument('path', metavar='PATH',
129 help="full or relative path to a single file or a directory to work on\n"
130 "(directories will be checked recursively)")
131 parser.add_argument('-w', '--working-directory', metavar="DIR",
132 help="Use custom working directory (instead of 'dirname PATH')")
133 args = parser.parse_args()
135 # check if path exists
136 args.path = os.path.abspath(args.path)
137 if not os.path.exists(args.path):
138 error("Can't find file/folder '" + args.path + "'")
140 # get root and set it as working directory (unless one is explicitly specified)
141 if args.working_directory:
142 root = os.path.abspath(args.working_directory)
143 elif os.path.isdir(args.path):
144 root = args.path
145 elif os.path.isfile(args.path):
146 root = os.path.dirname(args.path)
147 os.chdir(root)
149 # get dependencies for path recursively
150 deps = collect_dependencies(args.path)
152 # print output / prepare exit code
153 exit_code = 0
154 for dep in sorted(deps):
155 location = deps[dep].location
156 dependents = deps[dep].dependents
158 if args.mode == 'list':
159 if (location is None):
160 location = '---MISSING---'
161 print(dep + " - " + location + " (" + ", ".join(dependents) + ")")
162 elif args.mode == 'list-compact':
163 print(dep)
164 elif args.mode in ['check', 'check-missing']:
165 if ((location is None) or (root not in os.path.abspath(location))):
166 warning("Missing dependency " + dep + " (" + ", ".join(dependents) + ")")
167 exit_code = 2
169 # check for unused libraries
170 if args.mode in ['check', 'check-unused']:
171 installed_libs = [file for file in os.listdir(root) if file.endswith(".dll")]
172 deps_lower = [dep.lower() for dep in deps]
173 top_level_libs = [lib for lib in installed_libs if lib.lower() not in deps_lower]
174 for top_level_lib in top_level_libs:
175 warning("Unused dependency " + top_level_lib)
177 exit(exit_code)