mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 08:18:54 +02:00 
			
		
		
		
	 98d1c44f9b
			
		
	
	
		98d1c44f9b
		
	
	
	
	
		
			
			Adds a new "hidden" argument to mach decorators that allows the command to execute but will not be visible in `mach help`. Removes `mach pastebin` functionality, marks it as "hidden", and displays a notice that pastebin.mozilla.org has been decommissioned. Differential Revision: https://phabricator.services.mozilla.com/D240280
		
			
				
	
	
		
			378 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			378 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # 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/.
 | |
| 
 | |
| import argparse
 | |
| import logging
 | |
| import subprocess
 | |
| import sys
 | |
| from datetime import datetime, timedelta
 | |
| from operator import itemgetter
 | |
| 
 | |
| from mach.decorators import Command, CommandArgument, SubCommand
 | |
| from mozbuild.base import MozbuildObject
 | |
| 
 | |
| 
 | |
| def _get_busted_bugs(payload):
 | |
|     import requests
 | |
| 
 | |
|     payload = dict(payload)
 | |
|     payload["include_fields"] = "id,summary,last_change_time,resolution"
 | |
|     payload["blocks"] = 1543241
 | |
|     response = requests.get("https://bugzilla.mozilla.org/rest/bug", payload)
 | |
|     response.raise_for_status()
 | |
|     return response.json().get("bugs", [])
 | |
| 
 | |
| 
 | |
| @Command(
 | |
|     "busted",
 | |
|     category="misc",
 | |
|     description="Query known bugs in our tooling, and file new ones.",
 | |
| )
 | |
| def busted_default(command_context):
 | |
|     unresolved = _get_busted_bugs({"resolution": "---"})
 | |
|     creation_time = datetime.now() - timedelta(days=15)
 | |
|     creation_time = creation_time.strftime("%Y-%m-%dT%H-%M-%SZ")
 | |
|     resolved = _get_busted_bugs({"creation_time": creation_time})
 | |
|     resolved = [bug for bug in resolved if bug["resolution"]]
 | |
|     all_bugs = sorted(
 | |
|         unresolved + resolved, key=itemgetter("last_change_time"), reverse=True
 | |
|     )
 | |
|     if all_bugs:
 | |
|         for bug in all_bugs:
 | |
|             print(
 | |
|                 "[%s] Bug %s - %s"
 | |
|                 % (
 | |
|                     (
 | |
|                         "UNRESOLVED"
 | |
|                         if not bug["resolution"]
 | |
|                         else "RESOLVED - %s" % bug["resolution"]
 | |
|                     ),
 | |
|                     bug["id"],
 | |
|                     bug["summary"],
 | |
|                 )
 | |
|             )
 | |
|     else:
 | |
|         print("No known tooling issues found.")
 | |
| 
 | |
| 
 | |
| @SubCommand("busted", "file", description="File a bug for busted tooling.")
 | |
| @CommandArgument(
 | |
|     "against",
 | |
|     help=(
 | |
|         "The specific mach command that is busted (i.e. if you encountered "
 | |
|         "an error with `mach build`, run `mach busted file build`). If "
 | |
|         "the issue is not connected to any particular mach command, you "
 | |
|         "can also run `mach busted file general`."
 | |
|     ),
 | |
| )
 | |
| def busted_file(command_context, against):
 | |
|     import webbrowser
 | |
| 
 | |
|     if (
 | |
|         against != "general"
 | |
|         and against not in command_context._mach_context.commands.command_handlers
 | |
|     ):
 | |
|         print(
 | |
|             "%s is not a valid value for `against`. `against` must be "
 | |
|             "the name of a `mach` command, or else the string "
 | |
|             '"general".' % against
 | |
|         )
 | |
|         return 1
 | |
| 
 | |
|     if against == "general":
 | |
|         product = "Firefox Build System"
 | |
|         component = "General"
 | |
|     else:
 | |
|         import inspect
 | |
