diff options
-rw-r--r-- | scripts/BUILD | 28 | ||||
-rwxr-xr-x | scripts/bash_completion_test.sh | 730 | ||||
-rw-r--r-- | scripts/bazel-complete-template.bash | 469 | ||||
-rwxr-xr-x | scripts/testenv.sh | 33 | ||||
-rw-r--r-- | src/BUILD | 5 |
5 files changed, 1264 insertions, 1 deletions
diff --git a/scripts/BUILD b/scripts/BUILD new file mode 100644 index 0000000000..e3892a675f --- /dev/null +++ b/scripts/BUILD @@ -0,0 +1,28 @@ +# Scripts for IDE / Environment support for Bazel +package(default_visibility = ["//visibility:private"]) + +# The master copy of the completion logic are bazel-complete-template.bash +# and bazel-complete-header.bash. +# This is where you should make edits. +genrule( + name = "bash_completion", + srcs = ["bazel-complete-template.bash"], + outs = ["bazel-complete.bash"], + cmd = "set +x; cat $(SRCS) > $@\n" + + "touch WORKSPACE err.log\n" + + "trap \"rm WORKSPACE err.log\" EXIT\n" + + "$(location //src:bazel) help completion 2>err.log >>$@ || { cat err.log; exit 1; }", + output_to_bindir = 1, + tools = ["//src:bazel"], +) + +sh_test( + name = "bash_completion_test", + size = "small", + srcs = ["bash_completion_test.sh"], + data = [ + "bazel-complete.bash", + "testenv.sh", + "//src/test/shell:bashunit", + ], +) diff --git a/scripts/bash_completion_test.sh b/scripts/bash_completion_test.sh new file mode 100755 index 0000000000..3d82dbefcc --- /dev/null +++ b/scripts/bash_completion_test.sh @@ -0,0 +1,730 @@ +#!/bin/bash +# +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# bash_completion_test.sh: tests of bash command completion. + +: ${DIR:=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)} +source ${DIR}/testenv.sh || { echo "testenv.sh not found!" >&2; exit 1; } + +# List of command to test completion for +: ${COMMAND_ALIASES:=bazel} + +# Completion script +: ${COMPLETION:="$TEST_SRCDIR/scripts/bazel-complete.bash"} + +# Set this to test completion with package path (if enabled) +: ${PACKAGE_PATH_PREFIX:=} + +#### UTILITIES ######################################################### + +# Usage: array_join join_on array +# +# Joins all arguments using the first argument as separator +function array_join { + local joiner="$1" + shift + echo -n "$1" + shift + for i in "$@"; do + echo -n "${joiner}${i}" + done +} + +# Usage: expand <terminal-input> <flags> <stderr-file> +# +# Prints the string resulting from command expansion after the +# specified terminal input is typed at the shell. The argument +# is evaluated using 'echo -e', so \t can be used to invoke +# command expansion. STDERR output from the call to bash is +# sent to stderr-file so it can be inspected, if desired. +# +# This approach is rather gnarly, but guarantees good test fidelity, +# unlike "unit test" approaches based on invoking the completion function +# directly with COMP_WORDS etc defined. +expand() { + local input="$1" flags="$2" stderr_file="$3" + { + # The flags for blaze autocomplete script. + echo "$flags" + # This script is already sourced in a normal bash shell, but we need it + # for the tests, too. + echo "source $COMPLETION" + # Tricky! We turn "bazel" into a self-quoting command + # that echoes its argument string exactly, spaces and all. + # We assume no single-quotes in the input, though. + # + # Alias expansion still inserts an extra space after 'blaze', + # though, hence the following sed. Not sure why. + for i in ${COMMAND_ALIASES[@]}; do + echo "alias $i=\"echo $i'\"" + done + echo -en "$input'" + } | bash --norc -i 2>"$stderr_file" | + sed -e 's/^\('"$(array_join "\|" ${COMMAND_ALIASES[@]})"'\) /\1 /' +} + +# Usage: assert_expansion <prefix> <expected-expansion> <optional-flags> +# +# For multiple flags separate with semicolon. +# e.g. assert_expansion 'foo' 'foo_expand' 'flag1=bar;flag2=baz' +assert_expansion() { + local prefix=$1 expected=$2 flags=${3:-} + for i in ${COMMAND_ALIASES[@]}; do + local nprefix="$i $prefix" + local nexpected="$i $expected" + assert_equals "$nexpected" "$(expand "$nprefix\t" "$flags" "/dev/null")" + done +} + + +# Usage: assert_expansion_error_not_contains <prefix> <unexpected-error> +# <optional-flags> +# +# For multiple flags separate with semicolon. +# +# Asserts that tab-completing after typing the prefix will not result +# in STDERR receiving a string containing regex unexpected-error. +assert_expansion_error_not_contains() { + local prefix=$1 not_expected=$2 flags=${3:-} + local temp_file=$(mktemp -t tmp.stderr.XXXXXX) + for i in ${COMMAND_ALIASES[@]}; do + local nprefix="$i " + expand "$nprefix\t" "$flags" "$temp_file" > /dev/null + assert_not_contains "$not_expected" "$temp_file" + done +} + +#### FIXTURES ########################################################## + +make_empty_packages() { + touch video/streamer2/testing/BUILD + touch ${PACKAGE_PATH_PREFIX:-}video/streamer2/stuff/BUILD + touch video/streamer2/names/BUILD +} + +make_packages() { + mkdir -p video/streamer2/testing || fail "mkdir failed" + cat >video/streamer2/BUILD <<EOF +cc_library(name='task_lib', ...) +cc_library(name='token_bucket', ...) +cc_library(name='with_special+_,=-.@~chars', ...) +#cc_library(name='comment_build_target_1old', ...) +#cc_library(name='comment_build_target_2old', ...) +cc_library(name='comment_build_target_2new', ...) +#cc_test(name='token_bucket_t_1old', ...) +#cc_test(name='token_bucket_t_2old', ...) +cc_test(name='token_bucket_test', ...) +cc_binary(name='token_bucket_binary', ...) +java_binary ( name = 'JavaBinary', ...) +java_binary ( + name = 'AnotherJavaBinary' + ... +) +cc_binary(other='thing', name='pybin', ...) +genrule(name='checks/thingy', ...) +#cc_binary(name='comment_run_target_1old', ...) +#cc_binary(name='comment_run_target_2old', ...) +cc_binary(name='comment_run_target_2new', ...) +EOF + + mkdir -p ${PACKAGE_PATH_PREFIX:-}video/streamer2/stuff || fail "mkdir failed" + cat >${PACKAGE_PATH_PREFIX:-}video/streamer2/stuff/BUILD <<EOF +cc_library(name='stuff', ...) +EOF + + mkdir -p video/streamer2/names || fail "mkdir failed" + cat >video/streamer2/names/BUILD <<EOF +genrule( + name = 'foo', + cmd = ('name=foo'), +) +EOF + + mkdir -p dash || fail "mkdir failed" + cat >dash/BUILD <<EOF +cc_library( + name = "mia-bid-multiplier-mixer-module", +) +EOF + + mkdir -p video/notapackage +} + +#### UNIT TESTS ######################################################## + +source ${COMPLETION} + +assert_expansion_function() { + local ws=${PWD} + local function="$1" displacement="$2" type="$3" expected="$4" current="$5" + assert_equals "$(echo -e "${expected}")" \ + "$(eval "_bazel__${function} \"${ws}\" \"${displacement}\" \"${current}\" \"${type}\"")" +} + +test_expand_rules_in_package() { + make_packages + + assert_expansion_function "expand_rules_in_package" "" label \ + "stuff " "//video/streamer2/stuff:" + assert_expansion_function "expand_rules_in_package" "" label \ + 'task_lib ' 'video/streamer2:ta' + assert_expansion_function "expand_rules_in_package" "" label \ + 'with_special+_,=-.@~chars ' 'video/streamer2:with_s' + + # From a different directory + assert_expansion_function "expand_rules_in_package" "video/" label \ + 'task_lib ' 'streamer2:ta' + assert_expansion_function "expand_rules_in_package" "video/" label \ + '' 'video/streamer2:ta' + assert_expansion_function "expand_rules_in_package" "video/" label \ + 'with_special+_,=-.@~chars ' 'streamer2:with_s' + + # label should match test and non-test rules + assert_expansion_function "expand_rules_in_package" "" label \ + 'token_bucket_test \ntoken_bucket_binary ' \ + 'video/streamer2:token_bucket_' + assert_expansion_function "expand_rules_in_package" "" label \ + 'stuff ' 'video/streamer2/stuff:s' + # Test that label does not match commented-out rules. + assert_expansion_function "expand_rules_in_package" "" label \ + '' 'video/streamer2:comment_build_target_1o' + assert_expansion_function "expand_rules_in_package" "" label \ + 'comment_build_target_2new ' 'video/streamer2:comment_build_target_2' + + # Test that 'label-test' expands only test rules. + assert_expansion_function "expand_rules_in_package" "" label-test \ + 'token_bucket_test ' 'video/streamer2:to' + + # Test that 'label-test' does not match commented-out rules. + assert_expansion_function "expand_rules_in_package" "" label-test \ + '' 'video/streamer2:token_bucket_t_1o' + assert_expansion_function "expand_rules_in_package" "" label-test \ + 'token_bucket_test ' 'video/streamer2:token_bucket_t' + + # Test that :all wildcard is expanded when there is more than one + # match. + # + # One match => no :all. + assert_expansion_function "expand_rules_in_package" "" label-test \ + 'token_bucket_test ' 'video/streamer2:' + # Multiple matches => :all. + assert_expansion_function "expand_rules_in_package" "" label-test \ + 'all ' 'video/streamer2:a' + + # Test that label-bin expands only non-test binary rules. + assert_expansion_function "expand_rules_in_package" "" label-bin \ + 'token_bucket_binary ' 'video/streamer2:to' + + # Test that label-bin expands for binary and test rules, but not library + # with BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN set. + BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN=true \ + assert_expansion_function "expand_rules_in_package" "" label-bin \ + 'token_bucket_test \ntoken_bucket_binary ' 'video/streamer2:to' + + # Test the label-bin expands for test rules, with + # BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN set. + BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN=1 \ + assert_expansion_function "expand_rules_in_package" "" label-bin \ + 'token_bucket_test ' 'video/streamer2:token_bucket_t' + + # Test that 'label-bin' expands only non-test binary rules when the + # BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN is false. + BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN=false \ + assert_expansion_function "expand_rules_in_package" "" label-bin \ + 'token_bucket_binary ' 'video/streamer2:to' + + # Test that 'label-bin' does not match commented-out rules. + assert_expansion_function "expand_rules_in_package" "" label-bin \ + '' 'video/streamer2:comment_run_target_1o' + assert_expansion_function "expand_rules_in_package" "" label-bin \ + 'comment_run_target_2new ' 'video/streamer2:comment_run_target_2' + + # Test that 'label-bin' expands binaries with spaces in the build rules + assert_expansion_function "expand_rules_in_package" "" label-bin \ + 'JavaBinary ' 'video/streamer2:J' + + # Test that 'label-bin' expands targets when the name attribute is not first + assert_expansion_function "expand_rules_in_package" "" label-bin \ + 'pybin ' 'video/streamer2:py' + + # Test that 'label-bin' expands binaries with newlines in the build rules + assert_expansion_function "expand_rules_in_package" "" label-bin \ + 'AnotherJavaBinary ' 'video/streamer2:A' + + # Test that the expansion of rules with 'name=...' strings isn't messed up. + assert_expansion_function "expand_rules_in_package" "" label \ + 'foo ' 'video/streamer2/names:' +} + +test_expand_package_name() { + make_packages + assert_expansion_function "expand_package_name" "" "" \ + "//video/streamer2/stuff/\n//video/streamer2/stuff:" \ + "//video/streamer2/stu" + assert_expansion_function "expand_package_name" "" "" \ + "//video/notapackage/" \ + "//video/nota" + + assert_expansion_function "expand_package_name" "" "" \ + "video/streamer2/stuff/\nvideo/streamer2/stuff:" \ + "video/streamer2/stu" + assert_expansion_function "expand_package_name" "" "" \ + "video/notapackage/" \ + "video/nota" + + # From another directory + assert_expansion_function "expand_package_name" "video/" "" \ + "" \ + "video/streamer2/stu" + assert_expansion_function "expand_package_name" "video/" "" \ + "" \ + "video/nota" + assert_expansion_function "expand_package_name" "video/" "" \ + "streamer2/stuff/\nstreamer2/stuff:" \ + "streamer2/stu" + assert_expansion_function "expand_package_name" "video/" "" \ + "notapackage/" \ + "nota" + + # label-package + assert_expansion_function "expand_package_name" "" "label-package" \ + "//video/streamer2/stuff/\n//video/streamer2/stuff " \ + "//video/streamer2/stu" + assert_expansion_function "expand_package_name" "" "label-package" \ + "//video/notapackage/" \ + "//video/nota" +} + +test_expand_target_pattern() { + make_packages + assert_expansion_function "expand_target_pattern" "" label \ + "stuff " "//video/streamer2/stuff:" + + assert_expansion_function "expand_target_pattern" "" label \ + "//video/streamer2/stuff/\n//video/streamer2/stuff:" \ + "//video/streamer2/stu" + + assert_expansion_function "expand_target_pattern" "" label \ + "stuff " "video/streamer2/stuff:" + + assert_expansion_function "expand_target_pattern" "" label \ + "video/streamer2/stuff/\nvideo/streamer2/stuff:" \ + "video/streamer2/stu" + + assert_expansion_function "expand_target_pattern" "video/" label \ + "stuff " "streamer2/stuff:" + + assert_expansion_function "expand_target_pattern" "video/" label \ + "streamer2/stuff/\nstreamer2/stuff:" \ + "streamer2/stu" + + assert_expansion_function "expand_target_pattern" "video/" label \ + "stuff " "//video/streamer2/stuff:" + + assert_expansion_function "expand_target_pattern" "video/" label \ + "//video/streamer2/stuff/\n//video/streamer2/stuff:" \ + "//video/streamer2/stu" + + assert_expansion_function "expand_target_pattern" "video/" label \ + "" "video/streamer2/stuff:" + + assert_expansion_function "expand_target_pattern" "video/" label \ + "" "video/streamer2/stu" +} + +test_complete_pattern() { + make_packages + assert_expansion_function "complete_pattern" "" label \ + "stuff " "//video/streamer2/stuff:" + + assert_expansion_function "complete_pattern" "" label \ + "//video/streamer2/stuff/\n//video/streamer2/stuff:" \ + "//video/streamer2/stu" + + assert_expansion_function "complete_pattern" "" label-package \ + "//video/streamer2/stuff/\n//video/streamer2/stuff " \ + "//video/streamer2/stu" + + assert_expansion_function "complete_pattern" "" command \ + "clean " "clea" + + assert_expansion_function "complete_pattern" "" info-key \ + "install_base " "install_b" + + assert_expansion_function "complete_pattern" "" '{clean,add}' \ + "clean " "clea" + + assert_expansion_function "complete_pattern" "" 'command|{abc,def}' \ + "abc " "ab" + + assert_expansion_function "complete_pattern" "" 'command|{abc,def}' \ + "clean " "clea" + + # Assert label expansion + assert_expansion_function "complete_pattern" "" label \ + "stuff " "//video/streamer2/stuff:" + assert_expansion_function "complete_pattern" "" label \ + 'task_lib ' 'video/streamer2:ta' + assert_expansion_function "complete_pattern" "" label \ + 'with_special+_,=-.@~chars ' 'video/streamer2:with_s' + + # From a different directory + assert_expansion_function "complete_pattern" "video/" label \ + "stuff " "//video/streamer2/stuff:" + assert_expansion_function "complete_pattern" "video/" label \ + 'task_lib ' 'streamer2:ta' + assert_expansion_function "complete_pattern" "video/" label \ + '' 'video/streamer2:ta' + assert_expansion_function "complete_pattern" "video/" label \ + 'with_special+_,=-.@~chars ' 'streamer2:with_s' +} + +#### TESTS ############################################################# + +test_basic_subcommand_expansion() { + # 'Test basic subcommand completion' + assert_expansion 'bui' \ + 'build ' + assert_expansion 'hel' \ + 'help ' + assert_expansion 'shut' \ + 'shutdown ' +} + +test_common_options() { + # 'Test common option completion' + assert_expansion '--h' \ + '--host_jvm_' + assert_expansion '--host_jvm_a' \ + '--host_jvm_args=' +} + +test_build_options() { + # 'Test build option completion' + assert_expansion 'build --keep_g' \ + 'build --keep_going ' + assert_expansion 'build --expe' \ + 'build --experimental_' + # ...but 'help' doesn't expand this option: + assert_expansion 'help --cros' \ + 'help --cros' + assert_expansion 'build --test_stra' \ + 'build --test_strategy=' +} + +test_query_options() { + assert_expansion 'query --out' \ + 'query --output=' + + # Basic label expansion works for query, too. + make_packages + assert_expansion 'query video/streamer2:ta' \ + 'query video/streamer2:task_lib ' + assert_expansion 'query //video/streamer2:ta'\ + 'query //video/streamer2:task_lib ' +} + +test_run_options() { + # Should be the same as the build options. + # 'Test run option completion' + assert_expansion 'run --keep_g' \ + 'run --keep_going ' + assert_expansion 'run --expe' \ + 'run --experimental_' +} + +test_tristate_option() { + # 'Test tristate option completion' + assert_expansion 'build --nocache_test_result' \ + 'build --nocache_test_results ' +} + +make_dirs() { + mkdir -p video/streamer2/testing || fail "mkdir failed" + mkdir -p ${PACKAGE_PATH_PREFIX:-}video/streamer2/stuff || fail "mkdir failed" + mkdir -p video/streamer2/names || fail "mkdir failed" +} + + +test_directory_expansion() { + # 'Test expansion of directory names, even across package_path' + + make_dirs + + assert_expansion 'build vide' \ + 'build video/' + assert_expansion 'build video/' \ + 'build video/streamer2/' + assert_expansion 'build video/streamer2/t' \ + 'build video/streamer2/testing/' + assert_expansion 'build video/streamer2/s' \ + 'build video/streamer2/stuff/' + + # Now add BUILD files; it should no longer expand the trailing slashes: + make_empty_packages + + assert_expansion 'build video/streamer2/t' \ + 'build video/streamer2/testing' + assert_expansion 'build video/streamer2/s' \ + 'build video/streamer2/stuff' + + # Use of absolute forms of labels: + assert_expansion 'build //vide' \ + 'build //video/' + assert_expansion 'build //video/' \ + 'build //video/streamer2/' + assert_expansion 'build //video/streamer2/t' \ + 'build //video/streamer2/testing' + assert_expansion 'build //video/streamer2/s' \ + 'build //video/streamer2/stuff' +} + +test_directory_expansion_in_subdir() { + # 'Test expansion of directory names, when in a subdir of the workspace.' + + make_dirs + cd video 2>/dev/null || exit + + # Use of "video" while in "video" => no match: + assert_expansion 'build vide' \ + 'build vide' + assert_expansion 'build video/' \ + 'build video/' + assert_expansion 'build video/streamer2/t' \ + 'build video/streamer2/t' + assert_expansion 'build video/streamer2/s' \ + 'build video/streamer2/s' + + # Use of "//video" while in "video" => matches absolute: + assert_expansion 'build //vide' \ + 'build //video/' + assert_expansion 'build //video/' \ + 'build //video/streamer2/' + assert_expansion 'build //video/streamer2/t' \ + 'build //video/streamer2/testing/' + assert_expansion 'build //video/streamer2/s' \ + 'build //video/streamer2/stuff/' + + # Use of relative paths => matches + assert_expansion 'build streamer2/t' \ + 'build streamer2/testing/' + assert_expansion 'build streamer2/s' \ + 'build streamer2/stuff/' +} + +test_target_expansion() { + # 'Test expansion of target names within packages' + + make_packages + + # TODO(bazel-team): (2009) it would be good to test that "streamer2\t" + # yielded a menu of "streamer2:" and "streamer2/", but testing the + # terminal output (as opposed to the result of expansion) is + # beyond our ability right now. + + assert_expansion 'build video/streamer2:ta' \ + 'build video/streamer2:task_lib ' + + # Special characters + assert_expansion 'build video/streamer2:with_s' \ + 'build video/streamer2:with_special+_,=-.@~chars ' + + # Also, that 'bazel build' matches test and non-test rules (lack + # of trailing space after match => not unique match). + assert_expansion 'build video/streamer2:to' \ + 'build video/streamer2:token_bucket' + + assert_expansion 'build video/streamer2/s' \ + 'build video/streamer2/stuff' + + assert_expansion 'build video/streamer2/stuff:s' \ + 'build video/streamer2/stuff:stuff ' + + # Test that 'bazel build' does not match commented-out rules. + assert_expansion 'build video/streamer2:comment_build_target_1o' \ + 'build video/streamer2:comment_build_target_1o' + + assert_expansion 'build video/streamer2:comment_build_target_2' \ + 'build video/streamer2:comment_build_target_2new ' + + # Test that 'bazel test' expands only test rules. + assert_expansion 'test video/streamer2:to' \ + 'test video/streamer2:token_bucket_test ' + + # Test that 'blaze test' does not match commented-out rules. + assert_expansion 'test video/streamer2:token_bucket_t_1o' \ + 'test video/streamer2:token_bucket_t_1o' + + assert_expansion 'test video/streamer2:token_bucket_t' \ + 'test video/streamer2:token_bucket_test ' + + assert_expansion_error_not_contains 'test video/streamer2:match' \ + 'syntax error' + + # Test that :all wildcard is expanded when there is more than one + # match. + # + # One match => no :all. + assert_expansion 'test video/streamer2:' \ + 'test video/streamer2:token_bucket_test ' + # Multiple matches => :all. + assert_expansion 'build video/streamer2:a' \ + 'build video/streamer2:all ' + + # Test that 'bazel run' expands only non-test binary rules. + assert_expansion 'run video/streamer2:to' \ + 'run video/streamer2:token_bucket_binary ' + + # Test that 'bazel run' expands for binary and test rules, but not library + # with BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN set. + assert_expansion 'run video/streamer2:to' \ + 'run video/streamer2:token_bucket_' \ + 'BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN=true' + + # Test the 'bazel run' expands for test rules, with + # BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN set. + assert_expansion 'run video/streamer2:token_bucket_t' \ + 'run video/streamer2:token_bucket_test ' \ + 'BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN=1' + + # Test that 'bazel run' expands only non-test binary rules when the + # BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN is false. + assert_expansion 'run video/streamer2:to' \ + 'run video/streamer2:token_bucket_binary ' \ + 'BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN=false' + + # Test that 'bazel run' expands only non-test binary rules when the + # BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN is false. + assert_expansion 'run video/streamer2:to' \ + 'run video/streamer2:token_bucket_binary ' \ + 'BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN=0' + + # Test that 'bazel run' expands only non-test binary rules when the + # BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN is invalid. + assert_expansion 'run video/streamer2:to' \ + 'run video/streamer2:token_bucket_binary ' \ + 'BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN=junk' + + # Test that 'bazel run' expands only non-test binary rules when the + # BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN is empty. + assert_expansion 'run video/streamer2:to' \ + 'run video/streamer2:token_bucket_binary ' \ + 'BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN=' + + # Test that 'bazel run' does not match commented-out rules. + assert_expansion 'run video/streamer2:comment_run_target_1o' \ + 'run video/streamer2:comment_run_target_1o' + + assert_expansion 'run video/streamer2:comment_run_target_2' \ + 'run video/streamer2:comment_run_target_2new ' + + # Test that 'bazel run' expands binaries with spaces in the build rules + assert_expansion 'run video/streamer2:J' \ + 'run video/streamer2:JavaBinary ' + + # Test that 'bazel run' expands targets when the name attribute is not first + assert_expansion 'run video/streamer2:py' \ + 'run video/streamer2:pybin ' + + # Test that 'bazel run' expands binaries with newlines in the build rules + assert_expansion 'run video/streamer2:A' \ + 'run video/streamer2:AnotherJavaBinary ' + + # Test that the expansion of rules with 'name=...' strings isn't messed up. + assert_expansion 'build video/streamer2/names:' \ + 'build video/streamer2/names:foo ' + + # Test that dashes are matched even when locale isn't C. + LC_ALL=en_US.UTF-8 \ + assert_expansion 'build dash:m' \ + 'build dash:mia-bid-multiplier-mixer-module ' +} + +test_target_expansion_in_subdir() { + # 'Test expansion of targets when in a subdir of the workspace.' + + make_packages + cd video 2>/dev/null + + # Relative labels: + assert_expansion 'build streamer2:ta' \ + 'build streamer2:task_lib ' + + assert_expansion 'build streamer2:to' \ + 'build streamer2:token_bucket' + + assert_expansion 'build streamer2/s' \ + 'build streamer2/stuff' + + assert_expansion 'build streamer2/stuff:s' \ + 'build streamer2/stuff:stuff ' + + # (no match) + assert_expansion 'build video/streamer2:ta' \ + 'build video/streamer2:ta' + + # Absolute labels work as usual: + assert_expansion 'build //video/streamer2:ta' \ + 'build //video/streamer2:task_lib ' +} + +test_target_expansion_in_package() { + # 'Test expansion of targets when in a package.' + + make_packages + cd video/streamer2 2>/dev/null + + assert_expansion 'build :ta' \ + 'build :task_lib ' + + assert_expansion 'build :to' \ + 'build :token_bucket' + + # allow slashes in rule names + assert_expansion 'build :checks/th' \ + 'build :checks/thingy ' + + assert_expansion 'build s' \ + 'build stuff' + + # (no expansion) + assert_expansion 'build :s' \ + 'build :s' +} + +test_help() { + # "Test that bazel help expands subcommand names" + assert_expansion 'help qu' \ + 'help query ' + assert_expansion 'help bui' \ + 'help build ' + assert_expansion 'help shut' \ + 'help shutdown ' + assert_expansion 'help start' \ + 'help startup_options ' +} + +test_info() { + # "Test that bazel info keys are expanded" + assert_expansion 'info commi' \ + 'info committed-heap-size ' + assert_expansion 'info i' \ + 'info install_base ' + assert_expansion 'info --show_m' \ + 'info --show_make_env ' +} + +run_suite "Tests of bash completion of 'blaze' command." diff --git a/scripts/bazel-complete-template.bash b/scripts/bazel-complete-template.bash new file mode 100644 index 0000000000..e1df891b89 --- /dev/null +++ b/scripts/bazel-complete-template.bash @@ -0,0 +1,469 @@ +# -*- sh -*- (Bash only) +# +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# Bash completion of Bazel commands. +# +# The template is expanded at build time using tables of commands/options +# derived from the bazel executable built in the same client; the expansion is +# written to bazel-complete.bash. +# +# Provides command-completion for: +# - bazel prefix options (e.g. --host_jvm_args) +# - blaze command-set (e.g. build, test) +# - blaze command-specific options (e.g. --copts) +# - values for enum-valued options +# - package-names, exploring all package-path roots. +# - targets within packages. + +# The package path used by the completion routines. Unfortunately +# this isn't necessarily the same as the actual package path used by +# Bazel, but that's ok. (It's impossible for us to reliably know what +# the relevant package-path, so this is just a good guess. Users can +# override it if they want.) +# +# Don't use it directly. Generate the final script with +# bazel build //scripts:bash_completion instead. +# +: ${BAZEL_COMPLETION_PACKAGE_PATH:=%workspace%} + + +# If true, Bazel query is used for autocompletion. This is more +# accurate than the heuristic grep, especially for strangely-formatted +# BUILD files. But it can be slower, especially if the Bazel server +# is busy, and more brittle, if the BUILD file contains serious +# errors. This is an experimental feature. +: ${BAZEL_COMPLETION_USE_QUERY:=false} + + +# If true, Bazel run allows autocompletion for test targets. This is convenient +# for users who run a lot of tests/benchmarks locally with blaze run. +: ${BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN:=false} + +# Some commands might interfer with the important one, so don't complete them +: ${BAZEL_IGNORED_COMMAND_REGEX:="__none__"} + +# Bazel command +: ${BAZEL:=bazel} + +# Pattern to match for looking for a target +# BAZEL_BUILD_MATCH_PATTERN__* give the pattern for label-* +# when looking in the the build file. +# BAZEL_QUERY_MATCH_PATTERN__* give the pattern for label-* +# when using 'bazel query'. +# _RUNTEST are special case when BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN +# is on. +: ${BAZEL_BUILD_MATCH_PATTERN__test:='(.*_test|test_suite)'} +: ${BAZEL_QUERY_MATCH_PATTERN__test:='(test|test_suite)'} +: ${BAZEL_BUILD_MATCH_PATTERN__bin:='.*_binary'} +: ${BAZEL_QUERY_MATCH_PATTERN__bin:='(binary)'} +: ${BAZEL_BUILD_MATCH_PATTERN_RUNTEST__bin:='(.*_(binary|test)|test_suite)'} +: ${BAZEL_QUERY_MATCH_PATTERN_RUNTEST__bin:='(binary|test)'} +: ${BAZEL_BUILD_MATCH_PATTERN__:='.*'} +: ${BAZEL_QUERY_MATCH_PATTERN__:=''} + +# Usage: _bazel__get_rule_match_pattern <command> +# Determine what kind of rules to match, based on command. +_bazel__get_rule_match_pattern() { + local var_name pattern + if _bazel__is_true "$BAZEL_COMPLETION_USE_QUERY"; then + var_name="BAZEL_QUERY_MATCH_PATTERN" + else + var_name="BAZEL_BUILD_MATCH_PATTERN" + fi + if [[ "$1" =~ ^label-?([a-z]*)$ ]]; then + pattern=${BASH_REMATCH[1]:-} + if _bazel__is_true "$BAZEL_COMPLETION_ALLOW_TESTS_FOR_RUN"; then + eval "echo \"\${${var_name}_RUNTEST__${pattern}:-\$${var_name}__${pattern}}\"" + else + eval "echo \"\$${var_name}__${pattern}\"" + fi + fi +} + +# Compute workspace directory. Search for the innermost +# enclosing directory with a WORKSPACE file. +_bazel__get_workspace_path() { + local workspace=$PWD + while true; do + if [ -f "${workspace}/WORKSPACE" ]; then + break + elif [ -z "$workspace" -o "$workspace" = "/" ]; then + workspace=$PWD + break; + fi + workspace=${workspace%/*} + done + echo $workspace +} + + +# Find the current piece of the line to complete, but only do word breaks at +# certain characters. In particular, ignore these: "':= +# This method also takes into account the current cursor position. +# +# Works with both bash 3 and 4! Bash 3 and 4 perform different word breaks when +# computing the COMP_WORDS array. We need this here because Bazel options are of +# the form --a=b, and labels of the form //some/label:target. +_bazel__get_cword() { + local cur=${COMP_LINE:0:$COMP_POINT} + # This expression finds the last word break character, as defined in the + # COMP_WORDBREAKS variable, but without '=' or ':', which is not preceeded by + # a slash. Quote characters are also excluded. + local wordbreaks="$COMP_WORDBREAKS" + wordbreaks="${wordbreaks//\'/}" + wordbreaks="${wordbreaks//\"/}" + wordbreaks="${wordbreaks//:/}" + wordbreaks="${wordbreaks//=/}" + local word_start=$(expr "$cur" : '.*[^\]['"${wordbreaks}"']') + echo "${cur:$word_start}" +} + + +# Usage: _bazel__package_path <workspace> <displacement> +# +# Prints a list of package-path root directories, displaced using the +# current displacement from the workspace. All elements have a +# trailing slash. +_bazel__package_path() { + local workspace=$1 displacement=$2 root + IFS=: + for root in ${BAZEL_COMPLETION_PACKAGE_PATH//\%workspace\%/$workspace}; do + unset IFS + echo "$root/$displacement" + done +} + +# Usage: _bazel__options_for <command> +# +# Prints the set of options for a given Bazel command, e.g. "build". +_bazel__options_for() { + local options + if [[ "${BAZEL_COMMAND_LIST}" =~ ^(.* )?$1( .*)?$ ]]; then + local option_name=$(echo $1 | perl -ne 'print uc' | tr "-" "_") + eval "echo \${BAZEL_COMMAND_${option_name}_FLAGS}" | tr " " "\n" + fi +} +# Usage: _bazel__expansion_for <command> +# +# Prints the completion pattern for a given Bazel command, e.g. "build". +_bazel__expansion_for() { + local options + if [[ "${BAZEL_COMMAND_LIST}" =~ ^(.* )?$1( .*)?$ ]]; then + local option_name=$(echo $1 | perl -ne 'print uc' | tr "-" "_") + eval "echo \${BAZEL_COMMAND_${option_name}_ARGUMENT}" + fi +} + +# Usage: _bazel__matching_targets <kind> <prefix> +# +# Prints target names of kind <kind> and starting with <prefix> in the BUILD +# file given as standard input. <kind> is a basic regex (BRE) used to match the +# bazel rule kind and <prefix> is the prefix of the target name. +_bazel__matching_targets() { + local kind_pattern="$1" + local target_prefix="$2" + # The following commands do respectively: + # Remove BUILD file comments + # Replace \n by spaces to have the BUILD file in a single line + # Extract all rule types and target names + # Grep the kind pattern and the target prefix + # Returns the target name + sed 's/#.*$//' \ + | tr "\n" " " \ + | sed 's/\([a-zA-Z0-9_]*\) *(\([^)]* \)\{0,1\}name *= *['\''"]\([a-zA-Z0-9_/.+=,@~-]*\)['\''"][^)]*)/\ +type:\1 name:\3\ +/g' \ + | grep -E "^type:$kind_pattern name:$target_prefix" \ + | cut -d ':' -f 3 +} + + +# Usage: _bazel__is_true <string> +# +# Returns true or false based on the input string. The following are +# valid true values (the rest are false): "1", "true". +_bazel__is_true() { + local str="$1" + [[ "$str" == "1" || "$str" == "true" ]] +} + +# Usage: _bazel__expand_rules_in_package <workspace> <displacement> +# <current> <label-type> +# +# Expands rules in specified packages, exploring all roots of +# $BAZEL_COMPLETION_PACKAGE_PATH, not just $(pwd). Only rules +# appropriate to the command are printed. Sets $COMPREPLY array to +# result. +# +# If $BAZEL_COMPLETION_USE_QUERY is true, 'bazel query' is used +# instead, with the actual Bazel package path; +# $BAZEL_COMPLETION_PACKAGE_PATH is ignored in this case, since the +# actual Bazel value is likely to be more accurate. +_bazel__expand_rules_in_package() { + local workspace=$1 displacement=$2 current=$3 label_type=$4 + local package_name=$(echo "$current" | cut -f1 -d:) + local rule_prefix=$(echo "$current" | cut -f2 -d:) + local root buildfile rule_pattern r result + + result= + pattern=$(_bazel__get_rule_match_pattern "$label_type") + if _bazel__is_true "$BAZEL_COMPLETION_USE_QUERY"; then + package_name=$(echo "$package_name" | tr -d "'\"") # remove quotes + result=$(${BAZEL} --output_base=/tmp/${BAZEL}-completion-$USER query \ + --keep_going --noshow_progress \ + "kind('$pattern rule', '$package_name:*')" 2>/dev/null | + cut -f2 -d: | grep "^$rule_prefix") + else + for root in $(_bazel__package_path "$workspace" "$displacement"); do + buildfile="$root/$package_name/BUILD" + if [ -f "$buildfile" ]; then + result=$(_bazel__matching_targets \ + "$pattern" "$rule_prefix" <"$buildfile") + break + fi + done + fi + + index=$(echo $result | wc -w) + if [ -n "$result" ]; then + echo "$result" | tr " " "\n" | sed 's|$| |' + fi + # Include ":all" wildcard if there was no unique match. (The zero + # case is tricky: we need to include "all" in that case since + # otherwise we won't expand "a" to "all" in the absence of rules + # starting with "a".) + if [ $index -ne 1 ] && expr all : "\\($rule_prefix\\)" >/dev/null; then + echo "all " + fi +} + +# Usage: _bazel__expand_package_name <workspace> <displacement> <current-word> +# <label-type> +# +# Expands directories, but explores all roots of +# BAZEL_COMPLETION_PACKAGE_PATH, not just $(pwd). When a directory is +# a bazel package, the completion offers "pkg:" so you can expand +# inside the package. +# Sets $COMPREPLY array to result. +_bazel__expand_package_name() { + local workspace=$1 displacement=$2 current=$3 type=${4:-} root dir index + for root in $(_bazel__package_path "$workspace" "$displacement"); do + found=0 + for dir in $(compgen -d $root$current); do + [ -L "$dir" ] && continue # skip symlinks (e.g. bazel-bin) + [[ "$dir" =~ ^(.*/)?\.[^/]*$ ]] && continue # skip dotted dir (e.g. .git) + found=1 + echo "${dir#$root}/" + if [ -f $dir/BUILD ]; then + if [ "${type}" = "label-package" ]; then + echo "${dir#$root} " + else + echo "${dir#$root}:" + fi + fi + done + [ $found -gt 0 ] && break # Stop searching package path upon first match. + done +} + +# Usage: _bazel__expand_target_pattern <workspace> <displacement> +# <word> <label-syntax> +# +# Expands "word" to match target patterns, using the current workspace +# and displacement from it. "command" is used to filter rules. +# Sets $COMPREPLY array to result. +_bazel__expand_target_pattern() { + local workspace=$1 displacement=$2 current=$3 label_syntax=$4 + case "$current" in + //*:*) # Expand rule names within package, no displacement. + if [ "${label_syntax}" = "label-package" ]; then + compgen -S " " -W "BUILD" "$(echo current | cut -f ':' -d2)" + else + _bazel__expand_rules_in_package "$workspace" "" "$current" "$label_syntax" + fi + ;; + *:*) # Expand rule names within package, displaced. + if [ "${label_syntax}" = "label-package" ]; then + compgen -S " " -W "BUILD" "$(echo current | cut -f ':' -d2)" + else + _bazel__expand_rules_in_package \ + "$workspace" "$displacement" "$current" "$label_syntax" + fi + ;; + //*) # Expand filenames using package-path, no displacement + _bazel__expand_package_name "$workspace" "" "$current" "$label_syntax" + ;; + *) # Expand filenames using package-path, displaced. + if [ -n "$current" ]; then + _bazel__expand_package_name "$workspace" "$displacement" "$current" "$label_syntax" + fi + ;; + esac +} + +_bazel__get_command() { + for word in "${COMP_WORDS[@]:1:COMP_CWORD-1}"; do + if echo "$BAZEL_COMMAND_LIST" | grep -wsq -e "$word"; then + echo $word + break + fi + done +} + +# Returns the displacement to the workspace given in $1 +_bazel__get_displacement() { + if [[ "$PWD" =~ ^$1/.*$ ]]; then + echo ${PWD##$1/}/ + fi +} + + +# Usage: _bazel__complete_pattern <workspace> <displacement> <current> +# <type> +# +# Expand a word according to a type. The currently supported types are: +# - {a,b,c}: an enum that can take value a, b or c +# - label: a label of any kind +# - label-bin: a label to a runnable rule (basically to a _binary rule) +# - label-test: a label to a test rule +# - info-key: an info key as listed by `bazel help info-keys` +# - command: the name of a command +# - path: a file path +# - combinaison of previous type using | as separator +_bazel__complete_pattern() { + local workspace=$1 displacement=$2 current=$3 types=$4 + for type in $(echo $types | tr "|" "\n"); do + case "$type" in + label*) + _bazel__expand_target_pattern "$workspace" "$displacement" \ + "$current" "$type" + ;; + info-key) + compgen -S " " -W "${BAZEL_INFO_KEYS}" -- "$current" + ;; + "command") + local commands=$(echo "${BAZEL_COMMAND_LIST}" \ + | tr " " "\n" | grep -v "^${BAZEL_IGNORED_COMMAND_REGEX}$") + compgen -S " " -W "${commands}" -- "$current" + ;; + path) + compgen -f -- "$current" + ;; + *) + compgen -S " " -W "$type" -- "$current" + ;; + esac + done +} + +# Usage: _bazel__expand_options <workspace> <displacement> <current-word> +# <options> +# +# Expands options, making sure that if current-word contains an equals sign, +# it is handled appropriately. +_bazel__expand_options() { + local workspace="$1" displacement="$2" cur="$3" options="$4" + if [[ $cur =~ = ]]; then + # also expands special labels + current=$(echo "$cur" | cut -f2 -d=) + _bazel__complete_pattern "$workspace" "$displacement" "$current" \ + "$(compgen -W "$options" -- "$cur" | cut -f2 -d=)" \ + | sort -u + else + compgen -W "$(echo "$options" | sed 's|=.*$|=|')" -- "$cur" \ + | sed 's|\([^=]\)$|\1 |' + fi +} + + +_bazel__complete_stdout() { + local cur=$(_bazel__get_cword) word command displacement workspace + + # Determine command: "" (startup-options) or one of $BAZEL_COMMAND_LIST. + command="$(_bazel__get_command)" + + workspace="$(_bazel__get_workspace_path)" + displacement="$(_bazel__get_displacement ${workspace})" + + case "$command" in + "") # Expand startup-options or commands + local commands=$(echo "${BAZEL_COMMAND_LIST}" \ + | tr " " "\n" | grep -v "^${BAZEL_IGNORED_COMMAND_REGEX}$") + _bazel__expand_options "$workspace" "$displacement" "$cur" \ + "${commands}\ + ${BAZEL_STARTUP_OPTIONS}" + ;; + + *) + case "$cur" in + -*) # Expand options: + _bazel__expand_options "$workspace" "$displacement" "$cur" \ + "$(_bazel__options_for $command)" + ;; + *) # Expand target pattern + expansion_pattern="$(_bazel__expansion_for $command)" + NON_QUOTE_REGEX="^[\"']" + if [[ $command = query && $cur =~ $NON_QUOTE_REGEX ]]; then + : # Ideally we would expand query expressions---it's not + # that hard, conceptually---but readline is just too + # damn complex when it comes to quotation. Instead, + # for query, we just expand target patterns, unless + # the first char is a quote. + elif [ -n "$expansion_pattern" ]; then + _bazel__complete_pattern \ + "$workspace" "$displacement" "$cur" "$expansion_pattern" + fi + ;; + esac + ;; + esac +} + +_bazel__to_compreply() { + local replies="$1" + COMPREPLY=() + # Trick to preserve whitespaces + while IFS="" read -r reply; do + COMPREPLY+=("${reply}") + done < <(echo "${replies}") +} + +_bazel__complete() { + _bazel__to_compreply "$(_bazel__complete_stdout)" +} + +# Some users have aliases such as bt="bazel test" or bb="bazel build", this +# completion function allows them to have auto-completion for these aliases. +_bazel__complete_target_stdout() { + local cur=$(_bazel__get_cword) word command displacement workspace + + # Determine command: "" (startup-options) or one of $BAZEL_COMMAND_LIST. + command="$1" + + workspace="$(_bazel__get_workspace_path)" + displacement="$(_bazel__get_displacement ${workspace})" + + _bazel__to_compreply "$(_bazel__expand_target_pattern "$workspace" "$displacement" \ + "$cur" "$command")" +} + +# default completion for bazel +complete -F _bazel__complete -o nospace "${BAZEL}" + +######################################################################## +## DO NOT EDIT BELOW THIS LINE. +## This section is automatically generated by update-completion.sh. diff --git a/scripts/testenv.sh b/scripts/testenv.sh new file mode 100755 index 0000000000..42ee15381a --- /dev/null +++ b/scripts/testenv.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Setting up the environment for Bazel completion script test + +[ -z "$TEST_SRCDIR" ] && { echo "TEST_SRCDIR not set!" >&2; exit 1; } + +# Load the unit-testing framework +source "${TEST_SRCDIR}/src/test/shell/unittest.bash" || \ + { echo "Failed to source unittest.bash" >&2; exit 1; } + +set_up() { + mkdir -p $TEST_TMPDIR/workspace + cd $TEST_TMPDIR/workspace + touch $TEST_TMPDIR/workspace/WORKSPACE +} + +tear_down() { + rm -fr $TEST_TMPDIR/workspace/* +} @@ -80,7 +80,10 @@ genrule( cmd = "cat $(location //src/main/cpp:client) package-zip > $@ && zip -qA $@", executable = 1, output_to_bindir = 1, - visibility = ["//src/test:__subpackages__"], # For integration tests + visibility = [ + "//scripts:__pkg__", # For bash completion generation + "//src/test:__subpackages__", # For integration tests + ], ) filegroup( |