summaryrefslogtreecommitdiffstats
path: root/tools/generate_stubs
diff options
context:
space:
mode:
authorhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-02 20:50:56 +0000
committerhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-02 20:50:56 +0000
commit2ac1e7ca438583c681cbb686122f3ec69fa1d5ff (patch)
tree500afab56a65e9f61e311ee42c5e1e969cf5915a /tools/generate_stubs
parentde945c861f68df1f16d58b0570332976fed57f7d (diff)
downloadchromium_src-2ac1e7ca438583c681cbb686122f3ec69fa1d5ff.zip
chromium_src-2ac1e7ca438583c681cbb686122f3ec69fa1d5ff.tar.gz
chromium_src-2ac1e7ca438583c681cbb686122f3ec69fa1d5ff.tar.bz2
Generate stubs for OpenMAX IL
Generate stubs for OpenMAX IL so we don't need a real OpenMAX library for building. The actual library is loaded during runtime. TEST=Build is green TEST=Running omx_test works on hardware with OpenMAX support Review URL: http://codereview.chromium.org/661135 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@40418 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/generate_stubs')
-rwxr-xr-xtools/generate_stubs/generate_stubs.py968
-rwxr-xr-xtools/generate_stubs/generate_stubs_unittest.py283
2 files changed, 1251 insertions, 0 deletions
diff --git a/tools/generate_stubs/generate_stubs.py b/tools/generate_stubs/generate_stubs.py
new file mode 100755
index 0000000..e1ba671
--- /dev/null
+++ b/tools/generate_stubs/generate_stubs.py
@@ -0,0 +1,968 @@
+#!/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
+
+
+class SubprocessError(Error):
+ def __init__(self, message, error_code):
+ Error.__init__(self)
+ self.message = message
+ self.error_code = error_code
+
+ def __str__(self):
+ return 'Failed with code %s: %s' % (self.message, repr(self.error_code))
+
+
+# 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('(?P<return_type>.+?)'
+ '(?P<name>[_a-zA-Z][_a-zA-Z0-9]+)\s*'
+ '\((?P<params>.*?)\)')
+
+# Used for generating C++ identifiers.
+INVALID_C_IDENT_CHARS = re.compile('[^_a-zA-Z0-9]')
+
+# Constants defning the supported file types options.
+FILE_TYPE_WIN = 'windows_lib'
+FILE_TYPE_POSIX_STUB = 'posix_stubs'
+
+# Template for generating a stub function definition. Includes a forward
+# declaration marking the symbol as weak. This template takes the following
+# named parameters.
+# return_type: The return type.
+# name: The name of the function.
+# params: The parameters to the function.
+# return_prefix: 'return ' if this function is not void. '' otherwise.
+# arg_list: The arguments used to call the stub function.
+STUB_FUNCTION_DEFINITION = (
+ """extern %(return_type)s %(name)s(%(params)s) __attribute__((weak));
+%(return_type)s %(name)s(%(params)s) {
+ %(return_prefix)s%(name)s_ptr(%(arg_list)s);
+}""")
+
+# Template for the preamble for the stub header file with the header guards,
+# standard set of includes, and namespace opener. This template takes the
+# following named parameters:
+# guard_name: The macro to use as the header guard.
+# namespace: The namespace for the stub functions.
+STUB_HEADER_PREAMBLE = """// 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 {
+"""
+
+# Template for the end of the stub header. This closes the namespace and the
+# header guards. This template takes the following named parameters:
+# guard_name: The macro to use as the header guard.
+# namespace: The namespace for the stub functions.
+STUB_HEADER_CLOSER = """} // namespace %(namespace)s
+
+#endif // %(guard_name)s
+"""
+
+# The standard includes needed for the stub implementation file. Takes one
+# string substition with the path to the associated stub header file.
+IMPLEMENTATION_PREAMBLE = """// 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>
+"""
+
+# The start and end templates for the enum definitions used by the Umbrella
+# initializer.
+UMBRELLA_ENUM_START = """// Enum and typedef for umbrella initializer.
+enum StubModules {
+"""
+UMBRELLA_ENUM_END = """ kNumStubModules
+};
+
+"""
+
+# Start and end of the extern "C" section for the implementation contents.
+IMPLEMENTATION_CONTENTS_C_START = """extern "C" {
+
+"""
+IMPLEMENTATION_CONTENTS_C_END = """
+} // extern "C"
+
+
+"""
+
+# Templates for the start and end of a namespace. Takes one parameter, the
+# namespace name.
+NAMESPACE_START = """namespace %s {
+
+"""
+NAMESPACE_END = """} // namespace %s
+
+"""
+
+# Comment to include before the section declaring all the function pointers
+# used by the stub functions.
+FUNCTION_POINTER_SECTION_COMMENT = (
+ """// Static pointers that will hold the location of the real function
+// implementations after the module has been loaded.
+""")
+
+# Template for the module initialization check function. This template
+# takes two parameteres: the function name, and the conditional used to
+# verify the module's initialization.
+MODULE_INITIALIZATION_CHECK_FUNCTION = (
+ """// Returns true if all stubs have been properly initialized.
+bool %s() {
+ if (%s) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+""")
+
+# Template for the line that initialize the stub pointer. This template takes
+# the following named parameters:
+# name: The name of the function.
+# return_type: The return type.
+# params: The parameters to the function.
+STUB_POINTER_INITIALIZER = """ %(name)s_ptr =
+ reinterpret_cast<%(return_type)s (*)(%(parameters)s)>(
+ dlsym(module, "%(name)s"));
+"""
+
+# Template for module initializer function start and end. This template takes
+# one parameter which is the initializer function name.
+MODULE_INITIALIZE_START = """// Initializes the module stubs.
+void %s(void* module) {
+"""
+MODULE_INITIALIZE_END = """}
+
+"""
+
+# Template for module uninitializer function start and end. This template
+# takes one parameter which is the initializer function name.
+MODULE_UNINITIALIZE_START = (
+ """// Uninitialize the module stubs. Reset pointers to NULL.
+void %s() {
+""")
+MODULE_UNINITIALIZE_END = """}
+
+"""
+
+
+# Open namespace and add typedef for internal data structures used by the
+# umbrella initializer.
+UMBRELLA_INITIALIZER_START = """namespace %s {
+typedef std::map<StubModules, void*> StubHandleMap;
+"""
+
+# Function close DSOs on error and clean up dangling references.
+UMBRELLA_INITIALIZER_CLEANUP_FUNCTION = (
+ """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();
+}
+""")
+
+# Function to initialize each DSO for the given paths.
+UMBRELLA_INITIALIZER_INITIALIZE_FUNCTION_START = (
+ """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;
+ }
+ }
+""")
+
+# Template to generate code to check if each module initializer correctly
+# completed, and cleanup on failures. This template takes the following
+# named parameters.
+# conditional: The conditional expression for successful initialization.
+# uninitializers: The statements needed to uninitialize the modules.
+UMBRELLA_INITIALIZER_CHECK_AND_CLEANUP = (
+ """ // 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;
+}
+""")
+
+# Template for Initialize, Unininitialize, and IsInitialized functions for each
+# module. This template takes the following named parameters:
+# initialize: Name of the Initialize function.
+# uninitialize: Name of the Uninitialize function.
+# is_initialized: Name of the IsInitialized function.
+MODULE_FUNCTION_PROTOTYPES = """bool %(is_initialized)s();
+void %(initialize)s(void* module);
+void %(uninitialize)s();
+
+"""
+
+# Template for umbrella initializer declaration and associated datatypes.
+UMBRELLA_INITIALIZER_PROTOTYPE = (
+ """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);
+""")
+
+
+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.
+ """
+ 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('return_type').strip(),
+ 'name': m.group('name').strip(),
+ 'params': [arg.strip() for arg in m.group('params').split(',')]})
+ return signatures
+
+
+def WriteWindowsDefFile(module_name, signatures, outfile):
+ """Writes a windows def file to the given output file object.
+
+ 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.
+
+ Args:
+ module_name: The name of the module we are writing a stub for.
+ signatures: The list of signature hashes, as produced by ParseSignatures,
+ to create stubs for.
+ outfile: File handle to populate with definitions.
+ """
+ outfile.write('LIBRARY %s\n' % module_name)
+ outfile.write('EXPORTS\n')
+
+ for sig in signatures:
+ outfile.write(' %s\n' % sig['name'])
+
+
+def CreateWindowsLib(module_name, signatures, intermediate_dir, outdir_path):
+ """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. A temporary def file will be created
+ in the intermediate_dir.
+
+ Args:
+ module_name: The name of the module we are writing a stub for.
+ signatures: The list of signature hashes, as produced by ParseSignatures,
+ to create stubs for.
+ intermediate_dir: The directory where the generated .def files should go.
+ outdir_path: The directory where generated .lib files should go.
+
+ Raises:
+ SubprocessError: If invoking the windows "lib" tool fails, this is raised
+ with the error code.
+ """
+ def_file_path = os.path.join(intermediate_dir,
+ module_name + '.def')
+ lib_file_path = os.path.join(outdir_path,
+ module_name + '.lib')
+ outfile = open(def_file_path, 'w')
+ try:
+ WriteWindowsDefFile(module_name, signatures, outfile)
+ finally:
+ outfile.close()
+
+ # Invoke the "lib" program on Windows to create stub .lib files for the
+ # generated definitions. These .lib files can then be used during
+ # delayloading of the dynamic libraries.
+ ret = subprocess.call(['lib', '/nologo', '/machine:X86',
+ '/def:' + def_file_path,
+ '/out:' + lib_file_path])
+ if ret != 0:
+ raise SubprocessError(
+ 'Failed creating %s for %s' % (lib_file_path, def_file_path),
+ ret)
+
+
+class PosixStubWriter(object):
+ """Creates a file of stub functions for a library that is opened via dlopen.
+
+ 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 signature hashes, as produced by ParseSignatures,
+ 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 StubFunctionPointer(cls, signature):
+ """Generates a function pointer declaration for the given signature.
+
+ Args:
+ signature: A signature hash, as produced by ParseSignatures,
+ representating 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.
+
+ The function definitions are created with __attribute__((weak)) so that
+ they may be overridden by a real static link or mock versions to be used
+ when testing.
+
+ Args:
+ signature: A signature hash, as produced by ParseSignatures,
+ representating the function signature.
+
+ Returns:
+ A string with the stub function definition.
+ """
+ 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 STUB_FUNCTION_DEFINITION % {
+ '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.
+ """
+ outfile.write(IMPLEMENTATION_PREAMBLE % header_path)
+
+ @classmethod
+ def WriteUmbrellaInitializer(cls, module_names, namespace, outfile):
+ """Writes a single function that will open + initialize 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.
+ """
+ outfile.write(UMBRELLA_INITIALIZER_START % namespace)
+ outfile.write(UMBRELLA_INITIALIZER_CLEANUP_FUNCTION)
+
+ # Create the initializaiton function that calls all module initializers,
+ # checks if they succeeded, and backs out module loads on an error.
+ outfile.write(UMBRELLA_INITIALIZER_INITIALIZE_FUNCTION_START)
+ outfile.write(
+ '\n // Initialize each module if we have not already failed.\n')
+ for module in module_names:
+ outfile.write(' %s(opened_libraries[%s]);\n' %
+ (PosixStubWriter.InitializeModuleName(module),
+ PosixStubWriter.EnumName(module)))
+ outfile.write('\n')
+
+ # Output code to 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(UMBRELLA_INITIALIZER_CHECK_AND_CLEANUP % {
+ 'conditional': ' ||\n '.join(initializer_checks),
+ 'uninitializers': ';\n '.join(uninitializers)})
+ outfile.write('\n} // namespace %s\n' % 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 handle to populate.
+ """
+ outfile.write(STUB_HEADER_PREAMBLE %
+ {'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(MODULE_FUNCTION_PROTOTYPES % {
+ 'is_initialized': PosixStubWriter.IsInitializedName(name),
+ 'initialize': PosixStubWriter.InitializeModuleName(name),
+ 'uninitialize': PosixStubWriter.UninitializeModuleName(name)})
+
+ # Generate the enum for umbrella initializer.
+ outfile.write(UMBRELLA_ENUM_START)
+ 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(UMBRELLA_ENUM_END)
+
+ outfile.write(UMBRELLA_INITIALIZER_PROTOTYPE)
+ outfile.write(STUB_HEADER_CLOSER % {
+ 'namespace': namespace, 'guard_name':
+ 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(IMPLEMENTATION_CONTENTS_C_START)
+ self.WriteFunctionPointers(outfile)
+ self.WriteStubFunctions(outfile)
+ outfile.write(IMPLEMENTATION_CONTENTS_C_END)
+
+ outfile.write(NAMESPACE_START % namespace)
+ self.WriteModuleInitializeFunctions(outfile)
+ outfile.write(NAMESPACE_END % 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(FUNCTION_POINTER_SECTION_COMMENT)
+
+ 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 conditional expression to check the initialization of
+ # all the function pointers above. It should generate a conjuntion
+ # with each pointer on its own line, indented by six spaces to match
+ # the indentation level of MODULE_INITIALIZATION_CHECK_FUNCTION.
+ initialization_conditional = ' &&\n '.join(ptr_names)
+
+ outfile.write(MODULE_INITIALIZATION_CHECK_FUNCTION % (
+ PosixStubWriter.IsInitializedName(self.module_name),
+ initialization_conditional))
+
+ # Create function that initializes the module.
+ outfile.write(MODULE_INITIALIZE_START %
+ PosixStubWriter.InitializeModuleName(self.module_name))
+ for sig in self.signatures:
+ outfile.write(STUB_POINTER_INITIALIZER % {
+ 'name': sig['name'],
+ 'return_type': sig['return_type'],
+ 'parameters': ', '.join(sig['params'])})
+ outfile.write(MODULE_INITIALIZE_END)
+
+ # Create function that uninitializes the module (sets all pointers to
+ # NULL).
+ outfile.write(MODULE_UNINITIALIZE_START %
+ PosixStubWriter.UninitializeModuleName(self.module_name))
+ for sig in self.signatures:
+ outfile.write(' %s_ptr = NULL;\n' % sig['name'])
+ outfile.write(MODULE_UNINITIALIZE_END)
+
+
+def CreateOptionParser():
+ """Creates an OptionParser for the configuration options of script.
+
+ Returns:
+ A OptionParser object.
+ """
+ parser = optparse.OptionParser(usage='usage: %prog [options] input')
+ parser.add_option('-o',
+ '--output',
+ dest='out_dir',
+ default=None,
+ help='Output location.')
+ parser.add_option('-i',
+ '--intermediate_dir',
+ dest='intermediate_dir',
+ default=None,
+ help='Locaiton of intermediate files.')
+ 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))
+ return parser
+
+
+def ParseOptions():
+ """Parses the options and terminates program if they are not sane.
+
+ Returns:
+ The pair (optparse.OptionValues, [string]), that is the output of
+ a successful call to parser.parse_args().
+ """
+ parser = CreateOptionParser()
+ options, args = parser.parse_args()
+
+ if not args:
+ parser.error('No inputs specified')
+
+ if options.out_dir is None:
+ parser.error('Output location not 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)
+
+ return options, args
+
+
+def CreateOutputDirectories(options):
+ """Creates the intermediate and final output directories.
+
+ Given the parsed options, create the intermediate and final output
+ directories if they do not exist. Returns the paths to both directories
+ as a pair.
+
+ Args:
+ options: An OptionParser.OptionValues object with the parsed options.
+
+ Returns:
+ The pair (out_dir, intermediate_dir), both of which are strings.
+ """
+ out_dir = os.path.normpath(options.out_dir)
+ intermediate_dir = os.path.normpath(options.intermediate_dir)
+ if intermediate_dir is None:
+ intermediate_dir = out_dir
+
+ if not os.path.exists(out_dir):
+ os.makedirs(out_dir)
+ if not os.path.exists(intermediate_dir):
+ os.makedirs(intermediate_dir)
+
+ return out_dir, intermediate_dir
+
+
+def CreateWindowsLibForSigFiles(sig_files, out_dir, intermediate_dir):
+ """For each signature file, create a windows lib.
+
+ Args:
+ sig_files: Array of Strings with the paths to each signature file.
+ out_dir: String holding path to directory where the generated libs go.
+ intermediate_dir: String holding path to directory generated intermdiate
+ artifacts.
+ """
+ for input_path in sig_files:
+ infile = open(input_path, 'r')
+ try:
+ signatures = ParseSignatures(infile)
+ module_name = ExtractModuleName(os.path.basename(input_path))
+ CreateWindowsLib(module_name, signatures, intermediate_dir, out_dir)
+ finally:
+ infile.close()
+
+
+def CreatePosixStubsForSigFiles(sig_files, stub_name, out_dir,
+ intermediate_dir, path_from_source,
+ extra_stub_header):
+ """Create a posix stub library with a module for each signature file.
+
+ Args:
+ sig_files: Array of Strings with the paths to each signature file.
+ stub_name: String with the basename of the generated stub file.
+ out_dir: String holding path to directory for the .h files.
+ intermediate_dir: String holding path to directory for the .cc files.
+ path_from_source: String with relative path of generated files from the
+ project root.
+ extra_stub_header: String with path to file of extra lines to insert
+ into the generated header for the stub library.
+ """
+ header_base_name = stub_name + '.h'
+ header_path = os.path.join(out_dir, header_base_name)
+ impl_path = os.path.join(intermediate_dir, stub_name + '.cc')
+
+ module_names = [ExtractModuleName(path) for path in sig_files]
+ namespace = path_from_source.replace('/', '_').lower()
+ header_guard = '%s_' % namespace.upper()
+ header_include_path = os.path.join(path_from_source, header_base_name)
+
+ # First create the implementation file.
+ impl_file = open(impl_path, 'w')
+ try:
+ # Open the file, and create the preamble which consists of a file
+ # header plus any necessary includes.
+ PosixStubWriter.WriteImplementationPreamble(header_include_path,
+ impl_file)
+ if extra_stub_header is not None:
+ extra_header_file = open(extra_stub_header, 'r')
+ try:
+ impl_file.write('\n')
+ for line in extra_header_file:
+ impl_file.write(line)
+ impl_file.write('\n')
+ finally:
+ 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 sig_files:
+ name = ExtractModuleName(input_path)
+ infile = open(input_path, 'r')
+ try:
+ signatures = ParseSignatures(infile)
+ finally:
+ 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:
+ impl_file.close()
+
+ # Then create the associated header file.
+ header_file = open(header_path, 'w')
+ try:
+ PosixStubWriter.WriteHeaderContents(module_names, namespace,
+ header_guard, header_file)
+ finally:
+ header_file.close()
+
+
+def main():
+ options, args = ParseOptions()
+ out_dir, intermediate_dir = CreateOutputDirectories(options)
+
+ if options.type == FILE_TYPE_WIN:
+ CreateWindowsLibForSigFiles(args, out_dir, intermediate_dir)
+ elif options.type == FILE_TYPE_POSIX_STUB:
+ CreatePosixStubsForSigFiles(args, options.stubfile_name, out_dir,
+ intermediate_dir, options.path_from_source,
+ options.extra_stub_header)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/generate_stubs/generate_stubs_unittest.py b/tools/generate_stubs/generate_stubs_unittest.py
new file mode 100755
index 0000000..cfab996
--- /dev/null
+++ b/tools/generate_stubs/generate_stubs_unittest.py
@@ -0,0 +1,283 @@
+#!/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 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 WindowsLibUnittest(unittest.TestCase):
+ def testWriteWindowsDefFile(self):
+ module_name = 'my_module-1'
+ signatures = [sig[1] for sig in SIMPLE_SIGNATURES]
+ outfile = StringIO.StringIO()
+ gs.WriteWindowsDefFile(module_name, signatures, outfile)
+ contents = outfile.getvalue()
+
+ # Check that the file header is correct.
+ self.assertTrue(contents.startswith("""LIBRARY %s
+EXPORTS
+""" % module_name))
+
+ # Check that the signatures were exported.
+ for sig in 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 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("""extern int foo(int a) __attribute__((weak));
+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("""extern void waldo(void) __attribute__((weak));
+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()