xref: /openbmc/linux/scripts/spdxcheck.py (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
1d0259c42SBert Vermeulen#!/usr/bin/env python3
25385a295SThomas Gleixner# SPDX-License-Identifier: GPL-2.0
35385a295SThomas Gleixner# Copyright Thomas Gleixner <tglx@linutronix.de>
45385a295SThomas Gleixner
55385a295SThomas Gleixnerfrom argparse import ArgumentParser
65385a295SThomas Gleixnerfrom ply import lex, yacc
7bed95c43SJeremy Clineimport locale
85385a295SThomas Gleixnerimport traceback
9*0509b270SThomas Gleixnerimport fnmatch
105385a295SThomas Gleixnerimport sys
115385a295SThomas Gleixnerimport git
125385a295SThomas Gleixnerimport re
135385a295SThomas Gleixnerimport os
145385a295SThomas Gleixner
155385a295SThomas Gleixnerclass ParserException(Exception):
165385a295SThomas Gleixner    def __init__(self, tok, txt):
175385a295SThomas Gleixner        self.tok = tok
185385a295SThomas Gleixner        self.txt = txt
195385a295SThomas Gleixner
205385a295SThomas Gleixnerclass SPDXException(Exception):
215385a295SThomas Gleixner    def __init__(self, el, txt):
225385a295SThomas Gleixner        self.el = el
235385a295SThomas Gleixner        self.txt = txt
245385a295SThomas Gleixner
255385a295SThomas Gleixnerclass SPDXdata(object):
265385a295SThomas Gleixner    def __init__(self):
275385a295SThomas Gleixner        self.license_files = 0
285385a295SThomas Gleixner        self.exception_files = 0
295385a295SThomas Gleixner        self.licenses = [ ]
305385a295SThomas Gleixner        self.exceptions = { }
315385a295SThomas Gleixner
32a377ce75SThomas Gleixnerclass dirinfo(object):
33a377ce75SThomas Gleixner    def __init__(self):
34a377ce75SThomas Gleixner        self.missing = 0
35a377ce75SThomas Gleixner        self.total = 0
3667924b71SThomas Gleixner        self.files = []
37a377ce75SThomas Gleixner
3867924b71SThomas Gleixner    def update(self, fname, basedir, miss):
39a377ce75SThomas Gleixner        self.total += 1
40a377ce75SThomas Gleixner        self.missing += miss
4167924b71SThomas Gleixner        if miss:
4267924b71SThomas Gleixner            fname = './' + fname
4367924b71SThomas Gleixner            bdir = os.path.dirname(fname)
4467924b71SThomas Gleixner            if bdir == basedir.rstrip('/'):
4567924b71SThomas Gleixner                self.files.append(fname)
46a377ce75SThomas Gleixner
475385a295SThomas Gleixner# Read the spdx data from the LICENSES directory
485385a295SThomas Gleixnerdef read_spdxdata(repo):
495385a295SThomas Gleixner
505385a295SThomas Gleixner    # The subdirectories of LICENSES in the kernel source
518d7a7abfSVincenzo Frascino    # Note: exceptions needs to be parsed as last directory.
528d7a7abfSVincenzo Frascino    license_dirs = [ "preferred", "dual", "deprecated", "exceptions" ]
53fde5e903SJoe Perches    lictree = repo.head.commit.tree['LICENSES']
545385a295SThomas Gleixner
555385a295SThomas Gleixner    spdx = SPDXdata()
565385a295SThomas Gleixner
575385a295SThomas Gleixner    for d in license_dirs:
585385a295SThomas Gleixner        for el in lictree[d].traverse():
595385a295SThomas Gleixner            if not os.path.isfile(el.path):
605385a295SThomas Gleixner                continue
615385a295SThomas Gleixner
625385a295SThomas Gleixner            exception = None
6340751c6cSNishanth Menon            for l in open(el.path, encoding="utf-8").readlines():
645385a295SThomas Gleixner                if l.startswith('Valid-License-Identifier:'):
655385a295SThomas Gleixner                    lid = l.split(':')[1].strip().upper()
665385a295SThomas Gleixner                    if lid in spdx.licenses:
675385a295SThomas Gleixner                        raise SPDXException(el, 'Duplicate License Identifier: %s' %lid)
685385a295SThomas Gleixner                    else:
695385a295SThomas Gleixner                        spdx.licenses.append(lid)
705385a295SThomas Gleixner
715385a295SThomas Gleixner                elif l.startswith('SPDX-Exception-Identifier:'):
725385a295SThomas Gleixner                    exception = l.split(':')[1].strip().upper()
735385a295SThomas Gleixner                    spdx.exceptions[exception] = []
745385a295SThomas Gleixner
755385a295SThomas Gleixner                elif l.startswith('SPDX-Licenses:'):
765385a295SThomas Gleixner                    for lic in l.split(':')[1].upper().strip().replace(' ', '').replace('\t', '').split(','):
775385a295SThomas Gleixner                        if not lic in spdx.licenses:
788d7a7abfSVincenzo Frascino                            raise SPDXException(None, 'Exception %s missing license %s' %(exception, lic))
795385a295SThomas Gleixner                        spdx.exceptions[exception].append(lic)
805385a295SThomas Gleixner
815385a295SThomas Gleixner                elif l.startswith("License-Text:"):
825385a295SThomas Gleixner                    if exception:
835385a295SThomas Gleixner                        if not len(spdx.exceptions[exception]):
848d7a7abfSVincenzo Frascino                            raise SPDXException(el, 'Exception %s is missing SPDX-Licenses' %exception)
855385a295SThomas Gleixner                        spdx.exception_files += 1
865385a295SThomas Gleixner                    else:
875385a295SThomas Gleixner                        spdx.license_files += 1
885385a295SThomas Gleixner                    break
895385a295SThomas Gleixner    return spdx
905385a295SThomas Gleixner
915385a295SThomas Gleixnerclass id_parser(object):
925385a295SThomas Gleixner
935385a295SThomas Gleixner    reserved = [ 'AND', 'OR', 'WITH' ]
945385a295SThomas Gleixner    tokens = [ 'LPAR', 'RPAR', 'ID', 'EXC' ] + reserved
955385a295SThomas Gleixner
965385a295SThomas Gleixner    precedence = ( ('nonassoc', 'AND', 'OR'), )
975385a295SThomas Gleixner
985385a295SThomas Gleixner    t_ignore = ' \t'
995385a295SThomas Gleixner
1005385a295SThomas Gleixner    def __init__(self, spdx):
1015385a295SThomas Gleixner        self.spdx = spdx
1025385a295SThomas Gleixner        self.lasttok = None
1035385a295SThomas Gleixner        self.lastid = None
1045385a295SThomas Gleixner        self.lexer = lex.lex(module = self, reflags = re.UNICODE)
1055385a295SThomas Gleixner        # Initialize the parser. No debug file and no parser rules stored on disk
1065385a295SThomas Gleixner        # The rules are small enough to be generated on the fly
1075385a295SThomas Gleixner        self.parser = yacc.yacc(module = self, write_tables = False, debug = False)
1085385a295SThomas Gleixner        self.lines_checked = 0
1095385a295SThomas Gleixner        self.checked = 0
110*0509b270SThomas Gleixner        self.excluded = 0
1115385a295SThomas Gleixner        self.spdx_valid = 0
1125385a295SThomas Gleixner        self.spdx_errors = 0
113a377ce75SThomas Gleixner        self.spdx_dirs = {}
1140e7f0306SThomas Gleixner        self.dirdepth = -1
1150e7f0306SThomas Gleixner        self.basedir = '.'
1165385a295SThomas Gleixner        self.curline = 0
1175385a295SThomas Gleixner        self.deepest = 0
1185385a295SThomas Gleixner
1190e7f0306SThomas Gleixner    def set_dirinfo(self, basedir, dirdepth):
1200e7f0306SThomas Gleixner        if dirdepth >= 0:
1210e7f0306SThomas Gleixner            self.basedir = basedir
1220e7f0306SThomas Gleixner            bdir = basedir.lstrip('./').rstrip('/')
1230e7f0306SThomas Gleixner            if bdir != '':
1240e7f0306SThomas Gleixner                parts = bdir.split('/')
1250e7f0306SThomas Gleixner            else:
1260e7f0306SThomas Gleixner                parts = []
1270e7f0306SThomas Gleixner            self.dirdepth = dirdepth + len(parts)
1280e7f0306SThomas Gleixner
1295385a295SThomas Gleixner    # Validate License and Exception IDs
1305385a295SThomas Gleixner    def validate(self, tok):
1315385a295SThomas Gleixner        id = tok.value.upper()
1325385a295SThomas Gleixner        if tok.type == 'ID':
1335385a295SThomas Gleixner            if not id in self.spdx.licenses:
1345385a295SThomas Gleixner                raise ParserException(tok, 'Invalid License ID')
1355385a295SThomas Gleixner            self.lastid = id
1365385a295SThomas Gleixner        elif tok.type == 'EXC':
137bed95c43SJeremy Cline            if id not in self.spdx.exceptions:
1385385a295SThomas Gleixner                raise ParserException(tok, 'Invalid Exception ID')
1395385a295SThomas Gleixner            if self.lastid not in self.spdx.exceptions[id]:
1405385a295SThomas Gleixner                raise ParserException(tok, 'Exception not valid for license %s' %self.lastid)
1415385a295SThomas Gleixner            self.lastid = None
1425385a295SThomas Gleixner        elif tok.type != 'WITH':
1435385a295SThomas Gleixner            self.lastid = None
1445385a295SThomas Gleixner
1455385a295SThomas Gleixner    # Lexer functions
1465385a295SThomas Gleixner    def t_RPAR(self, tok):
1475385a295SThomas Gleixner        r'\)'
1485385a295SThomas Gleixner        self.lasttok = tok.type
1495385a295SThomas Gleixner        return tok
1505385a295SThomas Gleixner
1515385a295SThomas Gleixner    def t_LPAR(self, tok):
1525385a295SThomas Gleixner        r'\('
1535385a295SThomas Gleixner        self.lasttok = tok.type
1545385a295SThomas Gleixner        return tok
1555385a295SThomas Gleixner
1565385a295SThomas Gleixner    def t_ID(self, tok):
1575385a295SThomas Gleixner        r'[A-Za-z.0-9\-+]+'
1585385a295SThomas Gleixner
1595385a295SThomas Gleixner        if self.lasttok == 'EXC':
1605385a295SThomas Gleixner            print(tok)
1615385a295SThomas Gleixner            raise ParserException(tok, 'Missing parentheses')
1625385a295SThomas Gleixner
1635385a295SThomas Gleixner        tok.value = tok.value.strip()
1645385a295SThomas Gleixner        val = tok.value.upper()
1655385a295SThomas Gleixner
1665385a295SThomas Gleixner        if val in self.reserved:
1675385a295SThomas Gleixner            tok.type = val
1685385a295SThomas Gleixner        elif self.lasttok == 'WITH':
1695385a295SThomas Gleixner            tok.type = 'EXC'
1705385a295SThomas Gleixner
1715385a295SThomas Gleixner        self.lasttok = tok.type
1725385a295SThomas Gleixner        self.validate(tok)
1735385a295SThomas Gleixner        return tok
1745385a295SThomas Gleixner
1755385a295SThomas Gleixner    def t_error(self, tok):
1765385a295SThomas Gleixner        raise ParserException(tok, 'Invalid token')
1775385a295SThomas Gleixner
1785385a295SThomas Gleixner    def p_expr(self, p):
1795385a295SThomas Gleixner        '''expr : ID
1805385a295SThomas Gleixner                | ID WITH EXC
1815385a295SThomas Gleixner                | expr AND expr
1825385a295SThomas Gleixner                | expr OR expr
1835385a295SThomas Gleixner                | LPAR expr RPAR'''
1845385a295SThomas Gleixner        pass
1855385a295SThomas Gleixner
1865385a295SThomas Gleixner    def p_error(self, p):
1875385a295SThomas Gleixner        if not p:
1885385a295SThomas Gleixner            raise ParserException(None, 'Unfinished license expression')
1895385a295SThomas Gleixner        else:
1905385a295SThomas Gleixner            raise ParserException(p, 'Syntax error')
1915385a295SThomas Gleixner
1925385a295SThomas Gleixner    def parse(self, expr):
1935385a295SThomas Gleixner        self.lasttok = None
1945385a295SThomas Gleixner        self.lastid = None
1955385a295SThomas Gleixner        self.parser.parse(expr, lexer = self.lexer)
1965385a295SThomas Gleixner
1975385a295SThomas Gleixner    def parse_lines(self, fd, maxlines, fname):
1985385a295SThomas Gleixner        self.checked += 1
1995385a295SThomas Gleixner        self.curline = 0
200a377ce75SThomas Gleixner        fail = 1
2015385a295SThomas Gleixner        try:
2025385a295SThomas Gleixner            for line in fd:
2033a6ab5c7SThierry Reding                line = line.decode(locale.getpreferredencoding(False), errors='ignore')
2045385a295SThomas Gleixner                self.curline += 1
2055385a295SThomas Gleixner                if self.curline > maxlines:
2065385a295SThomas Gleixner                    break
2075385a295SThomas Gleixner                self.lines_checked += 1
2085385a295SThomas Gleixner                if line.find("SPDX-License-Identifier:") < 0:
2095385a295SThomas Gleixner                    continue
210959b4968SThomas Gleixner                expr = line.split(':')[1].strip()
211959b4968SThomas Gleixner                # Remove trailing comment closure
212a5f4cb42SAurélien Cedeyn                if line.strip().endswith('*/'):
213959b4968SThomas Gleixner                    expr = expr.rstrip('*/').strip()
214c5c55385SLukas Bulwahn                # Remove trailing xml comment closure
215c5c55385SLukas Bulwahn                if line.strip().endswith('-->'):
216c5c55385SLukas Bulwahn                    expr = expr.rstrip('-->').strip()
217959b4968SThomas Gleixner                # Special case for SH magic boot code files
218959b4968SThomas Gleixner                if line.startswith('LIST \"'):
219959b4968SThomas Gleixner                    expr = expr.rstrip('\"').strip()
2205385a295SThomas Gleixner                self.parse(expr)
2215385a295SThomas Gleixner                self.spdx_valid += 1
2225385a295SThomas Gleixner                #
2235385a295SThomas Gleixner                # Should we check for more SPDX ids in the same file and
2245385a295SThomas Gleixner                # complain if there are any?
2255385a295SThomas Gleixner                #
226a377ce75SThomas Gleixner                fail = 0
2275385a295SThomas Gleixner                break
2285385a295SThomas Gleixner
2295385a295SThomas Gleixner        except ParserException as pe:
2305385a295SThomas Gleixner            if pe.tok:
2315385a295SThomas Gleixner                col = line.find(expr) + pe.tok.lexpos
2325385a295SThomas Gleixner                tok = pe.tok.value
2335385a295SThomas Gleixner                sys.stdout.write('%s: %d:%d %s: %s\n' %(fname, self.curline, col, pe.txt, tok))
2345385a295SThomas Gleixner            else:
23528c9f3f9SDing Xiang                sys.stdout.write('%s: %d:0 %s\n' %(fname, self.curline, pe.txt))
2365385a295SThomas Gleixner            self.spdx_errors += 1
2375385a295SThomas Gleixner
2380e7f0306SThomas Gleixner        if fname == '-':
2390e7f0306SThomas Gleixner            return
2400e7f0306SThomas Gleixner
241a377ce75SThomas Gleixner        base = os.path.dirname(fname)
2420e7f0306SThomas Gleixner        if self.dirdepth > 0:
2430e7f0306SThomas Gleixner            parts = base.split('/')
2440e7f0306SThomas Gleixner            i = 0
2450e7f0306SThomas Gleixner            base = '.'
2460e7f0306SThomas Gleixner            while i < self.dirdepth and i < len(parts) and len(parts[i]):
2470e7f0306SThomas Gleixner                base += '/' + parts[i]
2480e7f0306SThomas Gleixner                i += 1
2490e7f0306SThomas Gleixner        elif self.dirdepth == 0:
2500e7f0306SThomas Gleixner            base = self.basedir
2510e7f0306SThomas Gleixner        else:
2520e7f0306SThomas Gleixner            base = './' + base.rstrip('/')
2530e7f0306SThomas Gleixner        base += '/'
2540e7f0306SThomas Gleixner
255a377ce75SThomas Gleixner        di = self.spdx_dirs.get(base, dirinfo())
25667924b71SThomas Gleixner        di.update(fname, base, fail)
257a377ce75SThomas Gleixner        self.spdx_dirs[base] = di
258a377ce75SThomas Gleixner
259*0509b270SThomas Gleixnerclass pattern(object):
260*0509b270SThomas Gleixner    def __init__(self, line):
261*0509b270SThomas Gleixner        self.pattern = line
262*0509b270SThomas Gleixner        self.match = self.match_file
263*0509b270SThomas Gleixner        if line == '.*':
264*0509b270SThomas Gleixner            self.match = self.match_dot
265*0509b270SThomas Gleixner        elif line.endswith('/'):
266*0509b270SThomas Gleixner            self.pattern = line[:-1]
267*0509b270SThomas Gleixner            self.match = self.match_dir
268*0509b270SThomas Gleixner        elif line.startswith('/'):
269*0509b270SThomas Gleixner            self.pattern = line[1:]
270*0509b270SThomas Gleixner            self.match = self.match_fn
271*0509b270SThomas Gleixner
272*0509b270SThomas Gleixner    def match_dot(self, fpath):
273*0509b270SThomas Gleixner        return os.path.basename(fpath).startswith('.')
274*0509b270SThomas Gleixner
275*0509b270SThomas Gleixner    def match_file(self, fpath):
276*0509b270SThomas Gleixner        return os.path.basename(fpath) == self.pattern
277*0509b270SThomas Gleixner
278*0509b270SThomas Gleixner    def match_fn(self, fpath):
279*0509b270SThomas Gleixner        return fnmatch.fnmatchcase(fpath, self.pattern)
280*0509b270SThomas Gleixner
281*0509b270SThomas Gleixner    def match_dir(self, fpath):
282*0509b270SThomas Gleixner        if self.match_fn(os.path.dirname(fpath)):
283*0509b270SThomas Gleixner            return True
284*0509b270SThomas Gleixner        return fpath.startswith(self.pattern)
285*0509b270SThomas Gleixner
286*0509b270SThomas Gleixnerdef exclude_file(fpath):
287*0509b270SThomas Gleixner    for rule in exclude_rules:
288*0509b270SThomas Gleixner        if rule.match(fpath):
289*0509b270SThomas Gleixner            return True
290*0509b270SThomas Gleixner    return False
291*0509b270SThomas Gleixner
2920e7f0306SThomas Gleixnerdef scan_git_tree(tree, basedir, dirdepth):
2930e7f0306SThomas Gleixner    parser.set_dirinfo(basedir, dirdepth)
2945385a295SThomas Gleixner    for el in tree.traverse():
2955385a295SThomas Gleixner        if not os.path.isfile(el.path):
2965385a295SThomas Gleixner            continue
297*0509b270SThomas Gleixner        if exclude_file(el.path):
298*0509b270SThomas Gleixner            parser.excluded += 1
299*0509b270SThomas Gleixner            continue
300bed95c43SJeremy Cline        with open(el.path, 'rb') as fd:
301bed95c43SJeremy Cline            parser.parse_lines(fd, args.maxlines, el.path)
3025385a295SThomas Gleixner
3030e7f0306SThomas Gleixnerdef scan_git_subtree(tree, path, dirdepth):
3045385a295SThomas Gleixner    for p in path.strip('/').split('/'):
3055385a295SThomas Gleixner        tree = tree[p]
3060e7f0306SThomas Gleixner    scan_git_tree(tree, path.strip('/'), dirdepth)
3075385a295SThomas Gleixner
308*0509b270SThomas Gleixnerdef read_exclude_file(fname):
309*0509b270SThomas Gleixner    rules = []
310*0509b270SThomas Gleixner    if not fname:
311*0509b270SThomas Gleixner        return rules
312*0509b270SThomas Gleixner    with open(fname) as fd:
313*0509b270SThomas Gleixner        for line in fd:
314*0509b270SThomas Gleixner            line = line.strip()
315*0509b270SThomas Gleixner            if line.startswith('#'):
316*0509b270SThomas Gleixner                continue
317*0509b270SThomas Gleixner            if not len(line):
318*0509b270SThomas Gleixner                continue
319*0509b270SThomas Gleixner            rules.append(pattern(line))
320*0509b270SThomas Gleixner    return rules
321*0509b270SThomas Gleixner
3225385a295SThomas Gleixnerif __name__ == '__main__':
3235385a295SThomas Gleixner
3245385a295SThomas Gleixner    ap = ArgumentParser(description='SPDX expression checker')
3255385a295SThomas Gleixner    ap.add_argument('path', nargs='*', help='Check path or file. If not given full git tree scan. For stdin use "-"')
3260e7f0306SThomas Gleixner    ap.add_argument('-d', '--dirs', action='store_true',
3270e7f0306SThomas Gleixner                    help='Show [sub]directory statistics.')
3280e7f0306SThomas Gleixner    ap.add_argument('-D', '--depth', type=int, default=-1,
3290e7f0306SThomas Gleixner                    help='Directory depth for -d statistics. Default: unlimited')
330*0509b270SThomas Gleixner    ap.add_argument('-e', '--exclude',
331*0509b270SThomas Gleixner                    help='File containing file patterns to exclude. Default: scripts/spdxexclude')
33267924b71SThomas Gleixner    ap.add_argument('-f', '--files', action='store_true',
33367924b71SThomas Gleixner                    help='Show files without SPDX.')
3345385a295SThomas Gleixner    ap.add_argument('-m', '--maxlines', type=int, default=15,
3355385a295SThomas Gleixner                    help='Maximum number of lines to scan in a file. Default 15')
3365385a295SThomas Gleixner    ap.add_argument('-v', '--verbose', action='store_true', help='Verbose statistics output')
3375385a295SThomas Gleixner    args = ap.parse_args()
3385385a295SThomas Gleixner
3395385a295SThomas Gleixner    # Sanity check path arguments
3405385a295SThomas Gleixner    if '-' in args.path and len(args.path) > 1:
3415385a295SThomas Gleixner        sys.stderr.write('stdin input "-" must be the only path argument\n')
3425385a295SThomas Gleixner        sys.exit(1)
3435385a295SThomas Gleixner
3445385a295SThomas Gleixner    try:
3455385a295SThomas Gleixner        # Use git to get the valid license expressions
3465385a295SThomas Gleixner        repo = git.Repo(os.getcwd())
3475385a295SThomas Gleixner        assert not repo.bare
3485385a295SThomas Gleixner
3495385a295SThomas Gleixner        # Initialize SPDX data
3505385a295SThomas Gleixner        spdx = read_spdxdata(repo)
3515385a295SThomas Gleixner
35240635128SBhaskar Chowdhury        # Initialize the parser
3535385a295SThomas Gleixner        parser = id_parser(spdx)
3545385a295SThomas Gleixner
3555385a295SThomas Gleixner    except SPDXException as se:
3565385a295SThomas Gleixner        if se.el:
3575385a295SThomas Gleixner            sys.stderr.write('%s: %s\n' %(se.el.path, se.txt))
3585385a295SThomas Gleixner        else:
3595385a295SThomas Gleixner            sys.stderr.write('%s\n' %se.txt)
3605385a295SThomas Gleixner        sys.exit(1)
3615385a295SThomas Gleixner
3625385a295SThomas Gleixner    except Exception as ex:
3635385a295SThomas Gleixner        sys.stderr.write('FAIL: %s\n' %ex)
3645385a295SThomas Gleixner        sys.stderr.write('%s\n' %traceback.format_exc())
3655385a295SThomas Gleixner        sys.exit(1)
3665385a295SThomas Gleixner
3675385a295SThomas Gleixner    try:
368*0509b270SThomas Gleixner        fname = args.exclude
369*0509b270SThomas Gleixner        if not fname:
370*0509b270SThomas Gleixner            fname = os.path.join(os.path.dirname(__file__), 'spdxexclude')
371*0509b270SThomas Gleixner        exclude_rules = read_exclude_file(fname)
372*0509b270SThomas Gleixner    except Exception as ex:
373*0509b270SThomas Gleixner        sys.stderr.write('FAIL: Reading exclude file %s: %s\n' %(fname, ex))
374*0509b270SThomas Gleixner        sys.exit(1)
375*0509b270SThomas Gleixner
376*0509b270SThomas Gleixner    try:
3775385a295SThomas Gleixner        if len(args.path) and args.path[0] == '-':
3783a6ab5c7SThierry Reding            stdin = os.fdopen(sys.stdin.fileno(), 'rb')
3793a6ab5c7SThierry Reding            parser.parse_lines(stdin, args.maxlines, '-')
3805385a295SThomas Gleixner        else:
3815385a295SThomas Gleixner            if args.path:
3825385a295SThomas Gleixner                for p in args.path:
3835385a295SThomas Gleixner                    if os.path.isfile(p):
3843a6ab5c7SThierry Reding                        parser.parse_lines(open(p, 'rb'), args.maxlines, p)
3855385a295SThomas Gleixner                    elif os.path.isdir(p):
3860e7f0306SThomas Gleixner                        scan_git_subtree(repo.head.reference.commit.tree, p,
3870e7f0306SThomas Gleixner                                         args.depth)
3885385a295SThomas Gleixner                    else:
3895385a295SThomas Gleixner                        sys.stderr.write('path %s does not exist\n' %p)
3905385a295SThomas Gleixner                        sys.exit(1)
3915385a295SThomas Gleixner            else:
3925385a295SThomas Gleixner                # Full git tree scan
3930e7f0306SThomas Gleixner                scan_git_tree(repo.head.commit.tree, '.', args.depth)
3940e7f0306SThomas Gleixner
3950e7f0306SThomas Gleixner            ndirs = len(parser.spdx_dirs)
3960e7f0306SThomas Gleixner            dirsok = 0
3970e7f0306SThomas Gleixner            if ndirs:
3980e7f0306SThomas Gleixner                for di in parser.spdx_dirs.values():
3990e7f0306SThomas Gleixner                    if not di.missing:
4000e7f0306SThomas Gleixner                        dirsok += 1
4015385a295SThomas Gleixner
4025385a295SThomas Gleixner            if args.verbose:
4035385a295SThomas Gleixner                sys.stderr.write('\n')
4045385a295SThomas Gleixner                sys.stderr.write('License files:     %12d\n' %spdx.license_files)
4055385a295SThomas Gleixner                sys.stderr.write('Exception files:   %12d\n' %spdx.exception_files)
4065385a295SThomas Gleixner                sys.stderr.write('License IDs        %12d\n' %len(spdx.licenses))
4075385a295SThomas Gleixner                sys.stderr.write('Exception IDs      %12d\n' %len(spdx.exceptions))
4085385a295SThomas Gleixner                sys.stderr.write('\n')
409*0509b270SThomas Gleixner                sys.stderr.write('Files excluded:    %12d\n' %parser.excluded)
4105385a295SThomas Gleixner                sys.stderr.write('Files checked:     %12d\n' %parser.checked)
4115385a295SThomas Gleixner                sys.stderr.write('Lines checked:     %12d\n' %parser.lines_checked)
412149d623fSThomas Gleixner                if parser.checked:
413149d623fSThomas Gleixner                    pc = int(100 * parser.spdx_valid / parser.checked)
414149d623fSThomas Gleixner                    sys.stderr.write('Files with SPDX:   %12d %3d%%\n' %(parser.spdx_valid, pc))
4155385a295SThomas Gleixner                sys.stderr.write('Files with errors: %12d\n' %parser.spdx_errors)
416a377ce75SThomas Gleixner                if ndirs:
417a377ce75SThomas Gleixner                    sys.stderr.write('\n')
418a377ce75SThomas Gleixner                    sys.stderr.write('Directories accounted: %8d\n' %ndirs)
419a377ce75SThomas Gleixner                    pc = int(100 * dirsok / ndirs)
420a377ce75SThomas Gleixner                    sys.stderr.write('Directories complete:  %8d %3d%%\n' %(dirsok, pc))
4215385a295SThomas Gleixner
4220e7f0306SThomas Gleixner            if ndirs and ndirs != dirsok and args.dirs:
4230e7f0306SThomas Gleixner                if args.verbose:
4240e7f0306SThomas Gleixner                    sys.stderr.write('\n')
4250e7f0306SThomas Gleixner                sys.stderr.write('Incomplete directories: SPDX in Files\n')
4260e7f0306SThomas Gleixner                for f in sorted(parser.spdx_dirs.keys()):
4270e7f0306SThomas Gleixner                    di = parser.spdx_dirs[f]
4280e7f0306SThomas Gleixner                    if di.missing:
4290e7f0306SThomas Gleixner                        valid = di.total - di.missing
4300e7f0306SThomas Gleixner                        pc = int(100 * valid / di.total)
4310e7f0306SThomas Gleixner                        sys.stderr.write('    %-80s: %5d of %5d  %3d%%\n' %(f, valid, di.total, pc))
4320e7f0306SThomas Gleixner
43367924b71SThomas Gleixner            if ndirs and ndirs != dirsok and args.files:
43467924b71SThomas Gleixner                if args.verbose or args.dirs:
43567924b71SThomas Gleixner                    sys.stderr.write('\n')
43667924b71SThomas Gleixner                sys.stderr.write('Files without SPDX:\n')
43767924b71SThomas Gleixner                for f in sorted(parser.spdx_dirs.keys()):
43867924b71SThomas Gleixner                    di = parser.spdx_dirs[f]
43967924b71SThomas Gleixner                    for f in sorted(di.files):
44067924b71SThomas Gleixner                        sys.stderr.write('    %s\n' %f)
44167924b71SThomas Gleixner
4425385a295SThomas Gleixner            sys.exit(0)
4435385a295SThomas Gleixner
4445385a295SThomas Gleixner    except Exception as ex:
4455385a295SThomas Gleixner        sys.stderr.write('FAIL: %s\n' %ex)
4465385a295SThomas Gleixner        sys.stderr.write('%s\n' %traceback.format_exc())
4475385a295SThomas Gleixner        sys.exit(1)
448