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