xref: /linux-6.15/scripts/spdxcheck.py (revision 154916f4)
1d0259c42SBert Vermeulen#!/usr/bin/env python3
25385a295SThomas Gleixner# SPDX-License-Identifier: GPL-2.0
35385a295SThomas Gleixner# Copyright Thomas Gleixner <[email protected]>
45385a295SThomas Gleixner
55385a295SThomas Gleixnerfrom argparse import ArgumentParser
65385a295SThomas Gleixnerfrom ply import lex, yacc
7bed95c43SJeremy Clineimport locale
85385a295SThomas Gleixnerimport traceback
90509b270SThomas 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
1100509b270SThomas 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()
217*154916f4SLukas Bulwahn                # Remove trailing Jinja2 comment closure
218*154916f4SLukas Bulwahn                if line.strip().endswith('#}'):
219*154916f4SLukas Bulwahn                    expr = expr.rstrip('#}').strip()
220959b4968SThomas Gleixner                # Special case for SH magic boot code files
221959b4968SThomas Gleixner                if line.startswith('LIST \"'):
222959b4968SThomas Gleixner                    expr = expr.rstrip('\"').strip()
223634d34e8SThomas Gleixner                # Remove j2 comment closure
224634d34e8SThomas Gleixner                if line.startswith('{#'):
225634d34e8SThomas Gleixner                    expr = expr.rstrip('#}').strip()
2265385a295SThomas Gleixner                self.parse(expr)
2275385a295SThomas Gleixner                self.spdx_valid += 1
2285385a295SThomas Gleixner                #
2295385a295SThomas Gleixner                # Should we check for more SPDX ids in the same file and
2305385a295SThomas Gleixner                # complain if there are any?
2315385a295SThomas Gleixner                #
232a377ce75SThomas Gleixner                fail = 0
2335385a295SThomas Gleixner                break
2345385a295SThomas Gleixner
2355385a295SThomas Gleixner        except ParserException as pe:
2365385a295SThomas Gleixner            if pe.tok:
2375385a295SThomas Gleixner                col = line.find(expr) + pe.tok.lexpos
2385385a295SThomas Gleixner                tok = pe.tok.value
2395385a295SThomas Gleixner                sys.stdout.write('%s: %d:%d %s: %s\n' %(fname, self.curline, col, pe.txt, tok))
2405385a295SThomas Gleixner            else:
24128c9f3f9SDing Xiang                sys.stdout.write('%s: %d:0 %s\n' %(fname, self.curline, pe.txt))
2425385a295SThomas Gleixner            self.spdx_errors += 1
2435385a295SThomas Gleixner
2440e7f0306SThomas Gleixner        if fname == '-':
2450e7f0306SThomas Gleixner            return
2460e7f0306SThomas Gleixner
247a377ce75SThomas Gleixner        base = os.path.dirname(fname)
2480e7f0306SThomas Gleixner        if self.dirdepth > 0:
2490e7f0306SThomas Gleixner            parts = base.split('/')
2500e7f0306SThomas Gleixner            i = 0
2510e7f0306SThomas Gleixner            base = '.'
2520e7f0306SThomas Gleixner            while i < self.dirdepth and i < len(parts) and len(parts[i]):
2530e7f0306SThomas Gleixner                base += '/' + parts[i]
2540e7f0306SThomas Gleixner                i += 1
2550e7f0306SThomas Gleixner        elif self.dirdepth == 0:
2560e7f0306SThomas Gleixner            base = self.basedir
2570e7f0306SThomas Gleixner        else:
2580e7f0306SThomas Gleixner            base = './' + base.rstrip('/')
2590e7f0306SThomas Gleixner        base += '/'
2600e7f0306SThomas Gleixner
261a377ce75SThomas Gleixner        di = self.spdx_dirs.get(base, dirinfo())
26267924b71SThomas Gleixner        di.update(fname, base, fail)
263a377ce75SThomas Gleixner        self.spdx_dirs[base] = di
264a377ce75SThomas Gleixner
2650509b270SThomas Gleixnerclass pattern(object):
2660509b270SThomas Gleixner    def __init__(self, line):
2670509b270SThomas Gleixner        self.pattern = line
2680509b270SThomas Gleixner        self.match = self.match_file
2690509b270SThomas Gleixner        if line == '.*':
2700509b270SThomas Gleixner            self.match = self.match_dot
2710509b270SThomas Gleixner        elif line.endswith('/'):
2720509b270SThomas Gleixner            self.pattern = line[:-1]
2730509b270SThomas Gleixner            self.match = self.match_dir
2740509b270SThomas Gleixner        elif line.startswith('/'):
2750509b270SThomas Gleixner            self.pattern = line[1:]
2760509b270SThomas Gleixner            self.match = self.match_fn
2770509b270SThomas Gleixner
2780509b270SThomas Gleixner    def match_dot(self, fpath):
2790509b270SThomas Gleixner        return os.path.basename(fpath).startswith('.')
2800509b270SThomas Gleixner
2810509b270SThomas Gleixner    def match_file(self, fpath):
2820509b270SThomas Gleixner        return os.path.basename(fpath) == self.pattern
2830509b270SThomas Gleixner
2840509b270SThomas Gleixner    def match_fn(self, fpath):
2850509b270SThomas Gleixner        return fnmatch.fnmatchcase(fpath, self.pattern)
2860509b270SThomas Gleixner
2870509b270SThomas Gleixner    def match_dir(self, fpath):
2880509b270SThomas Gleixner        if self.match_fn(os.path.dirname(fpath)):
2890509b270SThomas Gleixner            return True
2900509b270SThomas Gleixner        return fpath.startswith(self.pattern)
2910509b270SThomas Gleixner
2920509b270SThomas Gleixnerdef exclude_file(fpath):
2930509b270SThomas Gleixner    for rule in exclude_rules:
2940509b270SThomas Gleixner        if rule.match(fpath):
2950509b270SThomas Gleixner            return True
2960509b270SThomas Gleixner    return False
2970509b270SThomas Gleixner
2980e7f0306SThomas Gleixnerdef scan_git_tree(tree, basedir, dirdepth):
2990e7f0306SThomas Gleixner    parser.set_dirinfo(basedir, dirdepth)
3005385a295SThomas Gleixner    for el in tree.traverse():
3015385a295SThomas Gleixner        if not os.path.isfile(el.path):
3025385a295SThomas Gleixner            continue
3030509b270SThomas Gleixner        if exclude_file(el.path):
3040509b270SThomas Gleixner            parser.excluded += 1
3050509b270SThomas Gleixner            continue
306bed95c43SJeremy Cline        with open(el.path, 'rb') as fd:
307bed95c43SJeremy Cline            parser.parse_lines(fd, args.maxlines, el.path)
3085385a295SThomas Gleixner
3090e7f0306SThomas Gleixnerdef scan_git_subtree(tree, path, dirdepth):
3105385a295SThomas Gleixner    for p in path.strip('/').split('/'):
3115385a295SThomas Gleixner        tree = tree[p]
3120e7f0306SThomas Gleixner    scan_git_tree(tree, path.strip('/'), dirdepth)
3135385a295SThomas Gleixner
3140509b270SThomas Gleixnerdef read_exclude_file(fname):
3150509b270SThomas Gleixner    rules = []
3160509b270SThomas Gleixner    if not fname:
3170509b270SThomas Gleixner        return rules
3180509b270SThomas Gleixner    with open(fname) as fd:
3190509b270SThomas Gleixner        for line in fd:
3200509b270SThomas Gleixner            line = line.strip()
3210509b270SThomas Gleixner            if line.startswith('#'):
3220509b270SThomas Gleixner                continue
3230509b270SThomas Gleixner            if not len(line):
3240509b270SThomas Gleixner                continue
3250509b270SThomas Gleixner            rules.append(pattern(line))
3260509b270SThomas Gleixner    return rules
3270509b270SThomas Gleixner
3285385a295SThomas Gleixnerif __name__ == '__main__':
3295385a295SThomas Gleixner
3305385a295SThomas Gleixner    ap = ArgumentParser(description='SPDX expression checker')
3315385a295SThomas Gleixner    ap.add_argument('path', nargs='*', help='Check path or file. If not given full git tree scan. For stdin use "-"')
3320e7f0306SThomas Gleixner    ap.add_argument('-d', '--dirs', action='store_true',
3330e7f0306SThomas Gleixner                    help='Show [sub]directory statistics.')
3340e7f0306SThomas Gleixner    ap.add_argument('-D', '--depth', type=int, default=-1,
3350e7f0306SThomas Gleixner                    help='Directory depth for -d statistics. Default: unlimited')
3360509b270SThomas Gleixner    ap.add_argument('-e', '--exclude',
3370509b270SThomas Gleixner                    help='File containing file patterns to exclude. Default: scripts/spdxexclude')
33867924b71SThomas Gleixner    ap.add_argument('-f', '--files', action='store_true',
33967924b71SThomas Gleixner                    help='Show files without SPDX.')
3405385a295SThomas Gleixner    ap.add_argument('-m', '--maxlines', type=int, default=15,
3415385a295SThomas Gleixner                    help='Maximum number of lines to scan in a file. Default 15')
3425385a295SThomas Gleixner    ap.add_argument('-v', '--verbose', action='store_true', help='Verbose statistics output')
3435385a295SThomas Gleixner    args = ap.parse_args()
3445385a295SThomas Gleixner
3455385a295SThomas Gleixner    # Sanity check path arguments
3465385a295SThomas Gleixner    if '-' in args.path and len(args.path) > 1:
3475385a295SThomas Gleixner        sys.stderr.write('stdin input "-" must be the only path argument\n')
3485385a295SThomas Gleixner        sys.exit(1)
3495385a295SThomas Gleixner
3505385a295SThomas Gleixner    try:
3515385a295SThomas Gleixner        # Use git to get the valid license expressions
3525385a295SThomas Gleixner        repo = git.Repo(os.getcwd())
3535385a295SThomas Gleixner        assert not repo.bare
3545385a295SThomas Gleixner
3555385a295SThomas Gleixner        # Initialize SPDX data
3565385a295SThomas Gleixner        spdx = read_spdxdata(repo)
3575385a295SThomas Gleixner
35840635128SBhaskar Chowdhury        # Initialize the parser
3595385a295SThomas Gleixner        parser = id_parser(spdx)
3605385a295SThomas Gleixner
3615385a295SThomas Gleixner    except SPDXException as se:
3625385a295SThomas Gleixner        if se.el:
3635385a295SThomas Gleixner            sys.stderr.write('%s: %s\n' %(se.el.path, se.txt))
3645385a295SThomas Gleixner        else:
3655385a295SThomas Gleixner            sys.stderr.write('%s\n' %se.txt)
3665385a295SThomas Gleixner        sys.exit(1)
3675385a295SThomas Gleixner
3685385a295SThomas Gleixner    except Exception as ex:
3695385a295SThomas Gleixner        sys.stderr.write('FAIL: %s\n' %ex)
3705385a295SThomas Gleixner        sys.stderr.write('%s\n' %traceback.format_exc())
3715385a295SThomas Gleixner        sys.exit(1)
3725385a295SThomas Gleixner
3735385a295SThomas Gleixner    try:
3740509b270SThomas Gleixner        fname = args.exclude
3750509b270SThomas Gleixner        if not fname:
3760509b270SThomas Gleixner            fname = os.path.join(os.path.dirname(__file__), 'spdxexclude')
3770509b270SThomas Gleixner        exclude_rules = read_exclude_file(fname)
3780509b270SThomas Gleixner    except Exception as ex:
3790509b270SThomas Gleixner        sys.stderr.write('FAIL: Reading exclude file %s: %s\n' %(fname, ex))
3800509b270SThomas Gleixner        sys.exit(1)
3810509b270SThomas Gleixner
3820509b270SThomas Gleixner    try:
3835385a295SThomas Gleixner        if len(args.path) and args.path[0] == '-':
3843a6ab5c7SThierry Reding            stdin = os.fdopen(sys.stdin.fileno(), 'rb')
3853a6ab5c7SThierry Reding            parser.parse_lines(stdin, args.maxlines, '-')
3865385a295SThomas Gleixner        else:
3875385a295SThomas Gleixner            if args.path:
3885385a295SThomas Gleixner                for p in args.path:
3895385a295SThomas Gleixner                    if os.path.isfile(p):
3903a6ab5c7SThierry Reding                        parser.parse_lines(open(p, 'rb'), args.maxlines, p)
3915385a295SThomas Gleixner                    elif os.path.isdir(p):
3920e7f0306SThomas Gleixner                        scan_git_subtree(repo.head.reference.commit.tree, p,
3930e7f0306SThomas Gleixner                                         args.depth)
3945385a295SThomas Gleixner                    else:
3955385a295SThomas Gleixner                        sys.stderr.write('path %s does not exist\n' %p)
3965385a295SThomas Gleixner                        sys.exit(1)
3975385a295SThomas Gleixner            else:
3985385a295SThomas Gleixner                # Full git tree scan
3990e7f0306SThomas Gleixner                scan_git_tree(repo.head.commit.tree, '.', args.depth)
4000e7f0306SThomas Gleixner
4010e7f0306SThomas Gleixner            ndirs = len(parser.spdx_dirs)
4020e7f0306SThomas Gleixner            dirsok = 0
4030e7f0306SThomas Gleixner            if ndirs:
4040e7f0306SThomas Gleixner                for di in parser.spdx_dirs.values():
4050e7f0306SThomas Gleixner                    if not di.missing:
4060e7f0306SThomas Gleixner                        dirsok += 1
4075385a295SThomas Gleixner
4085385a295SThomas Gleixner            if args.verbose:
4095385a295SThomas Gleixner                sys.stderr.write('\n')
4105385a295SThomas Gleixner                sys.stderr.write('License files:     %12d\n' %spdx.license_files)
4115385a295SThomas Gleixner                sys.stderr.write('Exception files:   %12d\n' %spdx.exception_files)
4125385a295SThomas Gleixner                sys.stderr.write('License IDs        %12d\n' %len(spdx.licenses))
4135385a295SThomas Gleixner                sys.stderr.write('Exception IDs      %12d\n' %len(spdx.exceptions))
4145385a295SThomas Gleixner                sys.stderr.write('\n')
4150509b270SThomas Gleixner                sys.stderr.write('Files excluded:    %12d\n' %parser.excluded)
4165385a295SThomas Gleixner                sys.stderr.write('Files checked:     %12d\n' %parser.checked)
4175385a295SThomas Gleixner                sys.stderr.write('Lines checked:     %12d\n' %parser.lines_checked)
418149d623fSThomas Gleixner                if parser.checked:
419149d623fSThomas Gleixner                    pc = int(100 * parser.spdx_valid / parser.checked)
420149d623fSThomas Gleixner                    sys.stderr.write('Files with SPDX:   %12d %3d%%\n' %(parser.spdx_valid, pc))
4215015f8a5SBird, Tim                    missing = parser.checked - parser.spdx_valid
4225015f8a5SBird, Tim                    mpc = int(100 * missing / parser.checked)
4235015f8a5SBird, Tim                    sys.stderr.write('Files without SPDX:%12d %3d%%\n' %(missing, mpc))
4245385a295SThomas Gleixner                sys.stderr.write('Files with errors: %12d\n' %parser.spdx_errors)
425a377ce75SThomas Gleixner                if ndirs:
426a377ce75SThomas Gleixner                    sys.stderr.write('\n')
427a377ce75SThomas Gleixner                    sys.stderr.write('Directories accounted: %8d\n' %ndirs)
428a377ce75SThomas Gleixner                    pc = int(100 * dirsok / ndirs)
429a377ce75SThomas Gleixner                    sys.stderr.write('Directories complete:  %8d %3d%%\n' %(dirsok, pc))
4305385a295SThomas Gleixner
4310e7f0306SThomas Gleixner            if ndirs and ndirs != dirsok and args.dirs:
4320e7f0306SThomas Gleixner                if args.verbose:
4330e7f0306SThomas Gleixner                    sys.stderr.write('\n')
4340e7f0306SThomas Gleixner                sys.stderr.write('Incomplete directories: SPDX in Files\n')
4350e7f0306SThomas Gleixner                for f in sorted(parser.spdx_dirs.keys()):
4360e7f0306SThomas Gleixner                    di = parser.spdx_dirs[f]
4370e7f0306SThomas Gleixner                    if di.missing:
4380e7f0306SThomas Gleixner                        valid = di.total - di.missing
4390e7f0306SThomas Gleixner                        pc = int(100 * valid / di.total)
4400e7f0306SThomas Gleixner                        sys.stderr.write('    %-80s: %5d of %5d  %3d%%\n' %(f, valid, di.total, pc))
4410e7f0306SThomas Gleixner
44267924b71SThomas Gleixner            if ndirs and ndirs != dirsok and args.files:
44367924b71SThomas Gleixner                if args.verbose or args.dirs:
44467924b71SThomas Gleixner                    sys.stderr.write('\n')
44567924b71SThomas Gleixner                sys.stderr.write('Files without SPDX:\n')
44667924b71SThomas Gleixner                for f in sorted(parser.spdx_dirs.keys()):
44767924b71SThomas Gleixner                    di = parser.spdx_dirs[f]
44867924b71SThomas Gleixner                    for f in sorted(di.files):
44967924b71SThomas Gleixner                        sys.stderr.write('    %s\n' %f)
45067924b71SThomas Gleixner
4515385a295SThomas Gleixner            sys.exit(0)
4525385a295SThomas Gleixner
4535385a295SThomas Gleixner    except Exception as ex:
4545385a295SThomas Gleixner        sys.stderr.write('FAIL: %s\n' %ex)
4555385a295SThomas Gleixner        sys.stderr.write('%s\n' %traceback.format_exc())
4565385a295SThomas Gleixner        sys.exit(1)
457