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