123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- #!/usr/bin/env python
- # encoding: utf-8
- # Philipp Bender, 2012
- # Matt Clarkson, 2012
- import re, os
- from waflib.Task import Task
- from waflib.TaskGen import extension
- from waflib import Errors, Context
- """
- A simple tool to integrate protocol buffers into your build system.
- Example for C++:
- def configure(conf):
- conf.load('compiler_cxx cxx protoc')
- def build(bld):
- bld(
- features = 'cxx cxxprogram'
- source = 'main.cpp file1.proto proto/file2.proto',
- includes = '. proto',
- target = 'executable')
- Example for Python:
- def configure(conf):
- conf.load('python protoc')
- def build(bld):
- bld(
- features = 'py'
- source = 'main.py file1.proto proto/file2.proto',
- protoc_includes = 'proto')
- Example for both Python and C++ at same time:
- def configure(conf):
- conf.load('cxx python protoc')
- def build(bld):
- bld(
- features = 'cxx py'
- source = 'file1.proto proto/file2.proto',
- protoc_includes = 'proto') # or includes
- Example for Java:
- def options(opt):
- opt.load('java')
- def configure(conf):
- conf.load('python java protoc')
- # Here you have to point to your protobuf-java JAR and have it in classpath
- conf.env.CLASSPATH_PROTOBUF = ['protobuf-java-2.5.0.jar']
- def build(bld):
- bld(
- features = 'javac protoc',
- name = 'pbjava',
- srcdir = 'inc/ src', # directories used by javac
- source = ['inc/message_inc.proto', 'inc/message.proto'],
- # source is used by protoc for .proto files
- use = 'PROTOBUF',
- protoc_includes = ['inc']) # for protoc to search dependencies
- Notes when using this tool:
- - protoc command line parsing is tricky.
- The generated files can be put in subfolders which depend on
- the order of the include paths.
- Try to be simple when creating task generators
- containing protoc stuff.
- """
- class protoc(Task):
- run_str = '${PROTOC} ${PROTOC_FL:PROTOC_FLAGS} ${PROTOC_ST:INCPATHS} ${PROTOC_ST:PROTOC_INCPATHS} ${SRC[0].bldpath()}'
- color = 'BLUE'
- ext_out = ['.h', 'pb.cc', '.py', '.java']
- def scan(self):
- """
- Scan .proto dependencies
- """
- node = self.inputs[0]
- nodes = []
- names = []
- seen = []
- search_nodes = []
- if not node:
- return (nodes, names)
- if 'cxx' in self.generator.features:
- search_nodes = self.generator.includes_nodes
- if 'py' in self.generator.features or 'javac' in self.generator.features:
- for incpath in getattr(self.generator, 'protoc_includes', []):
- search_nodes.append(self.generator.bld.path.find_node(incpath))
- def parse_node(node):
- if node in seen:
- return
- seen.append(node)
- code = node.read().splitlines()
- for line in code:
- m = re.search(r'^import\s+"(.*)";.*(//)?.*', line)
- if m:
- dep = m.groups()[0]
- for incnode in search_nodes:
- found = incnode.find_resource(dep)
- if found:
- nodes.append(found)
- parse_node(found)
- else:
- names.append(dep)
- parse_node(node)
- # Add also dependencies path to INCPATHS so protoc will find the included file
- for deppath in nodes:
- self.env.append_value('INCPATHS', deppath.parent.bldpath())
- return (nodes, names)
- @extension('.proto')
- def process_protoc(self, node):
- incdirs = []
- out_nodes = []
- protoc_flags = []
- # ensure PROTOC_FLAGS is a list; a copy is used below anyway
- self.env.PROTOC_FLAGS = self.to_list(self.env.PROTOC_FLAGS)
- if 'cxx' in self.features:
- cpp_node = node.change_ext('.pb.cc')
- hpp_node = node.change_ext('.pb.h')
- self.source.append(cpp_node)
- out_nodes.append(cpp_node)
- out_nodes.append(hpp_node)
- protoc_flags.append('--cpp_out=%s' % node.parent.get_bld().bldpath())
- if 'py' in self.features:
- py_node = node.change_ext('_pb2.py')
- self.source.append(py_node)
- out_nodes.append(py_node)
- protoc_flags.append('--python_out=%s' % node.parent.get_bld().bldpath())
- if 'javac' in self.features:
- pkgname, javapkg, javacn, nodename = None, None, None, None
- messages = []
- # .java file name is done with some rules depending on .proto file content:
- # -) package is either derived from option java_package if present
- # or from package directive
- # -) file name is either derived from option java_outer_classname if present
- # or the .proto file is converted to camelcase. If a message
- # is named the same then the behaviour depends on protoc version
- #
- # See also: https://developers.google.com/protocol-buffers/docs/reference/java-generated#invocation
- code = node.read().splitlines()
- for line in code:
- m = re.search(r'^package\s+(.*);', line)
- if m:
- pkgname = m.groups()[0]
- m = re.search(r'^option\s+(\S*)\s*=\s*"(\S*)";', line)
- if m:
- optname = m.groups()[0]
- if optname == 'java_package':
- javapkg = m.groups()[1]
- elif optname == 'java_outer_classname':
- javacn = m.groups()[1]
- if self.env.PROTOC_MAJOR > '2':
- m = re.search(r'^message\s+(\w*)\s*{*', line)
- if m:
- messages.append(m.groups()[0])
- if javapkg:
- nodename = javapkg
- elif pkgname:
- nodename = pkgname
- else:
- raise Errors.WafError('Cannot derive java name from protoc file')
- nodename = nodename.replace('.',os.sep) + os.sep
- if javacn:
- nodename += javacn + '.java'
- else:
- if self.env.PROTOC_MAJOR > '2' and node.abspath()[node.abspath().rfind(os.sep)+1:node.abspath().rfind('.')].title() in messages:
- nodename += node.abspath()[node.abspath().rfind(os.sep)+1:node.abspath().rfind('.')].title().replace('_','') + 'OuterClass.java'
- else:
- nodename += node.abspath()[node.abspath().rfind(os.sep)+1:node.abspath().rfind('.')].title().replace('_','') + '.java'
- java_node = node.parent.find_or_declare(nodename)
- out_nodes.append(java_node)
- protoc_flags.append('--java_out=%s' % node.parent.get_bld().bldpath())
- # Make javac get also pick java code generated in build
- if not node.parent.get_bld() in self.javac_task.srcdir:
- self.javac_task.srcdir.append(node.parent.get_bld())
- if not out_nodes:
- raise Errors.WafError('Feature %r not supported by protoc extra' % self.features)
- tsk = self.create_task('protoc', node, out_nodes)
- tsk.env.append_value('PROTOC_FLAGS', protoc_flags)
- if 'javac' in self.features:
- self.javac_task.set_run_after(tsk)
- # Instruct protoc where to search for .proto included files.
- # For C++ standard include files dirs are used,
- # but this doesn't apply to Python for example
- for incpath in getattr(self, 'protoc_includes', []):
- incdirs.append(self.path.find_node(incpath).bldpath())
- tsk.env.PROTOC_INCPATHS = incdirs
- # PR2115: protoc generates output of .proto files in nested
- # directories by canonicalizing paths. To avoid this we have to pass
- # as first include the full directory file of the .proto file
- tsk.env.prepend_value('INCPATHS', node.parent.bldpath())
- use = getattr(self, 'use', '')
- if not 'PROTOBUF' in use:
- self.use = self.to_list(use) + ['PROTOBUF']
- def configure(conf):
- conf.check_cfg(package='protobuf', uselib_store='PROTOBUF', args=['--cflags', '--libs'])
- conf.find_program('protoc', var='PROTOC')
- conf.start_msg('Checking for protoc version')
- protocver = conf.cmd_and_log(conf.env.PROTOC + ['--version'], output=Context.BOTH)
- protocver = ''.join(protocver).strip()[protocver[0].rfind(' ')+1:]
- conf.end_msg(protocver)
- conf.env.PROTOC_MAJOR = protocver[:protocver.find('.')]
- conf.env.PROTOC_ST = '-I%s'
- conf.env.PROTOC_FL = '%s'
|