metropolis/build: add gotoolwrap

This adds gotoolwrap, a tiny Go executable used to wrap binaries that
want to access the monogon workspace as a GOPATH during build steps.

Test Plan: Used further down the stack in code generation.

X-Origin-Diff: phab/D750
GitOrigin-RevId: 83d11b94d025d3652fce88917b1664d93454c60f
diff --git a/metropolis/build/gotoolwrap/main.go b/metropolis/build/gotoolwrap/main.go
new file mode 100644
index 0000000..5b36048
--- /dev/null
+++ b/metropolis/build/gotoolwrap/main.go
@@ -0,0 +1,148 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// gotoolwrap is a tiny wrapper used to run executables that expect a standard
+// Go development environment setup to act on the monogon workspace. Notably,
+// it's used in Bazel rules to run tools like gofmt when invoked by some kinds
+// of code generation tools.
+//
+// Usage: ./gotoolwrap executable arg1 arg2
+//
+// gotoolwrap expects the following environment variables to be set (and unsets
+// them before calling the given executable):
+//  - GOTOOLWRAP_GOPATH: A synthetic GOPATH, eg. one generated by rules_go's
+//  				     go_path target.
+//  - GOTOOLWRAP_GOROOT: A Go SDK's GOROOT, eg. one from rules_go's GoSDK
+//                       provider.
+//
+// gotoolwrap will set PATH to contain GOROOT/bin, and set GOPATH and GOROOT as
+// resolved, absolute paths. Absolute paths are expected by tools like 'gofmt'.
+
+package main
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+)
+
+func main() {
+	gopath := os.Getenv("GOTOOLWRAP_GOPATH")
+	if gopath == "" {
+		log.Fatal("GOTOOLWRAP_GOPATH must be set")
+	}
+
+	goroot := os.Getenv("GOTOOLWRAP_GOROOT")
+	if goroot == "" {
+		log.Fatal("GOTOOLWRAP_GOROOT must be set")
+	}
+
+	if len(os.Args) < 2 {
+		log.Fatalf("No command specified")
+	}
+
+	// Resolve gopath and goroot to absolute paths.
+	gopathAbs, err := filepath.Abs(gopath)
+	if err != nil {
+		log.Fatalf("Abs(%q): %v", gopath, err)
+	}
+	gorootAbs, err := filepath.Abs(goroot)
+	if err != nil {
+		log.Fatalf("Abs(%q): %v", goroot, err)
+	}
+
+	// Ensure the resolved GOROOT has a bin/go and bin/gofmt.
+	gorootBin := filepath.Join(gorootAbs, "bin")
+	stat, err := os.Stat(gorootBin)
+	if err != nil {
+		log.Fatalf("Could not stat $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
+	}
+	if !stat.IsDir() {
+		log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) is not a directory", gorootBin)
+	}
+	// We list all files inside so that we can print them to the user for
+	// debugging purposes if that's not the case.
+	binFiles := make(map[string]bool)
+	files, err := ioutil.ReadDir(gorootBin)
+	if err != nil {
+		log.Fatalf("Could not read dir $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
+	}
+	for _, f := range files {
+		if f.IsDir() {
+			continue
+		}
+		binFiles[f.Name()] = true
+	}
+	if !binFiles["go"] || !binFiles["gofmt"] {
+		msg := "no files"
+		if len(binFiles) > 0 {
+			var names []string
+			for name, _ := range binFiles {
+				names = append(names, fmt.Sprintf("%q", name))
+			}
+			msg = fmt.Sprintf(": %s", strings.Join(names, ", "))
+		}
+		log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) does not contain go and/or gofmt, found %s", gorootBin, msg)
+	}
+
+	// Make new PATH.
+	path := os.Getenv("PATH")
+	if path == "" {
+		path = gorootBin
+	} else {
+		path = fmt.Sprintf("%s:%s", gorootBin, path)
+	}
+
+	cmd := exec.Command(os.Args[1], os.Args[2:]...)
+
+	// Copy current env into command's env, filtering out GOTOOLWRAP env vars
+	// and PATH (which we set ourselves).
+	for _, v := range os.Environ() {
+		if strings.HasPrefix(v, "GOTOOLWRAP_GOROOT=") {
+			continue
+		}
+		if strings.HasPrefix(v, "GOTOOLWRAP_GOPATH=") {
+			continue
+		}
+		if strings.HasPrefix(v, "PATH=") {
+			continue
+		}
+		cmd.Env = append(cmd.Env, v)
+	}
+	cmd.Env = append(cmd.Env,
+		fmt.Sprintf("GOROOT=%s", gorootAbs),
+		fmt.Sprintf("GOPATH=%s", gopathAbs),
+		fmt.Sprintf("PATH=%s", path),
+	)
+
+	// Run the command interactively.
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	cmd.Stdin = os.Stdin
+	if err := cmd.Run(); err != nil {
+		var exitErr *exec.ExitError
+		if errors.As(err, &exitErr) {
+			os.Exit(exitErr.ExitCode())
+		} else {
+			log.Fatalf("Could not run %q: %v", os.Args[1], err)
+		}
+	}
+}