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