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  }