summaryrefslogtreecommitdiffstats
path: root/tools/mach_commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/mach_commands.py')
-rw-r--r--tools/mach_commands.py364
1 files changed, 364 insertions, 0 deletions
diff --git a/tools/mach_commands.py b/tools/mach_commands.py
new file mode 100644
index 000000000..898073bb6
--- /dev/null
+++ b/tools/mach_commands.py
@@ -0,0 +1,364 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, # You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import, unicode_literals
+
+import sys
+import os
+import stat
+import platform
+import errno
+import subprocess
+
+from mach.decorators import (
+ CommandArgument,
+ CommandProvider,
+ Command,
+)
+
+from mozbuild.base import MachCommandBase, MozbuildObject
+
+
+@CommandProvider
+class SearchProvider(object):
+ @Command('dxr', category='misc',
+ description='Search for something in DXR.')
+ @CommandArgument('term', nargs='+', help='Term(s) to search for.')
+ def dxr(self, term):
+ import webbrowser
+ term = ' '.join(term)
+ uri = 'http://dxr.mozilla.org/mozilla-central/search?q=%s&redirect=true' % term
+ webbrowser.open_new_tab(uri)
+
+ @Command('mdn', category='misc',
+ description='Search for something on MDN.')
+ @CommandArgument('term', nargs='+', help='Term(s) to search for.')
+ def mdn(self, term):
+ import webbrowser
+ term = ' '.join(term)
+ uri = 'https://developer.mozilla.org/search?q=%s' % term
+ webbrowser.open_new_tab(uri)
+
+ @Command('google', category='misc',
+ description='Search for something on Google.')
+ @CommandArgument('term', nargs='+', help='Term(s) to search for.')
+ def google(self, term):
+ import webbrowser
+ term = ' '.join(term)
+ uri = 'https://www.google.com/search?q=%s' % term
+ webbrowser.open_new_tab(uri)
+
+ @Command('search', category='misc',
+ description='Search for something on the Internets. '
+ 'This will open 3 new browser tabs and search for the term on Google, '
+ 'MDN, and DXR.')
+ @CommandArgument('term', nargs='+', help='Term(s) to search for.')
+ def search(self, term):
+ self.google(term)
+ self.mdn(term)
+ self.dxr(term)
+
+
+@CommandProvider
+class UUIDProvider(object):
+ @Command('uuid', category='misc',
+ description='Generate a uuid.')
+ @CommandArgument('--format', '-f', choices=['idl', 'cpp', 'c++'],
+ help='Output format for the generated uuid.')
+ def uuid(self, format=None):
+ import uuid
+ u = uuid.uuid4()
+ if format in [None, 'idl']:
+ print(u)
+ if format is None:
+ print('')
+ if format in [None, 'cpp', 'c++']:
+ u = u.hex
+ print('{ 0x%s, 0x%s, 0x%s, \\' % (u[0:8], u[8:12], u[12:16]))
+ pairs = tuple(map(lambda n: u[n:n+2], range(16, 32, 2)))
+ print((' { ' + '0x%s, ' * 7 + '0x%s } }') % pairs)
+
+
+@CommandProvider
+class RageProvider(MachCommandBase):
+ @Command('rage', category='misc',
+ description='Express your frustration')
+ def rage(self):
+ """Have a bad experience developing Firefox? Run this command to
+ express your frustration.
+
+ This command will open your default configured web browser to a short
+ form where you can submit feedback. Just close the tab when done.
+ """
+ import getpass
+ import urllib
+ import webbrowser
+
+ # Try to resolve the current user.
+ user = None
+ with open(os.devnull, 'wb') as null:
+ if os.path.exists(os.path.join(self.topsrcdir, '.hg')):
+ try:
+ user = subprocess.check_output(['hg', 'config',
+ 'ui.username'],
+ cwd=self.topsrcdir,
+ stderr=null)
+
+ i = user.find('<')
+ if i >= 0:
+ user = user[i + 1:-2]
+ except subprocess.CalledProcessError:
+ pass
+ elif os.path.exists(os.path.join(self.topsrcdir, '.git')):
+ try:
+ user = subprocess.check_output(['git', 'config', '--get',
+ 'user.email'],
+ cwd=self.topsrcdir,
+ stderr=null)
+ except subprocess.CalledProcessError:
+ pass
+
+ if not user:
+ try:
+ user = getpass.getuser()
+ except Exception:
+ pass
+
+ url = 'https://docs.google.com/a/mozilla.com/forms/d/e/1FAIpQLSeDVC3IXJu5d33Hp_ZTCOw06xEUiYH1pBjAqJ1g_y63sO2vvA/viewform'
+ if user:
+ url += '?entry.1281044204=%s' % urllib.quote(user)
+
+ print('Please leave your feedback in the opened web form')
+ webbrowser.open_new_tab(url)
+
+
+@CommandProvider
+class PastebinProvider(object):
+ @Command('pastebin', category='misc',
+ description='Command line interface to pastebin.mozilla.org.')
+ @CommandArgument('--language', default=None,
+ help='Language to use for syntax highlighting')
+ @CommandArgument('--poster', default='',
+ help='Specify your name for use with pastebin.mozilla.org')
+ @CommandArgument('--duration', default='day',
+ choices=['d', 'day', 'm', 'month', 'f', 'forever'],
+ help='Keep for specified duration (default: %(default)s)')
+ @CommandArgument('file', nargs='?', default=None,
+ help='Specify the file to upload to pastebin.mozilla.org')
+
+ def pastebin(self, language, poster, duration, file):
+ import urllib
+ import urllib2
+
+ URL = 'https://pastebin.mozilla.org/'
+
+ FILE_TYPES = [{'value': 'text', 'name': 'None', 'extension': 'txt'},
+ {'value': 'bash', 'name': 'Bash', 'extension': 'sh'},
+ {'value': 'c', 'name': 'C', 'extension': 'c'},
+ {'value': 'cpp', 'name': 'C++', 'extension': 'cpp'},
+ {'value': 'html4strict', 'name': 'HTML', 'extension': 'html'},
+ {'value': 'javascript', 'name': 'Javascript', 'extension': 'js'},
+ {'value': 'javascript', 'name': 'Javascript', 'extension': 'jsm'},
+ {'value': 'lua', 'name': 'Lua', 'extension': 'lua'},
+ {'value': 'perl', 'name': 'Perl', 'extension': 'pl'},
+ {'value': 'php', 'name': 'PHP', 'extension': 'php'},
+ {'value': 'python', 'name': 'Python', 'extension': 'py'},
+ {'value': 'ruby', 'name': 'Ruby', 'extension': 'rb'},
+ {'value': 'css', 'name': 'CSS', 'extension': 'css'},
+ {'value': 'diff', 'name': 'Diff', 'extension': 'diff'},
+ {'value': 'ini', 'name': 'INI file', 'extension': 'ini'},
+ {'value': 'java', 'name': 'Java', 'extension': 'java'},
+ {'value': 'xml', 'name': 'XML', 'extension': 'xml'},
+ {'value': 'xml', 'name': 'XML', 'extension': 'xul'}]
+
+ lang = ''
+
+ if file:
+ try:
+ with open(file, 'r') as f:
+ content = f.read()
+ # TODO: Use mime-types instead of extensions; suprocess('file <f_name>')
+ # Guess File-type based on file extension
+ extension = file.split('.')[-1]
+ for l in FILE_TYPES:
+ if extension == l['extension']:
+ print('Identified file as %s' % l['name'])
+ lang = l['value']
+ except IOError:
+ print('ERROR. No such file')
+ return 1
+ else:
+ content = sys.stdin.read()
+ duration = duration[0]
+
+ if language:
+ lang = language
+
+
+ params = [
+ ('parent_pid', ''),
+ ('format', lang),
+ ('code2', content),
+ ('poster', poster),
+ ('expiry', duration),
+ ('paste', 'Send')]
+
+ data = urllib.urlencode(params)
+ print('Uploading ...')
+ try:
+ req = urllib2.Request(URL, data)
+ response = urllib2.urlopen(req)
+ http_response_code = response.getcode()
+ if http_response_code == 200:
+ print(response.geturl())
+ else:
+ print('Could not upload the file, '
+ 'HTTP Response Code %s' %(http_response_code))
+ except urllib2.URLError:
+ print('ERROR. Could not connect to pastebin.mozilla.org.')
+ return 1
+ return 0
+
+
+@CommandProvider
+class FormatProvider(MachCommandBase):
+ @Command('clang-format', category='misc',
+ description='Run clang-format on current changes')
+ @CommandArgument('--show', '-s', action = 'store_true',
+ help = 'Show diff output on instead of applying changes')
+ def clang_format(self, show=False):
+ import urllib2
+
+ plat = platform.system()
+ fmt = plat.lower() + "/clang-format-3.5"
+ fmt_diff = "clang-format-diff-3.5"
+
+ # We are currently using a modified version of clang-format hosted on people.mozilla.org.
+ # This is a temporary work around until we upstream the necessary changes and we can use
+ # a system version of clang-format. See bug 961541.
+ if plat == "Windows":
+ fmt += ".exe"
+ else:
+ arch = os.uname()[4]
+ if (plat != "Linux" and plat != "Darwin") or arch != 'x86_64':
+ print("Unsupported platform " + plat + "/" + arch +
+ ". Supported platforms are Windows/*, Linux/x86_64 and Darwin/x86_64")
+ return 1
+
+ os.chdir(self.topsrcdir)
+ self.prompt = True
+
+ try:
+ if not self.locate_or_fetch(fmt):
+ return 1
+ clang_format_diff = self.locate_or_fetch(fmt_diff)
+ if not clang_format_diff:
+ return 1
+
+ except urllib2.HTTPError as e:
+ print("HTTP error {0}: {1}".format(e.code, e.reason))
+ return 1
+
+ from subprocess import Popen, PIPE
+
+ if os.path.exists(".hg"):
+ diff_process = Popen(["hg", "diff", "-U0", "-r", "tip^",
+ "--include", "glob:**.c", "--include", "glob:**.cpp", "--include", "glob:**.h",
+ "--exclude", "listfile:.clang-format-ignore"], stdout=PIPE)
+ else:
+ git_process = Popen(["git", "diff", "-U0", "HEAD^"], stdout=PIPE)
+ try:
+ diff_process = Popen(["filterdiff", "--include=*.h", "--include=*.cpp",
+ "--exclude-from-file=.clang-format-ignore"],
+ stdin=git_process.stdout, stdout=PIPE)
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ print("Can't find filterdiff. Please install patchutils.")
+ else:
+ print("OSError {0}: {1}".format(e.code, e.reason))
+ return 1
+
+
+ args = [sys.executable, clang_format_diff, "-p1"]
+ if not show:
+ args.append("-i")
+ cf_process = Popen(args, stdin=diff_process.stdout)
+ return cf_process.communicate()[0]
+
+ def locate_or_fetch(self, root):
+ target = os.path.join(self._mach_context.state_dir, os.path.basename(root))
+ if not os.path.exists(target):
+ site = "https://people.mozilla.org/~ajones/clang-format/"
+ if self.prompt and raw_input("Download clang-format executables from {0} (yN)? ".format(site)).lower() != 'y':
+ print("Download aborted.")
+ return 1
+ self.prompt = False
+
+ u = site + root
+ print("Downloading {0} to {1}".format(u, target))
+ data = urllib2.urlopen(url=u).read()
+ temp = target + ".tmp"
+ with open(temp, "wb") as fh:
+ fh.write(data)
+ fh.close()
+ os.chmod(temp, os.stat(temp).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
+ os.rename(temp, target)
+ return target
+
+def mozregression_import():
+ # Lazy loading of mozregression.
+ # Note that only the mach_interface module should be used from this file.
+ try:
+ import mozregression.mach_interface
+ except ImportError:
+ return None
+ return mozregression.mach_interface
+
+
+def mozregression_create_parser():
+ # Create the mozregression command line parser.
+ # if mozregression is not installed, or not up to date, it will
+ # first be installed.
+ cmd = MozbuildObject.from_environment()
+ cmd._activate_virtualenv()
+ mozregression = mozregression_import()
+ if not mozregression:
+ # mozregression is not here at all, install it
+ cmd.virtualenv_manager.install_pip_package('mozregression')
+ print("mozregression was installed. please re-run your"
+ " command. If you keep getting this message please "
+ " manually run: 'pip install -U mozregression'.")
+ else:
+ # check if there is a new release available
+ release = mozregression.new_release_on_pypi()
+ if release:
+ print(release)
+ # there is one, so install it. Note that install_pip_package
+ # does not work here, so just run pip directly.
+ cmd.virtualenv_manager._run_pip([
+ 'install',
+ 'mozregression==%s' % release
+ ])
+ print("mozregression was updated to version %s. please"
+ " re-run your command." % release)
+ else:
+ # mozregression is up to date, return the parser.
+ return mozregression.parser()
+ # exit if we updated or installed mozregression because
+ # we may have already imported mozregression and running it
+ # as this may cause issues.
+ sys.exit(0)
+
+
+@CommandProvider
+class MozregressionCommand(MachCommandBase):
+ @Command('mozregression',
+ category='misc',
+ description=("Regression range finder for nightly"
+ " and inbound builds."),
+ parser=mozregression_create_parser)
+ def run(self, **options):
+ self._activate_virtualenv()
+ mozregression = mozregression_import()
+ mozregression.run(options)