github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/builder/commands.go (about)

     1  package builder
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"runtime"
     9  	"strings"
    10  
    11  	"tinygo.org/x/go-llvm"
    12  )
    13  
    14  // Commands lists command alternatives for various operating systems. These
    15  // commands may have a slightly different name across operating systems and
    16  // distributions or may not even exist in $PATH, in which case absolute paths
    17  // may be used.
    18  var commands = map[string][]string{}
    19  
    20  func init() {
    21  	llvmMajor := strings.Split(llvm.Version, ".")[0]
    22  	commands["clang"] = []string{"clang-" + llvmMajor}
    23  	commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
    24  	commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
    25  	commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"}
    26  	// Add the path to a Homebrew-installed LLVM for ease of use (no need to
    27  	// manually set $PATH).
    28  	if runtime.GOOS == "darwin" {
    29  		var prefix string
    30  		switch runtime.GOARCH {
    31  		case "amd64":
    32  			prefix = "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
    33  		case "arm64":
    34  			prefix = "/opt/homebrew/opt/llvm@" + llvmMajor + "/bin/"
    35  		default:
    36  			// unknown GOARCH
    37  			panic(fmt.Sprintf("unknown GOARCH: %s on darwin", runtime.GOARCH))
    38  		}
    39  		commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
    40  		commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
    41  		commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
    42  		commands["lldb"] = append(commands["lldb"], prefix+"lldb")
    43  	}
    44  	// Add the path for when LLVM was installed with the installer from
    45  	// llvm.org, which by default doesn't add LLVM to the $PATH environment
    46  	// variable.
    47  	if runtime.GOOS == "windows" {
    48  		commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
    49  		commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
    50  		commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
    51  		commands["lldb"] = append(commands["lldb"], "C:\\Program Files\\LLVM\\bin\\lldb.exe")
    52  	}
    53  	// Add the path to LLVM installed from ports.
    54  	if runtime.GOOS == "freebsd" {
    55  		prefix := "/usr/local/llvm" + llvmMajor + "/bin/"
    56  		commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
    57  		commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
    58  		commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
    59  		commands["lldb"] = append(commands["lldb"], prefix+"lldb")
    60  	}
    61  }
    62  
    63  // LookupCommand looks up the executable name for a given LLVM tool such as
    64  // clang or wasm-ld. It returns the (relative) command that can be used to
    65  // invoke the tool or an error if it could not be found.
    66  func LookupCommand(name string) (string, error) {
    67  	for _, cmdName := range commands[name] {
    68  		_, err := exec.LookPath(cmdName)
    69  		if err != nil {
    70  			if errors.Unwrap(err) == exec.ErrNotFound {
    71  				continue
    72  			}
    73  			return cmdName, err
    74  		}
    75  		return cmdName, nil
    76  	}
    77  	return "", errors.New("none of these commands were found in your $PATH: " + strings.Join(commands[name], " "))
    78  }
    79  
    80  func execCommand(name string, args ...string) error {
    81  	name, err := LookupCommand(name)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	cmd := exec.Command(name, args...)
    86  	cmd.Stdout = os.Stdout
    87  	cmd.Stderr = os.Stderr
    88  	return cmd.Run()
    89  }