1*3117ece4Schristos#!/usr/bin/env python3 2*3117ece4Schristos 3*3117ece4Schristos# ################################################################ 4*3117ece4Schristos# Copyright (c) Meta Platforms, Inc. and affiliates. 5*3117ece4Schristos# All rights reserved. 6*3117ece4Schristos# 7*3117ece4Schristos# This source code is licensed under both the BSD-style license (found in the 8*3117ece4Schristos# LICENSE file in the root directory of this source tree) and the GPLv2 (found 9*3117ece4Schristos# in the COPYING file in the root directory of this source tree). 10*3117ece4Schristos# You may select, at your option, one of the above-listed licenses. 11*3117ece4Schristos# ################################################################ 12*3117ece4Schristos 13*3117ece4Schristosimport enum 14*3117ece4Schristosimport glob 15*3117ece4Schristosimport os 16*3117ece4Schristosimport re 17*3117ece4Schristosimport sys 18*3117ece4Schristos 19*3117ece4SchristosROOT = os.path.join(os.path.dirname(__file__), "..") 20*3117ece4Schristos 21*3117ece4SchristosRELDIRS = [ 22*3117ece4Schristos "doc", 23*3117ece4Schristos "examples", 24*3117ece4Schristos "lib", 25*3117ece4Schristos "programs", 26*3117ece4Schristos "tests", 27*3117ece4Schristos "contrib/linux-kernel", 28*3117ece4Schristos] 29*3117ece4Schristos 30*3117ece4SchristosREL_EXCLUDES = [ 31*3117ece4Schristos "contrib/linux-kernel/test/include", 32*3117ece4Schristos] 33*3117ece4Schristos 34*3117ece4Schristosdef to_abs(d): 35*3117ece4Schristos return os.path.normpath(os.path.join(ROOT, d)) + "/" 36*3117ece4Schristos 37*3117ece4SchristosDIRS = [to_abs(d) for d in RELDIRS] 38*3117ece4SchristosEXCLUDES = [to_abs(d) for d in REL_EXCLUDES] 39*3117ece4Schristos 40*3117ece4SchristosSUFFIXES = [ 41*3117ece4Schristos ".c", 42*3117ece4Schristos ".h", 43*3117ece4Schristos "Makefile", 44*3117ece4Schristos ".mk", 45*3117ece4Schristos ".py", 46*3117ece4Schristos ".S", 47*3117ece4Schristos] 48*3117ece4Schristos 49*3117ece4Schristos# License should certainly be in the first 10 KB. 50*3117ece4SchristosMAX_BYTES = 10000 51*3117ece4SchristosMAX_LINES = 50 52*3117ece4Schristos 53*3117ece4SchristosLICENSE_LINES = [ 54*3117ece4Schristos "This source code is licensed under both the BSD-style license (found in the", 55*3117ece4Schristos "LICENSE file in the root directory of this source tree) and the GPLv2 (found", 56*3117ece4Schristos "in the COPYING file in the root directory of this source tree).", 57*3117ece4Schristos "You may select, at your option, one of the above-listed licenses.", 58*3117ece4Schristos] 59*3117ece4Schristos 60*3117ece4SchristosCOPYRIGHT_EXCEPTIONS = { 61*3117ece4Schristos # From zstdmt 62*3117ece4Schristos "threading.c", 63*3117ece4Schristos "threading.h", 64*3117ece4Schristos # From divsufsort 65*3117ece4Schristos "divsufsort.c", 66*3117ece4Schristos "divsufsort.h", 67*3117ece4Schristos} 68*3117ece4Schristos 69*3117ece4SchristosLICENSE_EXCEPTIONS = { 70*3117ece4Schristos # From divsufsort 71*3117ece4Schristos "divsufsort.c", 72*3117ece4Schristos "divsufsort.h", 73*3117ece4Schristos # License is slightly different because it references GitHub 74*3117ece4Schristos "linux_zstd.h", 75*3117ece4Schristos} 76*3117ece4Schristos 77*3117ece4Schristos 78*3117ece4Schristosdef valid_copyright(lines): 79*3117ece4Schristos YEAR_REGEX = re.compile("\d\d\d\d|present") 80*3117ece4Schristos for line in lines: 81*3117ece4Schristos line = line.strip() 82*3117ece4Schristos if "Copyright" not in line: 83*3117ece4Schristos continue 84*3117ece4Schristos if "present" in line: 85*3117ece4Schristos return (False, f"Copyright line '{line}' contains 'present'!") 86*3117ece4Schristos if "Meta Platforms, Inc" not in line: 87*3117ece4Schristos return (False, f"Copyright line '{line}' does not contain 'Meta Platforms, Inc'") 88*3117ece4Schristos year = YEAR_REGEX.search(line) 89*3117ece4Schristos if year is not None: 90*3117ece4Schristos return (False, f"Copyright line '{line}' contains {year.group(0)}; it should be yearless") 91*3117ece4Schristos if " (c) " not in line: 92*3117ece4Schristos return (False, f"Copyright line '{line}' does not contain ' (c) '!") 93*3117ece4Schristos return (True, "") 94*3117ece4Schristos return (False, "Copyright not found!") 95*3117ece4Schristos 96*3117ece4Schristos 97*3117ece4Schristosdef valid_license(lines): 98*3117ece4Schristos for b in range(len(lines)): 99*3117ece4Schristos if LICENSE_LINES[0] not in lines[b]: 100*3117ece4Schristos continue 101*3117ece4Schristos for l in range(len(LICENSE_LINES)): 102*3117ece4Schristos if LICENSE_LINES[l] not in lines[b + l]: 103*3117ece4Schristos message = f"""Invalid license line found starting on line {b + l}! 104*3117ece4SchristosExpected: '{LICENSE_LINES[l]}' 105*3117ece4SchristosActual: '{lines[b + l]}'""" 106*3117ece4Schristos return (False, message) 107*3117ece4Schristos return (True, "") 108*3117ece4Schristos return (False, "License not found!") 109*3117ece4Schristos 110*3117ece4Schristos 111*3117ece4Schristosdef valid_file(filename): 112*3117ece4Schristos with open(filename, "r") as f: 113*3117ece4Schristos lines = f.readlines(MAX_BYTES) 114*3117ece4Schristos lines = lines[:min(len(lines), MAX_LINES)] 115*3117ece4Schristos 116*3117ece4Schristos ok = True 117*3117ece4Schristos if os.path.basename(filename) not in COPYRIGHT_EXCEPTIONS: 118*3117ece4Schristos c_ok, c_msg = valid_copyright(lines) 119*3117ece4Schristos if not c_ok: 120*3117ece4Schristos print(f"{filename}: {c_msg}", file=sys.stderr) 121*3117ece4Schristos ok = False 122*3117ece4Schristos if os.path.basename(filename) not in LICENSE_EXCEPTIONS: 123*3117ece4Schristos l_ok, l_msg = valid_license(lines) 124*3117ece4Schristos if not l_ok: 125*3117ece4Schristos print(f"{filename}: {l_msg}", file=sys.stderr) 126*3117ece4Schristos ok = False 127*3117ece4Schristos return ok 128*3117ece4Schristos 129*3117ece4Schristos 130*3117ece4Schristosdef exclude(filename): 131*3117ece4Schristos for x in EXCLUDES: 132*3117ece4Schristos if filename.startswith(x): 133*3117ece4Schristos return True 134*3117ece4Schristos return False 135*3117ece4Schristos 136*3117ece4Schristosdef main(): 137*3117ece4Schristos invalid_files = [] 138*3117ece4Schristos for directory in DIRS: 139*3117ece4Schristos for suffix in SUFFIXES: 140*3117ece4Schristos files = set(glob.glob(f"{directory}/**/*{suffix}", recursive=True)) 141*3117ece4Schristos for filename in files: 142*3117ece4Schristos if exclude(filename): 143*3117ece4Schristos continue 144*3117ece4Schristos if not valid_file(filename): 145*3117ece4Schristos invalid_files.append(filename) 146*3117ece4Schristos if len(invalid_files) > 0: 147*3117ece4Schristos print("Fail!", file=sys.stderr) 148*3117ece4Schristos for f in invalid_files: 149*3117ece4Schristos print(f) 150*3117ece4Schristos return 1 151*3117ece4Schristos else: 152*3117ece4Schristos print("Pass!", file=sys.stderr) 153*3117ece4Schristos return 0 154*3117ece4Schristos 155*3117ece4Schristosif __name__ == "__main__": 156*3117ece4Schristos sys.exit(main()) 157