Blame ipc/ipdl/ipdl.py

Packit f0b94e
# This Source Code Form is subject to the terms of the Mozilla Public
Packit f0b94e
# License, v. 2.0. If a copy of the MPL was not distributed with this
Packit f0b94e
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Packit f0b94e
Packit f0b94e
import optparse, os, re, sys
Packit f0b94e
from cStringIO import StringIO
Packit f0b94e
from mozbuild.pythonutil import iter_modules_in_path
Packit f0b94e
import mozpack.path as mozpath
Packit f0b94e
import itertools
Packit f0b94e
from ConfigParser import RawConfigParser
Packit f0b94e
Packit f0b94e
import ipdl
Packit f0b94e
Packit f0b94e
def log(minv, fmt, *args):
Packit f0b94e
    if _verbosity >= minv:
Packit f0b94e
        print fmt % args
Packit f0b94e
Packit f0b94e
# process command line
Packit f0b94e
Packit f0b94e
op = optparse.OptionParser(usage='ipdl.py [options] IPDLfiles...')
Packit f0b94e
op.add_option('-I', '--include', dest='includedirs', default=[ ],
Packit f0b94e
              action='append',
Packit f0b94e
              help='Additional directory to search for included protocol specifications')
Packit f0b94e
op.add_option('-s', '--sync-msg-list', dest='syncMsgList', default='sync-messages.ini',
Packit f0b94e
              help="Config file listing allowed sync messages")
Packit f0b94e
op.add_option('-m', '--msg-metadata', dest='msgMetadata', default='message-metadata.ini',
Packit f0b94e
              help="Predicted message sizes for reducing serialization malloc overhead.")
Packit f0b94e
op.add_option('-v', '--verbose', dest='verbosity', default=1, action='count',
Packit f0b94e
              help='Verbose logging (specify -vv or -vvv for very verbose logging)')
Packit f0b94e
op.add_option('-q', '--quiet', dest='verbosity', action='store_const', const=0,
Packit f0b94e
              help="Suppress logging output")
Packit f0b94e
op.add_option('-d', '--outheaders-dir', dest='headersdir', default='.',
Packit f0b94e
              help="""Directory into which C++ headers will be generated.
Packit f0b94e
A protocol Foo in the namespace bar will cause the headers
Packit f0b94e
  dir/bar/Foo.h, dir/bar/FooParent.h, and dir/bar/FooParent.h
Packit f0b94e
to be generated""")
Packit f0b94e
op.add_option('-o', '--outcpp-dir', dest='cppdir', default='.',
Packit f0b94e
              help="""Directory into which C++ sources will be generated
Packit f0b94e
A protocol Foo in the namespace bar will cause the sources
Packit f0b94e
  cppdir/FooParent.cpp, cppdir/FooChild.cpp
Packit f0b94e
to be generated""")
Packit f0b94e
Packit f0b94e
options, files = op.parse_args()
Packit f0b94e
_verbosity = options.verbosity
Packit f0b94e
syncMsgList = options.syncMsgList
Packit f0b94e
msgMetadata = options.msgMetadata
Packit f0b94e
headersdir = options.headersdir
Packit f0b94e
cppdir = options.cppdir
Packit f0b94e
includedirs = [ os.path.abspath(incdir) for incdir in options.includedirs ]
Packit f0b94e
Packit f0b94e
if not len(files):
Packit f0b94e
    op.error("No IPDL files specified")
Packit f0b94e
Packit f0b94e
ipcmessagestartpath = os.path.join(headersdir, 'IPCMessageStart.h')
Packit f0b94e
ipc_msgtype_name_path = os.path.join(cppdir, 'IPCMessageTypeName.cpp')
Packit f0b94e
Packit f0b94e
# Compiling the IPDL files can take a long time, even on a fast machine.
Packit f0b94e
# Check to see whether we need to do any work.
Packit f0b94e
latestipdlmod = max(os.stat(f).st_mtime
Packit f0b94e
                    for f in itertools.chain(files,
Packit f0b94e
                                             iter_modules_in_path(mozpath.dirname(__file__))))