| 
 | |
|         import mozpack.path as mozpath
 | |
| 
 | |
|         # Look up the file implementing that command, then cross-refernce
 | |
|         # moz.build files to get the product/component.
 | |
|         handler = command_context._mach_context.commands.command_handlers[against]
 | |
|         sourcefile = mozpath.relpath(
 | |
|             inspect.getsourcefile(handler.func), command_context.topsrcdir
 | |
|         )
 | |
|         reader = command_context.mozbuild_reader(config_mode="empty")
 | |
|         try:
 | |
|             res = reader.files_info([sourcefile])[sourcefile]["BUG_COMPONENT"]
 | |
|             product, component = res.product, res.component
 | |
|         except TypeError:
 | |
|             # The file might not have a bug set.
 | |
|             product = "Firefox Build System"
 | |
|             component = "General"
 | |
| 
 | |
|     uri = (
 | |
|         "https://bugzilla.mozilla.org/enter_bug.cgi?"
 | |
|         "product=%s&component=%s&blocked=1543241" % (product, component)
 | |
|     )
 | |
|     webbrowser.open_new_tab(uri)
 | |
| 
 | |
| 
 | |
| def pastebin_create_parser():
 | |
|     # Accept all args so they can be promptly ignored.
 | |
|     parser = argparse.ArgumentParser()
 | |
|     parser.add_argument("argv", nargs=argparse.REMAINDER, help=argparse.SUPPRESS)
 | |
|     return parser
 | |
| 
 | |
| 
 | |
| @Command(
 | |
|     "pastebin",
 | |
|     category="misc",
 | |
|     hidden=True,
 | |
|     parser=pastebin_create_parser,
 | |
| )
 | |
| def pastebin(command_context, argv):
 | |
|     """Obsolete command line interface to `paste.mozilla.org`."""
 | |
| 
 | |
|     print(
 | |
|         "pastebin.mozilla.org has been decommissioned.\n"
 | |
|         "Please use your favorite search engine to find alternatives."
 | |
|     )
 | |
|     return 1
 | |
| 
 | |
| 
 | |
| class PypiBasedTool:
 | |
|     """
 | |
|     Helper for loading a tool that is hosted on pypi. The package is expected
 | |
|     to expose a `mach_interface` module which has `new_release_on_pypi`,
 | |
|     `parser`, and `run` functions.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, module_name, pypi_name=None):
 | |
|         self.name = module_name
 | |
|         self.pypi_name = pypi_name or module_name
 | |
| 
 | |
|     def _import(self):
 | |
|         # Lazy loading of the tools mach interface.
 | |
|         # Note that only the mach_interface module should be used from this file.
 | |
|         import importlib
 | |
| 
 | |
|         try:
 | |
|             return importlib.import_module("%s.mach_interface" % self.name)
 | |
|         except ImportError:
 | |
|             return None
 | |
| 
 | |
|     def create_parser(self, subcommand=None):
 | |
|         # Create the command line parser.
 | |
|         # If the tool is not installed, or not up to date, it will
 | |
|         # first be installed.
 | |
|         cmd = MozbuildObject.from_environment()
 | |
|         cmd.activate_virtualenv()
 | |
|         tool = self._import()
 | |
|         if not tool:
 | |
|             # The tool is not here at all, install it
 | |
|             cmd.virtualenv_manager.install_pip_package(self.pypi_name)
 | |
|             print(
 | |
|                 "%s was installed. please re-run your"
 | |
|                 " command. If you keep getting this message please "
 | |
|                 " manually run: 'pip install -U %s'." % (self.pypi_name, self.pypi_name)
 | |
|             )
 | |
|         else:
 | |
|             # Check if there is a new release available
 | |
|             release = tool.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.
 | |
|                 subprocess.check_call(
 | |
|                     [
 | |
|                         cmd.virtualenv_manager.python_path,
 | |
|                         "-m",
 | |
|                         "pip",
 | |
|                         "install",
 | |
|                         f"{self.pypi_name}=={release}",
 | |
|                     ]
 | |
|                 )
 | |
|                 print(
 | |
|                     "%s was updated to version %s. please"
 | |
|                     " re-run your command." % (self.pypi_name, release)
 | |
|                 )
 | |
|             else:
 | |
|                 # Tool is up to date, return the parser.
 | |
|                 if subcommand:
 | |
|                     return tool.parser(subcommand)
 | |
|                 else:
 | |
|                     return tool.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)
 | |
| 
 | |
|     def run(self, **options):
 | |
|         tool = self._import()
 | |
|         tool.run(options)
 | |
| 
 | |
| 
 | |
| 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.
 | |
|     loader = PypiBasedTool("mozregression")
 | |
|     return loader.create_parser()
 | |
| 
 | |
| 
 | |
| @Command(
 | |
|     "mozregression",
 | |
|     category="misc",
 | |
|     description="Regression range finder for nightly and inbound builds.",
 | |
|     parser=mozregression_create_parser,
 | |
| )
 | |
| def run(command_context, **options):
 | |
|     command_context.activate_virtualenv()
 | |
|     mozregression = PypiBasedTool("mozregression")
 | |
|     mozregression.run(**options)
 | |
| 
 | |
| 
 | |
| @Command(
 | |
|     "node",
 | |
|     category="devenv",
 | |
|     description="Run the NodeJS interpreter used for building.",
 | |
| )
 | |
| @CommandArgument("args", nargs=argparse.REMAINDER)
 | |
| def node(command_context, args):
 | |
|     from mozbuild.nodeutil import find_node_executable
 | |
| 
 | |
|     # Avoid logging the command
 | |
|     command_context.log_manager.terminal_handler.setLevel(logging.CRITICAL)
 | |
| 
 | |
|     node_path, _ = find_node_executable()
 | |
| 
 | |
|     return command_context.run_process(
 | |
|         [node_path] + args,
 | |
|         pass_thru=True,  # Allow user to run Node interactively.
 | |
|         ensure_exit_code=False,  # Don't throw on non-zero exit code.
 | |
|     )
 | |
| 
 | |
| 
 | |
| @Command(
 | |
|     "npm",
 | |
|     category="devenv",
 | |
|     description="Run the npm executable from the NodeJS used for building.",
 | |
| )
 | |
| @CommandArgument("args", nargs=argparse.REMAINDER)
 | |
| def npm(command_context, args):
 | |
|     from mozbuild.nodeutil import find_npm_executable
 | |
| 
 | |
|     # Avoid logging the command
 | |
|     command_context.log_manager.terminal_handler.setLevel(logging.CRITICAL)
 | |
| 
 | |
|     import os
 | |
| 
 | |
|     # Add node and npm from mozbuild to front of system path
 | |
|     #
 | |
|     # This isn't pretty, but npm currently executes itself with
 | |
|     # `#!/usr/bin/env node`, which means it just uses the node in the
 | |
|     # current PATH. As a result, stuff gets built wrong and installed
 | |
|     # in the wrong places and probably other badness too without this:
 | |
|     npm_path, _ = find_npm_executable()
 | |
|     if not npm_path:
 | |
|         exit(-1, "could not find npm executable")
 | |
|     path = os.path.abspath(os.path.dirname(npm_path))
 | |
|     os.environ["PATH"] = "{}{}{}".format(path, os.pathsep, os.environ["PATH"])
 | |
| 
 | |
|     # karma-firefox-launcher needs the path to firefox binary.
 | |
|     firefox_bin = command_context.get_binary_path(validate_exists=False)
 | |
|     if os.path.exists(firefox_bin):
 | |
|         os.environ["FIREFOX_BIN"] = firefox_bin
 | |
| 
 | |
|     return command_context.run_process(
 | |
|         [npm_path, "--scripts-prepend-node-path=auto"] + args,
 | |
|         pass_thru=True,  # Avoid eating npm output/error messages
 | |
|         ensure_exit_code=False,  # Don't throw on non-zero exit code.
 | |
|     )
 | |
| 
 | |
| 
 | |
| def logspam_create_parser(subcommand):
 | |
|     # Create the logspam command line parser.
 | |
|     # if logspam is not installed, or not up to date, it will
 | |
|     # first be installed.
 | |
|     loader = PypiBasedTool("logspam", "mozilla-log-spam")
 | |
|     return loader.create_parser(subcommand)
 | |
| 
 | |
| 
 | |
| from functools import partial
 | |
| 
 | |
| 
 | |
| @Command(
 | |
|     "logspam",
 | |
|     category="misc",
 | |
|     description="Warning categorizer for treeherder test runs.",
 | |
| )
 | |
| def logspam(command_context):
 | |
|     pass
 | |
| 
 | |
| 
 | |
| @SubCommand("logspam", "report", parser=partial(logspam_create_parser, "report"))
 | |
| def report(command_context, **options):
 | |
|     command_context.activate_virtualenv()
 | |
|     logspam = PypiBasedTool("logspam")
 | |
|     logspam.run(command="report", **options)
 | |
| 
 | |
| 
 | |
| @SubCommand("logspam", "bisect", parser=partial(logspam_create_parser, "bisect"))
 | |
| def bisect(command_context, **options):
 | |
|     command_context.activate_virtualenv()
 | |
|     logspam = PypiBasedTool("logspam")
 | |
|     logspam.run(command="bisect", **options)
 | |
| 
 | |
| 
 | |
| @SubCommand("logspam", "file", parser=partial(logspam_create_parser, "file"))
 | |
| def create(command_context, **options):
 | |
|     command_context.activate_virtualenv()
 | |
|     logspam = PypiBasedTool("logspam")
 | |
|     logspam.run(command="file", **options)
 | |
| 
 | |
| 
 | |
| # mots_loader will be used when running commands and subcommands, as well as
 | |
| # when creating the parsers.
 | |
| mots_loader = PypiBasedTool("mots")
 | |
| 
 | |
| 
 | |
| def mots_create_parser(subcommand=None):
 | |
|     return mots_loader.create_parser(subcommand)
 | |
| 
 | |
| 
 | |
| def mots_run_subcommand(command, command_context, **options):
 | |
|     command_context.activate_virtualenv()
 | |
|     mots_loader.run(command=command, **options)
 | |
| 
 | |
| 
 | |
| class motsSubCommand(SubCommand):
 | |
|     """A helper subclass that reduces repitition when defining subcommands."""
 | |
| 
 | |
|     def __init__(self, subcommand):
 | |
|         super().__init__(
 | |
|             "mots",
 | |
|             subcommand,
 | |
|             parser=partial(mots_create_parser, subcommand),
 | |
|         )
 | |
| 
 | |
| 
 | |
| @Command(
 | |
|     "mots",
 | |
|     category="misc",
 | |
|     description="Manage module information in-tree using the mots CLI.",
 | |
|     parser=mots_create_parser,
 | |
| )
 | |
| def mots(command_context, **options):
 | |
|     """The main mots command call."""
 | |
|     command_context.activate_virtualenv()
 | |
|     mots_loader.run(**options)
 | |
| 
 | |
| 
 | |
| # Define subcommands that will be proxied through mach.
 | |
| for sc in (
 | |
|     "clean",
 | |
|     "check-hashes",
 | |
|     "export",
 | |
|     "export-and-clean",
 | |
|     "module",
 | |
|     "query",
 | |
|     "settings",
 | |
|     "user",
 | |
|     "validate",
 | |
| ):
 | |
|     # Pass through args and kwargs, but add the subcommand string as the first argument.
 | |
|     motsSubCommand(sc)(lambda *a, **kw: mots_run_subcommand(sc, *a, **kw))
 |