|
| 1 | +# SPDX-FileCopyrightText: 2023 spdx contributors |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: Apache-2.0 |
| 4 | +import logging |
| 5 | +from datetime import datetime |
| 6 | +from typing import List |
| 7 | + |
| 8 | +from license_expression import get_spdx_licensing |
| 9 | + |
| 10 | +from spdx_tools.spdx.model import ( |
| 11 | + Actor, |
| 12 | + ActorType, |
| 13 | + Checksum, |
| 14 | + ChecksumAlgorithm, |
| 15 | + CreationInfo, |
| 16 | + Document, |
| 17 | + ExternalPackageRef, |
| 18 | + ExternalPackageRefCategory, |
| 19 | + File, |
| 20 | + FileType, |
| 21 | + Package, |
| 22 | + PackagePurpose, |
| 23 | + PackageVerificationCode, |
| 24 | + Relationship, |
| 25 | + RelationshipType, |
| 26 | +) |
| 27 | +from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document |
| 28 | +from spdx_tools.spdx.validation.validation_message import ValidationMessage |
| 29 | + |
| 30 | +# This example shows how to use the spdx-tools to create an SPDX document from scratch, |
| 31 | +# validate it and write it to a file. |
| 32 | + |
| 33 | + |
| 34 | +def test_spdx2_document_from_scratch(): |
| 35 | + # First up, we need general information about the creation of the document, summarised by the CreationInfo class. |
| 36 | + creation_info = CreationInfo( |
| 37 | + spdx_version="SPDX-2.3", |
| 38 | + spdx_id="SPDXRef-DOCUMENT", |
| 39 | + name="document name", |
| 40 | + data_license="CC0-1.0", |
| 41 | + document_namespace="https://some.namespace", |
| 42 | + creators=[Actor(ActorType.PERSON, "Jane Doe", "jane.doe@example.com")], |
| 43 | + created=datetime(2022, 1, 1), |
| 44 | + ) |
| 45 | + |
| 46 | + # creation_info is the only required property of the Document class (have a look there!), |
| 47 | + # the rest are optional lists. |
| 48 | + # So, we are set up to create a new document instance. |
| 49 | + document = Document(creation_info) |
| 50 | + |
| 51 | + # The document currently does not describe anything. Let's create a package that we can add to it. |
| 52 | + # The Package class has quite a few properties (have a look there!), |
| 53 | + # but only name, spdx_id and download_location are mandatory in SPDX v2.3. |
| 54 | + package = Package( |
| 55 | + name="package name", |
| 56 | + spdx_id="SPDXRef-Package", |
| 57 | + download_location="https://download.com", |
| 58 | + version="2.2.1", |
| 59 | + file_name="./foo.bar", |
| 60 | + supplier=Actor(ActorType.PERSON, "Jane Doe", "jane.doe@example.com"), |
| 61 | + originator=Actor(ActorType.ORGANIZATION, "some organization", "contact@example.com"), |
| 62 | + files_analyzed=True, |
| 63 | + verification_code=PackageVerificationCode( |
| 64 | + value="d6a770ba38583ed4bb4525bd96e50461655d2758", excluded_files=["./some.file"] |
| 65 | + ), |
| 66 | + checksums=[ |
| 67 | + Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"), |
| 68 | + Checksum(ChecksumAlgorithm.MD5, "624c1abb3664f4b35547e7c73864ad24"), |
| 69 | + ], |
| 70 | + license_concluded=get_spdx_licensing().parse("GPL-2.0-only OR MIT"), |
| 71 | + license_info_from_files=[get_spdx_licensing().parse("GPL-2.0-only"), get_spdx_licensing().parse("MIT")], |
| 72 | + license_declared=get_spdx_licensing().parse("GPL-2.0-only AND MIT"), |
| 73 | + license_comment="license comment", |
| 74 | + copyright_text="Copyright 2022 Jane Doe", |
| 75 | + description="package description", |
| 76 | + attribution_texts=["package attribution"], |
| 77 | + primary_package_purpose=PackagePurpose.LIBRARY, |
| 78 | + release_date=datetime(2015, 1, 1), |
| 79 | + external_references=[ |
| 80 | + ExternalPackageRef( |
| 81 | + category=ExternalPackageRefCategory.OTHER, |
| 82 | + reference_type="http://reference.type", |
| 83 | + locator="reference/locator", |
| 84 | + comment="external reference comment", |
| 85 | + ) |
| 86 | + ], |
| 87 | + ) |
| 88 | + |
| 89 | + # Now that we have a package defined, we can add it to the document's package property. |
| 90 | + document.packages = [package] |
| 91 | + |
| 92 | + # A DESCRIBES relationship asserts that the document indeed describes the package. |
| 93 | + describes_relationship = Relationship("SPDXRef-DOCUMENT", RelationshipType.DESCRIBES, "SPDXRef-Package") |
| 94 | + document.relationships = [describes_relationship] |
| 95 | + |
| 96 | + # Let's add two files. Have a look at the file class for all possible properties a file can have. |
| 97 | + file1 = File( |
| 98 | + name="./package/file1.py", |
| 99 | + spdx_id="SPDXRef-File1", |
| 100 | + file_types=[FileType.SOURCE], |
| 101 | + checksums=[ |
| 102 | + Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2758"), |
| 103 | + Checksum(ChecksumAlgorithm.MD5, "624c1abb3664f4b35547e7c73864ad24"), |
| 104 | + ], |
| 105 | + license_concluded=get_spdx_licensing().parse("MIT"), |
| 106 | + license_info_in_file=[get_spdx_licensing().parse("MIT")], |
| 107 | + copyright_text="Copyright 2022 Jane Doe", |
| 108 | + ) |
| 109 | + file2 = File( |
| 110 | + name="./package/file2.py", |
| 111 | + spdx_id="SPDXRef-File2", |
| 112 | + checksums=[ |
| 113 | + Checksum(ChecksumAlgorithm.SHA1, "d6a770ba38583ed4bb4525bd96e50461655d2759"), |
| 114 | + ], |
| 115 | + license_concluded=get_spdx_licensing().parse("GPL-2.0-only"), |
| 116 | + ) |
| 117 | + |
| 118 | + # Assuming the package contains those two files, we create two CONTAINS relationships. |
| 119 | + contains_relationship1 = Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File1") |
| 120 | + contains_relationship2 = Relationship("SPDXRef-Package", RelationshipType.CONTAINS, "SPDXRef-File2") |
| 121 | + |
| 122 | + # This library uses run-time type checks when assigning properties. |
| 123 | + # Because in-place alterations like .append() circumvent these checks, we don't use them here. |
| 124 | + document.relationships += [contains_relationship1, contains_relationship2] |
| 125 | + document.files += [file1, file2] |
| 126 | + |
| 127 | + # We now have created a document with basic creation information, describing a package that contains two files. |
| 128 | + # You can also add Annotations, Snippets and ExtractedLicensingInfo |
| 129 | + # to the document in an analogous manner to the above. |
| 130 | + # Have a look at their respective classes if you are unsure about their properties. |
| 131 | + assert len(document.packages) == 1 |
| 132 | + assert len(document.files) == 2 |
| 133 | + assert len(document.relationships) == 3 |
| 134 | + assert len(document.snippets) == 0 |
| 135 | + assert len(document.annotations) == 0 |
| 136 | + assert len(document.extracted_licensing_info) == 0 |
| 137 | + |
| 138 | + # This library provides comprehensive validation against the SPDX specification. |
| 139 | + # Note that details of the validation depend on the SPDX version of the document. |
| 140 | + validation_messages: List[ValidationMessage] = validate_full_spdx_document(document) |
| 141 | + |
| 142 | + # You can have a look at each entry's message and context (like spdx_id, parent_id, full_element) |
| 143 | + # which will help you pinpoint the location of the invalidity. |
| 144 | + for message in validation_messages: |
| 145 | + logging.warning(message.validation_message) |
| 146 | + logging.warning(message.context) |
| 147 | + |
| 148 | + # If the document is valid, validation_messages will be empty. |
| 149 | + assert validation_messages == [] |
0 commit comments