3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
11 from waflib.Task import Task
12 from waflib.TaskGen import extension
13 from waflib import Errors, Context
16 A simple tool to integrate protocol buffers into your build system.
21 conf.load('compiler_cxx cxx protoc')
25 features = 'cxx cxxprogram'
26 source = 'main.cpp file1.proto proto/file2.proto',
28 target = 'executable')
33 conf.load('python protoc')
38 source = 'main.py file1.proto proto/file2.proto',
39 protoc_includes = 'proto')
41 Example for both Python and C++ at same time:
44 conf.load('cxx python protoc')
49 source = 'file1.proto proto/file2.proto',
50 protoc_includes = 'proto') # or includes
59 conf.load('python java protoc')
60 # Here you have to point to your protobuf-java JAR and have it in classpath
61 conf.env.CLASSPATH_PROTOBUF = ['protobuf-java-2.5.0.jar']
65 features = 'javac protoc',
67 srcdir = 'inc/ src', # directories used by javac
68 source = ['inc/message_inc.proto', 'inc/message.proto'],
69 # source is used by protoc for .proto files
71 protoc_includes = ['inc']) # for protoc to search dependencies
76 Notes when using this tool:
78 - protoc command line parsing is tricky.
80 The generated files can be put in subfolders which depend on
81 the order of the include paths.
83 Try to be simple when creating task generators
84 containing protoc stuff.
89 run_str = '${PROTOC} ${PROTOC_FL:PROTOC_FLAGS} ${PROTOC_ST:INCPATHS} ${PROTOC_ST:PROTOC_INCPATHS} ${SRC[0].bldpath()}'
91 ext_out = ['.h', 'pb.cc', '.py', '.java']
94 Scan .proto dependencies
104 return (nodes, names)
106 if 'cxx' in self.generator.features:
107 search_nodes = self.generator.includes_nodes
109 if 'py' in self.generator.features or 'javac' in self.generator.features:
110 for incpath in getattr(self.generator, 'protoc_includes', []):
111 search_nodes.append(self.generator.bld.path.find_node(incpath))
113 def parse_node(node):
117 code = node.read().splitlines()
119 m = re.search(r'^import\s+"(.*)";.*(//)?.*', line)
122 for incnode in search_nodes:
123 found = incnode.find_resource(dep)
131 return (nodes, names)
134 def process_protoc(self, node):
139 # ensure PROTOC_FLAGS is a list; a copy is used below anyway
140 self.env.PROTOC_FLAGS = self.to_list(self.env.PROTOC_FLAGS)
142 if 'cxx' in self.features:
143 cpp_node = node.change_ext('.pb.cc')
144 hpp_node = node.change_ext('.pb.h')
145 self.source.append(cpp_node)
146 out_nodes.append(cpp_node)
147 out_nodes.append(hpp_node)
148 protoc_flags.append('--cpp_out=%s' % node.parent.get_bld().bldpath())
150 if 'py' in self.features:
151 py_node = node.change_ext('_pb2.py')
152 self.source.append(py_node)
153 out_nodes.append(py_node)
154 protoc_flags.append('--python_out=%s' % node.parent.get_bld().bldpath())
156 if 'javac' in self.features:
157 pkgname, javapkg, javacn, nodename = None, None, None, None
160 # .java file name is done with some rules depending on .proto file content:
161 # -) package is either derived from option java_package if present
162 # or from package directive
163 # -) file name is either derived from option java_outer_classname if present
164 # or the .proto file is converted to camelcase. If a message
165 # is named the same then the behaviour depends on protoc version
167 # See also: https://developers.google.com/protocol-buffers/docs/reference/java-generated#invocation
169 code = node.read().splitlines()
171 m = re.search(r'^package\s+(.*);', line)
173 pkgname = m.groups()[0]
174 m = re.search(r'^option\s+(\S*)\s*=\s*"(\S*)";', line)
176 optname = m.groups()[0]
177 if optname == 'java_package':
178 javapkg = m.groups()[1]
179 elif optname == 'java_outer_classname':
180 javacn = m.groups()[1]
181 if self.env.PROTOC_MAJOR > '2':
182 m = re.search(r'^message\s+(\w*)\s*{*', line)
184 messages.append(m.groups()[0])
191 raise Errors.WafError('Cannot derive java name from protoc file')
193 nodename = nodename.replace('.',os.sep) + os.sep
195 nodename += javacn + '.java'
197 if self.env.PROTOC_MAJOR > '2' and node.abspath()[node.abspath().rfind(os.sep)+1:node.abspath().rfind('.')].title() in messages:
198 nodename += node.abspath()[node.abspath().rfind(os.sep)+1:node.abspath().rfind('.')].title() + 'OuterClass.java'
200 nodename += node.abspath()[node.abspath().rfind(os.sep)+1:node.abspath().rfind('.')].title() + '.java'
202 java_node = node.parent.find_or_declare(nodename)
203 out_nodes.append(java_node)
204 protoc_flags.append('--java_out=%s' % node.parent.get_bld().bldpath())
206 # Make javac get also pick java code generated in build
207 if not node.parent.get_bld() in self.javac_task.srcdir:
208 self.javac_task.srcdir.append(node.parent.get_bld())
211 raise Errors.WafError('Feature %r not supported by protoc extra' % self.features)
213 tsk = self.create_task('protoc', node, out_nodes)
214 tsk.env.append_value('PROTOC_FLAGS', protoc_flags)
216 if 'javac' in self.features:
217 self.javac_task.set_run_after(tsk)
219 # Instruct protoc where to search for .proto included files.
220 # For C++ standard include files dirs are used,
221 # but this doesn't apply to Python for example
222 for incpath in getattr(self, 'protoc_includes', []):
223 incdirs.append(self.bld.path.find_node(incpath).bldpath())
224 tsk.env.PROTOC_INCPATHS = incdirs
226 use = getattr(self, 'use', '')
227 if not 'PROTOBUF' in use:
228 self.use = self.to_list(use) + ['PROTOBUF']
231 conf.check_cfg(package='protobuf', uselib_store='PROTOBUF', args=['--cflags', '--libs'])
232 conf.find_program('protoc', var='PROTOC')
233 conf.start_msg('Checking for protoc version')
234 protocver = conf.cmd_and_log(conf.env.PROTOC + ['--version'], output=Context.BOTH)
235 protocver = ''.join(protocver).strip()[protocver[0].rfind(' ')+1:]
236 conf.end_msg(protocver)
237 conf.env.PROTOC_MAJOR = protocver[:protocver.find('.')]
238 conf.env.PROTOC_ST = '-I%s'
239 conf.env.PROTOC_FL = '%s'