cpp_bundle_generator.py revision 58537e28ecd584eab876aee8be7156509866d23a
1# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import code
6import cpp_util
7from model import Platforms
8from schema_util import CapitalizeFirstLetter
9from schema_util import JsFunctionNameToClassName
10
11import json
12import os
13import re
14
15# TODO(miket/asargent) - parameterize this.
16SOURCE_BASE_PATH = 'chrome/common/extensions/api'
17
18def _RemoveDescriptions(node):
19  """Returns a copy of |schema| with "description" fields removed.
20  """
21  if isinstance(node, dict):
22    result = {}
23    for key, value in node.items():
24      # Some schemas actually have properties called "description", so only
25      # remove descriptions that have string values.
26      if key == 'description' and isinstance(value, basestring):
27        continue
28      result[key] = _RemoveDescriptions(value)
29    return result
30  if isinstance(node, list):
31    return [_RemoveDescriptions(v) for v in node]
32  return node
33
34
35class CppBundleGenerator(object):
36  """This class contains methods to generate code based on multiple schemas.
37  """
38
39  def __init__(self, root, model, api_defs, cpp_type_generator, cpp_namespace):
40    self._root = root
41    self._model = model
42    self._api_defs = api_defs
43    self._cpp_type_generator = cpp_type_generator
44    self._cpp_namespace = cpp_namespace
45
46    self.api_cc_generator = _APICCGenerator(self)
47    self.api_h_generator = _APIHGenerator(self)
48    self.schemas_cc_generator = _SchemasCCGenerator(self)
49    self.schemas_h_generator = _SchemasHGenerator(self)
50
51  def _GenerateHeader(self, file_base, body_code):
52    """Generates a code.Code object for a header file
53
54    Parameters:
55    - |file_base| - the base of the filename, e.g. 'foo' (for 'foo.h')
56    - |body_code| - the code to put in between the multiple inclusion guards"""
57    c = code.Code()
58    c.Append(cpp_util.CHROMIUM_LICENSE)
59    c.Append()
60    c.Append(cpp_util.GENERATED_BUNDLE_FILE_MESSAGE % SOURCE_BASE_PATH)
61    ifndef_name = cpp_util.GenerateIfndefName(SOURCE_BASE_PATH, file_base)
62    c.Append()
63    c.Append('#ifndef %s' % ifndef_name)
64    c.Append('#define %s' % ifndef_name)
65    c.Append()
66    c.Concat(body_code)
67    c.Append()
68    c.Append('#endif  // %s' % ifndef_name)
69    c.Append()
70    return c
71
72  def _GetPlatformIfdefs(self, model_object):
73    """Generates the "defined" conditional for an #if check if |model_object|
74    has platform restrictions. Returns None if there are no restrictions.
75    """
76    if model_object.platforms is None:
77      return None
78    ifdefs = []
79    for platform in model_object.platforms:
80      if platform == Platforms.CHROMEOS:
81        ifdefs.append('defined(OS_CHROMEOS)')
82      else:
83        raise ValueError("Unsupported platform ifdef: %s" % platform.name)
84    return ' and '.join(ifdefs)
85
86  def _GenerateRegisterFunctions(self, namespace_name, function):
87    c = code.Code()
88    function_ifdefs = self._GetPlatformIfdefs(function)
89    if function_ifdefs is not None:
90      c.Append("#if %s" % function_ifdefs, indent_level=0)
91
92    function_name = JsFunctionNameToClassName(namespace_name, function.name)
93    c.Append("registry->RegisterFunction<%sFunction>();" % (
94        function_name))
95
96    if function_ifdefs is not None:
97      c.Append("#endif  // %s" % function_ifdefs, indent_level=0)
98    return c
99
100  def _GenerateFunctionRegistryRegisterAll(self):
101    c = code.Code()
102    c.Append('// static')
103    c.Sblock('void GeneratedFunctionRegistry::RegisterAll('
104                 'ExtensionFunctionRegistry* registry) {')
105    for namespace in self._model.namespaces.values():
106      namespace_ifdefs = self._GetPlatformIfdefs(namespace)
107      if namespace_ifdefs is not None:
108        c.Append("#if %s" % namespace_ifdefs, indent_level=0)
109
110      namespace_name = CapitalizeFirstLetter(namespace.name.replace(
111          "experimental.", ""))
112      for function in namespace.functions.values():
113        if function.nocompile:
114          continue
115        c.Concat(self._GenerateRegisterFunctions(namespace.name, function))
116
117      for type_ in namespace.types.values():
118        for function in type_.functions.values():
119          if function.nocompile:
120            continue
121          namespace_types_name = JsFunctionNameToClassName(
122                namespace.name, type_.name)
123          c.Concat(self._GenerateRegisterFunctions(namespace_types_name,
124                                                   function))
125
126      if namespace_ifdefs is not None:
127        c.Append("#endif  // %s" % namespace_ifdefs, indent_level=0)
128    c.Eblock("}")
129    return c
130
131
132class _APIHGenerator(object):
133  """Generates the header for API registration / declaration"""
134  def __init__(self, cpp_bundle):
135    self._bundle = cpp_bundle
136
137  def Generate(self, namespace):
138    c = code.Code()
139
140    c.Append('#include <string>')
141    c.Append()
142    c.Append('#include "base/basictypes.h"')
143    c.Append()
144    c.Append("class ExtensionFunctionRegistry;")
145    c.Append()
146    c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace))
147    c.Append()
148    c.Append('class GeneratedFunctionRegistry {')
149    c.Sblock(' public:')
150    c.Append('static void RegisterAll('
151                 'ExtensionFunctionRegistry* registry);')
152    c.Eblock('};')
153    c.Append()
154    c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace))
155    return self._bundle._GenerateHeader('generated_api', c)
156
157
158class _APICCGenerator(object):
159  """Generates a code.Code object for the generated API .cc file"""
160
161  def __init__(self, cpp_bundle):
162    self._bundle = cpp_bundle
163
164  def Generate(self, namespace):
165    c = code.Code()
166    c.Append(cpp_util.CHROMIUM_LICENSE)
167    c.Append()
168    c.Append('#include "%s"' % (os.path.join(SOURCE_BASE_PATH,
169                                             'generated_api.h')))
170    c.Append()
171    for namespace in self._bundle._model.namespaces.values():
172      namespace_name = namespace.unix_name.replace("experimental_", "")
173      implementation_header = namespace.compiler_options.get(
174          "implemented_in",
175          "chrome/browser/extensions/api/%s/%s_api.h" % (namespace_name,
176                                                         namespace_name))
177      if not os.path.exists(
178          os.path.join(self._bundle._root,
179                       os.path.normpath(implementation_header))):
180        if "implemented_in" in namespace.compiler_options:
181          raise ValueError('Header file for namespace "%s" specified in '
182                          'compiler_options not found: %s' %
183                          (namespace.unix_name, implementation_header))
184        continue
185      ifdefs = self._bundle._GetPlatformIfdefs(namespace)
186      if ifdefs is not None:
187        c.Append("#if %s" % ifdefs, indent_level=0)
188
189      c.Append('#include "%s"' % implementation_header)
190
191      if ifdefs is not None:
192        c.Append("#endif  // %s" % ifdefs, indent_level=0)
193    c.Append()
194    c.Append('#include '
195                 '"chrome/browser/extensions/extension_function_registry.h"')
196    c.Append()
197    c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace))
198    c.Append()
199    c.Concat(self._bundle._GenerateFunctionRegistryRegisterAll())
200    c.Append()
201    c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace))
202    c.Append()
203    return c
204
205
206class _SchemasHGenerator(object):
207  """Generates a code.Code object for the generated schemas .h file"""
208  def __init__(self, cpp_bundle):
209    self._bundle = cpp_bundle
210
211  def Generate(self, namespace):
212    c = code.Code()
213    c.Append('#include <map>')
214    c.Append('#include <string>')
215    c.Append()
216    c.Append('#include "base/strings/string_piece.h"')
217    c.Append()
218    c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace))
219    c.Append()
220    c.Append('class GeneratedSchemas {')
221    c.Sblock(' public:')
222    c.Append('// Determines if schema named |name| is generated.')
223    c.Append('static bool IsGenerated(std::string name);')
224    c.Append()
225    c.Append('// Gets the API schema named |name|.')
226    c.Append('static base::StringPiece Get(const std::string& name);')
227    c.Eblock('};')
228    c.Append()
229    c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace))
230    return self._bundle._GenerateHeader('generated_schemas', c)
231
232
233def _FormatNameAsConstant(name):
234  """Formats a name to be a C++ constant of the form kConstantName"""
235  name = '%s%s' % (name[0].upper(), name[1:])
236  return 'k%s' % re.sub('_[a-z]',
237                        lambda m: m.group(0)[1].upper(),
238                        name.replace('.', '_'))
239
240
241class _SchemasCCGenerator(object):
242  """Generates a code.Code object for the generated schemas .cc file"""
243
244  def __init__(self, cpp_bundle):
245    self._bundle = cpp_bundle
246
247  def Generate(self, namespace):
248    c = code.Code()
249    c.Append(cpp_util.CHROMIUM_LICENSE)
250    c.Append()
251    c.Append('#include "%s"' % (os.path.join(SOURCE_BASE_PATH,
252                                             'generated_schemas.h')))
253    c.Append()
254    c.Append('#include "base/lazy_instance.h"')
255    c.Append()
256    c.Append('namespace {')
257    for api in self._bundle._api_defs:
258      namespace = self._bundle._model.namespaces[api.get('namespace')]
259      # JSON parsing code expects lists of schemas, so dump a singleton list.
260      json_content = json.dumps([_RemoveDescriptions(api)],
261                                separators=(',', ':'))
262      # Escape all double-quotes and backslashes. For this to output a valid
263      # JSON C string, we need to escape \ and ".
264      json_content = json_content.replace('\\', '\\\\').replace('"', '\\"')
265      c.Append('const char %s[] = "%s";' %
266          (_FormatNameAsConstant(namespace.name), json_content))
267    c.Append('}')
268    c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace))
269    c.Append()
270    c.Sblock('struct Static {')
271    c.Sblock('Static() {')
272    for api in self._bundle._api_defs:
273      namespace = self._bundle._model.namespaces[api.get('namespace')]
274      c.Append('schemas["%s"] = %s;' % (namespace.name,
275                                        _FormatNameAsConstant(namespace.name)))
276    c.Eblock('}')
277    c.Append()
278    c.Append('std::map<std::string, const char*> schemas;')
279    c.Eblock('};')
280    c.Append()
281    c.Append('base::LazyInstance<Static> g_lazy_instance;')
282    c.Append()
283    c.Append('// static')
284    c.Sblock('base::StringPiece GeneratedSchemas::Get('
285                  'const std::string& name) {')
286    c.Append('return IsGenerated(name) ? '
287             'g_lazy_instance.Get().schemas[name] : "";')
288    c.Eblock('}')
289    c.Append()
290    c.Append('// static')
291    c.Sblock('bool GeneratedSchemas::IsGenerated(std::string name) {')
292    c.Append('return g_lazy_instance.Get().schemas.count(name) > 0;')
293    c.Eblock('}')
294    c.Append()
295    c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace))
296    c.Append()
297    return c
298