third_party:waf: update to upstream 2.0.4 release
[bbaumbach/samba.git] / third_party / waf / waflib / extras / protoc.py
1 #! /usr/bin/env python
2 # encoding: utf-8
3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
4
5 #!/usr/bin/env python
6 # encoding: utf-8
7 # Philipp Bender, 2012
8 # Matt Clarkson, 2012
9
10 import re, os
11 from waflib.Task import Task
12 from waflib.TaskGen import extension
13 from waflib import Errors, Context
14
15 """
16 A simple tool to integrate protocol buffers into your build system.
17
18 Example for C++:
19
20     def configure(conf):
21         conf.load('compiler_cxx cxx protoc')
22
23     def build(bld):
24         bld(
25                 features = 'cxx cxxprogram'
26                 source   = 'main.cpp file1.proto proto/file2.proto',
27                 includes = '. proto',
28                 target   = 'executable')
29
30 Example for Python:
31
32     def configure(conf):
33         conf.load('python protoc')
34
35     def build(bld):
36         bld(
37                 features = 'py'
38                 source   = 'main.py file1.proto proto/file2.proto',
39                 protoc_includes  = 'proto')
40
41 Example for both Python and C++ at same time:
42
43     def configure(conf):
44         conf.load('cxx python protoc')
45
46     def build(bld):
47         bld(
48                 features = 'cxx py'
49                 source   = 'file1.proto proto/file2.proto',
50                 protoc_includes  = 'proto')     # or includes
51
52
53 Example for Java:
54
55     def options(opt):
56         opt.load('java')
57
58     def configure(conf):
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']
62
63     def build(bld):
64         bld(
65                 features = 'javac protoc',
66                 name = 'pbjava',
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
70                 use = 'PROTOBUF',
71                 protoc_includes = ['inc']) # for protoc to search dependencies
72
73
74
75
76 Notes when using this tool:
77
78 - protoc command line parsing is tricky.
79
80   The generated files can be put in subfolders which depend on
81   the order of the include paths.
82
83   Try to be simple when creating task generators
84   containing protoc stuff.
85
86 """
87
88 class protoc(Task):
89         run_str = '${PROTOC} ${PROTOC_FL:PROTOC_FLAGS} ${PROTOC_ST:INCPATHS} ${PROTOC_ST:PROTOC_INCPATHS} ${SRC[0].bldpath()}'
90         color   = 'BLUE'
91         ext_out = ['.h', 'pb.cc', '.py', '.java']
92         def scan(self):
93                 """
94                 Scan .proto dependencies
95                 """
96                 node = self.inputs[0]
97
98                 nodes = []
99                 names = []
100                 seen = []
101                 search_nodes = []
102
103                 if not node:
104                         return (nodes, names)
105
106                 if 'cxx' in self.generator.features:
107                         search_nodes = self.generator.includes_nodes
108
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))
112
113                 def parse_node(node):
114                         if node in seen:
115                                 return
116                         seen.append(node)
117                         code = node.read().splitlines()
118                         for line in code:
119                                 m = re.search(r'^import\s+"(.*)";.*(//)?.*', line)
120                                 if m:
121                                         dep = m.groups()[0]
122                                         for incnode in search_nodes:
123                                                 found = incnode.find_resource(dep)
124                                                 if found:
125                                                         nodes.append(found)
126                                                         parse_node(found)
127                                                 else:
128                                                         names.append(dep)
129
130                 parse_node(node)
131                 return (nodes, names)
132
133 @extension('.proto')
134 def process_protoc(self, node):
135         incdirs = []
136         out_nodes = []
137         protoc_flags = []
138
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)
141
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())
149
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())
155
156         if 'javac' in self.features:
157                 pkgname, javapkg, javacn, nodename = None, None, None, None
158                 messages = []
159
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
166                 #
167                 # See also: https://developers.google.com/protocol-buffers/docs/reference/java-generated#invocation
168
169                 code = node.read().splitlines()
170                 for line in code:
171                         m = re.search(r'^package\s+(.*);', line)
172                         if m:
173                                 pkgname = m.groups()[0]
174                         m = re.search(r'^option\s+(\S*)\s*=\s*"(\S*)";', line)
175                         if m:
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)
183                                 if m:
184                                         messages.append(m.groups()[0])
185
186                 if javapkg:
187                         nodename = javapkg
188                 elif pkgname:
189                         nodename = pkgname
190                 else:
191                         raise Errors.WafError('Cannot derive java name from protoc file')
192
193                 nodename = nodename.replace('.',os.sep) + os.sep
194                 if javacn:
195                         nodename += javacn + '.java'
196                 else:
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'
199                         else:
200                                 nodename += node.abspath()[node.abspath().rfind(os.sep)+1:node.abspath().rfind('.')].title() + '.java'
201
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())
205
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())
209
210         if not out_nodes:
211                 raise Errors.WafError('Feature %r not supported by protoc extra' % self.features)
212
213         tsk = self.create_task('protoc', node, out_nodes)
214         tsk.env.append_value('PROTOC_FLAGS', protoc_flags)
215
216         if 'javac' in self.features:
217                 self.javac_task.set_run_after(tsk)
218
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
225
226         use = getattr(self, 'use', '')
227         if not 'PROTOBUF' in use:
228                 self.use = self.to_list(use) + ['PROTOBUF']
229
230 def configure(conf):
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'