1'''
2Scons Builder for nanopb .proto definitions.
3
4This tool will locate the nanopb generator and use it to generate .pb.c and
5.pb.h files from the .proto files.
6
7Basic example
8-------------
9# Build myproto.pb.c and myproto.pb.h from myproto.proto
10myproto = env.NanopbProto("myproto")
11
12# Link nanopb core to the program
13env.Append(CPPPATH = "$NANOB")
14myprog = env.Program(["myprog.c", myproto, "$NANOPB/pb_encode.c", "$NANOPB/pb_decode.c"])
15
16Configuration options
17---------------------
18Normally, this script is used in the test environment of nanopb and it locates
19the nanopb generator by a relative path. If this script is used in another
20application, the path to nanopb root directory has to be defined:
21
22env.SetDefault(NANOPB = "path/to/nanopb")
23
24Additionally, the path to protoc and the options to give to protoc can be
25defined manually:
26
27env.SetDefault(PROTOC = "path/to/protoc")
28env.SetDefault(PROTOCFLAGS = "--plugin=protoc-gen-nanopb=path/to/protoc-gen-nanopb")
29'''
30
31import SCons.Action
32import SCons.Builder
33import SCons.Util
34import os.path
35
36class NanopbWarning(SCons.Warnings.Warning):
37    pass
38SCons.Warnings.enableWarningClass(NanopbWarning)
39
40def _detect_nanopb(env):
41    '''Find the path to nanopb root directory.'''
42    if env.has_key('NANOPB'):
43        # Use nanopb dir given by user
44        return env['NANOPB']
45
46    p = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
47    if os.path.isdir(p) and os.path.isfile(os.path.join(p, 'pb.h')):
48        # Assume we are running under tests/site_scons/site_tools
49        return p
50
51    raise SCons.Errors.StopError(NanopbWarning,
52        "Could not find the nanopb root directory")
53
54def _detect_protoc(env):
55    '''Find the path to the protoc compiler.'''
56    if env.has_key('PROTOC'):
57        # Use protoc defined by user
58        return env['PROTOC']
59
60    n = _detect_nanopb(env)
61    p1 = os.path.join(n, 'generator-bin', 'protoc' + env['PROGSUFFIX'])
62    if os.path.exists(p1):
63        # Use protoc bundled with binary package
64        return env['ESCAPE'](p1)
65
66    p = env.WhereIs('protoc')
67    if p:
68        # Use protoc from path
69        return env['ESCAPE'](p)
70
71    raise SCons.Errors.StopError(NanopbWarning,
72        "Could not find the protoc compiler")
73
74def _detect_protocflags(env):
75    '''Find the options to use for protoc.'''
76    if env.has_key('PROTOCFLAGS'):
77        return env['PROTOCFLAGS']
78
79    p = _detect_protoc(env)
80    n = _detect_nanopb(env)
81    p1 = os.path.join(n, 'generator-bin', 'protoc' + env['PROGSUFFIX'])
82    if p == env['ESCAPE'](p1):
83        # Using the bundled protoc, no options needed
84        return ''
85
86    e = env['ESCAPE']
87    if env['PLATFORM'] == 'win32':
88        return e('--plugin=protoc-gen-nanopb=' + os.path.join(n, 'generator', 'protoc-gen-nanopb.bat'))
89    else:
90        return e('--plugin=protoc-gen-nanopb=' + os.path.join(n, 'generator', 'protoc-gen-nanopb'))
91
92def _nanopb_proto_actions(source, target, env, for_signature):
93    esc = env['ESCAPE']
94    dirs = ' '.join(['-I' + esc(env.GetBuildPath(d)) for d in env['PROTOCPATH']])
95    return '$PROTOC $PROTOCFLAGS %s --nanopb_out=. %s' % (dirs, esc(str(source[0])))
96
97def _nanopb_proto_emitter(target, source, env):
98    basename = os.path.splitext(str(source[0]))[0]
99    target.append(basename + '.pb.h')
100
101    if os.path.exists(basename + '.options'):
102        source.append(basename + '.options')
103
104    return target, source
105
106_nanopb_proto_builder = SCons.Builder.Builder(
107    generator = _nanopb_proto_actions,
108    suffix = '.pb.c',
109    src_suffix = '.proto',
110    emitter = _nanopb_proto_emitter)
111
112def generate(env):
113    '''Add Builder for nanopb protos.'''
114
115    env['NANOPB'] = _detect_nanopb(env)
116    env['PROTOC'] = _detect_protoc(env)
117    env['PROTOCFLAGS'] = _detect_protocflags(env)
118
119    env.SetDefault(PROTOCPATH = ['.', os.path.join(env['NANOPB'], 'generator', 'proto')])
120
121    env.SetDefault(NANOPB_PROTO_CMD = '$PROTOC $PROTOCFLAGS --nanopb_out=. $SOURCES')
122    env['BUILDERS']['NanopbProto'] = _nanopb_proto_builder
123
124def exists(env):
125    return _detect_protoc(env) and _detect_protoc_opts(env)
126
127