Regres: Switch to building with LLVM 10

This is automatically downloaded and cached.
This is another step towards having hermetic builds, and is required for generating code coverage.

Bug: b/152192800
Change-Id: I7c35d92f5844b41ad326e7b11a7725cc6ea9696b
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/43111
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/tests/regres/cmd/regres/main.go b/tests/regres/cmd/regres/main.go
index a74f8cd..d8ad0a0 100644
--- a/tests/regres/cmd/regres/main.go
+++ b/tests/regres/cmd/regres/main.go
@@ -32,6 +32,7 @@
 	"errors"
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"log"
 	"math"
 	"os"
@@ -48,9 +49,11 @@
 	"../../consts"
 	"../../deqp"
 	"../../git"
+	"../../llvm"
 	"../../shell"
 	"../../testlist"
 	"../../util"
+
 	gerrit "github.com/andygrunwald/go-gerrit"
 )
 
@@ -70,6 +73,7 @@
 
 var (
 	numParallelTests = runtime.NumCPU()
+	llvmVersion      = llvm.Version{Major: 10}
 
 	cacheDir      = flag.String("cache", "cache", "path to the output cache directory")
 	gerritEmail   = flag.String("email", "$SS_REGRES_EMAIL", "gerrit email address for posting regres results")
@@ -111,20 +115,84 @@
 }
 
 type regres struct {
-	cmake         string // path to cmake executable
-	make          string // path to make executable
-	python        string // path to python executable
-	cacheRoot     string // path to the regres cache directory
-	gerritEmail   string // gerrit email address used for posting results
-	gerritUser    string // gerrit username used for posting results
-	gerritPass    string // gerrit password used for posting results
-	keepCheckouts bool   // don't delete source & build checkouts after testing
-	dryRun        bool   // don't post any reviews
-	maxProcMemory uint64 // max virtual memory for child processes
-	dailyNow      bool   // start with a daily run
-	dailyOnly     bool   // run only the daily run
-	dailyChange   string // Change hash to use for daily pass, HEAD if not provided
-	priority      string // Prioritize a single change with the given id
+	cmake         string          // path to cmake executable
+	make          string          // path to make executable
+	python        string          // path to python executable
+	tar           string          // path to tar executable
+	cacheRoot     string          // path to the regres cache directory
+	toolchain     *llvm.Toolchain // the LLVM toolchain used to build SwiftShader
+	gerritEmail   string          // gerrit email address used for posting results
+	gerritUser    string          // gerrit username used for posting results
+	gerritPass    string          // gerrit password used for posting results
+	keepCheckouts bool            // don't delete source & build checkouts after testing
+	dryRun        bool            // don't post any reviews
+	maxProcMemory uint64          // max virtual memory for child processes
+	dailyNow      bool            // start with a daily run
+	dailyOnly     bool            // run only the daily run
+	dailyChange   string          // Change hash to use for daily pass, HEAD if not provided
+	priority      string          // Prioritize a single change with the given id
+}
+
+// getToolchain returns the LLVM toolchain, possibly downloading and
+// decompressing it if it wasn't found in the cache directory.
+func getToolchain(tarExe, cacheRoot string) (*llvm.Toolchain, error) {
+	path := filepath.Join(cacheRoot, "llvm")
+
+	if toolchain := llvm.Search(path).Find(llvmVersion); toolchain != nil {
+		return toolchain, nil
+	}
+
+	// LLVM toolchain may have been updated, remove the directory if it exists.
+	os.RemoveAll(path)
+
+	log.Printf("Downloading LLVM %v toolchain...\n", llvmVersion)
+	tar, err := llvmVersion.Download()
+	if err != nil {
+		return nil, fmt.Errorf("Couldn't download LLVM %v: %v", llvmVersion, err)
+	}
+
+	tarFile := filepath.Join(cacheRoot, "llvm.tar.xz")
+	if err := ioutil.WriteFile(tarFile, tar, 0666); err != nil {
+		return nil, fmt.Errorf("Couldn't write '%v': %v", tarFile, err)
+	}
+	defer os.Remove(tarFile)
+
+	log.Printf("Decompressing LLVM %v toolchain...\n", llvmVersion)
+	target := filepath.Join(cacheRoot, "llvm-tmp")
+	os.MkdirAll(target, 0755)
+	defer os.RemoveAll(target)
+	if err := exec.Command(tarExe, "-xf", tarFile, "-C", target).Run(); err != nil {
+		return nil, fmt.Errorf("Couldn't decompress LLVM tar download: %v", err)
+	}
+
+	// The tar, once decompressed, holds a single root directory with a name
+	// starting with 'clang+llvm'. Move this to path.
+	files, err := filepath.Glob(filepath.Join(target, "*"))
+	if err != nil {
+		return nil, fmt.Errorf("Couldn't glob decompressed files: %v", err)
+	}
+	if len(files) != 1 || !util.IsDir(files[0]) {
+		return nil, fmt.Errorf("Unexpected decompressed files: %+v", files)
+	}
+	if err := os.Rename(files[0], path); err != nil {
+		return nil, fmt.Errorf("Couldn't move %v to %v", files[0], path)
+	}
+
+	// We should now have everything in the right place.
+	toolchain := llvm.Search(path).Find(llvmVersion)
+	if toolchain == nil {
+		return nil, fmt.Errorf("Couldn't find LLVM toolchain after downloading")
+	}
+
+	return toolchain, nil
+}
+
+// toolchainEnv() returns the environment variables for executing CMake commands.
+func (r *regres) toolchainEnv() []string {
+	return append([]string{
+		"CC=" + r.toolchain.Clang(),
+		"CXX=" + r.toolchain.ClangXX(),
+	}, os.Environ()...)
 }
 
 // resolveDirs ensures that the necessary directories used can be found, and
