summaryrefslogtreecommitdiffstats
path: root/buildtools/msys2checkdeps.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--buildtools/msys2checkdeps.py174
1 files changed, 174 insertions, 0 deletions
diff --git a/buildtools/msys2checkdeps.py b/buildtools/msys2checkdeps.py
new file mode 100644
index 0000000..31f7d5f
--- /dev/null
+++ b/buildtools/msys2checkdeps.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python
+# ------------------------------------------------------------------------------------------------------------------
+# list or check dependencies for binary distributions based on MSYS2 (requires the package mingw-w64-ntldd)
+#
+# run './msys2checkdeps.py --help' for usage information
+# ------------------------------------------------------------------------------------------------------------------
+
+from __future__ import print_function
+
+
+import argparse
+import os
+import subprocess
+import sys
+
+
+SYSTEMROOT = os.environ['SYSTEMROOT']
+
+
+class Dependency:
+ def __init__(self):
+ self.location = None
+ self.dependents = set()
+
+
+def warning(msg):
+ print("Warning: " + msg, file=sys.stderr)
+
+
+def error(msg):
+ print("Error: " + msg, file=sys.stderr)
+ exit(1)
+
+
+def call_ntldd(filename):
+ try:
+ output = subprocess.check_output(['ntldd', '-R', filename], stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ error("'ntldd' failed with '" + str(e) + "'")
+ except WindowsError as e:
+ error("Calling 'ntldd' failed with '" + str(e) + "' (have you installed 'mingw-w64-ntldd-git'?)")
+ except Exception as e:
+ error("Calling 'ntldd' failed with '" + str(e) + "'")
+ return output.decode('utf-8')
+
+
+def get_dependencies(filename, deps):
+ raw_list = call_ntldd(filename)
+
+ skip_indent = float('Inf')
+ parents = {}
+ parents[0] = os.path.basename(filename)
+ for line in raw_list.splitlines():
+ line = line[1:]
+ indent = len(line) - len(line.lstrip())
+ if indent > skip_indent:
+ continue
+ else:
+ skip_indent = float('Inf')
+
+ # if the dependency is not found in the working directory ntldd tries to find it on the search path
+ # which is indicated by the string '=>' followed by the determined location or 'not found'
+ if ('=>' in line):
+ (lib, location) = line.lstrip().split(' => ')
+ if location == 'not found':
+ location = None
+ else:
+ location = location.rsplit('(', 1)[0].strip()
+ else:
+ lib = line.rsplit('(', 1)[0].strip()
+ location = os.getcwd()
+
+ parents[indent+1] = lib
+
+ # we don't care about Microsoft libraries and their dependencies
+ if location and SYSTEMROOT in location:
+ skip_indent = indent
+ continue
+
+ if lib not in deps:
+ deps[lib] = Dependency()
+ deps[lib].location = location
+ deps[lib].dependents.add(parents[indent])
+ return deps
+
+
+def collect_dependencies(path):
+ # collect dependencies
+ # - each key in 'deps' will be the filename of a dependency
+ # - the corresponding value is an instance of class Dependency (containing full path and dependents)
+ deps = {}
+ if os.path.isfile(path):
+ deps = get_dependencies(path, deps)
+ elif os.path.isdir(path):
+ extensions = ['.exe', '.pyd', '.dll']
+ exclusions = ['distutils/command/wininst'] # python
+ for base, dirs, files in os.walk(path):
+ for f in files:
+ filepath = os.path.join(base, f)
+ (_, ext) = os.path.splitext(f)
+ if (ext.lower() not in extensions) or any(exclusion in filepath for exclusion in exclusions):
+ continue
+ deps = get_dependencies(filepath, deps)
+ return deps
+
+
+if __name__ == '__main__':
+ modes = ['list', 'list-compact', 'check', 'check-missing', 'check-unused']
+
+ # parse arguments from command line
+ parser = argparse.ArgumentParser(description="List or check dependencies for binary distributions based on MSYS2.\n"
+ "(requires the package 'mingw-w64-ntldd')",
+ formatter_class=argparse.RawTextHelpFormatter)
+ parser.add_argument('mode', metavar="MODE", choices=modes,
+ help="One of the following:\n"
+ " list - list dependencies in human-readable form\n"
+ " with full path and list of dependents\n"
+ " list-compact - list dependencies in compact form (as a plain list of filenames)\n"
+ " check - check for missing or unused dependencies (see below for details)\n"
+ " check-missing - check if all required dependencies are present in PATH\n"
+ " exits with error code 2 if missing dependencies are found\n"
+ " and prints the list to stderr\n"
+ " check-unused - check if any of the libraries in the root of PATH are unused\n"
+ " and prints the list to stderr")
+ parser.add_argument('path', metavar='PATH',
+ help="full or relative path to a single file or a directory to work on\n"
+ "(directories will be checked recursively)")
+ parser.add_argument('-w', '--working-directory', metavar="DIR",
+ help="Use custom working directory (instead of 'dirname PATH')")
+ args = parser.parse_args()
+
+ # check if path exists
+ args.path = os.path.abspath(args.path)
+ if not os.path.exists(args.path):
+ error("Can't find file/folder '" + args.path + "'")
+
+ # get root and set it as working directory (unless one is explicitly specified)
+ if args.working_directory:
+ root = os.path.abspath(args.working_directory)
+ elif os.path.isdir(args.path):
+ root = args.path
+ elif os.path.isfile(args.path):
+ root = os.path.dirname(args.path)
+ os.chdir(root)
+
+ # get dependencies for path recursively
+ deps = collect_dependencies(args.path)
+
+ # print output / prepare exit code
+ exit_code = 0
+ for dep in sorted(deps):
+ location = deps[dep].location
+ dependents = deps[dep].dependents
+
+ if args.mode == 'list':
+ if (location is None):
+ location = '---MISSING---'
+ print(dep + " - " + location + " (" + ", ".join(dependents) + ")")
+ elif args.mode == 'list-compact':
+ print(dep)
+ elif args.mode in ['check', 'check-missing']:
+ if ((location is None) or (root not in os.path.abspath(location))):
+ warning("Missing dependency " + dep + " (" + ", ".join(dependents) + ")")
+ exit_code = 2
+
+ # check for unused libraries
+ if args.mode in ['check', 'check-unused']:
+ installed_libs = [file for file in os.listdir(root) if file.endswith(".dll")]
+ deps_lower = [dep.lower() for dep in deps]
+ top_level_libs = [lib for lib in installed_libs if lib.lower() not in deps_lower]
+ for top_level_lib in top_level_libs:
+ warning("Unused dependency " + top_level_lib)
+
+ exit(exit_code)