mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-11 05:39:41 +02:00
mozboot.util.get_state_dir() returns a tuple of (<path>, <bool). The bool denotes whether or not the state dir came from an environment variable. But this value is only used in a single place, and is very easy to test for anyway. It's not worth the added complexity it imposes on all other consumers of this function. Let's just make this function return the path. Differential Revision: https://phabricator.services.mozilla.com/D15723 --HG-- extra : moz-landing-system : lando
285 lines
8.3 KiB
Python
285 lines
8.3 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/.
|
|
|
|
from __future__ import absolute_import, print_function, unicode_literals
|
|
|
|
import os
|
|
import platform
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from distutils.spawn import find_executable
|
|
|
|
from mozboot.util import get_state_dir
|
|
from mozterm import Terminal
|
|
from six import string_types
|
|
|
|
from .. import preset as pset
|
|
from ..cli import BaseTryParser
|
|
from ..tasks import generate_tasks, filter_tasks_by_paths
|
|
from ..push import check_working_directory, push_to_try, vcs
|
|
|
|
terminal = Terminal()
|
|
|
|
here = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
# Some tasks show up in the target task set, but are either special cases
|
|
# or uncommon enough that they should only be selectable with --full.
|
|
TARGET_TASK_FILTERS = (
|
|
'.*-ccov\/.*',
|
|
)
|
|
|
|
|
|
FZF_NOT_FOUND = """
|
|
Could not find the `fzf` binary.
|
|
|
|
The `mach try fuzzy` command depends on fzf. Please install it following the
|
|
appropriate instructions for your platform:
|
|
|
|
https://github.com/junegunn/fzf#installation
|
|
|
|
Only the binary is required, if you do not wish to install the shell and
|
|
editor integrations, download the appropriate binary and put it on your $PATH:
|
|
|
|
https://github.com/junegunn/fzf-bin/releases
|
|
""".lstrip()
|
|
|
|
FZF_INSTALL_FAILED = """
|
|
Failed to install fzf.
|
|
|
|
Please install fzf manually following the appropriate instructions for your
|
|
platform:
|
|
|
|
https://github.com/junegunn/fzf#installation
|
|
|
|
Only the binary is required, if you do not wish to install the shell and
|
|
editor integrations, download the appropriate binary and put it on your $PATH:
|
|
|
|
https://github.com/junegunn/fzf-bin/releases
|
|
""".lstrip()
|
|
|
|
FZF_HEADER = """
|
|
For more shortcuts, see {t.italic_white}mach help try fuzzy{t.normal} and {t.italic_white}man fzf
|
|
{shortcuts}
|
|
""".strip()
|
|
|
|
fzf_shortcuts = {
|
|
'ctrl-a': 'select-all',
|
|
'ctrl-d': 'deselect-all',
|
|
'ctrl-t': 'toggle-all',
|
|
'alt-bspace': 'beginning-of-line+kill-line',
|
|
'?': 'toggle-preview',
|
|
}
|
|
|
|
fzf_header_shortcuts = {
|
|
'cursor-up': 'ctrl-k',
|
|
'cursor-down': 'ctrl-j',
|
|
'toggle-select': 'tab',
|
|
'select-all': 'ctrl-a',
|
|
'accept': 'enter',
|
|
'cancel': 'ctrl-c',
|
|
}
|
|
|
|
|
|
class FuzzyParser(BaseTryParser):
|
|
name = 'fuzzy'
|
|
arguments = [
|
|
[['-q', '--query'],
|
|
{'metavar': 'STR',
|
|
'action': 'append',
|
|
'help': "Use the given query instead of entering the selection "
|
|
"interface. Equivalent to typing <query><ctrl-a><enter> "
|
|
"from the interface. Specifying multiple times schedules "
|
|
"the union of computed tasks.",
|
|
}],
|
|
[['-e', '--exact'],
|
|
{'action': 'store_true',
|
|
'default': False,
|
|
'help': "Enable exact match mode. Terms will use an exact match "
|
|
"by default, and terms prefixed with ' will become fuzzy."
|
|
}],
|
|
[['-u', '--update'],
|
|
{'action': 'store_true',
|
|
'default': False,
|
|
'help': "Update fzf before running.",
|
|
}],
|
|
]
|
|
common_groups = ['push', 'task', 'preset']
|
|
templates = ['artifact', 'path', 'env', 'rebuild', 'chemspill-prio', 'gecko-profile']
|
|
|
|
|
|
def run(cmd, cwd=None):
|
|
is_win = platform.system() == 'Windows'
|
|
return subprocess.call(cmd, cwd=cwd, shell=True if is_win else False)
|
|
|
|
|
|
def run_fzf_install_script(fzf_path):
|
|
if platform.system() == 'Windows':
|
|
cmd = ['bash', '-c', './install --bin']
|
|
else:
|
|
cmd = ['./install', '--bin']
|
|
|
|
if run(cmd, cwd=fzf_path):
|
|
print(FZF_INSTALL_FAILED)
|
|
sys.exit(1)
|
|
|
|
|
|
def fzf_bootstrap(update=False):
|
|
"""Bootstrap fzf if necessary and return path to the executable.
|
|
|
|
The bootstrap works by cloning the fzf repository and running the included
|
|
`install` script. If update is True, we will pull the repository and re-run
|
|
the install script.
|
|
"""
|
|
fzf_bin = find_executable('fzf')
|
|
if fzf_bin and not update:
|
|
return fzf_bin
|
|
|
|
fzf_path = os.path.join(get_state_dir(), 'fzf')
|
|
if update and not os.path.isdir(fzf_path):
|
|
print("fzf installed somewhere other than {}, please update manually".format(fzf_path))
|
|
sys.exit(1)
|
|
|
|
def get_fzf():
|
|
return find_executable('fzf', os.path.join(fzf_path, 'bin'))
|
|
|
|
if update:
|
|
ret = run(['git', 'pull'], cwd=fzf_path)
|
|
if ret:
|
|
print("Update fzf failed.")
|
|
sys.exit(1)
|
|
|
|
run_fzf_install_script(fzf_path)
|
|
return get_fzf()
|
|
|
|
if os.path.isdir(fzf_path):
|
|
fzf_bin = get_fzf()
|
|
if fzf_bin:
|
|
return fzf_bin
|
|
# Fzf is cloned, but binary doesn't exist. Try running the install script
|
|
return fzf_bootstrap(update=True)
|
|
|
|
install = raw_input("Could not detect fzf, install it now? [y/n]: ")
|
|
if install.lower() != 'y':
|
|
return
|
|
|
|
if not find_executable('git'):
|
|
print("Git not found.")
|
|
print(FZF_INSTALL_FAILED)
|
|
sys.exit(1)
|
|
|
|
cmd = ['git', 'clone', '--depth', '1', 'https://github.com/junegunn/fzf.git']
|
|
if subprocess.call(cmd, cwd=os.path.dirname(fzf_path)):
|
|
print(FZF_INSTALL_FAILED)
|
|
sys.exit(1)
|
|
|
|
run_fzf_install_script(fzf_path)
|
|
|
|
print("Installed fzf to {}".format(fzf_path))
|
|
return get_fzf()
|
|
|
|
|
|
def format_header():
|
|
shortcuts = []
|
|
for action, key in sorted(fzf_header_shortcuts.iteritems()):
|
|
shortcuts.append('{t.white}{action}{t.normal}: {t.yellow}<{key}>{t.normal}'.format(
|
|
t=terminal, action=action, key=key))
|
|
return FZF_HEADER.format(shortcuts=', '.join(shortcuts), t=terminal)
|
|
|
|
|
|
def run_fzf(cmd, tasks):
|
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
|
|
out = proc.communicate('\n'.join(tasks))[0].splitlines()
|
|
|
|
selected = []
|
|
query = None
|
|
if out:
|
|
query = out[0]
|
|
selected = out[1:]
|
|
return query, selected
|
|
|
|
|
|
def filter_target_task(task):
|
|
return not any(re.search(pattern, task) for pattern in TARGET_TASK_FILTERS)
|
|
|
|
|
|
def run_fuzzy_try(update=False, query=None, templates=None, full=False, parameters=None,
|
|
save=False, preset=None, mod_presets=False, push=True, message='{msg}',
|
|
paths=None, **kwargs):
|
|
if mod_presets:
|
|
return getattr(pset, mod_presets)(section='fuzzy')
|
|
|
|
fzf = fzf_bootstrap(update)
|
|
|
|
if not fzf:
|
|
print(FZF_NOT_FOUND)
|
|
return 1
|
|
|
|
check_working_directory(push)
|
|
tg = generate_tasks(parameters, full, root=vcs.path)
|
|
all_tasks = sorted(tg.tasks.keys())
|
|
|
|
if not full:
|
|
all_tasks = filter(filter_target_task, all_tasks)
|
|
|
|
if paths:
|
|
all_tasks = filter_tasks_by_paths(all_tasks, paths)
|
|
if not all_tasks:
|
|
return 1
|
|
|
|
key_shortcuts = [k + ':' + v for k, v in fzf_shortcuts.iteritems()]
|
|
base_cmd = [
|
|
fzf, '-m',
|
|
'--bind', ','.join(key_shortcuts),
|
|
'--header', format_header(),
|
|
# Using python to split the preview string is a bit convoluted,
|
|
# but is guaranteed to be available on all platforms.
|
|
'--preview', 'python -c "print(\\"\\n\\".join(sorted([s.strip(\\"\'\\") for s in \\"{+}\\".split()])))"', # noqa
|
|
'--preview-window=right:20%',
|
|
'--print-query',
|
|
]
|
|
|
|
if kwargs['exact']:
|
|
base_cmd.append('--exact')
|
|
|
|
query = query or []
|
|
if isinstance(query, string_types):
|
|
query = [query]
|
|
|
|
if preset:
|
|
query.append(pset.load(preset, section='fuzzy')[0])
|
|
|
|
commands = []
|
|
if query:
|
|
for q in query:
|
|
commands.append(base_cmd + ['-f', q])
|
|
else:
|
|
commands.append(base_cmd)
|
|
|
|
queries = []
|
|
selected = set()
|
|
for command in commands:
|
|
query, tasks = run_fzf(command, all_tasks)
|
|
if tasks:
|
|
queries.append(query)
|
|
selected.update(tasks)
|
|
|
|
if not selected:
|
|
print("no tasks selected")
|
|
return
|
|
|
|
if save:
|
|
pset.save('fuzzy', save, queries[0])
|
|
|
|
# build commit message
|
|
msg = "Fuzzy"
|
|
args = []
|
|
if paths:
|
|
args.append("paths={}".format(':'.join(paths)))
|
|
if query:
|
|
args.extend(["query={}".format(q) for q in queries])
|
|
if args:
|
|
msg = "{} {}".format(msg, '&'.join(args))
|
|
return push_to_try('fuzzy', message.format(msg=msg), selected, templates, push=push,
|
|
closed_tree=kwargs["closed_tree"])
|