wireshark/tools/msys2checkdeps.py
João Valverde aa6b8368b7 MSYS2: Add support for building a stand-alone NSIS installer
This changes the existing code for the MSVC installer as little
as possible to allow building the Wireshark .exe Windows installer
using the MinGW-w64 toolchain.

Currently the DLL dependency list is static, this may change in
the future. Ideally we would use CPack and install() logic
to copy the DLLs.

The msys2checkdeps.py script is copied from the Inkscape project[1].
It doesn't have a specific license identifier. The Inkscape project
is licensed under the GPL version 2 or later.

TODO: Download Npcap and USBPcap using CMake instead of requiring
manual action.

[1]https://gitlab.com/inkscape/inkscape

Ping #17771.
2023-05-22 00:54:39 +01:00

178 lines
6.9 KiB
Python

#!/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
# ------------------------------------------------------------------------------------------------------------------
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
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)