diff --git a/cmd/cmd.go b/cmd/cmd.go index 9f4de841..bb9b9966 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -17,6 +17,7 @@ import ( "asdf/internal/execenv" "asdf/internal/execute" "asdf/internal/help" + "asdf/internal/hook" "asdf/internal/info" "asdf/internal/installs" "asdf/internal/plugins" @@ -453,6 +454,12 @@ func execCommand(logger *log.Logger, command string, args []string) error { env = execenv.MergeEnv(execenv.SliceToMap(os.Environ()), env) + err = hook.RunWithOutput(conf, fmt.Sprintf("pre_%s_%s", plugin.Name, filepath.Base(executable)), args, os.Stdout, os.Stderr) + if err != nil { + os.Exit(1) + return err + } + return exec.Exec(executable, args, execute.MapToSlice(env)) } diff --git a/docs/guide/upgrading-from-v0-14-to-v0-15.md b/docs/guide/upgrading-from-v0-14-to-v0-15.md index e7288c15..a229dd87 100644 --- a/docs/guide/upgrading-from-v0-14-to-v0-15.md +++ b/docs/guide/upgrading-from-v0-14-to-v0-15.md @@ -57,6 +57,17 @@ In practice this isn't much of a problem. Most shell scripts DO contain a shebang line. If a tool managed by asdf provides scripts that don't have a shebang line one will need to be added to them. +### Custom shim templates are no longer supported + +This was a rarely used feature. The only plugin maintained by the core team +that used it was the Elixir plugin, and it no longer needs it. This feature +was originally added so that shim that get evaluated by a program rather than +executed contain code that is suitable for evaluation by a particular program +(in the case of Elixir this was the `iex` shell). Upon further investigation +it seems this feature only exists because the `PATH` for executables was +sometimes improperly set to include the **shims** rather than the other +**executables** for the selected version(s). + ## Installation Installation of version 0.15.0 is much simpler than previous versions of asdf. It's just three steps: diff --git a/internal/shims/shims.go b/internal/shims/shims.go index 837eab4f..217d8375 100644 --- a/internal/shims/shims.go +++ b/internal/shims/shims.go @@ -160,23 +160,28 @@ func ExecutableOnPath(path, command string) (string, error) { // GetExecutablePath returns the path of the executable func GetExecutablePath(conf config.Config, plugin plugins.Plugin, shimName string, version toolversions.Version) (string, error) { - path, err := getCustomExecutablePath(conf, plugin, shimName, version) - if err == nil { - return path, err - } - executables, err := ToolExecutables(conf, plugin, version) if err != nil { return "", err } + executable := "" + for _, executablePath := range executables { - executableName := filepath.Base(executablePath) - if executableName == shimName { - return executablePath, nil + if filepath.Base(executablePath) == shimName { + executable = executablePath } } + path, err := getCustomExecutablePath(conf, plugin, shimName, version, executable) + if err == nil { + return path, err + } + + if executable != "" { + return executable, nil + } + return "", fmt.Errorf("executable not found") } @@ -195,19 +200,24 @@ func GetToolsAndVersionsFromShimFile(shimPath string) (versions []toolversions.T return versions, err } -func getCustomExecutablePath(conf config.Config, plugin plugins.Plugin, shimName string, version toolversions.Version) (string, error) { +func getCustomExecutablePath(conf config.Config, plugin plugins.Plugin, shimName string, version toolversions.Version, executablePath string) (string, error) { var stdOut strings.Builder var stdErr strings.Builder installPath := installs.InstallPath(conf, plugin, version) env := map[string]string{"ASDF_INSTALL_TYPE": "version"} - err := plugin.RunCallback("exec-path", []string{installPath, shimName}, env, &stdOut, &stdErr) + relativePath, err := filepath.Rel(installPath, executablePath) if err != nil { return "", err } - return filepath.Join(installPath, stdOut.String()), err + err = plugin.RunCallback("exec-path", []string{installPath, shimName, relativePath}, env, &stdOut, &stdErr) + if err != nil { + return "", err + } + + return filepath.Join(installPath, strings.TrimSpace(stdOut.String())), err } // RemoveAll removes all shim scripts diff --git a/internal/shims/shims_test.go b/internal/shims/shims_test.go index cc790076..d96d43a8 100644 --- a/internal/shims/shims_test.go +++ b/internal/shims/shims_test.go @@ -121,11 +121,26 @@ func TestGetExecutablePath(t *testing.T) { t.Run("returns custom path when plugin has exec-path callback", func(t *testing.T) { // Create exec-path callback - installDummyExecPathScript(t, conf, plugin, version, "dummy") + installDummyExecPathScript(t, conf, plugin, version, "dummy", "echo 'bin/custom/dummy'") path, err := GetExecutablePath(conf, plugin, "dummy", version) assert.Nil(t, err) assert.Equal(t, filepath.Base(filepath.Dir(path)), "custom") + // Doesn't contain any trailing whitespace (newlines as the last char are common) + assert.Equal(t, path, strings.TrimSpace(path)) + }) + + t.Run("returns default path when plugin has exec-path callback that prints third argument", func(t *testing.T) { + // Create exec-path callback + installDummyExecPathScript(t, conf, plugin, version, "dummy", "echo \"$3\"") + + path, err := GetExecutablePath(conf, plugin, "dummy", version) + assert.Nil(t, err) + assert.Equal(t, filepath.Base(path), "dummy") + assert.Equal(t, filepath.Base(filepath.Dir(path)), "bin") + + // Doesn't contain any trailing whitespace (newlines as the last char are common) + assert.Equal(t, path, strings.TrimSpace(path)) }) } @@ -426,10 +441,10 @@ func generateConfig(t *testing.T) (config.Config, plugins.Plugin) { return conf, installPlugin(t, conf, "dummy_plugin", testPluginName) } -func installDummyExecPathScript(t *testing.T, conf config.Config, plugin plugins.Plugin, version toolversions.Version, name string) { +func installDummyExecPathScript(t *testing.T, conf config.Config, plugin plugins.Plugin, version toolversions.Version, name, script string) { t.Helper() execPath := filepath.Join(plugin.Dir, "bin", "exec-path") - contents := fmt.Sprintf("#!/usr/bin/env bash\necho 'bin/custom/%s'", name) + contents := fmt.Sprintf("#!/usr/bin/env bash\n%s\n", script) err := os.WriteFile(execPath, []byte(contents), 0o777) assert.Nil(t, err) diff --git a/main_test.go b/main_test.go index d13b798d..1fa57c58 100644 --- a/main_test.go +++ b/main_test.go @@ -79,9 +79,9 @@ func TestBatsTests(t *testing.T) { runBatsFile(t, dir, "shim_env_command.bats") }) - //t.Run("shim_exec", func(t *testing.T) { - // runBatsFile(t, dir, "shim_exec.bats") - //}) + t.Run("shim_exec", func(t *testing.T) { + runBatsFile(t, dir, "shim_exec.bats") + }) t.Run("shim_versions_command", func(t *testing.T) { runBatsFile(t, dir, "shim_versions_command.bats") diff --git a/test/shim_exec.bats b/test/shim_exec.bats index 284782c5..28eb9c8e 100644 --- a/test/shim_exec.bats +++ b/test/shim_exec.bats @@ -243,91 +243,96 @@ teardown() { [ "$output" = "System" ] } -@test "shim exec should use custom exec-env for tool" { - run asdf install dummy 2.0.0 - echo "export FOO=sourced" >"$ASDF_DIR/plugins/dummy/bin/exec-env" - mkdir "$ASDF_DIR/plugins/dummy/shims" - echo 'echo $FOO custom' >"$ASDF_DIR/plugins/dummy/shims/foo" - chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" - run asdf reshim dummy 2.0.0 +# These tests are disabled because the custom shims templates feature is no +# longer supported. +# +#@test "shim exec should use custom exec-env for tool" { +# run asdf install dummy 2.0.0 +# echo '#!/usr/bin/env bash +# export FOO=sourced' >"$ASDF_DIR/plugins/dummy/bin/exec-env" +# mkdir "$ASDF_DIR/plugins/dummy/shims" +# echo '#!/usr/bin/env bash +# echo $FOO custom' >"$ASDF_DIR/plugins/dummy/shims/foo" +# chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" +# run asdf reshim dummy 2.0.0 - echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" - run "$ASDF_DIR/shims/foo" - [ "$output" = "sourced custom" ] -} +# echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" +# run "$ASDF_DIR/shims/foo" +# [ "$output" = "sourced custom" ] +#} -@test "shim exec with custom exec-env using ASDF_INSTALL_PATH" { - run asdf install dummy 2.0.0 - echo 'export FOO=$ASDF_INSTALL_PATH/foo' >"$ASDF_DIR/plugins/dummy/bin/exec-env" - mkdir "$ASDF_DIR/plugins/dummy/shims" - echo 'echo $FOO custom' >"$ASDF_DIR/plugins/dummy/shims/foo" - chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" - run asdf reshim dummy 2.0.0 +#@test "shim exec with custom exec-env using ASDF_INSTALL_PATH" { +# run asdf install dummy 2.0.0 +# echo 'export FOO=$ASDF_INSTALL_PATH/foo' >"$ASDF_DIR/plugins/dummy/bin/exec-env" +# mkdir "$ASDF_DIR/plugins/dummy/shims" +# echo 'echo $FOO custom' >"$ASDF_DIR/plugins/dummy/shims/foo" +# chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" +# run asdf reshim dummy 2.0.0 - echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" - run "$ASDF_DIR/shims/foo" - [ "$output" = "$ASDF_DIR/installs/dummy/2.0.0/foo custom" ] -} +# echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" +# run "$ASDF_DIR/shims/foo" +# [ "$output" = "$ASDF_DIR/installs/dummy/2.0.0/foo custom" ] +#} -@test "shim exec doest not use custom exec-env for system version" { - run asdf install dummy 2.0.0 - echo "export FOO=sourced" >"$ASDF_DIR/plugins/dummy/bin/exec-env" - mkdir "$ASDF_DIR/plugins/dummy/shims" - echo 'echo $FOO custom' >"$ASDF_DIR/plugins/dummy/shims/foo" - chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" - run asdf reshim dummy 2.0.0 +#@test "shim exec doest not use custom exec-env for system version" { +# run asdf install dummy 2.0.0 +# echo "export FOO=sourced" >"$ASDF_DIR/plugins/dummy/bin/exec-env" +# mkdir "$ASDF_DIR/plugins/dummy/shims" +# echo 'echo $FOO custom' >"$ASDF_DIR/plugins/dummy/shims/foo" +# chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" +# run asdf reshim dummy 2.0.0 - echo "dummy system" >"$PROJECT_DIR/.tool-versions" +# echo "dummy system" >"$PROJECT_DIR/.tool-versions" - mkdir "$PROJECT_DIR/sys/" - echo 'echo x$FOO System' >"$PROJECT_DIR/sys/foo" - chmod +x "$PROJECT_DIR/sys/foo" +# mkdir "$PROJECT_DIR/sys/" +# echo 'echo x$FOO System' >"$PROJECT_DIR/sys/foo" +# chmod +x "$PROJECT_DIR/sys/foo" - run env "PATH=$PATH:$PROJECT_DIR/sys" "$ASDF_DIR/shims/foo" - [ "$output" = "x System" ] -} +# run env "PATH=$PATH:$PROJECT_DIR/sys" "$ASDF_DIR/shims/foo" +# [ "$output" = "x System" ] +#} -@test "shim exec should prepend the plugin paths on execution" { - run asdf install dummy 2.0.0 +#@test "shim exec should prepend the plugin paths on execution" { +# run asdf install dummy 2.0.0 - mkdir "$ASDF_DIR/plugins/dummy/shims" - echo 'which dummy' >"$ASDF_DIR/plugins/dummy/shims/foo" - chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" - run asdf reshim dummy 2.0.0 +# mkdir "$ASDF_DIR/plugins/dummy/shims" +# echo 'which dummy' >"$ASDF_DIR/plugins/dummy/shims/foo" +# chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" +# run asdf reshim dummy 2.0.0 - echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" +# echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" - run "$ASDF_DIR/shims/foo" - [ "$output" = "$ASDF_DIR/installs/dummy/2.0.0/bin/dummy" ] -} +# run "$ASDF_DIR/shims/foo" +# [ "$output" = "$ASDF_DIR/installs/dummy/2.0.0/bin/dummy" ] +#} -@test "shim exec should be able to find other shims in path" { - cp -rf "$ASDF_DIR/plugins/dummy" "$ASDF_DIR/plugins/gummy" +#@test "shim exec should be able to find other shims in path" { +# cp -rf "$ASDF_DIR/plugins/dummy" "$ASDF_DIR/plugins/gummy" - echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" - echo "gummy 2.0.0" >>"$PROJECT_DIR/.tool-versions" +# echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" +# echo "gummy 2.0.0" >>"$PROJECT_DIR/.tool-versions" - run asdf install +# run asdf install - mkdir "$ASDF_DIR/plugins/"{dummy,gummy}/shims +# mkdir "$ASDF_DIR/plugins/"{dummy,gummy}/shims - echo 'which dummy' >"$ASDF_DIR/plugins/dummy/shims/foo" - chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" +# echo 'which dummy' >"$ASDF_DIR/plugins/dummy/shims/foo" +# chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" - echo 'which gummy' >"$ASDF_DIR/plugins/dummy/shims/bar" - chmod +x "$ASDF_DIR/plugins/dummy/shims/bar" +# echo 'which gummy' >"$ASDF_DIR/plugins/dummy/shims/bar" +# chmod +x "$ASDF_DIR/plugins/dummy/shims/bar" - touch "$ASDF_DIR/plugins/gummy/shims/gummy" - chmod +x "$ASDF_DIR/plugins/gummy/shims/gummy" +# touch "$ASDF_DIR/plugins/gummy/shims/gummy" +# chmod +x "$ASDF_DIR/plugins/gummy/shims/gummy" - run asdf reshim +# run asdf reshim - run "$ASDF_DIR/shims/foo" - [ "$output" = "$ASDF_DIR/installs/dummy/2.0.0/bin/dummy" ] +# run "$ASDF_DIR/shims/foo" +# [ "$output" = "$ASDF_DIR/installs/dummy/2.0.0/bin/dummy" ] - run "$ASDF_DIR/shims/bar" - [ "$output" = "$ASDF_DIR/shims/gummy" ] -} +# run "$ASDF_DIR/shims/bar" +# [ "$output" = "$ASDF_DIR/shims/gummy" ] +#} @test "shim exec should remove shim_path from path on system version execution" { run asdf install dummy 2.0.0 @@ -335,7 +340,8 @@ teardown() { echo "dummy system" >"$PROJECT_DIR/.tool-versions" mkdir "$PROJECT_DIR/sys/" - echo 'which dummy' >"$PROJECT_DIR/sys/dummy" + echo '#!/usr/bin/env bash + which dummy' >"$PROJECT_DIR/sys/dummy" chmod +x "$PROJECT_DIR/sys/dummy" run env "PATH=$PATH:$PROJECT_DIR/sys" "$ASDF_DIR/shims/dummy" @@ -369,7 +375,8 @@ teardown() { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" mkdir "$custom_path" - echo "echo CUSTOM" >"$custom_path/foo" + echo '#!/usr/bin/env bash + echo CUSTOM' >"$custom_path/foo" chmod +x "$custom_path/foo" run asdf reshim dummy 1.0 @@ -384,11 +391,13 @@ teardown() { exec_path="$ASDF_DIR/plugins/dummy/bin/exec-path" custom_dummy="$ASDF_DIR/installs/dummy/1.0/custom/dummy" - echo "echo custom/dummy" >"$exec_path" + echo '#!/usr/bin/env bash + echo custom/dummy' >"$exec_path" chmod +x "$exec_path" mkdir "$(dirname "$custom_dummy")" - echo "echo CUSTOM" >"$custom_dummy" + echo '#!/usr/bin/env bash + echo CUSTOM' >"$custom_dummy" chmod +x "$custom_dummy" echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" @@ -401,7 +410,8 @@ teardown() { run asdf install dummy 1.0 exec_path="$ASDF_DIR/plugins/dummy/bin/exec-path" - echo 'echo $3 # always same path' >"$exec_path" + echo '#!/usr/bin/env bash + echo "$3" # always same path' >"$exec_path" chmod +x "$exec_path" echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" @@ -415,12 +425,12 @@ teardown() { echo dummy 1.0 >"$PROJECT_DIR/.tool-versions" cat >"$HOME/.asdfrc" <<-'EOM' -pre_dummy_dummy = echo PRE $version $1 $2 +pre_dummy_dummy = echo PRE $1 $2 EOM run "$ASDF_DIR/shims/dummy" hello world [ "$status" -eq 0 ] - echo "$output" | grep "PRE 1.0 hello world" + echo "$output" | grep "PRE hello world" echo "$output" | grep "This is Dummy 1.0! world hello" } @@ -430,15 +440,16 @@ EOM mkdir "$HOME/hook" pre_cmd="$HOME/hook/pre" - echo 'echo $* && false' >"$pre_cmd" + echo '#!/usr/bin/env bash + echo $* && false' >"$pre_cmd" chmod +x "$pre_cmd" cat >"$HOME/.asdfrc" <<'EOM' -pre_dummy_dummy = pre $1 no $plugin_name $2 +pre_dummy_dummy = pre $1 $2 EOM run env PATH="$PATH:$HOME/hook" "$ASDF_DIR/shims/dummy" hello world - [ "$output" = "hello no dummy world" ] + [ "$output" = "hello world" ] [ "$status" -eq 1 ] }