diff options
author | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-03 21:12:01 +0000 |
---|---|---|
committer | ajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-03 21:12:01 +0000 |
commit | f5b50b81cdf4a883c1eba35394b7b2191179d246 (patch) | |
tree | 2ab8ee914b6d655b4b7d3900d642e75266040234 /third_party | |
parent | 9cee84a06a0694547bf977f81918d318aecd2d9c (diff) | |
download | chromium_src-f5b50b81cdf4a883c1eba35394b7b2191179d246.zip chromium_src-f5b50b81cdf4a883c1eba35394b7b2191179d246.tar.gz chromium_src-f5b50b81cdf4a883c1eba35394b7b2191179d246.tar.bz2 |
Python script for auto-generating stub/def files for a list of signatures.
This python script takes a list of C-style signatures and generates either a windows def file, or a C++ stub file that allows for binding of functions via dlopen/dlsym. This is used to support delay loading in windows and simulate it on posix systems.
Review URL: http://codereview.chromium.org/113803
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17534 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'third_party')
-rwxr-xr-x | third_party/ffmpeg/generate_stubs.py | 833 | ||||
-rwxr-xr-x | third_party/ffmpeg/generate_stubs_unittest.py | 311 |
2 files changed, 1144 insertions, 0 deletions
diff --git a/third_party/ffmpeg/generate_stubs.py b/third_party/ffmpeg/generate_stubs.py new file mode 100755 index 0000000..1288697 --- /dev/null +++ b/third_party/ffmpeg/generate_stubs.py @@ -0,0 +1,833 @@ +#!/usr/bin/python +# +# Copyright (c) 2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Creates windows and posix stub files for a given set of signatures. + +For libraries that need to be loaded outside of the standard executable startup +path mechanism, stub files need to be generated for the wanted functions. In +windows, this is done via "def" files and the delay load mechanism. On a posix +system, a set of stub functions need to be generated that dispatch to functions +found via dlsym. + +This script takes a set of files, where each file is a list of C-style +signatures (one signature per line). The output is either a windows def file, +or a header + implementation file of stubs suitable for use in a posix system. +""" + +__author__ = 'ajwong@chromium.org (Albert J. Wong)' + +import optparse +import os +import re +import string +import subprocess + + +class Error(Exception): + pass + + +class BadSignatureError(Error): + pass + + +# Regular expression used to parse signatures in the input files. The regex +# is built around identifying the "identifier" for the function name. We +# consider the identifier to be the string that follows these constraints: +# +# 1) Starts with [_a-ZA-Z] (C++ spec 2.10). +# 2) Continues with [_a-ZA-Z0-9] (C++ spec 2.10). +# 3) Preceeds an opening parenthesis by 0 or more whitespace chars. +# +# From that, all preceeding characters are considered the return value. +# Trailing characters should have a substring matching the form (.*). That +# is considered the arguments. +SIGNATURE_REGEX = re.compile('(.+?)([_a-zA-Z][_a-zA-Z0-9]+)\s*\((.*?)\)') + +# Used for generating C++ identifiers. +INVALID_C_IDENT_CHARS = re.compile('[^_a-zA-Z0-9]') + +# Constans defning the supported file types options. +FILE_TYPE_WIN = 'windows_lib' +FILE_TYPE_POSIX_STUB = 'posix_stubs' + + +def RemoveTrailingSlashes(path): + """Removes trailing forward slashes from the path. + + We expect unix style path separators (/). Gyp passes unix style + separators even in windows so this is okay. + + Args: + path: The path to strip trailing slashes from. + + Returns: + The path with trailing slashes removed. + """ + # Special case a single trailing slash since '/' != '' in a path. + if path == '/': + return path + + return path.rstrip('/') + + +def ExtractModuleName(infile_path): + """Infers the module name from the input file path. + + The input filename is supposed to be in the form "ModuleName.sigs". + This function splits the filename from the extention on that basename of + the path and returns that as the module name. + + Args: + infile_path: String holding the path to the input file. + + Returns: + The module name as a string. + """ + # File name format is ModuleName.sig so split at the period. + basename = os.path.basename(infile_path) + return os.path.splitext(basename)[0] + + +def ParseSignatures(infile): + """Parses function signatures in the input file. + + This function parses a file of signatures into a list of dictionaries that + represent the function signatures in the input file. Each dictionary has + the following keys: + return_type: A string with the return type. + name: A string with the name of the function. + params: A list of each function parameter declaration (type + name) + + The format of the input file is one C-style function signature per line, no + trailing semicolon. Empty lines are allowed. An empty line is a line that + consists purely of whitespace. Lines that begin with a # are considered + comment lines and are ignored. + + We assume that "int foo(void)" is the same as "int foo()", which is not + true in C where "int foo()" is equivalent to "int foo(...)". Our generated + code is C++, and we do not handle varargs, so this is a case that can be + ignored for now. + + Args: + infile: File object holding a text file of function signatures. + + Returns: + A list of dictionaries, where each dictionary represents one function + signature. + + Raises: + BadSignatureError: A line could not be parsed as a signature. + """ + signatures = [] + for line in infile: + line = line.strip() + if line and line[0] != '#': + m = SIGNATURE_REGEX.match(line) + if m is None: + raise BadSignatureError('Unparsable line: %s' % line) + signatures.append( + {'return_type': m.group(1).strip(), + 'name': m.group(2).strip(), + 'params': [arg.strip() for arg in m.group(3).split(',')]}) + return signatures + + +class WindowsLibCreator(object): + """Creates an import library for the functions provided. + + In windows, we created a def file listing the library entry points, then + generate a stub .lib based on that def file. This class enapsulates this + logic. + """ + + def __init__(self, module_name, signatures, outdir_path): + """Initializes the WindowsLibCreator for creating a library stub. + + Args: + module_name: The name of the module we are writing a stub for. + signatures: The list of signatures to create stubs for. + outdir_path: The directory that generated files should go into. + """ + self.module_name = module_name + self.signatures = signatures + self.outdir_path = outdir_path + + def DefFilePath(self): + """Generates the path of the def file for the given module_name. + + Returns: + A string with the path to the def file. + """ + # Output file name is in the form "module_name.def". + return '%s/%s.def' % (self.outdir_path, self.module_name) + + def LibFilePath(self): + """Generates the path of the lib file for the given module_name. + + Returns: + A string with the path to the lib file. + """ + # Output file name is in the form "module_name.lib". + return '%s/%s.lib' % (self.outdir_path, self.module_name) + + def WriteDefFile(self): + """Creates a windows library defintion file. + + The def file format is basically a list of function names. Generation is + simple. After outputting the LIBRARY and EXPORTS lines, print out each + function name, one to a line, preceeded by 2 spaces. + + Calling this function will create a def file in the outdir_path with the + name "module_name.def". + """ + # Open the def file for writing. + try: + outfile = open(self.DefFilePath(), 'w') + self._WriteDefFile(outfile) + finally: + if outfile is not None: + outfile.close() + + def _WriteDefFile(self, outfile): + """Implementation of WriteDefFile for testing. + + This implementation allows injection of the outfile object for testing. + + Args: + outfile: File to populate with definitions. + """ + # Write file header. + outfile.write('LIBRARY %s\n' % self.module_name) + outfile.write('EXPORTS\n') + + # Output an entry for each signature. + for sig in self.signatures: + outfile.write(' ') + outfile.write(sig['name']) + outfile.write('\n') + + def CreateLib(self): + """Creates a windows library file. + + Calling this function will create a lib file in the outdir_path that exports + the signatures passed into the object. + """ + self.WriteDefFile() + subprocess.call(['lib', '/nologo', '/machine:X86', + '/def:' + self.DefFilePath(), + '/out:' + self.LibFilePath()]) + + +class PosixStubWriter(object): + """Creates a file of stub functions for a library that is opened via dloepn. + + Windows provides a function in their compiler known as delay loading, which + effectively generates a set of stub functions for a dynamic library that + delays loading of the dynamic library/resolution of the symbols until one of + the needed functions are accessed. + + In posix, RTLD_LAZY does something similar with DSOs. This is the default + link mode for DSOs. However, even though the symbol is not resolved until + first usage, the DSO must be present at load time of the main binary. + + To simulate the windows delay load procedure, we need to create a set of + stub functions that allow for correct linkage of the main binary, but + dispatch to the dynamically resolved symbol when the module is initialized. + + This class takes a list of function signatures, and generates a set of stub + functions plus initialization code for them. + """ + + def __init__(self, module_name, signatures): + """Initializes PosixStubWriter for this set of signatures and module_name. + + Args: + module_name: The name of the module we are writing a stub for. + signatures: The list of signatures to create stubs for. + """ + self.signatures = signatures + self.module_name = module_name + + @classmethod + def CStyleIdentifier(cls, identifier): + """Generates a C style identifier. + + The module_name has all invalid identifier characters removed (anything + that's not [_a-zA-Z0-9]) and is run through string.capwords to try + and approximate camel case. + + Args: + identifier: The string with the module name to turn to C-style. + + Returns: + A string that can be used as part of a C identifier. + """ + return string.capwords(re.sub(INVALID_C_IDENT_CHARS, '', identifier)) + + @classmethod + def EnumName(cls, module_name): + """Gets the enum name for the module. + + Takes the module name and creates a suitable enum name. The module_name + is munged to be a valid C identifier then prefixed with the string + "kModule" to generate a Google style enum name. + + Args: + module_name: The name of the module to generate an enum name for. + + Returns: + A string with the name of the enum value representing this module. + """ + return 'kModule%s' % PosixStubWriter.CStyleIdentifier(module_name) + + @classmethod + def IsInitializedName(cls, module_name): + """Gets the name of function that checks initialization of this module. + + The name is in the format IsModuleInitialized. Where "Module" is replaced + with the module name, munged to be a valid C identifier. + + Args: + module_name: The name of the module to generate the function name for. + + Returns: + A string with the name of the initialization check function. + """ + return 'Is%sInitialized' % PosixStubWriter.CStyleIdentifier(module_name) + + @classmethod + def InitializeModuleName(cls, module_name): + """Gets the name of the function that initializes this module. + + The name is in the format InitializeModule. Where "Module" is replaced + with the module name, munged to be a valid C identifier. + + Args: + module_name: The name of the module to generate the function name for. + + Returns: + A string with the name of the initialization function. + """ + return 'Initialize%s' % PosixStubWriter.CStyleIdentifier(module_name) + + @classmethod + def UninitializeModuleName(cls, module_name): + """Gets the name of the function that uninitializes this module. + + The name is in the format UninitializeModule. Where "Module" is replaced + with the module name, munged to be a valid C identifier. + + Args: + module_name: The name of the module to generate the function name for. + + Returns: + A string with the name of the uninitialization function. + """ + return 'Uninitialize%s' % PosixStubWriter.CStyleIdentifier(module_name) + + @classmethod + def HeaderFilePath(cls, stubfile_name, outdir_path): + """Generate the path to the header file for the stub code. + + Args: + stubfile_name: A string with name of the output file (minus extension). + outdir_path: A string with the path of the directory to create the files + in. + + Returns: + A string with the path to the header file. + """ + return '%s/%s.h' % (outdir_path, stubfile_name) + + @classmethod + def ImplementationFilePath(cls, stubfile_name, outdir_path): + """Generate the path to the implementation file for the stub code. + + Args: + stubfile_name: A string with name of the output file (minus extension). + outdir_path: A string with the path of the directory to create the files + in. + + Returns: + A string with the path to the implementation file. + """ + return '%s/%s.cc' % (outdir_path, stubfile_name) + + @classmethod + def StubFunctionPointer(cls, signature): + """Generates a function pointer declaration for the given signature. + + Args: + signature: The hash representing the function signature. + + Returns: + A string with the declaration of the function pointer for the signature. + """ + return 'static %s (*%s_ptr)(%s) = NULL;' % (signature['return_type'], + signature['name'], + ', '.join(signature['params'])) + + @classmethod + def StubFunction(cls, signature): + """Generates a stub function definition for the given signature. + + Args: + signature: The hash representing the function signature. + + Returns: + A string with the stub function definition. + """ + # Generate the return statement prefix if this is not a void function. + return_prefix = '' + if signature['return_type'] != 'void': + return_prefix = 'return ' + + # Generate the argument list. + arguments = [re.split('[\*& ]', arg)[-1].strip() for arg in + signature['params']] + arg_list = ', '.join(arguments) + if arg_list == 'void': + arg_list = '' + + return """%(return_type)s %(name)s(%(params)s) { + %(return_prefix)s%(name)s_ptr(%(arg_list)s); +}""" % {'return_type': signature['return_type'], + 'name': signature['name'], + 'params': ', '.join(signature['params']), + 'return_prefix': return_prefix, + 'arg_list': arg_list} + + @classmethod + def WriteImplementationPreamble(cls, header_path, outfile): + """Write the necessary includes for the implementation file. + + Args: + header_path: The path to the header file. + outfile: The file handle to populate. + """ + # Write file header. + outfile.write("""// This is generated file. Do not modify directly. + +#include "%s" + +#include <stdlib.h> // For NULL. +#include <dlfcn.h> // For dysym, dlopen. + +#include <map> +#include <vector> +""" % header_path) + + @classmethod + def WriteUmbrellaInitializer(cls, module_names, namespace, outfile): + """Writes a single function that will open + inialize each module. + + This intializer will take in an stl map of that lists the correct + dlopen target for each module. The map type is + std::map<enum StubModules, vector<std::string>> which matches one module + to a list of paths to try in dlopen. + + This function is an all-or-nothing function. If any module fails to load, + all other modules are dlclosed, and the function returns. Though it is + not enforced, this function should only be called once. + + Args: + module_names: A list with the names of the modules in this stub file. + namespace: The namespace these functions should be in. + outfile: The file handle to populate with pointer definitions. + """ + # Open namespace and add typedef for internal data structure. + outfile.write("""namespace %s { +typedef std::map<StubModules, void*> StubHandleMap; +""" % namespace) + + # Write a static cleanup function. + outfile.write("""static void CloseLibraries(StubHandleMap* stub_handles) { + for (StubHandleMap::const_iterator it = stub_handles->begin(); + it != stub_handles->end(); + ++it) { + dlclose(it->second); + } + + stub_handles->clear(); +} +""") + + # Create the stub initialize function. + outfile.write("""bool InitializeStubs(const StubPathMap& path_map) { + StubHandleMap opened_libraries; + for (int i = 0; i < kNumStubModules; ++i) { + StubModules cur_module = static_cast<StubModules>(i); + // If a module is missing, we fail. + StubPathMap::const_iterator it = path_map.find(cur_module); + if (it == path_map.end()) { + CloseLibraries(&opened_libraries); + return false; + } + + // Otherwise, attempt to dlopen the library. + const std::vector<std::string>& paths = it->second; + bool module_opened = false; + for (std::vector<std::string>::const_iterator dso_path = paths.begin(); + !module_opened && dso_path != paths.end(); + ++dso_path) { + void* handle = dlopen(dso_path->c_str(), RTLD_LAZY); + if (handle != NULL) { + module_opened = true; + opened_libraries[cur_module] = handle; + } + } + + if (!module_opened) { + CloseLibraries(&opened_libraries); + return false; + } + } + + // Initialize each module if we have not already failed. +""") + + # Call each module initializer. + for module in module_names: + outfile.write(' %s(opened_libraries[%s]);\n' % + (PosixStubWriter.InitializeModuleName(module), + PosixStubWriter.EnumName(module))) + outfile.write('\n') + + # Check the initialization status, clean up on error. + initializer_checks = ['!%s()' % PosixStubWriter.IsInitializedName(name) + for name in module_names] + uninitializers = ['%s()' % PosixStubWriter.UninitializeModuleName(name) + for name in module_names] + outfile.write(""" // Check that each module is initialized correctly. + // Close all previously opened libraries on failure. + if (%(conditional)s) { + %(uninitializers)s; + CloseLibraries(&opened_libraries); + return false; + } + + return true; +} + +} // namespace %(namespace)s +""" % {'conditional': ' ||\n '.join(initializer_checks), + 'uninitializers': ';\n '.join(uninitializers), + 'namespace': namespace}) + + @classmethod + def WriteHeaderContents(cls, module_names, namespace, header_guard, outfile): + """Writes a header file for the stub file generated for module_names. + + The header file exposes the following: + 1) An enum, StubModules, listing with an entry for each enum. + 2) A typedef for a StubPathMap allowing for specification of paths to + search for each module. + 3) The IsInitialized/Initialize/Uninitialize functions for each module. + 4) An umbrella initialize function for all modules. + + Args: + module_names: A list with the names of each module in this stub file. + namespace: The namespace these functions should be in. + header_guard: The macro to use as our header guard. + outfile: The output file to populate. + """ + outfile.write("""// This is generated file. Do not modify directly. + +#ifndef %(guard_name)s +#define %(guard_name)s + +#include <map> +#include <string> +#include <vector> + +namespace %(namespace)s { +""" % {'guard_name': header_guard, 'namespace': namespace}) + + # Generate the Initializer protoypes for each module. + outfile.write('// Individual module initializer functions.\n') + for name in module_names: + outfile.write("""bool %s(); +void %s(void* module); +void %s(); + +""" % (PosixStubWriter.IsInitializedName(name), + PosixStubWriter.InitializeModuleName(name), + PosixStubWriter.UninitializeModuleName(name))) + + # Generate the enum for umbrella initializer. + outfile.write('// Enum and typedef for umbrella initializer.\n') + outfile.write('enum StubModules {\n') + outfile.write(' %s = 0,\n' % PosixStubWriter.EnumName(module_names[0])) + for name in module_names[1:]: + outfile.write(' %s,\n' % PosixStubWriter.EnumName(name)) + outfile.write(' kNumStubModules\n') + outfile.write('};\n') + outfile.write('\n') + + # Generate typedef and prototype for umbrella initializer and close the + # header file. + outfile.write( +"""typedef std::map<StubModules, std::vector<std::string> > StubPathMap; + +// Umbrella initializer for all the modules in this stub file. +bool InitializeStubs(const StubPathMap& path_map); +} // namespace %s + +#endif // %s +""" % (namespace, header_guard)) + + def WriteImplementationContents(self, namespace, outfile): + """Given a file handle, write out the stub definitions for this module. + + Args: + namespace: The namespace these functions should be in. + outfile: The file handle to populate. + """ + outfile.write('extern "C" {\n') + self.WriteFunctionPointers(outfile) + self.WriteStubFunctions(outfile) + outfile.write('} // extern "C"\n') + outfile.write('\n') + + outfile.write('namespace %s {\n' % namespace) + outfile.write('\n') + self.WriteModuleInitializeFunctions(outfile) + outfile.write('} // namespace %s\n' % namespace) + + def WriteFunctionPointers(self, outfile): + """Write the function pointer declarations needed by the stubs. + + We need function pointers to hold the actual location of the function + implementation returned by dlsym. This function outputs a pointer + definition for each signature in the module. + + Pointers will be named with the following pattern "FuntionName_ptr". + + Args: + outfile: The file handle to populate with pointer definitions. + """ + outfile.write( +"""// Static pointers that will hold the location of the real function +// implementations after the module has been loaded. +""") + for sig in self.signatures: + outfile.write("%s\n" % PosixStubWriter.StubFunctionPointer(sig)) + outfile.write("\n"); + + def WriteStubFunctions(self, outfile): + """Write the function stubs to handle dispatching to real implementations. + + Functions that have a return type other than void will look as follows: + + ReturnType FunctionName(A a) { + return FunctionName_ptr(a); + } + + Functions with a return type of void will look as follows: + + void FunctionName(A a) { + FunctionName_ptr(a); + } + + Args: + outfile: The file handle to populate. + """ + outfile.write('// Stubs that dispatch to the real implementations.\n') + for sig in self.signatures: + outfile.write("%s\n" % PosixStubWriter.StubFunction(sig)); + + def WriteModuleInitializeFunctions(self, outfile): + """Write functions to initialize/query initlialization of the module. + + This creates 2 functions IsModuleInitialized and InitializeModule where + "Module" is replaced with the module name, first letter capitalized. + + The InitializeModule function takes a handle that is retrieved from dlopen + and attempts to assign each function pointer above via dlsym. + + The IsModuleInitialized returns true if none of the required functions + pointers are NULL. + + Args: + outfile: The file handle to populate. + """ + ptr_names = ['%s_ptr' % sig['name'] for sig in self.signatures] + + # Construct the function that tests that all the function pointers above + # have been properly populated. + outfile.write( +"""// Returns true if all stubs have been properly initialized. +bool %s() { + if (%s) { + return true; + } else { + return false; + } +} + +""" % (PosixStubWriter.IsInitializedName(self.module_name), + ' &&\n '.join(ptr_names))) + + # Create function that initializes the module. + outfile.write('// Initializes the module stubs.\n') + outfile.write('void %s(void* module) {\n' % + PosixStubWriter.InitializeModuleName(self.module_name)) + for sig in self.signatures: + outfile.write(""" %(name)s_ptr = + reinterpret_cast<%(return_type)s (*)(%(parameters)s)>( + dlsym(module, "%(name)s")); +""" % {'name': sig['name'], + 'return_type': sig['return_type'], + 'parameters': ', '.join(sig['params'])}) + outfile.write('}\n') + outfile.write('\n') + + # Create function that uninitializes the module (sets all pointers to + # NULL). + outfile.write('// Uninitialize the module stubs. Reset to NULL.\n') + outfile.write('void %s() {\n' % + PosixStubWriter.UninitializeModuleName(self.module_name)) + for sig in self.signatures: + outfile.write(' %s_ptr = NULL;\n' % sig['name']) + outfile.write('}\n') + outfile.write('\n') + + +def main(): + parser = optparse.OptionParser(usage='usage: %prog [options] input') + parser.add_option('-o', + '--output', + dest='out_dir', + default=None, + help='Output location.') + parser.add_option('-t', + '--type', + dest='type', + default=None, + help=('Type of file. Either "%s" or "%s"' % + (FILE_TYPE_POSIX_STUB, FILE_TYPE_WIN))) + parser.add_option('-s', + '--stubfile_name', + dest='stubfile_name', + default=None, + help=('Name of posix_stubs output file. Ignored for ' + '%s type.' % FILE_TYPE_WIN)) + parser.add_option('-p', + '--path_from_source', + dest='path_from_source', + default=None, + help=('The relative path from the project root that the ' + 'generated file should consider itself part of (eg. ' + 'third_party/ffmpeg). This is used to generate the ' + 'header guard and namespace for our initializer ' + 'functions and does NOT affect the physical output ' + 'location of the file like -o does. Ignored for ' + ' %s type.' % FILE_TYPE_WIN)) + parser.add_option('-e', + '--extra_stub_header', + dest='extra_stub_header', + default=None, + help=('File to insert after the system includes in the ' + 'generated stub implemenation file. Ignored for ' + '%s type.' % FILE_TYPE_WIN)) + (options, args) = parser.parse_args() + + if options.out_dir is None: + parser.error('Output location not specified') + if args: + parser.error('No inputs specified') + + if options.type not in [FILE_TYPE_WIN, FILE_TYPE_POSIX_STUB]: + parser.error('Invalid output file type') + + if options.type == FILE_TYPE_POSIX_STUB: + if options.stubfile_name is None: + parser.error('Output file name need for %s' % FILE_TYPE_POSIX_STUB) + if options.path_from_source is None: + parser.error('Path from source needed for %s' % FILE_TYPE_POSIX_STUB) + + # Make sure output directory exists. + out_dir = RemoveTrailingSlashes(options.out_dir) + if not os.path.exists(out_dir): + os.makedirs(out_dir) + + if options.type == FILE_TYPE_WIN: + for input_path in args: + try: + infile = open(input_path, 'r') + signatures = ParseSignatures(infile) + module_name = ExtractModuleName(os.path.basename(input_path)) + WindowsLibCreator(module_name, signatures, out_dir).CreateLib() + finally: + if infile is not None: + infile.close() + elif options.type == FILE_TYPE_POSIX_STUB: + # Find the paths to the output files. + header_path = PosixStubWriter.HeaderFilePath(options.stubfile_name, + out_dir) + impl_path = PosixStubWriter.ImplementationFilePath(options.stubfile_name, + out_dir) + + # Generate some convenience variables for bits of data needed below. + module_names = [ExtractModuleName(path) for path in args] + namespace = options.path_from_source.replace('/', '_').lower() + header_guard = '%s__' % namespace.upper() + header_include_path = '%s/%s' % (options.path_from_source, + os.path.basename(header_path)) + + # First create the implementation file. + try: + # Open the file, and create the preamble which consists of a file + # header plus any necessary includes. + impl_file = open(impl_path, 'w') + PosixStubWriter.WriteImplementationPreamble(header_include_path, + impl_file) + if options.extra_stub_header is not None: + try: + extra_header_file = open(options.extra_stub_header, 'r') + for line in extra_header_file: + impl_file.write(line) + impl_file.write('\n') + finally: + if extra_header_file is not None: + extra_header_file.close() + + # For each signature file, generate the stub population functions + # for that file. Each file represents one module. + for input_path in args: + name = ExtractModuleName(input_path) + try: + infile = open(input_path, 'r') + signatures = ParseSignatures(infile) + finally: + if infile is not None: + infile.close() + writer = PosixStubWriter(name, signatures) + writer.WriteImplementationContents(namespace, impl_file) + + # Lastly, output the umbrella function for the file. + PosixStubWriter.WriteUmbrellaInitializer(module_names, namespace, + impl_file) + finally: + if impl_file is not None: + impl_file.close() + + # Then create the associated header file. + try: + header_file = open(header_path, 'w') + PosixStubWriter.WriteHeaderContents(module_names, namespace, + header_guard, header_file) + finally: + if header_file is not None: + header_file.close() + + else: + raise Error('Should not reach here') + + +if __name__ == '__main__': + main() diff --git a/third_party/ffmpeg/generate_stubs_unittest.py b/third_party/ffmpeg/generate_stubs_unittest.py new file mode 100755 index 0000000..ccaa35b --- /dev/null +++ b/third_party/ffmpeg/generate_stubs_unittest.py @@ -0,0 +1,311 @@ +#!/usr/bin/python +# Copyright (c) 2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unittest for the generate_stubs.py. + +Since generate_stubs.py is a code generator, it is hard to do a very good +test. Instead of creating a golden-file test, which might be flakey, this +test elects instead to verify that various components "exist" within the +generated file as a sanity check. In particular, there is a simple hit +test to make sure that umbrella functions, etc., do try and include every +function they are responsible for invoking. Missing an invocation is quite +easily missed. + +There is no attempt to verify ordering of different components, or whether +or not those components are going to parse incorrectly because of prior +errors or positioning. Most of that should be caught really fast anyways +during any attempt to use a badly behaving script. +""" + +import re +import StringIO +import unittest +import generate_stubs as gs + + +def _MakeSignature(return_type, name, params): + return {'return_type': return_type, + 'name': name, + 'params': params} + + +SIMPLE_SIGNATURES = [ + ('int foo(int a)', _MakeSignature('int', 'foo', ['int a'])), + ('int bar(int a, double b)', _MakeSignature('int', 'bar', + ['int a', 'double b'])), + ('int baz(void)', _MakeSignature('int', 'baz', ['void'])), + ('void quux(void)', _MakeSignature('void', 'quux', ['void'])), + ('void waldo(void);', _MakeSignature('void', 'waldo', ['void'])), + ('int corge(void);', _MakeSignature('int', 'corge', ['void'])), + ] + +TRICKY_SIGNATURES = [ + ('const struct name *foo(int a, struct Test* b); ', + _MakeSignature('const struct name *', + 'foo', + ['int a', 'struct Test* b'])), + ('const struct name &foo(int a, struct Test* b);', + _MakeSignature('const struct name &', + 'foo', + ['int a', 'struct Test* b'])), + ('const struct name &_foo(int a, struct Test* b);', + _MakeSignature('const struct name &', + '_foo', + ['int a', 'struct Test* b'])), + ('struct name const * const _foo(int a, struct Test* b) ' + '__attribute__((inline));', + _MakeSignature('struct name const * const', + '_foo', + ['int a', 'struct Test* b'])) + ] + +INVALID_SIGNATURES = ['I am bad', 'Seriously bad(', ';;;'] + + +class GenerateStubModuleFunctionsUnittest(unittest.TestCase): + def testRemoveTrailingSlashes(self): + # Check simple cases. + self.assertEqual('/a/path', gs.RemoveTrailingSlashes('/a/path/')) + self.assertEqual('/a/path', gs.RemoveTrailingSlashes('/a/path///')) + self.assertEqual('/a/path', gs.RemoveTrailingSlashes('/a/path')) + + # Should not remove the last slash. + self.assertEqual('/', gs.RemoveTrailingSlashes('/')) + + def testExtractModuleName(self): + self.assertEqual('somefile-2', gs.ExtractModuleName('somefile-2.ext')) + + def testParseSignatures_EmptyFile(self): + # Empty file just generates empty signatures. + infile = StringIO.StringIO() + signatures = gs.ParseSignatures(infile) + self.assertEqual(0, len(signatures)) + + def testParseSignatures_SimpleSignatures(self): + file_contents = '\n'.join([x[0] for x in SIMPLE_SIGNATURES]) + infile = StringIO.StringIO(file_contents) + signatures = gs.ParseSignatures(infile) + self.assertEqual(len(SIMPLE_SIGNATURES), len(signatures)) + + # We assume signatures are in order. + for i in xrange(len(SIMPLE_SIGNATURES)): + self.assertEqual(SIMPLE_SIGNATURES[i][1], signatures[i], + msg='Expected %s\nActual %s\nFor %s' % + (SIMPLE_SIGNATURES[i][1], + signatures[i], + SIMPLE_SIGNATURES[i][0])) + + def testParseSignatures_TrickySignatures(self): + file_contents = '\n'.join([x[0] for x in TRICKY_SIGNATURES]) + infile = StringIO.StringIO(file_contents) + signatures = gs.ParseSignatures(infile) + self.assertEqual(len(TRICKY_SIGNATURES), len(signatures)) + + # We assume signatures are in order. + for i in xrange(len(TRICKY_SIGNATURES)): + self.assertEqual(TRICKY_SIGNATURES[i][1], signatures[i], + msg='Expected %s\nActual %s\nFor %s' % + (TRICKY_SIGNATURES[i][1], + signatures[i], + TRICKY_SIGNATURES[i][0])) + + def testParseSignatures_InvalidSignatures(self): + for i in INVALID_SIGNATURES: + infile = StringIO.StringIO(i) + self.assertRaises(gs.BadSignatureError, gs.ParseSignatures, infile) + + def testParseSignatures_CommentsIgnored(self): + my_sigs = [] + my_sigs.append('# a comment') + my_sigs.append(SIMPLE_SIGNATURES[0][0]) + my_sigs.append('# another comment') + my_sigs.append(SIMPLE_SIGNATURES[0][0]) + my_sigs.append('# a third comment') + my_sigs.append(SIMPLE_SIGNATURES[0][0]) + + file_contents = '\n'.join(my_sigs) + infile = StringIO.StringIO(file_contents) + signatures = gs.ParseSignatures(infile) + self.assertEqual(3, len(signatures)) + + +class WindowsLibCreatorUnittest(unittest.TestCase): + def setUp(self): + self.module_name = 'my_module-1' + self.signatures = [sig[1] for sig in SIMPLE_SIGNATURES] + self.out_dir = 'out_dir' + self.creator = gs.WindowsLibCreator(self.module_name, + self.signatures, + self.out_dir) + + def testDefFilePath(self): + self.assertEqual('out_dir/my_module-1.def', self.creator.DefFilePath()) + + def testLibFilePath(self): + self.assertEqual('out_dir/my_module-1.lib', self.creator.LibFilePath()) + + def testWriteDefFile(self): + outfile = StringIO.StringIO() + self.creator._WriteDefFile(outfile) + contents = outfile.getvalue() + + # Check that the file header is correct. + self.assertTrue(contents.startswith("""LIBRARY %s +EXPORTS +""" % self.module_name)) + + # Check that the signatures were exported. + for sig in self.signatures: + pattern = '\n %s\n' % sig['name'] + self.assertTrue(re.search(pattern, contents), + msg='Expected match of "%s" in %s' % (pattern, contents)) + + +class PosixStubWriterUnittest(unittest.TestCase): + def setUp(self): + self.module_name = 'my_module-1' + self.signatures = [sig[1] for sig in SIMPLE_SIGNATURES] + self.out_dir = 'out_dir' + self.writer = gs.PosixStubWriter(self.module_name, self.signatures) + + def testEnumName(self): + self.assertEqual('kModuleMy_module1', + gs.PosixStubWriter.EnumName(self.module_name)) + + def testIsInitializedName(self): + self.assertEqual('IsMy_module1Initialized', + gs.PosixStubWriter.IsInitializedName(self.module_name)) + + def testInitializeModuleName(self): + self.assertEqual( + 'InitializeMy_module1', + gs.PosixStubWriter.InitializeModuleName(self.module_name)) + + def testUninitializeModuleName(self): + self.assertEqual( + 'UninitializeMy_module1', + gs.PosixStubWriter.UninitializeModuleName(self.module_name)) + + def testHeaderFilePath(self): + self.assertEqual('outdir/mystubs-1.h', + gs.PosixStubWriter.HeaderFilePath('mystubs-1', 'outdir')) + + def testImplementationFilePath(self): + self.assertEqual( + 'outdir/mystubs-1.cc', + gs.PosixStubWriter.ImplementationFilePath('mystubs-1', 'outdir')) + + def testStubFunctionPointer(self): + self.assertEqual( + 'static int (*foo_ptr)(int a) = NULL;', + gs.PosixStubWriter.StubFunctionPointer(SIMPLE_SIGNATURES[0][1])) + + def testStubFunction(self): + # Test for a signature with a return value and a parameter. + self.assertEqual("""int foo(int a) { + return foo_ptr(a); +}""", gs.PosixStubWriter.StubFunction(SIMPLE_SIGNATURES[0][1])) + + # Test for a signature with a void return value and no parameters. + self.assertEqual("""void waldo(void) { + waldo_ptr(); +}""", gs.PosixStubWriter.StubFunction(SIMPLE_SIGNATURES[4][1])) + + def testWriteImplemenationContents(self): + outfile = StringIO.StringIO() + self.writer.WriteImplementationContents('my_namespace', outfile) + contents = outfile.getvalue() + + # Verify namespace exists somewhere. + self.assertTrue(contents.find('namespace my_namespace {') != -1) + + # Verify that each signature has an _ptr and a function call in the file. + # Check that the signatures were exported. + for sig in self.signatures: + decl = gs.PosixStubWriter.StubFunctionPointer(sig) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + # Verify that each signature has an stub function generated for it. + for sig in self.signatures: + decl = gs.PosixStubWriter.StubFunction(sig) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + # Find module initializer functions. Make sure all 3 exist. + decl = gs.PosixStubWriter.InitializeModuleName(self.module_name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.UninitializeModuleName(self.module_name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.IsInitializedName(self.module_name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + def testWriteHeaderContents(self): + # Data for header generation. + module_names = ['oneModule', 'twoModule'] + + # Make the header. + outfile = StringIO.StringIO() + self.writer.WriteHeaderContents(module_names, 'my_namespace', 'GUARD_', + outfile) + contents = outfile.getvalue() + + # Check for namespace and header guard. + self.assertTrue(contents.find('namespace my_namespace {') != -1) + self.assertTrue(contents.find('#ifndef GUARD_') != -1) + + # Check for umbrella initializer. + self.assertTrue(contents.find('InitializeStubs(') != -1) + + # Check per-module declarations. + for name in module_names: + # Check for enums. + decl = gs.PosixStubWriter.EnumName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + # Check for module initializer functions. + decl = gs.PosixStubWriter.IsInitializedName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.InitializeModuleName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.UninitializeModuleName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + + def testWriteUmbrellaInitializer(self): + # Data for header generation. + module_names = ['oneModule', 'twoModule'] + + # Make the header. + outfile = StringIO.StringIO() + self.writer.WriteUmbrellaInitializer(module_names, 'my_namespace', outfile) + contents = outfile.getvalue() + + # Check for umbrella initializer declaration. + self.assertTrue(contents.find('bool InitializeStubs(') != -1) + + # If the umbrella initializer is correctly written, each module will have + # its initializer called, checked, and uninitialized on failure. Sanity + # check that here. + for name in module_names: + # Check for module initializer functions. + decl = gs.PosixStubWriter.IsInitializedName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.InitializeModuleName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + decl = gs.PosixStubWriter.UninitializeModuleName(name) + self.assertTrue(contents.find(decl) != -1, + msg='Expected "%s" in %s' % (decl, contents)) + +if __name__ == '__main__': + unittest.main() |