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