xref: /aosp_15_r20/bionic/libc/tools/generate_notice.py (revision 8d67ca893c1523eb926b9080dbe4e2ffd2a27ba1)
1*8d67ca89SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*8d67ca89SAndroid Build Coastguard Worker# Run with directory arguments from any directory, with no special setup
3*8d67ca89SAndroid Build Coastguard Worker# required.
4*8d67ca89SAndroid Build Coastguard Worker
5*8d67ca89SAndroid Build Coastguard Workerimport os
6*8d67ca89SAndroid Build Coastguard Workerfrom pathlib import Path
7*8d67ca89SAndroid Build Coastguard Workerimport re
8*8d67ca89SAndroid Build Coastguard Workerimport sys
9*8d67ca89SAndroid Build Coastguard Workerfrom typing import Sequence
10*8d67ca89SAndroid Build Coastguard Worker
11*8d67ca89SAndroid Build Coastguard WorkerVERBOSE = False
12*8d67ca89SAndroid Build Coastguard Worker
13*8d67ca89SAndroid Build Coastguard Workercopyrights = set()
14*8d67ca89SAndroid Build Coastguard Worker
15*8d67ca89SAndroid Build Coastguard Worker
16*8d67ca89SAndroid Build Coastguard Workerdef warn(s):
17*8d67ca89SAndroid Build Coastguard Worker    sys.stderr.write("warning: %s\n" % s)
18*8d67ca89SAndroid Build Coastguard Worker
19*8d67ca89SAndroid Build Coastguard Worker
20*8d67ca89SAndroid Build Coastguard Workerdef warn_verbose(s):
21*8d67ca89SAndroid Build Coastguard Worker    if VERBOSE:
22*8d67ca89SAndroid Build Coastguard Worker        warn(s)
23*8d67ca89SAndroid Build Coastguard Worker
24*8d67ca89SAndroid Build Coastguard Worker
25*8d67ca89SAndroid Build Coastguard Workerdef is_interesting(path_str: str) -> bool:
26*8d67ca89SAndroid Build Coastguard Worker    path = Path(path_str.lower())
27*8d67ca89SAndroid Build Coastguard Worker    uninteresting_extensions = [
28*8d67ca89SAndroid Build Coastguard Worker        ".bp",
29*8d67ca89SAndroid Build Coastguard Worker        ".map",
30*8d67ca89SAndroid Build Coastguard Worker        ".md",
31*8d67ca89SAndroid Build Coastguard Worker        ".mk",
32*8d67ca89SAndroid Build Coastguard Worker        ".py",
33*8d67ca89SAndroid Build Coastguard Worker        ".pyc",
34*8d67ca89SAndroid Build Coastguard Worker        ".swp",
35*8d67ca89SAndroid Build Coastguard Worker        ".txt",
36*8d67ca89SAndroid Build Coastguard Worker        ".xml",
37*8d67ca89SAndroid Build Coastguard Worker    ]
38*8d67ca89SAndroid Build Coastguard Worker    if path.suffix in uninteresting_extensions:
39*8d67ca89SAndroid Build Coastguard Worker        return False
40*8d67ca89SAndroid Build Coastguard Worker    if path.name in {"notice", "readme", "pylintrc"}:
41*8d67ca89SAndroid Build Coastguard Worker        return False
42*8d67ca89SAndroid Build Coastguard Worker    # Backup files for some editors.
43*8d67ca89SAndroid Build Coastguard Worker    if path.match("*~"):
44*8d67ca89SAndroid Build Coastguard Worker        return False
45*8d67ca89SAndroid Build Coastguard Worker    return True
46*8d67ca89SAndroid Build Coastguard Worker
47*8d67ca89SAndroid Build Coastguard Worker
48*8d67ca89SAndroid Build Coastguard Workerdef is_copyright_end(line: str, first_line_was_hash: bool) -> bool:
49*8d67ca89SAndroid Build Coastguard Worker    endings = [
50*8d67ca89SAndroid Build Coastguard Worker        " $FreeBSD: ",
51*8d67ca89SAndroid Build Coastguard Worker        "$Citrus$",
52*8d67ca89SAndroid Build Coastguard Worker        "$FreeBSD$",
53*8d67ca89SAndroid Build Coastguard Worker        "*/",
54*8d67ca89SAndroid Build Coastguard Worker        "From: @(#)",
55*8d67ca89SAndroid Build Coastguard Worker        # OpenBSD likes to say where stuff originally came from:
56*8d67ca89SAndroid Build Coastguard Worker        "Original version ID:",
57*8d67ca89SAndroid Build Coastguard Worker        "\t$Citrus: ",
58*8d67ca89SAndroid Build Coastguard Worker        "\t$NetBSD: ",
59*8d67ca89SAndroid Build Coastguard Worker        "\t$OpenBSD: ",
60*8d67ca89SAndroid Build Coastguard Worker        "\t@(#)",
61*8d67ca89SAndroid Build Coastguard Worker        "\tcitrus Id: ",
62*8d67ca89SAndroid Build Coastguard Worker        "\tfrom: @(#)",
63*8d67ca89SAndroid Build Coastguard Worker        "from OpenBSD:",
64*8d67ca89SAndroid Build Coastguard Worker    ]
65*8d67ca89SAndroid Build Coastguard Worker    if first_line_was_hash and not line:
66*8d67ca89SAndroid Build Coastguard Worker        return True
67*8d67ca89SAndroid Build Coastguard Worker
68*8d67ca89SAndroid Build Coastguard Worker    for ending in endings:
69*8d67ca89SAndroid Build Coastguard Worker        if ending in line:
70*8d67ca89SAndroid Build Coastguard Worker            return True
71*8d67ca89SAndroid Build Coastguard Worker
72*8d67ca89SAndroid Build Coastguard Worker    return False
73*8d67ca89SAndroid Build Coastguard Worker
74*8d67ca89SAndroid Build Coastguard Worker
75*8d67ca89SAndroid Build Coastguard Workerdef extract_copyright_at(lines: Sequence[str], i: int) -> int:
76*8d67ca89SAndroid Build Coastguard Worker    first_line_was_hash = lines[i].startswith("#")
77*8d67ca89SAndroid Build Coastguard Worker
78*8d67ca89SAndroid Build Coastguard Worker    # Do we need to back up to find the start of the copyright header?
79*8d67ca89SAndroid Build Coastguard Worker    start = i
80*8d67ca89SAndroid Build Coastguard Worker    if not first_line_was_hash:
81*8d67ca89SAndroid Build Coastguard Worker        while start > 0:
82*8d67ca89SAndroid Build Coastguard Worker            if "/*" in lines[start - 1]:
83*8d67ca89SAndroid Build Coastguard Worker                break
84*8d67ca89SAndroid Build Coastguard Worker            start -= 1
85*8d67ca89SAndroid Build Coastguard Worker
86*8d67ca89SAndroid Build Coastguard Worker    # Read comment lines until we hit something that terminates a
87*8d67ca89SAndroid Build Coastguard Worker    # copyright header.
88*8d67ca89SAndroid Build Coastguard Worker    while i < len(lines):
89*8d67ca89SAndroid Build Coastguard Worker        if is_copyright_end(lines[i], first_line_was_hash):
90*8d67ca89SAndroid Build Coastguard Worker            break
91*8d67ca89SAndroid Build Coastguard Worker        i += 1
92*8d67ca89SAndroid Build Coastguard Worker
93*8d67ca89SAndroid Build Coastguard Worker    end = i
94*8d67ca89SAndroid Build Coastguard Worker
95*8d67ca89SAndroid Build Coastguard Worker    # Trim trailing cruft.
96*8d67ca89SAndroid Build Coastguard Worker    while end > 0:
97*8d67ca89SAndroid Build Coastguard Worker        line = lines[end - 1]
98*8d67ca89SAndroid Build Coastguard Worker        if line not in {
99*8d67ca89SAndroid Build Coastguard Worker                " *", " * ===================================================="
100*8d67ca89SAndroid Build Coastguard Worker        }:
101*8d67ca89SAndroid Build Coastguard Worker            break
102*8d67ca89SAndroid Build Coastguard Worker        end -= 1
103*8d67ca89SAndroid Build Coastguard Worker
104*8d67ca89SAndroid Build Coastguard Worker    # Remove C/assembler comment formatting, pulling out just the text.
105*8d67ca89SAndroid Build Coastguard Worker    clean_lines = []
106*8d67ca89SAndroid Build Coastguard Worker    for line in lines[start:end]:
107*8d67ca89SAndroid Build Coastguard Worker        line = line.replace("\t", "    ")
108*8d67ca89SAndroid Build Coastguard Worker        line = line.replace("/* ", "")
109*8d67ca89SAndroid Build Coastguard Worker        line = re.sub(r"^ \* ", "", line)
110*8d67ca89SAndroid Build Coastguard Worker        line = line.replace("** ", "")
111*8d67ca89SAndroid Build Coastguard Worker        line = line.replace("# ", "")
112*8d67ca89SAndroid Build Coastguard Worker        if line.startswith("++Copyright++"):
113*8d67ca89SAndroid Build Coastguard Worker            continue
114*8d67ca89SAndroid Build Coastguard Worker        line = line.replace("--Copyright--", "")
115*8d67ca89SAndroid Build Coastguard Worker        line = line.rstrip()
116*8d67ca89SAndroid Build Coastguard Worker        # These come last and take care of "blank" comment lines.
117*8d67ca89SAndroid Build Coastguard Worker        if line in {"#", " *", "**", "-"}:
118*8d67ca89SAndroid Build Coastguard Worker            line = ""
119*8d67ca89SAndroid Build Coastguard Worker        clean_lines.append(line)
120*8d67ca89SAndroid Build Coastguard Worker
121*8d67ca89SAndroid Build Coastguard Worker    # Trim blank lines from head and tail.
122*8d67ca89SAndroid Build Coastguard Worker    while clean_lines[0] == "":
123*8d67ca89SAndroid Build Coastguard Worker        clean_lines = clean_lines[1:]
124*8d67ca89SAndroid Build Coastguard Worker    while clean_lines[len(clean_lines) - 1] == "":
125*8d67ca89SAndroid Build Coastguard Worker        clean_lines = clean_lines[0:(len(clean_lines) - 1)]
126*8d67ca89SAndroid Build Coastguard Worker
127*8d67ca89SAndroid Build Coastguard Worker    copyrights.add("\n".join(clean_lines))
128*8d67ca89SAndroid Build Coastguard Worker
129*8d67ca89SAndroid Build Coastguard Worker    return i
130*8d67ca89SAndroid Build Coastguard Worker
131*8d67ca89SAndroid Build Coastguard Worker
132*8d67ca89SAndroid Build Coastguard Workerdef do_file(path: str) -> None:
133*8d67ca89SAndroid Build Coastguard Worker    raw = Path(path).read_bytes()
134*8d67ca89SAndroid Build Coastguard Worker    try:
135*8d67ca89SAndroid Build Coastguard Worker        content = raw.decode("utf-8")
136*8d67ca89SAndroid Build Coastguard Worker    except UnicodeDecodeError:
137*8d67ca89SAndroid Build Coastguard Worker        warn("bad UTF-8 in %s" % path)
138*8d67ca89SAndroid Build Coastguard Worker        content = raw.decode("iso-8859-1")
139*8d67ca89SAndroid Build Coastguard Worker
140*8d67ca89SAndroid Build Coastguard Worker    lines = content.split("\n")
141*8d67ca89SAndroid Build Coastguard Worker
142*8d67ca89SAndroid Build Coastguard Worker    if len(lines) <= 4:
143*8d67ca89SAndroid Build Coastguard Worker        warn_verbose("ignoring short file %s" % path)
144*8d67ca89SAndroid Build Coastguard Worker        return
145*8d67ca89SAndroid Build Coastguard Worker
146*8d67ca89SAndroid Build Coastguard Worker    if not "Copyright" in content:
147*8d67ca89SAndroid Build Coastguard Worker        if "public domain" in content.lower():
148*8d67ca89SAndroid Build Coastguard Worker            warn_verbose("ignoring public domain file %s" % path)
149*8d67ca89SAndroid Build Coastguard Worker            return
150*8d67ca89SAndroid Build Coastguard Worker        warn('no copyright notice found in "%s" (%d lines)' %
151*8d67ca89SAndroid Build Coastguard Worker             (path, len(lines)))
152*8d67ca89SAndroid Build Coastguard Worker        return
153*8d67ca89SAndroid Build Coastguard Worker
154*8d67ca89SAndroid Build Coastguard Worker    # Manually iterate because extract_copyright_at tells us how many lines to
155*8d67ca89SAndroid Build Coastguard Worker    # skip.
156*8d67ca89SAndroid Build Coastguard Worker    i = 0
157*8d67ca89SAndroid Build Coastguard Worker    while i < len(lines):
158*8d67ca89SAndroid Build Coastguard Worker        if "Copyright" in lines[i] and not "@(#) Copyright" in lines[i]:
159*8d67ca89SAndroid Build Coastguard Worker            i = extract_copyright_at(lines, i)
160*8d67ca89SAndroid Build Coastguard Worker        else:
161*8d67ca89SAndroid Build Coastguard Worker            i += 1
162*8d67ca89SAndroid Build Coastguard Worker
163*8d67ca89SAndroid Build Coastguard Worker
164*8d67ca89SAndroid Build Coastguard Workerdef do_dir(arg):
165*8d67ca89SAndroid Build Coastguard Worker    for directory, sub_directories, filenames in os.walk(arg):
166*8d67ca89SAndroid Build Coastguard Worker        if ".git" in sub_directories:
167*8d67ca89SAndroid Build Coastguard Worker            sub_directories.remove(".git")
168*8d67ca89SAndroid Build Coastguard Worker        sub_directories = sorted(sub_directories)
169*8d67ca89SAndroid Build Coastguard Worker
170*8d67ca89SAndroid Build Coastguard Worker        for filename in sorted(filenames):
171*8d67ca89SAndroid Build Coastguard Worker            path = os.path.join(directory, filename)
172*8d67ca89SAndroid Build Coastguard Worker            if is_interesting(path):
173*8d67ca89SAndroid Build Coastguard Worker                do_file(path)
174*8d67ca89SAndroid Build Coastguard Worker
175*8d67ca89SAndroid Build Coastguard Worker
176*8d67ca89SAndroid Build Coastguard Workerdef main() -> None:
177*8d67ca89SAndroid Build Coastguard Worker    args = sys.argv[1:]
178*8d67ca89SAndroid Build Coastguard Worker    if len(args) == 0:
179*8d67ca89SAndroid Build Coastguard Worker        args = ["."]
180*8d67ca89SAndroid Build Coastguard Worker
181*8d67ca89SAndroid Build Coastguard Worker    for arg in args:
182*8d67ca89SAndroid Build Coastguard Worker        if os.path.isdir(arg):
183*8d67ca89SAndroid Build Coastguard Worker            do_dir(arg)
184*8d67ca89SAndroid Build Coastguard Worker        else:
185*8d67ca89SAndroid Build Coastguard Worker            do_file(arg)
186*8d67ca89SAndroid Build Coastguard Worker
187*8d67ca89SAndroid Build Coastguard Worker    for notice in sorted(copyrights):
188*8d67ca89SAndroid Build Coastguard Worker        print(notice)
189*8d67ca89SAndroid Build Coastguard Worker        print()
190*8d67ca89SAndroid Build Coastguard Worker        print("-" * 67)
191*8d67ca89SAndroid Build Coastguard Worker        print()
192*8d67ca89SAndroid Build Coastguard Worker
193*8d67ca89SAndroid Build Coastguard Worker
194*8d67ca89SAndroid Build Coastguard Workerif __name__ == "__main__":
195*8d67ca89SAndroid Build Coastguard Worker    main()
196