cmd/buildlet: support Windows ARM64 qemu guests

The Windows ARM builders on EC2 run on qemu via KVM. We use a userspace
network device for simplicity, which also conveniently restricts which
ports are exposed. In order to make a request to the metatadata service,
which is routed in a different way than the public internet, we
explicitly forward the port to the guest VM on a special ip/port.

This introduces a hard-coded value for Windows ARM64 buildlets. We
should be able to improve this code to detect it, but this will get the
buildlet unblocked.

The buildlet image also includes llvm-arm64 mingw rather than a gcc
based distribution. This change also adds the correct directory to the
path.

For golang/go#42604

Change-Id: Ife2ebb900a08034d6e0dfa0982a24b312ee6d70a
Reviewed-on: https://go-review.googlesource.com/c/build/+/322653
Trust: Alexander Rakoczy <alex@golang.org>
Run-TryBot: Alexander Rakoczy <alex@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
diff --git a/cmd/buildlet/buildlet.go b/cmd/buildlet/buildlet.go
index 9ccb554..02a54a5 100644
--- a/cmd/buildlet/buildlet.go
+++ b/cmd/buildlet/buildlet.go
@@ -41,6 +41,7 @@
 	"time"
 
 	"cloud.google.com/go/compute/metadata"
+	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/ec2metadata"
 	"github.com/aws/aws-sdk-go/aws/session"
 	"golang.org/x/build/buildlet"
@@ -334,12 +335,18 @@
 	if ec2MdC != nil {
 		return ec2MdC.Available()
 	}
-	ses, err := session.NewSession()
+	cfg := aws.NewConfig()
+	// TODO(golang/go#42604) - Improve detection of our qemu forwarded
+	// metadata service for Windows ARM VMs running on EC2.
+	if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" {
+		cfg = cfg.WithEndpoint("http://10.0.2.100:8173/latest")
+	}
+	ses, err := session.NewSession(cfg)
 	if err != nil {
 		log.Printf("unable to create aws session: %s", err)
 		return false
 	}
-	ec2MdC = ec2metadata.New(ses)
+	ec2MdC = ec2metadata.New(ses, cfg)
 	return ec2MdC.Available()
 }
 
@@ -1123,11 +1130,10 @@
 func windowsBaseEnv(goarch string) (e []string) {
 	e = append(e, "GOBUILDEXIT=1") // exit all.bat with completion status
 
-	is64 := goarch != "386"
 	for _, pair := range os.Environ() {
 		const pathEq = "PATH="
 		if hasPrefixFold(pair, pathEq) {
-			e = append(e, "PATH="+windowsPath(pair[len(pathEq):], is64))
+			e = append(e, "PATH="+windowsPath(pair[len(pathEq):], goarch))
 		} else {
 			e = append(e, pair)
 		}
@@ -1143,15 +1149,18 @@
 // windowsPath cleans the windows %PATH% environment.
 // is64Bit is whether this is a windows-amd64-* builder.
 // The PATH is assumed to be that of the image described in env/windows/README.
-func windowsPath(old string, is64Bit bool) string {
+func windowsPath(old string, goarch string) string {
 	vv := filepath.SplitList(old)
 	newPath := make([]string, 0, len(vv))
+	is64Bit := goarch != "386"
 
 	// for windows-buildlet-v2 images
 	for _, v := range vv {
 		// The base VM image has both the 32-bit and 64-bit gcc installed.
 		// They're both in the environment, so scrub the one
 		// we don't want (TDM-GCC-64 or TDM-GCC-32).
+		//
+		// This is not present in arm64 images.
 		if strings.Contains(v, "TDM-GCC-") {
 			gcc64 := strings.Contains(v, "TDM-GCC-64")
 			if is64Bit != gcc64 {
@@ -1161,11 +1170,13 @@
 		newPath = append(newPath, v)
 	}
 
-	// for windows-amd64-* images
-	if is64Bit {
-		newPath = append(newPath, `C:\godep\gcc64\bin`)
-	} else {
+	switch goarch {
+	case "arm64":
+		newPath = append(newPath, `C:\godep\llvm-aarch64\bin`)
+	case "386":
 		newPath = append(newPath, `C:\godep\gcc32\bin`)
+	default:
+		newPath = append(newPath, `C:\godep\gcc64\bin`)
 	}
 
 	return strings.Join(newPath, string(filepath.ListSeparator))
@@ -1227,6 +1238,11 @@
 		} else {
 			err = errors.New("not respecting -halt flag on macOS in unknown environment")
 		}
+	case "windows":
+		err = errors.New("not respsecting -halt flag on windows in unknown environment")
+		if runtime.GOARCH == "arm64" {
+			err = exec.Command("shutdown /s").Run()
+		}
 	default:
 		err = errors.New("no system-specific halt command run; will just end buildlet process")
 	}