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