xref: /aosp_15_r20/external/fonttools/Snippets/checksum.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes#!/usr/bin/env python3
2*e1fe3e4aSElliott Hughes# -*- coding: utf-8 -*-
3*e1fe3e4aSElliott Hughes
4*e1fe3e4aSElliott Hughesimport argparse
5*e1fe3e4aSElliott Hughesimport hashlib
6*e1fe3e4aSElliott Hughesimport os
7*e1fe3e4aSElliott Hughesimport sys
8*e1fe3e4aSElliott Hughes
9*e1fe3e4aSElliott Hughesfrom os.path import basename
10*e1fe3e4aSElliott Hughes
11*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont
12*e1fe3e4aSElliott Hughes
13*e1fe3e4aSElliott Hughes
14*e1fe3e4aSElliott Hughesdef write_checksum(
15*e1fe3e4aSElliott Hughes    filepaths,
16*e1fe3e4aSElliott Hughes    stdout_write=False,
17*e1fe3e4aSElliott Hughes    use_ttx=False,
18*e1fe3e4aSElliott Hughes    include_tables=None,
19*e1fe3e4aSElliott Hughes    exclude_tables=None,
20*e1fe3e4aSElliott Hughes    do_not_cleanup=False,
21*e1fe3e4aSElliott Hughes):
22*e1fe3e4aSElliott Hughes    checksum_dict = {}
23*e1fe3e4aSElliott Hughes    for path in filepaths:
24*e1fe3e4aSElliott Hughes        if not os.path.exists(path):
25*e1fe3e4aSElliott Hughes            sys.stderr.write(
26*e1fe3e4aSElliott Hughes                "[checksum.py] ERROR: "
27*e1fe3e4aSElliott Hughes                + path
28*e1fe3e4aSElliott Hughes                + " is not a valid file path"
29*e1fe3e4aSElliott Hughes                + os.linesep
30*e1fe3e4aSElliott Hughes            )
31*e1fe3e4aSElliott Hughes            sys.exit(1)
32*e1fe3e4aSElliott Hughes
33*e1fe3e4aSElliott Hughes        if use_ttx:
34*e1fe3e4aSElliott Hughes            # append a .ttx extension to existing extension to maintain data about the binary that
35*e1fe3e4aSElliott Hughes            # was used to generate the .ttx XML dump.  This creates unique checksum path values for
36*e1fe3e4aSElliott Hughes            # paths that would otherwise not be unique with a file extension replacement with .ttx
37*e1fe3e4aSElliott Hughes            # An example is woff and woff2 web font files that share the same base file name:
38*e1fe3e4aSElliott Hughes            #
39*e1fe3e4aSElliott Hughes            #  coolfont-regular.woff  ==> coolfont-regular.ttx
40*e1fe3e4aSElliott Hughes            #  coolfont-regular.woff2 ==> coolfont-regular.ttx  (KAPOW! checksum data lost as this would overwrite dict value)
41*e1fe3e4aSElliott Hughes            temp_ttx_path = path + ".ttx"
42*e1fe3e4aSElliott Hughes
43*e1fe3e4aSElliott Hughes            tt = TTFont(path)
44*e1fe3e4aSElliott Hughes            tt.saveXML(temp_ttx_path, skipTables=exclude_tables, tables=include_tables)
45*e1fe3e4aSElliott Hughes            checksum_path = temp_ttx_path
46*e1fe3e4aSElliott Hughes        else:
47*e1fe3e4aSElliott Hughes            if include_tables is not None:
48*e1fe3e4aSElliott Hughes                sys.stderr.write(
49*e1fe3e4aSElliott Hughes                    "[checksum.py] -i and --include are not supported for font binary filepaths. \
50*e1fe3e4aSElliott Hughes                    Use these flags for checksums with the --ttx flag."
51*e1fe3e4aSElliott Hughes                )
52*e1fe3e4aSElliott Hughes                sys.exit(1)
53*e1fe3e4aSElliott Hughes            if exclude_tables is not None:
54*e1fe3e4aSElliott Hughes                sys.stderr.write(
55*e1fe3e4aSElliott Hughes                    "[checksum.py] -e and --exclude are not supported for font binary filepaths. \
56*e1fe3e4aSElliott Hughes                    Use these flags for checksums with the --ttx flag."
57*e1fe3e4aSElliott Hughes                )
58*e1fe3e4aSElliott Hughes                sys.exit(1)
59*e1fe3e4aSElliott Hughes            checksum_path = path
60*e1fe3e4aSElliott Hughes
61*e1fe3e4aSElliott Hughes        file_contents = _read_binary(checksum_path)
62*e1fe3e4aSElliott Hughes
63*e1fe3e4aSElliott Hughes        # store SHA1 hash data and associated file path basename in the checksum_dict dictionary
64*e1fe3e4aSElliott Hughes        checksum_dict[basename(checksum_path)] = hashlib.sha1(file_contents).hexdigest()
65*e1fe3e4aSElliott Hughes
66*e1fe3e4aSElliott Hughes        # remove temp ttx files when present
67*e1fe3e4aSElliott Hughes        if use_ttx and do_not_cleanup is False:
68*e1fe3e4aSElliott Hughes            os.remove(temp_ttx_path)
69*e1fe3e4aSElliott Hughes
70*e1fe3e4aSElliott Hughes    # generate the checksum list string for writes
71*e1fe3e4aSElliott Hughes    checksum_out_data = ""
72*e1fe3e4aSElliott Hughes    for key in checksum_dict.keys():
73*e1fe3e4aSElliott Hughes        checksum_out_data += checksum_dict[key] + "  " + key + "\n"
74*e1fe3e4aSElliott Hughes
75*e1fe3e4aSElliott Hughes    # write to stdout stream or file based upon user request (default = file write)
76*e1fe3e4aSElliott Hughes    if stdout_write:
77*e1fe3e4aSElliott Hughes        sys.stdout.write(checksum_out_data)
78*e1fe3e4aSElliott Hughes    else:
79*e1fe3e4aSElliott Hughes        checksum_report_filepath = "checksum.txt"
80*e1fe3e4aSElliott Hughes        with open(checksum_report_filepath, "w") as file:
81*e1fe3e4aSElliott Hughes            file.write(checksum_out_data)
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott Hughes
84*e1fe3e4aSElliott Hughesdef check_checksum(filepaths):
85*e1fe3e4aSElliott Hughes    check_failed = False
86*e1fe3e4aSElliott Hughes    for path in filepaths:
87*e1fe3e4aSElliott Hughes        if not os.path.exists(path):
88*e1fe3e4aSElliott Hughes            sys.stderr.write(
89*e1fe3e4aSElliott Hughes                "[checksum.py] ERROR: " + path + " is not a valid filepath" + os.linesep
90*e1fe3e4aSElliott Hughes            )
91*e1fe3e4aSElliott Hughes            sys.exit(1)
92*e1fe3e4aSElliott Hughes
93*e1fe3e4aSElliott Hughes        with open(path, mode="r") as file:
94*e1fe3e4aSElliott Hughes            for line in file.readlines():
95*e1fe3e4aSElliott Hughes                cleaned_line = line.rstrip()
96*e1fe3e4aSElliott Hughes                line_list = cleaned_line.split(" ")
97*e1fe3e4aSElliott Hughes                # eliminate empty strings parsed from > 1 space characters
98*e1fe3e4aSElliott Hughes                line_list = list(filter(None, line_list))
99*e1fe3e4aSElliott Hughes                if len(line_list) == 2:
100*e1fe3e4aSElliott Hughes                    expected_sha1 = line_list[0]
101*e1fe3e4aSElliott Hughes                    test_path = line_list[1]
102*e1fe3e4aSElliott Hughes                else:
103*e1fe3e4aSElliott Hughes                    sys.stderr.write(
104*e1fe3e4aSElliott Hughes                        "[checksum.py] ERROR: failed to parse checksum file values"
105*e1fe3e4aSElliott Hughes                        + os.linesep
106*e1fe3e4aSElliott Hughes                    )
107*e1fe3e4aSElliott Hughes                    sys.exit(1)
108*e1fe3e4aSElliott Hughes
109*e1fe3e4aSElliott Hughes                if not os.path.exists(test_path):
110*e1fe3e4aSElliott Hughes                    print(test_path + ": Filepath is not valid, ignored")
111*e1fe3e4aSElliott Hughes                else:
112*e1fe3e4aSElliott Hughes                    file_contents = _read_binary(test_path)
113*e1fe3e4aSElliott Hughes                    observed_sha1 = hashlib.sha1(file_contents).hexdigest()
114*e1fe3e4aSElliott Hughes                    if observed_sha1 == expected_sha1:
115*e1fe3e4aSElliott Hughes                        print(test_path + ": OK")
116*e1fe3e4aSElliott Hughes                    else:
117*e1fe3e4aSElliott Hughes                        print("-" * 80)
118*e1fe3e4aSElliott Hughes                        print(test_path + ": === FAIL ===")
119*e1fe3e4aSElliott Hughes                        print("Expected vs. Observed:")
120*e1fe3e4aSElliott Hughes                        print(expected_sha1)
121*e1fe3e4aSElliott Hughes                        print(observed_sha1)
122*e1fe3e4aSElliott Hughes                        print("-" * 80)
123*e1fe3e4aSElliott Hughes                        check_failed = True
124*e1fe3e4aSElliott Hughes
125*e1fe3e4aSElliott Hughes    # exit with status code 1 if any fails detected across all tests in the check
126*e1fe3e4aSElliott Hughes    if check_failed is True:
127*e1fe3e4aSElliott Hughes        sys.exit(1)
128*e1fe3e4aSElliott Hughes
129*e1fe3e4aSElliott Hughes
130*e1fe3e4aSElliott Hughesdef _read_binary(filepath):
131*e1fe3e4aSElliott Hughes    with open(filepath, mode="rb") as file:
132*e1fe3e4aSElliott Hughes        return file.read()
133*e1fe3e4aSElliott Hughes
134*e1fe3e4aSElliott Hughes
135*e1fe3e4aSElliott Hughesif __name__ == "__main__":
136*e1fe3e4aSElliott Hughes    parser = argparse.ArgumentParser(
137*e1fe3e4aSElliott Hughes        prog="checksum.py",
138*e1fe3e4aSElliott Hughes        description="A SHA1 hash checksum list generator and checksum testing script",
139*e1fe3e4aSElliott Hughes    )
140*e1fe3e4aSElliott Hughes    parser.add_argument(
141*e1fe3e4aSElliott Hughes        "-t", "--ttx", help="Calculate from ttx file", action="store_true"
142*e1fe3e4aSElliott Hughes    )
143*e1fe3e4aSElliott Hughes    parser.add_argument(
144*e1fe3e4aSElliott Hughes        "-s", "--stdout", help="Write output to stdout stream", action="store_true"
145*e1fe3e4aSElliott Hughes    )
146*e1fe3e4aSElliott Hughes    parser.add_argument(
147*e1fe3e4aSElliott Hughes        "-n",
148*e1fe3e4aSElliott Hughes        "--noclean",
149*e1fe3e4aSElliott Hughes        help="Do not discard *.ttx files used to calculate SHA1 hashes",
150*e1fe3e4aSElliott Hughes        action="store_true",
151*e1fe3e4aSElliott Hughes    )
152*e1fe3e4aSElliott Hughes    parser.add_argument(
153*e1fe3e4aSElliott Hughes        "-c", "--check", help="Verify checksum values vs. files", action="store_true"
154*e1fe3e4aSElliott Hughes    )
155*e1fe3e4aSElliott Hughes    parser.add_argument(
156*e1fe3e4aSElliott Hughes        "filepaths",
157*e1fe3e4aSElliott Hughes        nargs="+",
158*e1fe3e4aSElliott Hughes        help="One or more file paths.  Use checksum file path for -c/--check.  Use paths\
159*e1fe3e4aSElliott Hughes        to font files for all other commands.",
160*e1fe3e4aSElliott Hughes    )
161*e1fe3e4aSElliott Hughes
162*e1fe3e4aSElliott Hughes    parser.add_argument(
163*e1fe3e4aSElliott Hughes        "-i",
164*e1fe3e4aSElliott Hughes        "--include",
165*e1fe3e4aSElliott Hughes        action="append",
166*e1fe3e4aSElliott Hughes        help="Included OpenType tables for ttx data dump",
167*e1fe3e4aSElliott Hughes    )
168*e1fe3e4aSElliott Hughes    parser.add_argument(
169*e1fe3e4aSElliott Hughes        "-e",
170*e1fe3e4aSElliott Hughes        "--exclude",
171*e1fe3e4aSElliott Hughes        action="append",
172*e1fe3e4aSElliott Hughes        help="Excluded OpenType tables for ttx data dump",
173*e1fe3e4aSElliott Hughes    )
174*e1fe3e4aSElliott Hughes
175*e1fe3e4aSElliott Hughes    args = parser.parse_args(sys.argv[1:])
176*e1fe3e4aSElliott Hughes
177*e1fe3e4aSElliott Hughes    if args.check is True:
178*e1fe3e4aSElliott Hughes        check_checksum(args.filepaths)
179*e1fe3e4aSElliott Hughes    else:
180*e1fe3e4aSElliott Hughes        write_checksum(
181*e1fe3e4aSElliott Hughes            args.filepaths,
182*e1fe3e4aSElliott Hughes            stdout_write=args.stdout,
183*e1fe3e4aSElliott Hughes            use_ttx=args.ttx,
184*e1fe3e4aSElliott Hughes            do_not_cleanup=args.noclean,
185*e1fe3e4aSElliott Hughes            include_tables=args.include,
186*e1fe3e4aSElliott Hughes            exclude_tables=args.exclude,
187*e1fe3e4aSElliott Hughes        )
188