mojom_bindings_generator.py revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1#!/usr/bin/env python
2# Copyright 2013 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"""The frontend for the Mojo bindings system."""
7
8
9import argparse
10import imp
11import os
12import pprint
13import sys
14
15# Disable lint check for finding modules:
16# pylint: disable=F0401
17
18def _GetDirAbove(dirname):
19  """Returns the directory "above" this file containing |dirname| (which must
20  also be "above" this file)."""
21  path = os.path.abspath(__file__)
22  while True:
23    path, tail = os.path.split(path)
24    assert tail
25    if tail == dirname:
26      return path
27
28# Manually check for the command-line flag. (This isn't quite right, since it
29# ignores, e.g., "--", but it's close enough.)
30if "--use_chromium_bundled_pylibs" in sys.argv[1:]:
31  sys.path.insert(0, os.path.join(_GetDirAbove("mojo"), "third_party"))
32
33sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
34                                "pylib"))
35
36from mojom.error import Error
37from mojom.generate.data import OrderedModuleFromData
38from mojom.parse.parser import Parse
39from mojom.parse.translate import Translate
40
41
42def LoadGenerators(generators_string):
43  if not generators_string:
44    return []  # No generators.
45
46  script_dir = os.path.dirname(os.path.abspath(__file__))
47  generators = []
48  for generator_name in [s.strip() for s in generators_string.split(",")]:
49    # "Built-in" generators:
50    if generator_name.lower() == "c++":
51      generator_name = os.path.join(script_dir, "generators",
52                                    "mojom_cpp_generator.py")
53    elif generator_name.lower() == "javascript":
54      generator_name = os.path.join(script_dir, "generators",
55                                    "mojom_js_generator.py")
56    elif generator_name.lower() == "java":
57      generator_name = os.path.join(script_dir, "generators",
58                                    "mojom_java_generator.py")
59    elif generator_name.lower() == "python":
60      generator_name = os.path.join(script_dir, "generators",
61                                    "mojom_python_generator.py")
62    # Specified generator python module:
63    elif generator_name.endswith(".py"):
64      pass
65    else:
66      print "Unknown generator name %s" % generator_name
67      sys.exit(1)
68    generator_module = imp.load_source(os.path.basename(generator_name)[:-3],
69                                       generator_name)
70    generators.append(generator_module)
71  return generators
72
73
74def MakeImportStackMessage(imported_filename_stack):
75  """Make a (human-readable) message listing a chain of imports. (Returned
76  string begins with a newline (if nonempty) and does not end with one.)"""
77  return ''.join(
78      reversed(["\n  %s was imported by %s" % (a, b) for (a, b) in \
79                    zip(imported_filename_stack[1:], imported_filename_stack)]))
80
81
82def FindImportFile(dir_name, file_name, search_dirs):
83  for search_dir in [dir_name] + search_dirs:
84    path = os.path.join(search_dir, file_name)
85    if os.path.isfile(path):
86      return path
87  return os.path.join(dir_name, file_name)
88
89
90# Disable check for dangerous default arguments (they're "private" keyword
91# arguments; note that we want |_processed_files| to memoize across invocations
92# of |ProcessFile()|):
93# pylint: disable=W0102
94def ProcessFile(args, remaining_args, generator_modules, filename,
95                _processed_files={}, _imported_filename_stack=None):
96  # Memoized results.
97  if filename in _processed_files:
98    return _processed_files[filename]
99
100  if _imported_filename_stack is None:
101    _imported_filename_stack = []
102
103  # Ensure we only visit each file once.
104  if filename in _imported_filename_stack:
105    print "%s: Error: Circular dependency" % filename + \
106        MakeImportStackMessage(_imported_filename_stack + [filename])
107    sys.exit(1)
108
109  try:
110    with open(filename) as f:
111      source = f.read()
112  except IOError as e:
113    print "%s: Error: %s" % (e.filename, e.strerror) + \
114        MakeImportStackMessage(_imported_filename_stack + [filename])
115    sys.exit(1)
116
117  try:
118    tree = Parse(source, filename)
119  except Error as e:
120    print str(e) + MakeImportStackMessage(_imported_filename_stack + [filename])
121    sys.exit(1)
122
123  dirname, name = os.path.split(filename)
124  mojom = Translate(tree, name)
125  if args.debug_print_intermediate:
126    pprint.PrettyPrinter().pprint(mojom)
127
128  # Process all our imports first and collect the module object for each.
129  # We use these to generate proper type info.
130  for import_data in mojom['imports']:
131    import_filename = FindImportFile(dirname,
132                                     import_data['filename'],
133                                     args.import_directories)
134    import_data['module'] = ProcessFile(
135        args, remaining_args, generator_modules, import_filename,
136        _processed_files=_processed_files,
137        _imported_filename_stack=_imported_filename_stack + [filename])
138
139  module = OrderedModuleFromData(mojom)
140
141  # Set the path as relative to the source root.
142  module.path = os.path.relpath(os.path.abspath(filename),
143                                os.path.abspath(args.depth))
144
145  # Normalize to unix-style path here to keep the generators simpler.
146  module.path = module.path.replace('\\', '/')
147
148  for generator_module in generator_modules:
149    generator = generator_module.Generator(module, args.output_dir)
150    filtered_args = []
151    if hasattr(generator_module, 'GENERATOR_PREFIX'):
152      prefix = '--' + generator_module.GENERATOR_PREFIX + '_'
153      filtered_args = [arg for arg in remaining_args if arg.startswith(prefix)]
154    generator.GenerateFiles(filtered_args)
155
156  # Save result.
157  _processed_files[filename] = module
158  return module
159# pylint: enable=W0102
160
161
162def main():
163  parser = argparse.ArgumentParser(
164      description="Generate bindings from mojom files.")
165  parser.add_argument("filename", nargs="+",
166                      help="mojom input file")
167  parser.add_argument("-d", "--depth", dest="depth", default=".",
168                      help="depth from source root")
169  parser.add_argument("-o", "--output_dir", dest="output_dir", default=".",
170                      help="output directory for generated files")
171  parser.add_argument("-g", "--generators", dest="generators_string",
172                      metavar="GENERATORS",
173                      default="c++,javascript,java,python",
174                      help="comma-separated list of generators")
175  parser.add_argument("--debug_print_intermediate", action="store_true",
176                      help="print the intermediate representation")
177  parser.add_argument("-I", dest="import_directories", action="append",
178                      metavar="directory", default=[],
179                      help="add a directory to be searched for import files")
180  parser.add_argument("--use_chromium_bundled_pylibs", action="store_true",
181                      help="use Python modules bundled in the Chromium source")
182  (args, remaining_args) = parser.parse_known_args()
183
184  generator_modules = LoadGenerators(args.generators_string)
185
186  if not os.path.exists(args.output_dir):
187    os.makedirs(args.output_dir)
188
189  for filename in args.filename:
190    ProcessFile(args, remaining_args, generator_modules, filename)
191
192  return 0
193
194
195if __name__ == "__main__":
196  sys.exit(main())
197