xref: /aosp_15_r20/external/swiftshader/tests/regres/llvm/llvm.go (revision 03ce13f70fcc45d86ee91b7ee4cab1936a95046e)
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