162306a36Sopenharmony_ci#!/usr/bin/env python3
262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0
362306a36Sopenharmony_ci# Copyright Thomas Gleixner <tglx@linutronix.de>
462306a36Sopenharmony_ci
562306a36Sopenharmony_cifrom argparse import ArgumentParser
662306a36Sopenharmony_cifrom ply import lex, yacc
762306a36Sopenharmony_ciimport locale
862306a36Sopenharmony_ciimport traceback
962306a36Sopenharmony_ciimport fnmatch
1062306a36Sopenharmony_ciimport sys
1162306a36Sopenharmony_ciimport git
1262306a36Sopenharmony_ciimport re
1362306a36Sopenharmony_ciimport os
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ciclass ParserException(Exception):
1662306a36Sopenharmony_ci    def __init__(self, tok, txt):
1762306a36Sopenharmony_ci        self.tok = tok
1862306a36Sopenharmony_ci        self.txt = txt
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ciclass SPDXException(Exception):
2162306a36Sopenharmony_ci    def __init__(self, el, txt):
2262306a36Sopenharmony_ci        self.el = el
2362306a36Sopenharmony_ci        self.txt = txt
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ciclass SPDXdata(object):
2662306a36Sopenharmony_ci    def __init__(self):
2762306a36Sopenharmony_ci        self.license_files = 0
2862306a36Sopenharmony_ci        self.exception_files = 0
2962306a36Sopenharmony_ci        self.licenses = [ ]
3062306a36Sopenharmony_ci        self.exceptions = { }
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ciclass dirinfo(object):
3362306a36Sopenharmony_ci    def __init__(self):
3462306a36Sopenharmony_ci        self.missing = 0
3562306a36Sopenharmony_ci        self.total = 0
3662306a36Sopenharmony_ci        self.files = []
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci    def update(self, fname, basedir, miss):
3962306a36Sopenharmony_ci        self.total += 1
4062306a36Sopenharmony_ci        self.missing += miss
4162306a36Sopenharmony_ci        if miss:
4262306a36Sopenharmony_ci            fname = './' + fname
4362306a36Sopenharmony_ci            bdir = os.path.dirname(fname)
4462306a36Sopenharmony_ci            if bdir == basedir.rstrip('/'):
4562306a36Sopenharmony_ci                self.files.append(fname)
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci# Read the spdx data from the LICENSES directory
4862306a36Sopenharmony_cidef read_spdxdata(repo):
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci    # The subdirectories of LICENSES in the kernel source
5162306a36Sopenharmony_ci    # Note: exceptions needs to be parsed as last directory.
5262306a36Sopenharmony_ci    license_dirs = [ "preferred", "dual", "deprecated", "exceptions" ]
5362306a36Sopenharmony_ci    lictree = repo.head.commit.tree['LICENSES']
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci    spdx = SPDXdata()
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci    for d in license_dirs:
5862306a36Sopenharmony_ci        for el in lictree[d].traverse():
5962306a36Sopenharmony_ci            if not os.path.isfile(el.path):
6062306a36Sopenharmony_ci                continue
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci            exception = None
6362306a36Sopenharmony_ci            for l in open(el.path, encoding="utf-8").readlines():
6462306a36Sopenharmony_ci                if l.startswith('Valid-License-Identifier:'):
6562306a36Sopenharmony_ci                    lid = l.split(':')[1].strip().upper()
6662306a36Sopenharmony_ci                    if lid in spdx.licenses:
6762306a36Sopenharmony_ci                        raise SPDXException(el, 'Duplicate License Identifier: %s' %lid)
6862306a36Sopenharmony_ci                    else:
6962306a36Sopenharmony_ci                        spdx.licenses.append(lid)
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci                elif l.startswith('SPDX-Exception-Identifier:'):
7262306a36Sopenharmony_ci                    exception = l.split(':')[1].strip().upper()
7362306a36Sopenharmony_ci                    spdx.exceptions[exception] = []
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci                elif l.startswith('SPDX-Licenses:'):
7662306a36Sopenharmony_ci                    for lic in l.split(':')[1].upper().strip().replace(' ', '').replace('\t', '').split(','):
7762306a36Sopenharmony_ci                        if not lic in spdx.licenses:
7862306a36Sopenharmony_ci                            raise SPDXException(None, 'Exception %s missing license %s' %(exception, lic))
7962306a36Sopenharmony_ci                        spdx.exceptions[exception].append(lic)
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci                elif l.startswith("License-Text:"):
8262306a36Sopenharmony_ci                    if exception:
8362306a36Sopenharmony_ci                        if not len(spdx.exceptions[exception]):
8462306a36Sopenharmony_ci                            raise SPDXException(el, 'Exception %s is missing SPDX-Licenses' %exception)
8562306a36Sopenharmony_ci                        spdx.exception_files += 1
8662306a36Sopenharmony_ci                    else:
8762306a36Sopenharmony_ci                        spdx.license_files += 1
8862306a36Sopenharmony_ci                    break
8962306a36Sopenharmony_ci    return spdx
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ciclass id_parser(object):
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci    reserved = [ 'AND', 'OR', 'WITH' ]
9462306a36Sopenharmony_ci    tokens = [ 'LPAR', 'RPAR', 'ID', 'EXC' ] + reserved
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci    precedence = ( ('nonassoc', 'AND', 'OR'), )
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci    t_ignore = ' \t'
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci    def __init__(self, spdx):
10162306a36Sopenharmony_ci        self.spdx = spdx
10262306a36Sopenharmony_ci        self.lasttok = None
10362306a36Sopenharmony_ci        self.lastid = None
10462306a36Sopenharmony_ci        self.lexer = lex.lex(module = self, reflags = re.UNICODE)
10562306a36Sopenharmony_ci        # Initialize the parser. No debug file and no parser rules stored on disk
10662306a36Sopenharmony_ci        # The rules are small enough to be generated on the fly
10762306a36Sopenharmony_ci        self.parser = yacc.yacc(module = self, write_tables = False, debug = False)
10862306a36Sopenharmony_ci        self.lines_checked = 0
10962306a36Sopenharmony_ci        self.checked = 0
11062306a36Sopenharmony_ci        self.excluded = 0
11162306a36Sopenharmony_ci        self.spdx_valid = 0
11262306a36Sopenharmony_ci        self.spdx_errors = 0
11362306a36Sopenharmony_ci        self.spdx_dirs = {}
11462306a36Sopenharmony_ci        self.dirdepth = -1
11562306a36Sopenharmony_ci        self.basedir = '.'
11662306a36Sopenharmony_ci        self.curline = 0
11762306a36Sopenharmony_ci        self.deepest = 0
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci    def set_dirinfo(self, basedir, dirdepth):
12062306a36Sopenharmony_ci        if dirdepth >= 0:
12162306a36Sopenharmony_ci            self.basedir = basedir
12262306a36Sopenharmony_ci            bdir = basedir.lstrip('./').rstrip('/')
12362306a36Sopenharmony_ci            if bdir != '':
12462306a36Sopenharmony_ci                parts = bdir.split('/')
12562306a36Sopenharmony_ci            else:
12662306a36Sopenharmony_ci                parts = []
12762306a36Sopenharmony_ci            self.dirdepth = dirdepth + len(parts)
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci    # Validate License and Exception IDs
13062306a36Sopenharmony_ci    def validate(self, tok):
13162306a36Sopenharmony_ci        id = tok.value.upper()
13262306a36Sopenharmony_ci        if tok.type == 'ID':
13362306a36Sopenharmony_ci            if not id in self.spdx.licenses:
13462306a36Sopenharmony_ci                raise ParserException(tok, 'Invalid License ID')
13562306a36Sopenharmony_ci            self.lastid = id
13662306a36Sopenharmony_ci        elif tok.type == 'EXC':
13762306a36Sopenharmony_ci            if id not in self.spdx.exceptions:
13862306a36Sopenharmony_ci                raise ParserException(tok, 'Invalid Exception ID')
13962306a36Sopenharmony_ci            if self.lastid not in self.spdx.exceptions[id]:
14062306a36Sopenharmony_ci                raise ParserException(tok, 'Exception not valid for license %s' %self.lastid)
14162306a36Sopenharmony_ci            self.lastid = None
14262306a36Sopenharmony_ci        elif tok.type != 'WITH':
14362306a36Sopenharmony_ci            self.lastid = None
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci    # Lexer functions
14662306a36Sopenharmony_ci    def t_RPAR(self, tok):
14762306a36Sopenharmony_ci        r'\)'
14862306a36Sopenharmony_ci        self.lasttok = tok.type
14962306a36Sopenharmony_ci        return tok
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci    def t_LPAR(self, tok):
15262306a36Sopenharmony_ci        r'\('
15362306a36Sopenharmony_ci        self.lasttok = tok.type
15462306a36Sopenharmony_ci        return tok
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci    def t_ID(self, tok):
15762306a36Sopenharmony_ci        r'[A-Za-z.0-9\-+]+'
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci        if self.lasttok == 'EXC':
16062306a36Sopenharmony_ci            print(tok)
16162306a36Sopenharmony_ci            raise ParserException(tok, 'Missing parentheses')
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci        tok.value = tok.value.strip()
16462306a36Sopenharmony_ci        val = tok.value.upper()
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci        if val in self.reserved:
16762306a36Sopenharmony_ci            tok.type = val
16862306a36Sopenharmony_ci        elif self.lasttok == 'WITH':
16962306a36Sopenharmony_ci            tok.type = 'EXC'
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci        self.lasttok = tok.type
17262306a36Sopenharmony_ci        self.validate(tok)
17362306a36Sopenharmony_ci        return tok
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci    def t_error(self, tok):
17662306a36Sopenharmony_ci        raise ParserException(tok, 'Invalid token')
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci    def p_expr(self, p):
17962306a36Sopenharmony_ci        '''expr : ID
18062306a36Sopenharmony_ci                | ID WITH EXC
18162306a36Sopenharmony_ci                | expr AND expr
18262306a36Sopenharmony_ci                | expr OR expr
18362306a36Sopenharmony_ci                | LPAR expr RPAR'''
18462306a36Sopenharmony_ci        pass
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci    def p_error(self, p):
18762306a36Sopenharmony_ci        if not p:
18862306a36Sopenharmony_ci            raise ParserException(None, 'Unfinished license expression')
18962306a36Sopenharmony_ci        else:
19062306a36Sopenharmony_ci            raise ParserException(p, 'Syntax error')
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci    def parse(self, expr):
19362306a36Sopenharmony_ci        self.lasttok = None
19462306a36Sopenharmony_ci        self.lastid = None
19562306a36Sopenharmony_ci        self.parser.parse(expr, lexer = self.lexer)
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci    def parse_lines(self, fd, maxlines, fname):
19862306a36Sopenharmony_ci        self.checked += 1
19962306a36Sopenharmony_ci        self.curline = 0
20062306a36Sopenharmony_ci        fail = 1
20162306a36Sopenharmony_ci        try:
20262306a36Sopenharmony_ci            for line in fd:
20362306a36Sopenharmony_ci                line = line.decode(locale.getpreferredencoding(False), errors='ignore')
20462306a36Sopenharmony_ci                self.curline += 1
20562306a36Sopenharmony_ci                if self.curline > maxlines:
20662306a36Sopenharmony_ci                    break
20762306a36Sopenharmony_ci                self.lines_checked += 1
20862306a36Sopenharmony_ci                if line.find("SPDX-License-Identifier:") < 0:
20962306a36Sopenharmony_ci                    continue
21062306a36Sopenharmony_ci                expr = line.split(':')[1].strip()
21162306a36Sopenharmony_ci                # Remove trailing comment closure
21262306a36Sopenharmony_ci                if line.strip().endswith('*/'):
21362306a36Sopenharmony_ci                    expr = expr.rstrip('*/').strip()
21462306a36Sopenharmony_ci                # Remove trailing xml comment closure
21562306a36Sopenharmony_ci                if line.strip().endswith('-->'):
21662306a36Sopenharmony_ci                    expr = expr.rstrip('-->').strip()
21762306a36Sopenharmony_ci                # Special case for SH magic boot code files
21862306a36Sopenharmony_ci                if line.startswith('LIST \"'):
21962306a36Sopenharmony_ci                    expr = expr.rstrip('\"').strip()
22062306a36Sopenharmony_ci                self.parse(expr)
22162306a36Sopenharmony_ci                self.spdx_valid += 1
22262306a36Sopenharmony_ci                #
22362306a36Sopenharmony_ci                # Should we check for more SPDX ids in the same file and
22462306a36Sopenharmony_ci                # complain if there are any?
22562306a36Sopenharmony_ci                #
22662306a36Sopenharmony_ci                fail = 0
22762306a36Sopenharmony_ci                break
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci        except ParserException as pe:
23062306a36Sopenharmony_ci            if pe.tok:
23162306a36Sopenharmony_ci                col = line.find(expr) + pe.tok.lexpos
23262306a36Sopenharmony_ci                tok = pe.tok.value
23362306a36Sopenharmony_ci                sys.stdout.write('%s: %d:%d %s: %s\n' %(fname, self.curline, col, pe.txt, tok))
23462306a36Sopenharmony_ci            else:
23562306a36Sopenharmony_ci                sys.stdout.write('%s: %d:0 %s\n' %(fname, self.curline, pe.txt))
23662306a36Sopenharmony_ci            self.spdx_errors += 1
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci        if fname == '-':
23962306a36Sopenharmony_ci            return
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci        base = os.path.dirname(fname)
24262306a36Sopenharmony_ci        if self.dirdepth > 0:
24362306a36Sopenharmony_ci            parts = base.split('/')
24462306a36Sopenharmony_ci            i = 0
24562306a36Sopenharmony_ci            base = '.'
24662306a36Sopenharmony_ci            while i < self.dirdepth and i < len(parts) and len(parts[i]):
24762306a36Sopenharmony_ci                base += '/' + parts[i]
24862306a36Sopenharmony_ci                i += 1
24962306a36Sopenharmony_ci        elif self.dirdepth == 0:
25062306a36Sopenharmony_ci            base = self.basedir
25162306a36Sopenharmony_ci        else:
25262306a36Sopenharmony_ci            base = './' + base.rstrip('/')
25362306a36Sopenharmony_ci        base += '/'
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci        di = self.spdx_dirs.get(base, dirinfo())
25662306a36Sopenharmony_ci        di.update(fname, base, fail)
25762306a36Sopenharmony_ci        self.spdx_dirs[base] = di
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ciclass pattern(object):
26062306a36Sopenharmony_ci    def __init__(self, line):
26162306a36Sopenharmony_ci        self.pattern = line
26262306a36Sopenharmony_ci        self.match = self.match_file
26362306a36Sopenharmony_ci        if line == '.*':
26462306a36Sopenharmony_ci            self.match = self.match_dot
26562306a36Sopenharmony_ci        elif line.endswith('/'):
26662306a36Sopenharmony_ci            self.pattern = line[:-1]
26762306a36Sopenharmony_ci            self.match = self.match_dir
26862306a36Sopenharmony_ci        elif line.startswith('/'):
26962306a36Sopenharmony_ci            self.pattern = line[1:]
27062306a36Sopenharmony_ci            self.match = self.match_fn
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci    def match_dot(self, fpath):
27362306a36Sopenharmony_ci        return os.path.basename(fpath).startswith('.')
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci    def match_file(self, fpath):
27662306a36Sopenharmony_ci        return os.path.basename(fpath) == self.pattern
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci    def match_fn(self, fpath):
27962306a36Sopenharmony_ci        return fnmatch.fnmatchcase(fpath, self.pattern)
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci    def match_dir(self, fpath):
28262306a36Sopenharmony_ci        if self.match_fn(os.path.dirname(fpath)):
28362306a36Sopenharmony_ci            return True
28462306a36Sopenharmony_ci        return fpath.startswith(self.pattern)
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_cidef exclude_file(fpath):
28762306a36Sopenharmony_ci    for rule in exclude_rules:
28862306a36Sopenharmony_ci        if rule.match(fpath):
28962306a36Sopenharmony_ci            return True
29062306a36Sopenharmony_ci    return False
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cidef scan_git_tree(tree, basedir, dirdepth):
29362306a36Sopenharmony_ci    parser.set_dirinfo(basedir, dirdepth)
29462306a36Sopenharmony_ci    for el in tree.traverse():
29562306a36Sopenharmony_ci        if not os.path.isfile(el.path):
29662306a36Sopenharmony_ci            continue
29762306a36Sopenharmony_ci        if exclude_file(el.path):
29862306a36Sopenharmony_ci            parser.excluded += 1
29962306a36Sopenharmony_ci            continue
30062306a36Sopenharmony_ci        with open(el.path, 'rb') as fd:
30162306a36Sopenharmony_ci            parser.parse_lines(fd, args.maxlines, el.path)
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cidef scan_git_subtree(tree, path, dirdepth):
30462306a36Sopenharmony_ci    for p in path.strip('/').split('/'):
30562306a36Sopenharmony_ci        tree = tree[p]
30662306a36Sopenharmony_ci    scan_git_tree(tree, path.strip('/'), dirdepth)
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cidef read_exclude_file(fname):
30962306a36Sopenharmony_ci    rules = []
31062306a36Sopenharmony_ci    if not fname:
31162306a36Sopenharmony_ci        return rules
31262306a36Sopenharmony_ci    with open(fname) as fd:
31362306a36Sopenharmony_ci        for line in fd:
31462306a36Sopenharmony_ci            line = line.strip()
31562306a36Sopenharmony_ci            if line.startswith('#'):
31662306a36Sopenharmony_ci                continue
31762306a36Sopenharmony_ci            if not len(line):
31862306a36Sopenharmony_ci                continue
31962306a36Sopenharmony_ci            rules.append(pattern(line))
32062306a36Sopenharmony_ci    return rules
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ciif __name__ == '__main__':
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci    ap = ArgumentParser(description='SPDX expression checker')
32562306a36Sopenharmony_ci    ap.add_argument('path', nargs='*', help='Check path or file. If not given full git tree scan. For stdin use "-"')
32662306a36Sopenharmony_ci    ap.add_argument('-d', '--dirs', action='store_true',
32762306a36Sopenharmony_ci                    help='Show [sub]directory statistics.')
32862306a36Sopenharmony_ci    ap.add_argument('-D', '--depth', type=int, default=-1,
32962306a36Sopenharmony_ci                    help='Directory depth for -d statistics. Default: unlimited')
33062306a36Sopenharmony_ci    ap.add_argument('-e', '--exclude',
33162306a36Sopenharmony_ci                    help='File containing file patterns to exclude. Default: scripts/spdxexclude')
33262306a36Sopenharmony_ci    ap.add_argument('-f', '--files', action='store_true',
33362306a36Sopenharmony_ci                    help='Show files without SPDX.')
33462306a36Sopenharmony_ci    ap.add_argument('-m', '--maxlines', type=int, default=15,
33562306a36Sopenharmony_ci                    help='Maximum number of lines to scan in a file. Default 15')
33662306a36Sopenharmony_ci    ap.add_argument('-v', '--verbose', action='store_true', help='Verbose statistics output')
33762306a36Sopenharmony_ci    args = ap.parse_args()
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci    # Sanity check path arguments
34062306a36Sopenharmony_ci    if '-' in args.path and len(args.path) > 1:
34162306a36Sopenharmony_ci        sys.stderr.write('stdin input "-" must be the only path argument\n')
34262306a36Sopenharmony_ci        sys.exit(1)
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci    try:
34562306a36Sopenharmony_ci        # Use git to get the valid license expressions
34662306a36Sopenharmony_ci        repo = git.Repo(os.getcwd())
34762306a36Sopenharmony_ci        assert not repo.bare
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci        # Initialize SPDX data
35062306a36Sopenharmony_ci        spdx = read_spdxdata(repo)
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci        # Initialize the parser
35362306a36Sopenharmony_ci        parser = id_parser(spdx)
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci    except SPDXException as se:
35662306a36Sopenharmony_ci        if se.el:
35762306a36Sopenharmony_ci            sys.stderr.write('%s: %s\n' %(se.el.path, se.txt))
35862306a36Sopenharmony_ci        else:
35962306a36Sopenharmony_ci            sys.stderr.write('%s\n' %se.txt)
36062306a36Sopenharmony_ci        sys.exit(1)
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci    except Exception as ex:
36362306a36Sopenharmony_ci        sys.stderr.write('FAIL: %s\n' %ex)
36462306a36Sopenharmony_ci        sys.stderr.write('%s\n' %traceback.format_exc())
36562306a36Sopenharmony_ci        sys.exit(1)
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci    try:
36862306a36Sopenharmony_ci        fname = args.exclude
36962306a36Sopenharmony_ci        if not fname:
37062306a36Sopenharmony_ci            fname = os.path.join(os.path.dirname(__file__), 'spdxexclude')
37162306a36Sopenharmony_ci        exclude_rules = read_exclude_file(fname)
37262306a36Sopenharmony_ci    except Exception as ex:
37362306a36Sopenharmony_ci        sys.stderr.write('FAIL: Reading exclude file %s: %s\n' %(fname, ex))
37462306a36Sopenharmony_ci        sys.exit(1)
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci    try:
37762306a36Sopenharmony_ci        if len(args.path) and args.path[0] == '-':
37862306a36Sopenharmony_ci            stdin = os.fdopen(sys.stdin.fileno(), 'rb')
37962306a36Sopenharmony_ci            parser.parse_lines(stdin, args.maxlines, '-')
38062306a36Sopenharmony_ci        else:
38162306a36Sopenharmony_ci            if args.path:
38262306a36Sopenharmony_ci                for p in args.path:
38362306a36Sopenharmony_ci                    if os.path.isfile(p):
38462306a36Sopenharmony_ci                        parser.parse_lines(open(p, 'rb'), args.maxlines, p)
38562306a36Sopenharmony_ci                    elif os.path.isdir(p):
38662306a36Sopenharmony_ci                        scan_git_subtree(repo.head.reference.commit.tree, p,
38762306a36Sopenharmony_ci                                         args.depth)
38862306a36Sopenharmony_ci                    else:
38962306a36Sopenharmony_ci                        sys.stderr.write('path %s does not exist\n' %p)
39062306a36Sopenharmony_ci                        sys.exit(1)
39162306a36Sopenharmony_ci            else:
39262306a36Sopenharmony_ci                # Full git tree scan
39362306a36Sopenharmony_ci                scan_git_tree(repo.head.commit.tree, '.', args.depth)
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci            ndirs = len(parser.spdx_dirs)
39662306a36Sopenharmony_ci            dirsok = 0
39762306a36Sopenharmony_ci            if ndirs:
39862306a36Sopenharmony_ci                for di in parser.spdx_dirs.values():
39962306a36Sopenharmony_ci                    if not di.missing:
40062306a36Sopenharmony_ci                        dirsok += 1
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci            if args.verbose:
40362306a36Sopenharmony_ci                sys.stderr.write('\n')
40462306a36Sopenharmony_ci                sys.stderr.write('License files:     %12d\n' %spdx.license_files)
40562306a36Sopenharmony_ci                sys.stderr.write('Exception files:   %12d\n' %spdx.exception_files)
40662306a36Sopenharmony_ci                sys.stderr.write('License IDs        %12d\n' %len(spdx.licenses))
40762306a36Sopenharmony_ci                sys.stderr.write('Exception IDs      %12d\n' %len(spdx.exceptions))
40862306a36Sopenharmony_ci                sys.stderr.write('\n')
40962306a36Sopenharmony_ci                sys.stderr.write('Files excluded:    %12d\n' %parser.excluded)
41062306a36Sopenharmony_ci                sys.stderr.write('Files checked:     %12d\n' %parser.checked)
41162306a36Sopenharmony_ci                sys.stderr.write('Lines checked:     %12d\n' %parser.lines_checked)
41262306a36Sopenharmony_ci                if parser.checked:
41362306a36Sopenharmony_ci                    pc = int(100 * parser.spdx_valid / parser.checked)
41462306a36Sopenharmony_ci                    sys.stderr.write('Files with SPDX:   %12d %3d%%\n' %(parser.spdx_valid, pc))
41562306a36Sopenharmony_ci                sys.stderr.write('Files with errors: %12d\n' %parser.spdx_errors)
41662306a36Sopenharmony_ci                if ndirs:
41762306a36Sopenharmony_ci                    sys.stderr.write('\n')
41862306a36Sopenharmony_ci                    sys.stderr.write('Directories accounted: %8d\n' %ndirs)
41962306a36Sopenharmony_ci                    pc = int(100 * dirsok / ndirs)
42062306a36Sopenharmony_ci                    sys.stderr.write('Directories complete:  %8d %3d%%\n' %(dirsok, pc))
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci            if ndirs and ndirs != dirsok and args.dirs:
42362306a36Sopenharmony_ci                if args.verbose:
42462306a36Sopenharmony_ci                    sys.stderr.write('\n')
42562306a36Sopenharmony_ci                sys.stderr.write('Incomplete directories: SPDX in Files\n')
42662306a36Sopenharmony_ci                for f in sorted(parser.spdx_dirs.keys()):
42762306a36Sopenharmony_ci                    di = parser.spdx_dirs[f]
42862306a36Sopenharmony_ci                    if di.missing:
42962306a36Sopenharmony_ci                        valid = di.total - di.missing
43062306a36Sopenharmony_ci                        pc = int(100 * valid / di.total)
43162306a36Sopenharmony_ci                        sys.stderr.write('    %-80s: %5d of %5d  %3d%%\n' %(f, valid, di.total, pc))
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci            if ndirs and ndirs != dirsok and args.files:
43462306a36Sopenharmony_ci                if args.verbose or args.dirs:
43562306a36Sopenharmony_ci                    sys.stderr.write('\n')
43662306a36Sopenharmony_ci                sys.stderr.write('Files without SPDX:\n')
43762306a36Sopenharmony_ci                for f in sorted(parser.spdx_dirs.keys()):
43862306a36Sopenharmony_ci                    di = parser.spdx_dirs[f]
43962306a36Sopenharmony_ci                    for f in sorted(di.files):
44062306a36Sopenharmony_ci                        sys.stderr.write('    %s\n' %f)
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci            sys.exit(0)
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci    except Exception as ex:
44562306a36Sopenharmony_ci        sys.stderr.write('FAIL: %s\n' %ex)
44662306a36Sopenharmony_ci        sys.stderr.write('%s\n' %traceback.format_exc())
44762306a36Sopenharmony_ci        sys.exit(1)
448