tools/mach_commands.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/tools/mach_commands.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,396 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +from __future__ import unicode_literals
     1.9 +
    1.10 +import sys
    1.11 +import os
    1.12 +import stat
    1.13 +import platform
    1.14 +import urllib2
    1.15 +import errno
    1.16 +
    1.17 +from mach.decorators import (
    1.18 +    CommandArgument,
    1.19 +    CommandProvider,
    1.20 +    Command,
    1.21 +)
    1.22 +
    1.23 +from mozbuild.base import MachCommandBase
    1.24 +
    1.25 +
    1.26 +@CommandProvider
    1.27 +class SearchProvider(object):
    1.28 +    @Command('mxr', category='misc',
    1.29 +        description='Search for something in MXR.')
    1.30 +    @CommandArgument('term', nargs='+', help='Term(s) to search for.')
    1.31 +    def mxr(self, term):
    1.32 +        import webbrowser
    1.33 +        term = ' '.join(term)
    1.34 +        uri = 'https://mxr.mozilla.org/mozilla-central/search?string=%s' % term
    1.35 +        webbrowser.open_new_tab(uri)
    1.36 +
    1.37 +    @Command('dxr', category='misc',
    1.38 +        description='Search for something in DXR.')
    1.39 +    @CommandArgument('term', nargs='+', help='Term(s) to search for.')
    1.40 +    def dxr(self, term):
    1.41 +        import webbrowser
    1.42 +        term = ' '.join(term)
    1.43 +        uri = 'http://dxr.mozilla.org/search?tree=mozilla-central&q=%s' % term
    1.44 +        webbrowser.open_new_tab(uri)
    1.45 +
    1.46 +    @Command('mdn', category='misc',
    1.47 +        description='Search for something on MDN.')
    1.48 +    @CommandArgument('term', nargs='+', help='Term(s) to search for.')
    1.49 +    def mdn(self, term):
    1.50 +        import webbrowser
    1.51 +        term = ' '.join(term)
    1.52 +        uri = 'https://developer.mozilla.org/search?q=%s' % term
    1.53 +        webbrowser.open_new_tab(uri)
    1.54 +
    1.55 +    @Command('google', category='misc',
    1.56 +        description='Search for something on Google.')
    1.57 +    @CommandArgument('term', nargs='+', help='Term(s) to search for.')
    1.58 +    def google(self, term):
    1.59 +        import webbrowser
    1.60 +        term = ' '.join(term)
    1.61 +        uri = 'https://www.google.com/search?q=%s' % term
    1.62 +        webbrowser.open_new_tab(uri)
    1.63 +
    1.64 +    @Command('search', category='misc',
    1.65 +        description='Search for something on the Internets. '
    1.66 +        'This will open 3 new browser tabs and search for the term on Google, '
    1.67 +        'MDN, and MXR.')
    1.68 +    @CommandArgument('term', nargs='+', help='Term(s) to search for.')
    1.69 +    def search(self, term):
    1.70 +        self.google(term)
    1.71 +        self.mdn(term)
    1.72 +        self.mxr(term)
    1.73 +
    1.74 +
    1.75 +class Interface(object):
    1.76 +    '''
    1.77 +    Represents an XPIDL interface, in what file it is defined, what it derives
    1.78 +    from, what its uuid is, and where in the source file the uuid is.
    1.79 +    '''
    1.80 +    def __init__(self, filename, production):
    1.81 +        import xpidl
    1.82 +        assert isinstance(production, xpidl.Interface)
    1.83 +        self.name = production.name
    1.84 +        self.base = production.base
    1.85 +        self.filename = filename
    1.86 +        self.uuid = production.attributes.uuid
    1.87 +        location = production.location
    1.88 +        data = location._lexdata
    1.89 +        attr_pos = data.rfind(b'[', 0, location._lexpos)
    1.90 +        # uuid is always lowercase, but actual file content may not be.
    1.91 +        self.uuid_pos = data[attr_pos:location._lexpos].lower() \
    1.92 +                        .rfind(self.uuid) + attr_pos
    1.93 +
    1.94 +
    1.95 +class InterfaceRegistry(object):
    1.96 +    '''
    1.97 +    Tracks XPIDL interfaces, and allow to search them by name and by the
    1.98 +    interface they derive from.
    1.99 +    '''
   1.100 +    def __init__(self):
   1.101 +        self.by_name = {}
   1.102 +        self.by_base = {}
   1.103 +
   1.104 +    def get_by_name(self, name):
   1.105 +        return self.by_name.get(name, [])
   1.106 +
   1.107 +    def get_by_base(self, base):
   1.108 +        return self.by_base.get(base, [])
   1.109 +
   1.110 +    def add(self, interface):
   1.111 +        l = self.by_name.setdefault(interface.name, [])
   1.112 +        l.append(interface)
   1.113 +        l = self.by_base.setdefault(interface.base, [])
   1.114 +        l.append(interface)
   1.115 +
   1.116 +
   1.117 +class IDLUpdater(object):
   1.118 +    '''
   1.119 +    Updates interfaces uuids in IDL files.
   1.120 +    '''
   1.121 +    def __init__(self, interfaces):
   1.122 +        from mozpack.copier import FileRegistry
   1.123 +        self.interfaces = interfaces;
   1.124 +        self.registry = FileRegistry()
   1.125 +
   1.126 +    def add(self, name):
   1.127 +        for interface in self.interfaces.get_by_name(name):
   1.128 +            self._add(interface)
   1.129 +
   1.130 +    def _add(self, interface):
   1.131 +        from mozpack.files import GeneratedFile
   1.132 +        from uuid import uuid4
   1.133 +        path = interface.filename
   1.134 +        if not self.registry.contains(path):
   1.135 +            self.registry.add(path, GeneratedFile(open(path).read()))
   1.136 +        content = self.registry[path].content
   1.137 +        content = content[:interface.uuid_pos] + str(uuid4()) + \
   1.138 +                  content[interface.uuid_pos + len(interface.uuid):]
   1.139 +        self.registry[path].content = content
   1.140 +
   1.141 +        # Recurse through all the interfaces deriving from this one
   1.142 +        for derived in self.interfaces.get_by_base(interface.name):
   1.143 +            self._add(derived)
   1.144 +
   1.145 +    def update(self):
   1.146 +        for p, f in self.registry:
   1.147 +            f.copy(p)
   1.148 +
   1.149 +
   1.150 +@CommandProvider
   1.151 +class UUIDProvider(object):
   1.152 +    @Command('uuid', category='misc',
   1.153 +        description='Generate a uuid.')
   1.154 +    @CommandArgument('--format', '-f', choices=['idl', 'cpp', 'c++'],
   1.155 +                     help='Output format for the generated uuid.')
   1.156 +    def uuid(self, format=None):
   1.157 +        import uuid
   1.158 +        u = uuid.uuid4()
   1.159 +        if format in [None, 'idl']:
   1.160 +            print(u)
   1.161 +            if format is None:
   1.162 +                print('')
   1.163 +        if format in [None, 'cpp', 'c++']:
   1.164 +            u = u.hex
   1.165 +            print('{ 0x%s, 0x%s, 0x%s, \\' % (u[0:8], u[8:12], u[12:16]))
   1.166 +            pairs = tuple(map(lambda n: u[n:n+2], range(16, 32, 2)))
   1.167 +            print(('  { ' + '0x%s, ' * 7 + '0x%s } }') % pairs)
   1.168 +
   1.169 +    @Command('update-uuids', category='misc',
   1.170 +        description='Update IDL files with new UUIDs.')
   1.171 +    @CommandArgument('--path', default='.',
   1.172 +                     help='Base path under which uuids will be searched.')
   1.173 +    @CommandArgument('interfaces', nargs='+',
   1.174 +                     help='Changed interfaces whose UUIDs need to be updated. ' +
   1.175 +                          'Their descendants are updated as well.')
   1.176 +    def update_uuids(self, path, interfaces):
   1.177 +        import os
   1.178 +        import xpidl
   1.179 +        from mozpack.files import FileFinder
   1.180 +        import mozpack.path
   1.181 +        from tempfile import mkdtemp
   1.182 +
   1.183 +        finder = FileFinder(path, find_executables=False)
   1.184 +        # Avoid creating xpidllex and xpidlyacc in the current directory.
   1.185 +        tmpdir = mkdtemp()
   1.186 +        try:
   1.187 +            parser = xpidl.IDLParser(outputdir=tmpdir)
   1.188 +            registry = InterfaceRegistry()
   1.189 +            for p, f in finder.find('**/*.idl'):
   1.190 +                p = mozpack.path.join(path, p)
   1.191 +                try:
   1.192 +                    content = f.open().read()
   1.193 +                    idl = parser.parse(content, filename=p)
   1.194 +                except Exception:
   1.195 +                    continue
   1.196 +                for prod in idl.productions:
   1.197 +                    if isinstance(prod, xpidl.Interface):
   1.198 +                         registry.add(Interface(p, prod))
   1.199 +        finally:
   1.200 +            import shutil
   1.201 +            shutil.rmtree(tmpdir)
   1.202 +
   1.203 +        updates = IDLUpdater(registry)
   1.204 +
   1.205 +        for interface in interfaces:
   1.206 +            updates.add(interface)
   1.207 +
   1.208 +        updates.update()
   1.209 +
   1.210 +@CommandProvider
   1.211 +class PastebinProvider(object):
   1.212 +    @Command('pastebin', category='misc',
   1.213 +        description='Command line interface to pastebin.mozilla.org.')
   1.214 +    @CommandArgument('--language', default=None,
   1.215 +                     help='Language to use for syntax highlighting')
   1.216 +    @CommandArgument('--poster', default=None,
   1.217 +                     help='Specify your name for use with pastebin.mozilla.org')
   1.218 +    @CommandArgument('--duration', default='day',
   1.219 +                     choices=['d', 'day', 'm', 'month', 'f', 'forever'],
   1.220 +                     help='Keep for specified duration (default: %(default)s)')
   1.221 +    @CommandArgument('file', nargs='?', default=None,
   1.222 +                     help='Specify the file to upload to pastebin.mozilla.org')
   1.223 +
   1.224 +    def pastebin(self, language, poster, duration, file):
   1.225 +        import sys
   1.226 +        import urllib
   1.227 +
   1.228 +        URL = 'http://pastebin.mozilla.org/'
   1.229 +
   1.230 +        FILE_TYPES = [{'value': 'text', 'name': 'None', 'extension': 'txt'},
   1.231 +        {'value': 'bash', 'name': 'Bash', 'extension': 'sh'},
   1.232 +        {'value': 'c', 'name': 'C', 'extension': 'c'},
   1.233 +        {'value': 'cpp', 'name': 'C++', 'extension': 'cpp'},
   1.234 +        {'value': 'html4strict', 'name': 'HTML', 'extension': 'html'},
   1.235 +        {'value': 'javascript', 'name': 'Javascript', 'extension': 'js'},
   1.236 +        {'value': 'javascript', 'name': 'Javascript', 'extension': 'jsm'},
   1.237 +        {'value': 'lua', 'name': 'Lua', 'extension': 'lua'},
   1.238 +        {'value': 'perl', 'name': 'Perl', 'extension': 'pl'},
   1.239 +        {'value': 'php', 'name': 'PHP', 'extension': 'php'},
   1.240 +        {'value': 'python', 'name': 'Python', 'extension': 'py'},
   1.241 +        {'value': 'ruby', 'name': 'Ruby', 'extension': 'rb'},
   1.242 +        {'value': 'css', 'name': 'CSS', 'extension': 'css'},
   1.243 +        {'value': 'diff', 'name': 'Diff', 'extension': 'diff'},
   1.244 +        {'value': 'ini', 'name': 'INI file', 'extension': 'ini'},
   1.245 +        {'value': 'java', 'name': 'Java', 'extension': 'java'},
   1.246 +        {'value': 'xml', 'name': 'XML', 'extension': 'xml'},
   1.247 +        {'value': 'xml', 'name': 'XML', 'extension': 'xul'}]
   1.248 +
   1.249 +        lang = ''
   1.250 +
   1.251 +        if file:
   1.252 +            try:
   1.253 +                with open(file, 'r') as f:
   1.254 +                    content = f.read()
   1.255 +                # TODO: Use mime-types instead of extensions; suprocess('file <f_name>')
   1.256 +                # Guess File-type based on file extension
   1.257 +                extension = file.split('.')[-1]
   1.258 +                for l in FILE_TYPES:
   1.259 +                    if extension == l['extension']:
   1.260 +                        print('Identified file as %s' % l['name'])
   1.261 +                        lang = l['value']
   1.262 +            except IOError:
   1.263 +                print('ERROR. No such file')
   1.264 +                return 1
   1.265 +        else:
   1.266 +            content = sys.stdin.read()
   1.267 +        duration = duration[0]
   1.268 +
   1.269 +        if language:
   1.270 +            lang = language
   1.271 +
   1.272 +
   1.273 +        params = [
   1.274 +            ('parent_pid', ''),
   1.275 +            ('format', lang),
   1.276 +            ('code2', content),
   1.277 +            ('poster', poster),
   1.278 +            ('expiry', duration),
   1.279 +            ('paste', 'Send')]
   1.280 +
   1.281 +        data = urllib.urlencode(params)
   1.282 +        print('Uploading ...')
   1.283 +        try:
   1.284 +            req = urllib2.Request(URL, data)
   1.285 +            response = urllib2.urlopen(req)
   1.286 +            http_response_code = response.getcode()
   1.287 +            if http_response_code == 200:
   1.288 +                print(response.geturl())
   1.289 +            else:
   1.290 +                print('Could not upload the file, '
   1.291 +                      'HTTP Response Code %s' %(http_response_code))
   1.292 +        except urllib2.URLError:
   1.293 +            print('ERROR. Could not connect to pastebin.mozilla.org.')
   1.294 +            return 1
   1.295 +        return 0
   1.296 +
   1.297 +
   1.298 +@CommandProvider
   1.299 +class ReviewboardToolsProvider(MachCommandBase):
   1.300 +    @Command('rbt', category='devenv', allow_all_args=True,
   1.301 +        description='Run Reviewboard Tools')
   1.302 +    @CommandArgument('args', nargs='...', help='Arguments to rbt tool')
   1.303 +    def rbt(self, args):
   1.304 +        if not args:
   1.305 +            args = ['help']
   1.306 +
   1.307 +        self._activate_virtualenv()
   1.308 +        self.virtualenv_manager.install_pip_package('RBTools==0.6')
   1.309 +
   1.310 +        from rbtools.commands.main import main
   1.311 +
   1.312 +        # main() doesn't accept arguments and instead reads from sys.argv. So,
   1.313 +        # we fake it out.
   1.314 +        sys.argv = ['rbt'] + args
   1.315 +        return main()
   1.316 +
   1.317 +@CommandProvider
   1.318 +class FormatProvider(MachCommandBase):
   1.319 +    @Command('clang-format', category='misc',
   1.320 +        description='Run clang-format on current changes')
   1.321 +    @CommandArgument('--show', '-s', action = 'store_true',
   1.322 +        help = 'Show diff output on instead of applying changes')
   1.323 +    def clang_format(self, show=False):
   1.324 +        plat = platform.system()
   1.325 +        fmt = plat.lower() + "/clang-format-3.5"
   1.326 +        fmt_diff = "clang-format-diff-3.5"
   1.327 +
   1.328 +        # We are currently using a modified verion of clang-format hosted on people.mozilla.org.
   1.329 +        # This is a temporary work around until we upstream the necessary changes and we can use
   1.330 +        # a system version of clang-format. See bug 961541.
   1.331 +        if plat == "Windows":
   1.332 +            fmt += ".exe"
   1.333 +        else:
   1.334 +            arch = os.uname()[4]
   1.335 +            if (plat != "Linux" and plat != "Darwin") or arch != 'x86_64':
   1.336 +                print("Unsupported platform " + plat + "/" + arch +
   1.337 +                      ". Supported platforms are Windows/*, Linux/x86_64 and Darwin/x86_64")
   1.338 +                return 1
   1.339 +
   1.340 +        os.chdir(self.topsrcdir)
   1.341 +        self.prompt = True
   1.342 +
   1.343 +        try:
   1.344 +            if not self.locate_or_fetch(fmt):
   1.345 +                return 1
   1.346 +            clang_format_diff = self.locate_or_fetch(fmt_diff)
   1.347 +            if not clang_format_diff:
   1.348 +                return 1
   1.349 +
   1.350 +        except urllib2.HTTPError as e:
   1.351 +            print("HTTP error {0}: {1}".format(e.code, e.reason))
   1.352 +            return 1
   1.353 +
   1.354 +        from subprocess import Popen, PIPE
   1.355 +
   1.356 +        if os.path.exists(".hg"):
   1.357 +            diff_process = Popen(["hg", "diff", "-U0", "-r", "tip^",
   1.358 +                                  "--include", "glob:**.c", "--include", "glob:**.cpp", "--include", "glob:**.h",
   1.359 +                                  "--exclude", "listfile:.clang-format-ignore"], stdout=PIPE)
   1.360 +        else:
   1.361 +            git_process = Popen(["git", "diff", "-U0", "HEAD^"], stdout=PIPE)
   1.362 +            try:
   1.363 +                diff_process = Popen(["filterdiff", "--include=*.h", "--include=*.cpp",
   1.364 +                                      "--exclude-from-file=.clang-format-ignore"],
   1.365 +                                     stdin=git_process.stdout, stdout=PIPE)
   1.366 +            except OSError as e:
   1.367 +                if e.errno == errno.ENOENT:
   1.368 +                    print("Can't find filterdiff. Please install patchutils.")
   1.369 +                else:
   1.370 +                    print("OSError {0}: {1}".format(e.code, e.reason))
   1.371 +                return 1
   1.372 +
   1.373 +
   1.374 +        args = [sys.executable, clang_format_diff, "-p1"]
   1.375 +        if not show:
   1.376 +           args.append("-i")
   1.377 +        cf_process = Popen(args, stdin=diff_process.stdout)
   1.378 +        return cf_process.communicate()[0]
   1.379 +
   1.380 +    def locate_or_fetch(self, root):
   1.381 +        target = os.path.join(self._mach_context.state_dir, os.path.basename(root))
   1.382 +        if not os.path.exists(target):
   1.383 +            site = "https://people.mozilla.org/~ajones/clang-format/"
   1.384 +            if self.prompt and raw_input("Download clang-format executables from {0} (yN)? ".format(site)).lower() != 'y':
   1.385 +                print("Download aborted.")
   1.386 +                return 1
   1.387 +            self.prompt = False
   1.388 +
   1.389 +            u = site + root
   1.390 +            print("Downloading {0} to {1}".format(u, target))
   1.391 +            data = urllib2.urlopen(url=u).read()
   1.392 +            temp = target + ".tmp"
   1.393 +            with open(temp, "wb") as fh:
   1.394 +                fh.write(data)
   1.395 +                fh.close()
   1.396 +            os.chmod(temp, os.stat(temp).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
   1.397 +            os.rename(temp, target)
   1.398 +        return target
   1.399 +

mercurial