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