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