Skip to content

Commit b16c7a3

Browse files
authored
Merge pull request #100 from fosslight/develop
Support nuget package manager
2 parents e663b36 + ad08ec6 commit b16c7a3

File tree

9 files changed

+529
-3
lines changed

9 files changed

+529
-3
lines changed

.reuse/dep5

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,11 @@ License: Apache-2.0
9797
Files: tests/test_gradle2/*
9898
Copyright: 2014 Netflix
9999
License: Apache-2.0
100+
101+
Files: tests/test_nuget/*
102+
Copyright: 2022 LG Electronics
103+
License: Apache-2.0
104+
105+
Files: tests/test_nuget2/*
106+
Copyright: 2022 LG Electronics
107+
License: Apache-2.0

src/fosslight_dependency/_analyze_dependency.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from fosslight_dependency.package_manager.Swift import Swift
1717
from fosslight_dependency.package_manager.Carthage import Carthage
1818
from fosslight_dependency.package_manager.Go import Go
19+
from fosslight_dependency.package_manager.Nuget import Nuget
1920
import fosslight_util.constant as constant
2021

2122
logger = logging.getLogger(constant.LOGGER_NAME)
@@ -47,6 +48,8 @@ def analyze_dependency(package_manager_name, input_dir, output_dir, pip_activate
4748
package_manager = Carthage(input_dir, output_dir, github_token)
4849
elif package_manager_name == const.GO:
4950
package_manager = Go(input_dir, output_dir)
51+
elif package_manager_name == const.NUGET:
52+
package_manager = Nuget(input_dir, output_dir)
5053
else:
5154
logger.error(f"Not supported package manager name: {package_manager_name}")
5255
ret = False

src/fosslight_dependency/_package_manager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,12 @@ def check_and_run_license_scanner(platform, license_scanner_bin, file_dir):
257257
else:
258258
ret = subprocess.run(run_license_scanner, shell=True, stderr=subprocess.PIPE)
259259
if ret.returncode != 0 or ret.stderr:
260+
os.remove(tmp_output_file_name)
260261
return ""
261262

262263
fp = open(tmp_output_file_name, "r", encoding='utf8')
263264
license_output = fp.read()
264265
fp.close()
265-
os.remove(tmp_output_file_name)
266266

267267
if platform == const.LINUX:
268268
license_output_re = re.findall(r'.*contains license\(s\)\s(.*)', license_output)
@@ -275,6 +275,7 @@ def check_and_run_license_scanner(platform, license_scanner_bin, file_dir):
275275
license_name = ""
276276
else:
277277
license_name = ""
278+
os.remove(tmp_output_file_name)
278279

279280
except Exception as ex:
280281
logger.error(f"Failed to run license scan binary. {ex}")

src/fosslight_dependency/constant.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# -*- coding: utf-8 -*-
33
# Copyright (c) 2021 LG Electronics Inc.
44
# SPDX-License-Identifier: Apache-2.0
5+
import os
56

67
# System platform name
78
WINDOWS = 'Windows'
@@ -19,6 +20,7 @@
1920
SWIFT = 'swift'
2021
CARTHAGE = 'carthage'
2122
GO = 'go'
23+
NUGET = 'nuget'
2224

2325
# Supported package name and manifest file
2426
SUPPORT_PACKAE = {
@@ -31,7 +33,8 @@
3133
ANDROID: 'build.gradle',
3234
SWIFT: 'Package.resolved',
3335
CARTHAGE: 'Cartfile.resolved',
34-
GO: 'go.mod'
36+
GO: 'go.mod',
37+
NUGET: ['packages.config', os.path.join('obj', 'package.assets.json'), os.path.join('obj', 'project.assets.json')]
3538
}
3639

3740
# default android app name
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
from xml.etree.ElementTree import parse
10+
from xml.etree.ElementTree import 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_and_run_license_scanner
17+
18+
logger = logging.getLogger(constant.LOGGER_NAME)
19+
20+
21+
class Nuget(PackageManager):
22+
package_manager_name = const.NUGET
23+
24+
dn_url = "https://nuget.org/packages/"
25+
packageReference = False
26+
nuget_api_url = 'https://api.nuget.org/v3-flatcontainer/'
27+
28+
def __init__(self, input_dir, output_dir):
29+
super().__init__(self.package_manager_name, self.dn_url, input_dir, output_dir)
30+
31+
for manifest_i in const.SUPPORT_PACKAE.get(self.package_manager_name):
32+
if os.path.isfile(manifest_i):
33+
self.append_input_package_list_file(manifest_i)
34+
if manifest_i != 'packages.config':
35+
self.packageReference = True
36+
37+
def parse_oss_information(self, f_name):
38+
tmp_license_txt_file_name = 'tmp_license.txt'
39+
with open(f_name, 'r', encoding='utf8') as input_fp:
40+
sheet_list = []
41+
package_list = []
42+
if self.packageReference:
43+
package_list = self.get_package_list_in_packages_assets(input_fp)
44+
self.get_direct_package_in_packagereference()
45+
else:
46+
package_list = self.get_package_list_in_packages_config(input_fp)
47+
48+
for oss_origin_name, oss_version in package_list:
49+
try:
50+
oss_name = self.package_manager_name + ":" + oss_origin_name
51+
52+
comment = []
53+
dn_loc = ''
54+
homepage = ''
55+
license_name = ''
56+
57+
response = requests.get(f'{self.nuget_api_url}{oss_origin_name}/{oss_version}/{oss_origin_name}.nuspec')
58+
if response.status_code == 200:
59+
root = fromstring(response.text)
60+
xmlns = ''
61+
m = re.search('{.*}', root.tag)
62+
if m:
63+
xmlns = m.group(0)
64+
nupkg_metadata = root.find(f'{xmlns}metadata')
65+
66+
license_name_id = nupkg_metadata.find(f'{xmlns}license')
67+
if license_name_id is not None:
68+
license_name, license_comment = self.check_multi_license(license_name_id.text)
69+
if license_comment != '':
70+
comment.append(license_comment)
71+
else:
72+
license_url = nupkg_metadata.find(f'{xmlns}licenseUrl')
73+
if license_url is not None:
74+
url_res = requests.get(license_url.text)
75+
if url_res.status_code == 200:
76+
tmp_license_txt = open(tmp_license_txt_file_name, 'w', encoding='utf-8')
77+
tmp_license_txt.write(url_res.text)
78+
tmp_license_txt.close()
79+
license_name_with_license_scanner = check_and_run_license_scanner(self.platform,
80+
self.license_scanner_bin,
81+
tmp_license_txt_file_name)
82+
if license_name_with_license_scanner != "":
83+
license_name = license_name_with_license_scanner
84+
else:
85+
license_name = license_url.text
86+
repo_id = nupkg_metadata.find(f'{xmlns}repository')
87+
if repo_id is not None:
88+
dn_loc = repo_id.get("url")
89+
else:
90+
proj_url_id = nupkg_metadata.find(f'{xmlns}projectUrl')
91+
if proj_url_id is not None:
92+
dn_loc = proj_url_id.text
93+
homepage = f'{self.dn_url}{oss_origin_name}/{oss_version}'
94+
if dn_loc == '':
95+
dn_loc = homepage
96+
else:
97+
if dn_loc.endswith('.git'):
98+
dn_loc = dn_loc[:-4]
99+
else:
100+
comment.append('Fail to response for nuget api')
101+
102+
if self.direct_dep and self.packageReference:
103+
if oss_origin_name in self.direct_dep_list:
104+
comment.append('direct')
105+
else:
106+
comment.append('transitive')
107+
108+
sheet_list.append([','.join(self.input_package_list_file),
109+
oss_name, oss_version, license_name, dn_loc, homepage, '', '', ','.join(comment)])
110+
111+
except Exception as e:
112+
logger.warning(f"Failed to parse oss information: {e}")
113+
114+
if os.path.isfile(tmp_license_txt_file_name):
115+
os.remove(tmp_license_txt_file_name)
116+
117+
return sheet_list
118+
119+
def get_package_list_in_packages_config(self, input_fp):
120+
package_list = []
121+
root = parse(input_fp).getroot()
122+
for p in root.findall("package"):
123+
package_list.append([p.get("id"), p.get("version")])
124+
return package_list
125+
126+
def get_package_list_in_packages_assets(self, input_fp):
127+
package_list = []
128+
json_f = json.load(input_fp)
129+
for item in json_f['libraries']:
130+
if json_f['libraries'][item]['type'] == 'package':
131+
oss_info = item.split('/')
132+
package_list.append([oss_info[0], oss_info[1]])
133+
return package_list
134+
135+
def get_direct_package_in_packagereference(self):
136+
for f in os.listdir(self.input_dir):
137+
if os.path.isfile(f) and ((f.split('.')[-1] == 'csproj') or (f.split('.')[-1] == 'xproj')):
138+
with open(f, 'r', encoding='utf8') as input_fp:
139+
root = parse(input_fp).getroot()
140+
itemgroups = root.findall('ItemGroup')
141+
for itemgroup in itemgroups:
142+
for item in itemgroup.findall('PackageReference'):
143+
self.direct_dep_list.append(item.get('Include'))
144+
145+
def check_multi_license(self, license_name):
146+
multi_license = license_name
147+
license_comment = ''
148+
try:
149+
if license_name.startswith('(') and license_name.endswith(')'):
150+
license_name = license_name.lstrip('(').rstrip(')')
151+
license_comment = license_name
152+
multi_license = ','.join(re.split(r'OR|AND', license_name))
153+
except Exception as e:
154+
logger.warning(f'Fail to parse multi license in npm: {e}')
155+
156+
return multi_license, license_comment

0 commit comments

Comments
 (0)