From e690f21e4ef00321caa179b3ff6aca21d264bdf7 Mon Sep 17 00:00:00 2001 From: cornfeedhobo Date: Tue, 2 Feb 2021 02:38:42 -0600 Subject: [PATCH] Ensure goenv and go play together well This PR seeks to address several issues surrounding the go and goenv plugins. The nature of goenv allows for a situation where the initial shell does not point to a working go binary, and instead at the included shim script. The result is that one must run reload after moving to a project directory with a version file, however in doing so, PATH is updated, requiring they exit the shell or risk lookup collisions and unexpected behavior. This is solved by using preexec to check the version before changing directories and restarting the shell if the version has changed. The exec pattern is copied from _bash-it-restart, but is edited to support this specific use case. Additionally, tests have been uploaded and these are now being linted. --- clean_files.txt | 11 ++++---- plugins/available/go.plugin.bash | 41 ++++++++++++++++++++--------- plugins/available/goenv.plugin.bash | 32 ++++++++++++++++++---- test/plugins/go.plugin.bats | 22 ++++++---------- 4 files changed, 69 insertions(+), 37 deletions(-) mode change 100755 => 100644 plugins/available/go.plugin.bash diff --git a/clean_files.txt b/clean_files.txt index 85ec886b..1403c790 100644 --- a/clean_files.txt +++ b/clean_files.txt @@ -24,12 +24,6 @@ hooks .gitattributes lint_clean_files.sh -# plugins -# -plugins/available/history.plugin.bash -plugins/available/history-search.plugin.bash -plugins/available/history-substring-search.plugin.bash - # themes # themes/agnoster @@ -54,6 +48,11 @@ themes/modern plugins/available/basher.plugin.bash plugins/available/cmd-returned-notify.plugin.bash plugins/available/docker-machine.plugin.bash +plugins/available/go.plugin.bash +plugins/available/goenv.plugin.bash +plugins/available/history.plugin.bash +plugins/available/history-search.plugin.bash +plugins/available/history-substring-search.plugin.bash plugins/available/xterm.plugin.bash # completions diff --git a/plugins/available/go.plugin.bash b/plugins/available/go.plugin.bash old mode 100755 new mode 100644 index 566d7669..5592a006 --- a/plugins/available/go.plugin.bash +++ b/plugins/available/go.plugin.bash @@ -1,19 +1,36 @@ -#!/usr/bin/env bash - +# shellcheck shell=bash cite about-plugin about-plugin 'go environment variables & path configuration' -command -v go &>/dev/null || return +# Load after basher and goenv +# BASH_IT_LOAD_PRIORITY: 270 -function _go_pathmunge_wrap() { - IFS=':' local -a 'a=($1)' - local i=${#a[@]} - while [ $i -gt 0 ] ; do - i=$(( i - 1 )) - pathmunge "${a[i]}/bin" - done -} +# Test `go version` because goenv creates shim scripts that will be found in PATH +# but do not always resolve to a working install. +{ _command_exists go && go version &> /dev/null; } || return 0 export GOROOT="${GOROOT:-$(go env GOROOT)}" export GOPATH="${GOPATH:-$(go env GOPATH)}" -_go_pathmunge_wrap "${GOPATH}:${GOROOT}" + +# $GOPATH/bin is the default location for binaries. Because GOPATH accepts a list of paths and each +# might be managed differently, we add each path's /bin folder to PATH using pathmunge, +# while preserving ordering. +# e.g. GOPATH=foo:bar -> PATH=foo/bin:bar/bin +_bash-it-gopath-pathmunge() { + _about 'Ensures paths in GOPATH are added to PATH using pathmunge, with /bin appended' + _group 'go' + if [[ -z $GOPATH ]]; then + echo 'GOPATH empty' >&2 + return 1 + fi + local paths i + IFS=: read -r -a paths <<< "$GOPATH" + i=${#paths[@]} + while [[ $i -gt 0 ]]; do + i=$((i - 1)) + if [[ -n "${paths[i]}" ]]; then + pathmunge "${paths[i]}/bin" + fi + done +} +_bash-it-gopath-pathmunge diff --git a/plugins/available/goenv.plugin.bash b/plugins/available/goenv.plugin.bash index ecc1b1c2..d00fce67 100644 --- a/plugins/available/goenv.plugin.bash +++ b/plugins/available/goenv.plugin.bash @@ -1,20 +1,42 @@ +# shellcheck shell=bash cite about-plugin about-plugin 'load goenv, if you are using it' +# https://github.com/syndbg/goenv + +# Load after basher +# BASH_IT_LOAD_PRIORITY: 260 + # Don't modify the environment if we can't find the tool: # - Check if in $PATH already # - Check if installed manually to $GOENV_ROOT # - Check if installed manually to $HOME -_command_exists goenv || - [[ -n "$GOENV_ROOT" && -x "$GOENV_ROOT/bin/goenv" ]] || - [[ -x "$HOME/.goenv/bin/goenv" ]] || - return +_command_exists goenv \ + || [[ -n "$GOENV_ROOT" && -x "$GOENV_ROOT/bin/goenv" ]] \ + || [[ -x "$HOME/.goenv/bin/goenv" ]] \ + || return 0 # Set GOENV_ROOT, if not already set export GOENV_ROOT="${GOENV_ROOT:-$HOME/.goenv}" # Add GOENV_ROOT/bin to PATH, if that's where it's installed -! _command_exists goenv && [[ -x "$GOENV_ROOT/bin/goenv" ]] && pathmunge "$GOENV_ROOT/bin" +if ! _command_exists goenv && [[ -x "$GOENV_ROOT/bin/goenv" ]]; then + pathmunge "$GOENV_ROOT/bin" +fi # Initialize goenv eval "$(goenv init - bash)" + +# If moving to a directory with a goenv version set, reload the shell +# to ensure the shell environment matches expectations. +_bash-it-goenv-preexec() { + export GOENV_OLD_VERSION="$(goenv version-name)" +} +_bash-it-goenv-precmd() { + if [[ -n $GOENV_OLD_VERSION ]] && [[ "$GOENV_OLD_VERSION" != "$(goenv version-name)" ]]; then + exec env -u PATH -u GOROOT -u GOPATH -u GOENV_OLD_VERSION "${0/-/}" --login + fi + unset GOENV_OLD_VERSION +} +preexec_functions+=(_bash-it-goenv-preexec) +precmd_functions+=(_bash-it-goenv-precmd) diff --git a/test/plugins/go.plugin.bats b/test/plugins/go.plugin.bats index d511bd24..0211e845 100644 --- a/test/plugins/go.plugin.bats +++ b/test/plugins/go.plugin.bats @@ -4,57 +4,51 @@ load ../test_helper load ../../lib/helpers load ../../lib/composure -@test 'ensure _go_pathmunge_wrap is defined' { +@test 'ensure _bash-it-gopath-pathmunge is defined' { { [[ $CI ]] || _command_exists go; } || skip 'golang not found' load ../../plugins/available/go.plugin - run type -t _go_pathmunge_wrap + run type -t _bash-it-gopath-pathmunge assert_line 'function' } @test 'plugins go: single entry in GOPATH' { { [[ $CI ]] || _command_exists go; } || skip 'golang not found' export GOPATH="/foo" - export GOROOT="/baz" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "/foo/bin:/baz/bin" + assert_equal "$(cut -d':' -f1 <<<$PATH)" "/foo/bin" } @test 'plugins go: single entry in GOPATH, with space' { { [[ $CI ]] || _command_exists go; } || skip 'golang not found' export GOPATH="/foo bar" - export GOROOT="/baz" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "/foo bar/bin:/baz/bin" + assert_equal "$(cut -d':' -f1 <<<$PATH)" "/foo bar/bin" } @test 'plugins go: single entry in GOPATH, with escaped space' { { [[ $CI ]] || _command_exists go; } || skip 'golang not found' export GOPATH="/foo\ bar" - export GOROOT="/baz" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "/foo\ bar/bin:/baz/bin" + assert_equal "$(cut -d':' -f1 <<<$PATH)" "/foo\ bar/bin" } @test 'plugins go: multiple entries in GOPATH' { { [[ $CI ]] || _command_exists go; } || skip 'golang not found' export GOPATH="/foo:/bar" - export GOROOT="/baz" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1,2,3 <<<$PATH)" "/foo/bin:/bar/bin:/baz/bin" + assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "/foo/bin:/bar/bin" } @test 'plugins go: multiple entries in GOPATH, with space' { { [[ $CI ]] || _command_exists go; } || skip 'golang not found' export GOPATH="/foo:/foo bar" - export GOROOT="/baz" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1,2,3 <<<$PATH)" "/foo/bin:/foo bar/bin:/baz/bin" + assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "/foo/bin:/foo bar/bin" } @test 'plugins go: multiple entries in GOPATH, with escaped space' { { [[ $CI ]] || _command_exists go; } || skip 'golang not found' export GOPATH="/foo:/foo\ bar" - export GOROOT="/baz" load ../../plugins/available/go.plugin - assert_equal "$(cut -d':' -f1,2,3 <<<$PATH)" "/foo/bin:/foo\ bar/bin:/baz/bin" + assert_equal "$(cut -d':' -f1,2 <<<$PATH)" "/foo/bin:/foo\ bar/bin" }