blob: 4e5b5f23d914ff594f9099ea731d3ef01c0ca0e5 [file] [log] [blame]
# Copyright 2004-present Facebook. All Rights Reserved.
#
# Usage:
#
# To enable tab-completion for buck commands in bash, run the following
# command:
#
# source path/to/this/file/buck-completion.bash
#
#
# Debugging:
#
# This script will output a bunch of information about what it is doing and
# considering to $DEBUG_BUCK_COMPLETION_TTY. This enables debugging via
# the following steps. Also consider 'set -x', though this is a bit more
# painful.
#
# To enable debugging for this script, open a bash window and run:
#
# tty; cat >/dev/null
#
# Note the device output by the tty command for the next command.
#
# In a second bash window, run:
#
# export DEBUG_BUCK_COMPLETION_TTY=/dev/other-window-tty
# source path/to/this/file/buck-completion.bash
#
# replacing '/dev/other-window-tty' with the tty output from the first command.
#
# At this point, you can cd to a buck project, type a partial buck command, hit
# tab, and you should see logs in the original window of what this script is
# doing.
function _buck_completion_run() {
COMPREPLY=()
local words=( "${COMP_WORDS[@]}" )
local cword=$COMP_CWORD
local word="${words[$cword]}"
local prev="$3"
local log=_buck_completion_log
$log "==============================="
$log "word=$word"
$log "prev=$prev"
$log "words:"
for w in "${words[@]}"; do
$log " $w"
done
$log "COMP_TYPE=$COMP_TYPE"
case "$cword" in
0)
_buck_completion_internal_error "cword=0 in _buck_completion_run"
;;
1)
local commands=$(_buck_completion_echo_buck_commands)
COMPREPLY=( $(compgen -W "$commands" -- "$word") )
;;
*)
if _buck_completion_try_long_arg "--version --help"; then
return 0
fi
case "${words[1]}" in
audit) _buck_completion_try_audit "$@";;
build) _buck_completion_try_build "$@";;
cache) _buck_completion_try_cache "$@";;
clean) _buck_completion_try_clean "$@";;
install) _buck_completion_try_install "$@";;
project) _buck_completion_try_project "$@";;
quickstart) _buck_completion_try_quickstart "$@";;
run) _buck_completion_try_run "$@";;
targets) _buck_completion_try_targets "$@";;
test) _buck_completion_try_test "$@";;
uninstall) _buck_completion_try_uninstall "$@";;
esac
;;
esac
if [[ ${#COMPREPLY[@]} != 0 ]]; then
$log "=========="
$log "COMPREPLY:"
for r in "${COMPREPLY[@]}"; do
$log " $r"
done
fi
# Set return status
[[ ${#COMPREPLY[@]} > 0 ]]
}
function _buck_completion_try_build() {
_buck_completion_try_long_arg "--build-dependencies --help --no-cache --num-threads --verbose" \
|| _buck_completion_try_build_dependencies "$@" \
|| _buck_completion_try_target "$@"
}
function _buck_completion_try_audit() {
case "$cword" in
0 | 1)
_buck_completion_internal_error "cword=$cword in _buck_completion_try_audit"
return 1
;;
2)
COMPREPLY=( $(compgen -W "input classpath owner rules" -- "$word") )
;;
*)
if [[ "$word" == --* ]]; then
local extra
case "${words[2]}" in
input) extra="--dot --json";;
classpath) extra="--dot --json";;
owner) extra="--dot --full --guess-for-missing";;
rules) extra="--type";;
esac
_buck_completion_try_long_arg "--help --no-cache --verbose $extra"
else
_buck_completion_try_file "$@"
fi
;;
esac
}
function _buck_completion_try_cache() {
_buck_completion_try_long_arg "--help --no-cache --verbose"
}
function _buck_completion_try_clean() {
_buck_completion_try_long_arg "--help --no-cache --verbose --project"
}
function _buck_completion_try_install() {
_buck_completion_try_long_arg "
--activity
--build-dependencies
--help
--no-cache
--num-threads
--run
--uninstall
--verbose
--via-sd
--keep
--adb-threads
--emulator
--device
--serial" \
|| _buck_completion_try_short_arg "-all" \
|| _buck_completion_try_build_dependencies "$@" \
|| _buck_completion_try_serial "$@" \
|| _buck_completion_try_target "$@"
}
function _buck_completion_try_project() {
_buck_completion_try_long_arg "
--combined-project
--help
--ide
--no-cache
--process-annotations
--verbose
--without-tests"
}
function _buck_completion_try_quickstart() {
_buck_completion_try_long_arg "--android-sdk --dest-dir --help --no-cache --verbose" \
|| _buck_completion_try_dest_dir "$@"
}
function _buck_completion_try_run() {
_buck_completion_try_long_arg "--help --no-cache --verbose" \
|| _buck_completion_try_target "$@"
}
function _buck_completion_try_targets() {
_buck_completion_try_long_arg "
--build-dependencies
--help
--json
--no-cache
--num-threads
--referenced_file
--resolvealias
--show_output
--show_rulekey
--type
--verbose" \
|| _buck_completion_try_build_dependencies "$@" \
|| _buck_completion_try_resolve_alias "$@"
# TODO _buck_completion_try_referenced_file_set
}
function _buck_completion_try_test() {
_buck_completion_try_long_arg "
--all
--build-dependencies
--code-coverage
--debug
--dry-run
--help
--ignore-when-dependencies-fail
--jacoco
--no-cache
--no-results-cache
--num-threads
--verbose
--xml
--emulator
--device
--serial
--test-selectors
--explain-test-selectors
--exclude
--always_exclude" \
|| _buck_completion_try_build_dependencies "$@" \
|| _buck_completion_try_serial "$@" \
|| _buck_completion_try_target "$@"
}
function _buck_completion_try_uninstall() {
_buck_completion_try_long_arg "
--help
--no-cache
--verbose
--keep
--adb-threads
--emulator
--device
--serial" \
|| _buck_completion_try_short_arg "-all" \
|| _buck_completion_try_serial "$@" \
|| _buck_completion_try_target "$@"
}
function _buck_completion_try_long_arg() {
if [[ "$word" == --* ]]; then
COMPREPLY=( $(compgen -W "$@" -- "$word") )
fi
# Set return status
[[ "${#COMPREPLY[@]}" > 0 ]]
}
function _buck_completion_try_short_arg() {
if [[ "$word" == -* ]]; then
COMPREPLY=( $(compgen -W "$@" -- "$word") )
fi
# Set return status
[[ "${#COMPREPLY[@]}" > 0 ]]
}
function _buck_completion_try_build_dependencies() {
case "$prev" in
-b | --build-dependencies)
COMPREPLY=( $(compgen -W "FIRST_ORDER_ONLY WARN_ON_TRANSITIVE TRANSITIVE" -- "$word") )
;;
esac
# Set return status
[[ "${#COMPREPLY[@]}" > 0 ]]
}
function _buck_completion_try_dest_dir() {
if [[ "$prev" == "--dest-dir" ]]; then
COMPREPLY=( $(compgen -A directory -- "$word") )
fi
# Set return status
[[ "${#COMPREPLY[@]}" > 0 ]]
}
function _buck_completion_try_file() {
COMPREPLY=( $(compgen -A file -- "$word") )
# Set return status
[[ "${#COMPREPLY[@]}" > 0 ]]
}
function _buck_completion_try_serial() {
case "$prev" in
-s | --serial)
for d in "$(adb devices | tail -n+2 | awk '/./ { print $1 }')"; do
if [[ "$d" == "$word"* ]]; then
_buck_completion_add_reply "$d"
fi
done
;;
esac
# Set return status
[[ "${#COMPREPLY[@]}" > 0 ]]
}
function _buck_completion_try_resolve_alias() {
# Make sure we are in a buck root
local root=$(_buck_completion_get_root)
$log "root=$root"
if [[ -z "$root" ]]; then
return 1
fi
case "$prev" in
--resolvealias | --resolve-alias)
_buck_completion_add_target_alias "$@"
;;
esac
# Set return status
[[ "${#COMPREPLY[@]}" > 0 ]]
}
function _buck_completion_try_target() {
# Make sure we are in a buck root
local root=$(_buck_completion_get_root)
$log "root=$root"
if [[ -z "$root" ]]; then
return 1
fi
if [[ "$word" == //*:* ]]; then
_buck_completion_add_explicit_target_name_unsplit "$@"
elif [[ "$prev" == ":" || "$word" == ":" ]]; then
if [[ "$word" == ":" ]]; then
# Patch up word arrays to pretend we are looking at '' after the ':'
word=''
words[${#words[@]}]=''
cword=$(($cword + 1))
fi
_buck_completion_add_explicit_target_name "$@"
elif [[ "$word" == *:* ]]; then
_buck_completion_add_relative_target_name_unsplit "$@"
elif [[ "$word" == //* ]]; then
_buck_completion_add_explicit_target_path "$@"
else
_buck_completion_add_target_alias_or_relative_path "$@"
fi
# Set return status
[[ ${#COMPREPLY[@]} > 0 ]]
}
function _buck_completion_add_explicit_target_path() {
local dir="${root}/${word#//}"
$log "dir=$dir"
_buck_completion_add_relative_path_with_prefix "$dir" "//"
}
function _buck_completion_add_explicit_target_name() {
# TODO: verify previous words
local dir="${root}/${words[$(($cword - 2))]#//}"
local buck_file="$dir/BUCK"
local name_prefix="$word"
_buck_completion_add_target_names "$buck_file" "$name_prefix"
}
function _buck_completion_add_explicit_target_name_unsplit() {
local dir="${root}/$(echo $word | sed -E -e 's|//(.*):.*|\1|')"
local buck_file="$dir/BUCK"
local name_prefix="${word#//*:}"
_buck_completion_add_target_names "$buck_file" "$name_prefix"
}
function _buck_completion_add_relative_target_name_unsplit() {
local dir="${word%:*}"
local buck_file="$dir/BUCK"
local name_prefix="${word#*:}"
_buck_completion_add_target_names "$buck_file" "$name_prefix"
}
function _buck_completion_add_target_alias_or_relative_path() {
local dir="${word}"
$log "dir=$dir"
_buck_completion_add_target_alias "$@"
_buck_completion_add_relative_path_with_prefix "$dir" ""
}
function _buck_completion_add_target_alias() {
local prog='/^\[/ { p=0 } /^\[alias]/ { p=1 } /^ *[a-zA-Z_-]* *= *\/\// { if (p) print $1 }'
local aliases=( $(awk "$prog" < "$root/.buckconfig") )
for a in "${aliases[@]}"; do
if [[ "$a" == "$word"* ]]; then
_buck_completion_add_reply "$a"
fi
done
}
function _buck_completion_add_relative_path() {
local dir="${root}/${word}"
_buck_completion_add_relative_path_with_prefix "$dir" ""
}
function _buck_completion_add_relative_path_with_prefix() {
local dir="$1"
local prefix="$2"
# Complete directory containing BUCK file
local raw_dirs=( $(compgen -A directory -- "$dir") )
for d in "${raw_dirs[@]}"; do
local suffix
if [[ -f "$d/BUCK" ]]; then
suffix=':'
else
suffix='/'
fi
_buck_completion_add_reply "${prefix}${d#${root}/}${suffix}"
done
}
function _buck_completion_add_target_names() {
local buck_file="$1"
$log "buck_file=$buck_file"
local name_prefix="$2"
$log "name_prefix=$name_prefix"
if [[ ! -f "$buck_file" ]]; then
$log "No such file: '$buck_file'"
return 1
fi
local pattern="^ *name *= *[\'\"]\([^\'\"]*\)[\'\"] *, *$"
local target_names
if [[ -n "${BUCK_COMPLETION_HARDTARGETRESOLUTION-}" || -n "${BUCK_COMPLETION_USE_BUCK}" ]]; then
target_names=( $(_buck_completion_buck_output audit rules "$buck_file" 2>/dev/null | sed -n -e "s/$pattern/\\1/ p") )
else
target_names=( $(grep "$pattern" "$buck_file" | sed -n -e "s/$pattern/\\1/ p") )
fi
$log "target_names='${target_names[@]}'"
for name in "${target_names[@]}"; do
$log " considering target '$name'"
if [[ "$name" == "$name_prefix"* ]]; then
$log " adding '$name' because it has prefix '$name_prefix'"
_buck_completion_add_reply "${name}"
else
$log " not adding '$name' because it does not have prefix '$name_prefix'"
fi
done
}
function _buck_completion_buck_output() {
local nobuckcheck="$root/.nobuckcheck"
local preexisting=$([[ -e "$nobuckcheck" ]] && echo "$nobuckcheck")
touch "$nobuckcheck"
$(type -P buck) "$@" 2>/dev/null
if [[ -z "$preexisting" ]]; then
$log "REMOVING '$nobuckcheck' because it did not exist before."
rm "$nobuckcheck" &>/dev/null
else
$log "LEAVING '$nobuckcheck' because it existed before."
fi
}
function _buck_completion_get_root() {
local root=$PWD
while [[ -n "$root" ]]; do
if [[ -e "$root"/.buckconfig ]]; then
echo "$root" && return 0
fi
root=${root%/*}
done
}
function _buck_completion_add_reply() {
local completion="$1"
$log "Adding completion: '$completion'"
COMPREPLY[${#COMPREPLY[@]}]="$completion"
}
function _buck_completion_log() {
[[ -n "$DEBUG_BUCK_COMPLETION_TTY" ]] && echo "$@" >"$DEBUG_BUCK_COMPLETION_TTY"
}
function _buck_completion_echo_buck_commands() {
echo "audit build cache clean install project quickstart run targets test uninstall --version --help -V"
}
function _buck_completion_internal_error() {
$log ""
$log "Internal error in _buck_completion_echo_target_names:"
$log " $@"
$log "at:"
for f in "${FUNCNAME[@]}"; do
$log " $f"
done
}
complete -o nospace -F _buck_completion_run buck