''' Scons Builder for nanopb .proto definitions. This tool will locate the nanopb generator and use it to generate .pb.c and .pb.h files from the .proto files. Basic example ------------- # Build myproto.pb.c and myproto.pb.h from myproto.proto myproto = env.NanopbProto("myproto") # Link nanopb core to the program env.Append(CPPPATH = "$NANOB") myprog = env.Program(["myprog.c", myproto, "$NANOPB/pb_encode.c", "$NANOPB/pb_decode.c"]) Configuration options --------------------- Normally, this script is used in the test environment of nanopb and it locates the nanopb generator by a relative path. If this script is used in another application, the path to nanopb root directory has to be defined: env.SetDefault(NANOPB = "path/to/nanopb") Additionally, the path to protoc and the options to give to protoc can be defined manually: env.SetDefault(PROTOC = "path/to/protoc") env.SetDefault(PROTOCFLAGS = "--plugin=protoc-gen-nanopb=path/to/protoc-gen-nanopb") ''' import SCons.Action import SCons.Builder import SCons.Util import os.path class NanopbWarning(SCons.Warnings.Warning): pass SCons.Warnings.enableWarningClass(NanopbWarning) def _detect_nanopb(env): '''Find the path to nanopb root directory.''' if env.has_key('NANOPB'): # Use nanopb dir given by user return env['NANOPB'] p = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) if os.path.isdir(p) and os.path.isfile(os.path.join(p, 'pb.h')): # Assume we are running under tests/site_scons/site_tools return p raise SCons.Errors.StopError(NanopbWarning, "Could not find the nanopb root directory") def _detect_protoc(env): '''Find the path to the protoc compiler.''' if env.has_key('PROTOC'): # Use protoc defined by user return env['PROTOC'] n = _detect_nanopb(env) p1 = os.path.join(n, 'generator-bin', 'protoc' + env['PROGSUFFIX']) if os.path.exists(p1): # Use protoc bundled with binary package return env['ESCAPE'](p1) p = env.WhereIs('protoc') if p: # Use protoc from path return env['ESCAPE'](p) raise SCons.Errors.StopError(NanopbWarning, "Could not find the protoc compiler") def _detect_protocflags(env): '''Find the options to use for protoc.''' if env.has_key('PROTOCFLAGS'): return env['PROTOCFLAGS'] p = _detect_protoc(env) n = _detect_nanopb(env) p1 = os.path.join(n, 'generator-bin', 'protoc' + env['PROGSUFFIX']) if p == env['ESCAPE'](p1): # Using the bundled protoc, no options needed return '' e = env['ESCAPE'] if env['PLATFORM'] == 'win32': return e('--plugin=protoc-gen-nanopb=' + os.path.join(n, 'generator', 'protoc-gen-nanopb.bat')) else: return e('--plugin=protoc-gen-nanopb=' + os.path.join(n, 'generator', 'protoc-gen-nanopb')) def _nanopb_proto_actions(source, target, env, for_signature): esc = env['ESCAPE'] dirs = ' '.join(['-I' + esc(env.GetBuildPath(d)) for d in env['PROTOCPATH']]) return '$PROTOC $PROTOCFLAGS %s --nanopb_out=. %s' % (dirs, esc(str(source[0]))) def _nanopb_proto_emitter(target, source, env): basename = os.path.splitext(str(source[0]))[0] target.append(basename + '.pb.h') if os.path.exists(basename + '.options'): source.append(basename + '.options') return target, source _nanopb_proto_builder = SCons.Builder.Builder( generator = _nanopb_proto_actions, suffix = '.pb.c', src_suffix = '.proto', emitter = _nanopb_proto_emitter) def generate(env): '''Add Builder for nanopb protos.''' env['NANOPB'] = _detect_nanopb(env) env['PROTOC'] = _detect_protoc(env) env['PROTOCFLAGS'] = _detect_protocflags(env) env.SetDefault(PROTOCPATH = ['.', os.path.join(env['NANOPB'], 'generator', 'proto')]) env.SetDefault(NANOPB_PROTO_CMD = '$PROTOC $PROTOC_OPTS --nanopb_out=. $SOURCE') env['BUILDERS']['NanopbProto'] = _nanopb_proto_builder def exists(env): return _detect_protoc(env) and _detect_protoc_opts(env)