Bug 893976 - Add a mach command to update uuids for specific interfaces and their descendants. r=gps
authorMike Hommey <mh+mozilla@glandium.org>
Tue, 30 Jul 2013 08:57:28 +0900
changeset 140482 9251bba8a9b2d99137551659c214ac17b5dee1cc
parent 140481 03f984a3bd2a4d07ce5751c78fceabaf9008e58c
child 140483 f92a6dcf7361edd3ef7ea7d18c82cf2140bc81cc
push idunknown
push userunknown
push dateunknown
reviewersgps
bugs893976
milestone25.0a1
Bug 893976 - Add a mach command to update uuids for specific interfaces and their descendants. r=gps
build/mach_bootstrap.py
tools/mach_commands.py
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -45,16 +45,17 @@ SEARCH_PATHS = [
     'testing/mozbase/mozfile',
     'testing/mozbase/mozhttpd',
     'testing/mozbase/mozlog',
     'testing/mozbase/moznetwork',
     'testing/mozbase/mozprocess',
     'testing/mozbase/mozprofile',
     'testing/mozbase/mozrunner',
     'testing/mozbase/mozinfo',
+    'xpcom/idl-parser',
 ]
 
 # Individual files providing mach commands.
 MACH_MODULES = [
     'addon-sdk/mach_commands.py',
     'layout/tools/reftest/mach_commands.py',
     'python/mach_commands.py',
     'python/mach/mach/commands/commandinfo.py',
--- a/tools/mach_commands.py
+++ b/tools/mach_commands.py
@@ -55,24 +55,140 @@ class SearchProvider(object):
         'MDN, and MXR.')
     @CommandArgument('term', nargs='+', help='Term(s) to search for.')
     def search(self, term):
         self.google(term)
         self.mdn(term)
         self.mxr(term)
 
 
+class Interface(object):
+    '''
+    Represents an XPIDL interface, in what file it is defined, what it derives
+    from, what its uuid is, and where in the source file the uuid is.
+    '''
+    def __init__(self, filename, production):
+        import xpidl
+        assert isinstance(production, xpidl.Interface)
+        self.name = production.name
+        self.base = production.base
+        self.filename = filename
+        self.uuid = production.attributes.uuid
+        location = production.location
+        data = location._lexdata
+        attr_pos = data.rfind(b'[', 0, location._lexpos)
+        # uuid is always lowercase, but actual file content may not be.
+        self.uuid_pos = data[attr_pos:location._lexpos].lower() \
+                        .rfind(self.uuid) + attr_pos
+
+
+class InterfaceRegistry(object):
+    '''
+    Tracks XPIDL interfaces, and allow to search them by name and by the
+    interface they derive from.
+    '''
+    def __init__(self):
+        self.by_name = {}
+        self.by_base = {}
+
+    def get_by_name(self, name):
+        return self.by_name.get(name, [])
+
+    def get_by_base(self, base):
+        return self.by_base.get(base, [])
+
+    def add(self, interface):
+        l = self.by_name.setdefault(interface.name, [])
+        l.append(interface)
+        l = self.by_base.setdefault(interface.base, [])
+        l.append(interface)
+
+
+class IDLUpdater(object):
+    '''
+    Updates interfaces uuids in IDL files.
+    '''
+    def __init__(self, interfaces):
+        from mozpack.copier import FileRegistry
+        self.interfaces = interfaces;
+        self.registry = FileRegistry()
+
+    def add(self, name):
+        for interface in self.interfaces.get_by_name(name):
+            self._add(interface)
+
+    def _add(self, interface):
+        from mozpack.files import GeneratedFile
+        from uuid import uuid4
+        path = interface.filename
+        if not self.registry.contains(path):
+            self.registry.add(path, GeneratedFile(open(path).read()))
+        content = self.registry[path].content
+        content = content[:interface.uuid_pos] + str(uuid4()) + \
+                  content[interface.uuid_pos + len(interface.uuid):]
+        self.registry[path].content = content
+
+        # Recurse through all the interfaces deriving from this one
+        for derived in self.interfaces.get_by_base(interface.name):
+            self._add(derived)
+
+    def update(self):
+        for p, f in self.registry:
+            f.copy(p)
+
+
 @CommandProvider
 class UUIDProvider(object):
     @Command('uuid', category='misc',
         description='Generate a uuid.')
     @CommandArgument('--format', '-f', choices=['idl', 'cpp'], default='idl',
                      help='Output format for the generated uuid.')
     def uuid(self, format):
         import uuid
         u = uuid.uuid4()
         if format == 'idl':
             print(u)
         else:
             u = u.hex
             print('{ 0x%s, 0x%s, 0x%s, \\' % (u[0:8], u[8:12], u[12:16]))
             pairs = tuple(map(lambda n: u[n:n+2], range(16, 32, 2)))
             print(('  { ' + '0x%s, ' * 7 + '0x%s } }') % pairs)
+
+    @Command('update-uuids', category='misc',
+        description='Update IDL files with new UUIDs.')
+    @CommandArgument('--path', default='.',
+                     help='Base path under which uuids will be searched.')
+    @CommandArgument('interfaces', nargs='+',
+                     help='Changed interfaces whose UUIDs need to be updated. ' +
+                          'Their descendants are updated as well.')
+    def update_uuids(self, path, interfaces):
+        import os
+        import xpidl
+        from mozpack.files import FileFinder
+        import mozpack.path
+        from tempfile import mkdtemp
+
+        finder = FileFinder(path, find_executables=False)
+        # Avoid creating xpidllex and xpidlyacc in the current directory.
+        tmpdir = mkdtemp()
+        try:
+            parser = xpidl.IDLParser(outputdir=tmpdir)
+            registry = InterfaceRegistry()
+            for p, f in finder.find('**/*.idl'):
+                p = mozpack.path.join(path, p)
+                try:
+                    content = f.open().read()
+                    idl = parser.parse(content, filename=p)
+                except Exception:
+                    continue
+                for prod in idl.productions:
+                    if isinstance(prod, xpidl.Interface):
+                         registry.add(Interface(p, prod))
+        finally:
+            import shutil
+            shutil.rmtree(tmpdir)
+
+        updates = IDLUpdater(registry)
+
+        for interface in interfaces:
+            updates.add(interface)
+
+        updates.update()