|
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/. |
|
4 |
|
5 from __future__ import unicode_literals |
|
6 |
|
7 import sys |
|
8 import os |
|
9 import stat |
|
10 import platform |
|
11 import urllib2 |
|
12 import errno |
|
13 |
|
14 from mach.decorators import ( |
|
15 CommandArgument, |
|
16 CommandProvider, |
|
17 Command, |
|
18 ) |
|
19 |
|
20 from mozbuild.base import MachCommandBase |
|
21 |
|
22 |
|
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) |
|
33 |
|
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/search?tree=mozilla-central&q=%s' % term |
|
41 webbrowser.open_new_tab(uri) |
|
42 |
|
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) |
|
51 |
|
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) |
|
60 |
|
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) |
|
70 |
|
71 |
|
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 |
|
90 |
|
91 |
|
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 = {} |
|
100 |
|
101 def get_by_name(self, name): |
|
102 return self.by_name.get(name, []) |
|
103 |
|
104 def get_by_base(self, base): |
|
105 return self.by_base.get(base, []) |
|
106 |
|
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) |
|
112 |
|
113 |
|
114 class IDLUpdater(object): |
|
115 ''' |
|
116 Updates interfaces uuids in IDL files. |
|
117 ''' |
|
118 def __init__(self, interfaces): |
|
119 from mozpack.copier import FileRegistry |
|
120 self.interfaces = interfaces; |
|
121 self.registry = FileRegistry() |
|
122 |
|
123 def add(self, name): |
|
124 for interface in self.interfaces.get_by_name(name): |
|
125 self._add(interface) |
|
126 |
|
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 |
|
137 |
|
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) |
|
141 |
|
142 def update(self): |
|
143 for p, f in self.registry: |
|
144 f.copy(p) |
|
145 |
|
146 |
|
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) |
|
165 |
|
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 |
|
179 |
|
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) |
|
199 |
|
200 updates = IDLUpdater(registry) |
|
201 |
|
202 for interface in interfaces: |
|
203 updates.add(interface) |
|
204 |
|
205 updates.update() |
|
206 |
|
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') |
|
220 |
|
221 def pastebin(self, language, poster, duration, file): |
|
222 import sys |
|
223 import urllib |
|
224 |
|
225 URL = 'http://pastebin.mozilla.org/' |
|
226 |
|
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'}] |
|
245 |
|
246 lang = '' |
|
247 |
|
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] |
|
265 |
|
266 if language: |
|
267 lang = language |
|
268 |
|
269 |
|
270 params = [ |
|
271 ('parent_pid', ''), |
|
272 ('format', lang), |
|
273 ('code2', content), |
|
274 ('poster', poster), |
|
275 ('expiry', duration), |
|
276 ('paste', 'Send')] |
|
277 |
|
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 |
|
293 |
|
294 |
|
295 @CommandProvider |
|
296 class ReviewboardToolsProvider(MachCommandBase): |
|
297 @Command('rbt', category='devenv', allow_all_args=True, |
|
298 description='Run Reviewboard Tools') |
|
299 @CommandArgument('args', nargs='...', help='Arguments to rbt tool') |
|
300 def rbt(self, args): |
|
301 if not args: |
|
302 args = ['help'] |
|
303 |
|
304 self._activate_virtualenv() |
|
305 self.virtualenv_manager.install_pip_package('RBTools==0.6') |
|
306 |
|
307 from rbtools.commands.main import main |
|
308 |
|
309 # main() doesn't accept arguments and instead reads from sys.argv. So, |
|
310 # we fake it out. |
|
311 sys.argv = ['rbt'] + args |
|
312 return main() |
|
313 |
|
314 @CommandProvider |
|
315 class FormatProvider(MachCommandBase): |
|
316 @Command('clang-format', category='misc', |
|
317 description='Run clang-format on current changes') |
|
318 @CommandArgument('--show', '-s', action = 'store_true', |
|
319 help = 'Show diff output on instead of applying changes') |
|
320 def clang_format(self, show=False): |
|
321 plat = platform.system() |
|
322 fmt = plat.lower() + "/clang-format-3.5" |
|
323 fmt_diff = "clang-format-diff-3.5" |
|
324 |
|
325 # We are currently using a modified verion of clang-format hosted on people.mozilla.org. |
|
326 # This is a temporary work around until we upstream the necessary changes and we can use |
|
327 # a system version of clang-format. See bug 961541. |
|
328 if plat == "Windows": |
|
329 fmt += ".exe" |
|
330 else: |
|
331 arch = os.uname()[4] |
|
332 if (plat != "Linux" and plat != "Darwin") or arch != 'x86_64': |
|
333 print("Unsupported platform " + plat + "/" + arch + |
|
334 ". Supported platforms are Windows/*, Linux/x86_64 and Darwin/x86_64") |
|
335 return 1 |
|
336 |
|
337 os.chdir(self.topsrcdir) |
|
338 self.prompt = True |
|
339 |
|
340 try: |
|
341 if not self.locate_or_fetch(fmt): |
|
342 return 1 |
|
343 clang_format_diff = self.locate_or_fetch(fmt_diff) |
|
344 if not clang_format_diff: |
|
345 return 1 |
|
346 |
|
347 except urllib2.HTTPError as e: |
|
348 print("HTTP error {0}: {1}".format(e.code, e.reason)) |
|
349 return 1 |
|
350 |
|
351 from subprocess import Popen, PIPE |
|
352 |
|
353 if os.path.exists(".hg"): |
|
354 diff_process = Popen(["hg", "diff", "-U0", "-r", "tip^", |
|
355 "--include", "glob:**.c", "--include", "glob:**.cpp", "--include", "glob:**.h", |
|
356 "--exclude", "listfile:.clang-format-ignore"], stdout=PIPE) |
|
357 else: |
|
358 git_process = Popen(["git", "diff", "-U0", "HEAD^"], stdout=PIPE) |
|
359 try: |
|
360 diff_process = Popen(["filterdiff", "--include=*.h", "--include=*.cpp", |
|
361 "--exclude-from-file=.clang-format-ignore"], |
|
362 stdin=git_process.stdout, stdout=PIPE) |
|
363 except OSError as e: |
|
364 if e.errno == errno.ENOENT: |
|
365 print("Can't find filterdiff. Please install patchutils.") |
|
366 else: |
|
367 print("OSError {0}: {1}".format(e.code, e.reason)) |
|
368 return 1 |
|
369 |
|
370 |
|
371 args = [sys.executable, clang_format_diff, "-p1"] |
|
372 if not show: |
|
373 args.append("-i") |
|
374 cf_process = Popen(args, stdin=diff_process.stdout) |
|
375 return cf_process.communicate()[0] |
|
376 |
|
377 def locate_or_fetch(self, root): |
|
378 target = os.path.join(self._mach_context.state_dir, os.path.basename(root)) |
|
379 if not os.path.exists(target): |
|
380 site = "https://people.mozilla.org/~ajones/clang-format/" |
|
381 if self.prompt and raw_input("Download clang-format executables from {0} (yN)? ".format(site)).lower() != 'y': |
|
382 print("Download aborted.") |
|
383 return 1 |
|
384 self.prompt = False |
|
385 |
|
386 u = site + root |
|
387 print("Downloading {0} to {1}".format(u, target)) |
|
388 data = urllib2.urlopen(url=u).read() |
|
389 temp = target + ".tmp" |
|
390 with open(temp, "wb") as fh: |
|
391 fh.write(data) |
|
392 fh.close() |
|
393 os.chmod(temp, os.stat(temp).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) |
|
394 os.rename(temp, target) |
|
395 return target |
|
396 |