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