Skip to content

Commit cc137eb

Browse files
pombredanneLi
authored andcommitted
Streamline license normalization #1403
* Clarify the cascade of conditions for readability. * Do not deduplicate detections (do not use a set) * Format code Signed-off-by: Philippe Ombredanne <pombredanne@nexb.com>
1 parent 4433e3e commit cc137eb

3 files changed

Lines changed: 112 additions & 83 deletions

File tree

src/packagedcode/maven.py

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -150,61 +150,80 @@ def compute_normalized_license(self):
150150
return compute_normalized_license(self.declared_license)
151151

152152

153-
def compute_normalized_license(listed_license_dictionary):
153+
def compute_normalized_license(declared_license):
154154
"""
155155
Return a detected license expression from a declared license mapping.
156156
"""
157-
if listed_license_dictionary:
158-
licensing = Licensing()
159-
# Use set instead of list to avoid duplication.
160-
detected_licenses = set()
161-
for license_declaration in listed_license_dictionary:
162-
name = license_declaration.get('name')
163-
url = license_declaration.get('url')
164-
comments = license_declaration.get('comments')
165-
# 1. try detection on the value of name if not empty and keep this
166-
# 2. try detection on the value of url if not empty and keep this
167-
# 3. try detection on the value of comment if not empty and keep this
168-
# 4. if the three detection are for the same license, this becomes the kept license for that one licenses item
169-
# 5. if not, the name should have precedence and any unknowns
157+
if not declared_license:
158+
return
159+
160+
licensing = Licensing()
161+
162+
detected_licenses = []
163+
164+
for license_declaration in declared_license:
165+
# 1. try detection on the value of name if not empty and keep this
166+
name = license_declaration.get('name')
167+
via_name = models.compute_normalized_license(name)
168+
169+
# 2. try detection on the value of url if not empty and keep this
170+
url = license_declaration.get('url')
171+
via_url = models.compute_normalized_license(url)
172+
173+
# 3. try detection on the value of comment if not empty and keep this
174+
comments = license_declaration.get('comments')
175+
via_comments = models.compute_normalized_license(comments)
176+
177+
178+
if via_name:
179+
# The name should have precedence and any unknowns
170180
# in url and comment should be ignored.
171-
via_name = models.compute_normalized_license(name)
172-
via_url = models.compute_normalized_license(url)
173-
via_comments = models.compute_normalized_license(comments)
174-
175-
if via_name:
176-
# The name should have precedence and any unknowns
177-
# in url and comment should be ignored.
178-
if via_url == 'unknown':
179-
via_url = None
180-
if via_comments == 'unknown':
181-
via_comments = None
182-
183-
if via_name and ((via_name == via_url and via_comments == via_url) or (via_name == via_url and not via_comments) or (via_name == via_comments and not via_url)):
184-
# if three detection are the same and not empty, return the value
185-
# or one of url or comments is empty and the non-empty one equals to the name value
186-
detected_licenses.add(via_name)
181+
if via_url == 'unknown':
182+
via_url = None
183+
if via_comments == 'unknown':
184+
via_comments = None
185+
186+
# Check the three detections to decide which license to keep
187+
name_and_url = via_name == via_url
188+
name_and_comment = via_name == via_comments
189+
all_same = name_and_url and name_and_comment
190+
191+
if via_name:
192+
if all_same:
193+
detected_licenses.append(via_name)
194+
195+
# name and (url or comment) are same
196+
elif name_and_url and not via_comments:
197+
detected_licenses.append(via_name)
198+
elif name_and_comment and not via_url:
199+
detected_licenses.append(via_name)
200+
187201
else:
188-
# Form a list and the element does not contain any None value, since the None value means 'unknown' or real empty value from above assignment.
189-
detected_items = [item for item in (via_name, via_url, via_comments) if item]
190-
if detected_items:
191-
if len(detected_items) == 1:
192-
detected_licenses.add(detected_items[0])
202+
# we have some non-unknown license detected in url or comment
203+
detections = via_name, via_url, via_comments
204+
detections = [l for l in detections if l]
205+
if detections:
206+
if len(detections) == 1:
207+
combined_expression = detections[0]
193208
else:
194-
# Combine if name, url and comments are different licenses
195-
licensing = Licensing()
196-
total_license_expression = [licensing.parse(detected_item, simple=True) for detected_item in detected_items]
197-
combined_expression_object = licensing.AND(*total_license_expression)
198-
detected_licenses.add(str(combined_expression_object))
199-
if detected_licenses:
200-
if len(detected_licenses) == 1:
201-
return str(detected_licenses.pop())
202-
else:
203-
# Combine if pom contains more than 1 licenses declarations.
204-
licensing = Licensing()
205-
total_license_expression = [licensing.parse(detected_license, simple=True) for detected_license in detected_licenses]
206-
combined_expression_object = licensing.AND(*total_license_expression)
207-
return str(combined_expression_object)
209+
expressions = [
210+
licensing.parse(le, simple=True) for le in detections]
211+
combined_expression = str(licensing.AND(*expressions))
212+
detected_licenses.append(combined_expression)
213+
214+
elif via_url:
215+
detected_licenses.append(via_url)
216+
elif via_comments:
217+
detected_licenses.append(via_comments)
218+
219+
if len(detected_licenses) == 1:
220+
return detected_licenses[0]
221+
222+
if detected_licenses:
223+
# Combine if pom contains more than one licenses declarations.
224+
expressions = [licensing.parse(le, simple=True) for le in detected_licenses]
225+
combined_expression = licensing.AND(*expressions)
226+
return str(combined_expression)
208227

