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