Bug 1611596 [wpt PR 21420] - Fix buggy testcase font-variant-position-01.html, a...
[gecko.git] / tools / mach_commands.py
blobfa13107fa6f1b9b69586889d5a8166b4ebf597df
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 absolute_import, print_function, unicode_literals
7 import sys
9 from mach.decorators import (
10 CommandArgument,
11 CommandProvider,
12 Command,
13 SubCommand,
16 from mozbuild.base import MachCommandBase, MozbuildObject
19 @CommandProvider
20 class BustedProvider(object):
21 @Command('busted', category='misc',
22 description='Query known bugs in our tooling, and file new ones.')
23 def busted_default(self):
24 import requests
25 payload = {'include_fields': 'id,summary,last_change_time',
26 'blocks': 1543241,
27 'resolution': '---'}
28 response = requests.get('https://bugzilla.mozilla.org/rest/bug', payload)
29 response.raise_for_status()
30 json_response = response.json()
31 if 'bugs' in json_response and len(json_response['bugs']) > 0:
32 # Display most recently modifed bugs first.
33 bugs = sorted(json_response['bugs'], key=lambda item: item['last_change_time'],
34 reverse=True)
35 for bug in bugs:
36 print("Bug %s - %s" % (bug['id'], bug['summary']))
37 else:
38 print("No known tooling issues found.")
40 @SubCommand('busted',
41 'file',
42 description='File a bug for busted tooling.')
43 def busted_file(self):
44 import webbrowser
45 uri = ('https://bugzilla.mozilla.org/enter_bug.cgi?'
46 'product=Firefox%20Build%20System&component=General&blocked=1543241')
47 webbrowser.open_new_tab(uri)
50 @CommandProvider
51 class SearchProvider(object):
52 @Command('searchfox', category='misc',
53 description='Search for something in Searchfox.')
54 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
55 def searchfox(self, term):
56 import webbrowser
57 term = ' '.join(term)
58 uri = 'https://searchfox.org/mozilla-central/search?q=%s' % term
59 webbrowser.open_new_tab(uri)
60 @Command('dxr', category='misc',
61 description='Search for something in DXR.')
62 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
63 def dxr(self, term):
64 import webbrowser
65 term = ' '.join(term)
66 uri = 'http://dxr.mozilla.org/mozilla-central/search?q=%s&redirect=true' % term
67 webbrowser.open_new_tab(uri)
69 @Command('mdn', category='misc',
70 description='Search for something on MDN.')
71 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
72 def mdn(self, term):
73 import webbrowser
74 term = ' '.join(term)
75 uri = 'https://developer.mozilla.org/search?q=%s' % term
76 webbrowser.open_new_tab(uri)
78 @Command('google', category='misc',
79 description='Search for something on Google.')
80 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
81 def google(self, term):
82 import webbrowser
83 term = ' '.join(term)
84 uri = 'https://www.google.com/search?q=%s' % term
85 webbrowser.open_new_tab(uri)
87 @Command('search', category='misc',
88 description='Search for something on the Internets. '
89 'This will open 4 new browser tabs and search for the term on Google, '
90 'MDN, DXR, and Searchfox.')
91 @CommandArgument('term', nargs='+', help='Term(s) to search for.')
92 def search(self, term):
93 self.google(term)
94 self.mdn(term)
95 self.dxr(term)
96 self.searchfox(term)
99 @CommandProvider
100 class UUIDProvider(object):
101 @Command('uuid', category='misc',
102 description='Generate a uuid.')
103 @CommandArgument('--format', '-f', choices=['idl', 'cpp', 'c++'],
104 help='Output format for the generated uuid.')
105 def uuid(self, format=None):
106 import uuid
107 u = uuid.uuid4()
108 if format in [None, 'idl']:
109 print(u)
110 if format is None:
111 print('')
112 if format in [None, 'cpp', 'c++']:
113 u = u.hex
114 print('{ 0x%s, 0x%s, 0x%s, \\' % (u[0:8], u[8:12], u[12:16]))
115 pairs = tuple(map(lambda n: u[n:n+2], range(16, 32, 2)))
116 print((' { ' + '0x%s, ' * 7 + '0x%s } }') % pairs)
119 MACH_PASTEBIN_DURATIONS = {
120 'onetime': 'onetime',
121 'hour': '3600',
122 'day': '86400',
123 'week': '604800',
124 'month': '2073600',
127 EXTENSION_TO_HIGHLIGHTER = {
128 '.hgrc': 'ini',
129 'Dockerfile': 'docker',
130 'Makefile': 'make',
131 'applescript': 'applescript',
132 'arduino': 'arduino',
133 'bash': 'bash',
134 'bat': 'bat',
135 'c': 'c',
136 'clojure': 'clojure',
137 'cmake': 'cmake',
138 'coffee': 'coffee-script',
139 'console': 'console',
140 'cpp': 'cpp',
141 'cs': 'csharp',
142 'css': 'css',
143 'cu': 'cuda',
144 'cuda': 'cuda',
145 'dart': 'dart',
146 'delphi': 'delphi',
147 'diff': 'diff',
148 'django': 'django',
149 'docker': 'docker',
150 'elixir': 'elixir',
151 'erlang': 'erlang',
152 'go': 'go',
153 'h': 'c',
154 'handlebars': 'handlebars',
155 'haskell': 'haskell',
156 'hs': 'haskell',
157 'html': 'html',
158 'ini': 'ini',
159 'ipy': 'ipythonconsole',
160 'ipynb': 'ipythonconsole',
161 'irc': 'irc',
162 'j2': 'django',
163 'java': 'java',
164 'js': 'js',
165 'json': 'json',
166 'jsx': 'jsx',
167 'kt': 'kotlin',
168 'less': 'less',
169 'lisp': 'common-lisp',
170 'lsp': 'common-lisp',
171 'lua': 'lua',
172 'm': 'objective-c',
173 'make': 'make',
174 'matlab': 'matlab',
175 'md': '_markdown',
176 'nginx': 'nginx',
177 'numpy': 'numpy',
178 'patch': 'diff',
179 'perl': 'perl',
180 'php': 'php',
181 'pm': 'perl',
182 'postgresql': 'postgresql',
183 'py': 'python',
184 'rb': 'rb',
185 'rs': 'rust',
186 'rst': 'rst',
187 'sass': 'sass',
188 'scss': 'scss',
189 'sh': 'bash',
190 'sol': 'sol',
191 'sql': 'sql',
192 'swift': 'swift',
193 'tex': 'tex',
194 'typoscript': 'typoscript',
195 'vim': 'vim',
196 'xml': 'xml',
197 'xslt': 'xslt',
198 'yaml': 'yaml',
199 'yml': 'yaml'
203 def guess_highlighter_from_path(path):
204 '''Return a known highlighter from a given path
206 Attempt to select a highlighter by checking the file extension in the mapping
207 of extensions to highlighter. If that fails, attempt to pass the basename of
208 the file. Return `_code` as the default highlighter if that fails.
210 import os
212 _name, ext = os.path.splitext(path)
214 if ext.startswith('.'):
215 ext = ext[1:]
217 if ext in EXTENSION_TO_HIGHLIGHTER:
218 return EXTENSION_TO_HIGHLIGHTER[ext]
220 basename = os.path.basename(path)
222 return EXTENSION_TO_HIGHLIGHTER.get(basename, '_code')
225 PASTEMO_MAX_CONTENT_LENGTH = 250 * 1024 * 1024
227 PASTEMO_URL = 'https://paste.mozilla.org/api/'
229 MACH_PASTEBIN_DESCRIPTION = '''
230 Command line interface to paste.mozilla.org.
232 Takes either a filename whose content should be pasted, or reads
233 content from standard input. If a highlighter is specified it will
234 be used, otherwise the file name will be used to determine an
235 appropriate highlighter.
239 @CommandProvider
240 class PastebinProvider(object):
241 @Command('pastebin', category='misc',
242 description=MACH_PASTEBIN_DESCRIPTION)
243 @CommandArgument('--list-highlighters', action='store_true',
244 help='List known highlighters and exit')
245 @CommandArgument('--highlighter', default=None,
246 help='Syntax highlighting to use for paste')
247 @CommandArgument('--expires', default='week',
248 choices=sorted(MACH_PASTEBIN_DURATIONS.keys()),
249 help='Expire paste after given time duration (default: %(default)s)')
250 @CommandArgument('--verbose', action='store_true',
251 help='Print extra info such as selected syntax highlighter')
252 @CommandArgument('path', nargs='?', default=None,
253 help='Path to file for upload to paste.mozilla.org')
254 def pastebin(self, list_highlighters, highlighter, expires, verbose, path):
255 import requests
257 def verbose_print(*args, **kwargs):
258 '''Print a string if `--verbose` flag is set'''
259 if verbose:
260 print(*args, **kwargs)
262 # Show known highlighters and exit.
263 if list_highlighters:
264 lexers = set(EXTENSION_TO_HIGHLIGHTER.values())
265 print('Available lexers:\n'
266 ' - %s' % '\n - '.join(sorted(lexers)))
267 return 0
269 # Get a correct expiry value.
270 try:
271 verbose_print('Setting expiry from %s' % expires)
272 expires = MACH_PASTEBIN_DURATIONS[expires]
273 verbose_print('Using %s as expiry' % expires)
274 except KeyError:
275 print('%s is not a valid duration.\n'
276 '(hint: try one of %s)' %
277 (expires, ', '.join(MACH_PASTEBIN_DURATIONS.keys())))
278 return 1
280 data = {
281 'format': 'json',
282 'expires': expires,
285 # Get content to be pasted.
286 if path:
287 verbose_print('Reading content from %s' % path)
288 try:
289 with open(path, 'r') as f:
290 content = f.read()
291 except IOError:
292 print('ERROR. No such file %s' % path)
293 return 1
295 lexer = guess_highlighter_from_path(path)
296 if lexer:
297 data['lexer'] = lexer
298 else:
299 verbose_print('Reading content from stdin')
300 content = sys.stdin.read()
302 # Assert the length of content to be posted does not exceed the maximum.
303 content_length = len(content)
304 verbose_print('Checking size of content is okay (%d)' % content_length)
305 if content_length > PASTEMO_MAX_CONTENT_LENGTH:
306 print('Paste content is too large (%d, maximum %d)' %
307 (content_length, PASTEMO_MAX_CONTENT_LENGTH))
308 return 1
310 data['content'] = content
312 # Highlight as specified language, overwriting value set from filename.
313 if highlighter:
314 verbose_print('Setting %s as highlighter' % highlighter)
315 data['lexer'] = highlighter
317 try:
318 verbose_print('Sending request to %s' % PASTEMO_URL)
319 resp = requests.post(PASTEMO_URL, data=data)
321 # Error code should always be 400.
322 # Response content will include a helpful error message,
323 # so print it here (for example, if an invalid highlighter is
324 # provided, it will return a list of valid highlighters).
325 if resp.status_code >= 400:
326 print('Error code %d: %s' % (resp.status_code, resp.content))
327 return 1
329 verbose_print('Pasted successfully')
331 response_json = resp.json()
333 verbose_print('Paste highlighted as %s' % response_json['lexer'])
334 print(response_json['url'])
336 return 0
337 except Exception as e:
338 print('ERROR. Paste failed.')
339 print('%s' % e)
340 return 1
343 def mozregression_import():
344 # Lazy loading of mozregression.
345 # Note that only the mach_interface module should be used from this file.
346 try:
347 import mozregression.mach_interface
348 except ImportError:
349 return None
350 return mozregression.mach_interface
353 def mozregression_create_parser():
354 # Create the mozregression command line parser.
355 # if mozregression is not installed, or not up to date, it will
356 # first be installed.
357 cmd = MozbuildObject.from_environment()
358 cmd._activate_virtualenv()
359 mozregression = mozregression_import()
360 if not mozregression:
361 # mozregression is not here at all, install it
362 cmd.virtualenv_manager.install_pip_package('mozregression')
363 print("mozregression was installed. please re-run your"
364 " command. If you keep getting this message please "
365 " manually run: 'pip install -U mozregression'.")
366 else:
367 # check if there is a new release available
368 release = mozregression.new_release_on_pypi()
369 if release:
370 print(release)
371 # there is one, so install it. Note that install_pip_package
372 # does not work here, so just run pip directly.
373 cmd.virtualenv_manager._run_pip([
374 'install',
375 'mozregression==%s' % release
377 print("mozregression was updated to version %s. please"
378 " re-run your command." % release)
379 else:
380 # mozregression is up to date, return the parser.
381 return mozregression.parser()
382 # exit if we updated or installed mozregression because
383 # we may have already imported mozregression and running it
384 # as this may cause issues.
385 sys.exit(0)
388 @CommandProvider
389 class MozregressionCommand(MachCommandBase):
390 @Command('mozregression',
391 category='misc',
392 description=("Regression range finder for nightly"
393 " and inbound builds."),
394 parser=mozregression_create_parser)
395 def run(self, **options):
396 self._activate_virtualenv()
397 mozregression = mozregression_import()
398 mozregression.run(options)