-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdependency_inventory.py
More file actions
139 lines (113 loc) · 4.53 KB
/
Copy pathdependency_inventory.py
File metadata and controls
139 lines (113 loc) · 4.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env python3
"""Maven dependency inventory."""
import argparse
import csv
import json
import logging
import shutil
import subprocess
import sys
from pathlib import Path
LOG = logging.getLogger("dependency_inventory")
FIELDS = (
"module",
"group_id",
"artifact_id",
"declared_version",
"resolved_version",
"scope",
"direct",
"optional",
)
class UnsupportedBuildToolError(RuntimeError):
pass
def detect_build_tool(project_dir: Path) -> str:
if (project_dir / "pom.xml").is_file():
return "maven"
if (project_dir / "build.gradle").is_file() or (project_dir / "build.gradle.kts").is_file():
raise UnsupportedBuildToolError("Gradle is not supported yet")
raise UnsupportedBuildToolError("no supported build tool found")
def resolve(project_dir: Path, resolver_jar: Path) -> dict:
if shutil.which("java") is None:
raise RuntimeError("required command not found: java")
command = ["java", "-jar", str(resolver_jar), str(project_dir)]
LOG.info("running: %s", " ".join(command))
result = subprocess.run(command, text=True, capture_output=True)
if result.returncode:
if result.stderr:
LOG.error(result.stderr.rstrip())
raise RuntimeError(f"Maven resolver failed with exit code {result.returncode}")
return json.loads(result.stdout)
def flatten(data: dict) -> list[dict]:
rows = []
for module in data.get("modules", []):
module_name = module.get("path", ".")
for dependency in module.get("dependencies", []):
rows.append(
{
"module": module_name,
"group_id": dependency.get("groupId"),
"artifact_id": dependency.get("artifactId"),
"declared_version": dependency.get("declaredVersion"),
"resolved_version": dependency.get("resolvedVersion"),
"scope": dependency.get("scope"),
"direct": dependency.get("direct", False),
"optional": dependency.get("optional", False),
}
)
return rows
def write_output(rows: list[dict], output: Path | None, fmt: str) -> None:
if fmt == "json":
text = json.dumps(rows, indent=2)
if output:
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(text + "\n", encoding="utf-8")
else:
print(text)
return
if output:
output.parent.mkdir(parents=True, exist_ok=True)
with output.open("w", newline="", encoding="utf-8") as stream:
writer = csv.DictWriter(stream, fieldnames=FIELDS)
writer.writeheader()
writer.writerows(rows)
return
writer = csv.DictWriter(sys.stdout, fieldnames=FIELDS)
writer.writeheader()
writer.writerows(rows)
def print_cli(rows: list[dict]) -> None:
print("module dependency declared resolved scope type")
for row in rows:
name = f"{row['group_id']}:{row['artifact_id']}"
kind = "direct" if row["direct"] else "transitive"
print(
f"{row['module']:<9} {name:<34} "
f"{str(row['declared_version']):<11} {str(row['resolved_version']):<11} "
f"{row['scope']:<10} {kind}"
)
def main() -> int:
parser = argparse.ArgumentParser(description="Maven dependency inventory")
parser.add_argument("--debug", action="store_true", help="show debug logs")
subparsers = parser.add_subparsers(dest="command", required=True)
inventory = subparsers.add_parser("inventory", help="inventory project dependencies")
inventory.add_argument("project_dir", type=Path)
inventory.add_argument("-o", "--output", type=Path)
inventory.add_argument("--format", choices=("csv", "json"), default="csv")
inventory.add_argument(
"--resolver-jar",
type=Path,
default=Path(__file__).parent / "maven-resolver" / "target" / "maven-resolver-0.1.0-SNAPSHOT.jar",
)
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO, format="%(levelname)s: %(message)s")
if args.command == "inventory":
project_dir = args.project_dir.resolve()
detect_build_tool(project_dir)
data = resolve(project_dir, args.resolver_jar.resolve())
rows = flatten(data)
print_cli(rows)
if args.output:
write_output(rows, args.output.resolve(), args.format)
return 0
if __name__ == "__main__":
raise SystemExit(main())