| // 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) | 
 | 		} | 
 | 	} | 
 | } |