github.com/grumpyhome/grumpy@v0.3.1-0.20201208125205-7b775405bdf1/grumpy-tools-src/grumpy_tools/grumpc.py (about)

     1  #!/usr/bin/env python
     2  # coding=utf-8
     3  
     4  # Copyright 2016 Google Inc. All Rights Reserved.
     5  #
     6  # Licensed under the Apache License, Version 2.0 (the "License");
     7  # you may not use this file except in compliance with the License.
     8  # You may obtain a copy of the License at
     9  #
    10  #     http://www.apache.org/licenses/LICENSE-2.0
    11  #
    12  # Unless required by applicable law or agreed to in writing, software
    13  # distributed under the License is distributed on an "AS IS" BASIS,
    14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  # See the License for the specific language governing permissions and
    16  # limitations under the License.
    17  
    18  """A Python -> Go transcompiler."""
    19  
    20  from __future__ import unicode_literals
    21  
    22  import argparse
    23  import os
    24  import sys
    25  from StringIO import StringIO
    26  import textwrap
    27  import pickle
    28  import logging
    29  
    30  import dill
    31  
    32  from .compiler import block
    33  from .compiler import imputil
    34  from .compiler import stmt
    35  from .compiler import util
    36  from .compiler.parser import patch_pythonparser
    37  import pythonparser
    38  from .pep_support.pep3147pycache import make_transpiled_module_folders, should_refresh, set_checksum, fixed_keyword
    39  from . import pydeps
    40  
    41  logger = logging.getLogger(__name__)
    42  
    43  
    44  def _parse_and_visit(stream, script, modname):
    45    patch_pythonparser()
    46    gopath = os.environ['GOPATH']
    47  
    48    stream.seek(0)
    49    py_contents = stream.read()
    50    mod = pythonparser.parse(py_contents)
    51  
    52    # Do a pass for compiler directives from `from __future__ import *` statements
    53    future_node, future_features = imputil.parse_future_features(mod)
    54  
    55    importer = imputil.Importer(gopath, modname, script,
    56                                future_features.absolute_import)
    57    full_package_name = modname.replace('.', '/')
    58    mod_block = block.ModuleBlock(importer, full_package_name, script,
    59                                  py_contents, future_features)
    60  
    61    visitor = stmt.StatementVisitor(mod_block, future_node)
    62    # Indent so that the module body is aligned with the goto labels.
    63    with visitor.writer.indent_block():
    64      visitor.visit(mod)
    65    return visitor, mod_block
    66  
    67  
    68  def _collect_deps(script, modname, pep3147_folders, from_cache=False, update_cache=True):
    69    if from_cache:
    70      try:
    71        with open(pep3147_folders['dependencies_file']) as deps_dumpfile:
    72          deps, import_objects = pickle.load(deps_dumpfile)
    73        return deps, import_objects
    74      except Exception as err:
    75        # Race conditions with other scripts running or stale/broken dump
    76        logger.info("Could not load dependencies of '%s' from cache.", modname)
    77  
    78    if os.path.exists(script):
    79      deps, import_objects = pydeps.main(script, modname, with_imports=True) #, script, gopath)
    80    elif os.path.exists(os.path.join(pep3147_folders['cache_folder'], os.path.basename(script))):
    81      deps, import_objects = pydeps.main(
    82        os.path.join(pep3147_folders['cache_folder'], os.path.basename(script)),
    83        modname,
    84        package_dir=os.path.dirname(script),
    85        with_imports=True,
    86      )
    87    else:
    88      raise NotImplementedError()
    89  
    90    deps = set(deps).difference(_get_parent_packages(modname))
    91  
    92    if update_cache:
    93      try:
    94        with open(pep3147_folders['dependencies_file'], 'wb') as deps_dumpfile:
    95          pickle.dump((deps, import_objects), deps_dumpfile)
    96      except Exception as err:
    97        logger.warning("Could not store dependencies of '%s' on cache: %s", modname, err)
    98      else:
    99        logger.debug("Dependencies file regenerated")
   100    return deps, import_objects
   101  
   102  
   103  def _recursively_transpile(import_objects, ignore=None):
   104    ignore = ignore or set()
   105    for imp_obj in import_objects:
   106      if not imp_obj.is_native:
   107        name = imp_obj.name[1:] if imp_obj.name.startswith('.') else imp_obj.name
   108  
   109        if imp_obj.name in ignore:
   110          # logger.debug("Already collected '%s'. Ignoring", imp_obj.name)
   111          continue  # Do not do cyclic imports
   112  
   113        if not imp_obj.script:
   114          logger.debug("Importing '%s' will raise ImportError", imp_obj.name)
   115          ignore.add(imp_obj.name)
   116          continue  # Let the ImportError raise on run time
   117  
   118        # Recursively compile the discovered imports
   119        result = main(stream=open(imp_obj.script), modname=name, pep3147=True,
   120                      recursive=True, return_gocode=False, return_deps=True,
   121                      ignore=ignore)
   122        if name.endswith('.__init__'):
   123          name = name.rpartition('.__init__')[0]
   124          result = main(stream=open(imp_obj.script), modname=name, pep3147=True,
   125                        recursive=True, return_gocode=False, return_deps=True,
   126                        ignore=ignore)
   127        yield result['deps']
   128  
   129  
   130  def _transpile(script, modname, imports, visitor, mod_block):
   131    file_buffer = StringIO()
   132    writer = util.Writer(file_buffer)
   133    tmpl = textwrap.dedent("""\
   134        package $package
   135        import (
   136        \tπg "grumpy"
   137        $imports
   138        )
   139        var Code *πg.Code
   140        func init() {
   141        \tCode = πg.NewCode("<module>", $script, nil, 0, func(πF *πg.Frame, _ []*πg.Object) (*πg.Object, *πg.BaseException) {
   142        \t\tvar πR *πg.Object; _ = πR
   143        \t\tvar πE *πg.BaseException; _ = πE""")
   144    writer.write_tmpl(tmpl, package=fixed_keyword(modname.split('.')[-1]),
   145                      script=util.go_str(script), imports=imports)
   146    with writer.indent_block(2):
   147      for s in sorted(mod_block.strings):
   148        writer.write('ß{} := πg.InternStr({})'.format(s, util.go_str(s)))
   149      writer.write_temp_decls(mod_block)
   150      writer.write_block(mod_block, visitor.writer.getvalue())
   151    writer.write_tmpl(textwrap.dedent("""\
   152      \t\treturn nil, πE
   153      \t})
   154      \tπg.RegisterModule($modname, Code)
   155      }"""), modname=util.go_str(modname))
   156    return file_buffer
   157  
   158  
   159  def main(stream=None, modname=None, pep3147=False, recursive=False, return_gocode=True, ignore=None, return_deps=False):
   160    ignore = ignore or set()
   161    ignore.add(modname)
   162    script = os.path.abspath(stream.name)
   163    assert script and modname, 'Script "%s" or Modname "%s" is empty' % (script, modname)
   164  
   165    gopath = os.getenv('GOPATH', None)
   166    if not gopath:
   167      raise RuntimeError('GOPATH not set')
   168  
   169    pep3147_folders = make_transpiled_module_folders(script, modname)
   170    will_refresh = should_refresh(stream, script, modname)
   171  
   172    deps, import_objects = _collect_deps(script, modname, pep3147_folders, from_cache=(not will_refresh))
   173    deps = set(deps)
   174    imports = ''.join('\t// _ "' + _package_name(name) + '"\n' for name in deps)
   175  
   176    if will_refresh or return_gocode:
   177      visitor, mod_block = _parse_and_visit(stream, script, modname)
   178      file_buffer = _transpile(script, modname, imports, visitor, mod_block)
   179    else:
   180      file_buffer = None
   181  
   182    if recursive:
   183      transitive_deps = _recursively_transpile(import_objects, ignore=ignore)
   184  
   185    if pep3147:
   186      new_gopath = pep3147_folders['gopath_folder']
   187      if new_gopath not in os.environ['GOPATH'].split(os.pathsep):
   188        os.environ['GOPATH'] += os.pathsep + new_gopath
   189  
   190      if file_buffer:
   191        file_buffer.seek(0)
   192        mod_dir = pep3147_folders['transpiled_module_folder']
   193        with open(os.path.join(mod_dir, 'module.go'), 'w+') as transpiled_file:
   194          transpiled_file.write(file_buffer.read())
   195        set_checksum(stream, script, modname)
   196  
   197    result = {}
   198    if return_gocode:
   199      assert file_buffer, "Wrong logic paths. 'file_buffer' should be available here!"
   200      file_buffer.seek(0)
   201      result['gocode'] = file_buffer.read()
   202    if return_deps:
   203      result['deps'] = frozenset(deps.union(*transitive_deps))
   204    return result
   205  
   206  
   207  def _package_name(modname):
   208    if modname.startswith('__go__/'):
   209      return '__python__/' + modname
   210    return '__python__/' + fixed_keyword(modname).replace('.', '/')
   211  
   212  
   213  def _get_parent_packages(modname):
   214    package_parts = modname.split('.')
   215    parent_parts = package_parts[:-1]
   216    for i, _ in enumerate(parent_parts):
   217      yield '.'.join(parent_parts[:(-i or None)])