Update mojo sdk to rev 59145288bae55b0fce4276b017df6a1117bcf00f
[chromium-blink-merge.git] / mojo / public / tools / bindings / mojom_bindings_generator.py
blob2d1802c05ac6ec723c86f8f6a312a03b97efcd98
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.
6 """The frontend for the Mojo bindings system."""
9 import argparse
10 import imp
11 import os
12 import pprint
13 import sys
15 # Disable lint check for finding modules:
16 # pylint: disable=F0401
18 def _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
28 # Manually check for the command-line flag. (This isn't quite right, since it
29 # ignores, e.g., "--", but it's close enough.)
30 if "--use_bundled_pylibs" in sys.argv[1:]:
31 sys.path.insert(0, os.path.join(_GetDirAbove("public"), "public/third_party"))
33 sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
34 "pylib"))
36 from mojom.error import Error
37 from mojom.generate.data import OrderedModuleFromData
38 from mojom.parse.parser import Parse
39 from mojom.parse.translate import Translate
42 def LoadGenerators(generators_string):
43 if not generators_string:
44 return [] # No generators.
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 if generator_name.lower() == "dart":
54 generator_name = os.path.join(script_dir, "generators",
55 "mojom_dart_generator.py")
56 elif generator_name.lower() == "javascript":
57 generator_name = os.path.join(script_dir, "generators",
58 "mojom_js_generator.py")
59 elif generator_name.lower() == "java":
60 generator_name = os.path.join(script_dir, "generators",
61 "mojom_java_generator.py")
62 elif generator_name.lower() == "python":
63 generator_name = os.path.join(script_dir, "generators",
64 "mojom_python_generator.py")
65 # Specified generator python module:
66 elif generator_name.endswith(".py"):
67 pass
68 else:
69 print "Unknown generator name %s" % generator_name
70 sys.exit(1)
71 generator_module = imp.load_source(os.path.basename(generator_name)[:-3],
72 generator_name)
73 generators.append(generator_module)
74 return generators
77 def MakeImportStackMessage(imported_filename_stack):
78 """Make a (human-readable) message listing a chain of imports. (Returned
79 string begins with a newline (if nonempty) and does not end with one.)"""
80 return ''.join(
81 reversed(["\n %s was imported by %s" % (a, b) for (a, b) in \
82 zip(imported_filename_stack[1:], imported_filename_stack)]))
85 def FindImportFile(dir_name, file_name, search_dirs):
86 for search_dir in [dir_name] + search_dirs:
87 path = os.path.join(search_dir, file_name)
88 if os.path.isfile(path):
89 return path
90 return os.path.join(dir_name, file_name)
92 class MojomProcessor(object):
93 def __init__(self, should_generate):
94 self._should_generate = should_generate
95 self._processed_files = {}
96 self._parsed_files = {}
98 def ProcessFile(self, args, remaining_args, generator_modules, filename):
99 self._ParseFileAndImports(filename, args.import_directories, [])
101 return self._GenerateModule(args, remaining_args, generator_modules,
102 filename)
104 def _GenerateModule(self, args, remaining_args, generator_modules, filename):
105 # Return the already-generated module.
106 if filename in self._processed_files:
107 return self._processed_files[filename]
108 tree = self._parsed_files[filename]
110 dirname, name = os.path.split(filename)
111 mojom = Translate(tree, name)
112 if args.debug_print_intermediate:
113 pprint.PrettyPrinter().pprint(mojom)
115 # Process all our imports first and collect the module object for each.
116 # We use these to generate proper type info.
117 for import_data in mojom['imports']:
118 import_filename = FindImportFile(dirname,
119 import_data['filename'],
120 args.import_directories)
121 import_data['module'] = self._GenerateModule(
122 args, remaining_args, generator_modules, import_filename)
124 module = OrderedModuleFromData(mojom)
126 # Set the path as relative to the source root.
127 module.path = os.path.relpath(os.path.abspath(filename),
128 os.path.abspath(args.depth))
130 # Normalize to unix-style path here to keep the generators simpler.
131 module.path = module.path.replace('\\', '/')
133 if self._should_generate(filename):
134 for generator_module in generator_modules:
135 generator = generator_module.Generator(module, args.output_dir)
136 filtered_args = []
137 if hasattr(generator_module, 'GENERATOR_PREFIX'):
138 prefix = '--' + generator_module.GENERATOR_PREFIX + '_'
139 filtered_args = [arg for arg in remaining_args
140 if arg.startswith(prefix)]
141 generator.GenerateFiles(filtered_args)
143 # Save result.
144 self._processed_files[filename] = module
145 return module
147 def _ParseFileAndImports(self, filename, import_directories,
148 imported_filename_stack):
149 # Ignore already-parsed files.
150 if filename in self._parsed_files:
151 return
153 if filename in imported_filename_stack:
154 print "%s: Error: Circular dependency" % filename + \
155 MakeImportStackMessage(imported_filename_stack + [filename])
156 sys.exit(1)
158 try:
159 with open(filename) as f:
160 source = f.read()
161 except IOError as e:
162 print "%s: Error: %s" % (e.filename, e.strerror) + \
163 MakeImportStackMessage(imported_filename_stack + [filename])
164 sys.exit(1)
166 try:
167 tree = Parse(source, filename)
168 except Error as e:
169 full_stack = imported_filename_stack + [filename]
170 print str(e) + MakeImportStackMessage(full_stack)
171 sys.exit(1)
173 dirname = os.path.split(filename)[0]
174 for imp_entry in tree.import_list:
175 import_filename = FindImportFile(dirname,
176 imp_entry.import_filename, import_directories)
177 self._ParseFileAndImports(import_filename, import_directories,
178 imported_filename_stack + [filename])
180 self._parsed_files[filename] = tree
183 def main():
184 parser = argparse.ArgumentParser(
185 description="Generate bindings from mojom files.")
186 parser.add_argument("filename", nargs="+",
187 help="mojom input file")
188 parser.add_argument("-d", "--depth", dest="depth", default=".",
189 help="depth from source root")
190 parser.add_argument("-o", "--output_dir", dest="output_dir", default=".",
191 help="output directory for generated files")
192 parser.add_argument("-g", "--generators", dest="generators_string",
193 metavar="GENERATORS",
194 default="c++,dart,javascript,java,python",
195 help="comma-separated list of generators")
196 parser.add_argument("--debug_print_intermediate", action="store_true",
197 help="print the intermediate representation")
198 parser.add_argument("-I", dest="import_directories", action="append",
199 metavar="directory", default=[],
200 help="add a directory to be searched for import files")
201 parser.add_argument("--use_bundled_pylibs", action="store_true",
202 help="use Python modules bundled in the SDK")
203 (args, remaining_args) = parser.parse_known_args()
205 generator_modules = LoadGenerators(args.generators_string)
207 if not os.path.exists(args.output_dir):
208 os.makedirs(args.output_dir)
210 processor = MojomProcessor(lambda filename: filename in args.filename)
211 for filename in args.filename:
212 processor.ProcessFile(args, remaining_args, generator_modules, filename)
214 return 0
217 if __name__ == "__main__":
218 sys.exit(main())