fosslight-util 1.4.34__py3-none-any.whl → 2.1.28__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_util/_get_downloadable_url.py +466 -36
- fosslight_util/compare_yaml.py +20 -11
- fosslight_util/constant.py +35 -0
- fosslight_util/correct.py +46 -78
- fosslight_util/cover.py +60 -0
- fosslight_util/download.py +302 -95
- fosslight_util/exclude.py +65 -0
- fosslight_util/help.py +20 -8
- fosslight_util/oss_item.py +171 -110
- fosslight_util/output_format.py +147 -19
- fosslight_util/parsing_yaml.py +45 -23
- fosslight_util/read_excel.py +40 -39
- fosslight_util/set_log.py +30 -5
- fosslight_util/spdx_licenses.py +2 -1
- fosslight_util/write_cyclonedx.py +210 -0
- fosslight_util/write_excel.py +141 -133
- fosslight_util/write_opossum.py +14 -20
- fosslight_util/write_scancodejson.py +51 -32
- fosslight_util/write_spdx.py +162 -115
- fosslight_util/write_txt.py +2 -1
- fosslight_util/write_yaml.py +43 -49
- {fosslight_util-1.4.34.dist-info → fosslight_util-2.1.28.dist-info}/METADATA +32 -24
- fosslight_util-2.1.28.dist-info/RECORD +32 -0
- {fosslight_util-1.4.34.dist-info → fosslight_util-2.1.28.dist-info}/WHEEL +1 -1
- {fosslight_util-1.4.34.dist-info → fosslight_util-2.1.28.dist-info}/entry_points.txt +0 -1
- fosslight_util/convert_excel_to_yaml.py +0 -69
- fosslight_util-1.4.34.dist-info/RECORD +0 -30
- {fosslight_util-1.4.34.dist-info → fosslight_util-2.1.28.dist-info/licenses}/LICENSE +0 -0
- {fosslight_util-1.4.34.dist-info → fosslight_util-2.1.28.dist-info}/top_level.txt +0 -0
|
@@ -4,60 +4,489 @@
|
|
|
4
4
|
# SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
import logging
|
|
6
6
|
import re
|
|
7
|
+
import requests
|
|
8
|
+
from lastversion import latest
|
|
7
9
|
from bs4 import BeautifulSoup
|
|
8
10
|
from urllib.request import urlopen
|
|
9
11
|
import fosslight_util.constant as constant
|
|
10
|
-
from npm.bindings import npm_run
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(constant.LOGGER_NAME)
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
def
|
|
16
|
+
def version_exists(pkg_type, origin_name, version):
|
|
17
|
+
try:
|
|
18
|
+
if pkg_type in ['npm', 'npm2']:
|
|
19
|
+
r = requests.get(f"https://registry.npmjs.org/{origin_name}", timeout=5)
|
|
20
|
+
if r.status_code == 200:
|
|
21
|
+
data = r.json()
|
|
22
|
+
return version in data.get('versions', {})
|
|
23
|
+
elif pkg_type == 'pypi':
|
|
24
|
+
r = requests.get(f"https://pypi.org/pypi/{origin_name}/{version}/json", timeout=5)
|
|
25
|
+
return r.status_code == 200
|
|
26
|
+
elif pkg_type == 'maven':
|
|
27
|
+
r = requests.get(f'https://api.deps.dev/v3alpha/systems/maven/packages/{origin_name}', timeout=5)
|
|
28
|
+
if r.status_code == 200:
|
|
29
|
+
versions = r.json().get('versions', [])
|
|
30
|
+
for vobj in versions:
|
|
31
|
+
vkey = vobj.get('versionKey') or {}
|
|
32
|
+
if vkey.get('version') == version:
|
|
33
|
+
return True
|
|
34
|
+
return False
|
|
35
|
+
elif pkg_type == 'pub':
|
|
36
|
+
r = requests.get(f'https://pub.dev/api/packages/{origin_name}', timeout=5)
|
|
37
|
+
if r.status_code == 200:
|
|
38
|
+
versions = r.json().get('versions', [])
|
|
39
|
+
return any(v.get('version') == version for v in versions if isinstance(v, dict))
|
|
40
|
+
elif pkg_type == 'go':
|
|
41
|
+
if not version.startswith('v'):
|
|
42
|
+
version = f'v{version}'
|
|
43
|
+
r = requests.get(f'https://proxy.golang.org/{origin_name}/@v/list', timeout=5)
|
|
44
|
+
if r.status_code == 200:
|
|
45
|
+
listed = r.text.splitlines()
|
|
46
|
+
return version in listed
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.info(f'version_exists check failed ({pkg_type}:{origin_name}:{version}) {e}')
|
|
49
|
+
return True
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def extract_name_version_from_link(link, checkout_version):
|
|
54
|
+
oss_name = ""
|
|
55
|
+
oss_version = ""
|
|
56
|
+
matched = False
|
|
57
|
+
direct_maven = False
|
|
58
|
+
|
|
59
|
+
if link.startswith("www."):
|
|
60
|
+
link = link.replace("www.", "https://www.", 1)
|
|
61
|
+
|
|
62
|
+
if (not matched and (
|
|
63
|
+
link.startswith('https://repo1.maven.org/maven2/') or
|
|
64
|
+
link.startswith('https://dl.google.com/android/maven2/')
|
|
65
|
+
)):
|
|
66
|
+
parsed = parse_direct_maven_url(link)
|
|
67
|
+
if parsed:
|
|
68
|
+
origin_name, parsed_version = parsed
|
|
69
|
+
oss_name = origin_name # groupId:artifactId
|
|
70
|
+
oss_version = parsed_version or ""
|
|
71
|
+
matched = True
|
|
72
|
+
direct_maven = True
|
|
73
|
+
pkg_type = 'maven'
|
|
74
|
+
|
|
75
|
+
for direct_key in ["maven_repo1", "maven_google"]:
|
|
76
|
+
pattern = constant.PKG_PATTERN.get(direct_key)
|
|
77
|
+
if pattern and re.match(pattern, link):
|
|
78
|
+
parsed = parse_direct_maven_url(link)
|
|
79
|
+
if parsed:
|
|
80
|
+
origin_name, parsed_version = parsed
|
|
81
|
+
oss_name = origin_name
|
|
82
|
+
oss_version = parsed_version or ""
|
|
83
|
+
matched = True
|
|
84
|
+
direct_maven = True
|
|
85
|
+
pkg_type = 'maven'
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
if not matched:
|
|
89
|
+
for key, value in constant.PKG_PATTERN.items():
|
|
90
|
+
if key in ["maven_repo1", "maven_google"]:
|
|
91
|
+
continue
|
|
92
|
+
p = re.compile(value)
|
|
93
|
+
match = p.match(link)
|
|
94
|
+
if match:
|
|
95
|
+
try:
|
|
96
|
+
pkg_type = key
|
|
97
|
+
origin_name = match.group(1)
|
|
98
|
+
if (key == "pypi") or (key == "pypi2"):
|
|
99
|
+
oss_name = f"pypi:{origin_name}"
|
|
100
|
+
oss_name = re.sub(r"[-_.]+", "-", oss_name)
|
|
101
|
+
oss_version = match.group(2)
|
|
102
|
+
pkg_type = 'pypi'
|
|
103
|
+
elif key == "maven":
|
|
104
|
+
artifact = match.group(2)
|
|
105
|
+
oss_name = f"{origin_name}:{artifact}"
|
|
106
|
+
origin_name = oss_name
|
|
107
|
+
oss_version = match.group(3)
|
|
108
|
+
elif key == "npm" or key == "npm2":
|
|
109
|
+
oss_name = f"npm:{origin_name}"
|
|
110
|
+
oss_version = match.group(2)
|
|
111
|
+
elif key == "pub":
|
|
112
|
+
oss_name = f"pub:{origin_name}"
|
|
113
|
+
oss_version = match.group(2)
|
|
114
|
+
elif key == "cocoapods":
|
|
115
|
+
oss_name = f"cocoapods:{origin_name}"
|
|
116
|
+
elif key == "go":
|
|
117
|
+
if origin_name.endswith('/'):
|
|
118
|
+
origin_name = origin_name[:-1]
|
|
119
|
+
oss_name = f"go:{origin_name}"
|
|
120
|
+
oss_version = match.group(2)
|
|
121
|
+
elif key == "cargo":
|
|
122
|
+
oss_name = f"cargo:{origin_name}"
|
|
123
|
+
oss_version = match.group(2)
|
|
124
|
+
except Exception as ex:
|
|
125
|
+
logger.info(f"extract_name_version_from_link {key}:{ex}")
|
|
126
|
+
if oss_name:
|
|
127
|
+
matched = True
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
if not matched:
|
|
131
|
+
return "", "", link, ""
|
|
132
|
+
else:
|
|
133
|
+
need_latest = False
|
|
134
|
+
if not oss_version and checkout_version:
|
|
135
|
+
oss_version = checkout_version.strip()
|
|
136
|
+
if pkg_type in ["pypi", "maven", "npm", "npm2", "pub", "go"]:
|
|
137
|
+
if oss_version:
|
|
138
|
+
try:
|
|
139
|
+
if not version_exists(pkg_type, origin_name, oss_version):
|
|
140
|
+
logger.info(f'Version {oss_version} not found for {oss_name}; will attempt latest fallback')
|
|
141
|
+
need_latest = True
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.info(f'Version validation failed ({oss_name}:{oss_version}) {e}; will attempt latest fallback')
|
|
144
|
+
need_latest = True
|
|
145
|
+
else:
|
|
146
|
+
need_latest = True
|
|
147
|
+
if need_latest:
|
|
148
|
+
latest_ver = get_latest_package_version(link, pkg_type, origin_name)
|
|
149
|
+
if latest_ver:
|
|
150
|
+
if oss_version and latest_ver != oss_version:
|
|
151
|
+
logger.info(f'Fallback to latest version {latest_ver} (previous invalid: {oss_version})')
|
|
152
|
+
elif not oss_version:
|
|
153
|
+
logger.info(f'Using latest version {latest_ver} (no version detected)')
|
|
154
|
+
oss_version = latest_ver
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
if oss_version:
|
|
158
|
+
if pkg_type == 'maven' and direct_maven:
|
|
159
|
+
# Skip if oss_name malformed
|
|
160
|
+
if ':' in oss_name:
|
|
161
|
+
parts = oss_name.split(':', 1)
|
|
162
|
+
group_id, artifact_id = parts[0], parts[1]
|
|
163
|
+
group_path = group_id.replace('.', '/')
|
|
164
|
+
if (
|
|
165
|
+
link.startswith('https://repo1.maven.org/maven2/') or
|
|
166
|
+
link.startswith('http://repo1.maven.org/maven2/')
|
|
167
|
+
):
|
|
168
|
+
if not re.search(r'/\d[^/]*/*$', link.rstrip('/')):
|
|
169
|
+
link = (
|
|
170
|
+
f'https://repo1.maven.org/maven2/{group_path}/'
|
|
171
|
+
f'{artifact_id}/{oss_version}'
|
|
172
|
+
)
|
|
173
|
+
elif (
|
|
174
|
+
link.startswith('https://dl.google.com/android/maven2/') or
|
|
175
|
+
link.startswith('http://dl.google.com/android/maven2/')
|
|
176
|
+
):
|
|
177
|
+
if not re.search(r'/\d[^/]*/*$', link.rstrip('/')):
|
|
178
|
+
link = (
|
|
179
|
+
f'https://dl.google.com/android/maven2/{group_path}/'
|
|
180
|
+
f'{artifact_id}/{oss_version}/{artifact_id}-{oss_version}-sources.jar'
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
logger.debug(f'Skip maven normalization due to invalid oss_name: {oss_name}')
|
|
184
|
+
else:
|
|
185
|
+
link = get_new_link_with_version(link, pkg_type, origin_name, oss_version)
|
|
186
|
+
except Exception as _e:
|
|
187
|
+
logger.info(f'Failed to build versioned link for {oss_name or origin_name}:{oss_version} {_e}')
|
|
188
|
+
|
|
189
|
+
return oss_name, oss_version, link, pkg_type
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def parse_direct_maven_url(url):
|
|
193
|
+
try:
|
|
194
|
+
clean_url = url.replace('https://', '').replace('http://', '')
|
|
195
|
+
if clean_url.startswith('repo1.maven.org/maven2/'):
|
|
196
|
+
base_path = clean_url[len('repo1.maven.org/maven2/'):]
|
|
197
|
+
elif clean_url.startswith('dl.google.com/android/maven2/'):
|
|
198
|
+
base_path = clean_url[len('dl.google.com/android/maven2/'):]
|
|
199
|
+
else:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
base_path = base_path.rstrip('/')
|
|
203
|
+
# Strip file name if ends with known artifact extension.
|
|
204
|
+
if any(base_path.endswith(ext) for ext in ['.jar', '.pom', '.aar']):
|
|
205
|
+
base_path = '/'.join(base_path.split('/')[:-1])
|
|
206
|
+
|
|
207
|
+
parts = base_path.split('/')
|
|
208
|
+
if len(parts) < 2:
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
version = None
|
|
212
|
+
artifact_id = None
|
|
213
|
+
if len(parts) >= 3:
|
|
214
|
+
potential_version = parts[-1]
|
|
215
|
+
potential_artifact = parts[-2]
|
|
216
|
+
if re.search(r'\d', potential_version):
|
|
217
|
+
version = potential_version
|
|
218
|
+
artifact_id = potential_artifact
|
|
219
|
+
group_parts = parts[:-2]
|
|
220
|
+
else:
|
|
221
|
+
artifact_id = parts[-1]
|
|
222
|
+
group_parts = parts[:-1]
|
|
223
|
+
else:
|
|
224
|
+
artifact_id = parts[-1]
|
|
225
|
+
group_parts = parts[:-1]
|
|
226
|
+
|
|
227
|
+
group_id = '.'.join(group_parts)
|
|
228
|
+
if not group_id or not artifact_id:
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
maven_name = f"{group_id}:{artifact_id}"
|
|
232
|
+
return maven_name, version
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.debug(f'Failed to parse direct Maven URL {url}: {e}')
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_new_link_with_version(link, pkg_type, oss_name, oss_version):
|
|
239
|
+
if pkg_type == "pypi":
|
|
240
|
+
link = f'https://pypi.org/project/{oss_name}/{oss_version}'
|
|
241
|
+
elif pkg_type == "maven":
|
|
242
|
+
oss_name = oss_name.replace(':', '/')
|
|
243
|
+
link = f'https://mvnrepository.com/artifact/{oss_name}/{oss_version}'
|
|
244
|
+
elif pkg_type == "npm" or pkg_type == "npm2":
|
|
245
|
+
link = f'https://www.npmjs.com/package/{oss_name}/v/{oss_version}'
|
|
246
|
+
elif pkg_type == "pub":
|
|
247
|
+
link = f'https://pub.dev/packages/{oss_name}/versions/{oss_version}'
|
|
248
|
+
elif pkg_type == "go":
|
|
249
|
+
if not oss_version.startswith('v'):
|
|
250
|
+
oss_version = f'v{oss_version}'
|
|
251
|
+
link = f'https://pkg.go.dev/{oss_name}@{oss_version}'
|
|
252
|
+
elif pkg_type == "cargo":
|
|
253
|
+
link = f'https://crates.io/crates/{oss_name}/{oss_version}'
|
|
254
|
+
return link
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def get_latest_package_version(link, pkg_type, oss_name):
|
|
258
|
+
find_version = ''
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
if pkg_type in ['npm', 'npm2']:
|
|
262
|
+
npm_response = requests.get(f"https://registry.npmjs.org/{oss_name}")
|
|
263
|
+
if npm_response.status_code == 200:
|
|
264
|
+
find_version = npm_response.json().get("dist-tags", {}).get("latest")
|
|
265
|
+
elif pkg_type == 'pypi':
|
|
266
|
+
find_version = str(latest(oss_name, at='pip', output_format='version', pre_ok=True))
|
|
267
|
+
elif pkg_type == 'maven':
|
|
268
|
+
maven_response = requests.get(f'https://api.deps.dev/v3alpha/systems/maven/packages/{oss_name}')
|
|
269
|
+
if maven_response.status_code == 200:
|
|
270
|
+
versions = maven_response.json().get('versions', [])
|
|
271
|
+
if versions:
|
|
272
|
+
# Some version entries may miss publishedAt; fallback to semantic version ordering.
|
|
273
|
+
def sem_key(vstr: str):
|
|
274
|
+
# Parse semantic version with optional prerelease label
|
|
275
|
+
# Examples: 1.9.0, 1.10.0-alpha, 2.0.0-rc
|
|
276
|
+
m = re.match(r'^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[-.]([A-Za-z0-9]+))?$', vstr)
|
|
277
|
+
if not m:
|
|
278
|
+
return (0, 0, 0, 999)
|
|
279
|
+
major = int(m.group(1) or 0)
|
|
280
|
+
minor = int(m.group(2) or 0)
|
|
281
|
+
patch = int(m.group(3) or 0)
|
|
282
|
+
label = (m.group(4) or '').lower()
|
|
283
|
+
# Assign label weights: stable > rc > beta > alpha
|
|
284
|
+
label_weight_map = {
|
|
285
|
+
'alpha': -3,
|
|
286
|
+
'beta': -2,
|
|
287
|
+
'rc': -1
|
|
288
|
+
}
|
|
289
|
+
weight = label_weight_map.get(label, 0 if label == '' else -4)
|
|
290
|
+
return (major, minor, patch, weight)
|
|
291
|
+
|
|
292
|
+
with_pub = [v for v in versions if v.get('publishedAt')]
|
|
293
|
+
if with_pub:
|
|
294
|
+
cand = max(with_pub, key=lambda v: v.get('publishedAt'))
|
|
295
|
+
else:
|
|
296
|
+
decorated = []
|
|
297
|
+
for v in versions:
|
|
298
|
+
vkey = v.get('versionKey', {})
|
|
299
|
+
ver = vkey.get('version', '')
|
|
300
|
+
if ver:
|
|
301
|
+
decorated.append((sem_key(ver), ver, v))
|
|
302
|
+
if decorated:
|
|
303
|
+
decorated.sort(key=lambda t: t[0])
|
|
304
|
+
stable_candidates = [t for t in decorated if t[0][3] == 0]
|
|
305
|
+
if stable_candidates:
|
|
306
|
+
cand = stable_candidates[-1][2]
|
|
307
|
+
else:
|
|
308
|
+
cand = decorated[-1][2]
|
|
309
|
+
else:
|
|
310
|
+
cand = versions[-1]
|
|
311
|
+
find_version = cand.get('versionKey', {}).get('version', '')
|
|
312
|
+
elif pkg_type == 'pub':
|
|
313
|
+
pub_response = requests.get(f'https://pub.dev/api/packages/{oss_name}')
|
|
314
|
+
if pub_response.status_code == 200:
|
|
315
|
+
find_version = pub_response.json().get('latest').get('version')
|
|
316
|
+
elif pkg_type == 'go':
|
|
317
|
+
go_response = requests.get(f'https://proxy.golang.org/{oss_name}/@latest')
|
|
318
|
+
if go_response.status_code == 200:
|
|
319
|
+
find_version = go_response.json().get('Version')
|
|
320
|
+
if find_version.startswith('v'):
|
|
321
|
+
find_version = find_version[1:]
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.info(f'Fail to get latest package version({link}:{e})')
|
|
324
|
+
return find_version
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def get_downloadable_url(link, checkout_version):
|
|
16
328
|
|
|
329
|
+
ret = False
|
|
330
|
+
result_link = link
|
|
331
|
+
|
|
332
|
+
oss_name, oss_version, new_link, pkg_type = extract_name_version_from_link(link, checkout_version)
|
|
333
|
+
new_link = new_link.replace('http://', '')
|
|
334
|
+
new_link = new_link.replace('https://', '')
|
|
335
|
+
|
|
336
|
+
if pkg_type == "pypi":
|
|
337
|
+
ret, result_link = get_download_location_for_pypi(new_link)
|
|
338
|
+
elif pkg_type == "maven" or new_link.startswith('repo1.maven.org/') or new_link.startswith('dl.google.com/android/maven2/'):
|
|
339
|
+
ret, result_link = get_download_location_for_maven(new_link)
|
|
340
|
+
elif (pkg_type in ["npm", "npm2"]) or new_link.startswith('registry.npmjs.org/'):
|
|
341
|
+
ret, result_link = get_download_location_for_npm(new_link)
|
|
342
|
+
elif pkg_type == "pub":
|
|
343
|
+
ret, result_link = get_download_location_for_pub(new_link)
|
|
344
|
+
elif pkg_type == "go":
|
|
345
|
+
ret, result_link = get_download_location_for_go(new_link)
|
|
346
|
+
elif pkg_type == "cargo":
|
|
347
|
+
ret, result_link = get_download_location_for_cargo(new_link)
|
|
348
|
+
return ret, result_link, oss_name, oss_version, pkg_type
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def get_download_location_for_cargo(link):
|
|
352
|
+
# get the url for downloading source file: https://crates.io/api/v1/crates/<name>/<version>/download
|
|
17
353
|
ret = False
|
|
18
354
|
new_link = ''
|
|
355
|
+
host = 'https://crates.io/api/v1/crates'
|
|
19
356
|
|
|
20
|
-
|
|
21
|
-
|
|
357
|
+
try:
|
|
358
|
+
dn_loc_re = re.findall(r'crates.io\/crates\/([^\/]+)\/?([^\/]*)', link)
|
|
359
|
+
if dn_loc_re:
|
|
360
|
+
oss_name = dn_loc_re[0][0]
|
|
361
|
+
oss_version = dn_loc_re[0][1]
|
|
362
|
+
|
|
363
|
+
new_link = f'{host}/{oss_name}/{oss_version}/download'
|
|
364
|
+
res = urlopen(new_link)
|
|
365
|
+
if res.getcode() == 200:
|
|
366
|
+
ret = True
|
|
367
|
+
else:
|
|
368
|
+
logger.warning(f'Cannot find the valid link for cargo (url:{new_link}')
|
|
369
|
+
except Exception as error:
|
|
370
|
+
ret = False
|
|
371
|
+
logger.warning(f'Cannot find the link for cargo (url:{link}({(new_link)})): {error}')
|
|
372
|
+
|
|
373
|
+
return ret, new_link
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def get_download_location_for_go(link):
|
|
377
|
+
# get the url for downloading source file: https://proxy.golang.org/<module>/@v/VERSION.zip
|
|
378
|
+
ret = False
|
|
379
|
+
new_link = ''
|
|
380
|
+
host = 'https://proxy.golang.org'
|
|
22
381
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
382
|
+
try:
|
|
383
|
+
dn_loc_re = re.findall(r'pkg.go.dev\/([^\@]+)\@?([^\/]*)', link)
|
|
384
|
+
if dn_loc_re:
|
|
385
|
+
oss_name = dn_loc_re[0][0]
|
|
386
|
+
if oss_name.endswith('/'):
|
|
387
|
+
oss_name = oss_name[:-1]
|
|
388
|
+
oss_version = dn_loc_re[0][1]
|
|
389
|
+
|
|
390
|
+
new_link = f'{host}/{oss_name}/@v/{oss_version}.zip'
|
|
391
|
+
try:
|
|
392
|
+
res = urlopen(new_link)
|
|
393
|
+
if res.getcode() == 200:
|
|
394
|
+
ret = True
|
|
395
|
+
else:
|
|
396
|
+
logger.warning(f'Cannot find the valid link for go (url:{new_link}')
|
|
397
|
+
except Exception as e:
|
|
398
|
+
logger.warning(f'Fail to find the valid link for go (url:{new_link}: {e}')
|
|
399
|
+
except Exception as error:
|
|
400
|
+
ret = False
|
|
401
|
+
logger.warning(f'Cannot find the link for go (url:{link}({(new_link)})): {error}')
|
|
31
402
|
|
|
32
403
|
return ret, new_link
|
|
33
404
|
|
|
34
405
|
|
|
406
|
+
def get_available_wheel_urls(name, version):
|
|
407
|
+
try:
|
|
408
|
+
api_url = f'https://pypi.org/pypi/{name}/{version}/json'
|
|
409
|
+
response = requests.get(api_url)
|
|
410
|
+
if response.status_code == 200:
|
|
411
|
+
data = response.json()
|
|
412
|
+
wheel_urls = []
|
|
413
|
+
|
|
414
|
+
for file_info in data.get('urls', []):
|
|
415
|
+
if file_info.get('packagetype') == 'bdist_wheel':
|
|
416
|
+
wheel_urls.append(file_info.get('url'))
|
|
417
|
+
|
|
418
|
+
return wheel_urls
|
|
419
|
+
else:
|
|
420
|
+
logger.warning(f'Cannot get PyPI API data for {name}({version})')
|
|
421
|
+
return []
|
|
422
|
+
|
|
423
|
+
except Exception as error:
|
|
424
|
+
logger.warning(f'Failed to get wheel URLs from PyPI API: {error}')
|
|
425
|
+
return []
|
|
426
|
+
|
|
427
|
+
|
|
35
428
|
def get_download_location_for_pypi(link):
|
|
36
|
-
# get the url for downloading source file
|
|
429
|
+
# get the url for downloading source file: https://docs.pypi.org/api/ Predictable URLs
|
|
37
430
|
ret = False
|
|
38
431
|
new_link = ''
|
|
432
|
+
host = 'https://files.pythonhosted.org'
|
|
39
433
|
|
|
40
434
|
try:
|
|
41
435
|
dn_loc_re = re.findall(r'pypi.org\/project\/?([^\/]*)\/?([^\/]*)', link)
|
|
42
436
|
oss_name = dn_loc_re[0][0]
|
|
437
|
+
oss_name = re.sub(r"[-_.]+", "-", oss_name)
|
|
43
438
|
oss_version = dn_loc_re[0][1]
|
|
44
439
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
card_file_list = bs_obj.findAll('div', {'class': 'card file__card'})
|
|
51
|
-
|
|
52
|
-
for card_file in card_file_list:
|
|
53
|
-
file_code = card_file.find('code').text
|
|
54
|
-
if file_code == "source":
|
|
55
|
-
new_link = card_file.find('a').attrs['href']
|
|
440
|
+
# 1. Source distribution 시도
|
|
441
|
+
new_link = f'{host}/packages/source/{oss_name[0]}/{oss_name}/{oss_name}-{oss_version}.tar.gz'
|
|
442
|
+
try:
|
|
443
|
+
res = urlopen(new_link)
|
|
444
|
+
if res.getcode() == 200:
|
|
56
445
|
ret = True
|
|
57
|
-
|
|
446
|
+
return ret, new_link
|
|
447
|
+
except Exception:
|
|
448
|
+
oss_name = re.sub(r"[-]+", "_", oss_name)
|
|
449
|
+
new_link = f'{host}/packages/source/{oss_name[0]}/{oss_name}/{oss_name}-{oss_version}.tar.gz'
|
|
450
|
+
try:
|
|
451
|
+
res = urlopen(new_link)
|
|
452
|
+
if res.getcode() == 200:
|
|
453
|
+
ret = True
|
|
454
|
+
return ret, new_link
|
|
455
|
+
except Exception:
|
|
456
|
+
pass
|
|
457
|
+
|
|
458
|
+
# 2. Source distribution이 없으면 wheel 파일들을 시도
|
|
459
|
+
wheel_urls = get_available_wheel_urls(oss_name, oss_version)
|
|
460
|
+
|
|
461
|
+
if wheel_urls:
|
|
462
|
+
# Pure Python wheel을 우선적으로 찾기
|
|
463
|
+
for wheel_url in wheel_urls:
|
|
464
|
+
if 'py3-none-any' in wheel_url or 'py2.py3-none-any' in wheel_url:
|
|
465
|
+
try:
|
|
466
|
+
res = urlopen(wheel_url)
|
|
467
|
+
if res.getcode() == 200:
|
|
468
|
+
ret = True
|
|
469
|
+
new_link = wheel_url
|
|
470
|
+
logger.info(f'Using wheel file : {wheel_url}')
|
|
471
|
+
return ret, new_link
|
|
472
|
+
except Exception:
|
|
473
|
+
continue
|
|
474
|
+
|
|
475
|
+
# Pure Python wheel이 없으면 첫 번째 wheel 시도
|
|
476
|
+
if wheel_urls:
|
|
477
|
+
try:
|
|
478
|
+
res = urlopen(wheel_urls[0])
|
|
479
|
+
if res.getcode() == 200:
|
|
480
|
+
ret = True
|
|
481
|
+
new_link = wheel_urls[0]
|
|
482
|
+
logger.info(f'Using wheel file : {wheel_urls[0]}')
|
|
483
|
+
return ret, new_link
|
|
484
|
+
except Exception:
|
|
485
|
+
pass
|
|
486
|
+
|
|
58
487
|
except Exception as error:
|
|
59
488
|
ret = False
|
|
60
|
-
logger.warning('Cannot find the link for pypi (url:
|
|
489
|
+
logger.warning(f'Cannot find the link for pypi (url:{link}({new_link})) e:{str(error)}')
|
|
61
490
|
|
|
62
491
|
return ret, new_link
|
|
63
492
|
|
|
@@ -83,6 +512,13 @@ def get_download_location_for_maven(link):
|
|
|
83
512
|
return ret, new_link
|
|
84
513
|
else:
|
|
85
514
|
dn_loc = 'https://' + link
|
|
515
|
+
elif link.startswith('dl.google.com/android/maven2/'):
|
|
516
|
+
if link.endswith('.jar'):
|
|
517
|
+
new_link = 'https://' + link
|
|
518
|
+
ret = True
|
|
519
|
+
return ret, new_link
|
|
520
|
+
else:
|
|
521
|
+
dn_loc = 'https://' + link
|
|
86
522
|
else:
|
|
87
523
|
raise Exception("not valid url for maven")
|
|
88
524
|
|
|
@@ -118,6 +554,7 @@ def get_download_location_for_npm(link):
|
|
|
118
554
|
oss_name_npm = ""
|
|
119
555
|
tar_name = ""
|
|
120
556
|
|
|
557
|
+
link = link.replace('%40', '@')
|
|
121
558
|
if link.startswith('www.npmjs.com/') or link.startswith('registry.npmjs.org/'):
|
|
122
559
|
try:
|
|
123
560
|
dn_loc_split = link.split('/')
|
|
@@ -133,16 +570,9 @@ def get_download_location_for_npm(link):
|
|
|
133
570
|
oss_name_npm = dn_loc_split[idx]
|
|
134
571
|
tar_name = oss_name_npm
|
|
135
572
|
oss_version = dn_loc_split[idx+2]
|
|
136
|
-
except Exception:
|
|
137
|
-
pass
|
|
138
573
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
stderr, stdout = npm_run('view', oss_name_npm, 'version')
|
|
142
|
-
if stdout:
|
|
143
|
-
oss_version = stdout.strip()
|
|
144
|
-
tar_name = f"{tar_name}-{oss_version}"
|
|
145
|
-
new_link = 'https://registry.npmjs.org/' + oss_name_npm + '/-/' + tar_name + '.tgz'
|
|
574
|
+
tar_name = f'{tar_name}-{oss_version}'
|
|
575
|
+
new_link = f'https://registry.npmjs.org/{oss_name_npm}/-/{tar_name}.tgz'
|
|
146
576
|
ret = True
|
|
147
577
|
except Exception as error:
|
|
148
578
|
ret = False
|
|
@@ -158,7 +588,7 @@ def get_download_location_for_pub(link):
|
|
|
158
588
|
# download url format : https://pub.dev/packages/(oss_name)/versions/(oss_version).tar.gz
|
|
159
589
|
try:
|
|
160
590
|
if link.startswith('pub.dev/packages'):
|
|
161
|
-
new_link = 'https://{link}.tar.gz'
|
|
591
|
+
new_link = f'https://{link}.tar.gz'
|
|
162
592
|
ret = True
|
|
163
593
|
|
|
164
594
|
except Exception as error:
|
fosslight_util/compare_yaml.py
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
# SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
import
|
|
7
|
+
from typing import List, Dict
|
|
8
8
|
from fosslight_util.constant import LOGGER_NAME
|
|
9
|
-
from fosslight_util.
|
|
9
|
+
from fosslight_util.oss_item import FileItem
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(LOGGER_NAME)
|
|
12
12
|
VERSION = 'version'
|
|
@@ -14,12 +14,16 @@ LICENSE = 'license'
|
|
|
14
14
|
NAME = 'name'
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def compare_yaml(
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
def compare_yaml(before_fileitems: List[FileItem], after_fileitems: List[FileItem]) -> Dict[str, List]:
|
|
18
|
+
bf_raw = []
|
|
19
|
+
af_raw = []
|
|
20
|
+
for bf in before_fileitems:
|
|
21
|
+
bf_raw.extend(bf.get_print_json())
|
|
22
|
+
for af in after_fileitems:
|
|
23
|
+
af_raw.extend(af.get_print_json())
|
|
20
24
|
|
|
21
|
-
before_items = get_merged_item(
|
|
22
|
-
after_items = get_merged_item(
|
|
25
|
+
before_items = get_merged_item(bf_raw)
|
|
26
|
+
after_items = get_merged_item(af_raw)
|
|
23
27
|
|
|
24
28
|
new_before = []
|
|
25
29
|
for bi in before_items:
|
|
@@ -72,13 +76,18 @@ def compare_yaml(before_file, after_file):
|
|
|
72
76
|
def get_merged_item(oss_items):
|
|
73
77
|
item_list = []
|
|
74
78
|
for oi in oss_items:
|
|
75
|
-
if oi.exclude:
|
|
79
|
+
if oi.get("exclude", None):
|
|
76
80
|
continue
|
|
77
|
-
|
|
81
|
+
oi_name = oi.get("name", '')
|
|
82
|
+
oi_version = oi.get("version", '')
|
|
83
|
+
oi_license = oi.get("license", '')
|
|
84
|
+
if not (oi_name and oi_version and oi_license):
|
|
85
|
+
continue
|
|
86
|
+
item_info = {NAME: oi_name, VERSION: oi_version, LICENSE: oi_license}
|
|
78
87
|
|
|
79
|
-
filtered = next(filter(lambda oss_dict: oss_dict[NAME] ==
|
|
88
|
+
filtered = next(filter(lambda oss_dict: oss_dict[NAME] == oi_name and oss_dict[VERSION] == oi_version, item_list), None)
|
|
80
89
|
if filtered:
|
|
81
|
-
filtered[LICENSE].extend(
|
|
90
|
+
filtered[LICENSE].extend(oi_license)
|
|
82
91
|
filtered[LICENSE] = list(set(filtered[LICENSE]))
|
|
83
92
|
else:
|
|
84
93
|
item_list.append(item_info)
|
fosslight_util/constant.py
CHANGED
|
@@ -14,3 +14,38 @@ supported_sheet_and_scanner = {'SRC': FL_SOURCE,
|
|
|
14
14
|
f'SRC_{FL_DEPENDENCY}': FL_DEPENDENCY,
|
|
15
15
|
f'BIN_{FL_BINARY}': FL_BINARY,
|
|
16
16
|
f'DEP_{FL_DEPENDENCY}': FL_DEPENDENCY}
|
|
17
|
+
|
|
18
|
+
FOSSLIGHT_SCANNER = 'fosslight_scanner'
|
|
19
|
+
FOSSLIGHT_SOURCE = 'fosslight_source'
|
|
20
|
+
FOSSLIGHT_DEPENDENCY = 'fosslight_dependency'
|
|
21
|
+
FOSSLIGHT_BINARY = 'fosslight_binary'
|
|
22
|
+
|
|
23
|
+
SHEET_NAME_FOR_SCANNER = {
|
|
24
|
+
FOSSLIGHT_SOURCE: 'SRC_FL_Source',
|
|
25
|
+
FOSSLIGHT_BINARY: 'BIN_FL_Binary',
|
|
26
|
+
FOSSLIGHT_DEPENDENCY: 'DEP_FL_Dependency'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Github : https://github.com/(owner)/(repo)
|
|
30
|
+
# npm : https://www.npmjs.com/package/(package)/v/(version)
|
|
31
|
+
# npm2 : https://www.npmjs.com/package/@(group)/(package)/v/(version)
|
|
32
|
+
# pypi : https://pypi.org/project/(oss_name)/(version)
|
|
33
|
+
# pypi2 : https://files.pythonhosted.org/packages/source/(alphabet)/(oss_name)/(oss_name)-(version).tar.gz
|
|
34
|
+
# Maven: https://mvnrepository.com/artifact/(group)/(artifact)/(version)
|
|
35
|
+
# pub: https://pub.dev/packages/(package)/versions/(version)
|
|
36
|
+
# Cocoapods : https://cocoapods.org/(package)
|
|
37
|
+
# go : https://pkg.go.dev/(package_name_with_slash)@(version)
|
|
38
|
+
# cargo : https://crates.io/crates/(crate_name)/(version)
|
|
39
|
+
PKG_PATTERN = {
|
|
40
|
+
"pypi": r'https?:\/\/pypi\.org\/project\/([^\/]+)[\/]?([^\/]*)',
|
|
41
|
+
"pypi2": r'https?:\/\/files\.pythonhosted\.org\/packages\/source\/[\w]\/([^\/]+)\/[\S]+-([^\-]+)\.tar\.gz',
|
|
42
|
+
"maven": r'https?:\/\/mvnrepository\.com\/artifact\/([^\/]+)\/([^\/]+)\/?([^\/]*)',
|
|
43
|
+
"maven_repo1": r'https?:\/\/repo1\.maven\.org\/maven2\/(.*)',
|
|
44
|
+
"maven_google": r'https?:\/\/dl\.google\.com\/android\/maven2\/(.*)',
|
|
45
|
+
"npm": r'https?:\/\/www\.npmjs\.com\/package\/([^\/\@]+)(?:\/v\/)?([^\/]*)',
|
|
46
|
+
"npm2": r'https?:\/\/www\.npmjs\.com\/package\/(\@[^\/]+\/[^\/]+)(?:\/v\/)?([^\/]*)',
|
|
47
|
+
"pub": r'https?:\/\/pub\.dev\/packages\/([^\/]+)(?:\/versions\/)?([^\/]*)',
|
|
48
|
+
"cocoapods": r'https?:\/\/cocoapods\.org\/pods\/([^\/]+)',
|
|
49
|
+
"go": r'https?:\/\/pkg.go.dev\/([^\@]+)\@?v?([^\/]*)',
|
|
50
|
+
"cargo": r'https?:\/\/crates\.io\/crates\/([^\/]+)\/?([^\/]*)',
|
|
51
|
+
}
|