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