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 "swiftshader.googlesource.com/SwiftShader/tests/regres/util" 33) 34 35const maxLLVMVersion = 17 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 if sig != "" { 87 sigfile, err := os.Open(sig) 88 if err != nil { 89 return nil, fmt.Errorf("Couldn't open file '%s': %v", sig, err) 90 } 91 defer sigfile.Close() 92 93 keyfile, err := os.Open(key) 94 if err != nil { 95 return nil, fmt.Errorf("Couldn't open file '%s': %v", key, err) 96 } 97 defer keyfile.Close() 98 99 if err := util.CheckPGP(bytes.NewReader(content), sigfile, keyfile); err != nil { 100 return nil, err 101 } 102 } 103 return content, nil 104} 105 106// DownloadInfoForOS returns the download url, signature and key for the given 107// LLVM version for the given OS. 108func (v Version) DownloadInfoForOS(os string) (url, sig, key string, err error) { 109 switch v { 110 case Version{10, 0, 0}: 111 key = relfile("10.0.0.pub.key") 112 switch os { 113 case "linux": 114 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" 115 sig = relfile("10.0.0-ubuntu.sig") 116 return 117 case "darwin": 118 url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/clang+llvm-10.0.0-x86_64-apple-darwin.tar.xz" 119 sig = relfile("10.0.0-darwin.sig") 120 return 121 case "windows": 122 url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/LLVM-10.0.0-win64.exe" 123 sig = relfile("10.0.0-win64.sig") 124 return 125 default: 126 return "", "", "", fmt.Errorf("Unsupported OS: %v", os) 127 } 128 case Version{17, 0, 6}: 129 switch os { 130 case "linux": 131 url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-17.0.6/clang+llvm-17.0.6-x86_64-linux-gnu-ubuntu-22.04.tar.xz" 132 return 133 case "darwin": 134 url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-17.0.6/clang+llvm-17.0.6-arm64-apple-darwin22.0.tar.xz" 135 return 136 case "windows": 137 url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-17.0.6/LLVM-17.0.6-win64.exe" 138 return 139 default: 140 return "", "", "", fmt.Errorf("Unsupported OS: %v", os) 141 } 142 default: 143 return "", "", "", fmt.Errorf("Unknown download for LLVM %v", v) 144 } 145} 146func relfile(path string) string { 147 _, thisFile, _, _ := runtime.Caller(1) 148 thisDir := filepath.Dir(thisFile) 149 return filepath.Join(thisDir, path) 150} 151 152// Toolchain holds the paths and version information about an LLVM toolchain. 153type Toolchain struct { 154 Version Version 155 BinDir string 156} 157 158// Toolchains is a list of Toolchain 159type Toolchains []Toolchain 160 161// Find looks for a toolchain with the specific version. 162func (l Toolchains) Find(v Version) *Toolchain { 163 for _, t := range l { 164 if t.Version == v { 165 return &t 166 } 167 } 168 return nil 169} 170 171// FindAtLeast looks for a toolchain with the given version, returning the highest found version. 172func (l Toolchains) FindAtLeast(v Version) *Toolchain { 173 out := (*Toolchain)(nil) 174 for _, t := range l { 175 if t.Version.GreaterEqual(v) && (out == nil || out.Version.GreaterEqual(t.Version)) { 176 t := t 177 out = &t 178 } 179 } 180 return out 181} 182 183// Search looks for llvm toolchains in paths. 184// If paths is empty, then PATH is searched. 185func Search(paths ...string) Toolchains { 186 toolchains := map[Version]Toolchain{} 187 search := func(name string) { 188 if len(paths) > 0 { 189 for _, path := range paths { 190 if util.IsFile(path) { 191 path = filepath.Dir(path) 192 } 193 if t := toolchain(path); t != nil { 194 toolchains[t.Version] = *t 195 continue 196 } 197 if t := toolchain(filepath.Join(path, "bin")); t != nil { 198 toolchains[t.Version] = *t 199 continue 200 } 201 } 202 } else { 203 path, err := exec.LookPath(name) 204 if err == nil { 205 if t := toolchain(filepath.Dir(path)); t != nil { 206 toolchains[t.Version] = *t 207 } 208 } 209 } 210 } 211 212 search("clang") 213 for i := 8; i < maxLLVMVersion; i++ { 214 search(fmt.Sprintf("clang-%d", i)) 215 } 216 217 out := make([]Toolchain, 0, len(toolchains)) 218 for _, t := range toolchains { 219 out = append(out, t) 220 } 221 sort.Slice(out, func(i, j int) bool { return out[i].Version.GreaterEqual(out[j].Version) }) 222 223 return out 224} 225 226// Clang returns the path to the clang executable. 227func (t Toolchain) Clang() string { 228 return filepath.Join(t.BinDir, "clang"+exeExt()) 229} 230 231// ClangXX returns the path to the clang++ executable. 232func (t Toolchain) ClangXX() string { 233 return filepath.Join(t.BinDir, "clang++"+exeExt()) 234} 235 236// Cov returns the path to the llvm-cov executable. 237func (t Toolchain) Cov() string { 238 return filepath.Join(t.BinDir, "llvm-cov"+exeExt()) 239} 240 241// Profdata returns the path to the llvm-profdata executable. 242func (t Toolchain) Profdata() string { 243 return filepath.Join(t.BinDir, "llvm-profdata"+exeExt()) 244} 245 246func toolchain(dir string) *Toolchain { 247 t := Toolchain{BinDir: dir} 248 if t.resolve() { 249 return &t 250 } 251 return nil 252} 253 254func (t *Toolchain) resolve() bool { 255 if !util.IsFile(t.Profdata()) { // llvm-profdata doesn't have --version flag 256 return false 257 } 258 version, ok := parseVersion(t.Cov()) 259 t.Version = version 260 return ok 261} 262 263func exeExt() string { 264 switch runtime.GOOS { 265 case "windows": 266 return ".exe" 267 default: 268 return "" 269 } 270} 271 272var versionRE = regexp.MustCompile(`(?:clang|LLVM) version ([0-9]+)\.([0-9]+)\.([0-9]+)`) 273 274func parseVersion(tool string) (Version, bool) { 275 out, err := exec.Command(tool, "--version").Output() 276 if err != nil { 277 return Version{}, false 278 } 279 matches := versionRE.FindStringSubmatch(string(out)) 280 if len(matches) < 4 { 281 return Version{}, false 282 } 283 major, majorErr := strconv.Atoi(matches[1]) 284 minor, minorErr := strconv.Atoi(matches[2]) 285 point, pointErr := strconv.Atoi(matches[3]) 286 if majorErr != nil || minorErr != nil || pointErr != nil { 287 return Version{}, false 288 } 289 return Version{major, minor, point}, true 290} 291