1#!/usr/bin/env python
2
3# Copyright 2015 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17# Utility functions used to parse a list of DLL entry points.
18# Expected format:
19#
20#   <empty-line>   -> ignored
21#   #<comment>     -> ignored
22#   %<verbatim>    -> verbatim output for header files.
23#   !<prefix>      -> prefix name for header files.
24#   <return-type> <function-name> <signature> ; -> entry point declaration.
25#
26# Anything else is an error.
27
28import re
29import sys
30import argparse
31
32re_func = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_]*)\((.*)\);$""")
33re_param = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_]*)$""")
34
35class Entry:
36    """Small class used to model a single DLL entry point."""
37    def __init__(self, func_name, return_type, parameters):
38        """Initialize Entry instance. |func_name| is the function name,
39           |return_type| its return type, and |parameters| is a list of
40           (type,name) tuples from the entry's signature.
41        """
42        self.func_name = func_name
43        self.return_type = return_type
44        self.parameters = ""
45        self.vartypes = []
46        self.varnames = []
47        self.call = ""
48        comma = ""
49        for param in parameters:
50            self.vartypes.append(param[0])
51            self.varnames.append(param[1])
52            self.parameters += "%s%s %s" % (comma, param[0], param[1])
53            self.call += "%s%s" % (comma, param[1])
54            comma = ", "
55
56def banner_command(argv):
57    """Return sanitized command-line description.
58       |argv| must be a list of command-line parameters, e.g. sys.argv.
59       Return a string corresponding to the command, with platform-specific
60       paths removed."""
61
62    # Remove path from first parameter
63    argv = argv[:]
64    argv[0] = "host/commands/gen-entries.py"
65    return ' '.join(argv)
66
67def parse_entries_file(lines):
68    """Parse an .entries file and return a tuple of:
69        entries: list of Entry instances from the file.
70        prefix_name: prefix name from the file, or None.
71        verbatim: list of verbatim lines from the file.
72        errors: list of errors in the file, prefixed by line number.
73    """
74    entries = []
75    verbatim = []
76    errors = []
77    lineno = 0
78    prefix_name = None
79    for line in lines:
80        lineno += 1
81        line = line.strip()
82        if len(line) == 0:  # Ignore empty lines
83            continue
84        if line[0] == '#':  # Ignore comments
85            continue
86        if line[0] == '!':  # Prefix name
87            prefix_name = line[1:]
88            continue
89        if line[0] == '%':  # Verbatim line copy
90            verbatim.append(line[1:])
91            continue
92        # Must be a function signature.
93        m = re_func.match(line)
94        if not m:
95            errors.append("%d: '%s'" % (lineno, line))
96            continue
97
98        return_type, func_name, parameters = m.groups()
99        return_type = return_type.strip()
100        parameters = parameters.strip()
101        params = []
102        failure = False
103        if parameters != "void":
104            for parameter in parameters.split(','):
105                parameter = parameter.strip()
106                m = re_param.match(parameter)
107                if not m:
108                    errors.append("%d: parameter '%s'" % (lineno, parameter))
109                    failure = True
110                    break
111                else:
112                    param_type, param_name = m.groups()
113                    params.append((param_type.strip(), param_name.strip()))
114
115        if not failure:
116            entries.append(Entry(func_name, return_type, params))
117
118    return (entries, prefix_name, verbatim, errors)
119
120
121def gen_functions_header(entries, prefix_name, verbatim, filename, with_args):
122    """Generate a C header containing a macro listing all entry points.
123       |entries| is a list of Entry instances.
124       |prefix_name| is a prefix-name, it will be converted to upper-case.
125       |verbatim| is a list of verbatim lines that must appear before the
126       macro declaration. Useful to insert #include <> statements.
127       |filename| is the name of the original file.
128    """
129    prefix_name = prefix_name.upper()
130
131    print "// Auto-generated with: %s" % banner_command(sys.argv)
132    print "// DO NOT EDIT THIS FILE"
133    print ""
134    print "#ifndef %s_FUNCTIONS_H" % prefix_name
135    print "#define %s_FUNCTIONS_H" % prefix_name
136    print ""
137    for line in verbatim:
138        print line
139
140    print "#define LIST_%s_FUNCTIONS(X) \\" % prefix_name
141    for entry in entries:
142        if with_args:
143            print "  X(%s, %s, (%s), (%s)) \\" % \
144                    (entry.return_type, entry.func_name, entry.parameters,
145                     entry.call)
146        else:
147            print "  X(%s, %s, (%s)) \\" % \
148                    (entry.return_type, entry.func_name, entry.parameters)
149
150    print ""
151    print ""
152    print "#endif  // %s_FUNCTIONS_H" % prefix_name
153
154def gen_dll_wrapper(entries, prefix_name, verbatim, filename):
155    """Generate a C source file that contains functions that act as wrappers
156       for entry points located in another shared library. This allows the
157       code that calls these functions to perform lazy-linking to system
158       libraries.
159       |entries|, |prefix_name|, |verbatim| and |filename| are the same as
160       for gen_functions_header() above.
161    """
162    upper_name = prefix_name.upper()
163
164    ENTRY_PREFIX = "__dll_"
165
166    print "// Auto-generated with: %s" % banner_command(sys.argv)
167    print "// DO NOT EDIT THIS FILE"
168    print ""
169    print "#include <dlfcn.h>"
170    for line in verbatim:
171        print line
172
173    print ""
174    print "///"
175    print "///  W R A P P E R   P O I N T E R S"
176    print "///"
177    print ""
178    for entry in entries:
179        ptr_name = ENTRY_PREFIX + entry.func_name
180        print "static %s (*%s)(%s) = 0;" % \
181                (entry.return_type, ptr_name, entry.parameters)
182
183    print ""
184    print "///"
185    print "///  W R A P P E R   F U N C T I O N S"
186    print "///"
187    print ""
188
189    for entry in entries:
190        print "%s %s(%s) {" % \
191                (entry.return_type, entry.func_name, entry.parameters)
192        ptr_name = ENTRY_PREFIX + entry.func_name
193        if entry.return_type != "void":
194            print "  return %s(%s);" % (ptr_name, entry.call)
195        else:
196            print "  %s(%s);" % (ptr_name, entry.call)
197        print "}\n"
198
199    print ""
200    print "///"
201    print "///  I N I T I A L I Z A T I O N   F U N C T I O N"
202    print "///"
203    print ""
204
205    print "int %s_dynlink_init(void* lib) {" % prefix_name
206    for entry in entries:
207        ptr_name = ENTRY_PREFIX + entry.func_name
208        print "  %s = (%s(*)(%s))dlsym(lib, \"%s\");" % \
209                (ptr_name,
210                 entry.return_type,
211                 entry.parameters,
212                 entry.func_name)
213        print "  if (!%s) return -1;" % ptr_name
214    print "  return 0;"
215    print "}"
216
217
218def gen_windows_def_file(entries):
219    """Generate a windows DLL .def file. |entries| is a list of Entry instances.
220    """
221    print "EXPORTS"
222    for entry in entries:
223        print "    %s" % entry.func_name
224
225
226def gen_unix_sym_file(entries):
227    """Generate an ELF linker version file. |entries| is a list of Entry
228       instances.
229    """
230    print "VERSION {"
231    print "\tglobal:"
232    for entry in entries:
233        print "\t\t%s;" % entry.func_name
234    print "\tlocal:"
235    print "\t\t*;"
236    print "};"
237
238def gen_symbols(entries, underscore):
239    """Generate a list of symbols from |entries|, a list of Entry instances.
240       |underscore| is a boolean. If True, then prepend an underscore to each
241       symbol name.
242    """
243    prefix = ""
244    if underscore:
245        prefix = "_"
246    for entry in entries:
247        print "%s%s" % (prefix, entry.func_name)
248
249def parse_file(filename, lines, mode):
250    """Generate one of possible outputs from |filename|. |lines| must be a list
251       of text lines from the file, and |mode| is one of the --mode option
252       values.
253    """
254    entries, prefix_name, verbatim, errors = parse_entries_file(lines)
255    if errors:
256        for error in errors:
257            print >> sys.stderr, "ERROR: %s:%s" % (filename, error)
258        sys.exit(1)
259
260    if not prefix_name:
261        prefix_name = "unknown"
262
263    if mode == 'def':
264        gen_windows_def_file(entries)
265    elif mode == 'sym':
266        gen_unix_sym_file(entries)
267    elif mode == 'wrapper':
268        gen_dll_wrapper(entries, prefix_name, verbatim, filename)
269    elif mode == 'symbols':
270        gen_symbols(entries, False)
271    elif mode == '_symbols':
272        gen_symbols(entries, True)
273    elif mode == 'functions':
274        gen_functions_header(entries, prefix_name, verbatim, filename, False)
275    elif mode == 'funcargs':
276        gen_functions_header(entries, prefix_name, verbatim, filename, True)
277
278
279# List of valid --mode option values.
280mode_list = [
281    'def', 'sym', 'wrapper', 'symbols', '_symbols', 'functions', 'funcargs'
282]
283
284# Argument parsing.
285parser = argparse.ArgumentParser(
286    formatter_class=argparse.RawDescriptionHelpFormatter,
287    description="""\
288A script used to parse an .entries input file containing a list of function
289declarations, and generate various output files depending on the value of
290the --mode option, which can be:
291
292  def        Generate a windows DLL .def file.
293  sym        Generate a Unix .so linker script.
294  wrapper    Generate a C source file containing wrapper functions.
295  symbols    Generate a simple list of symbols, one per line.
296  _symbols   Generate a simple list of symbols, prefixed with _.
297  functions  Generate a C header containing a macro listing all functions.
298  funcargs   Like 'functions', but adds function call arguments to listing.
299
300""")
301parser.add_argument("--mode", help="Output mode", choices=mode_list)
302parser.add_argument("--output", help="output file")
303parser.add_argument("file", help=".entries file path")
304
305args = parser.parse_args()
306
307if not args.mode:
308    print >> sys.stderr, "ERROR: Please use --mode=<name>, see --help."
309    sys.exit(1)
310
311if args.output:
312    sys.stdout = open(args.output, "w+")
313
314if args.file == '--':
315    parse_file("<stdin>", sys.stdin, args.mode)
316else:
317    parse_file(args.file, open(args.file), args.mode)
318