157 lines
4.3 KiB
Python
157 lines
4.3 KiB
Python
|
#!/usr/bin/env python3
|
||
|
|
||
|
# ################################################################
|
||
|
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||
|
# All rights reserved.
|
||
|
#
|
||
|
# This source code is licensed under both the BSD-style license (found in the
|
||
|
# LICENSE file in the root directory of this source tree) and the GPLv2 (found
|
||
|
# in the COPYING file in the root directory of this source tree).
|
||
|
# You may select, at your option, one of the above-listed licenses.
|
||
|
# ################################################################
|
||
|
|
||
|
import enum
|
||
|
import glob
|
||
|
import os
|
||
|
import re
|
||
|
import sys
|
||
|
|
||
|
ROOT = os.path.join(os.path.dirname(__file__), "..")
|
||
|
|
||
|
RELDIRS = [
|
||
|
"doc",
|
||
|
"examples",
|
||
|
"lib",
|
||
|
"programs",
|
||
|
"tests",
|
||
|
"contrib/linux-kernel",
|
||
|
]
|
||
|
|
||
|
REL_EXCLUDES = [
|
||
|
"contrib/linux-kernel/test/include",
|
||
|
]
|
||
|
|
||
|
def to_abs(d):
|
||
|
return os.path.normpath(os.path.join(ROOT, d)) + "/"
|
||
|
|
||
|
DIRS = [to_abs(d) for d in RELDIRS]
|
||
|
EXCLUDES = [to_abs(d) for d in REL_EXCLUDES]
|
||
|
|
||
|
SUFFIXES = [
|
||
|
".c",
|
||
|
".h",
|
||
|
"Makefile",
|
||
|
".mk",
|
||
|
".py",
|
||
|
".S",
|
||
|
]
|
||
|
|
||
|
# License should certainly be in the first 10 KB.
|
||
|
MAX_BYTES = 10000
|
||
|
MAX_LINES = 50
|
||
|
|
||
|
LICENSE_LINES = [
|
||
|
"This source code is licensed under both the BSD-style license (found in the",
|
||
|
"LICENSE file in the root directory of this source tree) and the GPLv2 (found",
|
||
|
"in the COPYING file in the root directory of this source tree).",
|
||
|
"You may select, at your option, one of the above-listed licenses.",
|
||
|
]
|
||
|
|
||
|
COPYRIGHT_EXCEPTIONS = {
|
||
|
# From zstdmt
|
||
|
"threading.c",
|
||
|
"threading.h",
|
||
|
# From divsufsort
|
||
|
"divsufsort.c",
|
||
|
"divsufsort.h",
|
||
|
}
|
||
|
|
||
|
LICENSE_EXCEPTIONS = {
|
||
|
# From divsufsort
|
||
|
"divsufsort.c",
|
||
|
"divsufsort.h",
|
||
|
# License is slightly different because it references GitHub
|
||
|
"linux_zstd.h",
|
||
|
}
|
||
|
|
||
|
|
||
|
def valid_copyright(lines):
|
||
|
YEAR_REGEX = re.compile("\d\d\d\d|present")
|
||
|
for line in lines:
|
||
|
line = line.strip()
|
||
|
if "Copyright" not in line:
|
||
|
continue
|
||
|
if "present" in line:
|
||
|
return (False, f"Copyright line '{line}' contains 'present'!")
|
||
|
if "Meta Platforms, Inc" not in line:
|
||
|
return (False, f"Copyright line '{line}' does not contain 'Meta Platforms, Inc'")
|
||
|
year = YEAR_REGEX.search(line)
|
||
|
if year is not None:
|
||
|
return (False, f"Copyright line '{line}' contains {year.group(0)}; it should be yearless")
|
||
|
if " (c) " not in line:
|
||
|
return (False, f"Copyright line '{line}' does not contain ' (c) '!")
|
||
|
return (True, "")
|
||
|
return (False, "Copyright not found!")
|
||
|
|
||
|
|
||
|
def valid_license(lines):
|
||
|
for b in range(len(lines)):
|
||
|
if LICENSE_LINES[0] not in lines[b]:
|
||
|
continue
|
||
|
for l in range(len(LICENSE_LINES)):
|
||
|
if LICENSE_LINES[l] not in lines[b + l]:
|
||
|
message = f"""Invalid license line found starting on line {b + l}!
|
||
|
Expected: '{LICENSE_LINES[l]}'
|
||
|
Actual: '{lines[b + l]}'"""
|
||
|
return (False, message)
|
||
|
return (True, "")
|
||
|
return (False, "License not found!")
|
||
|
|
||
|
|
||
|
def valid_file(filename):
|
||
|
with open(filename, "r") as f:
|
||
|
lines = f.readlines(MAX_BYTES)
|
||
|
lines = lines[:min(len(lines), MAX_LINES)]
|
||
|
|
||
|
ok = True
|
||
|
if os.path.basename(filename) not in COPYRIGHT_EXCEPTIONS:
|
||
|
c_ok, c_msg = valid_copyright(lines)
|
||
|
if not c_ok:
|
||
|
print(f"{filename}: {c_msg}", file=sys.stderr)
|
||
|
ok = False
|
||
|
if os.path.basename(filename) not in LICENSE_EXCEPTIONS:
|
||
|
l_ok, l_msg = valid_license(lines)
|
||
|
if not l_ok:
|
||
|
print(f"{filename}: {l_msg}", file=sys.stderr)
|
||
|
ok = False
|
||
|
return ok
|
||
|
|
||
|
|
||
|
def exclude(filename):
|
||
|
for x in EXCLUDES:
|
||
|
if filename.startswith(x):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def main():
|
||
|
invalid_files = []
|
||
|
for directory in DIRS:
|
||
|
for suffix in SUFFIXES:
|
||
|
files = set(glob.glob(f"{directory}/**/*{suffix}", recursive=True))
|
||
|
for filename in files:
|
||
|
if exclude(filename):
|
||
|
continue
|
||
|
if not valid_file(filename):
|
||
|
invalid_files.append(filename)
|
||
|
if len(invalid_files) > 0:
|
||
|
print("Fail!", file=sys.stderr)
|
||
|
for f in invalid_files:
|
||
|
print(f)
|
||
|
return 1
|
||
|
else:
|
||
|
print("Pass!", file=sys.stderr)
|
||
|
return 0
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
sys.exit(main())
|