1'''This file dynamically builds the proto definitions for Python.''' 2from __future__ import absolute_import 3 4import os 5import os.path 6import sys 7import tempfile 8import shutil 9import traceback 10from tempfile import TemporaryDirectory 11from ._utils import has_grpcio_protoc, invoke_protoc, print_versions 12 13def build_nanopb_proto(protosrc, dirname): 14 '''Try to build a .proto file for python-protobuf. 15 Returns True if successful. 16 ''' 17 18 cmd = [ 19 "protoc", 20 "--python_out={}".format(dirname), 21 protosrc, 22 "-I={}".format(dirname), 23 ] 24 25 if has_grpcio_protoc(): 26 # grpcio-tools has an extra CLI argument 27 # from grpc.tools.protoc __main__ invocation. 28 cmd.append("-I={}".format(_utils.get_grpc_tools_proto_path())) 29 30 try: 31 invoke_protoc(argv=cmd) 32 except: 33 sys.stderr.write("Failed to build nanopb_pb2.py: " + ' '.join(cmd) + "\n") 34 sys.stderr.write(traceback.format_exc() + "\n") 35 return False 36 37 return True 38 39def load_nanopb_pb2(): 40 # To work, the generator needs python-protobuf built version of nanopb.proto. 41 # There are three methods to provide this: 42 # 43 # 1) Load a previously generated generator/proto/nanopb_pb2.py 44 # 2) Use protoc to build it and store it permanently generator/proto/nanopb_pb2.py 45 # 3) Use protoc to build it, but store only temporarily in system-wide temp folder 46 # 47 # By default these are tried in numeric order. 48 # If NANOPB_PB2_TEMP_DIR environment variable is defined, the 2) is skipped. 49 # If the value of the $NANOPB_PB2_TEMP_DIR exists as a directory, it is used instead 50 # of system temp folder. 51 52 tmpdir = os.getenv("NANOPB_PB2_TEMP_DIR") 53 temporary_only = (tmpdir is not None) 54 dirname = os.path.dirname(__file__) 55 protosrc = os.path.join(dirname, "nanopb.proto") 56 protodst = os.path.join(dirname, "nanopb_pb2.py") 57 58 if tmpdir is not None and not os.path.isdir(tmpdir): 59 tmpdir = None # Use system-wide temp dir 60 61 no_rebuild = bool(int(os.getenv("NANOPB_PB2_NO_REBUILD", default = 0))) 62 if bool(no_rebuild): 63 # Don't attempt to autogenerate nanopb_pb2.py, external build rules 64 # should have already done so. 65 import nanopb_pb2 as nanopb_pb2_mod 66 return nanopb_pb2_mod 67 68 if os.path.isfile(protosrc): 69 src_date = os.path.getmtime(protosrc) 70 if os.path.isfile(protodst) and os.path.getmtime(protodst) >= src_date: 71 try: 72 from . import nanopb_pb2 as nanopb_pb2_mod 73 return nanopb_pb2_mod 74 except Exception as e: 75 sys.stderr.write("Failed to import nanopb_pb2.py: " + str(e) + "\n" 76 "Will automatically attempt to rebuild this.\n" 77 "Verify that python-protobuf and protoc versions match.\n") 78 print_versions() 79 80 # Try to rebuild into generator/proto directory 81 if not temporary_only: 82 build_nanopb_proto(protosrc, dirname) 83 84 try: 85 from . import nanopb_pb2 as nanopb_pb2_mod 86 return nanopb_pb2_mod 87 except: 88 sys.stderr.write("Failed to import generator/proto/nanopb_pb2.py:\n") 89 sys.stderr.write(traceback.format_exc() + "\n") 90 91 # Try to rebuild into temporary directory 92 with TemporaryDirectory(prefix = 'nanopb-', dir = tmpdir) as protodir: 93 build_nanopb_proto(protosrc, protodir) 94 95 if protodir not in sys.path: 96 sys.path.insert(0, protodir) 97 98 try: 99 import nanopb_pb2 as nanopb_pb2_mod 100 return nanopb_pb2_mod 101 except: 102 sys.stderr.write("Failed to import %s/nanopb_pb2.py:\n" % protodir) 103 sys.stderr.write(traceback.format_exc() + "\n") 104 105 # If everything fails 106 sys.stderr.write("\n\nGenerating nanopb_pb2.py failed.\n") 107 sys.stderr.write("Make sure that a protoc generator is available and matches python-protobuf version.\n") 108 print_versions() 109 sys.exit(1) 110 111