xref: /aosp_15_r20/external/zstd/tests/test-license.py (revision 01826a4963a0d8a59bc3812d29bdf0fb76416722)
1#!/usr/bin/env python3
2
3# ################################################################
4# Copyright (c) Meta Platforms, Inc. and affiliates.
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    ".S",
47]
48
49# License should certainly be in the first 10 KB.
50MAX_BYTES = 10000
51MAX_LINES = 50
52
53LICENSE_LINES = [
54    "This source code is licensed under both the BSD-style license (found in the",
55    "LICENSE file in the root directory of this source tree) and the GPLv2 (found",
56    "in the COPYING file in the root directory of this source tree).",
57    "You may select, at your option, one of the above-listed licenses.",
58]
59
60COPYRIGHT_EXCEPTIONS = {
61    # From zstdmt
62    "threading.c",
63    "threading.h",
64    # From divsufsort
65    "divsufsort.c",
66    "divsufsort.h",
67}
68
69LICENSE_EXCEPTIONS = {
70    # From divsufsort
71    "divsufsort.c",
72    "divsufsort.h",
73    # License is slightly different because it references GitHub
74    "linux_zstd.h",
75}
76
77
78def valid_copyright(lines):
79    YEAR_REGEX = re.compile("\d\d\d\d|present")
80    for line in lines:
81        line = line.strip()
82        if "Copyright" not in line:
83            continue
84        if "present" in line:
85            return (False, f"Copyright line '{line}' contains 'present'!")
86        if "Meta Platforms, Inc" not in line:
87            return (False, f"Copyright line '{line}' does not contain 'Meta Platforms, Inc'")
88        year = YEAR_REGEX.search(line)
89        if year is not None:
90            return (False, f"Copyright line '{line}' contains {year.group(0)}; it should be yearless")
91        if " (c) " not in line:
92            return (False, f"Copyright line '{line}' does not contain ' (c) '!")
93        return (True, "")
94    return (False, "Copyright not found!")
95
96
97def valid_license(lines):
98    for b in range(len(lines)):
99        if LICENSE_LINES[0] not in lines[b]:
100            continue
101        for l in range(len(LICENSE_LINES)):
102            if LICENSE_LINES[l] not in lines[b + l]:
103                message = f"""Invalid license line found starting on line {b + l}!
104Expected: '{LICENSE_LINES[l]}'
105Actual: '{lines[b + l]}'"""
106                return (False, message)
107        return (True, "")
108    return (False, "License not found!")
109
110
111def valid_file(filename):
112    with open(filename, "r") as f:
113        lines = f.readlines(MAX_BYTES)
114    lines = lines[:min(len(lines), MAX_LINES)]
115
116    ok = True
117    if os.path.basename(filename) not in COPYRIGHT_EXCEPTIONS:
118        c_ok, c_msg = valid_copyright(lines)
119        if not c_ok:
120            print(f"{filename}: {c_msg}", file=sys.stderr)
121            ok = False
122    if os.path.basename(filename) not in LICENSE_EXCEPTIONS:
123        l_ok, l_msg = valid_license(lines)
124        if not l_ok:
125            print(f"{filename}: {l_msg}", file=sys.stderr)
126            ok = False
127    return ok
128
129
130def exclude(filename):
131    for x in EXCLUDES:
132        if filename.startswith(x):
133            return True
134    return False
135
136def main():
137    invalid_files = []
138    for directory in DIRS:
139        for suffix in SUFFIXES:
140            files = set(glob.glob(f"{directory}/**/*{suffix}", recursive=True))
141            for filename in files:
142                if exclude(filename):
143                    continue
144                if not valid_file(filename):
145                    invalid_files.append(filename)
146    if len(invalid_files) > 0:
147        print("Fail!", file=sys.stderr)
148        for f in invalid_files:
149            print(f)
150        return 1
151    else:
152        print("Pass!", file=sys.stderr)
153        return 0
154
155if __name__ == "__main__":
156    sys.exit(main())
157