1#!/usr/bin/env python 2# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6""" 7Creates a library loader (a header and implementation file), 8which is a wrapper for dlopen or direct linking with given library. 9 10The loader makes it possible to have the same client code for both cases, 11and also makes it easier to write code using dlopen (and also provides 12a standard way to do so, and limits the ugliness just to generated files). 13 14For more info refer to http://crbug.com/162733 . 15""" 16 17 18import optparse 19import os.path 20import re 21import sys 22 23 24HEADER_TEMPLATE = """// This is generated file. Do not modify directly. 25// Path to the code generator: %(generator_path)s . 26 27#ifndef %(unique_prefix)s 28#define %(unique_prefix)s 29 30%(wrapped_header_include)s 31 32#include <string> 33 34class %(class_name)s { 35 public: 36 %(class_name)s(); 37 ~%(class_name)s(); 38 39 bool Load(const std::string& library_name) 40 __attribute__((warn_unused_result)); 41 42 bool loaded() const { return loaded_; } 43 44%(member_decls)s 45 46 private: 47 void CleanUp(bool unload); 48 49#if defined(%(unique_prefix)s_DLOPEN) 50 void* library_; 51#endif 52 53 bool loaded_; 54 55 // Disallow copy constructor and assignment operator. 56 %(class_name)s(const %(class_name)s&); 57 void operator=(const %(class_name)s&); 58}; 59 60#endif // %(unique_prefix)s 61""" 62 63 64HEADER_MEMBER_TEMPLATE = """ typeof(&::%(function_name)s) %(function_name)s; 65""" 66 67 68IMPL_TEMPLATE = """// This is generated file. Do not modify directly. 69// Path to the code generator: %(generator_path)s . 70 71#include "%(generated_header_name)s" 72 73#include <dlfcn.h> 74 75// Put these sanity checks here so that they fire at most once 76// (to avoid cluttering the build output). 77#if !defined(%(unique_prefix)s_DLOPEN) && !defined(%(unique_prefix)s_DT_NEEDED) 78#error neither %(unique_prefix)s_DLOPEN nor %(unique_prefix)s_DT_NEEDED defined 79#endif 80#if defined(%(unique_prefix)s_DLOPEN) && defined(%(unique_prefix)s_DT_NEEDED) 81#error both %(unique_prefix)s_DLOPEN and %(unique_prefix)s_DT_NEEDED defined 82#endif 83 84%(class_name)s::%(class_name)s() : loaded_(false) { 85} 86 87%(class_name)s::~%(class_name)s() { 88 CleanUp(loaded_); 89} 90 91bool %(class_name)s::Load(const std::string& library_name) { 92 if (loaded_) 93 return false; 94 95#if defined(%(unique_prefix)s_DLOPEN) 96 library_ = dlopen(library_name.c_str(), RTLD_LAZY); 97 if (!library_) 98 return false; 99#endif 100 101%(member_init)s 102 103 loaded_ = true; 104 return true; 105} 106 107void %(class_name)s::CleanUp(bool unload) { 108#if defined(%(unique_prefix)s_DLOPEN) 109 if (unload) { 110 dlclose(library_); 111 library_ = NULL; 112 } 113#endif 114 loaded_ = false; 115%(member_cleanup)s 116} 117""" 118 119IMPL_MEMBER_INIT_TEMPLATE = """ 120#if defined(%(unique_prefix)s_DLOPEN) 121 %(function_name)s = 122 reinterpret_cast<typeof(this->%(function_name)s)>( 123 dlsym(library_, "%(function_name)s")); 124#endif 125#if defined(%(unique_prefix)s_DT_NEEDED) 126 %(function_name)s = &::%(function_name)s; 127#endif 128 if (!%(function_name)s) { 129 CleanUp(true); 130 return false; 131 } 132""" 133 134IMPL_MEMBER_CLEANUP_TEMPLATE = """ %(function_name)s = NULL; 135""" 136 137def main(): 138 parser = optparse.OptionParser() 139 parser.add_option('--name') 140 parser.add_option('--output-cc') 141 parser.add_option('--output-h') 142 parser.add_option('--header') 143 144 parser.add_option('--bundled-header') 145 parser.add_option('--use-extern-c', action='store_true', default=False) 146 parser.add_option('--link-directly', type=int, default=0) 147 148 options, args = parser.parse_args() 149 150 if not options.name: 151 parser.error('Missing --name parameter') 152 if not options.output_cc: 153 parser.error('Missing --output-cc parameter') 154 if not options.output_h: 155 parser.error('Missing --output-h parameter') 156 if not options.header: 157 parser.error('Missing --header paramater') 158 if not args: 159 parser.error('No function names specified') 160 161 # Make sure we are always dealing with paths relative to source tree root 162 # to avoid issues caused by different relative path roots. 163 source_tree_root = os.path.abspath( 164 os.path.join(os.path.dirname(__file__), '..', '..')) 165 options.output_cc = os.path.relpath(options.output_cc, source_tree_root) 166 options.output_h = os.path.relpath(options.output_h, source_tree_root) 167 168 # Create a unique prefix, e.g. for header guards. 169 # Stick a known string at the beginning to ensure this doesn't begin 170 # with an underscore, which is reserved for the C++ implementation. 171 unique_prefix = ('LIBRARY_LOADER_' + 172 re.sub(r'[\W]', '_', options.output_h).upper()) 173 174 member_decls = [] 175 member_init = [] 176 member_cleanup = [] 177 for fn in args: 178 member_decls.append(HEADER_MEMBER_TEMPLATE % { 179 'function_name': fn, 180 'unique_prefix': unique_prefix 181 }) 182 member_init.append(IMPL_MEMBER_INIT_TEMPLATE % { 183 'function_name': fn, 184 'unique_prefix': unique_prefix 185 }) 186 member_cleanup.append(IMPL_MEMBER_CLEANUP_TEMPLATE % { 187 'function_name': fn, 188 'unique_prefix': unique_prefix 189 }) 190 191 header = options.header 192 if options.link_directly == 0 and options.bundled_header: 193 header = options.bundled_header 194 wrapped_header_include = '#include %s\n' % header 195 196 # Some libraries (e.g. libpci) have headers that cannot be included 197 # without extern "C", otherwise they cause the link to fail. 198 # TODO(phajdan.jr): This is a workaround for broken headers. Remove it. 199 if options.use_extern_c: 200 wrapped_header_include = 'extern "C" {\n%s\n}\n' % wrapped_header_include 201 202 # It seems cleaner just to have a single #define here and #ifdefs in bunch 203 # of places, rather than having a different set of templates, duplicating 204 # or complicating more code. 205 if options.link_directly == 0: 206 wrapped_header_include += '#define %s_DLOPEN\n' % unique_prefix 207 elif options.link_directly == 1: 208 wrapped_header_include += '#define %s_DT_NEEDED\n' % unique_prefix 209 else: 210 parser.error('Invalid value for --link-directly. Should be 0 or 1.') 211 212 # Make it easier for people to find the code generator just in case. 213 # Doing it this way is more maintainable, because it's going to work 214 # even if file gets moved without updating the contents. 215 generator_path = os.path.relpath(__file__, source_tree_root) 216 217 header_contents = HEADER_TEMPLATE % { 218 'generator_path': generator_path, 219 'unique_prefix': unique_prefix, 220 'wrapped_header_include': wrapped_header_include, 221 'class_name': options.name, 222 'member_decls': ''.join(member_decls), 223 } 224 225 impl_contents = IMPL_TEMPLATE % { 226 'generator_path': generator_path, 227 'unique_prefix': unique_prefix, 228 'generated_header_name': options.output_h, 229 'class_name': options.name, 230 'member_init': ''.join(member_init), 231 'member_cleanup': ''.join(member_cleanup), 232 } 233 234 header_file = open(os.path.join(source_tree_root, options.output_h), 'w') 235 try: 236 header_file.write(header_contents) 237 finally: 238 header_file.close() 239 240 impl_file = open(os.path.join(source_tree_root, options.output_cc), 'w') 241 try: 242 impl_file.write(impl_contents) 243 finally: 244 impl_file.close() 245 246 return 0 247 248if __name__ == '__main__': 249 sys.exit(main()) 250