You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
6.8 KiB
275 lines
6.8 KiB
// 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
|
|
}
|