summaryrefslogtreecommitdiffstats
path: root/comm/python/thirdroc/thirdroc/rnp_symbols.py
blob: 95f430f59c380ee2486ee6de8d2a7251faa76902 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/usr/bin/python3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Parse rnp/rnp.h header file and build a symbols file suitable
for use with mozbuild.

This script is meant to be run when the public C API of librnp adds or removes functions so that
they can be exported by the shared library.

Limitations: The regex that captures the function name is very basic and may need adjusting if
the third_party/rnp/include/rnp/rnp.h format changes too much.
Also note that APIs that are marked deprecated are not checked for.

Dependencies: Only Python 3

Running:
  python3 rnp_symbols.py [-h] [rnp.h path] [rnp.symbols path]

Both file path arguments are optional. By default, the header file will be
read from "comm/third_party/rnp/include/rnp/rnp.h" and the symbols file will
be written to "comm/third_party/rnp/rnp.symbols".

Path arguments are relative to the current working directory, the defaults
will be determined based on the location of this script.

Either path argument can be '-' to use stdin or stdout respectively.
"""

import argparse
import os
import re
import sys

HERE = os.path.dirname(__file__)
TOPSRCDIR = os.path.abspath(os.path.join(HERE, "../../../../"))
THIRD_SRCDIR = os.path.join(TOPSRCDIR, "comm/third_party")
HEADER_FILE_REL = "rnp/include/rnp/rnp.h"
HEADER_FILE = os.path.join(THIRD_SRCDIR, HEADER_FILE_REL)
SYMBOLS_FILE_REL = "rnp/rnp.symbols"
SYMBOLS_FILE = os.path.join(THIRD_SRCDIR, SYMBOLS_FILE_REL)


FUNC_DECL_RE = re.compile(r"^RNP_API\s+.*?([a-zA-Z0-9_]+)\(.*$")


class FileArg:
    """Based on argparse.FileType from the Python standard library.
    Modified to not open the filehandles until the open() method is
    called.
    """

    def __init__(self, mode="r"):
        self._mode = mode
        self._fp = None
        self._file = None

    def __call__(self, string):
        # the special argument "-" means sys.std{in,out}
        if string == "-":
            if "r" in self._mode:
                self._fp = sys.stdin.buffer if "b" in self._mode else sys.stdin
            elif "w" in self._mode:
                self._fp = sys.stdout.buffer if "b" in self._mode else sys.stdout
            else:
                raise ValueError(f"Invalid mode {self._mode} for stdin/stdout")
        else:
            if "r" in self._mode:
                if not os.path.isfile(string):
                    raise ValueError(f"Cannot read file {string}, does not exist.")
            elif "w" in self._mode:
                if not os.access(string, os.W_OK):
                    raise ValueError(f"Cannot write file {string}, permission denied.")
            self._file = string
        return self

    def open(self):
        if self._fp:
            return self._fp
        return open(self._file, self._mode)


def get_func_name(line):
    """
    Extract the function name from a RNP_API function declaration.
    Examples:
    RNP_API rnp_result_t rnp_enable_debug(const char *file);

    RNP_API rnp_result_t rnp_ffi_create(rnp_ffi_t * ffi,
    """
    m = FUNC_DECL_RE.match(line)
    return m.group(1)


def extract_func_defs(filearg):
    """
    Look for RNP_API in the header file to find the names of the symbols that should be exported
    """
    with filearg.open() as fp:
        for line in fp:
            if line.startswith("RNP_API") and "RNP_DEPRECATED" not in line:
                func_name = get_func_name(line)
                yield func_name


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Update rnp.symbols file from rnp.h",
        epilog="To use stdin or stdout pass '-' for the argument.",
    )
    parser.add_argument(
        "header_file",
        default=HEADER_FILE,
        type=FileArg("r"),
        nargs="?",
        help=f"input path to rnp.h header file (default: {HEADER_FILE_REL})",
    )
    parser.add_argument(
        "symbols_file",
        default=SYMBOLS_FILE,
        type=FileArg("w"),
        nargs="?",
        help=f"output path to symbols file (default: {SYMBOLS_FILE_REL})",
    )

    args = parser.parse_args()

    with args.symbols_file.open() as out_fp:
        for symbol in sorted(list(extract_func_defs(args.header_file))):
            out_fp.write(f"{symbol}\n")