fosslight-util 2.1.28__py3-none-any.whl → 2.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_util/_get_downloadable_url.py +31 -5
- fosslight_util/download.py +78 -11
- fosslight_util/get_pom_license.py +120 -0
- {fosslight_util-2.1.28.dist-info → fosslight_util-2.1.30.dist-info}/METADATA +1 -2
- {fosslight_util-2.1.28.dist-info → fosslight_util-2.1.30.dist-info}/RECORD +9 -8
- {fosslight_util-2.1.28.dist-info → fosslight_util-2.1.30.dist-info}/WHEEL +0 -0
- {fosslight_util-2.1.28.dist-info → fosslight_util-2.1.30.dist-info}/entry_points.txt +0 -0
- {fosslight_util-2.1.28.dist-info → fosslight_util-2.1.30.dist-info}/licenses/LICENSE +0 -0
- {fosslight_util-2.1.28.dist-info → fosslight_util-2.1.30.dist-info}/top_level.txt +0 -0
|
@@ -499,13 +499,39 @@ def get_download_location_for_maven(link):
|
|
|
499
499
|
|
|
500
500
|
try:
|
|
501
501
|
if link.startswith('mvnrepository.com/artifact/'):
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
502
|
+
parts = link.replace('mvnrepository.com/artifact/', '').split('/')
|
|
503
|
+
if len(parts) < 2:
|
|
504
|
+
raise Exception('invalid mvnrepository artifact url')
|
|
505
|
+
group_raw = parts[0]
|
|
506
|
+
artifact_id = parts[1]
|
|
507
|
+
version = parts[2] if len(parts) > 2 and parts[2] else ''
|
|
508
|
+
group_path = group_raw.replace('.', '/')
|
|
509
|
+
|
|
510
|
+
repo_base = f'https://repo1.maven.org/maven2/{group_path}/{artifact_id}'
|
|
511
|
+
try:
|
|
512
|
+
urlopen(repo_base)
|
|
513
|
+
if version:
|
|
514
|
+
dn_loc = f'{repo_base}/{version}'
|
|
515
|
+
else:
|
|
516
|
+
new_link = repo_base
|
|
517
|
+
ret = True
|
|
518
|
+
return ret, new_link
|
|
519
|
+
except Exception:
|
|
520
|
+
google_base = f'https://dl.google.com/android/maven2/{group_path}/{artifact_id}'
|
|
521
|
+
if version:
|
|
522
|
+
google_sources = f'{google_base}/{version}/{artifact_id}-{version}-sources.jar'
|
|
523
|
+
try:
|
|
524
|
+
res_g = urlopen(google_sources)
|
|
525
|
+
if res_g.getcode() == 200:
|
|
526
|
+
ret = True
|
|
527
|
+
return ret, google_sources
|
|
528
|
+
except Exception:
|
|
529
|
+
pass
|
|
530
|
+
new_link = google_base
|
|
531
|
+
ret = True
|
|
532
|
+
return ret, new_link
|
|
505
533
|
|
|
506
534
|
elif link.startswith('repo1.maven.org/maven2/'):
|
|
507
|
-
dn_loc_split = link.replace('repo1.maven.org/maven2/', '').split('/')
|
|
508
|
-
|
|
509
535
|
if link.endswith('.tar.gz') or link.endswith('.jar') or link.endswith('.tar.xz'):
|
|
510
536
|
new_link = 'https://' + link
|
|
511
537
|
ret = True
|
fosslight_util/download.py
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
-
import
|
|
7
|
+
import requests
|
|
8
8
|
import tarfile
|
|
9
9
|
import zipfile
|
|
10
10
|
import logging
|
|
@@ -58,6 +58,22 @@ def alarm_handler(signum, frame):
|
|
|
58
58
|
raise TimeOutException(f'Timeout ({SIGNAL_TIMEOUT} sec)', 1)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def is_downloadable(url):
|
|
62
|
+
try:
|
|
63
|
+
h = requests.head(url, allow_redirects=True)
|
|
64
|
+
header = h.headers
|
|
65
|
+
content_type = header.get('content-type')
|
|
66
|
+
if 'text/html' in content_type.lower():
|
|
67
|
+
return False
|
|
68
|
+
content_disposition = header.get('content-disposition')
|
|
69
|
+
if content_disposition and 'attachment' in content_disposition.lower():
|
|
70
|
+
return True
|
|
71
|
+
return True
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.warning(f"is_downloadable - failed: {e}")
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
61
77
|
def change_src_link_to_https(src_link):
|
|
62
78
|
src_link = src_link.replace("git://", "https://")
|
|
63
79
|
if src_link.endswith(".git"):
|
|
@@ -381,25 +397,30 @@ def download_wget(link, target_dir, compressed_only, checkout_to):
|
|
|
381
397
|
link = new_link
|
|
382
398
|
|
|
383
399
|
if compressed_only:
|
|
400
|
+
# Check if link ends with known compression extensions
|
|
384
401
|
for ext in compression_extension:
|
|
385
402
|
if link.endswith(ext):
|
|
386
403
|
success = True
|
|
387
404
|
break
|
|
388
|
-
if not success:
|
|
389
|
-
if pkg_type == 'cargo':
|
|
390
|
-
success = True
|
|
391
405
|
else:
|
|
392
|
-
|
|
406
|
+
# If get_downloadable_url found a downloadable file, proceed
|
|
407
|
+
if ret:
|
|
408
|
+
success = True
|
|
409
|
+
else:
|
|
410
|
+
# No downloadable file found in package repositories, verify link is downloadable
|
|
411
|
+
if not is_downloadable(link):
|
|
412
|
+
raise Exception('Not a downloadable link (link:{0})'.format(link))
|
|
413
|
+
success = True
|
|
393
414
|
|
|
415
|
+
# Fallback: verify link is downloadable for compressed_only case
|
|
394
416
|
if not success:
|
|
395
|
-
|
|
417
|
+
if is_downloadable(link):
|
|
418
|
+
success = True
|
|
419
|
+
else:
|
|
420
|
+
raise Exception('Not a downloadable link (link:{0})'.format(link))
|
|
396
421
|
|
|
397
422
|
logger.info(f"wget: {link}")
|
|
398
|
-
|
|
399
|
-
outfile = os.path.join(target_dir, f'{oss_name}.tar.gz')
|
|
400
|
-
downloaded_file = wget.download(link, out=outfile)
|
|
401
|
-
else:
|
|
402
|
-
downloaded_file = wget.download(link, target_dir)
|
|
423
|
+
downloaded_file = download_file(link, target_dir)
|
|
403
424
|
if platform.system() != "Windows":
|
|
404
425
|
signal.alarm(0)
|
|
405
426
|
else:
|
|
@@ -416,6 +437,49 @@ def download_wget(link, target_dir, compressed_only, checkout_to):
|
|
|
416
437
|
return success, downloaded_file, msg, oss_name, oss_version
|
|
417
438
|
|
|
418
439
|
|
|
440
|
+
def download_file(url, target_dir):
|
|
441
|
+
local_path = ""
|
|
442
|
+
try:
|
|
443
|
+
try:
|
|
444
|
+
h = requests.head(url, allow_redirects=True)
|
|
445
|
+
final_url = h.url or url
|
|
446
|
+
headers = h.headers
|
|
447
|
+
except Exception:
|
|
448
|
+
final_url = url
|
|
449
|
+
headers = {}
|
|
450
|
+
|
|
451
|
+
with requests.get(final_url, stream=True, allow_redirects=True) as r:
|
|
452
|
+
r.raise_for_status()
|
|
453
|
+
|
|
454
|
+
filename = ""
|
|
455
|
+
cd = r.headers.get("Content-Disposition") or headers.get("Content-Disposition")
|
|
456
|
+
if cd:
|
|
457
|
+
m_star = re.search(r"filename\*=(?:UTF-8'')?([^;\r\n]+)", cd)
|
|
458
|
+
if m_star:
|
|
459
|
+
filename = urllib.parse.unquote(m_star.group(1).strip('"\''))
|
|
460
|
+
else:
|
|
461
|
+
m = re.search(r"filename=([^;\r\n]+)", cd)
|
|
462
|
+
if m:
|
|
463
|
+
filename = m.group(1).strip('"\'')
|
|
464
|
+
if not filename:
|
|
465
|
+
final_for_name = r.url or final_url
|
|
466
|
+
filename = os.path.basename(urllib.parse.urlparse(final_for_name).path)
|
|
467
|
+
if not filename:
|
|
468
|
+
filename = "downloaded_file"
|
|
469
|
+
if os.path.isdir(target_dir):
|
|
470
|
+
local_path = os.path.join(target_dir, filename)
|
|
471
|
+
else:
|
|
472
|
+
local_path = target_dir
|
|
473
|
+
|
|
474
|
+
with open(local_path, 'wb') as f:
|
|
475
|
+
for chunk in r.iter_content(chunk_size=8192):
|
|
476
|
+
f.write(chunk)
|
|
477
|
+
except Exception as e:
|
|
478
|
+
logger.warning(f"download_file - failed: {e}")
|
|
479
|
+
return None
|
|
480
|
+
return local_path
|
|
481
|
+
|
|
482
|
+
|
|
419
483
|
def extract_compressed_dir(src_dir, target_dir, remove_after_extract=True):
|
|
420
484
|
logger.debug(f"Extract Dir: {src_dir}")
|
|
421
485
|
try:
|
|
@@ -450,6 +514,9 @@ def extract_compressed_file(fname, extract_path, remove_after_extract=True, comp
|
|
|
450
514
|
decompress_bz2(fname, extract_path)
|
|
451
515
|
elif fname.endswith(".whl"):
|
|
452
516
|
unzip(fname, extract_path)
|
|
517
|
+
elif fname.endswith(".crate"):
|
|
518
|
+
with contextlib.closing(tarfile.open(fname, "r:gz")) as t:
|
|
519
|
+
t.extractall(path=extract_path)
|
|
453
520
|
else:
|
|
454
521
|
is_compressed_file = False
|
|
455
522
|
if compressed_only:
|
|
@@ -0,0 +1,120 @@
|
|
|
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 urllib.request
|
|
9
|
+
from urllib.error import URLError, HTTPError
|
|
10
|
+
from defusedxml.ElementTree import fromstring as xml_fromstring
|
|
11
|
+
import fosslight_util.constant as constant
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(constant.LOGGER_NAME)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_license_from_pom(group_id: str = None,
|
|
17
|
+
artifact_id: str = None,
|
|
18
|
+
version: str = None,
|
|
19
|
+
pom_path: str = None,
|
|
20
|
+
check_parent: bool = True) -> str:
|
|
21
|
+
|
|
22
|
+
def build_urls(g, a, v):
|
|
23
|
+
group_path = g.replace('.', '/')
|
|
24
|
+
name = f"{a}-{v}.pom"
|
|
25
|
+
repo1 = f"https://repo1.maven.org/maven2/{group_path}/{a}/{v}/{name}"
|
|
26
|
+
google = f"https://dl.google.com/android/maven2/{group_path}/{a}/{v}/{name}"
|
|
27
|
+
return [repo1, google]
|
|
28
|
+
|
|
29
|
+
def fetch_pom(g, a, v):
|
|
30
|
+
for url in build_urls(g, a, v):
|
|
31
|
+
try:
|
|
32
|
+
with urllib.request.urlopen(url) as resp:
|
|
33
|
+
return resp.read().decode('utf-8')
|
|
34
|
+
except (HTTPError, URLError):
|
|
35
|
+
continue
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
def extract_licenses(root):
|
|
39
|
+
licenses_elem = root.find('{*}licenses')
|
|
40
|
+
if licenses_elem is not None:
|
|
41
|
+
names = []
|
|
42
|
+
for lic in licenses_elem.findall('{*}license'):
|
|
43
|
+
name = lic.findtext('{*}name')
|
|
44
|
+
if name:
|
|
45
|
+
names.append(name.replace(',', ''))
|
|
46
|
+
if names:
|
|
47
|
+
return ', '.join(names)
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def extract_parent_info(root):
|
|
51
|
+
parent = root.find('{*}parent')
|
|
52
|
+
if parent is not None:
|
|
53
|
+
g = parent.findtext('{*}groupId') or parent.findtext('groupId')
|
|
54
|
+
a = parent.findtext('{*}artifactId') or parent.findtext('artifactId')
|
|
55
|
+
v = parent.findtext('{*}version') or parent.findtext('version')
|
|
56
|
+
if g and a and v:
|
|
57
|
+
return g, a, v
|
|
58
|
+
return None, None, None
|
|
59
|
+
|
|
60
|
+
visited = set()
|
|
61
|
+
|
|
62
|
+
def find_license_in_pom_recursive(g, a, v, check_parent_flag):
|
|
63
|
+
key = (g, a, v)
|
|
64
|
+
if key in visited:
|
|
65
|
+
return ''
|
|
66
|
+
visited.add(key)
|
|
67
|
+
content = fetch_pom(g, a, v)
|
|
68
|
+
if not content:
|
|
69
|
+
return ''
|
|
70
|
+
try:
|
|
71
|
+
root = xml_fromstring(content)
|
|
72
|
+
except Exception:
|
|
73
|
+
return ''
|
|
74
|
+
licenses = extract_licenses(root)
|
|
75
|
+
if licenses:
|
|
76
|
+
return licenses
|
|
77
|
+
if not check_parent_flag:
|
|
78
|
+
return ''
|
|
79
|
+
pg, pa, pv = extract_parent_info(root)
|
|
80
|
+
if pg and pa and pv:
|
|
81
|
+
return find_license_in_pom_recursive(pg, pa, pv, check_parent_flag)
|
|
82
|
+
return ''
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
if pom_path:
|
|
86
|
+
if not os.path.exists(pom_path):
|
|
87
|
+
logger.warning(f"POM file not found: {pom_path}")
|
|
88
|
+
return ''
|
|
89
|
+
try:
|
|
90
|
+
with open(pom_path, 'r', encoding='utf-8') as f:
|
|
91
|
+
content = f.read()
|
|
92
|
+
xml_start = content.find('<?xml')
|
|
93
|
+
if xml_start > 0:
|
|
94
|
+
content = content[xml_start:]
|
|
95
|
+
elif xml_start == -1:
|
|
96
|
+
root_start = content.find('<project')
|
|
97
|
+
if root_start > 0:
|
|
98
|
+
content = content[root_start:]
|
|
99
|
+
root = xml_fromstring(content)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.warning(f"Failed to parse POM file {pom_path}: {e}")
|
|
102
|
+
return ''
|
|
103
|
+
|
|
104
|
+
licenses = extract_licenses(root)
|
|
105
|
+
if licenses:
|
|
106
|
+
return licenses
|
|
107
|
+
if not check_parent:
|
|
108
|
+
return ''
|
|
109
|
+
pg, pa, pv = extract_parent_info(root)
|
|
110
|
+
if pg and pa and pv:
|
|
111
|
+
return find_license_in_pom_recursive(pg, pa, pv, check_parent)
|
|
112
|
+
|
|
113
|
+
logger.debug(f"No license info found in local POM: {pom_path}, Retry with remote fetch.")
|
|
114
|
+
|
|
115
|
+
if not (group_id and artifact_id and version):
|
|
116
|
+
return ''
|
|
117
|
+
return find_license_in_pom_recursive(group_id, artifact_id, version, check_parent)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.warning(f"Error getting license from POM: {e}")
|
|
120
|
+
return ''
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fosslight_util
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.30
|
|
4
4
|
Summary: FOSSLight Util
|
|
5
5
|
Home-page: https://github.com/fosslight/fosslight_util
|
|
6
6
|
Download-URL: https://github.com/fosslight/fosslight_util
|
|
@@ -20,7 +20,6 @@ Requires-Dist: progress
|
|
|
20
20
|
Requires-Dist: PyYAML
|
|
21
21
|
Requires-Dist: lastversion
|
|
22
22
|
Requires-Dist: coloredlogs
|
|
23
|
-
Requires-Dist: python3-wget
|
|
24
23
|
Requires-Dist: beautifulsoup4
|
|
25
24
|
Requires-Dist: jsonmerge
|
|
26
25
|
Requires-Dist: spdx-tools==0.8.*; sys_platform == "linux"
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
fosslight_util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
fosslight_util/_get_downloadable_url.py,sha256=
|
|
2
|
+
fosslight_util/_get_downloadable_url.py,sha256=Komp1ODhqCKbnqGl7T-EDT8KmQuvgOeZ-nkV2bT7E-s,25519
|
|
3
3
|
fosslight_util/compare_yaml.py,sha256=eLqqCLgERxRHN5vsnpQVMXIEU862Lx66mD_y4uMgQE4,2916
|
|
4
4
|
fosslight_util/constant.py,sha256=3BzJtyxC0o5IFAhYaUW-DeTKkA6f5tDJFJTb0k5ji9Y,2418
|
|
5
5
|
fosslight_util/correct.py,sha256=1WEAL-9_KhjFPLucPhv0PNN3K7avm0z8mU6sTuSyeHM,3864
|
|
6
6
|
fosslight_util/cover.py,sha256=qqqKzxqFwKimal764FaugRUBcHWdeKt8af6xeK0mH8E,2040
|
|
7
|
-
fosslight_util/download.py,sha256=
|
|
7
|
+
fosslight_util/download.py,sha256=AWwD3FWhF-bMagWINJ-Dg1VMuycXbe8VXX7qQ09YjYs,23565
|
|
8
8
|
fosslight_util/exclude.py,sha256=fDmBsZJ_F7O9Oh2T-07R03XNbElo1tFaf_z01KfSAqU,2399
|
|
9
|
+
fosslight_util/get_pom_license.py,sha256=x4_RHpM91s01j1OUWpKIxUjTGDH6y5AxTNDGkMajs4I,4253
|
|
9
10
|
fosslight_util/help.py,sha256=iyWmAaUQSHJtWv5mjFv0f3YoDVlDgEqdsDDEyImEUNc,2646
|
|
10
11
|
fosslight_util/oss_item.py,sha256=8890JHb5ZoKQWAwN7Fl8badnlYatJtF4MVJz1rdS4yQ,6938
|
|
11
12
|
fosslight_util/output_format.py,sha256=BP23LspxawDZ_a99oWLVKWUQ-G7P5uoUpjEXhkRFKwc,8801
|
|
@@ -24,9 +25,9 @@ fosslight_util/write_yaml.py,sha256=QlEKoIPQsEaYERfbP53TeKgnllYzhLQWm5wYjnWtVjE,
|
|
|
24
25
|
fosslight_util/resources/frequentLicenselist.json,sha256=GUhzK6tu7ok10fekOnmVmUgIGRC-acGABZKTNKfDyYA,4776157
|
|
25
26
|
fosslight_util/resources/frequent_license_nick_list.json,sha256=ryU2C_6ZxHbz90_sUN9OvI9GXkCMLu7oGcmd9W79YYo,5005
|
|
26
27
|
fosslight_util/resources/licenses.json,sha256=mK55z-bhY7Mjpj2KsO1crKGGL-X3F6MBFQJ0zLlx010,240843
|
|
27
|
-
fosslight_util-2.1.
|
|
28
|
-
fosslight_util-2.1.
|
|
29
|
-
fosslight_util-2.1.
|
|
30
|
-
fosslight_util-2.1.
|
|
31
|
-
fosslight_util-2.1.
|
|
32
|
-
fosslight_util-2.1.
|
|
28
|
+
fosslight_util-2.1.30.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
29
|
+
fosslight_util-2.1.30.dist-info/METADATA,sha256=-0EbMo4HMlTn1KyTXOFa8_s4yaWtrhh3QQmmDTroV_w,6339
|
|
30
|
+
fosslight_util-2.1.30.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
|
31
|
+
fosslight_util-2.1.30.dist-info/entry_points.txt,sha256=0yZggRWNwDaClDG8UmUA10UFG8cVX3Jiy5gG9nW7hJs,68
|
|
32
|
+
fosslight_util-2.1.30.dist-info/top_level.txt,sha256=2qyYWGLakgBRy4BqoBNt-I5C29tBr_e93e5e1pbuTGA,15
|
|
33
|
+
fosslight_util-2.1.30.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|