| ''' |
| 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 |
| from SCons.Script import Dir, File |
| import os.path |
| import platform |
| |
| try: |
| warningbase = SCons.Warnings.SConsWarning |
| except AttributeError: |
| warningbase = SCons.Warnings.Warning |
| |
| class NanopbWarning(warningbase): |
| pass |
| SCons.Warnings.enableWarningClass(NanopbWarning) |
| |
| def _detect_nanopb(env): |
| '''Find the path to nanopb root directory.''' |
| if 'NANOPB' in env: |
| # 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_python(env): |
| '''Find Python executable to use.''' |
| if 'PYTHON' in env: |
| return env['PYTHON'] |
| |
| p = env.WhereIs('python3') |
| if p: |
| return env['ESCAPE'](p) |
| |
| p = env.WhereIs('py.exe') |
| if p: |
| return env['ESCAPE'](p) + " -3" |
| |
| return env['ESCAPE'](sys.executable) |
| |
| def _detect_nanopb_generator(env): |
| '''Return command for running nanopb_generator.py''' |
| generator_cmd = os.path.join(env['NANOPB'], 'generator-bin', 'nanopb_generator' + env['PROGSUFFIX']) |
| if os.path.exists(generator_cmd): |
| # Binary package |
| return env['ESCAPE'](generator_cmd) |
| else: |
| # Source package |
| return env['PYTHON'] + " " + env['ESCAPE'](os.path.join(env['NANOPB'], 'generator', 'nanopb_generator.py')) |
| |
| def _detect_protoc(env): |
| '''Find the path to the protoc compiler.''' |
| if 'PROTOC' in env: |
| # 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 = os.path.join(n, 'generator', 'protoc') |
| if os.path.exists(p): |
| # Use the grcpio-tools protoc wrapper |
| if env['PLATFORM'] == 'win32': |
| return env['ESCAPE'](p + '.bat') |
| else: |
| return env['ESCAPE'](p) |
| |
| 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 'PROTOCFLAGS' in env: |
| return env['PROTOCFLAGS'] |
| |
| p = _detect_protoc(env) |
| n = _detect_nanopb(env) |
| p1 = os.path.join(n, 'generator', 'protoc' + env['PROGSUFFIX']) |
| p2 = os.path.join(n, 'generator-bin', 'protoc' + env['PROGSUFFIX']) |
| if p in [env['ESCAPE'](p1), env['ESCAPE'](p2)]: |
| # 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'] |
| |
| # Make protoc build inside the SConscript directory |
| prefix = os.path.dirname(str(source[-1])) |
| srcfile = esc(os.path.relpath(str(source[0]), prefix)) |
| include_dirs = '-I.' |
| for d in env['PROTOCPATH']: |
| d = env.GetBuildPath(d) |
| if not os.path.isabs(d): d = os.path.relpath(d, prefix) |
| include_dirs += ' -I' + esc(d) |
| |
| # when generating .pb.cpp sources, instead of pb.h generate .pb.hpp headers |
| source_extension = os.path.splitext(str(target[0]))[1] |
| header_extension = '.h' + source_extension[2:] |
| nanopb_flags = env['NANOPBFLAGS'] |
| if nanopb_flags: |
| nanopb_flags = '--source-extension=%s,--header-extension=%s,%s:.' % (source_extension, header_extension, nanopb_flags) |
| else: |
| nanopb_flags = '--source-extension=%s,--header-extension=%s:.' % (source_extension, header_extension) |
| |
| return SCons.Action.CommandAction('$PROTOC $PROTOCFLAGS %s --nanopb_out=%s %s' % (include_dirs, nanopb_flags, srcfile), |
| chdir = prefix) |
| |
| def _nanopb_proto_emitter(target, source, env): |
| basename = os.path.splitext(str(source[0]))[0] |
| source_extension = os.path.splitext(str(target[0]))[1] |
| header_extension = '.h' + source_extension[2:] |
| target.append(basename + '.pb' + header_extension) |
| |
| # This is a bit of a hack. protoc include paths work the sanest |
| # when the working directory is the same as the source root directory. |
| # To get that directory in _nanopb_proto_actions, we add SConscript to |
| # the list of source files. |
| source.append(File("SConscript")) |
| |
| 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) |
| |
| _nanopb_proto_cpp_builder = SCons.Builder.Builder( |
| generator = _nanopb_proto_actions, |
| suffix = '.pb.cpp', |
| 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['PYTHON'] = _detect_python(env) |
| env['NANOPB_GENERATOR'] = _detect_nanopb_generator(env) |
| env.SetDefault(NANOPBFLAGS = '') |
| |
| env.SetDefault(PROTOCPATH = [".", os.path.join(env['NANOPB'], 'generator', 'proto')]) |
| |
| env.SetDefault(NANOPB_PROTO_CMD = '$PROTOC $PROTOCFLAGS --nanopb_out=$NANOPBFLAGS:. $SOURCES') |
| env['BUILDERS']['NanopbProto'] = _nanopb_proto_builder |
| env['BUILDERS']['NanopbProtoCpp'] = _nanopb_proto_cpp_builder |
| |
| def exists(env): |
| return _detect_protoc(env) and _detect_protoc_opts(env) |
| |