Add support for bash completion of --config= names

This change supports the following command line arguments:
--bazelrc=<file>
--nosystem_rc
--noworkspace_rc
--nohome_rc

And supports finding bazelrc files in all the documented locations
as-per the docs at:
https://docs.bazel.build/versions/2.0.0/guide.html#bazelrc

It can handle command inheritance. So if you specify a "test" command
it knows to look at test, build, and common configs.

It also can deal with import and try-import commands, and can chase
those down to any nesting level.

Closes #10717.

PiperOrigin-RevId: 293661856
diff --git a/scripts/bazel-complete-template.bash b/scripts/bazel-complete-template.bash
index 7d35096..8a918a3 100644
--- a/scripts/bazel-complete-template.bash
+++ b/scripts/bazel-complete-template.bash
@@ -378,6 +378,198 @@
   fi
 }
 
+# Usage: _bazel__abspath <file>
+#
+#
+# Returns the absolute path to a file
+_bazel__abspath() {
+    echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
+ }
+
+# Usage: _bazel__rc_imports <workspace> <rc-file>
+#
+#
+# Returns the list of other RC imported (or try-imported) by a given RC file
+# Only return files we can actually find, and only return absolute paths
+_bazel__rc_imports() {
+  local workspace="$1" rc_dir rc_file="$2" import_files
+  rc_dir=$(dirname $rc_file)
+  import_files=$(cat $rc_file \
+      | sed 's/#.*//' \
+      | sed -E "/^(try-){0,1}import/!d" \
+      | sed -E "s/^(try-){0,1}import ([^ ]*).*$/\2/" \
+      | sort -u)
+
+  local confirmed_import_files=()
+  for import in $import_files; do
+    # rc imports can use %workspace% to refer to the workspace, and we need to substitute that here
+    import=${import//\%workspace\%/$workspace}
+    if [[ "${import:0:1}" != "/" ]] ; then
+      import="$rc_dir/$import"
+    fi
+    import=$(_bazel__abspath $import)
+    # Don't bother dealing with it further if we can't find it
+    if [ -f "$import" ] ; then
+      confirmed_import_files+=($import)
+    fi
+  done
+  echo "${confirmed_import_files[@]}"
+}
+
+# Usage: _bazel__rc_expand_imports <workspace> <processed-rc-files ...> __new__ <new-rc-files ...>
+#
+#
+# Function that receives a workspace and two lists. The first list is a list of RC files that have
+# already been processed, and the second list contains new RC files that need processing. Each new file will be
+# processed for "{try-}import" lines to discover more RC files that need parsing.
+# Any lines we find in "{try-}import" will be checked against known files (and not processed again if known).
+_bazel__rc_expand_imports() {
+  local workspace="$1" rc_file new_found="no" processed_files=() to_process_files=() discovered_files=()
+  # We've consumed workspace
+  shift
+  # Now grab everything else
+  local all_files=($@)
+  for rc_file in ${all_files[@]} ; do
+    if [ "$rc_file" == "__new__" ] ; then
+      new_found="yes"
+      continue
+    elif [ "$new_found" == "no" ] ; then
+      processed_files+=($rc_file)
+    else
+      to_process_files+=($rc_file)
+    fi
+  done
+
+  # For all the non-processed files, get the list of imports out of each of those files
+  for rc_file in "${to_process_files[@]}"; do
+    local potential_new_files+=($(_bazel__rc_imports "$workspace" "$rc_file"))
+    processed_files+=($rc_file)
+    for potential_new_file in ${potential_new_files[@]} ; do
+      if [[ ! " ${processed_files[@]} " =~ " ${potential_new_file} " ]] ; then
+        discovered_files+=($potential_new_file)
+      fi
+    done
+  done
+
+  # Finally, return two lists (separated by __new__) of the files that have been fully processed, and
+  # the files that need processing.
+  echo "${processed_files[@]}" "__new__" "${discovered_files[@]}"
+}
+
+# Usage: _bazel__rc_files <workspace>
+#
+#
+# Returns the list of RC files to examine, given the current command-line args.
+_bazel__rc_files() {
+  local workspace="$1" new_files=() processed_files=()
+  # Handle the workspace RC unless --noworkspace_rc says not to.
+  if [[ ! "${COMP_LINE}" =~ "--noworkspace_rc" ]]; then
+    local workspacerc="$workspace/.bazelrc"
+    if [ -f "$workspacerc" ] ; then
+      new_files+=($(_bazel__abspath $workspacerc))
+    fi
+  fi
+  # Handle the $HOME RC unless --nohome_rc says not to.
+  if [[ ! "${COMP_LINE}" =~ "--nohome_rc" ]]; then
+    local homerc="$HOME/.bazelrc"
+    if [ -f "$homerc" ] ; then
+      new_files+=($(_bazel__abspath $homerc))
+    fi
+  fi
+  # Handle the system level RC unless --nosystem_rc says not to.
+  if [[ ! "${COMP_LINE}" =~ "--nosystem_rc" ]]; then
+    local systemrc="/etc/bazel.bazelrc"
+    if [ -f "$systemrc" ] ; then
+      new_files+=($(_bazel__abspath $systemrc))
+    fi
+  fi
+  # Finally, if the user specified any on the command-line, then grab those
+  # keeping in mind that there may be several.
+  if [[ "${COMP_LINE}" =~ "--bazelrc=" ]]; then
+    # There's got to be a better way to do this, but... it gets the job done,
+    # even if there are multiple --bazelrc on the command line. The sed command
+    # SHOULD be simpler, but capturing several instances of the same pattern
+    # from the same line is tricky because of the greedy nature of .*
+    # Instead we transform it to multiple lines, and then back.
+    local cmdlnrcs=$(echo ${COMP_LINE} | sed -E "s/--bazelrc=/\n--bazelrc=/g" | sed -E "/--bazelrc/!d;s/^--bazelrc=([^ ]*).*$/\1/g" | tr "\n" " ")
+    for rc_file in $cmdlnrcs; do
+      if [ -f "$rc_file" ] ; then
+        new_files+=($(_bazel__abspath $rc_file))
+      fi
+    done
+  fi
+
+  # Each time we call _bazel__rc_expand_imports, it may find new files which then need to be expanded as well,
+  # so we loop until we've processed all new files.
+  while (( ${#new_files[@]} > 0 )) ; do
+    local all_files=($(_bazel__rc_expand_imports "$workspace" "${processed_files[@]}" "__new__" "${new_files[@]}"))
+    local new_found="no"
+    new_files=()
+    processed_files=()
+    for file in ${all_files[@]} ; do
+      if [ "$file" == "__new__" ] ; then
+        new_found="yes"
+        continue
+      elif [ "$new_found" == "no" ] ; then
+        processed_files+=($file)
+      else
+        new_files+=($file)
+      fi
+    done
+  done
+
+  echo "${processed_files[@]}"
+}
+
+# Usage: _bazel__all_configs <workspace> <command>
+#
+#
+# Gets contents of all RC files and searches them for config names
+# that could be used for expansion.
+_bazel__all_configs() {
+  local workspace="$1" command="$2" rc_files
+
+  # Start out getting a list of all RC files that we can look for configs in
+  # This respects the various command line options documented at
+  # https://docs.bazel.build/versions/2.0.0/guide.html#bazelrc
+  rc_files=$(_bazel__rc_files "$workspace")
+
+  # Commands can inherit configs from other commands, so build up command_match, which is
+  # a match list of the various commands that we can match against, given the command
+  # specified by the user
+  local build_inherit=("aquery" "clean" "coverage" "cquery" "info" "mobile-install" "print_action" "run" "test")
+  local test_inherit=("coverage")
+  local command_match="$command"
+  if [[ "${build_inherit[@]}" =~ "$command" ]]; then
+    command_match="$command_match|build"
+  fi
+  if [[ "${test_inherit[@]}" =~ "$command" ]]; then
+    command_match="$command_match|test"
+  fi
+
+  # The following commands do respectively:
+  #   Gets the contents of all relevant/allowed RC files
+  #   Remove file comments
+  #   Filter only the configs relevant to the current command
+  #   Extract the config names
+  #   Filters out redundant names and returns the results
+  cat $rc_files \
+      | sed 's/#.*//' \
+      | sed -E "/^($command_match):/!d" \
+      | sed -E "s/^($command_match):([^ ]*).*$/\2/" \
+      | sort -u
+}
+
+# Usage: _bazel__expand_config <workspace> <command> <current-word>
+#
+#
+# Expands configs, checking through the allowed rc files and parsing for configs
+# relevant to the current command
+_bazel__expand_config() {
+  local workspace="$1" command="$2" cur="$3" rc_files all_configs
+  all_configs=$(_bazel__all_configs "$workspace" "$command")
+  compgen -S " " -W "$all_configs" -- "$cur"
+}
 
 _bazel__complete_stdout() {
   local cur=$(_bazel__get_cword) word command displacement workspace
@@ -399,6 +591,9 @@
 
     *)
       case "$cur" in
+        --config=*) # Expand options:
+          _bazel__expand_config  "$workspace" "$command" "${cur#"--config="}"
+          ;;
         -*) # Expand options:
           _bazel__expand_options  "$workspace" "$displacement" "$cur" \
               "$(_bazel__options_for $command)"