win-depends 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: win_depends
3
+ Version: 0.1.0
4
+ Summary: A tool to analyze Windows DLL/EXE dependencies
5
+ Author: Vorga
6
+ Author-email: Your Name <you@example.com>
7
+ License: MIT
8
+ Requires-Python: >=3.6
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: pefile
11
+ Requires-Dist: tqdm
12
+ Dynamic: author
13
+ Dynamic: requires-python
14
+
15
+ # depends
16
+
17
+ 🧩 Windows DLL/EXE Dependency Tree Analyzer using `dumpbin`.
18
+
19
+ This tool analyzes dependencies of `.dll` or `.exe` files on Windows, builds a dependency tree, and reports missing dependencies. It uses `dumpbin.exe` from Visual Studio toolchains.
20
+
21
+ ## πŸ”§ Features
22
+
23
+ - Automatically finds `dumpbin.exe`
24
+ - Supports both single file and directory analysis
25
+ - Outputs:
26
+ - Tree structure with optional max depth
27
+ - DOT graph format for Graphviz
28
+ - Missing dependency check
29
+ - Optional inclusion of system DLLs
30
+ - Threaded for performance
31
+
32
+ ## πŸ“¦ Installation
33
+
34
+ ```bash
35
+ pip install .
@@ -0,0 +1,21 @@
1
+ # depends
2
+
3
+ 🧩 Windows DLL/EXE Dependency Tree Analyzer using `dumpbin`.
4
+
5
+ This tool analyzes dependencies of `.dll` or `.exe` files on Windows, builds a dependency tree, and reports missing dependencies. It uses `dumpbin.exe` from Visual Studio toolchains.
6
+
7
+ ## πŸ”§ Features
8
+
9
+ - Automatically finds `dumpbin.exe`
10
+ - Supports both single file and directory analysis
11
+ - Outputs:
12
+ - Tree structure with optional max depth
13
+ - DOT graph format for Graphviz
14
+ - Missing dependency check
15
+ - Optional inclusion of system DLLs
16
+ - Threaded for performance
17
+
18
+ ## πŸ“¦ Installation
19
+
20
+ ```bash
21
+ pip install .
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "win_depends"
7
+ version = "0.1.0"
8
+ description = "A tool to analyze Windows DLL/EXE dependencies"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Your Name", email = "you@example.com"}
14
+ ]
15
+ dependencies = [
16
+ "pefile",
17
+ "tqdm"
18
+ ]
19
+
20
+ [project.scripts]
21
+ win_depends = "win_depends.cli:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="win_depends",
5
+ version="0.1.0",
6
+ description="Windows DLL/EXE dependency tree and graph tool",
7
+ author="Vorga",
8
+ license="MIT",
9
+ packages=find_packages(),
10
+ entry_points={
11
+ "console_scripts": [
12
+ "win_depends=win_depends.cli:main"
13
+ ],
14
+ },
15
+ python_requires=">=3.6",
16
+ install_requires=[
17
+ "tqdm>=4.64.1", # θΏ›εΊ¦ζ‘εΊ“οΌˆε―ι€‰οΌ‰
18
+ "pefile>=2024.5.17", # PEζ–‡δ»Άθ§£ζžεΊ“
19
+ ],
20
+ )
File without changes
@@ -0,0 +1,77 @@
1
+ import argparse
2
+ import os
3
+ import sys
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ from .core import (
6
+ build_dependency_map_for_dir,
7
+ build_dependency_map_for_file,
8
+ report_missing_files,
9
+ report_unused_files,
10
+ print_tree,
11
+ export_dot
12
+ )
13
+
14
+ def main():
15
+ parser = argparse.ArgumentParser(description="Analyze DLL/EXE dependencies.")
16
+ parser.add_argument("target", help="Target DLL/EXE file or directory.")
17
+ parser.add_argument("--tree", nargs="?", const=-1, type=int, metavar="LEVEL",
18
+ help="Print dependency tree up to specified depth (default: unlimited).")
19
+ parser.add_argument("--graph", action="store_true", help="Print dependency graph in DOT format.")
20
+ parser.add_argument("--dot", metavar="FILENAME", help="Write DOT graph to file.")
21
+ parser.add_argument("--check-missing", action="store_true", help="Check for missing dependencies.")
22
+ parser.add_argument("--check-unused", action="store_true", help="Check for unused local DLL/EXE files.")
23
+ parser.add_argument("--detect-system", action="store_true",
24
+ help="Analyze system DLLs (default: off; only analyzes files in current directory).")
25
+
26
+ args = parser.parse_args()
27
+
28
+ target = args.target
29
+ is_dir = os.path.isdir(target)
30
+ is_file = os.path.isfile(target)
31
+
32
+ if not is_dir and not (is_file and target.lower().endswith((".dll", ".exe"))):
33
+ print(f"Error: '{target}' is not a valid DLL/EXE file or directory.")
34
+ sys.exit(1)
35
+
36
+ base_dir = target if is_dir else os.path.dirname(os.path.abspath(target))
37
+ file_set = {
38
+ f for f in os.listdir(base_dir)
39
+ if f.lower().endswith((".dll", ".exe")) and os.path.isfile(os.path.join(base_dir, f))
40
+ }
41
+
42
+ ignore_system = not args.detect_system
43
+
44
+ with ThreadPoolExecutor(max_workers=8) as executor:
45
+ if is_dir:
46
+ dep_map = build_dependency_map_for_dir(base_dir, file_set, executor,
47
+ ignore_system=ignore_system,
48
+ detect_system=args.detect_system)
49
+ else:
50
+ dep_map = build_dependency_map_for_file(target, base_dir, file_set, executor,
51
+ ignore_system=ignore_system,
52
+ detect_system=args.detect_system)
53
+
54
+ print(f"Analyzing dependencies in: {base_dir}")
55
+
56
+ missing = set()
57
+ if args.check_missing or args.tree is not None:
58
+ missing = report_missing_files(dep_map, base_dir, local_file_set=file_set, detect_system=args.detect_system)
59
+
60
+ if args.check_unused:
61
+ report_unused_files(dep_map, base_dir)
62
+
63
+ if args.tree is not None:
64
+ max_depth = None if args.tree == -1 else args.tree
65
+ print_tree(dep_map, max_depth=max_depth, missing_set=missing, local_file_set=file_set)
66
+
67
+ elif args.graph:
68
+ dot = export_dot(dep_map)
69
+ if args.dot:
70
+ with open(args.dot, "w", encoding="utf-8") as f:
71
+ f.write(dot)
72
+ print(f"DOT graph written to: {args.dot}")
73
+ else:
74
+ print(dot)
75
+
76
+ if __name__ == "__main__":
77
+ main()
@@ -0,0 +1,175 @@
1
+ import os
2
+ import shutil
3
+ from collections import defaultdict
4
+ from tqdm import tqdm
5
+ import concurrent.futures
6
+ import pefile
7
+ from functools import lru_cache
8
+
9
+ def get_env_paths():
10
+ paths = set()
11
+ for p in os.environ.get("PATH", "").split(os.pathsep):
12
+ if os.path.isdir(p):
13
+ paths.add(os.path.normcase(os.path.abspath(p)))
14
+ return paths
15
+
16
+ def is_valid_dependency(filename):
17
+ return filename.lower().endswith(".dll") or filename.lower().endswith(".exe")
18
+
19
+ def is_system_dll(filename):
20
+ name = filename.lower()
21
+ for path in get_env_paths():
22
+ candidate = os.path.join(path, name)
23
+ if os.path.isfile(candidate):
24
+ return True
25
+ return False
26
+
27
+ @lru_cache(maxsize=2048)
28
+ def get_cached_dependents(file_path, ignore_system, local_file_set_key, detect_system):
29
+ return tuple(get_dependents_uncached(file_path, ignore_system, local_file_set_key, detect_system))
30
+
31
+ def get_dependents_uncached(file_path, ignore_system, local_file_set_key, detect_system):
32
+ try:
33
+ pe = pefile.PE(file_path, fast_load=True)
34
+ pe.parse_data_directories()
35
+ deps = set()
36
+ for attr in ['DIRECTORY_ENTRY_IMPORT', 'DIRECTORY_ENTRY_DELAY_IMPORT', 'DIRECTORY_ENTRY_BOUND_IMPORT']:
37
+ entries = getattr(pe, attr, [])
38
+ for entry in entries:
39
+ dll = (getattr(entry, 'dll', None) or getattr(entry, 'name', b'')).decode(errors='ignore')
40
+ if is_valid_dependency(dll) and not (ignore_system and is_system_dll(dll)):
41
+ deps.add(dll)
42
+ return list(deps)
43
+ except Exception:
44
+ return []
45
+
46
+ def get_dependents(file_path, ignore_system=True, local_file_set=None, detect_system=False):
47
+ key = frozenset(local_file_set or [])
48
+ return list(get_cached_dependents(file_path, ignore_system, key, detect_system))
49
+
50
+ def build_dependency_map_for_dir(base_dir, local_file_set, executor, ignore_system=True, detect_system=False):
51
+ dep_map = defaultdict(list)
52
+ def analyze_file(file):
53
+ full_path = os.path.join(base_dir, file)
54
+ deps = get_dependents(full_path, ignore_system=ignore_system, local_file_set=local_file_set, detect_system=detect_system)
55
+ return file, deps
56
+
57
+ files = [entry.name for entry in os.scandir(base_dir)
58
+ if entry.is_file() and entry.name.lower().endswith((".dll", ".exe"))]
59
+
60
+ futures = {executor.submit(analyze_file, file): file for file in files}
61
+ for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures), desc="Analyzing files", unit="file"):
62
+ file, deps = future.result()
63
+ dep_map[file] = deps
64
+ return dep_map
65
+
66
+ def build_dependency_map_for_file(file_path, base_dir, local_file_set, executor, ignore_system=True, detect_system=False):
67
+ dep_map = defaultdict(list)
68
+ visited = set()
69
+ futures = {}
70
+
71
+ def submit(file):
72
+ if file in visited or file not in local_file_set:
73
+ return
74
+ visited.add(file)
75
+ full_path = os.path.join(base_dir, file)
76
+ futures[file] = executor.submit(get_dependents, full_path, ignore_system, local_file_set, detect_system)
77
+
78
+ root_file = os.path.basename(file_path)
79
+ submit(root_file)
80
+
81
+ with tqdm(total=1, desc="Resolving dependencies", unit="file") as pbar:
82
+ while futures:
83
+ done_files = list(futures.keys())
84
+ for file in done_files:
85
+ future = futures.pop(file)
86
+ deps = future.result()
87
+ dep_map[file] = deps
88
+ for dep in deps:
89
+ if dep not in visited and dep in local_file_set:
90
+ submit(dep)
91
+ pbar.total += 1
92
+ pbar.refresh()
93
+ pbar.update(1)
94
+
95
+ return dep_map
96
+
97
+ @lru_cache(maxsize=2)
98
+ def get_all_present_files(base_dir):
99
+ present = set()
100
+ for root, _, files in os.walk(base_dir):
101
+ for name in files:
102
+ if name.lower().endswith((".dll", ".exe")):
103
+ present.add(name)
104
+ return present
105
+
106
+ def report_missing_files(dep_map, base_dir, local_file_set, detect_system=False):
107
+ all_required = set()
108
+ for deps in dep_map.values():
109
+ all_required.update(deps)
110
+
111
+ present_files = get_all_present_files(base_dir)
112
+
113
+ missing = sorted(
114
+ dep for dep in (all_required - present_files)
115
+ if not is_system_dll(dep)
116
+ )
117
+
118
+ if not missing:
119
+ print("No missing dependencies.")
120
+ else:
121
+ print("Missing dependencies:")
122
+ for f in missing:
123
+ print(f" {f}")
124
+
125
+ return set(missing)
126
+
127
+ def report_unused_files(dep_map, base_dir):
128
+ required = set()
129
+ for deps in dep_map.values():
130
+ required.update(deps)
131
+ used = required.union(dep_map.keys())
132
+
133
+ present_files = get_all_present_files(base_dir)
134
+ unused = sorted(present_files - used)
135
+
136
+ if not unused:
137
+ print("No unused files.")
138
+ else:
139
+ print("Unused files:")
140
+ for f in unused:
141
+ print(f" {f}")
142
+
143
+ def print_tree(dep_map, max_depth=None, missing_set=None, local_file_set=None):
144
+ def recurse(file, prefix="", is_last=True, depth=0, path=None):
145
+ if path is None:
146
+ path = set()
147
+ connector = "└── " if is_last else "β”œβ”€β”€ "
148
+ label = file
149
+ is_circular = file in path
150
+ if is_circular:
151
+ label += " πŸ”"
152
+ if missing_set and file in missing_set:
153
+ label += " ❌"
154
+ print(f"{prefix}{connector}{label}")
155
+ if is_circular or (max_depth is not None and depth >= max_depth):
156
+ return
157
+ path.add(file)
158
+ children = [c for c in dep_map.get(file, []) if c != file]
159
+ for idx, dep in enumerate(children):
160
+ last = idx == len(children) - 1
161
+ new_prefix = prefix + (" " if is_last else "β”‚ ")
162
+ recurse(dep, new_prefix, last, depth + 1, path)
163
+ path.remove(file)
164
+
165
+ roots = list(dep_map.keys())
166
+ for idx, root in enumerate(roots):
167
+ recurse(root, "", idx == len(roots) - 1)
168
+
169
+ def export_dot(dep_map):
170
+ lines = ["digraph G {"]
171
+ for src, deps in dep_map.items():
172
+ for dep in deps:
173
+ lines.append(f' "{src}" -> "{dep}";')
174
+ lines.append("}")
175
+ return "\n".join(lines)
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: win_depends
3
+ Version: 0.1.0
4
+ Summary: A tool to analyze Windows DLL/EXE dependencies
5
+ Author: Vorga
6
+ Author-email: Your Name <you@example.com>
7
+ License: MIT
8
+ Requires-Python: >=3.6
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: pefile
11
+ Requires-Dist: tqdm
12
+ Dynamic: author
13
+ Dynamic: requires-python
14
+
15
+ # depends
16
+
17
+ 🧩 Windows DLL/EXE Dependency Tree Analyzer using `dumpbin`.
18
+
19
+ This tool analyzes dependencies of `.dll` or `.exe` files on Windows, builds a dependency tree, and reports missing dependencies. It uses `dumpbin.exe` from Visual Studio toolchains.
20
+
21
+ ## πŸ”§ Features
22
+
23
+ - Automatically finds `dumpbin.exe`
24
+ - Supports both single file and directory analysis
25
+ - Outputs:
26
+ - Tree structure with optional max depth
27
+ - DOT graph format for Graphviz
28
+ - Missing dependency check
29
+ - Optional inclusion of system DLLs
30
+ - Threaded for performance
31
+
32
+ ## πŸ“¦ Installation
33
+
34
+ ```bash
35
+ pip install .
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ win_depends/__init__.py
5
+ win_depends/cli.py
6
+ win_depends/core.py
7
+ win_depends.egg-info/PKG-INFO
8
+ win_depends.egg-info/SOURCES.txt
9
+ win_depends.egg-info/dependency_links.txt
10
+ win_depends.egg-info/entry_points.txt
11
+ win_depends.egg-info/requires.txt
12
+ win_depends.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ win_depends = win_depends.cli:main
@@ -0,0 +1,2 @@
1
+ pefile
2
+ tqdm
@@ -0,0 +1 @@
1
+ win_depends