Skip to content

Upgrade the 'File' class to the SPDX 2.1 specification #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions data/SPDXRdfExample.rdf
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
</ExternalDocumentRef>
</externalDocumentRef>
<referencesFile>
<File rdf:nodeID="A0">
<File rdf:about="https://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301#SPDXRef-File1">>
<licenseConcluded>
<ExtractedLicensingInfo rdf:nodeID="A1">
<extractedText>/*
Expand Down Expand Up @@ -97,7 +97,7 @@
</Review>
</reviewed>
<referencesFile>
<File rdf:nodeID="A2">
<File rdf:about="https://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301#SPDXRef-File2">
<copyrightText>Copyright 2010, 2011 Source Auditor Inc.</copyrightText>
<licenseComments></licenseComments>
<licenseInfoInFile rdf:resource="http://spdx.org/licenses/Apache-2.0"/>
Expand Down
1 change: 1 addition & 0 deletions data/SPDXSimpleTag.tag
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ PackageLicenseComments: <text>License Comments</text>
# File Info

FileName: testfile.java
SPDXID: SPDXRef-File
FileType: SOURCE
FileChecksum: SHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
LicenseConcluded: Apache-2.0
Expand Down
2 changes: 2 additions & 0 deletions data/SPDXTagExample.tag
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ PackageLicenseComments: <text>The declared license information can be found in t

## File Information
FileName: src/org/spdx/parser/DOAPProject.java
SPDXID: SPDXRef-File1
FileType: SOURCE
FileChecksum: SHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
LicenseConcluded: Apache-2.0
LicenseInfoInFile: Apache-2.0
FileCopyrightText: <text>Copyright 2010, 2011 Source Auditor Inc.</text>

FileName: Jenna-2.6.3/jena-2.6.3-sources.jar
SPDXID: SPDXRef-File2
FileType: ARCHIVE
FileChecksum: SHA1: 3ab4e1c67a2d28fced849ee1bb76e7391b93f125
LicenseConcluded: LicenseRef-1
Expand Down
4 changes: 2 additions & 2 deletions spdx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def validate(self, messages=None):

return (self.validate_ext_doc_id(messages) and
self.validate_spdx_doc_uri(messages) and
self.validate_chksum(messages)
self.validate_checksum(messages)
)

def validate_ext_doc_id(self, messages=None):
Expand All @@ -81,7 +81,7 @@ def validate_spdx_doc_uri(self, messages=None):
messages.append('ExternalDocumentRef has no SPDX Document URI.')
return False

def validate_chksum(self, messages=None):
def validate_checksum(self, messages=None):
messages = messages if messages is not None else []

if self.check_sum:
Expand Down
22 changes: 18 additions & 4 deletions spdx/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class File(object):
Represent an SPDX file.
Fields:
- name: File name, str mandatory one.
- spdx_id: Uniquely identify any element in an SPDX document which may be
referenced by other elements. Mandatory, one. Type: str.
- comment: File comment str, Optional zero or one.
- type: one of FileType.SOURCE, FileType.BINARY, FileType.ARCHIVE
and FileType.OTHER, optional zero or one.
Expand All @@ -54,8 +56,9 @@ class File(object):
- artifact_of_project_uri: list of project uris, possibly empty.
"""

def __init__(self, name, chk_sum=None):
def __init__(self, name, spdx_id=None, chk_sum=None):
self.name = name
self.spdx_id = spdx_id
self.comment = None
self.type = None
self.chk_sum = chk_sum
Expand Down Expand Up @@ -102,10 +105,21 @@ def validate(self, messages=None):

return (self.validate_concluded_license(messages)
and self.validate_type(messages)
and self.validate_chksum(messages)
and self.validate_checksum(messages)
and self.validate_licenses_in_file(messages)
and self.validate_copyright(messages)
and self.validate_artifacts(messages))
and self.validate_artifacts(messages)
and self.validate_spdx_id(messages))

def validate_spdx_id(self, messages=None):
# FIXME: messages should be returned
messages = messages if messages is not None else []

if self.spdx_id is None:
messages.append('File has no SPDX Identifier.')
return False
else:
return True

def validate_copyright(self, messages=None):
# FIXME: messages should be returned
Expand Down Expand Up @@ -164,7 +178,7 @@ def validate_type(self, messages=None):
messages.append('File type must be one of the constants defined in class spdx.file.FileType')
return False

def validate_chksum(self, messages=None):
def validate_checksum(self, messages=None):
# FIXME: messages should be returned
messages = messages if messages is not None else []

Expand Down
2 changes: 1 addition & 1 deletion spdx/parsers/lexers/tagvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Lexer(object):
'SPDXVersion': 'DOC_VERSION',
'DataLicense': 'DOC_LICENSE',
'DocumentName': 'DOC_NAME',
'SPDXID': 'DOC_SPDX_ID',
'SPDXID': 'SPDX_ID',
'DocumentComment': 'DOC_COMMENT',
'DocumentNamespace': 'DOC_NAMESPACE',
'ExternalDocumentRef': 'EXT_DOC_REF',
Expand Down
12 changes: 12 additions & 0 deletions spdx/parsers/rdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
'LICS_LIST_MEMBER' : 'Declaritive or Conjunctive license set member must be a license url or identifier',
'PKG_SINGLE_LICS' : 'Package concluded license must be a license url or spdx:noassertion or spdx:none.',
'PKG_LICS_INFO_FILES' : 'Package licenseInfoFromFiles must be a license or spdx:none or spdx:noassertion',
'FILE_SPDX_ID_VALUE': 'SPDXID must be "SPDXRef-[idstring]" where [idstring] is a unique string containing '
'letters, numbers, ".", "-".',
'FILE_TYPE' : 'File type must be binary, other, source or archive term.',
'FILE_SINGLE_LICS': 'File concluded license must be a license url or spdx:noassertion or spdx:none.',
'REVIEWER_VALUE' : 'Invalid reviewer value \'{0}\' must be Organization, Tool or Person.',
Expand Down Expand Up @@ -515,6 +517,7 @@ def parse_file(self, f_term):
for _, _, name in self.graph.triples((f_term, self.spdx_namespace['fileName'], None)):
self.builder.set_file_name(self.doc, six.text_type(name))

self.p_file_spdx_id(f_term, self.spdx_namespace['File'])
self.p_file_type(f_term, self.spdx_namespace['fileType'])
self.p_file_chk_sum(f_term, self.spdx_namespace['checksum'])
self.p_file_lic_conc(f_term, self.spdx_namespace['licenseConcluded'])
Expand Down Expand Up @@ -613,6 +616,15 @@ def p_file_lic_info(self, f_term, predicate):
if lic is not None:
self.builder.set_file_license_in_file(self.doc, lic)

def p_file_spdx_id(self, f_term, predicate):
try:
try:
self.builder.set_file_spdx_id(self.doc, f_term)
except SPDXValueError:
self.value_error('FILE_SPDX_ID_VALUE', f_term)
except CardinalityError:
self.more_than_one_error('FILE_SPDX_ID_VALUE')

def p_file_type(self, f_term, predicate):
"""Sets file type."""
try:
Expand Down
36 changes: 14 additions & 22 deletions spdx/parsers/tagvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
'FILE_NAME_VALUE': 'FileName must be a single line of text, line: {0}',
'FILE_COMMENT_VALUE': 'FileComment must be free form text, line:{0}',
'FILE_TYPE_VALUE': 'FileType must be one of OTHER, BINARY, SOURCE or ARCHIVE, line: {0}',
'FILE_SPDX_ID_VALUE': 'SPDXID must be "SPDXRef-[idstring]" where [idstring] is a unique string containing '
'letters, numbers, ".", "-".',
'FILE_CHKSUM_VALUE': 'FileChecksum must be a single line of text starting with \'SHA1:\', line:{0}',
'FILE_LICS_CONC_VALUE': 'LicenseConcluded must be NOASSERTION, NONE, license identifier or license list, line:{0}',
'FILE_LICS_INFO_VALUE': 'LicenseInfoInFile must be NOASSERTION, NONE or license identifier, line: {0}',
Expand Down Expand Up @@ -113,9 +115,9 @@ def p_start_2(self, p):

def p_attrib(self, p):
"""attrib : spdx_version
| spdx_id
| data_lics
| doc_name
| doc_spdx_id
| ext_doc_ref
| doc_comment
| doc_namespace
Expand Down Expand Up @@ -539,6 +541,17 @@ def p_file_name_2(self, p):
msg = ERROR_MESSAGES['FILE_NAME_VALUE'].format(p.lineno(1))
self.logger.log(msg)

def p_spdx_id(self, p):
"""spdx_id : SPDX_ID LINE"""
if six.PY2:
value = p[2].decode(encoding='utf-8')
else:
value = p[2]
if not self.builder.doc_spdx_id_set:
self.builder.set_doc_spdx_id(self.document, value)
else:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will need to add support for packages there too, right (doc, package, file)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very simple and clean approach BTW 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will need to add support for packages there too, right (doc, package, file)?

Yes, the only thing that remains is the support of packages. I plan to include that in the PR that contains support of the package class.

self.builder.set_file_spdx_id(self.document, value)

def p_file_comment_1(self, p):
"""file_comment : FILE_COMMENT TEXT"""
try:
Expand Down Expand Up @@ -1148,27 +1161,6 @@ def p_doc_name_2(self, p):
msg = ERROR_MESSAGES['DOC_NAME_VALUE'].format(p.lineno(1))
self.logger.log(msg)

def p_doc_spdx_id_1(self, p):
"""doc_spdx_id : DOC_SPDX_ID LINE"""
try:
if six.PY2:
value = p[2].decode(encoding='utf-8')
else:
value = p[2]
self.builder.set_doc_spdx_id(self.document, value)
except SPDXValueError:
self.error = True
msg = ERROR_MESSAGES['DOC_SPDX_ID_VALUE'].format(p.lineno(2))
self.logger.log(msg)
except CardinalityError:
self.more_than_one_error('SPDXID', p.lineno(1))

def p_doc_spdx_id_2(self, p):
"""doc_spdx_id : DOC_SPDX_ID error"""
self.error = True
msg = ERROR_MESSAGES['DOC_SPDX_ID_VALUE'].format(p.lineno(1))
self.logger.log(msg)

def p_ext_doc_refs_1(self, p):
"""ext_doc_ref : EXT_DOC_REF DOC_REF_ID DOC_URI EXT_DOC_REF_CHKSUM"""
try:
Expand Down
21 changes: 21 additions & 0 deletions spdx/parsers/tagvaluebuilders.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,26 @@ def set_file_name(self, doc, name):
else:
raise OrderError('File::Name')

def set_file_spdx_id(self, doc, spdx_id):
"""
Sets the file SPDX Identifier.
Raises OrderError if no package or no file defined.
Raises SPDXValueError if malformed value.
Raises CardinalityError if more than one spdx_id set.
"""
if self.has_package(doc) and self.has_file(doc):
if not self.file_spdx_id_set:
self.file_spdx_id_set = True
if validations.validate_file_spdx_id(spdx_id):
self.file(doc).spdx_id = spdx_id
return True
else:
raise SPDXValueError('File::SPDXID')
else:
raise CardinalityError('File::SPDXID')
else:
raise OrderError('File::SPDXID')

def set_file_comment(self, doc, text):
"""
Raises OrderError if no package or no file defined.
Expand Down Expand Up @@ -916,6 +936,7 @@ def has_package(self, doc):
def reset_file_stat(self):
"""Resets the builder's state to enable building new files."""
# FIXME: this state does not make sense
self.file_spdx_id_set = False
self.file_comment_set = False
self.file_type_set = False
self.file_chksum_set = False
Expand Down
9 changes: 9 additions & 0 deletions spdx/parsers/validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ def validate_pkg_lics_comment(value, optional=False):
return validate_is_free_form_text(value, optional)


def validate_file_spdx_id(value, optional=False):
value = value.split('#')[-1]
TEXT_RE = re.compile(r'SPDXRef-([A-Za-z0-9.\-]+)', re.UNICODE)
if value is None:
return optional
else:
return TEXT_RE.match(value) is not None


def validate_file_comment(value, optional=False):
return validate_is_free_form_text(value, optional)

Expand Down
5 changes: 4 additions & 1 deletion spdx/writers/rdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from __future__ import print_function
from __future__ import unicode_literals

import uuid

from rdflib import BNode
from rdflib import Graph
from rdflib import Literal
Expand Down Expand Up @@ -208,7 +210,8 @@ def create_file_node(self, doc_file):
"""
Create a node for spdx.file.
"""
file_node = BNode()
file_node = URIRef('http://www.spdx.org/files#{id}'.format(
id=str(doc_file.spdx_id)))
type_triple = (file_node, RDF.type, self.spdx_namespace.File)
self.graph.add(type_triple)

Expand Down
1 change: 1 addition & 0 deletions spdx/writers/tagvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def write_file(spdx_file, out):
"""
out.write('# File\n\n')
write_value('FileName', spdx_file.name, out)
write_value('SPDXID', spdx_file.spdx_id, out)
if spdx_file.has_optional_field('type'):
write_file_type(spdx_file.type, out)
write_value('FileChecksum', spdx_file.chk_sum.to_tv(), out)
Expand Down
5 changes: 4 additions & 1 deletion tests/data/doc_write/rdf-simple-plus.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"ns1:SpdxDocument": {
"ns1:describesPackage": {
"ns1:Package": {
"ns1:hasFile": null,
"ns1:hasFile": {
"@rdf:resource": "http://www.spdx.org/files#SPDXRef-File"
},
"ns1:name": "some/path",
"ns1:licenseDeclared": {
"@rdf:resource": "http://spdx.org/rdf/terms#noassertion"
Expand All @@ -31,6 +33,7 @@
},
"ns1:referencesFile": {
"ns1:File": {
"@rdf:about": "http://www.spdx.org/files#SPDXRef-File",
"ns1:fileName": "./some/path/tofile",
"ns1:checksum": {
"ns1:Checksum": {
Expand Down
11 changes: 7 additions & 4 deletions tests/data/doc_write/rdf-simple.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"ns1:SpdxDocument": {
"ns1:describesPackage": {
"ns1:Package": {
"ns1:hasFile": null,
"ns1:hasFile": {
"@rdf:resource": "http://www.spdx.org/files#SPDXRef-File"
},
"ns1:downloadLocation": {
"@rdf:resource": "http://spdx.org/rdf/terms#noassertion"
},
Expand All @@ -31,15 +33,16 @@
},
"ns1:referencesFile": {
"ns1:File": {
"@rdf:about": "http://www.spdx.org/files#SPDXRef-File",
"ns1:licenseInfoInFile": {
"@rdf:resource": "http://spdx.org/licenses/LGPL-2.1"
},
},
"ns1:checksum": {
"ns1:Checksum": {
"ns1:checksumValue": "SOME-SHA1",
"ns1:checksumValue": "SOME-SHA1",
"ns1:algorithm": "SHA1"
}
},
},
"ns1:licenseConcluded": {
"@rdf:resource": "http://spdx.org/rdf/terms#noassertion"
},
Expand Down
1 change: 1 addition & 0 deletions tests/data/doc_write/tv-simple-plus.tv
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ PackageLicenseInfoFromFiles: LGPL-2.1+
PackageCopyrightText: <text>Some copyrught</text>
# File
FileName: ./some/path/tofile
SPDXID: SPDXRef-File
FileChecksum: SHA1: SOME-SHA1
LicenseConcluded: NOASSERTION
LicenseInfoInFile: LGPL-2.1+
Expand Down
1 change: 1 addition & 0 deletions tests/data/doc_write/tv-simple.tv
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ PackageLicenseInfoFromFiles: LGPL-2.1
PackageCopyrightText: <text>Some copyrught</text>
# File
FileName: ./some/path/tofile
SPDXID: SPDXRef-File
FileChecksum: SHA1: SOME-SHA1
LicenseConcluded: NOASSERTION
LicenseInfoInFile: LGPL-2.1
Expand Down
3 changes: 3 additions & 0 deletions tests/test_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def test_document_validate_failures_returns_informative_messages(self):
pack = doc.package = Package('some/path', NoAssert())
file1 = File('./some/path/tofile')
file1.name = './some/path/tofile'
file1.spdx_id = 'SPDXRef-File'
file1.chk_sum = Algorithm('SHA1', 'SOME-SHA1')
lic1 = License.from_identifier('LGPL-2.1')
file1.add_lics(lic1)
Expand Down Expand Up @@ -108,6 +109,7 @@ def test_document_is_valid_when_using_or_later_licenses(self):

file1 = File('./some/path/tofile')
file1.name = './some/path/tofile'
file1.spdx_id = 'SPDXRef-File'
file1.chk_sum = Algorithm('SHA1', 'SOME-SHA1')
file1.conc_lics = NoAssert()
file1.copyright = NoAssert()
Expand Down Expand Up @@ -140,6 +142,7 @@ def _get_lgpl_doc(self, or_later=False):

file1 = File('./some/path/tofile')
file1.name = './some/path/tofile'
file1.spdx_id = 'SPDXRef-File'
file1.chk_sum = Algorithm('SHA1', 'SOME-SHA1')
file1.conc_lics = NoAssert()
file1.copyright = NoAssert()
Expand Down
Loading