This post has been replaced with a newer post that works with SDKMAN.

The Fish Shell is awesome but it doesn’t have a Posix-compatible shell language like Bash or ZSH. This is a problem when you’re trying to use third-party tools that expect a Posix-compatible shell. Tools like GVM, the Groovy enVironment Manager.

GVM, like it’s inspiration RVM, lets a developer switch between versions of the language toolchain. This is important because modern languages are moving targets and building an application against one version of the toolchain isn’t guaranteed to run against later versions.

GVM, in particular, wires itself into the Bash and ZSH shells. The best approach to getting GVM to work with Fish is to rewrite it for Fish. But the developers for GVM don’t seem inclined to do that. So we have to cheat.

There’s an existing bit of code in Oh My Fish that’s supposed to make GVM work with Fish but that code doesn’t actually work. So I put together an alternative that works cleanly. The first bit of code uses a Fish function to invoke gvm. It goes in the “~/.config/fish/functions” directory and must be named “~/.config/fish/functions/gvm.fish”.

function gvm
	set after_env (mktemp -t env)
	set path_env (mktemp -t env)

    bash -c "source ~/.gvm/bin/gvm-init.sh && gvm $argv && printenv > $after_env"

    # remove any pre-existing .gvm paths
    for elem in $PATH
    	switch $elem
    		case '*/.gvm/*'
    			# ignore
    		case '*'
    			echo "$elem" >> $path_env
    	end
    end

    for env in (cat $after_env)
    	set env_name (echo $env | sed s/=.\*//)
    	set env_value (echo $env | sed s/.\*=//)
    	switch $env_name
    		case 'PATH'
    			for elem in (echo $env_value | tr ':' '\n')
    				switch $elem
    					case '*/.gvm/*'
    						echo "$elem" >> $path_env
    				end
    			end
    		case '*'
    			switch $env_value
    				case '*/.gvm/*'
		    			eval set -g $env_name $env_value > /dev/null
    			end
    	end
    end
    set -gx PATH (cat $path_env) ^ /dev/null

    rm -f $after_env
    rm -f $path_env
end

This code works by running the GVM ‘init’ command and then the actual ‘gvm’ command in a Bash shell. It compares the environment before and after running those commands and applies the new or changed environment variables against the Fish environment. It does the same for the PATH environment variable.

The next bit of code goes into the “~/.config/fish/config.fish” file. This is the main configuration file for Fish.

set -gx PATH $PATH (find ~/.gvm/*/current/bin -maxdepth 0)

This bit of code looks like the non-functioning code in Oh My Fish. But here it’s used correctly, to setup the initial paths for the shell. It works by adding all of the “current” directories maintained by GVM into the initial shell path.

The last bit of code is optional, but, ironically, is also the most complicated. It provides command-line completion for the ‘gvm’ command. It goes in a new file named “~/.config/fish/completions/gvm.fish”. The completion script parses the installed versions and will provide version number completion when using the ‘gvm’ command.

function __fish_gvm_no_command --description 'Test if gvm has yet to be given the main command'
  set cmd (commandline -opc)
  if [ (count $cmd) -eq 1 ]
    return 0
  end
  return 1
end

function __fish_gvm_using_command
  set cmd (commandline -opc)
  if [ (count $cmd) -eq 2 ]
    if [ $argv[1] = $cmd[2] ]
      return 0
    end
  end
  return 1
end

function __fish_gvm_using_subcommand
  set cmd (commandline -opc)
  set cmd_main $argv[1]
  set cmd_sub $argv[2]

  if [ (count $cmd) -gt 2 ]
    if [ $cmd_main = $cmd[2] ]; and [ $cmd_sub = $cmd[3] ]
      return 0
    end
  end
  return 1
end

function __fish_gvm_specifying_candidate
  set cmd (commandline -opc)

  if [ (count $cmd) -gt 2 ]
    if [ $argv[1] = $cmd[2] ]
      return 0
    end
  end
  return 1
end

function __fish_gvm_candidates
  cat ~/.gvm/var/candidates | tr ',' '\n'
end

function __fish_gvm_installed_versions
  set cmd (commandline -opc)
  ls -v1 ~/.gvm/$cmd[3] | grep -v current
end

# install
complete -c gvm -f -n '__fish_gvm_no_command' -a 'install' -d 'Install new version'
complete -c gvm -f -n '__fish_gvm_no_command' -a 'i' -d 'Install new version'
complete -c gvm -f -n '__fish_gvm_using_command install' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_using_command i' -a "(__fish_gvm_candidates)"

# uninstall
complete -c gvm -f -n '__fish_gvm_no_command' -a 'uninstall' -d 'Uninstall version'
complete -c gvm -f -n '__fish_gvm_no_command' -a 'rm' -d 'Uninstall version'
complete -c gvm -f -n '__fish_gvm_using_command uninstall' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_using_command rm' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate uninstall' -a "(__fish_gvm_installed_versions)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate rm' -a "(__fish_gvm_installed_versions)"

# list
complete -c gvm -f -n '__fish_gvm_no_command' -a 'list' -d 'List versions'
complete -c gvm -f -n '__fish_gvm_no_command' -a 'ls' -d 'List versions'
complete -c gvm -f -n '__fish_gvm_using_command list' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_using_command ls' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate list' -a "(__fish_gvm_installed_versions)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate ls' -a "(__fish_gvm_installed_versions)"

# use
complete -c gvm -f -n '__fish_gvm_no_command' -a 'use' -d 'Use specific version'
complete -c gvm -f -n '__fish_gvm_no_command' -a 'u' -d 'Use specific version'
complete -c gvm -f -n '__fish_gvm_using_command use' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_using_command u' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate use' -a "(__fish_gvm_installed_versions)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate u' -a "(__fish_gvm_installed_versions)"

# default
complete -c gvm -f -n '__fish_gvm_no_command' -a 'default' -d 'Set default version'
complete -c gvm -f -n '__fish_gvm_no_command' -a 'd' -d 'Set default version'
complete -c gvm -f -n '__fish_gvm_using_command default' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_using_command d' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate default' -a "(__fish_gvm_installed_versions)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate d' -a "(__fish_gvm_installed_versions)"

# current
complete -c gvm -f -n '__fish_gvm_no_command' -a 'current' -d 'Display current version'
complete -c gvm -f -n '__fish_gvm_no_command' -a 'c' -d 'Display current version'
complete -c gvm -f -n '__fish_gvm_using_command current' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_using_command c' -a "(__fish_gvm_candidates)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate current' -a "(__fish_gvm_installed_versions)"
complete -c gvm -f -n '__fish_gvm_specifying_candidate c' -a "(__fish_gvm_installed_versions)"

# version
complete -c gvm -f -n '__fish_gvm_no_command' -a 'version' -d 'Display version'
complete -c gvm -f -n '__fish_gvm_no_command' -a 'v' -d 'Display version'
complete -c gvm -f -n '__fish_gvm_using_command version'
complete -c gvm -f -n '__fish_gvm_using_command v'

# broadcast
complete -c gvm -f -n '__fish_gvm_no_command' -a 'broadcast' -d 'Display broadcast message'
complete -c gvm -f -n '__fish_gvm_no_command' -a 'b' -d 'Display broadcast message'
complete -c gvm -f -n '__fish_gvm_using_command broadcast'
complete -c gvm -f -n '__fish_gvm_using_command b'

# help
complete -c gvm -f -n '__fish_gvm_no_command' -a 'help' -d 'Display broadcast message'
complete -c gvm -f -n '__fish_gvm_no_command' -a 'h' -d 'Display broadcast message'
complete -c gvm -f -n '__fish_gvm_using_command help'
complete -c gvm -f -n '__fish_gvm_using_command h'

# offline
complete -c gvm -f -n '__fish_gvm_no_command' -a 'offline' -d 'Set offline status'
complete -c gvm -f -n '__fish_gvm_using_command offline' -a 'enable disable'

# selfupdate
complete -c gvm -f -n '__fish_gvm_no_command' -a 'selfupdate' -d 'Update gvm'
complete -c gvm -f -n '__fish_gvm_using_command selfupdate' -a 'force'

# flush
complete -c gvm -f -n '__fish_gvm_no_command' -a 'flush' -d 'Clear out cache'
complete -c gvm -f -n '__fish_gvm_using_command flush' -a 'candidates broadcast archives temp'