Home | History | Annotate | Download | only in bindings
      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 
      9 import argparse
     10 import imp
     11 import os
     12 import pprint
     13 import sys
     14 
     15 # Disable lint check for finding modules:
     16 # pylint: disable=F0401
     17 
     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
     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.)
     30 if "--use_chromium_bundled_pylibs" in sys.argv[1:]:
     31   sys.path.insert(0, os.path.join(_GetDirAbove("mojo"), "third_party"))
     32 
     33 sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
     34                                 "pylib"))
     35 
     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
     40 
     41 
     42 def 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 
     74 def 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 
     82 def 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
     94 def 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 
    162 def 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 
    195 if __name__ == "__main__":
    196   sys.exit(main())
    197