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,368 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Copyright (c) 2022 LG Electronics Inc.
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import re
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
from defusedxml.ElementTree import parse, fromstring
|
|
11
|
+
import json
|
|
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
|
|
16
|
+
from fosslight_dependency._package_manager import check_license_name, get_url_to_purl
|
|
17
|
+
from fosslight_dependency.dependency_item import DependencyItem, change_dependson_to_purl
|
|
18
|
+
from fosslight_util.oss_item import OssItem
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(constant.LOGGER_NAME)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Nuget(PackageManager):
|
|
24
|
+
package_manager_name = const.NUGET
|
|
25
|
+
|
|
26
|
+
dn_url = "https://nuget.org/packages/"
|
|
27
|
+
packageReference = False
|
|
28
|
+
directory_packages_props = 'Directory.Packages.props'
|
|
29
|
+
nuget_api_url = 'https://api.nuget.org/v3-flatcontainer/'
|
|
30
|
+
dotnet_ver = []
|
|
31
|
+
_exclude_dirs = {"test", "tests", "sample", "samples", "example", "examples"}
|
|
32
|
+
|
|
33
|
+
def __init__(self, input_dir, output_dir):
|
|
34
|
+
super().__init__(self.package_manager_name, self.dn_url, input_dir, output_dir)
|
|
35
|
+
|
|
36
|
+
for manifest_i in const.SUPPORT_PACKAE.get(self.package_manager_name):
|
|
37
|
+
if os.path.exists(os.path.basename(manifest_i)):
|
|
38
|
+
self.append_input_package_list_file(os.path.basename(manifest_i))
|
|
39
|
+
if manifest_i != 'packages.config':
|
|
40
|
+
self.packageReference = True
|
|
41
|
+
|
|
42
|
+
def run_plugin(self):
|
|
43
|
+
ret = True
|
|
44
|
+
directory_packages_props_path = os.path.join(self.input_dir, self.directory_packages_props)
|
|
45
|
+
if not os.path.isfile(directory_packages_props_path):
|
|
46
|
+
return ret
|
|
47
|
+
|
|
48
|
+
logger.info(f"Found {self.directory_packages_props}. Using NuGet CPM flow.")
|
|
49
|
+
self.packageReference = True
|
|
50
|
+
|
|
51
|
+
restore_targets = self._find_restore_targets()
|
|
52
|
+
if restore_targets:
|
|
53
|
+
logger.info("Found .sln or .csproj files. Running 'dotnet restore'...")
|
|
54
|
+
for target_path, target_file in restore_targets:
|
|
55
|
+
logger.info(f"Restoring: {os.path.relpath(target_file, self.input_dir)}")
|
|
56
|
+
try:
|
|
57
|
+
result = subprocess.run(
|
|
58
|
+
['dotnet', 'restore', target_file, '/p:EnableWindowsTargeting=true'],
|
|
59
|
+
cwd=target_path,
|
|
60
|
+
capture_output=True,
|
|
61
|
+
text=True,
|
|
62
|
+
timeout=300
|
|
63
|
+
)
|
|
64
|
+
if result.returncode == 0:
|
|
65
|
+
logger.info(f"Successfully restored {os.path.relpath(target_file, self.input_dir)}")
|
|
66
|
+
else:
|
|
67
|
+
logger.warning(f"'dotnet restore' failed for {target_file} with return code {result.returncode}")
|
|
68
|
+
if result.stderr:
|
|
69
|
+
logger.warning(result.stderr)
|
|
70
|
+
except FileNotFoundError:
|
|
71
|
+
logger.error("'dotnet' command not found. Please install .NET SDK.")
|
|
72
|
+
except subprocess.TimeoutExpired:
|
|
73
|
+
logger.warning(f"'dotnet restore' timed out for {target_file}.")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.warning(f"Failed to run 'dotnet restore' for {target_file}: {e}")
|
|
76
|
+
else:
|
|
77
|
+
logger.warning("No .sln or .csproj files found to restore.")
|
|
78
|
+
|
|
79
|
+
self.project_dirs = []
|
|
80
|
+
found_projects = False
|
|
81
|
+
|
|
82
|
+
for root, dirs, files in os.walk(self.input_dir):
|
|
83
|
+
rel_root = os.path.relpath(root, self.input_dir)
|
|
84
|
+
parts = rel_root.split(os.sep) if rel_root != os.curdir else []
|
|
85
|
+
if any(p.lower() in self._exclude_dirs for p in parts):
|
|
86
|
+
continue
|
|
87
|
+
assets_json = os.path.join(root, 'obj', 'project.assets.json')
|
|
88
|
+
if os.path.isfile(assets_json):
|
|
89
|
+
found_projects = True
|
|
90
|
+
|
|
91
|
+
rel_path = os.path.relpath(assets_json, self.input_dir)
|
|
92
|
+
logger.info(f"Found project.assets.json at: {rel_path}")
|
|
93
|
+
|
|
94
|
+
if rel_path not in self.input_package_list_file:
|
|
95
|
+
self.append_input_package_list_file(rel_path)
|
|
96
|
+
|
|
97
|
+
project_dir = os.path.dirname(assets_json)
|
|
98
|
+
if project_dir.endswith(os.sep + 'obj'):
|
|
99
|
+
project_dir = project_dir[: -len(os.sep + 'obj')]
|
|
100
|
+
|
|
101
|
+
if project_dir and project_dir not in self.project_dirs:
|
|
102
|
+
self.project_dirs.append(project_dir)
|
|
103
|
+
|
|
104
|
+
if not found_projects:
|
|
105
|
+
logger.warning(
|
|
106
|
+
"Directory.Packages.props found and 'dotnet restore' completed, "
|
|
107
|
+
"but no obj/project.assets.json files were discovered."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return ret
|
|
111
|
+
|
|
112
|
+
def parse_oss_information(self, f_name):
|
|
113
|
+
tmp_license_txt_file_name = 'tmp_license.txt'
|
|
114
|
+
if f_name == self.directory_packages_props:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
relation_tree = {}
|
|
118
|
+
direct_dep_list = []
|
|
119
|
+
if not hasattr(self, 'global_purl_dict'):
|
|
120
|
+
self.global_purl_dict = {}
|
|
121
|
+
if not hasattr(self, 'processed_packages'):
|
|
122
|
+
self.processed_packages = {}
|
|
123
|
+
|
|
124
|
+
file_path = os.path.join(self.input_dir, f_name) if not os.path.isabs(f_name) else f_name
|
|
125
|
+
with open(file_path, 'r', encoding='utf8') as input_fp:
|
|
126
|
+
package_list = []
|
|
127
|
+
if self.packageReference:
|
|
128
|
+
package_list = self.get_package_info_in_packagereference(input_fp, relation_tree, direct_dep_list)
|
|
129
|
+
else:
|
|
130
|
+
package_list = self.get_package_list_in_packages_config(input_fp)
|
|
131
|
+
|
|
132
|
+
for oss_origin_name, oss_version in package_list:
|
|
133
|
+
try:
|
|
134
|
+
pkg_key = f'{oss_origin_name}({oss_version})'
|
|
135
|
+
if pkg_key in self.processed_packages:
|
|
136
|
+
existing_idx = self.processed_packages[pkg_key]
|
|
137
|
+
existing_dep_item = self.dep_items[existing_idx]
|
|
138
|
+
|
|
139
|
+
if pkg_key in relation_tree:
|
|
140
|
+
new_deps = relation_tree[pkg_key]
|
|
141
|
+
if existing_dep_item.depends_on_raw:
|
|
142
|
+
existing_deps_set = set(existing_dep_item.depends_on_raw)
|
|
143
|
+
new_deps_set = set(new_deps)
|
|
144
|
+
merged_deps = sorted(existing_deps_set | new_deps_set)
|
|
145
|
+
existing_dep_item.depends_on_raw = merged_deps
|
|
146
|
+
else:
|
|
147
|
+
existing_dep_item.depends_on_raw = new_deps
|
|
148
|
+
if self.direct_dep and self.packageReference:
|
|
149
|
+
if oss_origin_name in direct_dep_list:
|
|
150
|
+
if 'direct' not in existing_dep_item.oss_items[0].comment:
|
|
151
|
+
existing_dep_item.oss_items[0].comment = 'direct'
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
dep_item = DependencyItem()
|
|
155
|
+
oss_item = OssItem()
|
|
156
|
+
oss_item.name = f'{self.package_manager_name}:{oss_origin_name}'
|
|
157
|
+
oss_item.version = oss_version
|
|
158
|
+
|
|
159
|
+
license_name = ''
|
|
160
|
+
response = requests.get(f'{self.nuget_api_url.lower()}{oss_origin_name.lower()}/'
|
|
161
|
+
f'{oss_item.version.lower()}/{oss_origin_name.lower()}.nuspec')
|
|
162
|
+
if response.status_code == 200:
|
|
163
|
+
root = fromstring(response.text)
|
|
164
|
+
xmlns = ''
|
|
165
|
+
m = re.search('{.*}', root.tag)
|
|
166
|
+
if m:
|
|
167
|
+
xmlns = m.group(0)
|
|
168
|
+
nupkg_metadata = root.find(f'{xmlns}metadata')
|
|
169
|
+
|
|
170
|
+
license_name_id = nupkg_metadata.find(f'{xmlns}license')
|
|
171
|
+
if license_name_id is not None:
|
|
172
|
+
license_name, license_comment = self.check_multi_license(license_name_id.text)
|
|
173
|
+
if license_comment != '':
|
|
174
|
+
oss_item.comment = license_comment
|
|
175
|
+
else:
|
|
176
|
+
license_url = nupkg_metadata.find(f'{xmlns}licenseUrl')
|
|
177
|
+
if license_url is not None:
|
|
178
|
+
url_res = requests.get(license_url.text)
|
|
179
|
+
if url_res.status_code == 200:
|
|
180
|
+
license_name_with_scanner = check_license_name(url_res.text)
|
|
181
|
+
if license_name_with_scanner != "":
|
|
182
|
+
license_name = license_name_with_scanner
|
|
183
|
+
else:
|
|
184
|
+
license_name = license_url.text
|
|
185
|
+
oss_item.license = license_name
|
|
186
|
+
repo_id = nupkg_metadata.find(f'{xmlns}repository')
|
|
187
|
+
if repo_id is not None:
|
|
188
|
+
oss_item.download_location = repo_id.get("url")
|
|
189
|
+
else:
|
|
190
|
+
proj_url_id = nupkg_metadata.find(f'{xmlns}projectUrl')
|
|
191
|
+
if proj_url_id is not None:
|
|
192
|
+
oss_item.download_location = proj_url_id.text
|
|
193
|
+
oss_item.homepage = f'{self.dn_url}{oss_origin_name}'
|
|
194
|
+
if not oss_item.download_location:
|
|
195
|
+
oss_item.download_location = f'{oss_item.homepage}/{oss_item.version}'
|
|
196
|
+
else:
|
|
197
|
+
if oss_item.download_location.endswith('.git'):
|
|
198
|
+
oss_item.download_location = oss_item.download_location[:-4]
|
|
199
|
+
dep_item.purl = get_url_to_purl(f'{oss_item.homepage}/{oss_item.version}', self.package_manager_name)
|
|
200
|
+
else:
|
|
201
|
+
oss_item.comment = 'Fail to response for nuget api'
|
|
202
|
+
dep_item.purl = f'pkg:nuget/{oss_origin_name}@{oss_item.version}'
|
|
203
|
+
self.global_purl_dict[f'{oss_origin_name}({oss_item.version})'] = dep_item.purl
|
|
204
|
+
|
|
205
|
+
if self.direct_dep and self.packageReference:
|
|
206
|
+
if oss_origin_name in direct_dep_list:
|
|
207
|
+
oss_item.comment = 'direct'
|
|
208
|
+
else:
|
|
209
|
+
oss_item.comment = 'transitive'
|
|
210
|
+
|
|
211
|
+
key = f'{oss_origin_name}({oss_item.version})'
|
|
212
|
+
if key in relation_tree:
|
|
213
|
+
dep_item.depends_on_raw = relation_tree[key]
|
|
214
|
+
|
|
215
|
+
dep_item.oss_items.append(oss_item)
|
|
216
|
+
self.dep_items.append(dep_item)
|
|
217
|
+
self.processed_packages[pkg_key] = len(self.dep_items) - 1
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.warning(f"Failed to parse oss information: {e}")
|
|
221
|
+
if self.direct_dep:
|
|
222
|
+
self.dep_items = change_dependson_to_purl(self.global_purl_dict, self.dep_items)
|
|
223
|
+
|
|
224
|
+
if os.path.isfile(tmp_license_txt_file_name):
|
|
225
|
+
os.remove(tmp_license_txt_file_name)
|
|
226
|
+
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
def get_package_list_in_packages_config(self, input_fp):
|
|
230
|
+
package_list = []
|
|
231
|
+
root = parse(input_fp).getroot()
|
|
232
|
+
for p in root.findall("package"):
|
|
233
|
+
package_list.append([p.get("id"), p.get("version")])
|
|
234
|
+
return package_list
|
|
235
|
+
|
|
236
|
+
def get_package_info_in_packagereference(self, input_fp, relation_tree, direct_dep_list):
|
|
237
|
+
json_f = json.load(input_fp)
|
|
238
|
+
|
|
239
|
+
dotnet_ver = self.get_dotnet_ver_list(json_f)
|
|
240
|
+
package_list = self.get_package_list_in_packages_assets(json_f)
|
|
241
|
+
self.get_dependency_tree(json_f, relation_tree, dotnet_ver)
|
|
242
|
+
self.get_direct_dependencies_from_assets_json(json_f, direct_dep_list)
|
|
243
|
+
self.get_direct_package_in_packagereference(direct_dep_list)
|
|
244
|
+
|
|
245
|
+
return package_list
|
|
246
|
+
|
|
247
|
+
def get_package_list_in_packages_assets(self, json_f):
|
|
248
|
+
package_list = []
|
|
249
|
+
for item in json_f['libraries']:
|
|
250
|
+
if json_f['libraries'][item]['type'] == 'package':
|
|
251
|
+
oss_info = item.split('/')
|
|
252
|
+
package_list.append([oss_info[0], oss_info[1]])
|
|
253
|
+
return package_list
|
|
254
|
+
|
|
255
|
+
def get_dotnet_ver_list(self, json_f):
|
|
256
|
+
dotnet_ver = []
|
|
257
|
+
json_project_group = json_f['projectFileDependencyGroups']
|
|
258
|
+
for ver in json_project_group:
|
|
259
|
+
dotnet_ver.append(ver)
|
|
260
|
+
return dotnet_ver
|
|
261
|
+
|
|
262
|
+
def get_direct_dependencies_from_assets_json(self, json_f, direct_dep_list):
|
|
263
|
+
try:
|
|
264
|
+
json_project_group = json_f.get('projectFileDependencyGroups', {})
|
|
265
|
+
for _, dependencies in json_project_group.items():
|
|
266
|
+
if not dependencies:
|
|
267
|
+
continue
|
|
268
|
+
for dep_string in dependencies:
|
|
269
|
+
package_name = dep_string.split()[0] if dep_string else ''
|
|
270
|
+
if package_name and package_name not in direct_dep_list:
|
|
271
|
+
direct_dep_list.append(package_name)
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.warning(f"Failed to extract direct dependencies from project.assets.json: {e}")
|
|
274
|
+
|
|
275
|
+
def get_dependency_tree(self, json_f, relation_tree, dotnet_ver):
|
|
276
|
+
actual_versions = {}
|
|
277
|
+
for lib_key in json_f.get('libraries', {}):
|
|
278
|
+
if '/' in lib_key:
|
|
279
|
+
lib_name, lib_version = lib_key.split('/', 1)
|
|
280
|
+
actual_versions[lib_name.lower()] = lib_version
|
|
281
|
+
|
|
282
|
+
json_target = json_f['targets']
|
|
283
|
+
for item in json_target:
|
|
284
|
+
if item not in dotnet_ver:
|
|
285
|
+
continue
|
|
286
|
+
json_item = json_target[item]
|
|
287
|
+
for pkg in json_item:
|
|
288
|
+
json_pkg = json_item[pkg]
|
|
289
|
+
if 'type' not in json_pkg:
|
|
290
|
+
continue
|
|
291
|
+
if 'dependencies' not in json_pkg:
|
|
292
|
+
continue
|
|
293
|
+
if json_pkg['type'] != 'package':
|
|
294
|
+
continue
|
|
295
|
+
oss_info = pkg.split('/')
|
|
296
|
+
relation_tree[f'{oss_info[0]}({oss_info[1]})'] = []
|
|
297
|
+
for dep in json_pkg['dependencies']:
|
|
298
|
+
oss_name = dep
|
|
299
|
+
dep_ver_in_spec = json_pkg['dependencies'][dep]
|
|
300
|
+
actual_ver = actual_versions.get(oss_name.lower(), dep_ver_in_spec)
|
|
301
|
+
relation_tree[f'{oss_info[0]}({oss_info[1]})'].append(f'{oss_name}({actual_ver})')
|
|
302
|
+
|
|
303
|
+
def get_direct_package_in_packagereference(self, direct_dep_list):
|
|
304
|
+
if hasattr(self, 'project_dirs') and self.project_dirs:
|
|
305
|
+
for project_dir in self.project_dirs:
|
|
306
|
+
for f in os.listdir(project_dir):
|
|
307
|
+
f_path = os.path.join(project_dir, f)
|
|
308
|
+
if os.path.isfile(f_path) and ((f.split('.')[-1] == 'csproj') or (f.split('.')[-1] == 'xproj')):
|
|
309
|
+
with open(f_path, 'r', encoding='utf8') as input_fp:
|
|
310
|
+
root = parse(input_fp).getroot()
|
|
311
|
+
itemgroups = root.findall('ItemGroup')
|
|
312
|
+
for itemgroup in itemgroups:
|
|
313
|
+
for item in itemgroup.findall('PackageReference'):
|
|
314
|
+
pkg_name = item.get('Include')
|
|
315
|
+
if pkg_name and pkg_name not in direct_dep_list:
|
|
316
|
+
direct_dep_list.append(pkg_name)
|
|
317
|
+
else:
|
|
318
|
+
for f in os.listdir(self.input_dir):
|
|
319
|
+
f_path = os.path.join(self.input_dir, f)
|
|
320
|
+
if os.path.isfile(f_path) and ((f.split('.')[-1] == 'csproj') or (f.split('.')[-1] == 'xproj')):
|
|
321
|
+
with open(f_path, 'r', encoding='utf8') as input_fp:
|
|
322
|
+
root = parse(input_fp).getroot()
|
|
323
|
+
itemgroups = root.findall('ItemGroup')
|
|
324
|
+
for itemgroup in itemgroups:
|
|
325
|
+
for item in itemgroup.findall('PackageReference'):
|
|
326
|
+
pkg_name = item.get('Include')
|
|
327
|
+
if pkg_name and pkg_name not in direct_dep_list:
|
|
328
|
+
direct_dep_list.append(pkg_name)
|
|
329
|
+
|
|
330
|
+
def check_multi_license(self, license_name):
|
|
331
|
+
multi_license = license_name
|
|
332
|
+
license_comment = ''
|
|
333
|
+
try:
|
|
334
|
+
if license_name.startswith('(') and license_name.endswith(')'):
|
|
335
|
+
license_name = license_name.lstrip('(').rstrip(')')
|
|
336
|
+
license_comment = license_name
|
|
337
|
+
multi_license = ','.join(re.split(r'OR|AND', license_name))
|
|
338
|
+
except Exception as e:
|
|
339
|
+
logger.warning(f'Fail to parse multi license in npm: {e}')
|
|
340
|
+
|
|
341
|
+
return multi_license, license_comment
|
|
342
|
+
|
|
343
|
+
def _find_restore_targets(self):
|
|
344
|
+
sln_files = []
|
|
345
|
+
csproj_files = []
|
|
346
|
+
|
|
347
|
+
for root, dirs, files in os.walk(self.input_dir):
|
|
348
|
+
rel_root = os.path.relpath(root, self.input_dir)
|
|
349
|
+
parts = rel_root.split(os.sep) if rel_root != os.curdir else []
|
|
350
|
+
if any(p.lower() in self._exclude_dirs for p in parts):
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
depth = len(parts) if parts and parts[0] != '.' else 0
|
|
354
|
+
|
|
355
|
+
for f in files:
|
|
356
|
+
if f.endswith('.sln'):
|
|
357
|
+
sln_files.append((depth, root, os.path.join(root, f)))
|
|
358
|
+
elif f.endswith('.csproj'):
|
|
359
|
+
csproj_files.append((depth, root, os.path.join(root, f)))
|
|
360
|
+
|
|
361
|
+
result = []
|
|
362
|
+
if sln_files:
|
|
363
|
+
result.extend([(d, f) for _, d, f in sln_files])
|
|
364
|
+
|
|
365
|
+
if csproj_files:
|
|
366
|
+
result.extend([(d, f) for _, d, f in csproj_files])
|
|
367
|
+
|
|
368
|
+
return result
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Copyright (c) 2025 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 fosslight_util.constant as constant
|
|
12
|
+
import fosslight_dependency.constant as const
|
|
13
|
+
from fosslight_dependency._package_manager import PackageManager, get_url_to_purl
|
|
14
|
+
from fosslight_dependency.dependency_item import DependencyItem, change_dependson_to_purl
|
|
15
|
+
from fosslight_dependency.package_manager.Npm import check_multi_license
|
|
16
|
+
from fosslight_util.oss_item import OssItem
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(constant.LOGGER_NAME)
|
|
19
|
+
node_modules = 'node_modules'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Pnpm(PackageManager):
|
|
23
|
+
package_manager_name = const.PNPM
|
|
24
|
+
|
|
25
|
+
dn_url = 'https://www.npmjs.com/package/'
|
|
26
|
+
input_file_name = 'tmp_pnpm_license_output.json'
|
|
27
|
+
flag_tmp_node_modules = False
|
|
28
|
+
project_name_list = []
|
|
29
|
+
pkg_list = {}
|
|
30
|
+
|
|
31
|
+
def __init__(self, input_dir, output_dir):
|
|
32
|
+
super().__init__(self.package_manager_name, self.dn_url, input_dir, output_dir)
|
|
33
|
+
|
|
34
|
+
def __del__(self):
|
|
35
|
+
if os.path.isfile(self.input_file_name):
|
|
36
|
+
os.remove(self.input_file_name)
|
|
37
|
+
if self.flag_tmp_node_modules:
|
|
38
|
+
shutil.rmtree(node_modules, ignore_errors=True)
|
|
39
|
+
|
|
40
|
+
def run_plugin(self):
|
|
41
|
+
ret = True
|
|
42
|
+
|
|
43
|
+
pnpm_install_cmd = 'pnpm install --prod --ignore-scripts --ignore-pnpmfile'
|
|
44
|
+
if os.path.isdir(node_modules) != 1:
|
|
45
|
+
logger.info(f"node_modules directory is not existed. So it executes '{pnpm_install_cmd}'.")
|
|
46
|
+
self.flag_tmp_node_modules = True
|
|
47
|
+
cmd_ret = subprocess.call(pnpm_install_cmd, shell=True)
|
|
48
|
+
if cmd_ret != 0:
|
|
49
|
+
logger.error(f"{pnpm_install_cmd} returns an error")
|
|
50
|
+
ret = False
|
|
51
|
+
if ret:
|
|
52
|
+
project_cmd = 'pnpm ls -r --depth -1 -P --json'
|
|
53
|
+
ret_txt = subprocess.check_output(project_cmd, text=True, shell=True)
|
|
54
|
+
if ret_txt is not None:
|
|
55
|
+
deps_l = json.loads(ret_txt)
|
|
56
|
+
for items in deps_l:
|
|
57
|
+
self.project_name_list.append(items["name"])
|
|
58
|
+
return ret
|
|
59
|
+
|
|
60
|
+
def parse_direct_dependencies(self):
|
|
61
|
+
if not self.direct_dep:
|
|
62
|
+
return
|
|
63
|
+
try:
|
|
64
|
+
direct_cmd = 'pnpm ls -r --depth 0 -P --json'
|
|
65
|
+
ret_txt = subprocess.check_output(direct_cmd, text=True, shell=True)
|
|
66
|
+
if ret_txt is not None:
|
|
67
|
+
deps_l = json.loads(ret_txt)
|
|
68
|
+
for item in deps_l:
|
|
69
|
+
if 'dependencies' in item and isinstance(item['dependencies'], dict):
|
|
70
|
+
self.direct_dep_list.extend(item['dependencies'].keys())
|
|
71
|
+
else:
|
|
72
|
+
self.direct_dep = False
|
|
73
|
+
logger.warning('Cannot print direct/transitive dependency')
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.warning(f'Fail to print direct/transitive dependency: {e}')
|
|
76
|
+
self.direct_dep = False
|
|
77
|
+
if self.direct_dep:
|
|
78
|
+
self.direct_dep_list = list(filter(lambda dep: dep not in self.project_name_list, self.direct_dep_list))
|
|
79
|
+
|
|
80
|
+
def extract_dependencies(self, dependencies, purl_dict):
|
|
81
|
+
dep_item_list = []
|
|
82
|
+
for dep_name, dep_info in dependencies.items():
|
|
83
|
+
if dep_name not in self.project_name_list:
|
|
84
|
+
if dep_name in self.pkg_list.keys():
|
|
85
|
+
if dep_info.get('version') in self.pkg_list[dep_name]:
|
|
86
|
+
continue
|
|
87
|
+
self.pkg_list.setdefault(dep_name, []).append(dep_info.get('version'))
|
|
88
|
+
dep_item = DependencyItem()
|
|
89
|
+
oss_item = OssItem()
|
|
90
|
+
oss_item.name = f'npm:{dep_name}'
|
|
91
|
+
oss_item.version = dep_info.get('version')
|
|
92
|
+
|
|
93
|
+
license_name = dep_info.get('license')
|
|
94
|
+
if license_name:
|
|
95
|
+
multi_license, license_comment, multi_flag = check_multi_license(license_name, '')
|
|
96
|
+
if multi_flag:
|
|
97
|
+
oss_item.comment = license_comment
|
|
98
|
+
license_name = multi_license
|
|
99
|
+
else:
|
|
100
|
+
license_name = license_name.replace(",", "")
|
|
101
|
+
oss_item.license = license_name
|
|
102
|
+
|
|
103
|
+
oss_item.homepage = f'{self.dn_url}{dep_name}'
|
|
104
|
+
oss_item.download_location = dep_info.get('repository')
|
|
105
|
+
if oss_item.download_location:
|
|
106
|
+
if oss_item.download_location.endswith('.git'):
|
|
107
|
+
oss_item.download_location = oss_item.download_location[:-4]
|
|
108
|
+
if oss_item.download_location.startswith('git://'):
|
|
109
|
+
oss_item.download_location = 'https://' + oss_item.download_location[6:]
|
|
110
|
+
elif oss_item.download_location.startswith('git+https://'):
|
|
111
|
+
oss_item.download_location = 'https://' + oss_item.download_location[12:]
|
|
112
|
+
elif oss_item.download_location.startswith('git+ssh://git@'):
|
|
113
|
+
oss_item.download_location = 'https://' + oss_item.download_location[14:]
|
|
114
|
+
else:
|
|
115
|
+
oss_item.download_location = f'{self.dn_url}{dep_name}/v/{oss_item.version}'
|
|
116
|
+
|
|
117
|
+
dn_loc = f'{oss_item.homepage}/v/{oss_item.version}'
|
|
118
|
+
dep_item.purl = get_url_to_purl(dn_loc, 'npm')
|
|
119
|
+
purl_dict[f'{dep_name}({oss_item.version})'] = dep_item.purl
|
|
120
|
+
|
|
121
|
+
if dep_name in self.direct_dep_list:
|
|
122
|
+
oss_item.comment = 'direct'
|
|
123
|
+
else:
|
|
124
|
+
oss_item.comment = 'transitive'
|
|
125
|
+
|
|
126
|
+
if 'dependencies' in dep_info:
|
|
127
|
+
for dn, di in dep_info.get('dependencies').items():
|
|
128
|
+
if dn not in self.project_name_list:
|
|
129
|
+
dep_item.depends_on_raw.append(f"{dn}({di['version']})")
|
|
130
|
+
|
|
131
|
+
dep_item.oss_items.append(oss_item)
|
|
132
|
+
dep_item_list.append(dep_item)
|
|
133
|
+
|
|
134
|
+
if 'dependencies' in dep_info:
|
|
135
|
+
dep_item_list_inner, purl_dict_inner = self.extract_dependencies(dep_info['dependencies'], purl_dict)
|
|
136
|
+
dep_item_list.extend(dep_item_list_inner)
|
|
137
|
+
purl_dict.update(purl_dict_inner)
|
|
138
|
+
|
|
139
|
+
return dep_item_list, purl_dict
|
|
140
|
+
|
|
141
|
+
def parse_oss_information_for_pnpm(self):
|
|
142
|
+
project_cmd = 'pnpm ls --json -r --depth Infinity -P --long'
|
|
143
|
+
ret_txt = subprocess.check_output(project_cmd, text=True, shell=True)
|
|
144
|
+
if ret_txt is not None:
|
|
145
|
+
deps_l = json.loads(ret_txt)
|
|
146
|
+
purl_dict = {}
|
|
147
|
+
for items in deps_l:
|
|
148
|
+
if 'dependencies' in items:
|
|
149
|
+
dep_item_list_inner, purl_dict_inner = self.extract_dependencies(items['dependencies'], purl_dict)
|
|
150
|
+
self.dep_items.extend(dep_item_list_inner)
|
|
151
|
+
purl_dict.update(purl_dict_inner)
|
|
152
|
+
if self.direct_dep:
|
|
153
|
+
self.dep_items = change_dependson_to_purl(purl_dict, self.dep_items)
|
|
154
|
+
else:
|
|
155
|
+
logger.warning(f'No output for {project_cmd}')
|