| #!/usr/bin/env python |
| # Copyright 2012 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """ |
| Creates a library loader (a header and implementation file), |
| which is a wrapper for dlopen or direct linking with given library. |
| |
| The loader makes it possible to have the same client code for both cases, |
| and also makes it easier to write code using dlopen (and also provides |
| a standard way to do so, and limits the ugliness just to generated files). |
| |
| For more info refer to http://crbug.com/162733 . |
| """ |
| |
| |
| import optparse |
| import os.path |
| import re |
| import sys |
| |
| |
| HEADER_TEMPLATE = """// This is generated file. Do not modify directly. |
| // Path to the code generator: %(generator_path)s . |
| |
| #ifndef %(unique_prefix)s |
| #define %(unique_prefix)s |
| |
| %(wrapped_header_include)s |
| |
| #include <string> |
| |
| class %(class_name)s { |
| public: |
| %(class_name)s(); |
| ~%(class_name)s(); |
| |
| bool Load(const std::string& library_name) |
| __attribute__((warn_unused_result)); |
| |
| bool loaded() const { return loaded_; } |
| |
| %(member_decls)s |
| |
| private: |
| void CleanUp(bool unload); |
| |
| #if defined(%(unique_prefix)s_DLOPEN) |
| void* library_; |
| #endif |
| |
| bool loaded_; |
| |
| // Disallow copy constructor and assignment operator. |
| %(class_name)s(const %(class_name)s&); |
| void operator=(const %(class_name)s&); |
| }; |
| |
| #endif // %(unique_prefix)s |
| """ |
| |
| |
| HEADER_MEMBER_TEMPLATE = """ decltype(&::%(function_name)s) %(function_name)s; |
| """ |
| |
| |
| IMPL_TEMPLATE = """// This is generated file. Do not modify directly. |
| // Path to the code generator: %(generator_path)s . |
| |
| #include "%(generated_header_name)s" |
| |
| #include <dlfcn.h> |
| |
| // Put these sanity checks here so that they fire at most once |
| // (to avoid cluttering the build output). |
| #if !defined(%(unique_prefix)s_DLOPEN) && !defined(%(unique_prefix)s_DT_NEEDED) |
| #error neither %(unique_prefix)s_DLOPEN nor %(unique_prefix)s_DT_NEEDED defined |
| #endif |
| #if defined(%(unique_prefix)s_DLOPEN) && defined(%(unique_prefix)s_DT_NEEDED) |
| #error both %(unique_prefix)s_DLOPEN and %(unique_prefix)s_DT_NEEDED defined |
| #endif |
| |
| %(class_name)s::%(class_name)s() : loaded_(false) { |
| } |
| |
| %(class_name)s::~%(class_name)s() { |
| CleanUp(loaded_); |
| } |
| |
| bool %(class_name)s::Load(const std::string& library_name) { |
| if (loaded_) |
| return false; |
| |
| #if defined(%(unique_prefix)s_DLOPEN) |
| library_ = dlopen(library_name.c_str(), RTLD_LAZY); |
| if (!library_) |
| return false; |
| #endif |
| |
| %(member_init)s |
| |
| loaded_ = true; |
| return true; |
| } |
| |
| void %(class_name)s::CleanUp(bool unload) { |
| #if defined(%(unique_prefix)s_DLOPEN) |
| if (unload) { |
| dlclose(library_); |
| library_ = NULL; |
| } |
| #endif |
| loaded_ = false; |
| %(member_cleanup)s |
| } |
| """ |
| |
| IMPL_MEMBER_INIT_TEMPLATE = """ |
| #if defined(%(unique_prefix)s_DLOPEN) |
| %(function_name)s = |
| reinterpret_cast<decltype(this->%(function_name)s)>( |
| dlsym(library_, "%(function_name)s")); |
| #endif |
| #if defined(%(unique_prefix)s_DT_NEEDED) |
| %(function_name)s = &::%(function_name)s; |
| #endif |
| if (!%(function_name)s) { |
| CleanUp(true); |
| return false; |
| } |
| """ |
| |
| IMPL_MEMBER_CLEANUP_TEMPLATE = """ %(function_name)s = NULL; |
| """ |
| |
| def main(): |
| parser = optparse.OptionParser() |
| parser.add_option('--name') |
| parser.add_option('--output-cc') |
| parser.add_option('--output-h') |
| parser.add_option('--header') |
| |
| parser.add_option('--bundled-header') |
| parser.add_option('--use-extern-c', action='store_true', default=False) |
| parser.add_option('--link-directly', type=int, default=0) |
| |
| options, args = parser.parse_args() |
| |
| if not options.name: |
| parser.error('Missing --name parameter') |
| if not options.output_cc: |
| parser.error('Missing --output-cc parameter') |
| if not options.output_h: |
| parser.error('Missing --output-h parameter') |
| if not options.header: |
| parser.error('Missing --header paramater') |
| if not args: |
| parser.error('No function names specified') |
| |
| if os.path.dirname(options.output_cc) != os.path.dirname(options.output_h): |
| parser.error('--output-cc and --output-h must be in same directory') |
| |
| # Create a unique prefix, e.g. for header guards. |
| # Stick a known string at the beginning to ensure this doesn't begin |
| # with an underscore, which is reserved for the C++ implementation. |
| unique_prefix = ( |
| 'LIBRARY_LOADER_' + |
| re.sub(r'[\W]', '_', os.path.basename(options.output_h)).upper()) |
| |
| member_decls = [] |
| member_init = [] |
| member_cleanup = [] |
| for fn in args: |
| member_decls.append(HEADER_MEMBER_TEMPLATE % { |
| 'function_name': fn, |
| 'unique_prefix': unique_prefix |
| }) |
| member_init.append(IMPL_MEMBER_INIT_TEMPLATE % { |
| 'function_name': fn, |
| 'unique_prefix': unique_prefix |
| }) |
| member_cleanup.append(IMPL_MEMBER_CLEANUP_TEMPLATE % { |
| 'function_name': fn, |
| 'unique_prefix': unique_prefix |
| }) |
| |
| header = options.header |
| if options.link_directly == 0 and options.bundled_header: |
| header = options.bundled_header |
| wrapped_header_include = '#include %s\n' % header |
| |
| # Some libraries (e.g. libpci) have headers that cannot be included |
| # without extern "C", otherwise they cause the link to fail. |
| # TODO(phajdan.jr): This is a workaround for broken headers. Remove it. |
| if options.use_extern_c: |
| wrapped_header_include = 'extern "C" {\n%s\n}\n' % wrapped_header_include |
| |
| # It seems cleaner just to have a single #define here and #ifdefs in bunch |
| # of places, rather than having a different set of templates, duplicating |
| # or complicating more code. |
| if options.link_directly == 0: |
| wrapped_header_include += '#define %s_DLOPEN\n' % unique_prefix |
| elif options.link_directly == 1: |
| wrapped_header_include += '#define %s_DT_NEEDED\n' % unique_prefix |
| else: |
| parser.error('Invalid value for --link-directly. Should be 0 or 1.') |
| |
| # Make it easier for people to find the code generator just in case. |
| # Doing it this way is more maintainable, because it's going to work |
| # even if file gets moved without updating the contents. |
| source_tree_root = os.path.abspath( |
| os.path.join(os.path.dirname(__file__), '..', '..')) |
| generator_path = os.path.relpath(__file__, source_tree_root) |
| |
| header_contents = HEADER_TEMPLATE % { |
| 'generator_path': generator_path, |
| 'unique_prefix': unique_prefix, |
| 'wrapped_header_include': wrapped_header_include, |
| 'class_name': options.name, |
| 'member_decls': ''.join(member_decls), |
| } |
| |
| impl_contents = IMPL_TEMPLATE % { |
| 'generator_path': generator_path, |
| 'unique_prefix': unique_prefix, |
| 'generated_header_name': os.path.basename(options.output_h), |
| 'class_name': options.name, |
| 'member_init': ''.join(member_init), |
| 'member_cleanup': ''.join(member_cleanup), |
| } |
| |
| header_file = open(options.output_h, 'w') |
| try: |
| header_file.write(header_contents) |
| finally: |
| header_file.close() |
| |
| impl_file = open(options.output_cc, 'w') |
| try: |
| impl_file.write(impl_contents) |
| finally: |
| impl_file.close() |
| |
| return 0 |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |