mach_commands.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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. from __future__ import absolute_import, unicode_literals
  5. import sys
  6. import os
  7. import stat
  8. import platform
  9. import errno
  10. import subprocess
  11. from mach.decorators import (
  12. CommandArgument,
  13. CommandProvider,
  14. Command,
  15. )
  16. from mozbuild.base import MachCommandBase, MozbuildObject
  17. @CommandProvider
  18. class SearchProvider(object):
  19. @Command('dxr', category='misc',
  20. description='Search for something in DXR.')
  21. @CommandArgument('term', nargs='+', help='Term(s) to search for.')
  22. def dxr(self, term):
  23. import webbrowser
  24. term = ' '.join(term)
  25. uri = 'http://dxr.mozilla.org/mozilla-central/search?q=%s&redirect=true' % term
  26. webbrowser.open_new_tab(uri)
  27. @Command('mdn', category='misc',
  28. description='Search for something on MDN.')
  29. @CommandArgument('term', nargs='+', help='Term(s) to search for.')
  30. def mdn(self, term):
  31. import webbrowser
  32. term = ' '.join(term)
  33. uri = 'https://developer.mozilla.org/search?q=%s' % term
  34. webbrowser.open_new_tab(uri)
  35. @Command('google', category='misc',
  36. description='Search for something on Google.')
  37. @CommandArgument('term', nargs='+', help='Term(s) to search for.')
  38. def google(self, term):
  39. import webbrowser
  40. term = ' '.join(term)
  41. uri = 'https://www.google.com/search?q=%s' % term
  42. webbrowser.open_new_tab(uri)
  43. @Command('search', category='misc',
  44. description='Search for something on the Internets. '
  45. 'This will open 3 new browser tabs and search for the term on Google, '
  46. 'MDN, and DXR.')
  47. @CommandArgument('term', nargs='+', help='Term(s) to search for.')
  48. def search(self, term):
  49. self.google(term)
  50. self.mdn(term)
  51. self.dxr(term)
  52. @CommandProvider
  53. class UUIDProvider(object):
  54. @Command('uuid', category='misc',
  55. description='Generate a uuid.')
  56. @CommandArgument('--format', '-f', choices=['idl', 'cpp', 'c++'],
  57. help='Output format for the generated uuid.')
  58. def uuid(self, format=None):
  59. import uuid
  60. u = uuid.uuid4()
  61. if format in [None, 'idl']:
  62. print(u)
  63. if format is None:
  64. print('')
  65. if format in [None, 'cpp', 'c++']:
  66. u = u.hex
  67. print('{ 0x%s, 0x%s, 0x%s, \\' % (u[0:8], u[8:12], u[12:16]))
  68. pairs = tuple(map(lambda n: u[n:n+2], range(16, 32, 2)))
  69. print((' { ' + '0x%s, ' * 7 + '0x%s } }') % pairs)
  70. @CommandProvider
  71. class RageProvider(MachCommandBase):
  72. @Command('rage', category='misc',
  73. description='Express your frustration')
  74. def rage(self):
  75. """Have a bad experience developing Firefox? Run this command to
  76. express your frustration.
  77. This command will open your default configured web browser to a short
  78. form where you can submit feedback. Just close the tab when done.
  79. """
  80. import getpass
  81. import urllib
  82. import webbrowser
  83. # Try to resolve the current user.
  84. user = None
  85. with open(os.devnull, 'wb') as null:
  86. if os.path.exists(os.path.join(self.topsrcdir, '.hg')):
  87. try:
  88. user = subprocess.check_output(['hg', 'config',
  89. 'ui.username'],
  90. cwd=self.topsrcdir,
  91. stderr=null)
  92. i = user.find('<')
  93. if i >= 0:
  94. user = user[i + 1:-2]
  95. except subprocess.CalledProcessError:
  96. pass
  97. elif os.path.exists(os.path.join(self.topsrcdir, '.git')):
  98. try:
  99. user = subprocess.check_output(['git', 'config', '--get',
  100. 'user.email'],
  101. cwd=self.topsrcdir,
  102. stderr=null)
  103. except subprocess.CalledProcessError:
  104. pass
  105. if not user:
  106. try:
  107. user = getpass.getuser()
  108. except Exception:
  109. pass
  110. url = 'https://docs.google.com/a/mozilla.com/forms/d/e/1FAIpQLSeDVC3IXJu5d33Hp_ZTCOw06xEUiYH1pBjAqJ1g_y63sO2vvA/viewform'
  111. if user:
  112. url += '?entry.1281044204=%s' % urllib.quote(user)
  113. print('Please leave your feedback in the opened web form')
  114. webbrowser.open_new_tab(url)
  115. @CommandProvider
  116. class PastebinProvider(object):
  117. @Command('pastebin', category='misc',
  118. description='Command line interface to pastebin.mozilla.org.')
  119. @CommandArgument('--language', default=None,
  120. help='Language to use for syntax highlighting')
  121. @CommandArgument('--poster', default='',
  122. help='Specify your name for use with pastebin.mozilla.org')
  123. @CommandArgument('--duration', default='day',
  124. choices=['d', 'day', 'm', 'month', 'f', 'forever'],
  125. help='Keep for specified duration (default: %(default)s)')
  126. @CommandArgument('file', nargs='?', default=None,
  127. help='Specify the file to upload to pastebin.mozilla.org')
  128. def pastebin(self, language, poster, duration, file):
  129. import urllib
  130. import urllib2
  131. URL = 'https://pastebin.mozilla.org/'
  132. FILE_TYPES = [{'value': 'text', 'name': 'None', 'extension': 'txt'},
  133. {'value': 'bash', 'name': 'Bash', 'extension': 'sh'},
  134. {'value': 'c', 'name': 'C', 'extension': 'c'},
  135. {'value': 'cpp', 'name': 'C++', 'extension': 'cpp'},
  136. {'value': 'html4strict', 'name': 'HTML', 'extension': 'html'},
  137. {'value': 'javascript', 'name': 'Javascript', 'extension': 'js'},
  138. {'value': 'javascript', 'name': 'Javascript', 'extension': 'jsm'},
  139. {'value': 'lua', 'name': 'Lua', 'extension': 'lua'},
  140. {'value': 'perl', 'name': 'Perl', 'extension': 'pl'},
  141. {'value': 'php', 'name': 'PHP', 'extension': 'php'},
  142. {'value': 'python', 'name': 'Python', 'extension': 'py'},
  143. {'value': 'ruby', 'name': 'Ruby', 'extension': 'rb'},
  144. {'value': 'css', 'name': 'CSS', 'extension': 'css'},
  145. {'value': 'diff', 'name': 'Diff', 'extension': 'diff'},
  146. {'value': 'ini', 'name': 'INI file', 'extension': 'ini'},
  147. {'value': 'java', 'name': 'Java', 'extension': 'java'},
  148. {'value': 'xml', 'name': 'XML', 'extension': 'xml'},
  149. {'value': 'xml', 'name': 'XML', 'extension': 'xul'}]
  150. lang = ''
  151. if file:
  152. try:
  153. with open(file, 'r') as f:
  154. content = f.read()
  155. # TODO: Use mime-types instead of extensions; suprocess('file <f_name>')
  156. # Guess File-type based on file extension
  157. extension = file.split('.')[-1]
  158. for l in FILE_TYPES:
  159. if extension == l['extension']:
  160. print('Identified file as %s' % l['name'])
  161. lang = l['value']
  162. except IOError:
  163. print('ERROR. No such file')
  164. return 1
  165. else:
  166. content = sys.stdin.read()
  167. duration = duration[0]
  168. if language:
  169. lang = language
  170. params = [
  171. ('parent_pid', ''),
  172. ('format', lang),
  173. ('code2', content),
  174. ('poster', poster),
  175. ('expiry', duration),
  176. ('paste', 'Send')]
  177. data = urllib.urlencode(params)
  178. print('Uploading ...')
  179. try:
  180. req = urllib2.Request(URL, data)
  181. response = urllib2.urlopen(req)
  182. http_response_code = response.getcode()
  183. if http_response_code == 200:
  184. print(response.geturl())
  185. else:
  186. print('Could not upload the file, '
  187. 'HTTP Response Code %s' %(http_response_code))
  188. except urllib2.URLError:
  189. print('ERROR. Could not connect to pastebin.mozilla.org.')
  190. return 1
  191. return 0
  192. @CommandProvider
  193. class FormatProvider(MachCommandBase):
  194. @Command('clang-format', category='misc',
  195. description='Run clang-format on current changes')
  196. @CommandArgument('--show', '-s', action = 'store_true',
  197. help = 'Show diff output on instead of applying changes')
  198. def clang_format(self, show=False):
  199. import urllib2
  200. plat = platform.system()
  201. fmt = plat.lower() + "/clang-format-3.5"
  202. fmt_diff = "clang-format-diff-3.5"
  203. # We are currently using a modified version of clang-format hosted on people.mozilla.org.
  204. # This is a temporary work around until we upstream the necessary changes and we can use
  205. # a system version of clang-format. See bug 961541.
  206. if plat == "Windows":
  207. fmt += ".exe"
  208. else:
  209. arch = os.uname()[4]
  210. if (plat != "Linux" and plat != "Darwin") or arch != 'x86_64':
  211. print("Unsupported platform " + plat + "/" + arch +
  212. ". Supported platforms are Windows/*, Linux/x86_64 and Darwin/x86_64")
  213. return 1
  214. os.chdir(self.topsrcdir)
  215. self.prompt = True
  216. try:
  217. if not self.locate_or_fetch(fmt):
  218. return 1
  219. clang_format_diff = self.locate_or_fetch(fmt_diff)
  220. if not clang_format_diff:
  221. return 1
  222. except urllib2.HTTPError as e:
  223. print("HTTP error {0}: {1}".format(e.code, e.reason))
  224. return 1
  225. from subprocess import Popen, PIPE
  226. if os.path.exists(".hg"):
  227. diff_process = Popen(["hg", "diff", "-U0", "-r", "tip^",
  228. "--include", "glob:**.c", "--include", "glob:**.cpp", "--include", "glob:**.h",
  229. "--exclude", "listfile:.clang-format-ignore"], stdout=PIPE)
  230. else:
  231. git_process = Popen(["git", "diff", "-U0", "HEAD^"], stdout=PIPE)
  232. try:
  233. diff_process = Popen(["filterdiff", "--include=*.h", "--include=*.cpp",
  234. "--exclude-from-file=.clang-format-ignore"],
  235. stdin=git_process.stdout, stdout=PIPE)
  236. except OSError as e:
  237. if e.errno == errno.ENOENT:
  238. print("Can't find filterdiff. Please install patchutils.")
  239. else:
  240. print("OSError {0}: {1}".format(e.code, e.reason))
  241. return 1
  242. args = [sys.executable, clang_format_diff, "-p1"]
  243. if not show:
  244. args.append("-i")
  245. cf_process = Popen(args, stdin=diff_process.stdout)
  246. return cf_process.communicate()[0]
  247. def locate_or_fetch(self, root):
  248. target = os.path.join(self._mach_context.state_dir, os.path.basename(root))
  249. if not os.path.exists(target):
  250. site = "https://people.mozilla.org/~ajones/clang-format/"
  251. if self.prompt and raw_input("Download clang-format executables from {0} (yN)? ".format(site)).lower() != 'y':
  252. print("Download aborted.")
  253. return 1
  254. self.prompt = False
  255. u = site + root
  256. print("Downloading {0} to {1}".format(u, target))
  257. data = urllib2.urlopen(url=u).read()
  258. temp = target + ".tmp"
  259. with open(temp, "wb") as fh:
  260. fh.write(data)
  261. fh.close()
  262. os.chmod(temp, os.stat(temp).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
  263. os.rename(temp, target)
  264. return target
  265. def mozregression_import():
  266. # Lazy loading of mozregression.
  267. # Note that only the mach_interface module should be used from this file.
  268. try:
  269. import mozregression.mach_interface
  270. except ImportError:
  271. return None
  272. return mozregression.mach_interface
  273. def mozregression_create_parser():
  274. # Create the mozregression command line parser.
  275. # if mozregression is not installed, or not up to date, it will
  276. # first be installed.
  277. cmd = MozbuildObject.from_environment()
  278. cmd._activate_virtualenv()
  279. mozregression = mozregression_import()
  280. if not mozregression:
  281. # mozregression is not here at all, install it
  282. cmd.virtualenv_manager.install_pip_package('mozregression')
  283. print("mozregression was installed. please re-run your"
  284. " command. If you keep getting this message please "
  285. " manually run: 'pip install -U mozregression'.")
  286. else:
  287. # check if there is a new release available
  288. release = mozregression.new_release_on_pypi()
  289. if release:
  290. print(release)
  291. # there is one, so install it. Note that install_pip_package
  292. # does not work here, so just run pip directly.
  293. cmd.virtualenv_manager._run_pip([
  294. 'install',
  295. 'mozregression==%s' % release
  296. ])
  297. print("mozregression was updated to version %s. please"
  298. " re-run your command." % release)
  299. else:
  300. # mozregression is up to date, return the parser.
  301. return mozregression.parser()
  302. # exit if we updated or installed mozregression because
  303. # we may have already imported mozregression and running it
  304. # as this may cause issues.
  305. sys.exit(0)
  306. @CommandProvider
  307. class MozregressionCommand(MachCommandBase):
  308. @Command('mozregression',
  309. category='misc',
  310. description=("Regression range finder for nightly"
  311. " and inbound builds."),
  312. parser=mozregression_create_parser)
  313. def run(self, **options):
  314. self._activate_virtualenv()
  315. mozregression = mozregression_import()
  316. mozregression.run(options)