Skip to content

Commit f04963f

Browse files
authored
Add dependency graph generation feature (#214)
Signed-off-by: fhdufhdu <[email protected]>
1 parent 2f0cc36 commit f04963f

File tree

4 files changed

+97
-3
lines changed

4 files changed

+97
-3
lines changed

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ fosslight_util>=1.4.47
88
PyGithub
99
requirements-parser
1010
defusedxml
11-
packageurl-python
11+
packageurl-python
12+
igraph
13+
matplotlib
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# Copyright (c) 2021 LG Electronics Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
from typing import Optional, Tuple
6+
import igraph as ig
7+
import matplotlib.pyplot as plt
8+
9+
10+
class GraphConvertor:
11+
def __init__(self, package_list: Optional[list] = None):
12+
self._verticies = {}
13+
self._edges = []
14+
if package_list:
15+
self.init_list(package_list)
16+
17+
def init_list(self, package_list: list):
18+
"""
19+
Initialize package_list to self._verticies and self._edges
20+
21+
Args:
22+
package_list (list): List containing package information
23+
"""
24+
depend_on_package_dict = {}
25+
for idx, package_info in enumerate(package_list):
26+
package_name = package_info[0]
27+
depend_on_packages_str = package_info[-1]
28+
depend_on_packages = list(map((lambda x: x.strip()), depend_on_packages_str.split(",")))
29+
self._verticies[package_name] = idx
30+
depend_on_package_dict[package_name] = depend_on_packages
31+
else:
32+
for package_name, depend_on_packages in depend_on_package_dict.items():
33+
if not package_name:
34+
pass
35+
else:
36+
package_idx = self._verticies[package_name]
37+
for depend_on_package in depend_on_packages:
38+
if not depend_on_package:
39+
pass
40+
else:
41+
depend_on_package_idx = self._verticies[depend_on_package]
42+
self._edges.append((package_idx, depend_on_package_idx))
43+
44+
def save(self, path: str, size: Tuple[(int, int)]):
45+
g = ig.Graph((len(self._verticies)), (self._edges), directed=True)
46+
47+
g["title"] = "Dependency Graph"
48+
g.vs["name"] = list(self._verticies.keys())
49+
50+
fig, ax = plt.subplots(figsize=(tuple(map((lambda x: x / 100), size))))
51+
fig.tight_layout()
52+
53+
ig.plot(
54+
g,
55+
target=ax,
56+
layout="kk",
57+
vertex_size=15,
58+
vertex_color=["#FFD2D2"],
59+
vertex_label=(g.vs["name"]),
60+
vertex_label_dist=1.5,
61+
vertex_label_size=7.0,
62+
edge_width=0.5,
63+
edge_color=["#FFD2D2"],
64+
edge_arrow_size=5,
65+
edge_arrow_width=5,
66+
)
67+
68+
fig.savefig(path)

src/fosslight_dependency/_help.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
\t\t\t\t\t(If you want to generate the specific file name, add the output path with file name.)
3838
-f <format> [<format> ...]\t Output formats (excel, csv, opossum, yaml, spdx-tag, spdx-yaml, spdx-json, spdx-xml)
3939
\t\t\t\t Multiple formats can be specified separated by space.
40+
--graph-path <save_path> \t Enter the path where the graph image will be saved
41+
\t\t\t\t\t(ex. /your/directory/path/filename.{pdf, jpg, png}) (recommend pdf extension)
42+
--graph-size <width> <height> Enter the size of the graph image (The size unit is pixels)
43+
\t\t\t\t\t--graph-path option is required
4044
--direct\t\t\t Print the direct/transitive dependency type in comment.
4145
\t\tChoice 'True' or 'False'. (default:True)
4246
--notice\t\t\t Print the open source license notice text.

src/fosslight_dependency/run_dependency_scanner.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
if platform.system() != 'Windows':
2222
from fosslight_util.write_spdx import write_spdx
2323
from fosslight_util.cover import CoverItem
24+
from fosslight_dependency._graph_convertor import GraphConvertor
2425

2526
# Package Name
2627
_PKG_NAME = "fosslight_dependency"
@@ -92,7 +93,8 @@ def find_package_manager(input_dir, abs_path_to_exclude=[]):
9293

9394
def run_dependency_scanner(package_manager='', input_dir='', output_dir_file='', pip_activate_cmd='',
9495
pip_deactivate_cmd='', output_custom_dir='', app_name=const.default_app_name,
95-
github_token='', formats=[], direct=True, path_to_exclude=[]):
96+
github_token='', formats=[], direct=True, path_to_exclude=[], graph_path='',
97+
graph_size=(600, 600)):
9698
global logger
9799

98100
ret = True
@@ -233,6 +235,15 @@ def run_dependency_scanner(package_manager='', input_dir='', output_dir_file='',
233235
if cover_comment:
234236
cover.comment += f', {cover_comment}'
235237

238+
if ret and graph_path:
239+
graph_path = os.path.abspath(graph_path)
240+
try:
241+
converter = GraphConvertor(sheet_list[_sheet_name])
242+
converter.save(graph_path, graph_size)
243+
logger.info(f"Output graph image file: {graph_path}")
244+
except Exception as e:
245+
logger.error(f'Fail to make graph image: {e}')
246+
236247
combined_paths_and_files = [os.path.join(output_path, file) for file in output_files]
237248
results = []
238249
for i, output_extension in enumerate(output_extensions):
@@ -276,6 +287,8 @@ def main():
276287
app_name = const.default_app_name
277288
github_token = ''
278289
format = ''
290+
graph_path = ''
291+
graph_size = (600, 600)
279292
direct = True
280293

281294
parser = argparse.ArgumentParser(add_help=False)
@@ -291,6 +304,8 @@ def main():
291304
parser.add_argument('-n', '--appname', nargs=1, type=str, required=False)
292305
parser.add_argument('-t', '--token', nargs=1, type=str, required=False)
293306
parser.add_argument('-f', '--format', nargs="*", type=str, required=False)
307+
parser.add_argument('--graph-path', nargs=1, type=str, required=False)
308+
parser.add_argument('--graph-size', nargs=2, type=int, metavar=("WIDTH", "HEIGHT"), required=False)
294309
parser.add_argument('--direct', choices=('true', 'false'), default='True', required=False)
295310
parser.add_argument('--notice', action='store_true', required=False)
296311

@@ -324,6 +339,10 @@ def main():
324339
github_token = ''.join(args.token)
325340
if args.format: # -f option
326341
format = list(args.format)
342+
if args.graph_path:
343+
graph_path = ''.join(args.graph_path)
344+
if args.graph_size:
345+
graph_size = args.graph_size
327346
if args.direct: # --direct option
328347
if args.direct == 'true':
329348
direct = True
@@ -343,7 +362,8 @@ def main():
343362
sys.exit(0)
344363

345364
run_dependency_scanner(package_manager, input_dir, output_dir, pip_activate_cmd, pip_deactivate_cmd,
346-
output_custom_dir, app_name, github_token, format, direct, path_to_exclude)
365+
output_custom_dir, app_name, github_token, format, direct, path_to_exclude,
366+
graph_path, graph_size)
347367

348368

349369
if __name__ == '__main__':

0 commit comments

Comments
 (0)