github.com/delicb/asdf-exec@v0.1.3-0.20220111003559-af5f44250ab7/shim.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "os/exec" 8 "path" 9 "strings" 10 ) 11 12 const ( 13 asdfPluginPrefix string = "# asdf-plugin: " 14 listBinPathScript string = "list-bin-paths" 15 ) 16 17 // Executable is an instance of a single executable 18 type Executable struct { 19 Name string 20 PluginName string 21 PluginVersion string 22 } 23 24 // ParseExecutableLine returns an executable from a shim plugin line 25 func ParseExecutableLine(name string, fullLine string) (Executable, error) { 26 line := strings.Replace(fullLine, asdfPluginPrefix, "", -1) 27 tokens := strings.Split(line, " ") 28 if len(tokens) != 2 { 29 return Executable{}, fmt.Errorf("bad line %s", fullLine) 30 } 31 return Executable{ 32 Name: name, 33 PluginName: strings.TrimSpace(tokens[0]), 34 PluginVersion: strings.TrimSpace(tokens[1]), 35 }, nil 36 } 37 38 // GetExecutablesFromShim retrieves all the executable for a shim 39 func GetExecutablesFromShim(name string, content string) (executables []Executable, err error) { 40 for _, line := range strings.Split(content, "\n") { 41 line = strings.TrimSpace(line) 42 if strings.HasPrefix(line, asdfPluginPrefix) { 43 executable, err := ParseExecutableLine(name, line) 44 if err != nil { 45 return executables, err 46 } 47 executables = append(executables, executable) 48 } 49 } 50 return 51 } 52 53 // GetExecutablesFromShimFile retrieves all the executable for a shim file 54 func GetExecutablesFromShimFile(filepath string) ([]Executable, error) { 55 name := path.Base(filepath) 56 content, err := ioutil.ReadFile(filepath) 57 if err != nil { 58 return []Executable{}, err 59 } 60 return GetExecutablesFromShim(name, string(content)) 61 } 62 63 // FindSystemExecutable returns the path to the system 64 // executable if found 65 func FindSystemExecutable(executableName string) (string, bool) { 66 currentPath := os.Getenv("PATH") 67 defer os.Setenv("PATH", currentPath) 68 os.Setenv("PATH", RemoveAsdfPath(currentPath)) 69 executablePath, err := exec.LookPath(executableName) 70 return executablePath, err == nil 71 } 72 73 // FindExecutable returns the path to the executable to be executed 74 func FindExecutable(executableName string, config Config) (string, bool, error) { 75 shimPath := GetShimPath(executableName) 76 executables, err := GetExecutablesFromShimFile(shimPath) 77 if err != nil { 78 return "", false, err 79 } 80 81 plugins := make(map[string][]Executable) 82 83 for _, executable := range executables { 84 pluginExecutables, ok := plugins[executable.PluginName] 85 if !ok { 86 pluginExecutables = []Executable{} 87 } 88 pluginExecutables = append(pluginExecutables, executable) 89 plugins[executable.PluginName] = pluginExecutables 90 } 91 92 for plugin, pluginExecutables := range plugins { 93 toolVersions, found, err := FindVersions(plugin, config) 94 if err != nil { 95 return "", false, err 96 } 97 if !found { 98 continue 99 } 100 101 for _, toolVersion := range toolVersions { 102 if toolVersion == "system" { 103 if executablePath, found := FindSystemExecutable(executableName); found { 104 return executablePath, true, nil 105 } 106 } 107 for _, executable := range pluginExecutables { 108 if toolVersion == executable.PluginVersion { 109 if executablePath, err := GetExecutablePath(executable); err == nil { 110 return executablePath, true, nil 111 } 112 } 113 } 114 } 115 } 116 117 return "", false, nil 118 } 119 120 // GetShimPath returns the path of the shim 121 func GetShimPath(shimName string) string { 122 return path.Join(GetAsdfDataPath(), "shims", shimName) 123 } 124 125 // GetExecutablePath returns the path of the executable 126 func GetExecutablePath(executable Executable) (string, error) { 127 pluginPath := GetPluginPath(executable.PluginName) 128 installPath := path.Join( 129 GetAsdfDataPath(), 130 "installs", 131 executable.PluginName, 132 executable.PluginVersion, 133 ) 134 135 listBinPath := path.Join(pluginPath, "bin", listBinPathScript) 136 if _, err := os.Stat(listBinPath); err != nil { 137 exePath := path.Join(installPath, "bin", executable.Name) 138 if _, err := os.Stat(exePath); err != nil { 139 return "", fmt.Errorf("executable not found") 140 } 141 return exePath, nil 142 } 143 144 bashPath, err := exec.LookPath("bash") 145 if err != nil { 146 return "", err 147 } 148 cmd := &exec.Cmd{ 149 Path: bashPath, 150 Args: []string{bashPath, listBinPath}, 151 Env: []string{"ASDF_INSTALL_VERSION=2"}, 152 } 153 rawBinPaths, err := cmd.Output() 154 if err != nil { 155 return "", err 156 } 157 paths := strings.Split(string(rawBinPaths), " ") 158 for _, binPath := range paths { 159 binPath = strings.TrimSpace(binPath) 160 exePath := path.Join(installPath, binPath, executable.Name) 161 if _, err := os.Stat(exePath); err == nil { 162 return exePath, nil 163 } 164 } 165 return "", fmt.Errorf("executable not found") 166 }