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)])