blob: 86dc3a1066b05b0771daff6ee766b2c3e0068976 [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"
Serge Bazanskib76580c2021-03-31 22:07:01 +020039 "log"
40 "os"
41 "os/exec"
42 "path/filepath"
43 "strings"
44)
45
46func main() {
47 gopath := os.Getenv("GOTOOLWRAP_GOPATH")
48 if gopath == "" {
49 log.Fatal("GOTOOLWRAP_GOPATH must be set")
50 }
51
52 goroot := os.Getenv("GOTOOLWRAP_GOROOT")
53 if goroot == "" {
54 log.Fatal("GOTOOLWRAP_GOROOT must be set")
55 }
56
57 if len(os.Args) < 2 {
58 log.Fatalf("No command specified")
59 }
60
61 // Resolve gopath and goroot to absolute paths.
62 gopathAbs, err := filepath.Abs(gopath)
63 if err != nil {
64 log.Fatalf("Abs(%q): %v", gopath, err)
65 }
66 gorootAbs, err := filepath.Abs(goroot)
67 if err != nil {
68 log.Fatalf("Abs(%q): %v", goroot, err)
69 }
70
71 // Ensure the resolved GOROOT has a bin/go and bin/gofmt.
72 gorootBin := filepath.Join(gorootAbs, "bin")
73 stat, err := os.Stat(gorootBin)
74 if err != nil {
75 log.Fatalf("Could not stat $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
76 }
77 if !stat.IsDir() {
78 log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) is not a directory", gorootBin)
79 }
80 // We list all files inside so that we can print them to the user for
81 // debugging purposes if that's not the case.
82 binFiles := make(map[string]bool)
Lorenz Brun764a2de2021-11-22 16:26:36 +010083 files, err := os.ReadDir(gorootBin)
Serge Bazanskib76580c2021-03-31 22:07:01 +020084 if err != nil {
85 log.Fatalf("Could not read dir $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
86 }
87 for _, f := range files {
88 if f.IsDir() {
89 continue
90 }
91 binFiles[f.Name()] = true
92 }
93 if !binFiles["go"] || !binFiles["gofmt"] {
94 msg := "no files"
95 if len(binFiles) > 0 {
96 var names []string
97 for name, _ := range binFiles {
98 names = append(names, fmt.Sprintf("%q", name))
99 }
100 msg = fmt.Sprintf(": %s", strings.Join(names, ", "))
101 }
102 log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) does not contain go and/or gofmt, found %s", gorootBin, msg)
103 }
104
105 // Make new PATH.
106 path := os.Getenv("PATH")
107 if path == "" {
108 path = gorootBin
109 } else {
110 path = fmt.Sprintf("%s:%s", gorootBin, path)
111 }
112
113 cmd := exec.Command(os.Args[1], os.Args[2:]...)
114
115 // Copy current env into command's env, filtering out GOTOOLWRAP env vars
116 // and PATH (which we set ourselves).
117 for _, v := range os.Environ() {
118 if strings.HasPrefix(v, "GOTOOLWRAP_GOROOT=") {
119 continue
120 }
121 if strings.HasPrefix(v, "GOTOOLWRAP_GOPATH=") {
122 continue
123 }
124 if strings.HasPrefix(v, "PATH=") {
125 continue
126 }
127 cmd.Env = append(cmd.Env, v)
128 }
Lorenz Brunc2e3b1b2021-11-11 11:06:41 +0100129 // Create a temporary cache directory and remove everything afterwards.
130 // Based on code from
131 // @io_bazel_rules_go//go/tools/builders:stdliblist.go L174
132 tempDir, err := os.MkdirTemp("", "gocache")
133 if err != nil {
134 log.Fatalf("Cannot create temporary directory: %v", err)
135 }
136 defer os.RemoveAll(tempDir)
137
Serge Bazanskib76580c2021-03-31 22:07:01 +0200138 cmd.Env = append(cmd.Env,
139 fmt.Sprintf("GOROOT=%s", gorootAbs),
140 fmt.Sprintf("GOPATH=%s", gopathAbs),
141 fmt.Sprintf("PATH=%s", path),
Lorenz Brunc2e3b1b2021-11-11 11:06:41 +0100142 fmt.Sprintf("GO111MODULE=off"),
143 fmt.Sprintf("GOCACHE=%s", tempDir),
Serge Bazanskib76580c2021-03-31 22:07:01 +0200144 )
145
146 // Run the command interactively.
147 cmd.Stdout = os.Stdout
148 cmd.Stderr = os.Stderr
149 cmd.Stdin = os.Stdin
150 if err := cmd.Run(); err != nil {
151 var exitErr *exec.ExitError
152 if errors.As(err, &exitErr) {
153 os.Exit(exitErr.ExitCode())
154 } else {
155 log.Fatalf("Could not run %q: %v", os.Args[1], err)
156 }
157 }
158}