• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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