| // 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) |
| } |
| // Create a temporary cache directory and remove everything afterwards. |
| // Based on code from |
| // @io_bazel_rules_go//go/tools/builders:stdliblist.go L174 |
| tempDir, err := os.MkdirTemp("", "gocache") |
| if err != nil { |
| log.Fatalf("Cannot create temporary directory: %v", err) |
| } |
| defer os.RemoveAll(tempDir) |
| |
| cmd.Env = append(cmd.Env, |
| fmt.Sprintf("GOROOT=%s", gorootAbs), |
| fmt.Sprintf("GOPATH=%s", gopathAbs), |
| fmt.Sprintf("PATH=%s", path), |
| fmt.Sprintf("GO111MODULE=off"), |
| fmt.Sprintf("GOCACHE=%s", tempDir), |
| ) |
| |
| // 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) |
| } |
| } |
| } |