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
9 from mach
.decorators
import (
16 from mozbuild
.base
import MachCommandBase
, MozbuildObject
20 class BustedProvider(MachCommandBase
):
21 @Command('busted', category
='misc',
22 description
='Query known bugs in our tooling, and file new ones.')
23 def busted_default(self
):
25 payload
= {'include_fields': 'id,summary,last_change_time',
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'],
36 print("Bug %s - %s" % (bug
['id'], bug
['summary']))
38 print("No known tooling issues found.")
42 description
='File a bug for busted tooling.')
45 'The specific mach command that is busted (i.e. if you encountered '
46 'an error with `mach build`, run `mach busted file build`). If '
47 'the issue is not connected to any particular mach command, you '
48 'can also run `mach busted file general`.'))
49 def busted_file(self
, against
):
52 if (against
!= 'general' and
53 against
not in self
._mach
_context
.commands
.command_handlers
):
54 print('%s is not a valid value for `against`. `against` must be '
55 'the name of a `mach` command, or else the string '
56 '"general".' % against
)
59 if against
== 'general':
60 product
= 'Firefox Build System'
64 import mozpack
.path
as mozpath
66 # Look up the file implementing that command, then cross-refernce
67 # moz.build files to get the product/component.
68 handler
= self
._mach
_context
.commands
.command_handlers
[against
]
69 method
= getattr(handler
.cls
, handler
.method
)
70 sourcefile
= mozpath
.relpath(inspect
.getsourcefile(method
),
72 reader
= self
.mozbuild_reader(config_mode
='empty')
74 res
= reader
.files_info(
75 [sourcefile
])[sourcefile
]['BUG_COMPONENT']
76 product
, component
= res
.product
, res
.component
78 # The file might not have a bug set.
79 product
= 'Firefox Build System'
82 uri
= ('https://bugzilla.mozilla.org/enter_bug.cgi?'
83 'product=%s&component=%s&blocked=1543241' % (product
, component
))
84 webbrowser
.open_new_tab(uri
)
88 class UUIDProvider(object):
89 @Command('uuid', category
='misc',
90 description
='Generate a uuid.')
91 @CommandArgument('--format', '-f', choices
=['idl', 'cpp', 'c++'],
92 help='Output format for the generated uuid.')
93 def uuid(self
, format
=None):
96 if format
in [None, 'idl']:
100 if format
in [None, 'cpp', 'c++']:
102 print('{ 0x%s, 0x%s, 0x%s, \\' % (u
[0:8], u
[8:12], u
[12:16]))
103 pairs
= tuple(map(lambda n
: u
[n
:n
+2], range(16, 32, 2)))
104 print((' { ' + '0x%s, ' * 7 + '0x%s } }') % pairs
)
107 MACH_PASTEBIN_DURATIONS
= {
108 'onetime': 'onetime',
115 EXTENSION_TO_HIGHLIGHTER
= {
117 'Dockerfile': 'docker',
119 'applescript': 'applescript',
120 'arduino': 'arduino',
124 'clojure': 'clojure',
126 'coffee': 'coffee-script',
127 'console': 'console',
142 'handlebars': 'handlebars',
143 'haskell': 'haskell',
147 'ipy': 'ipythonconsole',
148 'ipynb': 'ipythonconsole',
157 'lisp': 'common-lisp',
158 'lsp': 'common-lisp',
170 'postgresql': 'postgresql',
182 'typoscript': 'typoscript',
191 def guess_highlighter_from_path(path
):
192 '''Return a known highlighter from a given path
194 Attempt to select a highlighter by checking the file extension in the mapping
195 of extensions to highlighter. If that fails, attempt to pass the basename of
196 the file. Return `_code` as the default highlighter if that fails.
200 _name
, ext
= os
.path
.splitext(path
)
202 if ext
.startswith('.'):
205 if ext
in EXTENSION_TO_HIGHLIGHTER
:
206 return EXTENSION_TO_HIGHLIGHTER
[ext
]
208 basename
= os
.path
.basename(path
)
210 return EXTENSION_TO_HIGHLIGHTER
.get(basename
, '_code')
213 PASTEMO_MAX_CONTENT_LENGTH
= 250 * 1024 * 1024
215 PASTEMO_URL
= 'https://paste.mozilla.org/api/'
217 MACH_PASTEBIN_DESCRIPTION
= '''
218 Command line interface to paste.mozilla.org.
220 Takes either a filename whose content should be pasted, or reads
221 content from standard input. If a highlighter is specified it will
222 be used, otherwise the file name will be used to determine an
223 appropriate highlighter.
228 class PastebinProvider(object):
229 @Command('pastebin', category
='misc',
230 description
=MACH_PASTEBIN_DESCRIPTION
)
231 @CommandArgument('--list-highlighters', action
='store_true',
232 help='List known highlighters and exit')
233 @CommandArgument('--highlighter', default
=None,
234 help='Syntax highlighting to use for paste')
235 @CommandArgument('--expires', default
='week',
236 choices
=sorted(MACH_PASTEBIN_DURATIONS
.keys()),
237 help='Expire paste after given time duration (default: %(default)s)')
238 @CommandArgument('--verbose', action
='store_true',
239 help='Print extra info such as selected syntax highlighter')
240 @CommandArgument('path', nargs
='?', default
=None,
241 help='Path to file for upload to paste.mozilla.org')
242 def pastebin(self
, list_highlighters
, highlighter
, expires
, verbose
, path
):
245 def verbose_print(*args
, **kwargs
):
246 '''Print a string if `--verbose` flag is set'''
248 print(*args
, **kwargs
)
250 # Show known highlighters and exit.
251 if list_highlighters
:
252 lexers
= set(EXTENSION_TO_HIGHLIGHTER
.values())
253 print('Available lexers:\n'
254 ' - %s' % '\n - '.join(sorted(lexers
)))
257 # Get a correct expiry value.
259 verbose_print('Setting expiry from %s' % expires
)
260 expires
= MACH_PASTEBIN_DURATIONS
[expires
]
261 verbose_print('Using %s as expiry' % expires
)
263 print('%s is not a valid duration.\n'
264 '(hint: try one of %s)' %
265 (expires
, ', '.join(MACH_PASTEBIN_DURATIONS
.keys())))
273 # Get content to be pasted.
275 verbose_print('Reading content from %s' % path
)
277 with
open(path
, 'r') as f
:
280 print('ERROR. No such file %s' % path
)
283 lexer
= guess_highlighter_from_path(path
)
285 data
['lexer'] = lexer
287 verbose_print('Reading content from stdin')
288 content
= sys
.stdin
.read()
290 # Assert the length of content to be posted does not exceed the maximum.
291 content_length
= len(content
)
292 verbose_print('Checking size of content is okay (%d)' % content_length
)
293 if content_length
> PASTEMO_MAX_CONTENT_LENGTH
:
294 print('Paste content is too large (%d, maximum %d)' %
295 (content_length
, PASTEMO_MAX_CONTENT_LENGTH
))
298 data
['content'] = content
300 # Highlight as specified language, overwriting value set from filename.
302 verbose_print('Setting %s as highlighter' % highlighter
)
303 data
['lexer'] = highlighter
306 verbose_print('Sending request to %s' % PASTEMO_URL
)
307 resp
= requests
.post(PASTEMO_URL
, data
=data
)
309 # Error code should always be 400.
310 # Response content will include a helpful error message,
311 # so print it here (for example, if an invalid highlighter is
312 # provided, it will return a list of valid highlighters).
313 if resp
.status_code
>= 400:
314 print('Error code %d: %s' % (resp
.status_code
, resp
.content
))
317 verbose_print('Pasted successfully')
319 response_json
= resp
.json()
321 verbose_print('Paste highlighted as %s' % response_json
['lexer'])
322 print(response_json
['url'])
325 except Exception as e
:
326 print('ERROR. Paste failed.')
331 def mozregression_import():
332 # Lazy loading of mozregression.
333 # Note that only the mach_interface module should be used from this file.
335 import mozregression
.mach_interface
338 return mozregression
.mach_interface
341 def mozregression_create_parser():
342 # Create the mozregression command line parser.
343 # if mozregression is not installed, or not up to date, it will
344 # first be installed.
345 cmd
= MozbuildObject
.from_environment()
346 cmd
._activate
_virtualenv
()
347 mozregression
= mozregression_import()
348 if not mozregression
:
349 # mozregression is not here at all, install it
350 cmd
.virtualenv_manager
.install_pip_package('mozregression')
351 print("mozregression was installed. please re-run your"
352 " command. If you keep getting this message please "
353 " manually run: 'pip install -U mozregression'.")
355 # check if there is a new release available
356 release
= mozregression
.new_release_on_pypi()
359 # there is one, so install it. Note that install_pip_package
360 # does not work here, so just run pip directly.
361 cmd
.virtualenv_manager
._run
_pip
([
363 'mozregression==%s' % release
365 print("mozregression was updated to version %s. please"
366 " re-run your command." % release
)
368 # mozregression is up to date, return the parser.
369 return mozregression
.parser()
370 # exit if we updated or installed mozregression because
371 # we may have already imported mozregression and running it
372 # as this may cause issues.
377 class MozregressionCommand(MachCommandBase
):
378 @Command('mozregression',
380 description
=("Regression range finder for nightly"
381 " and inbound builds."),
382 parser
=mozregression_create_parser
)
383 def run(self
, **options
):
384 self
._activate
_virtualenv
()
385 mozregression
= mozregression_import()
386 mozregression
.run(options
)