Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 1 | # -*- sh -*- (Bash only) |
| 2 | # |
Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 3 | # Copyright 2015 The Bazel Authors. All rights reserved. |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 4 | # |
| 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. |
nharmata | 1694de6 | 2018-11-12 09:22:23 -0800 | [diff] [blame] | 16 | |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 17 | # The template is expanded at build time using tables of commands/options |
| 18 | # derived from the bazel executable built in the same client; the expansion is |
| 19 | # written to bazel-complete.bash. |
| 20 | # |
nharmata | 1694de6 | 2018-11-12 09:22:23 -0800 | [diff] [blame] | 21 | # Don't use this script directly. Generate the final script with |
| 22 | # bazel build //scripts:bash_completion instead. |
| 23 | |
| 24 | # This script expects a header to be prepended to it that defines the following |
| 25 | # nullary functions: |
| 26 | # |
| 27 | # _bazel_completion_use_query - Has a successful exit code if |
| 28 | # BAZEL_COMPLETION_USE_QUERY is "true". |
| 29 | # |
| 30 | # _bazel_completion_allow_tests_for_run - Has a successful exit code if |
| 31 | # BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN is "true". |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 32 | |
| 33 | # The package path used by the completion routines. Unfortunately |
| 34 | # this isn't necessarily the same as the actual package path used by |
| 35 | # Bazel, but that's ok. (It's impossible for us to reliably know what |
| 36 | # the relevant package-path, so this is just a good guess. Users can |
| 37 | # override it if they want.) |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 38 | : ${BAZEL_COMPLETION_PACKAGE_PATH:=%workspace%} |
| 39 | |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 40 | # Some commands might interfer with the important one, so don't complete them |
| 41 | : ${BAZEL_IGNORED_COMMAND_REGEX:="__none__"} |
| 42 | |
Martin Probst | f84650c | 2017-11-29 02:09:42 -0800 | [diff] [blame] | 43 | # bazel & ibazel commands |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 44 | : ${BAZEL:=bazel} |
Martin Probst | f84650c | 2017-11-29 02:09:42 -0800 | [diff] [blame] | 45 | : ${IBAZEL:=ibazel} |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 46 | |
| 47 | # Pattern to match for looking for a target |
| 48 | # BAZEL_BUILD_MATCH_PATTERN__* give the pattern for label-* |
Benjamin Peterson | dac096c | 2019-03-22 10:29:40 -0700 | [diff] [blame] | 49 | # when looking in the build file. |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 50 | # BAZEL_QUERY_MATCH_PATTERN__* give the pattern for label-* |
| 51 | # when using 'bazel query'. |
nharmata | 1694de6 | 2018-11-12 09:22:23 -0800 | [diff] [blame] | 52 | # _RUNTEST is a special case for _bazel_completion_allow_tests_for_run. |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 53 | : ${BAZEL_BUILD_MATCH_PATTERN__test:='(.*_test|test_suite)'} |
| 54 | : ${BAZEL_QUERY_MATCH_PATTERN__test:='(test|test_suite)'} |
| 55 | : ${BAZEL_BUILD_MATCH_PATTERN__bin:='.*_binary'} |
| 56 | : ${BAZEL_QUERY_MATCH_PATTERN__bin:='(binary)'} |
| 57 | : ${BAZEL_BUILD_MATCH_PATTERN_RUNTEST__bin:='(.*_(binary|test)|test_suite)'} |
| 58 | : ${BAZEL_QUERY_MATCH_PATTERN_RUNTEST__bin:='(binary|test)'} |
| 59 | : ${BAZEL_BUILD_MATCH_PATTERN__:='.*'} |
| 60 | : ${BAZEL_QUERY_MATCH_PATTERN__:=''} |
| 61 | |
| 62 | # Usage: _bazel__get_rule_match_pattern <command> |
| 63 | # Determine what kind of rules to match, based on command. |
| 64 | _bazel__get_rule_match_pattern() { |
| 65 | local var_name pattern |
nharmata | 1694de6 | 2018-11-12 09:22:23 -0800 | [diff] [blame] | 66 | if _bazel_completion_use_query; then |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 67 | var_name="BAZEL_QUERY_MATCH_PATTERN" |
| 68 | else |
| 69 | var_name="BAZEL_BUILD_MATCH_PATTERN" |
| 70 | fi |
| 71 | if [[ "$1" =~ ^label-?([a-z]*)$ ]]; then |
| 72 | pattern=${BASH_REMATCH[1]:-} |
nharmata | 1694de6 | 2018-11-12 09:22:23 -0800 | [diff] [blame] | 73 | if _bazel_completion_allow_tests_for_run; then |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 74 | eval "echo \"\${${var_name}_RUNTEST__${pattern}:-\$${var_name}__${pattern}}\"" |
| 75 | else |
| 76 | eval "echo \"\$${var_name}__${pattern}\"" |
| 77 | fi |
| 78 | fi |
| 79 | } |
| 80 | |
| 81 | # Compute workspace directory. Search for the innermost |
| 82 | # enclosing directory with a WORKSPACE file. |
| 83 | _bazel__get_workspace_path() { |
| 84 | local workspace=$PWD |
| 85 | while true; do |
| 86 | if [ -f "${workspace}/WORKSPACE" ]; then |
| 87 | break |
| 88 | elif [ -z "$workspace" -o "$workspace" = "/" ]; then |
| 89 | workspace=$PWD |
| 90 | break; |
| 91 | fi |
| 92 | workspace=${workspace%/*} |
| 93 | done |
| 94 | echo $workspace |
| 95 | } |
| 96 | |
| 97 | |
| 98 | # Find the current piece of the line to complete, but only do word breaks at |
| 99 | # certain characters. In particular, ignore these: "':= |
| 100 | # This method also takes into account the current cursor position. |
| 101 | # |
| 102 | # Works with both bash 3 and 4! Bash 3 and 4 perform different word breaks when |
| 103 | # computing the COMP_WORDS array. We need this here because Bazel options are of |
| 104 | # the form --a=b, and labels of the form //some/label:target. |
| 105 | _bazel__get_cword() { |
| 106 | local cur=${COMP_LINE:0:$COMP_POINT} |
| 107 | # This expression finds the last word break character, as defined in the |
| 108 | # COMP_WORDBREAKS variable, but without '=' or ':', which is not preceeded by |
| 109 | # a slash. Quote characters are also excluded. |
| 110 | local wordbreaks="$COMP_WORDBREAKS" |
| 111 | wordbreaks="${wordbreaks//\'/}" |
| 112 | wordbreaks="${wordbreaks//\"/}" |
| 113 | wordbreaks="${wordbreaks//:/}" |
| 114 | wordbreaks="${wordbreaks//=/}" |
| 115 | local word_start=$(expr "$cur" : '.*[^\]['"${wordbreaks}"']') |
| 116 | echo "${cur:$word_start}" |
| 117 | } |
| 118 | |
| 119 | |
| 120 | # Usage: _bazel__package_path <workspace> <displacement> |
| 121 | # |
| 122 | # Prints a list of package-path root directories, displaced using the |
| 123 | # current displacement from the workspace. All elements have a |
| 124 | # trailing slash. |
| 125 | _bazel__package_path() { |
| 126 | local workspace=$1 displacement=$2 root |
| 127 | IFS=: |
| 128 | for root in ${BAZEL_COMPLETION_PACKAGE_PATH//\%workspace\%/$workspace}; do |
| 129 | unset IFS |
| 130 | echo "$root/$displacement" |
| 131 | done |
| 132 | } |
| 133 | |
| 134 | # Usage: _bazel__options_for <command> |
| 135 | # |
| 136 | # Prints the set of options for a given Bazel command, e.g. "build". |
| 137 | _bazel__options_for() { |
| 138 | local options |
| 139 | if [[ "${BAZEL_COMMAND_LIST}" =~ ^(.* )?$1( .*)?$ ]]; then |
Philip Patsch | 27be709 | 2018-08-28 02:30:51 -0700 | [diff] [blame] | 140 | # assumes option names only use ASCII characters |
| 141 | local option_name=$(echo $1 | tr a-z A-Z | tr "-" "_") |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 142 | eval "echo \${BAZEL_COMMAND_${option_name}_FLAGS}" | tr " " "\n" |
| 143 | fi |
| 144 | } |
| 145 | # Usage: _bazel__expansion_for <command> |
| 146 | # |
| 147 | # Prints the completion pattern for a given Bazel command, e.g. "build". |
| 148 | _bazel__expansion_for() { |
| 149 | local options |
| 150 | if [[ "${BAZEL_COMMAND_LIST}" =~ ^(.* )?$1( .*)?$ ]]; then |
Philip Patsch | 27be709 | 2018-08-28 02:30:51 -0700 | [diff] [blame] | 151 | # assumes option names only use ASCII characters |
| 152 | local option_name=$(echo $1 | tr a-z A-Z | tr "-" "_") |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 153 | eval "echo \${BAZEL_COMMAND_${option_name}_ARGUMENT}" |
| 154 | fi |
| 155 | } |
| 156 | |
| 157 | # Usage: _bazel__matching_targets <kind> <prefix> |
| 158 | # |
| 159 | # Prints target names of kind <kind> and starting with <prefix> in the BUILD |
| 160 | # file given as standard input. <kind> is a basic regex (BRE) used to match the |
| 161 | # bazel rule kind and <prefix> is the prefix of the target name. |
| 162 | _bazel__matching_targets() { |
| 163 | local kind_pattern="$1" |
| 164 | local target_prefix="$2" |
| 165 | # The following commands do respectively: |
| 166 | # Remove BUILD file comments |
| 167 | # Replace \n by spaces to have the BUILD file in a single line |
| 168 | # Extract all rule types and target names |
| 169 | # Grep the kind pattern and the target prefix |
| 170 | # Returns the target name |
| 171 | sed 's/#.*$//' \ |
| 172 | | tr "\n" " " \ |
| 173 | | sed 's/\([a-zA-Z0-9_]*\) *(\([^)]* \)\{0,1\}name *= *['\''"]\([a-zA-Z0-9_/.+=,@~-]*\)['\''"][^)]*)/\ |
| 174 | type:\1 name:\3\ |
| 175 | /g' \ |
Googler | 86a4047 | 2016-09-06 18:01:11 +0000 | [diff] [blame] | 176 | | "grep" -E "^type:$kind_pattern name:$target_prefix" \ |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 177 | | cut -d ':' -f 3 |
| 178 | } |
| 179 | |
| 180 | |
| 181 | # Usage: _bazel__is_true <string> |
| 182 | # |
| 183 | # Returns true or false based on the input string. The following are |
| 184 | # valid true values (the rest are false): "1", "true". |
| 185 | _bazel__is_true() { |
| 186 | local str="$1" |
| 187 | [[ "$str" == "1" || "$str" == "true" ]] |
| 188 | } |
| 189 | |
| 190 | # Usage: _bazel__expand_rules_in_package <workspace> <displacement> |
| 191 | # <current> <label-type> |
| 192 | # |
| 193 | # Expands rules in specified packages, exploring all roots of |
| 194 | # $BAZEL_COMPLETION_PACKAGE_PATH, not just $(pwd). Only rules |
| 195 | # appropriate to the command are printed. Sets $COMPREPLY array to |
| 196 | # result. |
| 197 | # |
nharmata | 1694de6 | 2018-11-12 09:22:23 -0800 | [diff] [blame] | 198 | # If _bazel_completion_use_query has a successful exit code, 'bazel query' is |
| 199 | # used instead, with the actual Bazel package path; |
| 200 | # $BAZEL_COMPLETION_PACKAGE_PATH is ignored in this case, since the actual Bazel |
| 201 | # value is likely to be more accurate. |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 202 | _bazel__expand_rules_in_package() { |
| 203 | local workspace=$1 displacement=$2 current=$3 label_type=$4 |
| 204 | local package_name=$(echo "$current" | cut -f1 -d:) |
| 205 | local rule_prefix=$(echo "$current" | cut -f2 -d:) |
| 206 | local root buildfile rule_pattern r result |
| 207 | |
| 208 | result= |
| 209 | pattern=$(_bazel__get_rule_match_pattern "$label_type") |
nharmata | 1694de6 | 2018-11-12 09:22:23 -0800 | [diff] [blame] | 210 | if _bazel_completion_use_query; then |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 211 | package_name=$(echo "$package_name" | tr -d "'\"") # remove quotes |
| 212 | result=$(${BAZEL} --output_base=/tmp/${BAZEL}-completion-$USER query \ |
Googler | 2976576 | 2019-03-06 01:41:43 -0800 | [diff] [blame] | 213 | --keep_going --noshow_progress --output=label \ |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 214 | "kind('$pattern rule', '$package_name:*')" 2>/dev/null | |
Googler | 86a4047 | 2016-09-06 18:01:11 +0000 | [diff] [blame] | 215 | cut -f2 -d: | "grep" "^$rule_prefix") |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 216 | else |
| 217 | for root in $(_bazel__package_path "$workspace" "$displacement"); do |
Ian Cottrell | a7c5472 | 2017-08-24 11:20:43 +0200 | [diff] [blame] | 218 | buildfile="$root/$package_name/BUILD.bazel" |
| 219 | if [ ! -f "$buildfile" ]; then |
| 220 | buildfile="$root/$package_name/BUILD" |
| 221 | fi |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 222 | if [ -f "$buildfile" ]; then |
| 223 | result=$(_bazel__matching_targets \ |
| 224 | "$pattern" "$rule_prefix" <"$buildfile") |
| 225 | break |
| 226 | fi |
| 227 | done |
| 228 | fi |
| 229 | |
| 230 | index=$(echo $result | wc -w) |
| 231 | if [ -n "$result" ]; then |
| 232 | echo "$result" | tr " " "\n" | sed 's|$| |' |
| 233 | fi |
| 234 | # Include ":all" wildcard if there was no unique match. (The zero |
| 235 | # case is tricky: we need to include "all" in that case since |
| 236 | # otherwise we won't expand "a" to "all" in the absence of rules |
| 237 | # starting with "a".) |
| 238 | if [ $index -ne 1 ] && expr all : "\\($rule_prefix\\)" >/dev/null; then |
| 239 | echo "all " |
| 240 | fi |
| 241 | } |
| 242 | |
| 243 | # Usage: _bazel__expand_package_name <workspace> <displacement> <current-word> |
| 244 | # <label-type> |
| 245 | # |
| 246 | # Expands directories, but explores all roots of |
| 247 | # BAZEL_COMPLETION_PACKAGE_PATH, not just $(pwd). When a directory is |
| 248 | # a bazel package, the completion offers "pkg:" so you can expand |
| 249 | # inside the package. |
| 250 | # Sets $COMPREPLY array to result. |
| 251 | _bazel__expand_package_name() { |
| 252 | local workspace=$1 displacement=$2 current=$3 type=${4:-} root dir index |
| 253 | for root in $(_bazel__package_path "$workspace" "$displacement"); do |
| 254 | found=0 |
| 255 | for dir in $(compgen -d $root$current); do |
| 256 | [ -L "$dir" ] && continue # skip symlinks (e.g. bazel-bin) |
| 257 | [[ "$dir" =~ ^(.*/)?\.[^/]*$ ]] && continue # skip dotted dir (e.g. .git) |
| 258 | found=1 |
| 259 | echo "${dir#$root}/" |
Ian Cottrell | a7c5472 | 2017-08-24 11:20:43 +0200 | [diff] [blame] | 260 | if [ -f $dir/BUILD.bazel -o -f $dir/BUILD ]; then |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 261 | if [ "${type}" = "label-package" ]; then |
| 262 | echo "${dir#$root} " |
| 263 | else |
| 264 | echo "${dir#$root}:" |
| 265 | fi |
| 266 | fi |
| 267 | done |
| 268 | [ $found -gt 0 ] && break # Stop searching package path upon first match. |
| 269 | done |
| 270 | } |
| 271 | |
| 272 | # Usage: _bazel__expand_target_pattern <workspace> <displacement> |
| 273 | # <word> <label-syntax> |
| 274 | # |
| 275 | # Expands "word" to match target patterns, using the current workspace |
| 276 | # and displacement from it. "command" is used to filter rules. |
| 277 | # Sets $COMPREPLY array to result. |
| 278 | _bazel__expand_target_pattern() { |
| 279 | local workspace=$1 displacement=$2 current=$3 label_syntax=$4 |
| 280 | case "$current" in |
| 281 | //*:*) # Expand rule names within package, no displacement. |
| 282 | if [ "${label_syntax}" = "label-package" ]; then |
| 283 | compgen -S " " -W "BUILD" "$(echo current | cut -f ':' -d2)" |
| 284 | else |
| 285 | _bazel__expand_rules_in_package "$workspace" "" "$current" "$label_syntax" |
| 286 | fi |
| 287 | ;; |
| 288 | *:*) # Expand rule names within package, displaced. |
| 289 | if [ "${label_syntax}" = "label-package" ]; then |
| 290 | compgen -S " " -W "BUILD" "$(echo current | cut -f ':' -d2)" |
| 291 | else |
| 292 | _bazel__expand_rules_in_package \ |
| 293 | "$workspace" "$displacement" "$current" "$label_syntax" |
| 294 | fi |
| 295 | ;; |
| 296 | //*) # Expand filenames using package-path, no displacement |
| 297 | _bazel__expand_package_name "$workspace" "" "$current" "$label_syntax" |
| 298 | ;; |
| 299 | *) # Expand filenames using package-path, displaced. |
| 300 | if [ -n "$current" ]; then |
| 301 | _bazel__expand_package_name "$workspace" "$displacement" "$current" "$label_syntax" |
| 302 | fi |
| 303 | ;; |
| 304 | esac |
| 305 | } |
| 306 | |
| 307 | _bazel__get_command() { |
| 308 | for word in "${COMP_WORDS[@]:1:COMP_CWORD-1}"; do |
Googler | 86a4047 | 2016-09-06 18:01:11 +0000 | [diff] [blame] | 309 | if echo "$BAZEL_COMMAND_LIST" | "grep" -wsq -e "$word"; then |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 310 | echo $word |
| 311 | break |
| 312 | fi |
| 313 | done |
| 314 | } |
| 315 | |
| 316 | # Returns the displacement to the workspace given in $1 |
| 317 | _bazel__get_displacement() { |
| 318 | if [[ "$PWD" =~ ^$1/.*$ ]]; then |
| 319 | echo ${PWD##$1/}/ |
| 320 | fi |
| 321 | } |
| 322 | |
| 323 | |
| 324 | # Usage: _bazel__complete_pattern <workspace> <displacement> <current> |
| 325 | # <type> |
| 326 | # |
| 327 | # Expand a word according to a type. The currently supported types are: |
| 328 | # - {a,b,c}: an enum that can take value a, b or c |
| 329 | # - label: a label of any kind |
| 330 | # - label-bin: a label to a runnable rule (basically to a _binary rule) |
| 331 | # - label-test: a label to a test rule |
| 332 | # - info-key: an info key as listed by `bazel help info-keys` |
| 333 | # - command: the name of a command |
| 334 | # - path: a file path |
| 335 | # - combinaison of previous type using | as separator |
| 336 | _bazel__complete_pattern() { |
| 337 | local workspace=$1 displacement=$2 current=$3 types=$4 |
| 338 | for type in $(echo $types | tr "|" "\n"); do |
| 339 | case "$type" in |
| 340 | label*) |
| 341 | _bazel__expand_target_pattern "$workspace" "$displacement" \ |
| 342 | "$current" "$type" |
| 343 | ;; |
| 344 | info-key) |
| 345 | compgen -S " " -W "${BAZEL_INFO_KEYS}" -- "$current" |
| 346 | ;; |
| 347 | "command") |
| 348 | local commands=$(echo "${BAZEL_COMMAND_LIST}" \ |
Googler | 86a4047 | 2016-09-06 18:01:11 +0000 | [diff] [blame] | 349 | | tr " " "\n" | "grep" -v "^${BAZEL_IGNORED_COMMAND_REGEX}$") |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 350 | compgen -S " " -W "${commands}" -- "$current" |
| 351 | ;; |
| 352 | path) |
| 353 | compgen -f -- "$current" |
| 354 | ;; |
| 355 | *) |
| 356 | compgen -S " " -W "$type" -- "$current" |
| 357 | ;; |
| 358 | esac |
| 359 | done |
| 360 | } |
| 361 | |
| 362 | # Usage: _bazel__expand_options <workspace> <displacement> <current-word> |
| 363 | # <options> |
| 364 | # |
| 365 | # Expands options, making sure that if current-word contains an equals sign, |
| 366 | # it is handled appropriately. |
| 367 | _bazel__expand_options() { |
| 368 | local workspace="$1" displacement="$2" cur="$3" options="$4" |
| 369 | if [[ $cur =~ = ]]; then |
| 370 | # also expands special labels |
| 371 | current=$(echo "$cur" | cut -f2 -d=) |
| 372 | _bazel__complete_pattern "$workspace" "$displacement" "$current" \ |
| 373 | "$(compgen -W "$options" -- "$cur" | cut -f2 -d=)" \ |
| 374 | | sort -u |
| 375 | else |
| 376 | compgen -W "$(echo "$options" | sed 's|=.*$|=|')" -- "$cur" \ |
| 377 | | sed 's|\([^=]\)$|\1 |' |
| 378 | fi |
| 379 | } |
| 380 | |
Eric Klein | 2b708fb | 2020-02-06 13:23:25 -0800 | [diff] [blame] | 381 | # Usage: _bazel__abspath <file> |
| 382 | # |
| 383 | # |
| 384 | # Returns the absolute path to a file |
| 385 | _bazel__abspath() { |
| 386 | echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")" |
| 387 | } |
| 388 | |
| 389 | # Usage: _bazel__rc_imports <workspace> <rc-file> |
| 390 | # |
| 391 | # |
| 392 | # Returns the list of other RC imported (or try-imported) by a given RC file |
| 393 | # Only return files we can actually find, and only return absolute paths |
| 394 | _bazel__rc_imports() { |
| 395 | local workspace="$1" rc_dir rc_file="$2" import_files |
| 396 | rc_dir=$(dirname $rc_file) |
| 397 | import_files=$(cat $rc_file \ |
| 398 | | sed 's/#.*//' \ |
| 399 | | sed -E "/^(try-){0,1}import/!d" \ |
| 400 | | sed -E "s/^(try-){0,1}import ([^ ]*).*$/\2/" \ |
| 401 | | sort -u) |
| 402 | |
| 403 | local confirmed_import_files=() |
| 404 | for import in $import_files; do |
| 405 | # rc imports can use %workspace% to refer to the workspace, and we need to substitute that here |
| 406 | import=${import//\%workspace\%/$workspace} |
| 407 | if [[ "${import:0:1}" != "/" ]] ; then |
| 408 | import="$rc_dir/$import" |
| 409 | fi |
| 410 | import=$(_bazel__abspath $import) |
| 411 | # Don't bother dealing with it further if we can't find it |
| 412 | if [ -f "$import" ] ; then |
| 413 | confirmed_import_files+=($import) |
| 414 | fi |
| 415 | done |
| 416 | echo "${confirmed_import_files[@]}" |
| 417 | } |
| 418 | |
| 419 | # Usage: _bazel__rc_expand_imports <workspace> <processed-rc-files ...> __new__ <new-rc-files ...> |
| 420 | # |
| 421 | # |
| 422 | # Function that receives a workspace and two lists. The first list is a list of RC files that have |
| 423 | # already been processed, and the second list contains new RC files that need processing. Each new file will be |
| 424 | # processed for "{try-}import" lines to discover more RC files that need parsing. |
| 425 | # Any lines we find in "{try-}import" will be checked against known files (and not processed again if known). |
| 426 | _bazel__rc_expand_imports() { |
| 427 | local workspace="$1" rc_file new_found="no" processed_files=() to_process_files=() discovered_files=() |
| 428 | # We've consumed workspace |
| 429 | shift |
| 430 | # Now grab everything else |
| 431 | local all_files=($@) |
| 432 | for rc_file in ${all_files[@]} ; do |
| 433 | if [ "$rc_file" == "__new__" ] ; then |
| 434 | new_found="yes" |
| 435 | continue |
| 436 | elif [ "$new_found" == "no" ] ; then |
| 437 | processed_files+=($rc_file) |
| 438 | else |
| 439 | to_process_files+=($rc_file) |
| 440 | fi |
| 441 | done |
| 442 | |
| 443 | # For all the non-processed files, get the list of imports out of each of those files |
| 444 | for rc_file in "${to_process_files[@]}"; do |
| 445 | local potential_new_files+=($(_bazel__rc_imports "$workspace" "$rc_file")) |
| 446 | processed_files+=($rc_file) |
| 447 | for potential_new_file in ${potential_new_files[@]} ; do |
| 448 | if [[ ! " ${processed_files[@]} " =~ " ${potential_new_file} " ]] ; then |
| 449 | discovered_files+=($potential_new_file) |
| 450 | fi |
| 451 | done |
| 452 | done |
| 453 | |
| 454 | # Finally, return two lists (separated by __new__) of the files that have been fully processed, and |
| 455 | # the files that need processing. |
| 456 | echo "${processed_files[@]}" "__new__" "${discovered_files[@]}" |
| 457 | } |
| 458 | |
| 459 | # Usage: _bazel__rc_files <workspace> |
| 460 | # |
| 461 | # |
| 462 | # Returns the list of RC files to examine, given the current command-line args. |
| 463 | _bazel__rc_files() { |
| 464 | local workspace="$1" new_files=() processed_files=() |
| 465 | # Handle the workspace RC unless --noworkspace_rc says not to. |
| 466 | if [[ ! "${COMP_LINE}" =~ "--noworkspace_rc" ]]; then |
| 467 | local workspacerc="$workspace/.bazelrc" |
| 468 | if [ -f "$workspacerc" ] ; then |
| 469 | new_files+=($(_bazel__abspath $workspacerc)) |
| 470 | fi |
| 471 | fi |
| 472 | # Handle the $HOME RC unless --nohome_rc says not to. |
| 473 | if [[ ! "${COMP_LINE}" =~ "--nohome_rc" ]]; then |
| 474 | local homerc="$HOME/.bazelrc" |
| 475 | if [ -f "$homerc" ] ; then |
| 476 | new_files+=($(_bazel__abspath $homerc)) |
| 477 | fi |
| 478 | fi |
| 479 | # Handle the system level RC unless --nosystem_rc says not to. |
| 480 | if [[ ! "${COMP_LINE}" =~ "--nosystem_rc" ]]; then |
| 481 | local systemrc="/etc/bazel.bazelrc" |
| 482 | if [ -f "$systemrc" ] ; then |
| 483 | new_files+=($(_bazel__abspath $systemrc)) |
| 484 | fi |
| 485 | fi |
| 486 | # Finally, if the user specified any on the command-line, then grab those |
| 487 | # keeping in mind that there may be several. |
| 488 | if [[ "${COMP_LINE}" =~ "--bazelrc=" ]]; then |
| 489 | # There's got to be a better way to do this, but... it gets the job done, |
| 490 | # even if there are multiple --bazelrc on the command line. The sed command |
| 491 | # SHOULD be simpler, but capturing several instances of the same pattern |
| 492 | # from the same line is tricky because of the greedy nature of .* |
| 493 | # Instead we transform it to multiple lines, and then back. |
| 494 | local cmdlnrcs=$(echo ${COMP_LINE} | sed -E "s/--bazelrc=/\n--bazelrc=/g" | sed -E "/--bazelrc/!d;s/^--bazelrc=([^ ]*).*$/\1/g" | tr "\n" " ") |
| 495 | for rc_file in $cmdlnrcs; do |
| 496 | if [ -f "$rc_file" ] ; then |
| 497 | new_files+=($(_bazel__abspath $rc_file)) |
| 498 | fi |
| 499 | done |
| 500 | fi |
| 501 | |
| 502 | # Each time we call _bazel__rc_expand_imports, it may find new files which then need to be expanded as well, |
| 503 | # so we loop until we've processed all new files. |
| 504 | while (( ${#new_files[@]} > 0 )) ; do |
| 505 | local all_files=($(_bazel__rc_expand_imports "$workspace" "${processed_files[@]}" "__new__" "${new_files[@]}")) |
| 506 | local new_found="no" |
| 507 | new_files=() |
| 508 | processed_files=() |
| 509 | for file in ${all_files[@]} ; do |
| 510 | if [ "$file" == "__new__" ] ; then |
| 511 | new_found="yes" |
| 512 | continue |
| 513 | elif [ "$new_found" == "no" ] ; then |
| 514 | processed_files+=($file) |
| 515 | else |
| 516 | new_files+=($file) |
| 517 | fi |
| 518 | done |
| 519 | done |
| 520 | |
| 521 | echo "${processed_files[@]}" |
| 522 | } |
| 523 | |
| 524 | # Usage: _bazel__all_configs <workspace> <command> |
| 525 | # |
| 526 | # |
| 527 | # Gets contents of all RC files and searches them for config names |
| 528 | # that could be used for expansion. |
| 529 | _bazel__all_configs() { |
| 530 | local workspace="$1" command="$2" rc_files |
| 531 | |
| 532 | # Start out getting a list of all RC files that we can look for configs in |
| 533 | # This respects the various command line options documented at |
| 534 | # https://docs.bazel.build/versions/2.0.0/guide.html#bazelrc |
| 535 | rc_files=$(_bazel__rc_files "$workspace") |
| 536 | |
| 537 | # Commands can inherit configs from other commands, so build up command_match, which is |
| 538 | # a match list of the various commands that we can match against, given the command |
| 539 | # specified by the user |
| 540 | local build_inherit=("aquery" "clean" "coverage" "cquery" "info" "mobile-install" "print_action" "run" "test") |
| 541 | local test_inherit=("coverage") |
| 542 | local command_match="$command" |
| 543 | if [[ "${build_inherit[@]}" =~ "$command" ]]; then |
| 544 | command_match="$command_match|build" |
| 545 | fi |
| 546 | if [[ "${test_inherit[@]}" =~ "$command" ]]; then |
| 547 | command_match="$command_match|test" |
| 548 | fi |
| 549 | |
| 550 | # The following commands do respectively: |
| 551 | # Gets the contents of all relevant/allowed RC files |
| 552 | # Remove file comments |
| 553 | # Filter only the configs relevant to the current command |
| 554 | # Extract the config names |
| 555 | # Filters out redundant names and returns the results |
| 556 | cat $rc_files \ |
| 557 | | sed 's/#.*//' \ |
| 558 | | sed -E "/^($command_match):/!d" \ |
| 559 | | sed -E "s/^($command_match):([^ ]*).*$/\2/" \ |
| 560 | | sort -u |
| 561 | } |
| 562 | |
| 563 | # Usage: _bazel__expand_config <workspace> <command> <current-word> |
| 564 | # |
| 565 | # |
| 566 | # Expands configs, checking through the allowed rc files and parsing for configs |
| 567 | # relevant to the current command |
| 568 | _bazel__expand_config() { |
| 569 | local workspace="$1" command="$2" cur="$3" rc_files all_configs |
| 570 | all_configs=$(_bazel__all_configs "$workspace" "$command") |
| 571 | compgen -S " " -W "$all_configs" -- "$cur" |
| 572 | } |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 573 | |
| 574 | _bazel__complete_stdout() { |
| 575 | local cur=$(_bazel__get_cword) word command displacement workspace |
| 576 | |
| 577 | # Determine command: "" (startup-options) or one of $BAZEL_COMMAND_LIST. |
| 578 | command="$(_bazel__get_command)" |
| 579 | |
| 580 | workspace="$(_bazel__get_workspace_path)" |
| 581 | displacement="$(_bazel__get_displacement ${workspace})" |
| 582 | |
| 583 | case "$command" in |
| 584 | "") # Expand startup-options or commands |
| 585 | local commands=$(echo "${BAZEL_COMMAND_LIST}" \ |
Googler | 86a4047 | 2016-09-06 18:01:11 +0000 | [diff] [blame] | 586 | | tr " " "\n" | "grep" -v "^${BAZEL_IGNORED_COMMAND_REGEX}$") |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 587 | _bazel__expand_options "$workspace" "$displacement" "$cur" \ |
| 588 | "${commands}\ |
| 589 | ${BAZEL_STARTUP_OPTIONS}" |
| 590 | ;; |
| 591 | |
| 592 | *) |
| 593 | case "$cur" in |
Eric Klein | 2b708fb | 2020-02-06 13:23:25 -0800 | [diff] [blame] | 594 | --config=*) # Expand options: |
| 595 | _bazel__expand_config "$workspace" "$command" "${cur#"--config="}" |
| 596 | ;; |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 597 | -*) # Expand options: |
| 598 | _bazel__expand_options "$workspace" "$displacement" "$cur" \ |
| 599 | "$(_bazel__options_for $command)" |
| 600 | ;; |
| 601 | *) # Expand target pattern |
| 602 | expansion_pattern="$(_bazel__expansion_for $command)" |
| 603 | NON_QUOTE_REGEX="^[\"']" |
| 604 | if [[ $command = query && $cur =~ $NON_QUOTE_REGEX ]]; then |
| 605 | : # Ideally we would expand query expressions---it's not |
| 606 | # that hard, conceptually---but readline is just too |
| 607 | # damn complex when it comes to quotation. Instead, |
| 608 | # for query, we just expand target patterns, unless |
| 609 | # the first char is a quote. |
| 610 | elif [ -n "$expansion_pattern" ]; then |
| 611 | _bazel__complete_pattern \ |
| 612 | "$workspace" "$displacement" "$cur" "$expansion_pattern" |
| 613 | fi |
| 614 | ;; |
| 615 | esac |
| 616 | ;; |
| 617 | esac |
| 618 | } |
| 619 | |
| 620 | _bazel__to_compreply() { |
| 621 | local replies="$1" |
| 622 | COMPREPLY=() |
| 623 | # Trick to preserve whitespaces |
| 624 | while IFS="" read -r reply; do |
| 625 | COMPREPLY+=("${reply}") |
| 626 | done < <(echo "${replies}") |
Zach Pomerantz | ea6d12f | 2019-08-20 01:30:23 -0700 | [diff] [blame] | 627 | # Null may be set despite there being no completions |
Googler | 532778d | 2019-08-26 06:18:22 -0700 | [diff] [blame] | 628 | if [ ${#COMPREPLY[@]} -eq 1 ] && [ -z ${COMPREPLY[0]} ]; then |
Zach Pomerantz | ea6d12f | 2019-08-20 01:30:23 -0700 | [diff] [blame] | 629 | COMPREPLY=() |
| 630 | fi |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 631 | } |
| 632 | |
| 633 | _bazel__complete() { |
| 634 | _bazel__to_compreply "$(_bazel__complete_stdout)" |
| 635 | } |
| 636 | |
| 637 | # Some users have aliases such as bt="bazel test" or bb="bazel build", this |
| 638 | # completion function allows them to have auto-completion for these aliases. |
| 639 | _bazel__complete_target_stdout() { |
| 640 | local cur=$(_bazel__get_cword) word command displacement workspace |
| 641 | |
| 642 | # Determine command: "" (startup-options) or one of $BAZEL_COMMAND_LIST. |
| 643 | command="$1" |
| 644 | |
| 645 | workspace="$(_bazel__get_workspace_path)" |
| 646 | displacement="$(_bazel__get_displacement ${workspace})" |
| 647 | |
| 648 | _bazel__to_compreply "$(_bazel__expand_target_pattern "$workspace" "$displacement" \ |
Damien Martin-Guillerez | 60fde44 | 2016-07-22 15:47:25 +0000 | [diff] [blame] | 649 | "$cur" "$(_bazel__expansion_for $command)")" |
Damien Martin-Guillerez | 5f9c6ba | 2015-04-09 21:10:33 +0000 | [diff] [blame] | 650 | } |
| 651 | |
| 652 | # default completion for bazel |
| 653 | complete -F _bazel__complete -o nospace "${BAZEL}" |
Martin Probst | f84650c | 2017-11-29 02:09:42 -0800 | [diff] [blame] | 654 | complete -F _bazel__complete -o nospace "${IBAZEL}" |