1#!/usr/bin/env python 2# encoding: utf-8 3# Philipp Bender, 2012 4# Matt Clarkson, 2012 5 6import re, os 7from waflib.Task import Task 8from waflib.TaskGen import extension 9from waflib import Errors, Context, Logs 10 11""" 12A simple tool to integrate protocol buffers into your build system. 13 14Example for C++: 15 16 def configure(conf): 17 conf.load('compiler_cxx cxx protoc') 18 19 def build(bld): 20 bld( 21 features = 'cxx cxxprogram' 22 source = 'main.cpp file1.proto proto/file2.proto', 23 includes = '. proto', 24 target = 'executable') 25 26Example for Python: 27 28 def configure(conf): 29 conf.load('python protoc') 30 31 def build(bld): 32 bld( 33 features = 'py' 34 source = 'main.py file1.proto proto/file2.proto', 35 protoc_includes = 'proto') 36 37Example for both Python and C++ at same time: 38 39 def configure(conf): 40 conf.load('cxx python protoc') 41 42 def build(bld): 43 bld( 44 features = 'cxx py' 45 source = 'file1.proto proto/file2.proto', 46 protoc_includes = 'proto') # or includes 47 48 49Example for Java: 50 51 def options(opt): 52 opt.load('java') 53 54 def configure(conf): 55 conf.load('python java protoc') 56 # Here you have to point to your protobuf-java JAR and have it in classpath 57 conf.env.CLASSPATH_PROTOBUF = ['protobuf-java-2.5.0.jar'] 58 59 def build(bld): 60 bld( 61 features = 'javac protoc', 62 name = 'pbjava', 63 srcdir = 'inc/ src', # directories used by javac 64 source = ['inc/message_inc.proto', 'inc/message.proto'], 65 # source is used by protoc for .proto files 66 use = 'PROTOBUF', 67 protoc_includes = ['inc']) # for protoc to search dependencies 68 69 70Protoc includes passed via protoc_includes are either relative to the taskgen 71or to the project and are searched in this order. 72 73Include directories external to the waf project can also be passed to the 74extra by using protoc_extincludes 75 76 protoc_extincludes = ['/usr/include/pblib'] 77 78 79Notes when using this tool: 80 81- protoc command line parsing is tricky. 82 83 The generated files can be put in subfolders which depend on 84 the order of the include paths. 85 86 Try to be simple when creating task generators 87 containing protoc stuff. 88 89""" 90 91class protoc(Task): 92 run_str = '${PROTOC} ${PROTOC_FL:PROTOC_FLAGS} ${PROTOC_ST:INCPATHS} ${PROTOC_ST:PROTOC_INCPATHS} ${PROTOC_ST:PROTOC_EXTINCPATHS} ${SRC[0].bldpath()}' 93 color = 'BLUE' 94 ext_out = ['.h', 'pb.cc', '.py', '.java'] 95 def scan(self): 96 """ 97 Scan .proto dependencies 98 """ 99 node = self.inputs[0] 100 101 nodes = [] 102 names = [] 103 seen = [] 104 search_nodes = [] 105 106 if not node: 107 return (nodes, names) 108 109 if 'cxx' in self.generator.features: 110 search_nodes = self.generator.includes_nodes 111 112 if 'py' in self.generator.features or 'javac' in self.generator.features: 113 for incpath in getattr(self.generator, 'protoc_includes', []): 114 incpath_node = self.generator.path.find_node(incpath) 115 if incpath_node: 116 search_nodes.append(incpath_node) 117 else: 118 # Check if relative to top-level for extra tg dependencies 119 incpath_node = self.generator.bld.path.find_node(incpath) 120 if incpath_node: 121 search_nodes.append(incpath_node) 122 else: 123 raise Errors.WafError('protoc: include path %r does not exist' % incpath) 124 125 126 def parse_node(node): 127 if node in seen: 128 return 129 seen.append(node) 130 code = node.read().splitlines() 131 for line in code: 132 m = re.search(r'^import\s+"(.*)";.*(//)?.*', line) 133 if m: 134 dep = m.groups()[0] 135 for incnode in search_nodes: 136 found = incnode.find_resource(dep) 137 if found: 138 nodes.append(found) 139 parse_node(found) 140 else: 141 names.append(dep) 142 143 parse_node(node) 144 # Add also dependencies path to INCPATHS so protoc will find the included file 145 for deppath in nodes: 146 self.env.append_unique('INCPATHS', deppath.parent.bldpath()) 147 return (nodes, names) 148 149@extension('.proto') 150def process_protoc(self, node): 151 incdirs = [] 152 out_nodes = [] 153 protoc_flags = [] 154 155 # ensure PROTOC_FLAGS is a list; a copy is used below anyway 156 self.env.PROTOC_FLAGS = self.to_list(self.env.PROTOC_FLAGS) 157 158 if 'cxx' in self.features: 159 cpp_node = node.change_ext('.pb.cc') 160 hpp_node = node.change_ext('.pb.h') 161 self.source.append(cpp_node) 162 out_nodes.append(cpp_node) 163 out_nodes.append(hpp_node) 164 protoc_flags.append('--cpp_out=%s' % node.parent.get_bld().bldpath()) 165 166 if 'py' in self.features: 167 py_node = node.change_ext('_pb2.py') 168 self.source.append(py_node) 169 out_nodes.append(py_node) 170 protoc_flags.append('--python_out=%s' % node.parent.get_bld().bldpath()) 171 172 if 'javac' in self.features: 173 # Make javac get also pick java code generated in build 174 if not node.parent.get_bld() in self.javac_task.srcdir: 175 self.javac_task.srcdir.append(node.parent.get_bld()) 176 177 protoc_flags.append('--java_out=%s' % node.parent.get_bld().bldpath()) 178 node.parent.get_bld().mkdir() 179 180 tsk = self.create_task('protoc', node, out_nodes) 181 tsk.env.append_value('PROTOC_FLAGS', protoc_flags) 182 183 if 'javac' in self.features: 184 self.javac_task.set_run_after(tsk) 185 186 # Instruct protoc where to search for .proto included files. 187 # For C++ standard include files dirs are used, 188 # but this doesn't apply to Python for example 189 for incpath in getattr(self, 'protoc_includes', []): 190 incpath_node = self.path.find_node(incpath) 191 if incpath_node: 192 incdirs.append(incpath_node.bldpath()) 193 else: 194 # Check if relative to top-level for extra tg dependencies 195 incpath_node = self.bld.path.find_node(incpath) 196 if incpath_node: 197 incdirs.append(incpath_node.bldpath()) 198 else: 199 raise Errors.WafError('protoc: include path %r does not exist' % incpath) 200 201 tsk.env.PROTOC_INCPATHS = incdirs 202 203 # Include paths external to the waf project (ie. shared pb repositories) 204 tsk.env.PROTOC_EXTINCPATHS = getattr(self, 'protoc_extincludes', []) 205 206 # PR2115: protoc generates output of .proto files in nested 207 # directories by canonicalizing paths. To avoid this we have to pass 208 # as first include the full directory file of the .proto file 209 tsk.env.prepend_value('INCPATHS', node.parent.bldpath()) 210 211 use = getattr(self, 'use', '') 212 if not 'PROTOBUF' in use: 213 self.use = self.to_list(use) + ['PROTOBUF'] 214 215def configure(conf): 216 conf.check_cfg(package='protobuf', uselib_store='PROTOBUF', args=['--cflags', '--libs']) 217 conf.find_program('protoc', var='PROTOC') 218 conf.start_msg('Checking for protoc version') 219 protocver = conf.cmd_and_log(conf.env.PROTOC + ['--version'], output=Context.BOTH) 220 protocver = ''.join(protocver).strip()[protocver[0].rfind(' ')+1:] 221 conf.end_msg(protocver) 222 conf.env.PROTOC_MAJOR = protocver[:protocver.find('.')] 223 conf.env.PROTOC_ST = '-I%s' 224 conf.env.PROTOC_FL = '%s' 225