@@ -165,6 +233,7 @@
 		{"cmake", &r.cmake},
 		{"make", &r.make},
 		{"python", &r.python},
+		{"tar", &r.tar},
 	} {
 		path, err := exec.LookPath(e.name)
 		if err != nil {
@@ -192,6 +261,12 @@
 		return cause.Wrap(err, "Couldn't resolve all directories")
 	}
 
+	toolchain, err := getToolchain(r.tar, r.cacheRoot)
+	if err != nil {
+		return cause.Wrap(err, "Couldn't download LLVM toolchain")
+	}
+	r.toolchain = toolchain
+
 	client, err := gerrit.NewClient(gerritURL, nil)
 	if err != nil {
 		return cause.Wrap(err, "Couldn't create gerrit client")
@@ -211,10 +286,10 @@
 	for {
 		if now := time.Now(); toDate(now) != lastUpdatedTestLists && now.Hour() >= dailyUpdateTestListHour {
 			lastUpdatedTestLists = toDate(now)
-			if err := r.updateTestLists(client, subzero); err != nil {
+			if err := r.updateTestLists(client, backendSubzero); err != nil {
 				log.Println(err.Error())
 			}
-			if err := r.updateTestLists(client, llvm); err != nil {
+			if err := r.updateTestLists(client, backendLLVM); err != nil {
 				log.Println(err.Error())
 			}
 		}
@@ -820,7 +895,7 @@
 		srcDir:         srcDir,
 		resDir:         resDir,
 		buildDir:       filepath.Join(srcDir, "build"),
-		reactorBackend: llvm,
+		reactorBackend: backendLLVM,
 	}
 }
 
@@ -832,8 +907,8 @@
 type reactorBackend string
 
 const (
-	llvm    reactorBackend = "LLVM"
-	subzero reactorBackend = "Subzero"
+	backendLLVM    reactorBackend = "LLVM"
+	backendSubzero reactorBackend = "Subzero"
 )
 
 type test struct {
@@ -842,6 +917,7 @@
 	srcDir         string         // directory for the SwiftShader checkout
 	resDir         string         // directory for the test results
 	buildDir       string         // directory for SwiftShader build
+	toolchain      llvm.Toolchain // the toolchain used for building
 	reactorBackend reactorBackend // backend for SwiftShader build
 }
 
@@ -896,7 +972,7 @@
 		return cause.Wrap(err, "Failed to create build directory")
 	}
 
-	if err := shell.Shell(buildTimeout, t.r.cmake, t.buildDir,
+	if err := shell.Env(buildTimeout, t.r.cmake, t.buildDir, t.r.toolchainEnv(),
 		"-DCMAKE_BUILD_TYPE=Release",
 		"-DSWIFTSHADER_DCHECK_ALWAYS_ON=1",
 		"-DREACTOR_VERIFY_LLVM_IR=1",
diff --git a/tests/regres/llvm/llvm.go b/tests/regres/llvm/llvm.go
index 8981277..7095824 100644
--- a/tests/regres/llvm/llvm.go
+++ b/tests/regres/llvm/llvm.go
@@ -39,6 +39,10 @@
 	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 {
@@ -138,6 +142,16 @@
 // 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)
@@ -193,6 +207,16 @@
 	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())
diff --git a/tests/regres/shell/shell.go b/tests/regres/shell/shell.go
index 0b6f84c..48c68db 100644
--- a/tests/regres/shell/shell.go
+++ b/tests/regres/shell/shell.go
@@ -31,7 +31,14 @@
 // directory wd.
 // If the process does not finish within timeout a errTimeout will be returned.
 func Shell(timeout time.Duration, exe, wd string, args ...string) error {
-	if out, err := Exec(timeout, exe, wd, nil, args...); err != nil {
+	return Env(timeout, exe, wd, nil, args...)
+}
+
+// Env runs the executable exe with the given arguments, in the working
+// directory wd, with the custom env.
+// If the process does not finish within timeout a errTimeout will be returned.
+func Env(timeout time.Duration, exe, wd string, env []string, args ...string) error {
+	if out, err := Exec(timeout, exe, wd, env, args...); err != nil {
 		return cause.Wrap(err, "%s", out)
 	}
 	return nil