2 # SPDX-License-Identifier: GPL-2.0
3 # Copyright Thomas Gleixner <tglx@linutronix.de>
5 from argparse
import ArgumentParser
6 from ply
import lex
, yacc
13 class ParserException(Exception):
14 def __init__(self
, tok
, txt
):
18 class SPDXException(Exception):
19 def __init__(self
, el
, txt
):
23 class SPDXdata(object):
25 self
.license_files
= 0
26 self
.exception_files
= 0
30 # Read the spdx data from the LICENSES directory
31 def read_spdxdata(repo
):
33 # The subdirectories of LICENSES in the kernel source
34 license_dirs
= [ "preferred", "other", "exceptions" ]
35 lictree
= repo
.heads
.master
.commit
.tree
['LICENSES']
39 for d
in license_dirs
:
40 for el
in lictree
[d
].traverse():
41 if not os
.path
.isfile(el
.path
):
45 for l
in open(el
.path
).readlines():
46 if l
.startswith('Valid-License-Identifier:'):
47 lid
= l
.split(':')[1].strip().upper()
48 if lid
in spdx
.licenses
:
49 raise SPDXException(el
, 'Duplicate License Identifier: %s' %lid
)
51 spdx
.licenses
.append(lid
)
53 elif l
.startswith('SPDX-Exception-Identifier:'):
54 exception
= l
.split(':')[1].strip().upper()
55 spdx
.exceptions
[exception
] = []
57 elif l
.startswith('SPDX-Licenses:'):
58 for lic
in l
.split(':')[1].upper().strip().replace(' ', '').replace('\t', '').split(','):
59 if not lic
in spdx
.licenses
:
60 raise SPDXException(None, 'Exception %s missing license %s' %(ex
, lic
))
61 spdx
.exceptions
[exception
].append(lic
)
63 elif l
.startswith("License-Text:"):
65 if not len(spdx
.exceptions
[exception
]):
66 raise SPDXException(el
, 'Exception %s is missing SPDX-Licenses' %excid)
67 spdx
.exception_files
+= 1
69 spdx
.license_files
+= 1
73 class id_parser(object):
75 reserved
= [ 'AND', 'OR', 'WITH' ]
76 tokens
= [ 'LPAR', 'RPAR', 'ID', 'EXC' ] + reserved
78 precedence
= ( ('nonassoc', 'AND', 'OR'), )
82 def __init__(self
, spdx
):
86 self
.lexer
= lex
.lex(module
= self
, reflags
= re
.UNICODE
)
87 # Initialize the parser. No debug file and no parser rules stored on disk
88 # The rules are small enough to be generated on the fly
89 self
.parser
= yacc
.yacc(module
= self
, write_tables
= False, debug
= False)
90 self
.lines_checked
= 0
97 # Validate License and Exception IDs
98 def validate(self
, tok
):
99 id = tok
.value
.upper()
101 if not id in self
.spdx
.licenses
:
102 raise ParserException(tok
, 'Invalid License ID')
104 elif tok
.type == 'EXC':
105 if not self
.spdx
.exceptions
.has_key(id):
106 raise ParserException(tok
, 'Invalid Exception ID')
107 if self
.lastid
not in self
.spdx
.exceptions
[id]:
108 raise ParserException(tok
, 'Exception not valid for license %s' %self
.lastid
)
110 elif tok
.type != 'WITH':
114 def t_RPAR(self
, tok
):
116 self
.lasttok
= tok
.type
119 def t_LPAR(self
, tok
):
121 self
.lasttok
= tok
.type
127 if self
.lasttok
== 'EXC':
129 raise ParserException(tok
, 'Missing parentheses')
131 tok
.value
= tok
.value
.strip()
132 val
= tok
.value
.upper()
134 if val
in self
.reserved
:
136 elif self
.lasttok
== 'WITH':
139 self
.lasttok
= tok
.type
143 def t_error(self
, tok
):
144 raise ParserException(tok
, 'Invalid token')
154 def p_error(self
, p
):
156 raise ParserException(None, 'Unfinished license expression')
158 raise ParserException(p
, 'Syntax error')
160 def parse(self
, expr
):
163 self
.parser
.parse(expr
, lexer
= self
.lexer
)
165 def parse_lines(self
, fd
, maxlines
, fname
):
171 if self
.curline
> maxlines
:
173 self
.lines_checked
+= 1
174 if line
.find("SPDX-License-Identifier:") < 0:
176 expr
= line
.split(':')[1].replace('*/', '').strip()
180 # Should we check for more SPDX ids in the same file and
181 # complain if there are any?
185 except ParserException
as pe
:
187 col
= line
.find(expr
) + pe
.tok
.lexpos
189 sys
.stdout
.write('%s: %d:%d %s: %s\n' %(fname
, self
.curline
, col
, pe
.txt
, tok
))
191 sys
.stdout
.write('%s: %d:0 %s\n' %(fname
, self
.curline
, col
, pe
.txt
))
192 self
.spdx_errors
+= 1
194 def scan_git_tree(tree
):
195 for el
in tree
.traverse():
196 # Exclude stuff which would make pointless noise
197 # FIXME: Put this somewhere more sensible
198 if el
.path
.startswith("LICENSES"):
200 if el
.path
.find("license-rules.rst") >= 0:
202 if el
.path
== 'scripts/checkpatch.pl':
204 if not os
.path
.isfile(el
.path
):
206 parser
.parse_lines(open(el
.path
), args
.maxlines
, el
.path
)
208 def scan_git_subtree(tree
, path
):
209 for p
in path
.strip('/').split('/'):
213 if __name__
== '__main__':
215 ap
= ArgumentParser(description
='SPDX expression checker')
216 ap
.add_argument('path', nargs
='*', help='Check path or file. If not given full git tree scan. For stdin use "-"')
217 ap
.add_argument('-m', '--maxlines', type=int, default
=15,
218 help='Maximum number of lines to scan in a file. Default 15')
219 ap
.add_argument('-v', '--verbose', action
='store_true', help='Verbose statistics output')
220 args
= ap
.parse_args()
222 # Sanity check path arguments
223 if '-' in args
.path
and len(args
.path
) > 1:
224 sys
.stderr
.write('stdin input "-" must be the only path argument\n')
228 # Use git to get the valid license expressions
229 repo
= git
.Repo(os
.getcwd())
232 # Initialize SPDX data
233 spdx
= read_spdxdata(repo
)
235 # Initilize the parser
236 parser
= id_parser(spdx
)
238 except SPDXException
as se
:
240 sys
.stderr
.write('%s: %s\n' %(se
.el
.path
, se
.txt
))
242 sys
.stderr
.write('%s\n' %se.txt
)
245 except Exception as ex
:
246 sys
.stderr
.write('FAIL: %s\n' %ex)
247 sys
.stderr
.write('%s\n' %traceback
.format_exc())
251 if len(args
.path
) and args
.path
[0] == '-':
252 parser
.parse_lines(sys
.stdin
, args
.maxlines
, '-')
256 if os
.path
.isfile(p
):
257 parser
.parse_lines(open(p
), args
.maxlines
, p
)
258 elif os
.path
.isdir(p
):
259 scan_git_subtree(repo
.head
.reference
.commit
.tree
, p
)
261 sys
.stderr
.write('path %s does not exist\n' %p
)
265 scan_git_tree(repo
.head
.commit
.tree
)
268 sys
.stderr
.write('\n')
269 sys
.stderr
.write('License files: %12d\n' %spdx
.license_files
)
270 sys
.stderr
.write('Exception files: %12d\n' %spdx
.exception_files
)
271 sys
.stderr
.write('License IDs %12d\n' %len(spdx
.licenses
))
272 sys
.stderr
.write('Exception IDs %12d\n' %len(spdx
.exceptions
))
273 sys
.stderr
.write('\n')
274 sys
.stderr
.write('Files checked: %12d\n' %parser
.checked
)
275 sys
.stderr
.write('Lines checked: %12d\n' %parser
.lines_checked
)
276 sys
.stderr
.write('Files with SPDX: %12d\n' %parser
.spdx_valid
)
277 sys
.stderr
.write('Files with errors: %12d\n' %parser
.spdx_errors
)
281 except Exception as ex
:
282 sys
.stderr
.write('FAIL: %s\n' %ex)
283 sys
.stderr
.write('%s\n' %traceback
.format_exc())