1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5import optparse, os, re, sys
6from cStringIO import StringIO
7from mozbuild.pythonutil import iter_modules_in_path
8import mozpack.path as mozpath
9import itertools
10
11import ipdl
12
13def log(minv, fmt, *args):
14    if _verbosity >= minv:
15        print fmt % args
16
17# process command line
18
19op = optparse.OptionParser(usage='ipdl.py [options] IPDLfiles...')
20op.add_option('-I', '--include', dest='includedirs', default=[ ],
21              action='append',
22              help='Additional directory to search for included protocol specifications')
23op.add_option('-v', '--verbose', dest='verbosity', default=1, action='count',
24              help='Verbose logging (specify -vv or -vvv for very verbose logging)')
25op.add_option('-q', '--quiet', dest='verbosity', action='store_const', const=0,
26              help="Suppress logging output")
27op.add_option('-d', '--outheaders-dir', dest='headersdir', default='.',
28              help="""Directory into which C++ headers will be generated.
29A protocol Foo in the namespace bar will cause the headers
30  dir/bar/Foo.h, dir/bar/FooParent.h, and dir/bar/FooParent.h
31to be generated""")
32op.add_option('-o', '--outcpp-dir', dest='cppdir', default='.',
33              help="""Directory into which C++ sources will be generated
34A protocol Foo in the namespace bar will cause the sources
35  cppdir/FooParent.cpp, cppdir/FooChild.cpp
36to be generated""")
37
38
39options, files = op.parse_args()
40_verbosity = options.verbosity
41headersdir = options.headersdir
42cppdir = options.cppdir
43includedirs = [ os.path.abspath(incdir) for incdir in options.includedirs ]
44
45if not len(files):
46    op.error("No IPDL files specified")
47
48ipcmessagestartpath = os.path.join(headersdir, 'IPCMessageStart.h')
49ipc_msgtype_name_path = os.path.join(cppdir, 'IPCMessageTypeName.cpp')
50
51# Compiling the IPDL files can take a long time, even on a fast machine.
52# Check to see whether we need to do any work.
53latestipdlmod = max(os.stat(f).st_mtime
54                    for f in itertools.chain(files,
55                                             iter_modules_in_path(mozpath.dirname(__file__))))
56
57def outputModTime(f):
58    # A non-existant file is newer than everything.
59    if not os.path.exists(f):
60        return 0
61    return os.stat(f).st_mtime
62
63# Because the IPDL headers are placed into directories reflecting their
64# namespace, collect a list here so we can easily map output names without
65# parsing the actual IPDL files themselves.
66headersmap = {}
67for (path, dirs, headers) in os.walk(headersdir):
68    for h in headers:
69        base = os.path.basename(h)
70        if base in headersmap:
71            root, ext = os.path.splitext(base)
72            print >>sys.stderr, 'A protocol named', root, 'exists in multiple namespaces'
73            sys.exit(1)
74        headersmap[base] = os.path.join(path, h)
75
76def outputfiles(f):
77    base = os.path.basename(f)
78    root, ext = os.path.splitext(base)
79
80    suffixes = ['']
81    if ext == '.ipdl':
82        suffixes += ['Child', 'Parent']
83
84    for suffix in suffixes:
85        yield os.path.join(cppdir, "%s%s.cpp" % (root, suffix))
86        header = "%s%s.h" % (root, suffix)
87        # If the header already exists on disk, use that.  Otherwise,
88        # just claim that the header is found in headersdir.
89        if header in headersmap:
90            yield headersmap[header]
91        else:
92            yield os.path.join(headersdir, header)
93
94def alloutputfiles():
95    for f in files:
96        for s in outputfiles(f):
97            yield s
98    yield ipcmessagestartpath
99
100earliestoutputmod = min(outputModTime(f) for f in alloutputfiles())
101
102if latestipdlmod < earliestoutputmod:
103    sys.exit(0)
104
105log(2, 'Generated C++ headers will be generated relative to "%s"', headersdir)
106log(2, 'Generated C++ sources will be generated in "%s"', cppdir)
107
108allmessages = {}
109allprotocols = []
110
111def normalizedFilename(f):
112    if f == '-':
113        return '<stdin>'
114    return f
115
116# First pass: parse and type-check all protocols
117for f in files:
118    log(2, os.path.basename(f))
119    filename = normalizedFilename(f)
120    if f == '-':
121        fd = sys.stdin
122    else:
123        fd = open(f)
124
125    specstring = fd.read()
126    fd.close()
127
128    ast = ipdl.parse(specstring, filename, includedirs=includedirs)
129    if ast is None:
130        print >>sys.stderr, 'Specification could not be parsed.'
131        sys.exit(1)
132
133    log(2, 'checking types')
134    if not ipdl.typecheck(ast):
135        print >>sys.stderr, 'Specification is not well typed.'
136        sys.exit(1)
137
138    if _verbosity > 2:
139        log(3, '  pretty printed code:')
140        ipdl.genipdl(ast, codedir)
141
142# Second pass: generate code
143for f in files:
144    # Read from parser cache
145    filename = normalizedFilename(f)
146    ast = ipdl.parse(None, filename, includedirs=includedirs)
147    ipdl.gencxx(filename, ast, headersdir, cppdir)
148
149    if ast.protocol:
150        allmessages[ast.protocol.name] = ipdl.genmsgenum(ast)
151        allprotocols.append('%sMsgStart' % ast.protocol.name)
152
153allprotocols.sort()
154
155ipcmsgstart = StringIO()
156
157print >>ipcmsgstart, """
158// CODE GENERATED by ipdl.py. Do not edit.
159
160#ifndef IPCMessageStart_h
161#define IPCMessageStart_h
162
163enum IPCMessageStart {
164"""
165
166for name in allprotocols:
167    print >>ipcmsgstart, "  %s," % name
168    print >>ipcmsgstart, "  %sChild," % name
169
170print >>ipcmsgstart, """
171  LastMsgIndex
172};
173
174static_assert(LastMsgIndex <= 65536, "need to update IPC_MESSAGE_MACRO");
175
176#endif // ifndef IPCMessageStart_h
177"""
178
179ipc_msgtype_name = StringIO()
180print >>ipc_msgtype_name, """
181// CODE GENERATED by ipdl.py. Do not edit.
182#include <cstdint>
183
184#include "IPCMessageStart.h"
185
186using std::uint32_t;
187
188namespace {
189
190enum IPCMessages {
191"""
192
193for protocol in sorted(allmessages.keys()):
194    for (msg, num) in allmessages[protocol].idnums:
195        if num:
196            print >>ipc_msgtype_name, "  %s = %s," % (msg, num)
197        elif not msg.endswith('End'):
198            print >>ipc_msgtype_name,  "  %s__%s," % (protocol, msg)
199
200print >>ipc_msgtype_name, """
201};
202
203} // anonymous namespace
204
205namespace mozilla {
206namespace ipc {
207
208const char* StringFromIPCMessageType(uint32_t aMessageType)
209{
210  switch (aMessageType) {
211"""
212
213for protocol in sorted(allmessages.keys()):
214    for (msg, num) in allmessages[protocol].idnums:
215        if num or msg.endswith('End'):
216            continue
217        print >>ipc_msgtype_name, """
218  case %s__%s:
219    return "%s::%s";""" % (protocol, msg, protocol, msg)
220
221print >>ipc_msgtype_name, """
222  default:
223    return "???";
224  }
225}
226
227} // namespace ipc
228} // namespace mozilla
229"""
230
231ipdl.writeifmodified(ipcmsgstart.getvalue(), ipcmessagestartpath)
232ipdl.writeifmodified(ipc_msgtype_name.getvalue(), ipc_msgtype_name_path)
233