pebble/third_party/nanopb/tests/site_scons/site_tools/nanopb.py

205 lines
6.8 KiB
Python
Raw Permalink Normal View History

'''
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)