Packit f0b94e
Packit f0b94e
def outputModTime(f):
Packit f0b94e
    # A non-existant file is newer than everything.
Packit f0b94e
    if not os.path.exists(f):
Packit f0b94e
        return 0
Packit f0b94e
    return os.stat(f).st_mtime
Packit f0b94e
Packit f0b94e
# Because the IPDL headers are placed into directories reflecting their
Packit f0b94e
# namespace, collect a list here so we can easily map output names without
Packit f0b94e
# parsing the actual IPDL files themselves.
Packit f0b94e
headersmap = {}
Packit f0b94e
for (path, dirs, headers) in os.walk(headersdir):
Packit f0b94e
    for h in headers:
Packit f0b94e
        base = os.path.basename(h)
Packit f0b94e
        if base in headersmap:
Packit f0b94e
            root, ext = os.path.splitext(base)
Packit f0b94e
            print >>sys.stderr, 'A protocol named', root, 'exists in multiple namespaces'
Packit f0b94e
            sys.exit(1)
Packit f0b94e
        headersmap[base] = os.path.join(path, h)
Packit f0b94e
Packit f0b94e
def outputfiles(f):
Packit f0b94e
    base = os.path.basename(f)
Packit f0b94e
    root, ext = os.path.splitext(base)
Packit f0b94e
Packit f0b94e
    suffixes = ['']
Packit f0b94e
    if ext == '.ipdl':
Packit f0b94e
        suffixes += ['Child', 'Parent']
Packit f0b94e
Packit f0b94e
    for suffix in suffixes:
Packit f0b94e
        yield os.path.join(cppdir, "%s%s.cpp" % (root, suffix))
Packit f0b94e
        header = "%s%s.h" % (root, suffix)
Packit f0b94e
        # If the header already exists on disk, use that.  Otherwise,
Packit f0b94e
        # just claim that the header is found in headersdir.
Packit f0b94e
        if header in headersmap:
Packit f0b94e
            yield headersmap[header]
Packit f0b94e
        else:
Packit f0b94e
            yield os.path.join(headersdir, header)
Packit f0b94e
Packit f0b94e
def alloutputfiles():
Packit f0b94e
    for f in files:
Packit f0b94e
        for s in outputfiles(f):
Packit f0b94e
            yield s
Packit f0b94e
    yield ipcmessagestartpath
Packit f0b94e
Packit f0b94e
earliestoutputmod = min(outputModTime(f) for f in alloutputfiles())
Packit f0b94e
Packit f0b94e
if latestipdlmod < earliestoutputmod:
Packit f0b94e
    sys.exit(0)
Packit f0b94e
Packit f0b94e
log(2, 'Generated C++ headers will be generated relative to "%s"', headersdir)
Packit f0b94e
log(2, 'Generated C++ sources will be generated in "%s"', cppdir)
Packit f0b94e
Packit f0b94e
allmessages = {}
Packit f0b94e
allmessageprognames = []
Packit f0b94e
allprotocols = []
Packit f0b94e
Packit f0b94e
def normalizedFilename(f):
Packit f0b94e
    if f == '-':
Packit f0b94e
        return '<stdin>'
Packit f0b94e
    return f
Packit f0b94e
Packit f0b94e
log(2, 'Reading sync message list')
Packit f0b94e
parser = RawConfigParser()
Packit f0b94e
parser.readfp(open(options.syncMsgList))
Packit f0b94e
syncMsgList = parser.sections()
Packit f0b94e
Packit f0b94e
# Read message metadata. Right now we only have 'segment_capacity'
Packit f0b94e
# for the standard segment size used for serialization.
Packit f0b94e
log(2, 'Reading message metadata...')
Packit f0b94e
msgMetadataConfig = RawConfigParser()
Packit f0b94e
msgMetadataConfig.readfp(open(options.msgMetadata))
Packit f0b94e
Packit f0b94e
segmentCapacityDict = {}
Packit f0b94e
for msgName in msgMetadataConfig.sections():
Packit f0b94e
    if msgMetadataConfig.has_option(msgName, 'segment_capacity'):