209228

210229
def build_url(group_id, artifact_id, version, filename, baseurl='http://repo1.maven.org/maven2'):

tests/packagedcode/data/maven2/aopalliance-1.0.pom.package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"code_view_url": null,
2222
"vcs_url": null,
2323
"copyright": null,
24-
"license_expression": "gpl-1.0-plus AND public-domain",
24+
"license_expression": "public-domain AND gpl-1.0-plus",
2525
"declared_license": [
2626
{
2727
"name": "Public Domain",

tests/packagedcode/test_maven.py

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def test_package_root_is_properly_returned_for_metainf_poms(self):
222222
resource_attributes = dict(packages=attr.ib(default=attr.Factory(list), repr=False))
223223

224224
codebase = Codebase(test_dir, resource_attributes=resource_attributes)
225-
manifest_resource = [r for r in codebase.walk() if r.name=='pom.xml'][0]
225+
manifest_resource = [r for r in codebase.walk() if r.name == 'pom.xml'][0]
226226
package = maven.MavenPomPackage.recognize(manifest_resource.location)
227227
manifest_resource.packages.append(package.to_dict())
228228
manifest_resource.save(codebase)
@@ -359,82 +359,92 @@ class TestMavenComputeNormalizedLicense(testcase.FileBasedTesting):
359359
test_data_dir = os.path.join(os.path.dirname(__file__), 'data')
360360

361361
def test_compute_normalized_license_two_names_only(self):
362-
declared_license = [{'name': 'apache-2.0'},
363-
{'name': 'mit'}
364-
]
362+
declared_license = [
363+
{'name': 'apache-2.0'},
364+
{'name': 'mit'}
365+
]
365366
result = maven.compute_normalized_license(declared_license)
366367
expected = 'apache-2.0 AND mit'
367368
assert expected == result
368369

369370
def test_compute_normalized_license_tree_nodes(self):
370-
declared_license = [{'name': 'apache-2.0'},
371-
{'name': 'mit'}
372-
]
371+
declared_license = [
372+
{'name': 'apache-2.0'},
373+
{'name': 'mit'}
374+
]
373375
result = maven.compute_normalized_license(declared_license)
374376
expected = 'apache-2.0 AND mit'
375377
assert expected == result
376-
378+
377379
def test_compute_normalized_license_with_unknown_url(self):
378-
declared_license = [{'name': 'apache-2.0', 'url': 'unknown'},
379-
{'name': 'mit'}
380-
]
380+
declared_license = [
381+
{'name': 'apache-2.0', 'url': 'unknown'},
382+
{'name': 'mit'}
383+
]
381384
result = maven.compute_normalized_license(declared_license)
382385
expected = 'apache-2.0 AND mit'
383386
assert expected == result
384387

385388
def test_compute_normalized_license_with_unknown_url_known_comments(self):
386-
declared_license = [{'name': 'apache-2.0', 'url': 'unknown', 'comments': 'apache-2.0'},
387-
{'name': 'mit'}
388-
]
389+
declared_license = [
390+
{'name': 'apache-2.0', 'url': 'unknown', 'comments': 'apache-2.0'},
391+
{'name': 'mit'}
392+
]
389393
result = maven.compute_normalized_license(declared_license)
390394
expected = 'apache-2.0 AND mit'
391395
assert expected == result
392396

393397
def test_compute_normalized_license_with_unknown_url_unknown_comments(self):
394-
declared_license = [{'name': 'apache-2.0', 'url': 'unknown', 'comments': 'unknown'},
395-
{'name': 'mit'}
396-
]
398+
declared_license = [
399+
{'name': 'apache-2.0', 'url': 'unknown', 'comments': 'unknown'},
400+
{'name': 'mit'}
401+
]
397402
result = maven.compute_normalized_license(declared_license)
398403
expected = 'apache-2.0 AND mit'
399404
assert expected == result
400-
405+
401406
def test_compute_normalized_license_unknown_name(self):
402-
declared_license = [{'name': 'unknown', 'url': 'apache-2.0'},
403-
{'name': 'mit'}
404-
]
407+
declared_license = [
408+
{'name': 'unknown', 'url': 'apache-2.0'},
409+
{'name': 'mit'}
410+
]
405411
result = maven.compute_normalized_license(declared_license)
406412
expected = '(unknown AND apache-2.0) AND mit'
407413
assert expected == result
408414

409415
def test_compute_normalized_license_same_name_and_url(self):
410-
declared_license = [{'name': 'apache-2.0', 'url': 'apache-2.0'},
411-
{'name': 'mit'}
412-
]
416+
declared_license = [
417+
{'name': 'apache-2.0', 'url': 'apache-2.0'},
418+
{'name': 'mit'}
419+
]
413420
result = maven.compute_normalized_license(declared_license)
414421
expected = 'apache-2.0 AND mit'
415422
assert expected == result
416423

417424
def test_compute_normalized_license_same_name_url_comments(self):
418-
declared_license = [{'name': 'apache-2.0', 'url': 'apache-2.0', 'comments': 'apache-2.0'},
419-
{'name': 'mit'}
420-
]
425+
declared_license = [
426+
{'name': 'apache-2.0', 'url': 'apache-2.0', 'comments': 'apache-2.0'},
427+
{'name': 'mit'}
428+
]
421429
result = maven.compute_normalized_license(declared_license)
422430
expected = 'apache-2.0 AND mit'
423431
assert expected == result
424432

425433
def test_compute_normalized_license_with_url_invalid(self):
426-
declared_license = [{'name': 'MIT', 'url': 'LICENSE.txt'},
427-
]
434+
declared_license = [
435+
{'name': 'MIT', 'url': 'LICENSE.txt'},
436+
]
428437
result = maven.compute_normalized_license(declared_license)
429438
expected = 'mit'
430439
assert expected == result
431440

432441
def test_compute_normalized_license_with_duplicated_license(self):
433-
declared_license = [{'name': 'LGPL'},
434-
{'name': 'GNU Lesser General Public License', 'url': 'http://www.gnu.org/licenses/lgpl.html'},
435-
]
442+
declared_license = [
443+
{'name': 'LGPL'},
444+
{'name': 'GNU Lesser General Public License', 'url': 'http://www.gnu.org/licenses/lgpl.html'},
445+
]
436446
result = maven.compute_normalized_license(declared_license)
437-
expected = 'lgpl-2.0-plus'
447+
expected = 'lgpl-2.0-plus AND lgpl-2.0-plus'
438448
assert expected == result
439449

440450

0 commit comments

Comments
 (0)