I did such a script (find_dependencies.py) a couple of years back in order to do exactly what you want to do, i.e., find the dependencies of Graphviz executables and DLLs. I used it to get the dependencies in the CMakeLists.txt right. I hope it still works.
Unfortunately, the forum prohibits me from attaching the file, but here’s the code:
#!/usr/bin/env python3
"""Find Windows DLL dependencies"""
import argparse
import os
import pathlib
import re
import shlex
import subprocess
import sys
from typing import List
dumpbin = "C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Tools/MSVC/14.42.34433/bin/Hostx64/x64/dumpbin.exe"
#
assert os.path.exists(dumpbin)
def run(args: List[str]):
p = subprocess.run(
args,
stdout=subprocess.PIPE,
universal_newlines=True,
)
if (p.returncode != 0):
print(p.stdout)
print(p.stderr, file=sys.stderr)
sys.exit(p.returncode)
return p.stdout
def determine_architecture(file):
if not file.exists():
print(f"Error: File not found: {file}", file=sys.stderr)
sys.exit(1)
dumpbin_output = run([dumpbin, "-headers", file])
for line in dumpbin_output.split("\n"):
if "10B magic # (PE32)" in line:
return "x86"
if "20B magic # (PE32+)" in line:
return "x64"
return None
def get_architecture(files):
architecture_candidates = list(set([determine_architecture(pathlib.Path(file)) for file in files]))
if len(architecture_candidates) > 1:
print(f"Error: Ambiguous architecture: {' or '.join(architecture_candidates)}", file=sys.stderr)
sys.exit(1)
architecture = architecture_candidates[0]
if architecture is None:
print(f"Error: Could not determine architecture")
sys.exit(1)
assert architecture in ["x86", "x64"]
return architecture
def get_search_directories(files, architecture):
search_directories = list(set([pathlib.Path(file).parent for file in files]))
# note that the system directory names are very counterintuitive
windows_directory = pathlib.Path("C:/Windows")
windows_system_directory_name = "SysWOW64" if architecture == "x86" else "SYSTEM32"
windows_system_directory = windows_directory / windows_system_directory_name
program_files_directory = pathlib.Path("C:/Program Files (x86)" if architecture == "x86" else "C:/Program Files")
visual_studio_community_directory = program_files_directory / "Microsoft Visual Studio/2022/Community"
visual_studio_buildtools_directory = program_files_directory / "Microsoft Visual Studio/2022/BuildTools"
visual_studio_community_ide_directory = visual_studio_community_directory / "Common7/IDE"
visual_studio_community_ide2_directory = visual_studio_buildtools_directory / "Common7/IDE"
visual_studio_community_llvm_directory = visual_studio_community_directory / "VC/Tools/Llvm" / architecture / "bin"
visual_studio_community_llvm2_directory = visual_studio_buildtools_directory / "VC/Tools/Llvm" / "bin"
# visual_studio_community_clang_directory = visual_studio_buildtools_directory / "VC/Tools/MSVC/14.42.34433/bin/Hostx64/x64"
visual_studio_community_clang_directory = pathlib.Path("C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Tools/MSVC/14.42.34433/bin/Hostx86/x64")
assert os.path.exists(visual_studio_community_clang_directory / "clang_rt.asan_dynamic-x86_64.dll")
windows_directories = [
windows_system_directory,
visual_studio_community_ide_directory,
visual_studio_community_ide2_directory,
visual_studio_community_llvm_directory,
visual_studio_community_llvm2_directory,
visual_studio_community_clang_directory,
]
search_directories += windows_directories
search_directories += "C:/Users/magjac/graphviz/build/_CPack_Packages/win64/NSIS/Graphviz-13.0.0~dev.20250106.1542-win64/bin"
return search_directories
def create_binary(name, path):
return {
"name": name,
"path": path,
"dependencies": [],
}
def add_binary_and_dependencies(name, path, binaries, search_directories, skip_found_system_binaries):
if name not in binaries:
binaries[name] = create_binary(name, path)
binary = binaries[name]
if path is None:
return binary
if "C:\\Program Files" in str(path):
return binary
if "C:\\Windows" in str(path):
return binary
dependencies = binary["dependencies"]
dumpbin_output = run([dumpbin, "-dependents", path])
for line in dumpbin_output.split("\n"):
mo = re.match(r" *([^ ]*\.dll)", line)
if mo:
dependency_name = mo.group(1)
if dependency_name in binaries:
dependency_binary = binaries[dependency_name]
else:
dependency_path = locate_dependency(dependency_name, search_directories)
if skip_found_system_binaries:
if "C:\\Program Files" in str(dependency_path):
continue
if "C:\\Windows" in str(dependency_path):
continue
dependency_binary = add_binary_and_dependencies(dependency_name, dependency_path, binaries, search_directories, skip_found_system_binaries)
dependencies.append(dependency_binary)
return binary
def locate_dependency(name, search_directories):
for directory in search_directories:
path = pathlib.Path(directory) / name
if path.exists():
return path
return None
def generate_dot_src(binaries):
dot_src = ""
dot_src += "digraph {\n"
dot_src += " node [shape=box style=filled colorscheme=set312]\n"
for binary in binaries.values():
color = 4 if binary["path"] is None else 7
dot_src += f'"{binary["name"]}" [color={color}]\n'
for dependency_binary in binary["dependencies"]:
dot_src += f'"{binary["name"]}" -> "{dependency_binary["name"]}"\n'
dot_src += "}\n"
return dot_src
def print_dependency_hierarchy(binary, missing_only=False, parents=""):
if parents:
hierarchy = parents + "->"
else:
hierarchy = ""
hierarchy += binary["name"]
dependency_binaries = binary["dependencies"]
path = binary["path"]
if len(dependency_binaries) == 0:
if not missing_only or path is None:
print(hierarchy)
else:
for dependency_binary in dependency_binaries:
print_dependency_hierarchy(dependency_binary, missing_only, hierarchy)
def main(args: List[str]) -> int: # pylint: disable=C0116
parser = argparse.ArgumentParser(
description="Find Windows DLL dependencies"
)
parser.add_argument("files", nargs="+", help="Binaries or DLLs to search for dependencies")
parser.add_argument(
"--generate-dot", "-g", action="store_true", help="Generate Graphviz DOT source"
)
parser.add_argument(
"--print-dependency-hierarchy", "-d", action="store_true", help="Print dependency hierarchy"
)
parser.add_argument(
"--missing-only", "-m", action="store_true", help="Print missing binaries only"
)
parser.add_argument(
"--skip-found-system-binaries", "-s", action="store_true", help="Don't print found system binaries"
)
options = parser.parse_args(args[1:])
architecture = get_architecture(options.files)
search_directories = get_search_directories(options.files, architecture)
binaries = {}
for path in options.files:
name = pathlib.Path(path).name
binaries[name] = create_binary(name, path)
for path in options.files:
name = pathlib.Path(path).name
add_binary_and_dependencies(name, path, binaries, search_directories, options.skip_found_system_binaries)
if options.generate_dot:
dot_src = generate_dot_src(binaries)
print(dot_src)
return 0
if options.print_dependency_hierarchy:
for path in options.files:
name = pathlib.Path(path).name
binary = binaries[name]
print_dependency_hierarchy(binary, options.missing_only)
return 0
for name, binary in binaries.items():
path = binary["path"]
if not options.missing_only or path is None:
print(f'{name:40}{binary["path"]}')
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))