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