Packit f0b94e
        capacity = msgMetadataConfig.get(msgName, 'segment_capacity')
Packit f0b94e
        segmentCapacityDict[msgName] = capacity
Packit f0b94e
Packit f0b94e
# First pass: parse and type-check all protocols
Packit f0b94e
for f in files:
Packit f0b94e
    log(2, os.path.basename(f))
Packit f0b94e
    filename = normalizedFilename(f)
Packit f0b94e
    if f == '-':
Packit f0b94e
        fd = sys.stdin
Packit f0b94e
    else:
Packit f0b94e
        fd = open(f)
Packit f0b94e
Packit f0b94e
    specstring = fd.read()
Packit f0b94e
    fd.close()
Packit f0b94e
Packit f0b94e
    ast = ipdl.parse(specstring, filename, includedirs=includedirs)
Packit f0b94e
    if ast is None:
Packit f0b94e
        print >>sys.stderr, 'Specification could not be parsed.'
Packit f0b94e
        sys.exit(1)
Packit f0b94e
Packit f0b94e
    log(2, 'checking types')
Packit f0b94e
    if not ipdl.typecheck(ast):
Packit f0b94e
        print >>sys.stderr, 'Specification is not well typed.'
Packit f0b94e
        sys.exit(1)
Packit f0b94e
Packit f0b94e
    if not ipdl.checkSyncMessage(ast, syncMsgList):
Packit f0b94e
        print >>sys.stderr, 'Error: New sync IPC messages must be reviewed by an IPC peer and recorded in %s' % options.syncMsgList
Packit f0b94e
        sys.exit(1)
Packit f0b94e
Packit f0b94e
    if _verbosity > 2:
Packit f0b94e
        log(3, '  pretty printed code:')
Packit f0b94e
        ipdl.genipdl(ast, codedir)
Packit f0b94e
Packit f0b94e
if not ipdl.checkFixedSyncMessages(parser):
Packit f0b94e
    # Errors have alraedy been printed to stderr, just exit
Packit f0b94e
    sys.exit(1)
Packit f0b94e
Packit f0b94e
# Second pass: generate code
Packit f0b94e
for f in files:
Packit f0b94e
    # Read from parser cache
Packit f0b94e
    filename = normalizedFilename(f)
Packit f0b94e
    ast = ipdl.parse(None, filename, includedirs=includedirs)
Packit f0b94e
    ipdl.gencxx(filename, ast, headersdir, cppdir, segmentCapacityDict)
Packit f0b94e
Packit f0b94e
    if ast.protocol:
Packit f0b94e
        allmessages[ast.protocol.name] = ipdl.genmsgenum(ast)
Packit f0b94e
        allprotocols.append('%sMsgStart' % ast.protocol.name)
Packit f0b94e
        # e.g. PContent::RequestMemoryReport (not prefixed or suffixed.)
Packit f0b94e
        for md in ast.protocol.messageDecls:
Packit f0b94e
            allmessageprognames.append('%s::%s' % (md.namespace, md.decl.progname))
Packit f0b94e
Packit f0b94e
allprotocols.sort()
Packit f0b94e
Packit f0b94e
# Check if we have undefined message names in segmentCapacityDict.
Packit f0b94e
# This is a fool-proof of the 'message-metadata.ini' file.
Packit f0b94e
undefinedMessages = set(segmentCapacityDict.keys()) - set(allmessageprognames)
Packit f0b94e
if len(undefinedMessages) > 0:
Packit f0b94e
    print >>sys.stderr, 'Error: Undefined message names in message-metadata.ini:'
Packit f0b94e
    print >>sys.stderr, undefinedMessages
Packit f0b94e
    sys.exit(1)
