Nick Terrell | 1f14435 | 2020-03-26 16:57:48 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | # ################################################################ |
| 4 | # Copyright (c) 2016-2020, 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 | |
| 13 | import datetime |
| 14 | import enum |
| 15 | import glob |
| 16 | import os |
| 17 | import sys |
| 18 | |
| 19 | YEAR = datetime.datetime.now().year |
| 20 | |
| 21 | YEAR_STR = str(YEAR) |
| 22 | |
| 23 | ROOT = os.path.join(os.path.dirname(__file__), "..") |
| 24 | |
| 25 | RELDIRS = [ |
| 26 | "doc", |
| 27 | "examples", |
| 28 | "lib", |
| 29 | "programs", |
| 30 | "tests", |
| 31 | ] |
| 32 | |
| 33 | DIRS = [os.path.join(ROOT, d) for d in RELDIRS] |
| 34 | |
| 35 | class File(enum.Enum): |
| 36 | C = 1 |
| 37 | H = 2 |
| 38 | MAKE = 3 |
| 39 | PY = 4 |
| 40 | |
| 41 | SUFFIX = { |
| 42 | File.C: ".c", |
| 43 | File.H: ".h", |
| 44 | File.MAKE: "Makefile", |
| 45 | File.PY: ".py", |
| 46 | } |
| 47 | |
| 48 | # License should certainly be in the first 10 KB. |
| 49 | MAX_BYTES = 10000 |
| 50 | MAX_LINES = 50 |
| 51 | |
| 52 | LICENSE_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 | |
| 59 | COPYRIGHT_EXCEPTIONS = { |
| 60 | # From zstdmt |
| 61 | "threading.c", |
| 62 | "threading.h", |
| 63 | # From divsufsort |
| 64 | "divsufsort.c", |
| 65 | "divsufsort.h", |
| 66 | } |
| 67 | |
| 68 | LICENSE_EXCEPTIONS = { |
| 69 | # From divsufsort |
| 70 | "divsufsort.c", |
| 71 | "divsufsort.h", |
| 72 | } |
| 73 | |
| 74 | |
| 75 | def valid_copyright(lines): |
| 76 | for line in lines: |
| 77 | line = line.strip() |
| 78 | if "Copyright" not in line: |
| 79 | continue |
| 80 | if "present" in line: |
| 81 | return (False, f"Copyright line '{line}' contains 'present'!") |
| 82 | if "Facebook, Inc" not in line: |
| 83 | return (False, f"Copyright line '{line}' does not contain 'Facebook, Inc'") |
| 84 | if YEAR_STR not in line: |
| 85 | return (False, f"Copyright line '{line}' does not contain {YEAR}") |
| 86 | if " (c) " not in line: |
| 87 | return (False, f"Copyright line '{line}' does not contain ' (c) '!") |
| 88 | return (True, "") |
| 89 | return (False, "Copyright not found!") |
| 90 | |
| 91 | |
| 92 | def valid_license(lines): |
| 93 | for b in range(len(lines)): |
| 94 | if LICENSE_LINES[0] not in lines[b]: |
| 95 | continue |
| 96 | for l in range(len(LICENSE_LINES)): |
| 97 | if LICENSE_LINES[l] not in lines[b + l]: |
| 98 | message = f"""Invalid license line found starting on line {b + l}! |
| 99 | Expected: '{LICENSE_LINES[l]}' |
| 100 | Actual: '{lines[b + l]}'""" |
| 101 | return (False, message) |
| 102 | return (True, "") |
| 103 | return (False, "License not found!") |
| 104 | |
| 105 | |
| 106 | def valid_file(filename): |
| 107 | with open(filename, "r") as f: |
| 108 | lines = f.readlines(MAX_BYTES) |
| 109 | lines = lines[:min(len(lines), MAX_LINES)] |
| 110 | |
| 111 | ok = True |
| 112 | if os.path.basename(filename) not in COPYRIGHT_EXCEPTIONS: |
| 113 | c_ok, c_msg = valid_copyright(lines) |
| 114 | if not c_ok: |
| 115 | print(f"{filename}: {c_msg}") |
| 116 | ok = False |
| 117 | if os.path.basename(filename) not in LICENSE_EXCEPTIONS: |
| 118 | l_ok, l_msg = valid_license(lines) |
| 119 | if not l_ok: |
| 120 | print(f"{filename}: {l_msg}") |
| 121 | ok = False |
| 122 | return ok |
| 123 | |
| 124 | |
| 125 | def main(): |
| 126 | invalid_files = [] |
| 127 | for directory in DIRS: |
| 128 | for suffix in SUFFIX.values(): |
| 129 | files = set(glob.glob(f"{directory}/*{suffix}")) |
| 130 | files |= set(glob.glob(f"{directory}/**/*{suffix}")) |
| 131 | for filename in files: |
| 132 | if not valid_file(filename): |
| 133 | invalid_files.append(filename) |
| 134 | if len(invalid_files) > 0: |
| 135 | print(f"Invalid files: {invalid_files}") |
| 136 | else: |
| 137 | print("Pass!") |
| 138 | return len(invalid_files) |
| 139 | |
| 140 | if __name__ == "__main__": |
| 141 | sys.exit(main()) |