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: from __future__ import unicode_literals michael@0: michael@0: import sys michael@0: import os michael@0: import stat michael@0: import platform michael@0: import urllib2 michael@0: import errno michael@0: michael@0: from mach.decorators import ( michael@0: CommandArgument, michael@0: CommandProvider, michael@0: Command, michael@0: ) michael@0: michael@0: from mozbuild.base import MachCommandBase michael@0: michael@0: michael@0: @CommandProvider michael@0: class SearchProvider(object): michael@0: @Command('mxr', category='misc', michael@0: description='Search for something in MXR.') michael@0: @CommandArgument('term', nargs='+', help='Term(s) to search for.') michael@0: def mxr(self, term): michael@0: import webbrowser michael@0: term = ' '.join(term) michael@0: uri = 'https://mxr.mozilla.org/mozilla-central/search?string=%s' % term michael@0: webbrowser.open_new_tab(uri) michael@0: michael@0: @Command('dxr', category='misc', michael@0: description='Search for something in DXR.') michael@0: @CommandArgument('term', nargs='+', help='Term(s) to search for.') michael@0: def dxr(self, term): michael@0: import webbrowser michael@0: term = ' '.join(term) michael@0: uri = 'http://dxr.mozilla.org/search?tree=mozilla-central&q=%s' % term michael@0: webbrowser.open_new_tab(uri) michael@0: michael@0: @Command('mdn', category='misc', michael@0: description='Search for something on MDN.') michael@0: @CommandArgument('term', nargs='+', help='Term(s) to search for.') michael@0: def mdn(self, term): michael@0: import webbrowser michael@0: term = ' '.join(term) michael@0: uri = 'https://developer.mozilla.org/search?q=%s' % term michael@0: webbrowser.open_new_tab(uri) michael@0: michael@0: @Command('google', category='misc', michael@0: description='Search for something on Google.') michael@0: @CommandArgument('term', nargs='+', help='Term(s) to search for.') michael@0: def google(self, term): michael@0: import webbrowser michael@0: term = ' '.join(term) michael@0: uri = 'https://www.google.com/search?q=%s' % term michael@0: webbrowser.open_new_tab(uri) michael@0: michael@0: @Command('search', category='misc', michael@0: description='Search for something on the Internets. ' michael@0: 'This will open 3 new browser tabs and search for the term on Google, ' michael@0: 'MDN, and MXR.') michael@0: @CommandArgument('term', nargs='+', help='Term(s) to search for.') michael@0: def search(self, term): michael@0: self.google(term) michael@0: self.mdn(term) michael@0: self.mxr(term) michael@0: michael@0: michael@0: class Interface(object): michael@0: ''' michael@0: Represents an XPIDL interface, in what file it is defined, what it derives michael@0: from, what its uuid is, and where in the source file the uuid is. michael@0: ''' michael@0: def __init__(self, filename, production): michael@0: import xpidl michael@0: assert isinstance(production, xpidl.Interface) michael@0: self.name = production.name michael@0: self.base = production.base michael@0: self.filename = filename michael@0: self.uuid = production.attributes.uuid michael@0: location = production.location michael@0: data = location._lexdata michael@0: attr_pos = data.rfind(b'[', 0, location._lexpos) michael@0: # uuid is always lowercase, but actual file content may not be. michael@0: self.uuid_pos = data[attr_pos:location._lexpos].lower() \ michael@0: .rfind(self.uuid) + attr_pos michael@0: michael@0: michael@0: class InterfaceRegistry(object): michael@0: ''' michael@0: Tracks XPIDL interfaces, and allow to search them by name and by the michael@0: interface they derive from. michael@0: ''' michael@0: def __init__(self): michael@0: self.by_name = {} michael@0: self.by_base = {} michael@0: michael@0: def get_by_name(self, name): michael@0: return self.by_name.get(name, []) michael@0: michael@0: def get_by_base(self, base): michael@0: return self.by_base.get(base, []) michael@0: michael@0: def add(self, interface): michael@0: l = self.by_name.setdefault(interface.name, []) michael@0: l.append(interface) michael@0: l = self.by_base.setdefault(interface.base, []) michael@0: l.append(interface) michael@0: michael@0: michael@0: class IDLUpdater(object): michael@0: ''' michael@0: Updates interfaces uuids in IDL files. michael@0: ''' michael@0: def __init__(self, interfaces): michael@0: from mozpack.copier import FileRegistry michael@0: self.interfaces = interfaces; michael@0: self.registry = FileRegistry() michael@0: michael@0: def add(self, name): michael@0: for interface in self.interfaces.get_by_name(name): michael@0: self._add(interface) michael@0: michael@0: def _add(self, interface): michael@0: from mozpack.files import GeneratedFile michael@0: from uuid import uuid4 michael@0: path = interface.filename michael@0: if not self.registry.contains(path): michael@0: self.registry.add(path, GeneratedFile(open(path).read())) michael@0: content = self.registry[path].content michael@0: content = content[:interface.uuid_pos] + str(uuid4()) + \ michael@0: content[interface.uuid_pos + len(interface.uuid):] michael@0: self.registry[path].content = content michael@0: michael@0: # Recurse through all the interfaces deriving from this one michael@0: for derived in self.interfaces.get_by_base(interface.name): michael@0: self._add(derived) michael@0: michael@0: def update(self): michael@0: for p, f in self.registry: michael@0: f.copy(p) michael@0: michael@0: michael@0: @CommandProvider michael@0: class UUIDProvider(object): michael@0: @Command('uuid', category='misc', michael@0: description='Generate a uuid.') michael@0: @CommandArgument('--format', '-f', choices=['idl', 'cpp', 'c++'], michael@0: help='Output format for the generated uuid.') michael@0: def uuid(self, format=None): michael@0: import uuid michael@0: u = uuid.uuid4() michael@0: if format in [None, 'idl']: michael@0: print(u) michael@0: if format is None: michael@0: print('') michael@0: if format in [None, 'cpp', 'c++']: michael@0: u = u.hex michael@0: print('{ 0x%s, 0x%s, 0x%s, \\' % (u[0:8], u[8:12], u[12:16])) michael@0: pairs = tuple(map(lambda n: u[n:n+2], range(16, 32, 2))) michael@0: print((' { ' + '0x%s, ' * 7 + '0x%s } }') % pairs) michael@0: michael@0: @Command('update-uuids', category='misc', michael@0: description='Update IDL files with new UUIDs.') michael@0: @CommandArgument('--path', default='.', michael@0: help='Base path under which uuids will be searched.') michael@0: @CommandArgument('interfaces', nargs='+', michael@0: help='Changed interfaces whose UUIDs need to be updated. ' + michael@0: 'Their descendants are updated as well.') michael@0: def update_uuids(self, path, interfaces): michael@0: import os michael@0: import xpidl michael@0: from mozpack.files import FileFinder michael@0: import mozpack.path michael@0: from tempfile import mkdtemp michael@0: michael@0: finder = FileFinder(path, find_executables=False) michael@0: # Avoid creating xpidllex and xpidlyacc in the current directory. michael@0: tmpdir = mkdtemp() michael@0: try: michael@0: parser = xpidl.IDLParser(outputdir=tmpdir) michael@0: registry = InterfaceRegistry() michael@0: for p, f in finder.find('**/*.idl'): michael@0: p = mozpack.path.join(path, p) michael@0: try: michael@0: content = f.open().read() michael@0: idl = parser.parse(content, filename=p) michael@0: except Exception: michael@0: continue michael@0: for prod in idl.productions: michael@0: if isinstance(prod, xpidl.Interface): michael@0: registry.add(Interface(p, prod)) michael@0: finally: michael@0: import shutil michael@0: shutil.rmtree(tmpdir) michael@0: michael@0: updates = IDLUpdater(registry) michael@0: michael@0: for interface in interfaces: michael@0: updates.add(interface) michael@0: michael@0: updates.update() michael@0: michael@0: @CommandProvider michael@0: class PastebinProvider(object): michael@0: @Command('pastebin', category='misc', michael@0: description='Command line interface to pastebin.mozilla.org.') michael@0: @CommandArgument('--language', default=None, michael@0: help='Language to use for syntax highlighting') michael@0: @CommandArgument('--poster', default=None, michael@0: help='Specify your name for use with pastebin.mozilla.org') michael@0: @CommandArgument('--duration', default='day', michael@0: choices=['d', 'day', 'm', 'month', 'f', 'forever'], michael@0: help='Keep for specified duration (default: %(default)s)') michael@0: @CommandArgument('file', nargs='?', default=None, michael@0: help='Specify the file to upload to pastebin.mozilla.org') michael@0: michael@0: def pastebin(self, language, poster, duration, file): michael@0: import sys michael@0: import urllib michael@0: michael@0: URL = 'http://pastebin.mozilla.org/' michael@0: michael@0: FILE_TYPES = [{'value': 'text', 'name': 'None', 'extension': 'txt'}, michael@0: {'value': 'bash', 'name': 'Bash', 'extension': 'sh'}, michael@0: {'value': 'c', 'name': 'C', 'extension': 'c'}, michael@0: {'value': 'cpp', 'name': 'C++', 'extension': 'cpp'}, michael@0: {'value': 'html4strict', 'name': 'HTML', 'extension': 'html'}, michael@0: {'value': 'javascript', 'name': 'Javascript', 'extension': 'js'}, michael@0: {'value': 'javascript', 'name': 'Javascript', 'extension': 'jsm'}, michael@0: {'value': 'lua', 'name': 'Lua', 'extension': 'lua'}, michael@0: {'value': 'perl', 'name': 'Perl', 'extension': 'pl'}, michael@0: {'value': 'php', 'name': 'PHP', 'extension': 'php'}, michael@0: {'value': 'python', 'name': 'Python', 'extension': 'py'}, michael@0: {'value': 'ruby', 'name': 'Ruby', 'extension': 'rb'}, michael@0: {'value': 'css', 'name': 'CSS', 'extension': 'css'}, michael@0: {'value': 'diff', 'name': 'Diff', 'extension': 'diff'}, michael@0: {'value': 'ini', 'name': 'INI file', 'extension': 'ini'}, michael@0: {'value': 'java', 'name': 'Java', 'extension': 'java'}, michael@0: {'value': 'xml', 'name': 'XML', 'extension': 'xml'}, michael@0: {'value': 'xml', 'name': 'XML', 'extension': 'xul'}] michael@0: michael@0: lang = '' michael@0: michael@0: if file: michael@0: try: michael@0: with open(file, 'r') as f: michael@0: content = f.read() michael@0: # TODO: Use mime-types instead of extensions; suprocess('file ') michael@0: # Guess File-type based on file extension michael@0: extension = file.split('.')[-1] michael@0: for l in FILE_TYPES: michael@0: if extension == l['extension']: michael@0: print('Identified file as %s' % l['name']) michael@0: lang = l['value'] michael@0: except IOError: michael@0: print('ERROR. No such file') michael@0: return 1 michael@0: else: michael@0: content = sys.stdin.read() michael@0: duration = duration[0] michael@0: michael@0: if language: michael@0: lang = language michael@0: michael@0: michael@0: params = [ michael@0: ('parent_pid', ''), michael@0: ('format', lang), michael@0: ('code2', content), michael@0: ('poster', poster), michael@0: ('expiry', duration), michael@0: ('paste', 'Send')] michael@0: michael@0: data = urllib.urlencode(params) michael@0: print('Uploading ...') michael@0: try: michael@0: req = urllib2.Request(URL, data) michael@0: response = urllib2.urlopen(req) michael@0: http_response_code = response.getcode() michael@0: if http_response_code == 200: michael@0: print(response.geturl()) michael@0: else: michael@0: print('Could not upload the file, ' michael@0: 'HTTP Response Code %s' %(http_response_code)) michael@0: except urllib2.URLError: michael@0: print('ERROR. Could not connect to pastebin.mozilla.org.') michael@0: return 1 michael@0: return 0 michael@0: michael@0: michael@0: @CommandProvider michael@0: class ReviewboardToolsProvider(MachCommandBase): michael@0: @Command('rbt', category='devenv', allow_all_args=True, michael@0: description='Run Reviewboard Tools') michael@0: @CommandArgument('args', nargs='...', help='Arguments to rbt tool') michael@0: def rbt(self, args): michael@0: if not args: michael@0: args = ['help'] michael@0: michael@0: self._activate_virtualenv() michael@0: self.virtualenv_manager.install_pip_package('RBTools==0.6') michael@0: michael@0: from rbtools.commands.main import main michael@0: michael@0: # main() doesn't accept arguments and instead reads from sys.argv. So, michael@0: # we fake it out. michael@0: sys.argv = ['rbt'] + args michael@0: return main() michael@0: michael@0: @CommandProvider michael@0: class FormatProvider(MachCommandBase): michael@0: @Command('clang-format', category='misc', michael@0: description='Run clang-format on current changes') michael@0: @CommandArgument('--show', '-s', action = 'store_true', michael@0: help = 'Show diff output on instead of applying changes') michael@0: def clang_format(self, show=False): michael@0: plat = platform.system() michael@0: fmt = plat.lower() + "/clang-format-3.5" michael@0: fmt_diff = "clang-format-diff-3.5" michael@0: michael@0: # We are currently using a modified verion of clang-format hosted on people.mozilla.org. michael@0: # This is a temporary work around until we upstream the necessary changes and we can use michael@0: # a system version of clang-format. See bug 961541. michael@0: if plat == "Windows": michael@0: fmt += ".exe" michael@0: else: michael@0: arch = os.uname()[4] michael@0: if (plat != "Linux" and plat != "Darwin") or arch != 'x86_64': michael@0: print("Unsupported platform " + plat + "/" + arch + michael@0: ". Supported platforms are Windows/*, Linux/x86_64 and Darwin/x86_64") michael@0: return 1 michael@0: michael@0: os.chdir(self.topsrcdir) michael@0: self.prompt = True michael@0: michael@0: try: michael@0: if not self.locate_or_fetch(fmt): michael@0: return 1 michael@0: clang_format_diff = self.locate_or_fetch(fmt_diff) michael@0: if not clang_format_diff: michael@0: return 1 michael@0: michael@0: except urllib2.HTTPError as e: michael@0: print("HTTP error {0}: {1}".format(e.code, e.reason)) michael@0: return 1 michael@0: michael@0: from subprocess import Popen, PIPE michael@0: michael@0: if os.path.exists(".hg"): michael@0: diff_process = Popen(["hg", "diff", "-U0", "-r", "tip^", michael@0: "--include", "glob:**.c", "--include", "glob:**.cpp", "--include", "glob:**.h", michael@0: "--exclude", "listfile:.clang-format-ignore"], stdout=PIPE) michael@0: else: michael@0: git_process = Popen(["git", "diff", "-U0", "HEAD^"], stdout=PIPE) michael@0: try: michael@0: diff_process = Popen(["filterdiff", "--include=*.h", "--include=*.cpp", michael@0: "--exclude-from-file=.clang-format-ignore"], michael@0: stdin=git_process.stdout, stdout=PIPE) michael@0: except OSError as e: michael@0: if e.errno == errno.ENOENT: michael@0: print("Can't find filterdiff. Please install patchutils.") michael@0: else: michael@0: print("OSError {0}: {1}".format(e.code, e.reason)) michael@0: return 1 michael@0: michael@0: michael@0: args = [sys.executable, clang_format_diff, "-p1"] michael@0: if not show: michael@0: args.append("-i") michael@0: cf_process = Popen(args, stdin=diff_process.stdout) michael@0: return cf_process.communicate()[0] michael@0: michael@0: def locate_or_fetch(self, root): michael@0: target = os.path.join(self._mach_context.state_dir, os.path.basename(root)) michael@0: if not os.path.exists(target): michael@0: site = "https://people.mozilla.org/~ajones/clang-format/" michael@0: if self.prompt and raw_input("Download clang-format executables from {0} (yN)? ".format(site)).lower() != 'y': michael@0: print("Download aborted.") michael@0: return 1 michael@0: self.prompt = False michael@0: michael@0: u = site + root michael@0: print("Downloading {0} to {1}".format(u, target)) michael@0: data = urllib2.urlopen(url=u).read() michael@0: temp = target + ".tmp" michael@0: with open(temp, "wb") as fh: michael@0: fh.write(data) michael@0: fh.close() michael@0: os.chmod(temp, os.stat(temp).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) michael@0: os.rename(temp, target) michael@0: return target michael@0: