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 """ 7 Creates a library loader (a header and implementation file), 8 which is a wrapper for dlopen or direct linking with given library. 9 10 The loader makes it possible to have the same client code for both cases, 11 and also makes it easier to write code using dlopen (and also provides 12 a standard way to do so, and limits the ugliness just to generated files). 13 14 For more info refer to http://crbug.com/162733 . 15 """ 16 17 18 import optparse 19 import os.path 20 import re 21 import sys 22 23 24 HEADER_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 34 class %(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 64 HEADER_MEMBER_TEMPLATE = """ typeof(&::%(function_name)s) %(function_name)s; 65 """ 66 67 68 IMPL_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 91 bool %(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 107 void %(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 119 IMPL_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 134 IMPL_MEMBER_CLEANUP_TEMPLATE = """ %(function_name)s = NULL; 135 """ 136 137 def 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 248 if __name__ == '__main__': 249 sys.exit(main()) 250