Packit f0b94e
Packit f0b94e
ipcmsgstart = StringIO()
Packit f0b94e
Packit f0b94e
print >>ipcmsgstart, """
Packit f0b94e
// CODE GENERATED by ipdl.py. Do not edit.
Packit f0b94e
Packit f0b94e
#ifndef IPCMessageStart_h
Packit f0b94e
#define IPCMessageStart_h
Packit f0b94e
Packit f0b94e
enum IPCMessageStart {
Packit f0b94e
"""
Packit f0b94e
Packit f0b94e
for name in allprotocols:
Packit f0b94e
    print >>ipcmsgstart, "  %s," % name
Packit f0b94e
Packit f0b94e
print >>ipcmsgstart, """
Packit f0b94e
  LastMsgIndex
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
static_assert(LastMsgIndex <= 65536, "need to update IPC_MESSAGE_MACRO");
Packit f0b94e
Packit f0b94e
#endif // ifndef IPCMessageStart_h
Packit f0b94e
"""
Packit f0b94e
Packit f0b94e
ipc_msgtype_name = StringIO()
Packit f0b94e
print >>ipc_msgtype_name, """
Packit f0b94e
// CODE GENERATED by ipdl.py. Do not edit.
Packit f0b94e
#include <cstdint>
Packit f0b94e
Packit f0b94e
#include "mozilla/ipc/ProtocolUtils.h"
Packit f0b94e
#include "IPCMessageStart.h"
Packit f0b94e
Packit f0b94e
using std::uint32_t;
Packit f0b94e
Packit f0b94e
namespace {
Packit f0b94e
Packit f0b94e
enum IPCMessages {
Packit f0b94e
"""
Packit f0b94e
Packit f0b94e
for protocol in sorted(allmessages.keys()):
Packit f0b94e
    for (msg, num) in allmessages[protocol].idnums:
Packit f0b94e
        if num:
Packit f0b94e
            print >>ipc_msgtype_name, "  %s = %s," % (msg, num)
Packit f0b94e
        elif not msg.endswith('End'):
Packit f0b94e
            print >>ipc_msgtype_name,  "  %s__%s," % (protocol, msg)
Packit f0b94e
Packit f0b94e
print >>ipc_msgtype_name, """
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
} // anonymous namespace
Packit f0b94e
Packit f0b94e
namespace IPC {
Packit f0b94e
Packit f0b94e
const char* StringFromIPCMessageType(uint32_t aMessageType)
Packit f0b94e
{
Packit f0b94e
  switch (aMessageType) {
Packit f0b94e
"""
Packit f0b94e
Packit f0b94e
for protocol in sorted(allmessages.keys()):
Packit f0b94e
    for (msg, num) in allmessages[protocol].idnums:
Packit f0b94e
        if num or msg.endswith('End'):
Packit f0b94e
            continue
Packit f0b94e
        print >>ipc_msgtype_name, """
Packit f0b94e
  case %s__%s:
Packit f0b94e
    return "%s::%s";""" % (protocol, msg, protocol, msg)
Packit f0b94e
Packit f0b94e
print >>ipc_msgtype_name, """
Packit f0b94e
  case CHANNEL_OPENED_MESSAGE_TYPE:
Packit f0b94e
    return "CHANNEL_OPENED_MESSAGE";
Packit f0b94e
  case SHMEM_DESTROYED_MESSAGE_TYPE:
Packit f0b94e
    return "SHMEM_DESTROYED_MESSAGE";
Packit f0b94e
  case SHMEM_CREATED_MESSAGE_TYPE:
Packit f0b94e
    return "SHMEM_CREATED_MESSAGE";
Packit f0b94e
  case GOODBYE_MESSAGE_TYPE:
Packit f0b94e
    return "GOODBYE_MESSAGE";
Packit f0b94e
  case CANCEL_MESSAGE_TYPE:
Packit f0b94e
    return "CANCEL_MESSAGE";
Packit f0b94e
  default:
Packit f0b94e
    return "<unknown IPC msg name>";
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
} // namespace IPC
Packit f0b94e
"""
Packit f0b94e
Packit f0b94e
ipdl.writeifmodified(ipcmsgstart.getvalue(), ipcmessagestartpath)
Packit f0b94e
ipdl.writeifmodified(ipc_msgtype_name.getvalue(), ipc_msgtype_name_path)