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.
- win_depends-0.1.0/PKG-INFO +35 -0
- win_depends-0.1.0/README.md +21 -0
- win_depends-0.1.0/pyproject.toml +21 -0
- win_depends-0.1.0/setup.cfg +4 -0
- win_depends-0.1.0/setup.py +20 -0
- win_depends-0.1.0/win_depends/__init__.py +0 -0
- win_depends-0.1.0/win_depends/cli.py +77 -0
- win_depends-0.1.0/win_depends/core.py +175 -0
- win_depends-0.1.0/win_depends.egg-info/PKG-INFO +35 -0
- win_depends-0.1.0/win_depends.egg-info/SOURCES.txt +12 -0
- win_depends-0.1.0/win_depends.egg-info/dependency_links.txt +1 -0
- win_depends-0.1.0/win_depends.egg-info/entry_points.txt +2 -0
- win_depends-0.1.0/win_depends.egg-info/requires.txt +2 -0
- win_depends-0.1.0/win_depends.egg-info/top_level.txt +1 -0
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
win_depends
|