Bumping manifests a=b2g-bump
[gecko.git] / tools / mach_commands.py
blob3ab150825470981f5b54edc8791e0d7cecec0381
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, # You can obtain one at http://mozilla.org/MPL/2.0/.
5 from __future__ import unicode_literals
7 import sys
8 import os
9 import stat
10 import platform
11 import urllib2
12 import errno
14 from mach.decorators import (
15 CommandArgument,
16 CommandProvider,
17 Command,
20 from mozbuild.base import MachCommandBase
23 @CommandProvider
24 class SearchProvider(object):
25 @Command('mxr', category='misc',
26 description='Search for something in MXR.')
27 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
28 def mxr(self, term):
29 import webbrowser
30 term = ' '.join(term)
31 uri = 'https://mxr.mozilla.org/mozilla-central/search?string=%s' % term
32 webbrowser.open_new_tab(uri)
34 @Command('dxr', category='misc',
35 description='Search for something in DXR.')
36 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
37 def dxr(self, term):
38 import webbrowser
39 term = ' '.join(term)
40 uri = 'http://dxr.mozilla.org/mozilla-central/search?q=%s&redirect=true' % term
41 webbrowser.open_new_tab(uri)
43 @Command('mdn', category='misc',
44 description='Search for something on MDN.')
45 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
46 def mdn(self, term):
47 import webbrowser
48 term = ' '.join(term)
49 uri = 'https://developer.mozilla.org/search?q=%s' % term
50 webbrowser.open_new_tab(uri)
52 @Command('google', category='misc',
53 description='Search for something on Google.')
54 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
55 def google(self, term):
56 import webbrowser
57 term = ' '.join(term)
58 uri = 'https://www.google.com/search?q=%s' % term
59 webbrowser.open_new_tab(uri)
61 @Command('search', category='misc',
62 description='Search for something on the Internets. '
63 'This will open 3 new browser tabs and search for the term on Google, '
64 'MDN, and MXR.')
65 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
66 def search(self, term):
67 self.google(term)
68 self.mdn(term)
69 self.mxr(term)
72 class Interface(object):
73 '''
74 Represents an XPIDL interface, in what file it is defined, what it derives
75 from, what its uuid is, and where in the source file the uuid is.
76 '''
77 def __init__(self, filename, production):
78 import xpidl
79 assert isinstance(production, xpidl.Interface)
80 self.name = production.name
81 self.base = production.base
82 self.filename = filename
83 self.uuid = production.attributes.uuid
84 location = production.location
85 data = location._lexdata
86 attr_pos = data.rfind(b'[', 0, location._lexpos)
87 # uuid is always lowercase, but actual file content may not be.
88 self.uuid_pos = data[attr_pos:location._lexpos].lower() \
89 .rfind(self.uuid) + attr_pos
92 class InterfaceRegistry(object):
93 '''
94 Tracks XPIDL interfaces, and allow to search them by name and by the
95 interface they derive from.
96 '''
97 def __init__(self):
98 self.by_name = {}
99 self.by_base = {}
101 def get_by_name(self, name):
102 return self.by_name.get(name, [])
104 def get_by_base(self, base):
105 return self.by_base.get(base, [])
107 def add(self, interface):
108 l = self.by_name.setdefault(interface.name, [])
109 l.append(interface)
110 l = self.by_base.setdefault(interface.base, [])
111 l.append(interface)
114 class IDLUpdater(object):
116 Updates interfaces uuids in IDL files.
118 def __init__(self, interfaces):
119 from mozpack.copier import FileRegistry
120 self.interfaces = interfaces;
121 self.registry = FileRegistry()
123 def add(self, name):
124 for interface in self.interfaces.get_by_name(name):
125 self._add(interface)
127 def _add(self, interface):
128 from mozpack.files import GeneratedFile
129 from uuid import uuid4
130 path = interface.filename
131 if not self.registry.contains(path):
132 self.registry.add(path, GeneratedFile(open(path).read()))
133 content = self.registry[path].content
134 content = content[:interface.uuid_pos] + str(uuid4()) + \
135 content[interface.uuid_pos + len(interface.uuid):]
136 self.registry[path].content = content
138 # Recurse through all the interfaces deriving from this one
139 for derived in self.interfaces.get_by_base(interface.name):
140 self._add(derived)
142 def update(self):
143 for p, f in self.registry:
144 f.copy(p)
147 @CommandProvider
148 class UUIDProvider(object):
149 @Command('uuid', category='misc',
150 description='Generate a uuid.')
151 @CommandArgument('--format', '-f', choices=['idl', 'cpp', 'c++'],
152 help='Output format for the generated uuid.')
153 def uuid(self, format=None):
154 import uuid
155 u = uuid.uuid4()
156 if format in [None, 'idl']:
157 print(u)
158 if format is None:
159 print('')
160 if format in [None, 'cpp', 'c++']:
161 u = u.hex
162 print('{ 0x%s, 0x%s, 0x%s, \\' % (u[0:8], u[8:12], u[12:16]))
163 pairs = tuple(map(lambda n: u[n:n+2], range(16, 32, 2)))
164 print((' { ' + '0x%s, ' * 7 + '0x%s } }') % pairs)
166 @Command('update-uuids', category='misc',
167 description='Update IDL files with new UUIDs.')
168 @CommandArgument('--path', default='.',
169 help='Base path under which uuids will be searched.')
170 @CommandArgument('interfaces', nargs='+',
171 help='Changed interfaces whose UUIDs need to be updated. ' +
172 'Their descendants are updated as well.')
173 def update_uuids(self, path, interfaces):
174 import os
175 import xpidl
176 from mozpack.files import FileFinder
177 import mozpack.path
178 from tempfile import mkdtemp
180 finder = FileFinder(path, find_executables=False)
181 # Avoid creating xpidllex and xpidlyacc in the current directory.
182 tmpdir = mkdtemp()
183 try:
184 parser = xpidl.IDLParser(outputdir=tmpdir)
185 registry = InterfaceRegistry()
186 for p, f in finder.find('**/*.idl'):
187 p = mozpack.path.join(path, p)
188 try:
189 content = f.open().read()
190 idl = parser.parse(content, filename=p)
191 except Exception:
192 continue
193 for prod in idl.productions:
194 if isinstance(prod, xpidl.Interface):
195 registry.add(Interface(p, prod))
196 finally:
197 import shutil
198 shutil.rmtree(tmpdir)
200 updates = IDLUpdater(registry)
202 for interface in interfaces:
203 updates.add(interface)
205 updates.update()
207 @CommandProvider
208 class PastebinProvider(object):
209 @Command('pastebin', category='misc',
210 description='Command line interface to pastebin.mozilla.org.')
211 @CommandArgument('--language', default=None,
212 help='Language to use for syntax highlighting')
213 @CommandArgument('--poster', default=None,
214 help='Specify your name for use with pastebin.mozilla.org')
215 @CommandArgument('--duration', default='day',
216 choices=['d', 'day', 'm', 'month', 'f', 'forever'],
217 help='Keep for specified duration (default: %(default)s)')
218 @CommandArgument('file', nargs='?', default=None,
219 help='Specify the file to upload to pastebin.mozilla.org')
221 def pastebin(self, language, poster, duration, file):
222 import sys
223 import urllib
225 URL = 'http://pastebin.mozilla.org/'
227 FILE_TYPES = [{'value': 'text', 'name': 'None', 'extension': 'txt'},
228 {'value': 'bash', 'name': 'Bash', 'extension': 'sh'},
229 {'value': 'c', 'name': 'C', 'extension': 'c'},
230 {'value': 'cpp', 'name': 'C++', 'extension': 'cpp'},
231 {'value': 'html4strict', 'name': 'HTML', 'extension': 'html'},
232 {'value': 'javascript', 'name': 'Javascript', 'extension': 'js'},
233 {'value': 'javascript', 'name': 'Javascript', 'extension': 'jsm'},
234 {'value': 'lua', 'name': 'Lua', 'extension': 'lua'},
235 {'value': 'perl', 'name': 'Perl', 'extension': 'pl'},
236 {'value': 'php', 'name': 'PHP', 'extension': 'php'},
237 {'value': 'python', 'name': 'Python', 'extension': 'py'},
238 {'value': 'ruby', 'name': 'Ruby', 'extension': 'rb'},
239 {'value': 'css', 'name': 'CSS', 'extension': 'css'},
240 {'value': 'diff', 'name': 'Diff', 'extension': 'diff'},
241 {'value': 'ini', 'name': 'INI file', 'extension': 'ini'},
242 {'value': 'java', 'name': 'Java', 'extension': 'java'},
243 {'value': 'xml', 'name': 'XML', 'extension': 'xml'},
244 {'value': 'xml', 'name': 'XML', 'extension': 'xul'}]
246 lang = ''
248 if file:
249 try:
250 with open(file, 'r') as f:
251 content = f.read()
252 # TODO: Use mime-types instead of extensions; suprocess('file <f_name>')
253 # Guess File-type based on file extension
254 extension = file.split('.')[-1]
255 for l in FILE_TYPES:
256 if extension == l['extension']:
257 print('Identified file as %s' % l['name'])
258 lang = l['value']
259 except IOError:
260 print('ERROR. No such file')
261 return 1
262 else:
263 content = sys.stdin.read()
264 duration = duration[0]
266 if language:
267 lang = language
270 params = [
271 ('parent_pid', ''),
272 ('format', lang),
273 ('code2', content),
274 ('poster', poster),
275 ('expiry', duration),
276 ('paste', 'Send')]
278 data = urllib.urlencode(params)
279 print('Uploading ...')
280 try:
281 req = urllib2.Request(URL, data)
282 response = urllib2.urlopen(req)
283 http_response_code = response.getcode()
284 if http_response_code == 200:
285 print(response.geturl())
286 else:
287 print('Could not upload the file, '
288 'HTTP Response Code %s' %(http_response_code))
289 except urllib2.URLError:
290 print('ERROR. Could not connect to pastebin.mozilla.org.')
291 return 1
292 return 0
295 @CommandProvider
296 class FormatProvider(MachCommandBase):
297 @Command('clang-format', category='misc',
298 description='Run clang-format on current changes')
299 @CommandArgument('--show', '-s', action = 'store_true',
300 help = 'Show diff output on instead of applying changes')
301 def clang_format(self, show=False):
302 plat = platform.system()
303 fmt = plat.lower() + "/clang-format-3.5"
304 fmt_diff = "clang-format-diff-3.5"
306 # We are currently using a modified version of clang-format hosted on people.mozilla.org.
307 # This is a temporary work around until we upstream the necessary changes and we can use
308 # a system version of clang-format. See bug 961541.
309 if plat == "Windows":
310 fmt += ".exe"
311 else:
312 arch = os.uname()[4]
313 if (plat != "Linux" and plat != "Darwin") or arch != 'x86_64':
314 print("Unsupported platform " + plat + "/" + arch +
315 ". Supported platforms are Windows/*, Linux/x86_64 and Darwin/x86_64")
316 return 1
318 os.chdir(self.topsrcdir)
319 self.prompt = True
321 try:
322 if not self.locate_or_fetch(fmt):
323 return 1
324 clang_format_diff = self.locate_or_fetch(fmt_diff)
325 if not clang_format_diff:
326 return 1
328 except urllib2.HTTPError as e:
329 print("HTTP error {0}: {1}".format(e.code, e.reason))
330 return 1
332 from subprocess import Popen, PIPE
334 if os.path.exists(".hg"):
335 diff_process = Popen(["hg", "diff", "-U0", "-r", "tip^",
336 "--include", "glob:**.c", "--include", "glob:**.cpp", "--include", "glob:**.h",
337 "--exclude", "listfile:.clang-format-ignore"], stdout=PIPE)
338 else:
339 git_process = Popen(["git", "diff", "-U0", "HEAD^"], stdout=PIPE)
340 try:
341 diff_process = Popen(["filterdiff", "--include=*.h", "--include=*.cpp",
342 "--exclude-from-file=.clang-format-ignore"],
343 stdin=git_process.stdout, stdout=PIPE)
344 except OSError as e:
345 if e.errno == errno.ENOENT:
346 print("Can't find filterdiff. Please install patchutils.")
347 else:
348 print("OSError {0}: {1}".format(e.code, e.reason))
349 return 1
352 args = [sys.executable, clang_format_diff, "-p1"]
353 if not show:
354 args.append("-i")
355 cf_process = Popen(args, stdin=diff_process.stdout)
356 return cf_process.communicate()[0]
358 def locate_or_fetch(self, root):
359 target = os.path.join(self._mach_context.state_dir, os.path.basename(root))
360 if not os.path.exists(target):
361 site = "https://people.mozilla.org/~ajones/clang-format/"
362 if self.prompt and raw_input("Download clang-format executables from {0} (yN)? ".format(site)).lower() != 'y':
363 print("Download aborted.")
364 return 1
365 self.prompt = False
367 u = site + root
368 print("Downloading {0} to {1}".format(u, target))
369 data = urllib2.urlopen(url=u).read()
370 temp = target + ".tmp"
371 with open(temp, "wb") as fh:
372 fh.write(data)
373 fh.close()
374 os.chmod(temp, os.stat(temp).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
375 os.rename(temp, target)
376 return target