-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'pa-spdx-concluded-license' of https://github.com/ferroc…
…ene/reuse-tool into feature/comments
- Loading branch information
Showing
7 changed files
with
403 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org> | ||
# SPDX-FileCopyrightText: 2022 Florian Snow <[email protected]> | ||
# SPDX-FileCopyrightText: 2022 Pietro Albini <[email protected]> | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
|
@@ -28,17 +29,21 @@ | |
class _MultiprocessingContainer: | ||
"""Container that remembers some data in order to generate a FileReport.""" | ||
|
||
def __init__(self, project, do_checksum): | ||
def __init__(self, project, do_checksum, add_license_concluded): | ||
self.project = project | ||
self.do_checksum = do_checksum | ||
self.add_license_concluded = add_license_concluded | ||
|
||
def __call__(self, file_): | ||
# pylint: disable=broad-except | ||
try: | ||
return _MultiprocessingResult( | ||
file_, | ||
FileReport.generate( | ||
self.project, file_, do_checksum=self.do_checksum | ||
self.project, | ||
file_, | ||
do_checksum=self.do_checksum, | ||
add_license_concluded=self.add_license_concluded, | ||
), | ||
None, | ||
) | ||
|
@@ -99,7 +104,12 @@ def to_dict(self): | |
"file_reports": [report.to_dict() for report in self.file_reports], | ||
} | ||
|
||
def bill_of_materials(self) -> str: | ||
def bill_of_materials( | ||
self, | ||
*, | ||
creator_person: Optional[str] = None, | ||
creator_organization: Optional[str] = None, | ||
) -> str: | ||
"""Generate a bill of materials from the project. | ||
See https://spdx.org/specifications. | ||
|
@@ -120,8 +130,10 @@ def bill_of_materials(self) -> str: | |
|
||
# Author | ||
# TODO: Fix Person and Organization | ||
out.write("Creator: Person: Anonymous ()\n") | ||
out.write("Creator: Organization: Anonymous ()\n") | ||
out.write(f"Creator: Person: {format_creator(creator_person)}\n") | ||
out.write( | ||
f"Creator: Organization: {format_creator(creator_organization)}\n" | ||
) | ||
out.write(f"Creator: Tool: reuse-{__version__}\n") | ||
|
||
now = datetime.datetime.utcnow() | ||
|
@@ -146,9 +158,9 @@ def bill_of_materials(self) -> str: | |
out.write(f"FileName: {report.spdxfile.name}\n") | ||
out.write(f"SPDXID: {report.spdxfile.spdx_id}\n") | ||
out.write(f"FileChecksum: SHA1: {report.spdxfile.chk_sum}\n") | ||
# IMPORTANT: Make no assertion about concluded license. This tool | ||
# cannot, with full certainty, determine the license of a file. | ||
out.write("LicenseConcluded: NOASSERTION\n") | ||
out.write( | ||
f"LicenseConcluded: {report.spdxfile.license_concluded}\n" | ||
) | ||
|
||
for lic in sorted(report.spdxfile.licenses_in_file): | ||
out.write(f"LicenseInfoInFile: {lic}\n") | ||
|
@@ -184,6 +196,7 @@ def generate( | |
project: Project, | ||
do_checksum: bool = True, | ||
multiprocessing: bool = cpu_count() > 1, | ||
add_license_concluded: bool = False, | ||
) -> "ProjectReport": | ||
"""Generate a ProjectReport from a Project.""" | ||
project_report = cls(do_checksum=do_checksum) | ||
|
@@ -193,7 +206,9 @@ def generate( | |
project.licenses_without_extension | ||
) | ||
|
||
container = _MultiprocessingContainer(project, do_checksum) | ||
container = _MultiprocessingContainer( | ||
project, do_checksum, add_license_concluded | ||
) | ||
|
||
if multiprocessing: | ||
with mp.Pool() as pool: | ||
|
@@ -313,6 +328,7 @@ def __init__(self, name, spdx_id=None, chk_sum=None): | |
self.spdx_id: str = spdx_id | ||
self.chk_sum: str = chk_sum | ||
self.licenses_in_file: List[str] = [] | ||
self.license_concluded: str = None | ||
self.copyright: str = None | ||
self.comment: str = None | ||
|
||
|
@@ -345,7 +361,11 @@ def to_dict(self): | |
|
||
@classmethod | ||
def generate( | ||
cls, project: Project, path: PathLike, do_checksum: bool = True | ||
cls, | ||
project: Project, | ||
path: PathLike, | ||
do_checksum: bool = True, | ||
add_license_concluded: bool = False, | ||
) -> "FileReport": | ||
"""Generate a FileReport from a path in a Project.""" | ||
path = Path(path) | ||
|
@@ -386,7 +406,27 @@ def generate( | |
# Add license to report. | ||
report.spdxfile.licenses_in_file.append(identifier) | ||
|
||
# Copyright text and comment | ||
if not add_license_concluded: | ||
report.spdxfile.license_concluded = "NOASSERTION" | ||
elif not spdx_info.spdx_expressions: | ||
report.spdxfile.license_concluded = "NONE" | ||
else: | ||
# Merge all the license expressions together, wrapping them in | ||
# parentheses to make sure an expression doesn't spill into another | ||
# one. The extra parentheses will be removed by the roundtrip | ||
# through parse() -> simplify() -> render(). | ||
report.spdxfile.license_concluded = ( | ||
_LICENSING.parse( | ||
" AND ".join( | ||
f"({expression})" | ||
for expression in spdx_info.spdx_expressions | ||
), | ||
) | ||
.simplify() | ||
.render() | ||
) | ||
|
||
# Copyright text | ||
report.spdxfile.copyright = "\n".join(sorted(spdx_info.copyright_lines)) | ||
report.spdxfile.comment = spdx_info.comment | ||
|
||
|
@@ -396,3 +436,13 @@ def __hash__(self): | |
if self.spdxfile.chk_sum is not None: | ||
return hash(self.spdxfile.name + self.spdxfile.chk_sum) | ||
return super().__hash__(self) | ||
|
||
|
||
def format_creator(creator): | ||
"""Render the creator field based on the provided flag""" | ||
if creator is None: | ||
return "Anonymous ()" | ||
if "(" in creator and creator.endswith(")"): | ||
# The creator field already contains an email address | ||
return creator | ||
return creator + " ()" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org> | ||
# SPDX-FileCopyrightText: 2022 Pietro Albini <[email protected]> | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
|
@@ -22,10 +23,47 @@ def add_arguments(parser) -> None: | |
parser.add_argument( | ||
"--output", "-o", dest="file", action="store", type=PathType("w") | ||
) | ||
parser.add_argument( | ||
"--add-license-concluded", | ||
action="store_true", | ||
help=_( | ||
"Populate the LicenseConcluded field. Note that REUSE cannot " | ||
"guarantee the field is accurate." | ||
), | ||
) | ||
parser.add_argument( | ||
"--creator-person", | ||
metavar="NAME", | ||
help=_("Name of the person signing off on the SPDX report"), | ||
) | ||
parser.add_argument( | ||
"--creator-organization", | ||
metavar="NAME", | ||
help=_("Name of the organization signing off on the SPDX report"), | ||
) | ||
|
||
|
||
def run(args, project: Project, out=sys.stdout) -> int: | ||
"""Print the project's bill of materials.""" | ||
# The SPDX spec mandates that a creator must be specified when a license | ||
# conclusion is made, so here we enforce that. More context: | ||
# | ||
# https://github.com/fsfe/reuse-tool/issues/586#issuecomment-1310425706 | ||
# | ||
if ( | ||
args.add_license_concluded | ||
and args.creator_person is None | ||
and args.creator_organization is None | ||
): | ||
print( | ||
_( | ||
"error: --creator-person=NAME or --creator-organization=NAME" | ||
" required when --add-license-concluded is provided" | ||
), | ||
file=sys.stderr, | ||
) | ||
return 1 | ||
|
||
with contextlib.ExitStack() as stack: | ||
if args.file: | ||
out = stack.enter_context(args.file.open("w", encoding="utf-8")) | ||
|
@@ -43,9 +81,16 @@ def run(args, project: Project, out=sys.stdout) -> int: | |
) | ||
|
||
report = ProjectReport.generate( | ||
project, multiprocessing=not args.no_multiprocessing | ||
project, | ||
multiprocessing=not args.no_multiprocessing, | ||
add_license_concluded=args.add_license_concluded, | ||
) | ||
|
||
out.write(report.bill_of_materials()) | ||
out.write( | ||
report.bill_of_materials( | ||
creator_person=args.creator_person, | ||
creator_organization=args.creator_organization, | ||
) | ||
) | ||
|
||
return 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org> | ||
# SPDX-FileCopyrightText: 2022 Florian Snow <[email protected]> | ||
# SPDX-FileCopyrightText: 2022 Carmen Bianca Bakker <[email protected]> | ||
# SPDX-FileCopyrightText: 2022 Pietro Albini <[email protected]> | ||
# | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
|
@@ -84,6 +85,11 @@ def multiprocessing(request, monkeypatch) -> bool: | |
yield request.param | ||
|
||
|
||
@pytest.fixture(params=[True, False]) | ||
def add_license_concluded(request) -> bool: | ||
yield request | ||
|
||
|
||
@pytest.fixture() | ||
def empty_directory(tmpdir_factory) -> Path: | ||
"""Create a temporary empty directory.""" | ||
|
@@ -117,6 +123,13 @@ def fake_repository(tmpdir_factory) -> Path: | |
"SPDX-License-Identifier: LicenseRef-custom", | ||
encoding="utf-8", | ||
) | ||
(directory / "src/multiple_licenses.rs").write_text( | ||
"SPDX-FileCopyrightText: 2022 Jane Doe\n" | ||
"SPDX-License-Identifier: GPL-3.0-or-later\n" | ||
"SPDX-License-Identifier: Apache-2.0 OR CC0-1.0" | ||
" WITH Autoconf-exception-3.0\n", | ||
encoding="utf-8", | ||
) | ||
|
||
os.chdir(directory) | ||
return directory | ||
|
@@ -162,15 +175,7 @@ def git_repository(fake_repository: Path, git_exe: Optional[str]) -> Path: | |
) | ||
|
||
subprocess.run([git_exe, "add", str(fake_repository)], check=True) | ||
subprocess.run( | ||
[ | ||
git_exe, | ||
"commit", | ||
"-m", | ||
"initial", | ||
], | ||
check=True, | ||
) | ||
git_commit(git_exe, "initial") | ||
|
||
return fake_repository | ||
|
||
|
@@ -227,15 +232,7 @@ def submodule_repository( | |
) | ||
|
||
subprocess.run([git_exe, "add", str(submodule)], check=True) | ||
subprocess.run( | ||
[ | ||
git_exe, | ||
"commit", | ||
"-m", | ||
"initial", | ||
], | ||
check=True, | ||
) | ||
git_commit(git_exe, "initial") | ||
|
||
os.chdir(git_repository) | ||
|
||
|
@@ -256,15 +253,7 @@ def submodule_repository( | |
], | ||
check=True, | ||
) | ||
subprocess.run( | ||
[ | ||
git_exe, | ||
"commit", | ||
"-m", | ||
"add submodule", | ||
], | ||
check=True, | ||
) | ||
git_commit(git_exe, "add submodule") | ||
|
||
(git_repository / ".gitmodules.license").write_text(header) | ||
|
||
|
@@ -367,4 +356,22 @@ def mock_date_today(monkeypatch): | |
monkeypatch.setattr(datetime, "date", date) | ||
|
||
|
||
def git_commit(git_exe, message): | ||
subprocess.run( | ||
[ | ||
git_exe, | ||
# Git can be globally configured to digitally sign all commits, | ||
# which causes problems when running the test suite, as the user | ||
# would be prompted to authorize the signing multiple times per | ||
# test execution. The option below configures git to skip signing. | ||
"-c", | ||
"commit.gpgSign=false", | ||
"commit", | ||
"-m", | ||
message, | ||
], | ||
check=True, | ||
) | ||
|
||
|
||
# REUSE-IgnoreEnd |
Oops, something went wrong.