blob: eb70884265c5a85535f0da584f20679d39eaa08b [file] [log] [blame]
Serge Bazanskib76580c2021-03-31 22:07:01 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17// gotoolwrap is a tiny wrapper used to run executables that expect a standard
18// Go development environment setup to act on the monogon workspace. Notably,
19// it's used in Bazel rules to run tools like gofmt when invoked by some kinds
20// of code generation tools.
21//
22// Usage: ./gotoolwrap executable arg1 arg2
23//
24// gotoolwrap expects the following environment variables to be set (and unsets
25// them before calling the given executable):
26// - GOTOOLWRAP_GOPATH: A synthetic GOPATH, eg. one generated by rules_go's
27// go_path target.
28// - GOTOOLWRAP_GOROOT: A Go SDK's GOROOT, eg. one from rules_go's GoSDK
29// provider.
30//
31// gotoolwrap will set PATH to contain GOROOT/bin, and set GOPATH and GOROOT as
32// resolved, absolute paths. Absolute paths are expected by tools like 'gofmt'.
33
34package main
35
36import (
37 "errors"
38 "fmt"
39 "io/ioutil"
40 "log"
41 "os"
42 "os/exec"
43 "path/filepath"
44 "strings"
45)
46
47func main() {
48 gopath := os.Getenv("GOTOOLWRAP_GOPATH")
49 if gopath == "" {
50 log.Fatal("GOTOOLWRAP_GOPATH must be set")
51 }
52
53 goroot := os.Getenv("GOTOOLWRAP_GOROOT")
54 if goroot == "" {
55 log.Fatal("GOTOOLWRAP_GOROOT must be set")
56 }
57
58 if len(os.Args) < 2 {
59 log.Fatalf("No command specified")
60 }
61
62 // Resolve gopath and goroot to absolute paths.
63 gopathAbs, err := filepath.Abs(gopath)
64 if err != nil {
65 log.Fatalf("Abs(%q): %v", gopath, err)
66 }
67 gorootAbs, err := filepath.Abs(goroot)
68 if err != nil {
69 log.Fatalf("Abs(%q): %v", goroot, err)
70 }
71
72 // Ensure the resolved GOROOT has a bin/go and bin/gofmt.
73 gorootBin := filepath.Join(gorootAbs, "bin")
74 stat, err := os.Stat(gorootBin)
75 if err != nil {
76 log.Fatalf("Could not stat $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
77 }
78 if !stat.IsDir() {
79 log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) is not a directory", gorootBin)
80 }
81 // We list all files inside so that we can print them to the user for
82 // debugging purposes if that's not the case.
83 binFiles := make(map[string]bool)
84 files, err := ioutil.ReadDir(gorootBin)
85 if err != nil {
86 log.Fatalf("Could not read dir $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
87 }
88 for _, f := range files {
89 if f.IsDir() {
90 continue
91 }
92 binFiles[f.Name()] = true
93 }
94 if !binFiles["go"] || !binFiles["gofmt"] {
95 msg := "no files"
96 if len(binFiles) > 0 {
97 var names []string
98 for name, _ := range binFiles {
99 names = append(names, fmt.Sprintf("%q", name))
100 }
101 msg = fmt.Sprintf(": %s", strings.Join(names, ", "))
102 }
103 log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) does not contain go and/or gofmt, found %s", gorootBin, msg)
104 }
105
106 // Make new PATH.
107 path := os.Getenv("PATH")
108 if path == "" {
109 path = gorootBin
110 } else {
111 path = fmt.Sprintf("%s:%s", gorootBin, path)
112 }
113
114 cmd := exec.Command(os.Args[1], os.Args[2:]...)
115
116 // Copy current env into command's env, filtering out GOTOOLWRAP env vars
117 // and PATH (which we set ourselves).
118 for _, v := range os.Environ() {
119 if strings.HasPrefix(v, "GOTOOLWRAP_GOROOT=") {
120 continue
121 }
122 if strings.HasPrefix(v, "GOTOOLWRAP_GOPATH=") {
123 continue
124 }
125 if strings.HasPrefix(v, "PATH=") {
126 continue
127 }
128 cmd.Env = append(cmd.Env, v)
129 }
Lorenz Brunc2e3b1b2021-11-11 11:06:41 +0100130 // Create a temporary cache directory and remove everything afterwards.
131 // Based on code from
132 // @io_bazel_rules_go//go/tools/builders:stdliblist.go L174
133 tempDir, err := os.MkdirTemp("", "gocache")
134 if err != nil {
135 log.Fatalf("Cannot create temporary directory: %v", err)
136 }
137 defer os.RemoveAll(tempDir)
138
Serge Bazanskib76580c2021-03-31 22:07:01 +0200139 cmd.Env = append(cmd.Env,
140 fmt.Sprintf("GOROOT=%s", gorootAbs),
141 fmt.Sprintf("GOPATH=%s", gopathAbs),
142 fmt.Sprintf("PATH=%s", path),
Lorenz Brunc2e3b1b2021-11-11 11:06:41 +0100143 fmt.Sprintf("GO111MODULE=off"),
144 fmt.Sprintf("GOCACHE=%s", tempDir),
Serge Bazanskib76580c2021-03-31 22:07:01 +0200145 )
146
147 // Run the command interactively.
148 cmd.Stdout = os.Stdout
149 cmd.Stderr = os.Stderr
150 cmd.Stdin = os.Stdin
151 if err := cmd.Run(); err != nil {
152 var exitErr *exec.ExitError
153 if errors.As(err, &exitErr) {
154 os.Exit(exitErr.ExitCode())
155 } else {
156 log.Fatalf("Could not run %q: %v", os.Args[1], err)
157 }
158 }
159}