fosslight-dependency 3.0.7__py3-none-any.whl → 4.1.30__py3-none-any.whl
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.
- fosslight_dependency/LICENSES/LICENSE +201 -0
- fosslight_dependency/LICENSES/LicenseRef-3rd_party_licenses.txt +1254 -0
- fosslight_dependency/__init__.py +0 -1
- fosslight_dependency/_analyze_dependency.py +130 -0
- fosslight_dependency/_graph_convertor.py +67 -0
- fosslight_dependency/_help.py +79 -0
- fosslight_dependency/_package_manager.py +397 -0
- fosslight_dependency/cli.py +127 -0
- fosslight_dependency/constant.py +57 -0
- fosslight_dependency/dependency_item.py +103 -0
- fosslight_dependency/package_manager/Android.py +90 -0
- fosslight_dependency/package_manager/Cargo.py +144 -0
- fosslight_dependency/package_manager/Carthage.py +130 -0
- fosslight_dependency/package_manager/Cocoapods.py +194 -0
- fosslight_dependency/package_manager/Go.py +179 -0
- fosslight_dependency/package_manager/Gradle.py +123 -0
- fosslight_dependency/package_manager/Helm.py +106 -0
- fosslight_dependency/package_manager/Maven.py +274 -0
- fosslight_dependency/package_manager/Npm.py +296 -0
- fosslight_dependency/package_manager/Nuget.py +368 -0
- fosslight_dependency/package_manager/Pnpm.py +155 -0
- fosslight_dependency/package_manager/Pub.py +241 -0
- fosslight_dependency/package_manager/Pypi.py +395 -0
- fosslight_dependency/package_manager/Swift.py +159 -0
- fosslight_dependency/package_manager/Unity.py +118 -0
- fosslight_dependency/package_manager/Yarn.py +231 -0
- fosslight_dependency/package_manager/__init__.py +0 -0
- fosslight_dependency/run_dependency_scanner.py +393 -0
- fosslight_dependency-4.1.30.dist-info/METADATA +213 -0
- fosslight_dependency-4.1.30.dist-info/RECORD +37 -0
- {fosslight_dependency-3.0.7.dist-info → fosslight_dependency-4.1.30.dist-info}/WHEEL +1 -1
- fosslight_dependency-4.1.30.dist-info/entry_points.txt +2 -0
- fosslight_dependency-4.1.30.dist-info/licenses/LICENSES/Apache-2.0.txt +201 -0
- fosslight_dependency-4.1.30.dist-info/licenses/LICENSES/LicenseRef-3rd_party_licenses.txt +1254 -0
- fosslight_dependency-4.1.30.dist-info/licenses/LICENSES/MIT.txt +21 -0
- fosslight_dependency/_version.py +0 -1
- fosslight_dependency/analyze_dependency.py +0 -1090
- fosslight_dependency/third_party/askalono/askalono.exe +0 -0
- fosslight_dependency/third_party/askalono/askalono_macos +0 -0
- fosslight_dependency/third_party/nomos/nomossa +0 -0
- fosslight_dependency-3.0.7.dist-info/3rd_party_licenses.txt +0 -726
- fosslight_dependency-3.0.7.dist-info/METADATA +0 -51
- fosslight_dependency-3.0.7.dist-info/RECORD +0 -13
- fosslight_dependency-3.0.7.dist-info/entry_points.txt +0 -3
- {fosslight_dependency-3.0.7.dist-info → fosslight_dependency-4.1.30.dist-info/licenses}/LICENSE +0 -0
- {fosslight_dependency-3.0.7.dist-info → fosslight_dependency-4.1.30.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Copyright (c) 2021 LG Electronics Inc.
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
import subprocess
|
|
9
|
+
import shutil
|
|
10
|
+
from bs4 import BeautifulSoup as bs
|
|
11
|
+
from defusedxml.ElementTree import parse
|
|
12
|
+
import re
|
|
13
|
+
import fosslight_util.constant as constant
|
|
14
|
+
import fosslight_dependency.constant as const
|
|
15
|
+
from fosslight_dependency._package_manager import PackageManager
|
|
16
|
+
from fosslight_dependency._package_manager import version_refine, get_url_to_purl, change_file_mode
|
|
17
|
+
from fosslight_dependency.dependency_item import DependencyItem, change_dependson_to_purl
|
|
18
|
+
from fosslight_util.get_pom_license import get_license_from_pom
|
|
19
|
+
from fosslight_util.oss_item import OssItem
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(constant.LOGGER_NAME)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Maven(PackageManager):
|
|
25
|
+
package_manager_name = const.MAVEN
|
|
26
|
+
|
|
27
|
+
dn_url = 'https://mvnrepository.com/artifact/'
|
|
28
|
+
input_file_name = os.path.join('target', 'generated-resources', 'licenses.xml')
|
|
29
|
+
is_run_plugin = False
|
|
30
|
+
output_custom_dir = ''
|
|
31
|
+
|
|
32
|
+
def __init__(self, input_dir, output_dir, output_custom_dir):
|
|
33
|
+
super().__init__(self.package_manager_name, self.dn_url, input_dir, output_dir)
|
|
34
|
+
self.is_run_plugin = False
|
|
35
|
+
|
|
36
|
+
if output_custom_dir:
|
|
37
|
+
self.output_custom_dir = output_custom_dir
|
|
38
|
+
self.input_file_name = os.path.join(output_custom_dir, os.sep.join(self.input_file_name.split(os.sep)[1:]))
|
|
39
|
+
|
|
40
|
+
self.append_input_package_list_file(self.input_file_name)
|
|
41
|
+
|
|
42
|
+
def __del__(self):
|
|
43
|
+
if self.is_run_plugin:
|
|
44
|
+
self.clean_run_maven_plugin_output()
|
|
45
|
+
|
|
46
|
+
def run_plugin(self):
|
|
47
|
+
ret = True
|
|
48
|
+
|
|
49
|
+
if not os.path.isfile(self.input_file_name):
|
|
50
|
+
pom_backup = 'pom.xml_backup'
|
|
51
|
+
|
|
52
|
+
ret = self.add_plugin_in_pom(pom_backup)
|
|
53
|
+
if ret:
|
|
54
|
+
ret_plugin = self.run_maven_plugin()
|
|
55
|
+
if ret_plugin:
|
|
56
|
+
self.is_run_plugin = True
|
|
57
|
+
|
|
58
|
+
if os.path.isfile(pom_backup):
|
|
59
|
+
shutil.move(pom_backup, const.SUPPORT_PACKAE.get(self.package_manager_name))
|
|
60
|
+
else:
|
|
61
|
+
self.set_direct_dependencies(False)
|
|
62
|
+
|
|
63
|
+
return ret
|
|
64
|
+
|
|
65
|
+
def add_plugin_in_pom(self, pom_backup):
|
|
66
|
+
ret = False
|
|
67
|
+
xml = 'xml'
|
|
68
|
+
|
|
69
|
+
manifest_file = const.SUPPORT_PACKAE.get(self.package_manager_name)
|
|
70
|
+
if os.path.isfile(manifest_file) != 1:
|
|
71
|
+
logger.error(f"{manifest_file} is not existed in this directory.")
|
|
72
|
+
return ret
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
shutil.move(manifest_file, pom_backup)
|
|
76
|
+
|
|
77
|
+
license_maven_plugin = '<plugin>\
|
|
78
|
+
<groupId>org.codehaus.mojo</groupId>\
|
|
79
|
+
<artifactId>license-maven-plugin</artifactId>\
|
|
80
|
+
<version>2.0.0</version>\
|
|
81
|
+
<executions>\
|
|
82
|
+
<execution>\
|
|
83
|
+
<id>aggregate-download-licenses</id>\
|
|
84
|
+
<goals>\
|
|
85
|
+
<goal>aggregate-download-licenses</goal>\
|
|
86
|
+
</goals>\
|
|
87
|
+
</execution>\
|
|
88
|
+
</executions>\
|
|
89
|
+
<configuration>\
|
|
90
|
+
<excludedScopes>test</excludedScopes>\
|
|
91
|
+
</configuration>\
|
|
92
|
+
</plugin>'
|
|
93
|
+
|
|
94
|
+
tmp_plugin = bs(license_maven_plugin, xml)
|
|
95
|
+
|
|
96
|
+
license_maven_plugins = f"<plugins>{license_maven_plugin}<plugins>"
|
|
97
|
+
tmp_plugins = bs(license_maven_plugins, xml)
|
|
98
|
+
|
|
99
|
+
with open(pom_backup, 'r', encoding='utf8') as f:
|
|
100
|
+
f_xml = f.read()
|
|
101
|
+
f_content = bs(f_xml, xml)
|
|
102
|
+
|
|
103
|
+
build = f_content.find('build')
|
|
104
|
+
if build is not None:
|
|
105
|
+
plugins = build.find('plugins')
|
|
106
|
+
if plugins is not None:
|
|
107
|
+
plugins.append(tmp_plugin.plugin)
|
|
108
|
+
ret = True
|
|
109
|
+
else:
|
|
110
|
+
build.append(tmp_plugins.plugins)
|
|
111
|
+
ret = True
|
|
112
|
+
except Exception as e:
|
|
113
|
+
ret = False
|
|
114
|
+
logger.error(f"Failed to add plugin in pom : {e}")
|
|
115
|
+
|
|
116
|
+
if ret:
|
|
117
|
+
with open(manifest_file, "w", encoding='utf8') as f_w:
|
|
118
|
+
f_w.write(f_content.prettify(formatter="minimal").encode().decode('utf-8'))
|
|
119
|
+
|
|
120
|
+
return ret
|
|
121
|
+
|
|
122
|
+
def clean_run_maven_plugin_output(self):
|
|
123
|
+
directory_name = os.path.dirname(self.input_file_name)
|
|
124
|
+
licenses_path = os.path.join(directory_name, 'licenses')
|
|
125
|
+
if os.path.isdir(directory_name):
|
|
126
|
+
if os.path.isdir(licenses_path):
|
|
127
|
+
shutil.rmtree(licenses_path)
|
|
128
|
+
os.remove(self.input_file_name)
|
|
129
|
+
|
|
130
|
+
if len(os.listdir(directory_name)) == 0:
|
|
131
|
+
shutil.rmtree(directory_name)
|
|
132
|
+
|
|
133
|
+
top_path = self.input_file_name.split(os.sep)[0]
|
|
134
|
+
if len(os.listdir(top_path)) == 0:
|
|
135
|
+
shutil.rmtree(top_path)
|
|
136
|
+
|
|
137
|
+
def run_maven_plugin(self):
|
|
138
|
+
ret_plugin = True
|
|
139
|
+
logger.info('Run maven license scanning plugin with temporary pom.xml')
|
|
140
|
+
current_mode = ''
|
|
141
|
+
if os.path.isfile('mvnw') or os.path.isfile('mvnw.cmd'):
|
|
142
|
+
if self.platform == const.WINDOWS:
|
|
143
|
+
cmd_mvn = "mvnw.cmd"
|
|
144
|
+
else:
|
|
145
|
+
cmd_mvn = "./mvnw"
|
|
146
|
+
current_mode = change_file_mode(cmd_mvn)
|
|
147
|
+
else:
|
|
148
|
+
cmd_mvn = "mvn"
|
|
149
|
+
cmd = f"{cmd_mvn} license:aggregate-download-licenses"
|
|
150
|
+
|
|
151
|
+
ret = subprocess.call(cmd, shell=True)
|
|
152
|
+
if ret != 0:
|
|
153
|
+
logger.error(f"Failed to run maven plugin: {cmd}")
|
|
154
|
+
ret_plugin = False
|
|
155
|
+
|
|
156
|
+
if ret_plugin:
|
|
157
|
+
cmd = f"{cmd_mvn} dependency:tree"
|
|
158
|
+
try:
|
|
159
|
+
ret_txt = subprocess.check_output(cmd, text=True, shell=True)
|
|
160
|
+
if ret_txt is not None:
|
|
161
|
+
self.parse_dependency_tree(ret_txt)
|
|
162
|
+
self.set_direct_dependencies(True)
|
|
163
|
+
else:
|
|
164
|
+
logger.error(f"Failed to run: {cmd}")
|
|
165
|
+
self.set_direct_dependencies(False)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.error(f"Failed to run '{cmd}': {e}")
|
|
168
|
+
self.set_direct_dependencies(False)
|
|
169
|
+
if current_mode:
|
|
170
|
+
change_file_mode(cmd_mvn, current_mode)
|
|
171
|
+
return ret_plugin
|
|
172
|
+
|
|
173
|
+
def create_dep_stack(self, dep_line):
|
|
174
|
+
dep_stack = []
|
|
175
|
+
cur_flag = ''
|
|
176
|
+
dep_level = -1
|
|
177
|
+
dep_level_plus = False
|
|
178
|
+
for line in dep_line.split('\n'):
|
|
179
|
+
try:
|
|
180
|
+
if not re.search(r'[.*INFO.*]', line):
|
|
181
|
+
continue
|
|
182
|
+
if len(line) <= 7:
|
|
183
|
+
continue
|
|
184
|
+
line = line[7:]
|
|
185
|
+
|
|
186
|
+
prev_flag = cur_flag
|
|
187
|
+
prev_dep_level = dep_level
|
|
188
|
+
dep_level = line.count("|")
|
|
189
|
+
|
|
190
|
+
re_result = re.findall(r'([\+|\\]\-)\s([^\:\s]+\:[^\:\s]+)\:(?:[^\:\s]+)\:([^\:\s]+)\:([^\:\s]+)', line)
|
|
191
|
+
if re_result:
|
|
192
|
+
cur_flag = re_result[0][0]
|
|
193
|
+
if (prev_flag == '\\-') and (prev_dep_level == dep_level):
|
|
194
|
+
dep_level_plus = True
|
|
195
|
+
if dep_level_plus and (prev_flag == '\\-') and (prev_dep_level != dep_level):
|
|
196
|
+
dep_level_plus = False
|
|
197
|
+
if dep_level_plus:
|
|
198
|
+
dep_level += 1
|
|
199
|
+
if re_result[0][3] == 'test':
|
|
200
|
+
continue
|
|
201
|
+
dep_name = f'{re_result[0][1]}({re_result[0][2]})'
|
|
202
|
+
dep_stack = dep_stack[:dep_level] + [dep_name]
|
|
203
|
+
yield dep_stack[:dep_level], dep_name
|
|
204
|
+
else:
|
|
205
|
+
cur_flag = ''
|
|
206
|
+
except Exception as e:
|
|
207
|
+
logger.warning(f"Failed to parse dependency tree: {e}")
|
|
208
|
+
|
|
209
|
+
def parse_dependency_tree(self, f_name):
|
|
210
|
+
try:
|
|
211
|
+
for stack, name in self.create_dep_stack(f_name):
|
|
212
|
+
if len(stack) == 0:
|
|
213
|
+
self.direct_dep_list.append(name)
|
|
214
|
+
else:
|
|
215
|
+
if stack[-1] not in self.relation_tree:
|
|
216
|
+
self.relation_tree[stack[-1]] = []
|
|
217
|
+
self.relation_tree[stack[-1]].append(name)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.warning(f'Fail to parse maven dependency tree:{e}')
|
|
220
|
+
|
|
221
|
+
def parse_oss_information(self, f_name):
|
|
222
|
+
with open(f_name, 'r', encoding='utf8') as input_fp:
|
|
223
|
+
tree = parse(input_fp)
|
|
224
|
+
|
|
225
|
+
root = tree.getroot()
|
|
226
|
+
dependencies = root.find("dependencies")
|
|
227
|
+
purl_dict = {}
|
|
228
|
+
|
|
229
|
+
for d in dependencies.iter("dependency"):
|
|
230
|
+
dep_item = DependencyItem()
|
|
231
|
+
oss_item = OssItem()
|
|
232
|
+
groupid = d.findtext("groupId")
|
|
233
|
+
artifactid = d.findtext("artifactId")
|
|
234
|
+
version = d.findtext("version")
|
|
235
|
+
oss_item.version = version_refine(version)
|
|
236
|
+
|
|
237
|
+
oss_item.name = f"{groupid}:{artifactid}"
|
|
238
|
+
oss_item.download_location = f"{self.dn_url}{groupid}/{artifactid}/{version}"
|
|
239
|
+
oss_item.homepage = f"{self.dn_url}{groupid}/{artifactid}"
|
|
240
|
+
dep_item.purl = get_url_to_purl(oss_item.download_location, self.package_manager_name)
|
|
241
|
+
purl_dict[f'{oss_item.name}({oss_item.version})'] = dep_item.purl
|
|
242
|
+
|
|
243
|
+
licenses = d.find("licenses")
|
|
244
|
+
if len(licenses):
|
|
245
|
+
license_names = []
|
|
246
|
+
for key_license in licenses.iter("license"):
|
|
247
|
+
if key_license.findtext("name") is not None:
|
|
248
|
+
license_names.append(key_license.findtext("name").replace(",", ""))
|
|
249
|
+
oss_item.license = ', '.join(license_names)
|
|
250
|
+
if not oss_item.license:
|
|
251
|
+
license_names = get_license_from_pom(groupid, artifactid, version)
|
|
252
|
+
if license_names:
|
|
253
|
+
oss_item.license = license_names
|
|
254
|
+
|
|
255
|
+
dep_key = f"{oss_item.name}({version})"
|
|
256
|
+
|
|
257
|
+
if self.direct_dep:
|
|
258
|
+
if len(self.direct_dep_list) > 0:
|
|
259
|
+
if dep_key in self.direct_dep_list:
|
|
260
|
+
oss_item.comment = 'direct'
|
|
261
|
+
else:
|
|
262
|
+
oss_item.comment = 'transitive'
|
|
263
|
+
try:
|
|
264
|
+
if dep_key in self.relation_tree:
|
|
265
|
+
dep_item.depends_on_raw = self.relation_tree[dep_key]
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.error(f"Fail to find oss scope in dependency tree: {e}")
|
|
268
|
+
|
|
269
|
+
dep_item.oss_items.append(oss_item)
|
|
270
|
+
self.dep_items.append(dep_item)
|
|
271
|
+
|
|
272
|
+
if self.direct_dep:
|
|
273
|
+
self.dep_items = change_dependson_to_purl(purl_dict, self.dep_items)
|
|
274
|
+
return
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Copyright (c) 2021 LG Electronics Inc.
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
import subprocess
|
|
9
|
+
import json
|
|
10
|
+
import shutil
|
|
11
|
+
import re
|
|
12
|
+
import requests
|
|
13
|
+
import fosslight_util.constant as constant
|
|
14
|
+
import fosslight_dependency.constant as const
|
|
15
|
+
from fosslight_dependency._package_manager import PackageManager, get_url_to_purl
|
|
16
|
+
from fosslight_dependency.dependency_item import DependencyItem, change_dependson_to_purl
|
|
17
|
+
from fosslight_util.oss_item import OssItem
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(constant.LOGGER_NAME)
|
|
20
|
+
node_modules = 'node_modules'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Npm(PackageManager):
|
|
24
|
+
package_manager_name = const.NPM
|
|
25
|
+
|
|
26
|
+
dn_url = 'https://www.npmjs.com/package/'
|
|
27
|
+
input_file_name = 'tmp_npm_license_output.json'
|
|
28
|
+
tmp_custom_json = 'custom.json'
|
|
29
|
+
flag_tmp_node_modules = False
|
|
30
|
+
_network_available = False
|
|
31
|
+
|
|
32
|
+
def __init__(self, input_dir, output_dir):
|
|
33
|
+
super().__init__(self.package_manager_name, self.dn_url, input_dir, output_dir)
|
|
34
|
+
self._check_network_available()
|
|
35
|
+
|
|
36
|
+
def __del__(self):
|
|
37
|
+
if os.path.isfile(os.path.join(self.input_dir, self.input_file_name)):
|
|
38
|
+
os.remove(os.path.join(self.input_dir, self.input_file_name))
|
|
39
|
+
if self.flag_tmp_node_modules:
|
|
40
|
+
shutil.rmtree(os.path.join(self.input_dir, node_modules), ignore_errors=True)
|
|
41
|
+
if os.path.exists(os.path.join(self.input_dir, self.tmp_custom_json)):
|
|
42
|
+
os.remove(os.path.join(self.input_dir, self.tmp_custom_json))
|
|
43
|
+
|
|
44
|
+
def run_plugin(self):
|
|
45
|
+
ret = self.start_license_checker()
|
|
46
|
+
return ret
|
|
47
|
+
|
|
48
|
+
def start_license_checker(self):
|
|
49
|
+
ret = True
|
|
50
|
+
license_checker_cmd = f'license-checker --production --json --out {self.input_file_name}'
|
|
51
|
+
custom_path_option = ' --customPath '
|
|
52
|
+
npm_install_cmd = 'npm install --production --ignore-scripts'
|
|
53
|
+
|
|
54
|
+
if os.path.isdir(node_modules) != 1:
|
|
55
|
+
logger.info(f"node_modules directory is not existed. So it executes '{npm_install_cmd}'.")
|
|
56
|
+
self.flag_tmp_node_modules = True
|
|
57
|
+
cmd_ret = subprocess.call(npm_install_cmd, shell=True)
|
|
58
|
+
if cmd_ret != 0:
|
|
59
|
+
logger.error(f"{npm_install_cmd} failed")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
# customized json file for obtaining specific items with license-checker
|
|
63
|
+
self.make_custom_json(self.tmp_custom_json)
|
|
64
|
+
|
|
65
|
+
cmd = license_checker_cmd + custom_path_option + self.tmp_custom_json
|
|
66
|
+
cmd_ret = subprocess.call(cmd, shell=True)
|
|
67
|
+
if cmd_ret != 0:
|
|
68
|
+
logger.error(f"It returns the error: {cmd}")
|
|
69
|
+
logger.error("Please check if the license-checker is installed.(sudo npm install -g license-checker)")
|
|
70
|
+
ret = False
|
|
71
|
+
else:
|
|
72
|
+
self.append_input_package_list_file(self.input_file_name)
|
|
73
|
+
if os.path.exists(self.tmp_custom_json):
|
|
74
|
+
os.remove(self.tmp_custom_json)
|
|
75
|
+
|
|
76
|
+
return ret
|
|
77
|
+
|
|
78
|
+
def make_custom_json(self, tmp_custom_json):
|
|
79
|
+
with open(tmp_custom_json, 'w', encoding='utf8') as custom:
|
|
80
|
+
custom.write(
|
|
81
|
+
"{\n\t\"name\": \"\",\n\t\"version\": \"\",\n\t\"licenses\": \"\",\n\t\"repository\": \
|
|
82
|
+
\"\",\n\t\"url\": \"\",\n\t\"copyright\": \"\",\n\t\"licenseText\": \"\"\n}\n".encode().decode("utf-8"))
|
|
83
|
+
|
|
84
|
+
def parse_rel_dependencies(self, rel_name, rel_ver, rel_dependencies):
|
|
85
|
+
_dependencies = 'dependencies'
|
|
86
|
+
_version = 'version'
|
|
87
|
+
_peer = 'peerMissing'
|
|
88
|
+
|
|
89
|
+
for rel_dep_name in rel_dependencies.keys():
|
|
90
|
+
# Optional, non-installed dependencies are listed as empty objects
|
|
91
|
+
if rel_dependencies[rel_dep_name] == {}:
|
|
92
|
+
continue
|
|
93
|
+
if _peer in rel_dependencies[rel_dep_name]:
|
|
94
|
+
if rel_dependencies[rel_dep_name][_peer]:
|
|
95
|
+
continue
|
|
96
|
+
if f'{rel_name}({rel_ver})' not in self.relation_tree:
|
|
97
|
+
self.relation_tree[f'{rel_name}({rel_ver})'] = []
|
|
98
|
+
elif f'{rel_dep_name}({rel_dependencies[rel_dep_name][_version]})' in self.relation_tree[f'{rel_name}({rel_ver})']:
|
|
99
|
+
continue
|
|
100
|
+
self.relation_tree[f'{rel_name}({rel_ver})'].append(f'{rel_dep_name}({rel_dependencies[rel_dep_name][_version]})')
|
|
101
|
+
if _dependencies in rel_dependencies[rel_dep_name]:
|
|
102
|
+
self.parse_rel_dependencies(rel_dep_name, rel_dependencies[rel_dep_name][_version],
|
|
103
|
+
rel_dependencies[rel_dep_name][_dependencies])
|
|
104
|
+
|
|
105
|
+
def parse_transitive_relationship(self):
|
|
106
|
+
_dependencies = 'dependencies'
|
|
107
|
+
_version = 'version'
|
|
108
|
+
_name = 'name'
|
|
109
|
+
ret = True
|
|
110
|
+
err_msg = ''
|
|
111
|
+
|
|
112
|
+
cmd = 'npm ls -a --omit=dev --json -s'
|
|
113
|
+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, encoding='utf-8')
|
|
114
|
+
rel_tree = result.stdout
|
|
115
|
+
if not rel_tree or rel_tree.strip() == '':
|
|
116
|
+
logger.error(f"No output for {cmd}, stderr: {result.stderr}")
|
|
117
|
+
ret = False
|
|
118
|
+
elif result.returncode > 1:
|
|
119
|
+
logger.error(f"'{cmd}' failed with exit code({result.returncode}), stderr: {result.stderr}")
|
|
120
|
+
ret = False
|
|
121
|
+
if ret:
|
|
122
|
+
if result.returncode == 1:
|
|
123
|
+
logger.debug(f"'{cmd}' has warnings: {result.stderr}")
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
rel_json = json.loads(rel_tree)
|
|
127
|
+
if len(rel_json) < 1:
|
|
128
|
+
ret = False
|
|
129
|
+
else:
|
|
130
|
+
self.package_name = f'{rel_json[_name]}({rel_json.get(_version, "")})'
|
|
131
|
+
if _dependencies in rel_json:
|
|
132
|
+
self.parse_rel_dependencies(rel_json[_name], rel_json.get(_version, ""), rel_json[_dependencies])
|
|
133
|
+
except Exception as e:
|
|
134
|
+
ret = False
|
|
135
|
+
err_msg = e
|
|
136
|
+
return ret, err_msg
|
|
137
|
+
|
|
138
|
+
def parse_direct_dependencies(self):
|
|
139
|
+
if not self.direct_dep:
|
|
140
|
+
return
|
|
141
|
+
try:
|
|
142
|
+
if os.path.isfile(const.SUPPORT_PACKAE.get(self.package_manager_name)):
|
|
143
|
+
ret, err_msg = self.parse_transitive_relationship()
|
|
144
|
+
if not ret:
|
|
145
|
+
self.direct_dep = False
|
|
146
|
+
logger.warning(f'It cannot print direct/transitive dependency: {err_msg}')
|
|
147
|
+
else:
|
|
148
|
+
logger.info('Direct/transitive support is not possible because the package.json file does not exist.')
|
|
149
|
+
self.direct_dep = False
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.warning(f'Cannot print direct/transitive dependency: {e}')
|
|
152
|
+
self.direct_dep = False
|
|
153
|
+
|
|
154
|
+
def parse_oss_information(self, f_name):
|
|
155
|
+
with open(f_name, 'r', encoding='utf8') as json_file:
|
|
156
|
+
json_data = json.load(json_file)
|
|
157
|
+
|
|
158
|
+
_licenses = 'licenses'
|
|
159
|
+
_repository = 'repository'
|
|
160
|
+
_private = 'private'
|
|
161
|
+
|
|
162
|
+
keys = [key for key in json_data]
|
|
163
|
+
purl_dict = {}
|
|
164
|
+
for i in range(0, len(keys)):
|
|
165
|
+
dep_item = DependencyItem()
|
|
166
|
+
oss_item = OssItem()
|
|
167
|
+
d = json_data.get(keys[i - 1])
|
|
168
|
+
oss_init_name = d['name']
|
|
169
|
+
oss_item.name = self.package_manager_name + ":" + oss_init_name
|
|
170
|
+
|
|
171
|
+
if d[_licenses]:
|
|
172
|
+
license_name = d[_licenses]
|
|
173
|
+
else:
|
|
174
|
+
license_name = ''
|
|
175
|
+
|
|
176
|
+
oss_item.version = d['version']
|
|
177
|
+
package_path = d['path']
|
|
178
|
+
|
|
179
|
+
private_pkg = False
|
|
180
|
+
if _private in d and d[_private]:
|
|
181
|
+
private_pkg = True
|
|
182
|
+
|
|
183
|
+
oss_item.download_location = ''
|
|
184
|
+
|
|
185
|
+
npm_dl_url = f"{self.dn_url}{oss_init_name}/v/{oss_item.version}"
|
|
186
|
+
npm_home_url = f"{self.dn_url}{oss_init_name}"
|
|
187
|
+
dep_item.purl = get_url_to_purl(npm_dl_url, self.package_manager_name)
|
|
188
|
+
purl_dict[f'{oss_init_name}({oss_item.version})'] = dep_item.purl
|
|
189
|
+
|
|
190
|
+
repo_url = d[_repository] if d[_repository] else ''
|
|
191
|
+
if private_pkg:
|
|
192
|
+
oss_item.homepage = repo_url or ''
|
|
193
|
+
oss_item.download_location = oss_item.homepage
|
|
194
|
+
oss_item.comment = 'private'
|
|
195
|
+
else:
|
|
196
|
+
npm_url_exists = False
|
|
197
|
+
if self._network_available is True:
|
|
198
|
+
npm_url_exists = self._npm_url_exists(oss_init_name)
|
|
199
|
+
|
|
200
|
+
if self._network_available and not npm_url_exists:
|
|
201
|
+
oss_item.homepage = repo_url or ""
|
|
202
|
+
oss_item.download_location = oss_item.homepage
|
|
203
|
+
else:
|
|
204
|
+
oss_item.homepage = repo_url or npm_home_url
|
|
205
|
+
oss_item.download_location = npm_dl_url
|
|
206
|
+
|
|
207
|
+
if self.package_name == f'{oss_init_name}({oss_item.version})':
|
|
208
|
+
oss_item.comment = 'root package'
|
|
209
|
+
elif self.direct_dep and len(self.relation_tree) > 0:
|
|
210
|
+
if f'{oss_init_name}({oss_item.version})' in self.relation_tree[self.package_name]:
|
|
211
|
+
oss_item.comment = 'direct'
|
|
212
|
+
else:
|
|
213
|
+
oss_item.comment = 'transitive'
|
|
214
|
+
|
|
215
|
+
if f'{oss_init_name}({oss_item.version})' in self.relation_tree:
|
|
216
|
+
dep_item.depends_on_raw = self.relation_tree[f'{oss_init_name}({oss_item.version})']
|
|
217
|
+
|
|
218
|
+
manifest_file_path = os.path.join(package_path, const.SUPPORT_PACKAE.get(self.package_manager_name))
|
|
219
|
+
multi_license, license_comment, multi_flag = check_multi_license(license_name, manifest_file_path)
|
|
220
|
+
|
|
221
|
+
if multi_flag:
|
|
222
|
+
oss_item.comment = license_comment
|
|
223
|
+
license_name = multi_license
|
|
224
|
+
else:
|
|
225
|
+
license_name = license_name.replace(",", "")
|
|
226
|
+
license_name = check_unknown_license(license_name, manifest_file_path)
|
|
227
|
+
oss_item.license = license_name
|
|
228
|
+
|
|
229
|
+
dep_item.oss_items.append(oss_item)
|
|
230
|
+
self.dep_items.append(dep_item)
|
|
231
|
+
|
|
232
|
+
if self.direct_dep:
|
|
233
|
+
self.dep_items = change_dependson_to_purl(purl_dict, self.dep_items)
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
def _check_network_available(self) -> bool:
|
|
237
|
+
test_url = 'https://registry.npmjs.org/'
|
|
238
|
+
try:
|
|
239
|
+
resp = requests.head(test_url, timeout=3, allow_redirects=True)
|
|
240
|
+
self._network_available = resp.status_code < 400
|
|
241
|
+
except Exception:
|
|
242
|
+
self._network_available = False
|
|
243
|
+
return self._network_available
|
|
244
|
+
|
|
245
|
+
def _npm_url_exists(self, package_name: str) -> bool:
|
|
246
|
+
url = f"https://registry.npmjs.org/{package_name}"
|
|
247
|
+
try:
|
|
248
|
+
resp = requests.head(url, timeout=3, allow_redirects=True)
|
|
249
|
+
if resp.status_code == 405:
|
|
250
|
+
resp = requests.get(url, timeout=3, allow_redirects=True)
|
|
251
|
+
return resp.status_code < 400
|
|
252
|
+
except Exception:
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def check_multi_license(license_name, manifest_file_path):
|
|
257
|
+
multi_license_list = []
|
|
258
|
+
multi_license = ''
|
|
259
|
+
license_comment = ''
|
|
260
|
+
multi_flag = False
|
|
261
|
+
try:
|
|
262
|
+
if isinstance(license_name, list):
|
|
263
|
+
for i in range(0, len(license_name)):
|
|
264
|
+
l_i = license_name[i].replace(",", "")
|
|
265
|
+
multi_license_list.append(check_unknown_license(l_i, manifest_file_path))
|
|
266
|
+
multi_license = ','.join(multi_license_list)
|
|
267
|
+
multi_flag = True
|
|
268
|
+
else:
|
|
269
|
+
if license_name.startswith('(') and license_name.endswith(')'):
|
|
270
|
+
license_name = license_name.lstrip('(').rstrip(')')
|
|
271
|
+
license_comment = license_name
|
|
272
|
+
multi_license = ','.join(re.split(r'OR|AND', license_name))
|
|
273
|
+
multi_flag = True
|
|
274
|
+
except Exception as e:
|
|
275
|
+
multi_license = license_name
|
|
276
|
+
logger.warning(f'Fail to parse multi license in npm: {e}')
|
|
277
|
+
|
|
278
|
+
return multi_license, license_comment, multi_flag
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def check_unknown_license(license_name, manifest_file_path):
|
|
282
|
+
if license_name.endswith('*'):
|
|
283
|
+
license_name = license_name[:-1]
|
|
284
|
+
|
|
285
|
+
if license_name == 'UNKNOWN':
|
|
286
|
+
try:
|
|
287
|
+
with open(manifest_file_path, 'r') as f:
|
|
288
|
+
json_f = json.load(f)
|
|
289
|
+
for key in json_f.keys():
|
|
290
|
+
if key == 'license':
|
|
291
|
+
license_name = json_f[key]
|
|
292
|
+
break
|
|
293
|
+
except Exception as e:
|
|
294
|
+
logging.warning(f"Cannot check unknown license: {e}")
|
|
295
|
+
|
|
296
|
+
return license_name
|