michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import optparse, os, re, sys michael@0: from cStringIO import StringIO michael@0: from mozbuild.pythonutil import iter_modules_in_path michael@0: import mozpack.path as mozpath michael@0: import itertools michael@0: michael@0: import ipdl michael@0: michael@0: def log(minv, fmt, *args): michael@0: if _verbosity >= minv: michael@0: print fmt % args michael@0: michael@0: # process command line michael@0: michael@0: op = optparse.OptionParser(usage='ipdl.py [options] IPDLfiles...') michael@0: op.add_option('-I', '--include', dest='includedirs', default=[ ], michael@0: action='append', michael@0: help='Additional directory to search for included protocol specifications') michael@0: op.add_option('-v', '--verbose', dest='verbosity', default=1, action='count', michael@0: help='Verbose logging (specify -vv or -vvv for very verbose logging)') michael@0: op.add_option('-q', '--quiet', dest='verbosity', action='store_const', const=0, michael@0: help="Suppress logging output") michael@0: op.add_option('-d', '--outheaders-dir', dest='headersdir', default='.', michael@0: help="""Directory into which C++ headers will be generated. michael@0: A protocol Foo in the namespace bar will cause the headers michael@0: dir/bar/Foo.h, dir/bar/FooParent.h, and dir/bar/FooParent.h michael@0: to be generated""") michael@0: op.add_option('-o', '--outcpp-dir', dest='cppdir', default='.', michael@0: help="""Directory into which C++ sources will be generated michael@0: A protocol Foo in the namespace bar will cause the sources michael@0: cppdir/FooParent.cpp, cppdir/FooChild.cpp michael@0: to be generated""") michael@0: michael@0: michael@0: options, files = op.parse_args() michael@0: _verbosity = options.verbosity michael@0: headersdir = options.headersdir michael@0: cppdir = options.cppdir michael@0: includedirs = [ os.path.abspath(incdir) for incdir in options.includedirs ] michael@0: michael@0: if not len(files): michael@0: op.error("No IPDL files specified") michael@0: michael@0: ipcmessagestartpath = os.path.join(headersdir, 'IPCMessageStart.h') michael@0: michael@0: # Compiling the IPDL files can take a long time, even on a fast machine. michael@0: # Check to see whether we need to do any work. michael@0: latestipdlmod = max(os.stat(f).st_mtime michael@0: for f in itertools.chain(files, michael@0: iter_modules_in_path(mozpath.dirname(__file__)))) michael@0: michael@0: def outputModTime(f): michael@0: # A non-existant file is newer than everything. michael@0: if not os.path.exists(f): michael@0: return 0 michael@0: return os.stat(f).st_mtime michael@0: michael@0: # Because the IPDL headers are placed into directories reflecting their michael@0: # namespace, collect a list here so we can easily map output names without michael@0: # parsing the actual IPDL files themselves. michael@0: headersmap = {} michael@0: for (path, dirs, headers) in os.walk(headersdir): michael@0: for h in headers: michael@0: base = os.path.basename(h) michael@0: if base in headersmap: michael@0: root, ext = os.path.splitext(base) michael@0: print >>sys.stderr, 'A protocol named', root, 'exists in multiple namespaces' michael@0: sys.exit(1) michael@0: headersmap[base] = os.path.join(path, h) michael@0: michael@0: def outputfiles(f): michael@0: base = os.path.basename(f) michael@0: root, ext = os.path.splitext(base) michael@0: michael@0: suffixes = [''] michael@0: if ext == '.ipdl': michael@0: suffixes += ['Child', 'Parent'] michael@0: michael@0: for suffix in suffixes: michael@0: yield os.path.join(cppdir, "%s%s.cpp" % (root, suffix)) michael@0: header = "%s%s.h" % (root, suffix) michael@0: # If the header already exists on disk, use that. Otherwise, michael@0: # just claim that the header is found in headersdir. michael@0: if header in headersmap: michael@0: yield headersmap[header] michael@0: else: michael@0: yield os.path.join(headersdir, header) michael@0: michael@0: def alloutputfiles(): michael@0: for f in files: michael@0: for s in outputfiles(f): michael@0: yield s michael@0: yield ipcmessagestartpath michael@0: michael@0: earliestoutputmod = min(outputModTime(f) for f in alloutputfiles()) michael@0: michael@0: if latestipdlmod < earliestoutputmod: michael@0: sys.exit(0) michael@0: michael@0: log(2, 'Generated C++ headers will be generated relative to "%s"', headersdir) michael@0: log(2, 'Generated C++ sources will be generated in "%s"', cppdir) michael@0: michael@0: allprotocols = [] michael@0: michael@0: def normalizedFilename(f): michael@0: if f == '-': michael@0: return '' michael@0: return f michael@0: michael@0: # First pass: parse and type-check all protocols michael@0: for f in files: michael@0: log(2, os.path.basename(f)) michael@0: filename = normalizedFilename(f) michael@0: if f == '-': michael@0: fd = sys.stdin michael@0: else: michael@0: fd = open(f) michael@0: michael@0: specstring = fd.read() michael@0: fd.close() michael@0: michael@0: ast = ipdl.parse(specstring, filename, includedirs=includedirs) michael@0: if ast is None: michael@0: print >>sys.stderr, 'Specification could not be parsed.' michael@0: sys.exit(1) michael@0: michael@0: log(2, 'checking types') michael@0: if not ipdl.typecheck(ast): michael@0: print >>sys.stderr, 'Specification is not well typed.' michael@0: sys.exit(1) michael@0: michael@0: if _verbosity > 2: michael@0: log(3, ' pretty printed code:') michael@0: ipdl.genipdl(ast, codedir) michael@0: michael@0: # Second pass: generate code michael@0: for f in files: michael@0: # Read from parser cache michael@0: filename = normalizedFilename(f) michael@0: ast = ipdl.parse(None, filename, includedirs=includedirs) michael@0: ipdl.gencxx(filename, ast, headersdir, cppdir) michael@0: michael@0: if ast.protocol: michael@0: allprotocols.append('%sMsgStart' % ast.protocol.name) michael@0: michael@0: michael@0: allprotocols.sort() michael@0: michael@0: ipcmsgstart = StringIO() michael@0: michael@0: print >>ipcmsgstart, """ michael@0: // CODE GENERATED by ipdl.py. Do not edit. michael@0: michael@0: #ifndef IPCMessageStart_h michael@0: #define IPCMessageStart_h michael@0: michael@0: enum IPCMessageStart { michael@0: """ michael@0: michael@0: for name in allprotocols: michael@0: print >>ipcmsgstart, " %s," % name michael@0: print >>ipcmsgstart, " %sChild," % name michael@0: michael@0: print >>ipcmsgstart, """ michael@0: LastMsgIndex michael@0: }; michael@0: michael@0: static_assert(LastMsgIndex <= 65536, "need to update IPC_MESSAGE_MACRO"); michael@0: michael@0: #endif // ifndef IPCMessageStart_h michael@0: """ michael@0: michael@0: ipdl.writeifmodified(ipcmsgstart.getvalue(), ipcmessagestartpath)