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 +