1// Copyright 2020 The SwiftShader Authors. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package llvm provides functions and types for locating and using the llvm 16// toolchains. 17package llvm 18 19import ( 20 "bytes" 21 "fmt" 22 "io/ioutil" 23 "net/http" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "regexp" 28 "runtime" 29 "sort" 30 "strconv" 31 32 "../util" 33) 34 35const maxLLVMVersion = 10 36 37// Version holds the build version information of an LLVM toolchain. 38type Version struct { 39 Major, Minor, Point int 40} 41 42func (v Version) String() string { 43 return fmt.Sprintf("%v.%v.%v", v.Major, v.Minor, v.Point) 44} 45 46// GreaterEqual returns true if v >= rhs. 47func (v Version) GreaterEqual(rhs Version) bool { 48 if v.Major > rhs.Major { 49 return true 50 } 51 if v.Major < rhs.Major { 52 return false 53 } 54 if v.Minor > rhs.Minor { 55 return true 56 } 57 if v.Minor < rhs.Minor { 58 return false 59 } 60 return v.Point >= rhs.Point 61} 62 63// Download downloads and verifies the LLVM toolchain for the current OS. 64func (v Version) Download() ([]byte, error) { 65 return v.DownloadForOS(runtime.GOOS) 66} 67 68// DownloadForOS downloads and verifies the LLVM toolchain for the given OS. 69func (v Version) DownloadForOS(osName string) ([]byte, error) { 70 url, sig, key, err := v.DownloadInfoForOS(osName) 71 if err != nil { 72 return nil, err 73 } 74 75 resp, err := http.Get(url) 76 if err != nil { 77 return nil, fmt.Errorf("Could not download LLVM from %v: %v", url, err) 78 } 79 defer resp.Body.Close() 80 81 content, err := ioutil.ReadAll(resp.Body) 82 if err != nil { 83 return nil, fmt.Errorf("Could not download LLVM from %v: %v", url, err) 84 } 85 86 sigfile, err := os.Open(sig) 87 if err != nil { 88 return nil, fmt.Errorf("Couldn't open file '%s': %v", sig, err) 89 } 90 defer sigfile.Close() 91 92 keyfile, err := os.Open(key) 93 if err != nil { 94 return nil, fmt.Errorf("Couldn't open file '%s': %v", key, err) 95 } 96 defer keyfile.Close() 97 98 if err := util.CheckPGP(bytes.NewReader(content), sigfile, keyfile); err != nil { 99 return nil, err 100 } 101 return content, nil 102} 103 104// DownloadInfoForOS returns the download url, signature and key for the given 105// LLVM version for the given OS. 106func (v Version) DownloadInfoForOS(os string) (url, sig, key string, err error) { 107 switch v { 108 case Version{10, 0, 0}: 109 key = relfile("10.0.0.pub.key") 110 switch os { 111 case "linux": 112 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" 113 sig = relfile("10.0.0-ubuntu.sig") 114 return 115 case "darwin": 116 url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-x86_64-apple-darwin.tar.xz" 117 sig = relfile("10.0.0-darwin.sig") 118 return 119 case "windows": 120 url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/LLVM-10.0.0-win64.exe" 121 sig = relfile("10.0.0-win64.sig") 122 return 123 default: 124 return "", "", "", fmt.Errorf("Unsupported OS: %v", os) 125 } 126 default: 127 return "", "", "", fmt.Errorf("Unknown download for LLVM %v", v) 128 } 129} 130func relfile(path string) string { 131 _, thisFile, _, _ := runtime.Caller(1) 132 thisDir := filepath.Dir(thisFile) 133 return filepath.Join(thisDir, path) 134} 135 136// Toolchain holds the paths and version information about an LLVM toolchain. 137type Toolchain struct { 138 Version Version 139 BinDir string 140} 141 142// Toolchains is a list of Toolchain 143type Toolchains []Toolchain 144 145// Find looks for a toolchain with the specific version. 146func (l Toolchains) Find(v Version) *Toolchain { 147 for _, t := range l { 148 if t.Version == v { 149 return &t 150 } 151 } 152 return nil 153} 154 155// FindAtLeast looks for a toolchain with the given version, returning the highest found version. 156func (l Toolchains) FindAtLeast(v Version) *Toolchain { 157 out := (*Toolchain)(nil) 158 for _, t := range l { 159 if t.Version.GreaterEqual(v) && (out == nil || out.Version.GreaterEqual(t.Version)) { 160 t := t 161 out = &t 162 } 163 } 164 return out 165} 166 167// Search looks for llvm toolchains in paths. 168// If paths is empty, then PATH is searched. 169func Search(paths ...string) Toolchains { 170 toolchains := map[Version]Toolchain{} 171 search := func(name string) { 172 if len(paths) > 0 { 173 for _, path := range paths { 174 if util.IsFile(path) { 175 path = filepath.Dir(path) 176 } 177 if t := toolchain(path); t != nil { 178 toolchains[t.Version] = *t 179 continue 180 } 181 if t := toolchain(filepath.Join(path, "bin")); t != nil { 182 toolchains[t.Version] = *t 183 continue 184 } 185 } 186 } else { 187 path, err := exec.LookPath(name) 188 if err == nil { 189 if t := toolchain(filepath.Dir(path)); t != nil { 190 toolchains[t.Version] = *t 191 } 192 } 193 } 194 } 195 196 search("clang") 197 for i := 8; i < maxLLVMVersion; i++ { 198 search(fmt.Sprintf("clang-%d", i)) 199 } 200 201 out := make([]Toolchain, 0, len(toolchains)) 202 for _, t := range toolchains { 203 out = append(out, t) 204 } 205 sort.Slice(out, func(i, j int) bool { return out[i].Version.GreaterEqual(out[j].Version) }) 206 207 return out 208} 209 210// Clang returns the path to the clang executable. 211func (t Toolchain) Clang() string { 212 return filepath.Join(t.BinDir, "clang"+exeExt()) 213} 214 215// ClangXX returns the path to the clang++ executable. 216func (t Toolchain) ClangXX() string { 217 return filepath.Join(t.BinDir, "clang++"+exeExt()) 218} 219 220// Cov returns the path to the llvm-cov executable. 221func (t Toolchain) Cov() string { 222 return filepath.Join(t.BinDir, "llvm-cov"+exeExt()) 223} 224 225// Profdata returns the path to the llvm-profdata executable. 226func (t Toolchain) Profdata() string { 227 return filepath.Join(t.BinDir, "llvm-profdata"+exeExt()) 228} 229 230func toolchain(dir string) *Toolchain { 231 t := Toolchain{BinDir: dir} 232 if t.resolve() { 233 return &t 234 } 235 return nil 236} 237 238func (t *Toolchain) resolve() bool { 239 if !util.IsFile(t.Profdata()) { // llvm-profdata doesn't have --version flag 240 return false 241 } 242 version, ok := parseVersion(t.Cov()) 243 t.Version = version 244 return ok 245} 246 247func exeExt() string { 248 switch runtime.GOOS { 249 case "windows": 250 return ".exe" 251 default: 252 return "" 253 } 254} 255 256var versionRE = regexp.MustCompile(`(?:clang|LLVM) version ([0-9]+)\.([0-9]+)\.([0-9]+)`) 257 258func parseVersion(tool string) (Version, bool) { 259 out, err := exec.Command(tool, "--version").Output() 260 if err != nil { 261 return Version{}, false 262 } 263 matches := versionRE.FindStringSubmatch(string(out)) 264 if len(matches) < 4 { 265 return Version{}, false 266 } 267 major, majorErr := strconv.Atoi(matches[1]) 268 minor, minorErr := strconv.Atoi(matches[2]) 269 point, pointErr := strconv.Atoi(matches[3]) 270 if majorErr != nil || minorErr != nil || pointErr != nil { 271 return Version{}, false 272 } 273 return Version{major, minor, point}, true 274} 275