blob: d3f89bb6f86544837eeb1440c91712ecb6c45aec [file] [log] [blame]
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +00001# -*- sh -*- (Bash only)
2#
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00003# Copyright 2015 The Bazel Authors. All rights reserved.
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +00004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17#
18# Bash completion of Bazel commands.
19#
20# The template is expanded at build time using tables of commands/options
21# derived from the bazel executable built in the same client; the expansion is
22# written to bazel-complete.bash.
23#
24# Provides command-completion for:
25# - bazel prefix options (e.g. --host_jvm_args)
26# - blaze command-set (e.g. build, test)
27# - blaze command-specific options (e.g. --copts)
28# - values for enum-valued options
29# - package-names, exploring all package-path roots.
30# - targets within packages.
31
32# The package path used by the completion routines. Unfortunately
33# this isn't necessarily the same as the actual package path used by
34# Bazel, but that's ok. (It's impossible for us to reliably know what
35# the relevant package-path, so this is just a good guess. Users can
36# override it if they want.)
37#
38# Don't use it directly. Generate the final script with
39# bazel build //scripts:bash_completion instead.
40#
41: ${BAZEL_COMPLETION_PACKAGE_PATH:=%workspace%}
42
43
44# If true, Bazel query is used for autocompletion. This is more
45# accurate than the heuristic grep, especially for strangely-formatted
46# BUILD files. But it can be slower, especially if the Bazel server
47# is busy, and more brittle, if the BUILD file contains serious
48# errors. This is an experimental feature.
49: ${BAZEL_COMPLETION_USE_QUERY:=false}
50
51
52# If true, Bazel run allows autocompletion for test targets. This is convenient
53# for users who run a lot of tests/benchmarks locally with blaze run.
54: ${BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN:=false}
55
56# Some commands might interfer with the important one, so don't complete them
57: ${BAZEL_IGNORED_COMMAND_REGEX:="__none__"}
58
Martin Probstf84650c2017-11-29 02:09:42 -080059# bazel & ibazel commands
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +000060: ${BAZEL:=bazel}
Martin Probstf84650c2017-11-29 02:09:42 -080061: ${IBAZEL:=ibazel}
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +000062
63# Pattern to match for looking for a target
64# BAZEL_BUILD_MATCH_PATTERN__* give the pattern for label-*
65# when looking in the the build file.
66# BAZEL_QUERY_MATCH_PATTERN__* give the pattern for label-*
67# when using 'bazel query'.
68# _RUNTEST are special case when BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN
69# is on.
70: ${BAZEL_BUILD_MATCH_PATTERN__test:='(.*_test|test_suite)'}
71: ${BAZEL_QUERY_MATCH_PATTERN__test:='(test|test_suite)'}
72: ${BAZEL_BUILD_MATCH_PATTERN__bin:='.*_binary'}
73: ${BAZEL_QUERY_MATCH_PATTERN__bin:='(binary)'}
74: ${BAZEL_BUILD_MATCH_PATTERN_RUNTEST__bin:='(.*_(binary|test)|test_suite)'}
75: ${BAZEL_QUERY_MATCH_PATTERN_RUNTEST__bin:='(binary|test)'}
76: ${BAZEL_BUILD_MATCH_PATTERN__:='.*'}
77: ${BAZEL_QUERY_MATCH_PATTERN__:=''}
78
79# Usage: _bazel__get_rule_match_pattern <command>
80# Determine what kind of rules to match, based on command.
81_bazel__get_rule_match_pattern() {
82 local var_name pattern
83 if _bazel__is_true "$BAZEL_COMPLETION_USE_QUERY"; then
84 var_name="BAZEL_QUERY_MATCH_PATTERN"
85 else
86 var_name="BAZEL_BUILD_MATCH_PATTERN"
87 fi
88 if [[ "$1" =~ ^label-?([a-z]*)$ ]]; then
89 pattern=${BASH_REMATCH[1]:-}
90 if _bazel__is_true "$BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN"; then
91 eval "echo \"\${${var_name}_RUNTEST__${pattern}:-\$${var_name}__${pattern}}\""
92 else
93 eval "echo \"\$${var_name}__${pattern}\""
94 fi
95 fi
96}
97
98# Compute workspace directory. Search for the innermost
99# enclosing directory with a WORKSPACE file.
100_bazel__get_workspace_path() {
101 local workspace=$PWD
102 while true; do
103 if [ -f "${workspace}/WORKSPACE" ]; then
104 break
105 elif [ -z "$workspace" -o "$workspace" = "/" ]; then
106 workspace=$PWD
107 break;
108 fi
109 workspace=${workspace%/*}
110 done
111 echo $workspace
112}
113
114
115# Find the current piece of the line to complete, but only do word breaks at
116# certain characters. In particular, ignore these: "':=
117# This method also takes into account the current cursor position.
118#
119# Works with both bash 3 and 4! Bash 3 and 4 perform different word breaks when
120# computing the COMP_WORDS array. We need this here because Bazel options are of
121# the form --a=b, and labels of the form //some/label:target.
122_bazel__get_cword() {
123 local cur=${COMP_LINE:0:$COMP_POINT}
124 # This expression finds the last word break character, as defined in the
125 # COMP_WORDBREAKS variable, but without '=' or ':', which is not preceeded by
126 # a slash. Quote characters are also excluded.
127 local wordbreaks="$COMP_WORDBREAKS"
128 wordbreaks="${wordbreaks//\'/}"
129 wordbreaks="${wordbreaks//\"/}"
130 wordbreaks="${wordbreaks//:/}"
131 wordbreaks="${wordbreaks//=/}"
132 local word_start=$(expr "$cur" : '.*[^\]['"${wordbreaks}"']')
133 echo "${cur:$word_start}"
134}
135
136
137# Usage: _bazel__package_path <workspace> <displacement>
138#
139# Prints a list of package-path root directories, displaced using the
140# current displacement from the workspace. All elements have a
141# trailing slash.
142_bazel__package_path() {
143 local workspace=$1 displacement=$2 root
144 IFS=:
145 for root in ${BAZEL_COMPLETION_PACKAGE_PATH//\%workspace\%/$workspace}; do
146 unset IFS
147 echo "$root/$displacement"
148 done
149}
150
151# Usage: _bazel__options_for <command>
152#
153# Prints the set of options for a given Bazel command, e.g. "build".
154_bazel__options_for() {
155 local options
156 if [[ "${BAZEL_COMMAND_LIST}" =~ ^(.* )?$1( .*)?$ ]]; then
157 local option_name=$(echo $1 | perl -ne 'print uc' | tr "-" "_")
158 eval "echo \${BAZEL_COMMAND_${option_name}_FLAGS}" | tr " " "\n"
159 fi
160}
161# Usage: _bazel__expansion_for <command>
162#
163# Prints the completion pattern for a given Bazel command, e.g. "build".
164_bazel__expansion_for() {
165 local options
166 if [[ "${BAZEL_COMMAND_LIST}" =~ ^(.* )?$1( .*)?$ ]]; then
167 local option_name=$(echo $1 | perl -ne 'print uc' | tr "-" "_")
168 eval "echo \${BAZEL_COMMAND_${option_name}_ARGUMENT}"
169 fi
170}
171
172# Usage: _bazel__matching_targets <kind> <prefix>
173#
174# Prints target names of kind <kind> and starting with <prefix> in the BUILD
175# file given as standard input. <kind> is a basic regex (BRE) used to match the
176# bazel rule kind and <prefix> is the prefix of the target name.
177_bazel__matching_targets() {
178 local kind_pattern="$1"
179 local target_prefix="$2"
180 # The following commands do respectively:
181 # Remove BUILD file comments
182 # Replace \n by spaces to have the BUILD file in a single line
183 # Extract all rule types and target names
184 # Grep the kind pattern and the target prefix
185 # Returns the target name
186 sed 's/#.*$//' \
187 | tr "\n" " " \
188 | sed 's/\([a-zA-Z0-9_]*\) *(\([^)]* \)\{0,1\}name *= *['\''"]\([a-zA-Z0-9_/.+=,@~-]*\)['\''"][^)]*)/\
189type:\1 name:\3\
190/g' \
Googler86a40472016-09-06 18:01:11 +0000191 | "grep" -E "^type:$kind_pattern name:$target_prefix" \
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +0000192 | cut -d ':' -f 3
193}
194
195
196# Usage: _bazel__is_true <string>
197#
198# Returns true or false based on the input string. The following are
199# valid true values (the rest are false): "1", "true".
200_bazel__is_true() {
201 local str="$1"
202 [[ "$str" == "1" || "$str" == "true" ]]
203}
204
205# Usage: _bazel__expand_rules_in_package <workspace> <displacement>
206# <current> <label-type>
207#
208# Expands rules in specified packages, exploring all roots of
209# $BAZEL_COMPLETION_PACKAGE_PATH, not just $(pwd). Only rules
210# appropriate to the command are printed. Sets $COMPREPLY array to
211# result.
212#
213# If $BAZEL_COMPLETION_USE_QUERY is true, 'bazel query' is used
214# instead, with the actual Bazel package path;
215# $BAZEL_COMPLETION_PACKAGE_PATH is ignored in this case, since the
216# actual Bazel value is likely to be more accurate.
217_bazel__expand_rules_in_package() {
218 local workspace=$1 displacement=$2 current=$3 label_type=$4
219 local package_name=$(echo "$current" | cut -f1 -d:)
220 local rule_prefix=$(echo "$current" | cut -f2 -d:)
221 local root buildfile rule_pattern r result
222
223 result=
224 pattern=$(_bazel__get_rule_match_pattern "$label_type")
225 if _bazel__is_true "$BAZEL_COMPLETION_USE_QUERY"; then
226 package_name=$(echo "$package_name" | tr -d "'\"") # remove quotes
227 result=$(${BAZEL} --output_base=/tmp/${BAZEL}-completion-$USER query \
228 --keep_going --noshow_progress \
229 "kind('$pattern rule', '$package_name:*')" 2>/dev/null |
Googler86a40472016-09-06 18:01:11 +0000230 cut -f2 -d: | "grep" "^$rule_prefix")
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +0000231 else
232 for root in $(_bazel__package_path "$workspace" "$displacement"); do
Ian Cottrella7c54722017-08-24 11:20:43 +0200233 buildfile="$root/$package_name/BUILD.bazel"
234 if [ ! -f "$buildfile" ]; then
235 buildfile="$root/$package_name/BUILD"
236 fi
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +0000237 if [ -f "$buildfile" ]; then
238 result=$(_bazel__matching_targets \
239 "$pattern" "$rule_prefix" <"$buildfile")
240 break
241 fi
242 done
243 fi
244
245 index=$(echo $result | wc -w)
246 if [ -n "$result" ]; then
247 echo "$result" | tr " " "\n" | sed 's|$| |'
248 fi
249 # Include ":all" wildcard if there was no unique match. (The zero
250 # case is tricky: we need to include "all" in that case since
251 # otherwise we won't expand "a" to "all" in the absence of rules
252 # starting with "a".)
253 if [ $index -ne 1 ] && expr all : "\\($rule_prefix\\)" >/dev/null; then
254 echo "all "
255 fi
256}
257
258# Usage: _bazel__expand_package_name <workspace> <displacement> <current-word>
259# <label-type>
260#
261# Expands directories, but explores all roots of
262# BAZEL_COMPLETION_PACKAGE_PATH, not just $(pwd). When a directory is
263# a bazel package, the completion offers "pkg:" so you can expand
264# inside the package.
265# Sets $COMPREPLY array to result.
266_bazel__expand_package_name() {
267 local workspace=$1 displacement=$2 current=$3 type=${4:-} root dir index
268 for root in $(_bazel__package_path "$workspace" "$displacement"); do
269 found=0
270 for dir in $(compgen -d $root$current); do
271 [ -L "$dir" ] && continue # skip symlinks (e.g. bazel-bin)
272 [[ "$dir" =~ ^(.*/)?\.[^/]*$ ]] && continue # skip dotted dir (e.g. .git)
273 found=1
274 echo "${dir#$root}/"
Ian Cottrella7c54722017-08-24 11:20:43 +0200275 if [ -f $dir/BUILD.bazel -o -f $dir/BUILD ]; then
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +0000276 if [ "${type}" = "label-package" ]; then
277 echo "${dir#$root} "
278 else
279 echo "${dir#$root}:"
280 fi
281 fi
282 done
283 [ $found -gt 0 ] && break # Stop searching package path upon first match.
284 done
285}
286
287# Usage: _bazel__expand_target_pattern <workspace> <displacement>
288# <word> <label-syntax>
289#
290# Expands "word" to match target patterns, using the current workspace
291# and displacement from it. "command" is used to filter rules.
292# Sets $COMPREPLY array to result.
293_bazel__expand_target_pattern() {
294 local workspace=$1 displacement=$2 current=$3 label_syntax=$4
295 case "$current" in
296 //*:*) # Expand rule names within package, no displacement.
297 if [ "${label_syntax}" = "label-package" ]; then
298 compgen -S " " -W "BUILD" "$(echo current | cut -f ':' -d2)"
299 else
300 _bazel__expand_rules_in_package "$workspace" "" "$current" "$label_syntax"
301 fi
302 ;;
303 *:*) # Expand rule names within package, displaced.
304 if [ "${label_syntax}" = "label-package" ]; then
305 compgen -S " " -W "BUILD" "$(echo current | cut -f ':' -d2)"
306 else
307 _bazel__expand_rules_in_package \
308 "$workspace" "$displacement" "$current" "$label_syntax"
309 fi
310 ;;
311 //*) # Expand filenames using package-path, no displacement
312 _bazel__expand_package_name "$workspace" "" "$current" "$label_syntax"
313 ;;
314 *) # Expand filenames using package-path, displaced.
315 if [ -n "$current" ]; then
316 _bazel__expand_package_name "$workspace" "$displacement" "$current" "$label_syntax"
317 fi
318 ;;
319 esac
320}
321
322_bazel__get_command() {
323 for word in "${COMP_WORDS[@]:1:COMP_CWORD-1}"; do
Googler86a40472016-09-06 18:01:11 +0000324 if echo "$BAZEL_COMMAND_LIST" | "grep" -wsq -e "$word"; then
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +0000325 echo $word
326 break
327 fi
328 done
329}
330
331# Returns the displacement to the workspace given in $1
332_bazel__get_displacement() {
333 if [[ "$PWD" =~ ^$1/.*$ ]]; then
334 echo ${PWD##$1/}/
335 fi
336}
337
338
339# Usage: _bazel__complete_pattern <workspace> <displacement> <current>
340# <type>
341#
342# Expand a word according to a type. The currently supported types are:
343# - {a,b,c}: an enum that can take value a, b or c
344# - label: a label of any kind
345# - label-bin: a label to a runnable rule (basically to a _binary rule)
346# - label-test: a label to a test rule
347# - info-key: an info key as listed by `bazel help info-keys`
348# - command: the name of a command
349# - path: a file path
350# - combinaison of previous type using | as separator
351_bazel__complete_pattern() {
352 local workspace=$1 displacement=$2 current=$3 types=$4
353 for type in $(echo $types | tr "|" "\n"); do
354 case "$type" in
355 label*)
356 _bazel__expand_target_pattern "$workspace" "$displacement" \
357 "$current" "$type"
358 ;;
359 info-key)
360 compgen -S " " -W "${BAZEL_INFO_KEYS}" -- "$current"
361 ;;
362 "command")
363 local commands=$(echo "${BAZEL_COMMAND_LIST}" \
Googler86a40472016-09-06 18:01:11 +0000364 | tr " " "\n" | "grep" -v "^${BAZEL_IGNORED_COMMAND_REGEX}$")
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +0000365 compgen -S " " -W "${commands}" -- "$current"
366 ;;
367 path)
368 compgen -f -- "$current"
369 ;;
370 *)
371 compgen -S " " -W "$type" -- "$current"
372 ;;
373 esac
374 done
375}
376
377# Usage: _bazel__expand_options <workspace> <displacement> <current-word>
378# <options>
379#
380# Expands options, making sure that if current-word contains an equals sign,
381# it is handled appropriately.
382_bazel__expand_options() {
383 local workspace="$1" displacement="$2" cur="$3" options="$4"
384 if [[ $cur =~ = ]]; then
385 # also expands special labels
386 current=$(echo "$cur" | cut -f2 -d=)
387 _bazel__complete_pattern "$workspace" "$displacement" "$current" \
388 "$(compgen -W "$options" -- "$cur" | cut -f2 -d=)" \
389 | sort -u
390 else
391 compgen -W "$(echo "$options" | sed 's|=.*$|=|')" -- "$cur" \
392 | sed 's|\([^=]\)$|\1 |'
393 fi
394}
395
396
397_bazel__complete_stdout() {
398 local cur=$(_bazel__get_cword) word command displacement workspace
399
400 # Determine command: "" (startup-options) or one of $BAZEL_COMMAND_LIST.
401 command="$(_bazel__get_command)"
402
403 workspace="$(_bazel__get_workspace_path)"
404 displacement="$(_bazel__get_displacement ${workspace})"
405
406 case "$command" in
407 "") # Expand startup-options or commands
408 local commands=$(echo "${BAZEL_COMMAND_LIST}" \
Googler86a40472016-09-06 18:01:11 +0000409 | tr " " "\n" | "grep" -v "^${BAZEL_IGNORED_COMMAND_REGEX}$")
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +0000410 _bazel__expand_options "$workspace" "$displacement" "$cur" \
411 "${commands}\
412 ${BAZEL_STARTUP_OPTIONS}"
413 ;;
414
415 *)
416 case "$cur" in
417 -*) # Expand options:
418 _bazel__expand_options "$workspace" "$displacement" "$cur" \
419 "$(_bazel__options_for $command)"
420 ;;
421 *) # Expand target pattern
422 expansion_pattern="$(_bazel__expansion_for $command)"
423 NON_QUOTE_REGEX="^[\"']"
424 if [[ $command = query && $cur =~ $NON_QUOTE_REGEX ]]; then
425 : # Ideally we would expand query expressions---it's not
426 # that hard, conceptually---but readline is just too
427 # damn complex when it comes to quotation. Instead,
428 # for query, we just expand target patterns, unless
429 # the first char is a quote.
430 elif [ -n "$expansion_pattern" ]; then
431 _bazel__complete_pattern \
432 "$workspace" "$displacement" "$cur" "$expansion_pattern"
433 fi
434 ;;
435 esac
436 ;;
437 esac
438}
439
440_bazel__to_compreply() {
441 local replies="$1"
442 COMPREPLY=()
443 # Trick to preserve whitespaces
444 while IFS="" read -r reply; do
445 COMPREPLY+=("${reply}")
446 done < <(echo "${replies}")
447}
448
449_bazel__complete() {
450 _bazel__to_compreply "$(_bazel__complete_stdout)"
451}
452
453# Some users have aliases such as bt="bazel test" or bb="bazel build", this
454# completion function allows them to have auto-completion for these aliases.
455_bazel__complete_target_stdout() {
456 local cur=$(_bazel__get_cword) word command displacement workspace
457
458 # Determine command: "" (startup-options) or one of $BAZEL_COMMAND_LIST.
459 command="$1"
460
461 workspace="$(_bazel__get_workspace_path)"
462 displacement="$(_bazel__get_displacement ${workspace})"
463
464 _bazel__to_compreply "$(_bazel__expand_target_pattern "$workspace" "$displacement" \
Damien Martin-Guillerez60fde442016-07-22 15:47:25 +0000465 "$cur" "$(_bazel__expansion_for $command)")"
Damien Martin-Guillerez5f9c6ba2015-04-09 21:10:33 +0000466}
467
468# default completion for bazel
469complete -F _bazel__complete -o nospace "${BAZEL}"
Martin Probstf84650c2017-11-29 02:09:42 -0800470complete -F _bazel__complete -o nospace "${IBAZEL}"