// Copyright 2020 The SwiftShader Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package llvm provides functions and types for locating and using the llvm // toolchains. package llvm import ( "bytes" "fmt" "io/ioutil" "net/http" "os" "os/exec" "path/filepath" "regexp" "runtime" "sort" "strconv" "../util" ) const maxLLVMVersion = 10 // Version holds the build version information of an LLVM toolchain. type Version struct { Major, Minor, Point int } func (v Version) String() string { return fmt.Sprintf("%v.%v.%v", v.Major, v.Minor, v.Point) } // GreaterEqual returns true if v >= rhs. func (v Version) GreaterEqual(rhs Version) bool { if v.Major > rhs.Major { return true } if v.Major < rhs.Major { return false } if v.Minor > rhs.Minor { return true } if v.Minor < rhs.Minor { return false } return v.Point >= rhs.Point } // Download downloads and verifies the LLVM toolchain for the current OS. func (v Version) Download() ([]byte, error) { return v.DownloadForOS(runtime.GOOS) } // DownloadForOS downloads and verifies the LLVM toolchain for the given OS. func (v Version) DownloadForOS(osName string) ([]byte, error) { url, sig, key, err := v.DownloadInfoForOS(osName) if err != nil { return nil, err } resp, err := http.Get(url) if err != nil { return nil, fmt.Errorf("Could not download LLVM from %v: %v", url, err) } defer resp.Body.Close() content, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("Could not download LLVM from %v: %v", url, err) } sigfile, err := os.Open(sig) if err != nil { return nil, fmt.Errorf("Couldn't open file '%s': %v", sig, err) } defer sigfile.Close() keyfile, err := os.Open(key) if err != nil { return nil, fmt.Errorf("Couldn't open file '%s': %v", key, err) } defer keyfile.Close() if err := util.CheckPGP(bytes.NewReader(content), sigfile, keyfile); err != nil { return nil, err } return content, nil } // DownloadInfoForOS returns the download url, signature and key for the given // LLVM version for the given OS. func (v Version) DownloadInfoForOS(os string) (url, sig, key string, err error) { switch v { case Version{10, 0, 0}: key = relfile("10.0.0.pub.key") switch os { case "linux": url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz" sig = relfile("10.0.0-ubuntu.sig") return case "darwin": url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-x86_64-apple-darwin.tar.xz" sig = relfile("10.0.0-darwin.sig") return case "windows": url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/LLVM-10.0.0-win64.exe" sig = relfile("10.0.0-win64.sig") return default: return "", "", "", fmt.Errorf("Unsupported OS: %v", os) } default: return "", "", "", fmt.Errorf("Unknown download for LLVM %v", v) } } func relfile(path string) string { _, thisFile, _, _ := runtime.Caller(1) thisDir := filepath.Dir(thisFile) return filepath.Join(thisDir, path) } // Toolchain holds the paths and version information about an LLVM toolchain. type Toolchain struct { Version Version BinDir string } // Toolchains is a list of Toolchain type Toolchains []Toolchain // Find looks for a toolchain with the specific version. func (l Toolchains) Find(v Version) *Toolchain { for _, t := range l { if t.Version == v { return &t } } return nil } // FindAtLeast looks for a toolchain with the given version, returning the highest found version. func (l Toolchains) FindAtLeast(v Version) *Toolchain { out := (*Toolchain)(nil) for _, t := range l { if t.Version.GreaterEqual(v) && (out == nil || out.Version.GreaterEqual(t.Version)) { t := t out = &t } } return out } // Search looks for llvm toolchains in paths. // If paths is empty, then PATH is searched. func Search(paths ...string) Toolchains { toolchains := map[Version]Toolchain{} search := func(name string) { if len(paths) > 0 { for _, path := range paths { if util.IsFile(path) { path = filepath.Dir(path) } if t := toolchain(path); t != nil { toolchains[t.Version] = *t continue } if t := toolchain(filepath.Join(path, "bin")); t != nil { toolchains[t.Version] = *t continue } } } else { path, err := exec.LookPath(name) if err == nil { if t := toolchain(filepath.Dir(path)); t != nil { toolchains[t.Version] = *t } } } } search("clang") for i := 8; i < maxLLVMVersion; i++ { search(fmt.Sprintf("clang-%d", i)) } out := make([]Toolchain, 0, len(toolchains)) for _, t := range toolchains { out = append(out, t) } sort.Slice(out, func(i, j int) bool { return out[i].Version.GreaterEqual(out[j].Version) }) return out } // Clang returns the path to the clang executable. func (t Toolchain) Clang() string { return filepath.Join(t.BinDir, "clang"+exeExt()) } // ClangXX returns the path to the clang++ executable. func (t Toolchain) ClangXX() string { return filepath.Join(t.BinDir, "clang++"+exeExt()) } // Cov returns the path to the llvm-cov executable. func (t Toolchain) Cov() string { return filepath.Join(t.BinDir, "llvm-cov"+exeExt()) } // Profdata returns the path to the llvm-profdata executable. func (t Toolchain) Profdata() string { return filepath.Join(t.BinDir, "llvm-profdata"+exeExt()) } func toolchain(dir string) *Toolchain { t := Toolchain{BinDir: dir} if t.resolve() { return &t } return nil } func (t *Toolchain) resolve() bool { if !util.IsFile(t.Profdata()) { // llvm-profdata doesn't have --version flag return false } version, ok := parseVersion(t.Cov()) t.Version = version return ok } func exeExt() string { switch runtime.GOOS { case "windows": return ".exe" default: return "" } } var versionRE = regexp.MustCompile(`(?:clang|LLVM) version ([0-9]+)\.([0-9]+)\.([0-9]+)`) func parseVersion(tool string) (Version, bool) { out, err := exec.Command(tool, "--version").Output() if err != nil { return Version{}, false } matches := versionRE.FindStringSubmatch(string(out)) if len(matches) < 4 { return Version{}, false } major, majorErr := strconv.Atoi(matches[1]) minor, minorErr := strconv.Atoi(matches[2]) point, pointErr := strconv.Atoi(matches[3]) if majorErr != nil || minorErr != nil || pointErr != nil { return Version{}, false } return Version{major, minor, point}, true }