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