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
10from ConfigParser import RawConfigParser
11
12import ipdl
13
14def log(minv, fmt, *args):
15    if _verbosity >= minv:
16        print fmt % args
17
18# process command line
19
20op = optparse.OptionParser(usage='ipdl.py [options] IPDLfiles...')
21op.add_option('-I', '--include', dest='includedirs', default=[ ],
22              action='append',
23              help='Additional directory to search for included protocol specifications')
24op.add_option('-s', '--sync-msg-list', dest='syncMsgList', default='sync-messages.ini',
25              help="Config file listing allowed sync messages")
26op.add_option('-m', '--msg-metadata', dest='msgMetadata', default='message-metadata.ini',
27              help="Predicted message sizes for reducing serialization malloc overhead.")
28op.add_option('-v', '--verbose', dest='verbosity', default=1, action='count',
29              help='Verbose logging (specify -vv or -vvv for very verbose logging)')
30op.add_option('-q', '--quiet', dest='verbosity', action='store_const', const=0,
31              help="Suppress logging output")
32op.add_option('-d', '--outheaders-dir', dest='headersdir', default='.',
33              help="""Directory into which C++ headers will be generated.
34A protocol Foo in the namespace bar will cause the headers
35  dir/bar/Foo.h, dir/bar/FooParent.h, and dir/bar/FooParent.h
36to be generated""")
37op.add_option('-o', '--outcpp-dir', dest='cppdir', default='.',
38              help="""Directory into which C++ sources will be generated
39A protocol Foo in the namespace bar will cause the sources
40  cppdir/FooParent.cpp, cppdir/FooChild.cpp
41to be generated""")
42
43options, files = op.parse_args()
44_verbosity = options.verbosity
45syncMsgList = options.syncMsgList
46msgMetadata = options.msgMetadata
47headersdir = options.headersdir
48cppdir = options.cppdir
49includedirs = [ os.path.abspath(incdir) for incdir in options.includedirs ]
50
51if not len(files):
52    op.error("No IPDL files specified")
53
54ipcmessagestartpath = os.path.join(headersdir, 'IPCMessageStart.h')
55ipc_msgtype_name_path = os.path.join(cppdir, 'IPCMessageTypeName.cpp')
56
57# Compiling the IPDL files can take a long time, even on a fast machine.
58# Check to see whether we need to do any work.
59latestipdlmod = max(os.stat(f).st_mtime
60                    for f in itertools.chain(files,
61                                             iter_modules_in_path(mozpath.dirname(__file__))))
62
63def outputModTime(f):
64    # A non-existant file is newer than everything.
65    if not os.path.exists(f):
66        return 0
67    return os.stat(f).st_mtime
68
69# Because the IPDL headers are placed into directories reflecting their
70# namespace, collect a list here so we can easily map output names without
71# parsing the actual IPDL files themselves.
72headersmap = {}
73for (path, dirs, headers) in os.walk(headersdir):
74    for h in headers:
75        base = os.path.basename(h)
76        if base in headersmap:
77            root, ext = os.path.splitext(base)
78            print >>sys.stderr, 'A protocol named', root, 'exists in multiple namespaces'
79            sys.exit(1)
80        headersmap[base] = os.path.join(path, h)
81
82def outputfiles(f):
83    base = os.path.basename(f)
84    root, ext = os.path.splitext(base)
85
86    suffixes = ['']
87    if ext == '.ipdl':
88        suffixes += ['Child', 'Parent']
89
90    for suffix in suffixes:
91        yield os.path.join(cppdir, "%s%s.cpp" % (root, suffix))
92        header = "%s%s.h" % (root, suffix)
93        # If the header already exists on disk, use that.  Otherwise,
94        # just claim that the header is found in headersdir.
95        if header in headersmap:
96            yield headersmap[header]
97        else:
98            yield os.path.join(headersdir, header)
99
100def alloutputfiles():
101    for f in files:
102        for s in outputfiles(f):
103            yield s
104    yield ipcmessagestartpath
105
106earliestoutputmod = min(outputModTime(f) for f in alloutputfiles())
107
108if latestipdlmod < earliestoutputmod:
109    sys.exit(0)
110
111log(2, 'Generated C++ headers will be generated relative to "%s"', headersdir)
112log(2, 'Generated C++ sources will be generated in "%s"', cppdir)
113
114allmessages = {}
115allmessageprognames = []
116allprotocols = []
117
118def normalizedFilename(f):
119    if f == '-':
120        return '<stdin>'
121    return f
122
123log(2, 'Reading sync message list')
124parser = RawConfigParser()
125parser.readfp(open(options.syncMsgList))
126syncMsgList = parser.sections()
127
128# Read message metadata. Right now we only have 'segment_capacity'
129# for the standard segment size used for serialization.
130log(2, 'Reading message metadata...')
131msgMetadataConfig = RawConfigParser()
132msgMetadataConfig.readfp(open(options.msgMetadata))
133
134segmentCapacityDict = {}
135for msgName in msgMetadataConfig.sections():
136    if msgMetadataConfig.has_option(msgName, 'segment_capacity'):
137        capacity = msgMetadataConfig.get(msgName, 'segment_capacity')
138        segmentCapacityDict[msgName] = capacity
139
140# First pass: parse and type-check all protocols
141for f in files:
142    log(2, os.path.basename(f))
143    filename = normalizedFilename(f)
144    if f == '-':
145        fd = sys.stdin
146    else:
147        fd = open(f)
148
149    specstring = fd.read()
150    fd.close()
151
152    ast = ipdl.parse(specstring, filename, includedirs=includedirs)
153    if ast is None:
154        print >>sys.stderr, 'Specification could not be parsed.'
155        sys.exit(1)
156
157    log(2, 'checking types')
158    if not ipdl.typecheck(ast):
159        print >>sys.stderr, 'Specification is not well typed.'
160        sys.exit(1)
161
162    if not ipdl.checkSyncMessage(ast, syncMsgList):
163        print >>sys.stderr, 'Error: New sync IPC messages must be reviewed by an IPC peer and recorded in %s' % options.syncMsgList
164        sys.exit(1)
165
166    if _verbosity > 2:
167        log(3, '  pretty printed code:')
168        ipdl.genipdl(ast, codedir)
169
170if not ipdl.checkFixedSyncMessages(parser):
171    # Errors have alraedy been printed to stderr, just exit
172    sys.exit(1)
173
174# Second pass: generate code
175for f in files:
176    # Read from parser cache
177    filename = normalizedFilename(f)
178    ast = ipdl.parse(None, filename, includedirs=includedirs)
179    ipdl.gencxx(filename, ast, headersdir, cppdir, segmentCapacityDict)
180
181    if ast.protocol:
182        allmessages[ast.protocol.name] = ipdl.genmsgenum(ast)
183        allprotocols.append('%sMsgStart' % ast.protocol.name)
184        # e.g. PContent::RequestMemoryReport (not prefixed or suffixed.)
185        for md in ast.protocol.messageDecls:
186            allmessageprognames.append('%s::%s' % (md.namespace, md.decl.progname))
187
188allprotocols.sort()
189
190# Check if we have undefined message names in segmentCapacityDict.
191# This is a fool-proof of the 'message-metadata.ini' file.
192undefinedMessages = set(segmentCapacityDict.keys()) - set(allmessageprognames)
193if len(undefinedMessages) > 0:
194    print >>sys.stderr, 'Error: Undefined message names in message-metadata.ini:'
195    print >>sys.stderr, undefinedMessages
196    sys.exit(1)
197
198ipcmsgstart = StringIO()
199
200print >>ipcmsgstart, """
201// CODE GENERATED by ipdl.py. Do not edit.
202
203#ifndef IPCMessageStart_h
204#define IPCMessageStart_h
205
206enum IPCMessageStart {
207"""
208
209for name in allprotocols:
210    print >>ipcmsgstart, "  %s," % name
211
212print >>ipcmsgstart, """
213  LastMsgIndex
214};
215
216static_assert(LastMsgIndex <= 65536, "need to update IPC_MESSAGE_MACRO");
217
218#endif // ifndef IPCMessageStart_h
219"""
220
221ipc_msgtype_name = StringIO()
222print >>ipc_msgtype_name, """
223// CODE GENERATED by ipdl.py. Do not edit.
224#include <cstdint>
225
226#include "mozilla/ipc/ProtocolUtils.h"
227#include "IPCMessageStart.h"
228
229using std::uint32_t;
230
231namespace {
232
233enum IPCMessages {
234"""
235
236for protocol in sorted(allmessages.keys()):
237    for (msg, num) in allmessages[protocol].idnums:
238        if num:
239            print >>ipc_msgtype_name, "  %s = %s," % (msg, num)
240        elif not msg.endswith('End'):
241            print >>ipc_msgtype_name,  "  %s__%s," % (protocol, msg)
242
243print >>ipc_msgtype_name, """
244};
245
246} // anonymous namespace
247
248namespace IPC {
249
250const char* StringFromIPCMessageType(uint32_t aMessageType)
251{
252  switch (aMessageType) {
253"""
254
255for protocol in sorted(allmessages.keys()):
256    for (msg, num) in allmessages[protocol].idnums:
257        if num or msg.endswith('End'):
258            continue
259        print >>ipc_msgtype_name, """
260  case %s__%s:
261    return "%s::%s";""" % (protocol, msg, protocol, msg)
262
263print >>ipc_msgtype_name, """
264  case CHANNEL_OPENED_MESSAGE_TYPE:
265    return "CHANNEL_OPENED_MESSAGE";
266  case SHMEM_DESTROYED_MESSAGE_TYPE:
267    return "SHMEM_DESTROYED_MESSAGE";
268  case SHMEM_CREATED_MESSAGE_TYPE:
269    return "SHMEM_CREATED_MESSAGE";
270  case GOODBYE_MESSAGE_TYPE:
271    return "GOODBYE_MESSAGE";
272  case CANCEL_MESSAGE_TYPE:
273    return "CANCEL_MESSAGE";
274  default:
275    return "<unknown IPC msg name>";
276  }
277}
278
279} // namespace IPC
280"""
281
282ipdl.writeifmodified(ipcmsgstart.getvalue(), ipcmessagestartpath)
283ipdl.writeifmodified(ipc_msgtype_name.getvalue(), ipc_msgtype_name_path)
284