zephyr/scripts/west_commands/completion/west-completion.bash
Martí Bolívar 0d5e6c13e9 boards/shields: re-work handling in cmake and west
Remove the boards and shields lists from the 'usage' target output.
That might have been readable at some point long ago in Zephyr's
history, when only a few boards were available, but right now it's
obscuring the high level targets we really want 'usage' to print.

Instead, add 'boards' and 'shields' targets which the user can run to
get those lists, and reference them from the 'usage' output. This
makes 'usage' squintable again. We use the new list_boards.py script
from the 'boards' target.

Reference the 'help' target from 'usage' as well, and drop the
recommendation that people run '--target help' from the 'west build
--help' output for the 'west build --target' option. The canonical
place to look is 'usage' now.

Use the new list_boards.py code from 'west boards' as well, which
allows us to add the board's directory as a format string key, in
addition to its name and architecture.

Keep west-completion.bash up to date. While doing that, I noticed that
a bunch of references to this file refer to a stale location, so fix
those too.

Finally, the 'usage' output is what we print for a failed board or
shield lookup, so that needs to be updated also. Handle that by
invoking boards.cmake and a new shields.cmake in CMake script mode to
print the relevant output.

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
2021-01-15 15:07:49 -05:00

847 lines
17 KiB
Bash

# Bash auto-completion for west subcommands and flags. To initialize, run
#
# source west-completion.bash
#
# To make it persistent, add it to e.g. your .bashrc.
__west_previous_extglob_setting=$(shopt -p extglob)
shopt -s extglob
# The following function is based on code from:
#
# bash_completion - programmable completion functions for bash 3.2+
#
# Copyright © 2006-2008, Ian Macdonald <ian@caliban.org>
# © 2009-2010, Bash Completion Maintainers
# <bash-completion-devel@lists.alioth.debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
# The latest version of this software can be obtained here:
#
# http://bash-completion.alioth.debian.org/
#
# RELEASE: 2.x
# This function can be used to access a tokenized list of words
# on the command line:
#
# __git_reassemble_comp_words_by_ref '=:'
# if test "${words_[cword_-1]}" = -w
# then
# ...
# fi
#
# The argument should be a collection of characters from the list of
# word completion separators (COMP_WORDBREAKS) to treat as ordinary
# characters.
#
# This is roughly equivalent to going back in time and setting
# COMP_WORDBREAKS to exclude those characters. The intent is to
# make option types like --date=<type> and <rev>:<path> easy to
# recognize by treating each shell word as a single token.
#
# It is best not to set COMP_WORDBREAKS directly because the value is
# shared with other completion scripts. By the time the completion
# function gets called, COMP_WORDS has already been populated so local
# changes to COMP_WORDBREAKS have no effect.
#
# Output: words_, cword_, cur_.
__west_reassemble_comp_words_by_ref()
{
local exclude i j first
# Which word separators to exclude?
exclude="${1//[^$COMP_WORDBREAKS]}"
cword_=$COMP_CWORD
if [ -z "$exclude" ]; then
words_=("${COMP_WORDS[@]}")
return
fi
# List of word completion separators has shrunk;
# re-assemble words to complete.
for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
# Append each nonempty word consisting of just
# word separator characters to the current word.
first=t
while
[ $i -gt 0 ] &&
[ -n "${COMP_WORDS[$i]}" ] &&
# word consists of excluded word separators
[ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
do
# Attach to the previous token,
# unless the previous token is the command name.
if [ $j -ge 2 ] && [ -n "$first" ]; then
((j--))
fi
first=
words_[$j]=${words_[j]}${COMP_WORDS[i]}
if [ $i = $COMP_CWORD ]; then
cword_=$j
fi
if (($i < ${#COMP_WORDS[@]} - 1)); then
((i++))
else
# Done.
return
fi
done
words_[$j]=${words_[j]}${COMP_WORDS[i]}
if [ $i = $COMP_CWORD ]; then
cword_=$j
fi
done
}
if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
_get_comp_words_by_ref ()
{
local exclude cur_ words_ cword_
if [ "$1" = "-n" ]; then
exclude=$2
shift 2
fi
__west_reassemble_comp_words_by_ref "$exclude"
cur_=${words_[cword_]}
while [ $# -gt 0 ]; do
case "$1" in
cur)
cur=$cur_
;;
prev)
prev=${words_[$cword_-1]}
;;
words)
words=("${words_[@]}")
;;
cword)
cword=$cword_
;;
esac
shift
done
}
fi
if ! type _tilde >/dev/null 2>&1; then
# Perform tilde (~) completion
# @return True (0) if completion needs further processing,
# False (> 0) if tilde is followed by a valid username, completions
# are put in COMPREPLY and no further processing is necessary.
_tilde()
{
local result=0
if [[ $1 == \~* && $1 != */* ]]; then
# Try generate ~username completions
COMPREPLY=( $( compgen -P '~' -u -- "${1#\~}" ) )
result=${#COMPREPLY[@]}
# 2>/dev/null for direct invocation, e.g. in the _tilde unit test
[[ $result -gt 0 ]] && compopt -o filenames 2>/dev/null
fi
return $result
}
fi
if ! type _quote_readline_by_ref >/dev/null 2>&1; then
# This function quotes the argument in a way so that readline dequoting
# results in the original argument. This is necessary for at least
# `compgen' which requires its arguments quoted/escaped:
#
# $ ls "a'b/"
# c
# $ compgen -f "a'b/" # Wrong, doesn't return output
# $ compgen -f "a\'b/" # Good
# a\'b/c
#
# See also:
# - http://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html
# - http://www.mail-archive.com/bash-completion-devel@lists.alioth.\
# debian.org/msg01944.html
# @param $1 Argument to quote
# @param $2 Name of variable to return result to
_quote_readline_by_ref()
{
if [ -z "$1" ]; then
# avoid quoting if empty
printf -v $2 %s "$1"
elif [[ $1 == \'* ]]; then
# Leave out first character
printf -v $2 %s "${1:1}"
elif [[ $1 == \~* ]]; then
# avoid escaping first ~
printf -v $2 \~%q "${1:1}"
else
printf -v $2 %q "$1"
fi
# Replace double escaping ( \\ ) by single ( \ )
# This happens always when argument is already escaped at cmdline,
# and passed to this function as e.g.: file\ with\ spaces
[[ ${!2} == *\\* ]] && printf -v $2 %s "${1//\\\\/\\}"
# If result becomes quoted like this: $'string', re-evaluate in order to
# drop the additional quoting. See also: http://www.mail-archive.com/
# bash-completion-devel@lists.alioth.debian.org/msg01942.html
[[ ${!2} == \$* ]] && eval $2=${!2}
} # _quote_readline_by_ref()
fi
# This function turns on "-o filenames" behavior dynamically. It is present
# for bash < 4 reasons. See http://bugs.debian.org/272660#64 for info about
# the bash < 4 compgen hack.
_compopt_o_filenames()
{
# We test for compopt availability first because directly invoking it on
# bash < 4 at this point may cause terminal echo to be turned off for some
# reason, see https://bugzilla.redhat.com/653669 for more info.
type compopt &>/dev/null && compopt -o filenames 2>/dev/null || \
compgen -f /non-existing-dir/ >/dev/null
}
if ! type _filedir >/dev/null 2>&1; then
# This function performs file and directory completion. It's better than
# simply using 'compgen -f', because it honours spaces in filenames.
# @param $1 If `-d', complete only on directories. Otherwise filter/pick only
# completions with `.$1' and the uppercase version of it as file
# extension.
#
_filedir()
{
local IFS=$'\n'
_tilde "$cur" || return
local -a toks
local x tmp
x=$( compgen -d -- "$cur" ) &&
while read -r tmp; do
toks+=( "$tmp" )
done <<< "$x"
if [[ "$1" != -d ]]; then
local quoted
_quote_readline_by_ref "$cur" quoted
# Munge xspec to contain uppercase version too
# http://thread.gmane.org/gmane.comp.shells.bash.bugs/15294/focus=15306
local xspec=${1:+"!*.@($1|${1^^})"}
x=$( compgen -f -X "$xspec" -- $quoted ) &&
while read -r tmp; do
toks+=( "$tmp" )
done <<< "$x"
# Try without filter if it failed to produce anything and configured to
[[ -n ${COMP_FILEDIR_FALLBACK:-} && -n "$1" && ${#toks[@]} -lt 1 ]] && \
x=$( compgen -f -- $quoted ) &&
while read -r tmp; do
toks+=( "$tmp" )
done <<< "$x"
fi
if [[ ${#toks[@]} -ne 0 ]]; then
# 2>/dev/null for direct invocation, e.g. in the _filedir unit test
_compopt_o_filenames
COMPREPLY+=( "${toks[@]}" )
fi
} # _filedir()
fi
# Misc helpers taken from Docker:
# https://github.com/docker/docker-ce/blob/master/components/cli/contrib/completion/bash/docker
# __west_pos_first_nonflag finds the position of the first word that is neither
# option nor an option's argument. If there are options that require arguments,
# you should pass a glob describing those options, e.g. "--option1|-o|--option2"
# Use this function to restrict completions to exact positions after the argument list.
__west_pos_first_nonflag()
{
local argument_flags=$1
local counter=$((${subcommand_pos:-${command_pos}} + 1))
while [ "$counter" -le "$cword" ]; do
if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then
(( counter++ ))
# eat "=" in case of --option=arg syntax
[ "${words[$counter]}" = "=" ] && (( counter++ ))
else
case "${words[$counter]}" in
-*)
;;
*)
break
;;
esac
fi
# Bash splits words at "=", retaining "=" as a word, examples:
# "--debug=false" => 3 words, "--log-opt syslog-facility=daemon" => 4 words
while [ "${words[$counter + 1]}" = "=" ] ; do
counter=$(( counter + 2))
done
(( counter++ ))
done
echo $counter
}
# __west_map_key_of_current_option returns `key` if we are currently completing the
# value of a map option (`key=value`) which matches the extglob given as an argument.
# This function is needed for key-specific completions.
__west_map_key_of_current_option()
{
local glob="$1"
local key glob_pos
if [ "$cur" = "=" ] ; then # key= case
key="$prev"
glob_pos=$((cword - 2))
elif [[ $cur == *=* ]] ; then # key=value case (OSX)
key=${cur%=*}
glob_pos=$((cword - 1))
elif [ "$prev" = "=" ] ; then
key=${words[$cword - 2]} # key=value case
glob_pos=$((cword - 3))
else
return
fi
[ "${words[$glob_pos]}" = "=" ] && ((glob_pos--)) # --option=key=value syntax
[[ ${words[$glob_pos]} == @($glob) ]] && echo "$key"
}
# __west_value_of_option returns the value of the first option matching `option_glob`.
# Valid values for `option_glob` are option names like `--log-level` and globs like
# `--log-level|-l`
# Only positions between the command and the current word are considered.
__west_value_of_option()
{
local option_extglob=$(__west_to_extglob "$1")
local counter=$((command_pos + 1))
while [ "$counter" -lt "$cword" ]; do
case ${words[$counter]} in
$option_extglob )
echo "${words[$counter + 1]}"
break
;;
esac
(( counter++ ))
done
}
# __west_to_alternatives transforms a multiline list of strings into a single line
# string with the words separated by `|`.
# This is used to prepare arguments to __west_pos_first_nonflag().
__west_to_alternatives()
{
local parts=( $1 )
local IFS='|'
echo "${parts[*]}"
}
# __west_to_extglob transforms a multiline list of options into an extglob pattern
# suitable for use in case statements.
__west_to_extglob()
{
local extglob=$( __west_to_alternatives "$1" )
echo "@($extglob)"
}
__set_comp_dirs()
{
_filedir -d
}
__set_comp_files()
{
_filedir
}
# Sets completions for $cur, from the possibilities in $1..n
__set_comp()
{
# "${*:1}" gives a single argument with arguments $1..n
COMPREPLY=($(compgen -W "${*:1}" -- "$cur"))
}
__west_x()
{
west 2>/dev/null "$@"
}
__set_comp_west_projs()
{
__set_comp "$(__west_x list --format={name} "$@")"
}
__set_comp_west_boards()
{
__set_comp "$(__west_x boards --format={name} "$@")"
}
__comp_west_west()
{
case "$prev" in
--zephyr-base|-z)
__set_comp_dirs
return
;;
# We don't know how to autocomplete any others
$(__west_to_extglob "$global_args_opts") )
return
;;
esac
case "$cur" in
-*)
__set_comp $global_bool_opts $global_args_opts
;;
*)
local counter=$( __west_pos_first_nonflag "$(__west_to_extglob "$global_args_opts")" )
if [ "$cword" -eq "$counter" ]; then
__set_comp ${cmds[*]}
fi
;;
esac
}
__comp_west_init()
{
local init_args_opts="
--manifest -m
--manifest-rev --mr
--local -l
"
case "$prev" in
--local|-l)
__set_comp_dirs
return
;;
esac
case "$cur" in
-*)
__set_comp $init_args_opts
;;
esac
}
__comp_west_update()
{
local update_bool_opts="
--keep-descendants -k
--rebase -r
"
case "$cur" in
-*)
__set_comp $update_bool_opts
;;
*)
__set_comp_west_projs
;;
esac
}
__comp_west_list()
{
local list_args_opts="
--format -f
"
case "$prev" in
# We don't know how to autocomplete those
$(__west_to_extglob "$list_args_opts") )
return
;;
esac
case "$cur" in
-*)
__set_comp $list_args_opts
;;
*)
__set_comp_west_projs
;;
esac
}
__comp_west_manifest()
{
local manifest_bool_opts="
--freeze
"
local manifest_args_opts="
--out -o
"
case "$prev" in
--out|-o)
__set_comp_files
return
;;
esac
case "$cur" in
-*)
__set_comp $manifest_bool_opts $manifest_args_opts
;;
esac
}
__comp_west_diff()
{
case "$cur" in
*)
__set_comp_west_projs
;;
esac
}
__comp_west_status()
{
case "$cur" in
*)
__set_comp_west_projs
;;
esac
}
__comp_west_forall()
{
local forall_args_opts="
-c
"
case "$prev" in
# We don't know how to autocomplete those
$(__west_to_extglob "$forall_args_opts") )
return
;;
esac
case "$cur" in
-*)
__set_comp $forall_args_opts
;;
*)
__set_comp_west_projs
;;
esac
}
__comp_west_config()
{
local config_bool_opts="
--global
--local
--system
"
case "$cur" in
-*)
__set_comp $config_bool_opts
;;
esac
}
__comp_west_help()
{
case "$cur" in
*)
local counter=$( __west_pos_first_nonflag "$(__west_to_extglob "$global_args_opts")" )
if [ "$cword" -eq "$counter" ]; then
__set_comp ${cmds[*]}
fi
;;
esac
}
# Zephyr extension commands
__comp_west_completion()
{
case "$cur" in
*)
local counter=$( __west_pos_first_nonflag "$(__west_to_extglob "$global_args_opts")" )
if [ "$cword" -eq "$counter" ]; then
__set_comp "bash"
fi
;;
esac
}
__comp_west_boards()
{
local boards_args_opts="
--format -f --name -n
--arch-root --board-root
"
case "$prev" in
--format|-f|--name|-n)
# We don't know how to autocomplete these.
return
;;
--arch-root)
__set_comp_dirs
return
;;
--board-root)
__set_comp_dirs
return
;;
esac
case "$cur" in
-*)
__set_comp $boards_args_opts
;;
esac
}
__comp_west_build()
{
local build_bool_opts="
--cmake -c
--cmake-only
-n --just-print --dry-run --recon
--force -f
"
local build_args_opts="
--board -b
--build-dir -d
--target -t
--pristine -p
--build-opt -o
"
case "$prev" in
--board|-b)
__set_comp_west_boards
return
;;
--build-dir|-d)
__set_comp_dirs
return
;;
--pristine|-p)
__set_comp "auto always never"
return
;;
# We don't know how to autocomplete those
$(__west_to_extglob "$build_args_opts") )
return
;;
esac
case "$cur" in
-*)
__set_comp $build_bool_opts $build_args_opts
;;
*)
__set_comp_dirs
;;
esac
}
__comp_west_sign()
{
local sign_bool_opts="
--force -f
--bin --no-bin
--hex --no-hex
"
local sign_args_opts="
--build-dir -d
--tool -t
--tool-path -p
-B --sbin
-H --shex
"
case "$prev" in
--build-dir|-d|--tool-path|-p)
__set_comp_dirs
return
;;
--tool|-t)
__set_comp "imgtool"
return
;;
-B|--sbin|-H|--shex)
__set_comp_files
return
;;
esac
case "$cur" in
-*)
__set_comp $sign_bool_opts $sign_args_opts
;;
esac
}
__comp_west_runner_cmd()
{
# Common arguments for runners
local runner_bool_opts="
--context -H
--skip-rebuild
"
local runner_args_opts="
--build-dir -d
--cmake-cache -c
--runner -r
--board-dir
--elf-file
--hex-file
--bin-file
--gdb
--openocd
--openocd-search
"
case "$prev" in
--build-dir|-d|--cmake-cache|-c|--board-dir|--gdb|--openocd|--openocd-search)
__set_comp_dirs
return
;;
--elf-file|--hex-file|--bin-file)
__set_comp_files
return
;;
esac
case "$cur" in
-*)
__set_comp $runner_bool_opts $runner_args_opts
;;
esac
}
__comp_west_flash()
{
__comp_west_runner_cmd
}
__comp_west_debug()
{
__comp_west_runner_cmd
}
__comp_west_debugserver()
{
__comp_west_runner_cmd
}
__comp_west_attach()
{
__comp_west_runner_cmd
}
__comp_west()
{
local previous_extglob_setting=$(shopt -p extglob)
shopt -s extglob
# Reset to default, to make sure compgen works properly
local IFS=$' \t\n'
local builtin_cmds=(
init
update
list
manifest
diff
status
forall
config
help
)
local zephyr_ext_cmds=(
completion
boards
build
sign
flash
debug
debugserver
attach
zephyr-export
)
local cmds=(${builtin_cmds[*]} ${zephyr_ext_cmds[*]})
# Global options for all commands
local global_bool_opts="
--help -h
--verbose -v
--version -V
"
local global_args_opts="
--zephyr-base -z
"
COMPREPLY=()
local cur words cword prev
_get_comp_words_by_ref -n : cur words cword prev
local command='west' command_pos=0
local counter=1
while [ "$counter" -lt "$cword" ]; do
case "${words[$counter]}" in
west)
return 0
;;
$(__west_to_extglob "$global_args_opts") )
(( counter++ ))
;;
-*)
;;
=)
(( counter++ ))
;;
*)
command="${words[$counter]}"
command_pos=$counter
break
;;
esac
(( counter++ ))
done
# Construct the function name to be called
local completions_func=__comp_west_${command//-/_}
#echo "comp_func: ${completions_func}"
declare -F $completions_func >/dev/null && $completions_func
# Restore the user's extglob setting
eval "$previous_extglob_setting"
return 0
}
eval "$__west_previous_extglob_setting"
unset __west_previous_extglob_setting
complete -F __comp_west west