Skip to content

Commit

Permalink
Merge pull request #314 from carmenbianca/license-plus
Browse files Browse the repository at this point in the history
Better support for unary "+" operator in license identifiers
  • Loading branch information
carmenbianca authored Jan 29, 2022
2 parents 973c0ea + 745e5ec commit af6abf1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 9 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ The versions follow [semantic versioning](https://semver.org).

### Fixed

- Better support for unary "+" operator in license identifiers. For example, if
`Apache-1.0+` appears as a declared license, it should not be identified as
missing, bad, or unused if `LICENSES/Apache-1.0.txt` exists. It is, however,
identified separately as a used license. (#123)

- When `addheader` creates a `.license` file, that file now has a newline at the
end. (#477)

Expand Down
28 changes: 19 additions & 9 deletions src/reuse/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,11 @@ def used_licenses(self) -> Set[str]:
if self._used_licenses is not None:
return self._used_licenses

self._used_licenses = set(self.licenses) - self.unused_licenses
self._used_licenses = {
lic
for file_report in self.file_reports
for lic in file_report.spdxfile.licenses_in_file
}
return self._used_licenses

@property
Expand All @@ -257,15 +261,16 @@ def unused_licenses(self) -> Set[str]:
if self._unused_licenses is not None:
return self._unused_licenses

all_used_licenses = {
lic
for file_report in self.file_reports
for lic in file_report.spdxfile.licenses_in_file
# First collect licenses that are suspected to be unused.
suspected_unused_licenses = {
lic for lic in self.licenses if lic not in self.used_licenses
}
# Remove false positives.
self._unused_licenses = {
lic for lic in self.licenses if lic not in all_used_licenses
lic
for lic in suspected_unused_licenses
if f"{lic}+" not in self.used_licenses
}

return self._unused_licenses

@property
Expand Down Expand Up @@ -365,11 +370,16 @@ def generate(
spdx_info = project.spdx_info_of(path)
for expression in spdx_info.spdx_expressions:
for identifier in _LICENSING.license_keys(expression):
# A license expression akin to Apache-1.0+ should register
# correctly if LICENSES/Apache-1.0.txt exists.
identifiers = {identifier}
if identifier.endswith("+"):
identifiers.add(identifier[:-1])
# Bad license
if identifier not in project.license_map:
if not identifiers.intersection(project.license_map):
report.bad_licenses.add(identifier)
# Missing license
if identifier not in project.licenses:
if not identifiers.intersection(project.licenses):
report.missing_licenses.add(identifier)

# Add license to report.
Expand Down
58 changes: 58 additions & 0 deletions tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ def test_generate_file_report_file_bad_license(fake_repository):
assert result.missing_licenses == {"fakelicense"}


def test_generate_file_report_license_contains_plus(fake_repository):
"""Given a license expression akin to Apache-1.0+, LICENSES/Apache-1.0.txt
should be an appropriate license file.
"""
(fake_repository / "foo.py").write_text(
"SPDX" "-License-Identifier: Apache-1.0+"
)
(fake_repository / "LICENSES/Apache-1.0.txt").touch()
project = Project(fake_repository)
result = FileReport.generate(project, "foo.py")

assert result.spdxfile.copyright == ""
assert not result.bad_licenses
assert not result.missing_licenses


def test_generate_file_report_exception(fake_repository):
"""Simple generate test to test if the exception is detected."""
project = Project(fake_repository)
Expand Down Expand Up @@ -153,6 +169,48 @@ def test_generate_project_report_unused_license(
assert result.unused_licenses == {"MIT"}


def test_generate_project_report_unused_license_plus(
fake_repository, multiprocessing
):
"""Apache-1.0+ is not an unused license if LICENSES/Apache-1.0.txt
exists.
Furthermore, Apache-1.0+ is separately identified as a used license.
"""
(fake_repository / "foo.py").write_text(
"SPDX" "-License-Identifier: Apache-1.0+"
)
(fake_repository / "bar.py").write_text(
"SPDX" "-License-Identifier: Apache-1.0"
)
(fake_repository / "LICENSES/Apache-1.0.txt").touch()

project = Project(fake_repository)
result = ProjectReport.generate(project, multiprocessing=multiprocessing)

assert not result.unused_licenses
assert {"Apache-1.0", "Apache-1.0+"}.issubset(result.used_licenses)


def test_generate_project_report_unused_license_plus_only_plus(
fake_repository, multiprocessing
):
"""If Apache-1.0+ is the only declared license in the project,
LICENSES/Apache-1.0.txt should not be an unused license.
"""
(fake_repository / "foo.py").write_text(
"SPDX" "-License-Identifier: Apache-1.0+"
)
(fake_repository / "LICENSES/Apache-1.0.txt").touch()

project = Project(fake_repository)
result = ProjectReport.generate(project, multiprocessing=multiprocessing)

assert not result.unused_licenses
assert "Apache-1.0+" in result.used_licenses
assert "Apache-1.0" not in result.used_licenses


def test_generate_project_report_bad_license_in_file(
fake_repository, multiprocessing
):
Expand Down

0 comments on commit af6abf1

Please sign in to comment.