forked from BrowserWorks/Waterfox
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1384593 - Add an fzf based fuzzy try selector, r=armenzg
This try selector works as follows: 1. Generate target tasks (similar to ./mach taskgraph target) 2. Pipe all tasks to fzf (a fuzzy finding binary, this will be bootstrapped if necessary) 3. Allow user to make selection 4. Save selected tasks to 'try_task_config.json'. This is a new try scheduling mechanism built into taskcluster (see bug 1380306). 5. Use `hg push-to-try` (or git-cinnabar) to push the added file to try. This will use a temporary commit, so no trace of 'try_task_config.json' should be left over after use. If you get messages like STOP! No try syntax found, you need to update version-control-tools: ./mach mercurial-setup --update MozReview-Commit-ID: 4xHwZ9fATLv --HG-- extra : rebase_source : e22ccb44d5e99e1556bf7315b096b5d6ac96c918
- Loading branch information
Showing
4 changed files
with
328 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
# 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 subprocess | ||
import sys | ||
from distutils.spawn import find_executable | ||
|
||
from mozboot.util import get_state_dir | ||
|
||
from ..tasks import generate_target | ||
from ..vcs import VCSHelper | ||
|
||
try: | ||
import blessings | ||
terminal = blessings.Terminal() | ||
except ImportError: | ||
from mozlint.formatters.stylish import NullTerminal | ||
terminal = NullTerminal() | ||
|
||
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_RUN_INSTALL_WIZARD = """ | ||
{t.bold}Running the fzf installation wizard.{t.normal} | ||
Only the fzf binary is required, if you do not wish to install the shell | ||
integrations, {t.bold}feel free to press 'n' at each of the prompts.{t.normal} | ||
""".format(t=terminal) | ||
|
||
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', | ||
} | ||
|
||
|
||
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, bin_only=False): | ||
# We could run this without installing the shell integrations on all | ||
# platforms, but those integrations are actually really useful so give user | ||
# the choice. | ||
if platform.system() == 'Windows': | ||
cmd = ['bash', '-c', './install --bin'] | ||
else: | ||
cmd = ['./install'] | ||
if bin_only: | ||
cmd.append('--bin') | ||
else: | ||
print(FZF_RUN_INSTALL_WIZARD) | ||
|
||
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()[0], '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, bin_only=True) | ||
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_fuzzy_try(update): | ||
fzf = fzf_bootstrap(update) | ||
|
||
if not fzf: | ||
print(FZF_NOT_FOUND) | ||
return | ||
|
||
vcs = VCSHelper.create() | ||
vcs.check_working_directory() | ||
|
||
all_tasks = generate_target() | ||
|
||
key_shortcuts = [k + ':' + v for k, v in fzf_shortcuts.iteritems()] | ||
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%', | ||
] | ||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE) | ||
selected = proc.communicate('\n'.join(all_tasks))[0].splitlines() | ||
|
||
if not selected: | ||
print("no tasks selected") | ||
return | ||
|
||
return vcs.push_to_try("Pushed via 'mach try fuzzy', see diff for scheduled tasks", selected) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# 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 | ||
|
||
from mozboot.util import get_state_dir | ||
from mozbuild.base import MozbuildObject | ||
from mozpack.files import FileFinder | ||
|
||
from taskgraph.generator import TaskGraphGenerator | ||
from taskgraph.parameters import load_parameters_file | ||
|
||
here = os.path.abspath(os.path.dirname(__file__)) | ||
build = MozbuildObject.from_environment(cwd=here) | ||
|
||
|
||
def invalidate(cache): | ||
if not os.path.isfile(cache): | ||
return | ||
|
||
tc_dir = os.path.join(build.topsrcdir, 'taskcluster') | ||
tmod = max(os.path.getmtime(os.path.join(tc_dir, p)) for p, _ in FileFinder(tc_dir)) | ||
cmod = os.path.getmtime(cache) | ||
|
||
if tmod > cmod: | ||
os.remove(cache) | ||
|
||
|
||
def generate_target(params='project=mozilla-central'): | ||
cache_dir = os.path.join(get_state_dir()[0], 'cache', 'taskgraph') | ||
cache = os.path.join(cache_dir, 'target_task_set') | ||
|
||
invalidate(cache) | ||
if os.path.isfile(cache): | ||
with open(cache, 'r') as fh: | ||
return fh.read().splitlines() | ||
|
||
if not os.path.isdir(cache_dir): | ||
os.makedirs(cache_dir) | ||
|
||
print("Task configuration changed, generating target tasks") | ||
params = load_parameters_file(params) | ||
params.check() | ||
|
||
root = os.path.join(build.topsrcdir, 'taskcluster', 'ci') | ||
tg = TaskGraphGenerator(root_dir=root, parameters=params).target_task_set | ||
labels = [label for label in tg.graph.visit_postorder()] | ||
|
||
with open(cache, 'w') as fh: | ||
fh.write('\n'.join(labels)) | ||
return labels |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters