blob: d4599a18e04d57dbb535573ef44b0aefd7b9ba8a [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Serge Bazanskib76580c2021-03-31 22:07:01 +02002// SPDX-License-Identifier: Apache-2.0
Serge Bazanskib76580c2021-03-31 22:07:01 +02003
4// gotoolwrap is a tiny wrapper used to run executables that expect a standard
5// Go development environment setup to act on the monogon workspace. Notably,
6// it's used in Bazel rules to run tools like gofmt when invoked by some kinds
7// of code generation tools.
8//
9// Usage: ./gotoolwrap executable arg1 arg2
10//
11// gotoolwrap expects the following environment variables to be set (and unsets
12// them before calling the given executable):
13// - GOTOOLWRAP_GOPATH: A synthetic GOPATH, eg. one generated by rules_go's
14// go_path target.
15// - GOTOOLWRAP_GOROOT: A Go SDK's GOROOT, eg. one from rules_go's GoSDK
16// provider.
Tim Windelschmidtddc5e6a2024-04-23 23:44:34 +020017// - GOTOOLWRAP_COPYOUT: Pairs of source:destination elements separated by ;.
18// Files given in this list will be copied from source
19// (relative to the GOPATH sources) to the destination
20// (absolute path). This is primarily useful when dealing
21// with code generators which insist on putting their
22// generated files within the GOPATH, and allows copying
23// of said files to a declared output file.
Serge Bazanskib76580c2021-03-31 22:07:01 +020024//
25// gotoolwrap will set PATH to contain GOROOT/bin, and set GOPATH and GOROOT as
26// resolved, absolute paths. Absolute paths are expected by tools like 'gofmt'.
27
28package main
29
30import (
31 "errors"
32 "fmt"
Serge Bazanskib76580c2021-03-31 22:07:01 +020033 "log"
34 "os"
35 "os/exec"
36 "path/filepath"
37 "strings"
38)
39
40func main() {
41 gopath := os.Getenv("GOTOOLWRAP_GOPATH")
42 if gopath == "" {
43 log.Fatal("GOTOOLWRAP_GOPATH must be set")
44 }
45
46 goroot := os.Getenv("GOTOOLWRAP_GOROOT")
47 if goroot == "" {
48 log.Fatal("GOTOOLWRAP_GOROOT must be set")
49 }
50
51 if len(os.Args) < 2 {
52 log.Fatalf("No command specified")
53 }
54
55 // Resolve gopath and goroot to absolute paths.
56 gopathAbs, err := filepath.Abs(gopath)
57 if err != nil {
58 log.Fatalf("Abs(%q): %v", gopath, err)
59 }
60 gorootAbs, err := filepath.Abs(goroot)
61 if err != nil {
62 log.Fatalf("Abs(%q): %v", goroot, err)
63 }
64
65 // Ensure the resolved GOROOT has a bin/go and bin/gofmt.
66 gorootBin := filepath.Join(gorootAbs, "bin")
67 stat, err := os.Stat(gorootBin)
68 if err != nil {
69 log.Fatalf("Could not stat $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
70 }
71 if !stat.IsDir() {
72 log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) is not a directory", gorootBin)
73 }
74 // We list all files inside so that we can print them to the user for
75 // debugging purposes if that's not the case.
76 binFiles := make(map[string]bool)
Lorenz Brun764a2de2021-11-22 16:26:36 +010077 files, err := os.ReadDir(gorootBin)
Serge Bazanskib76580c2021-03-31 22:07:01 +020078 if err != nil {
79 log.Fatalf("Could not read dir $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
80 }
81 for _, f := range files {
82 if f.IsDir() {
83 continue
84 }
85 binFiles[f.Name()] = true
86 }
87 if !binFiles["go"] || !binFiles["gofmt"] {
88 msg := "no files"
89 if len(binFiles) > 0 {
90 var names []string
Tim Windelschmidt6b6428d2024-04-11 01:35:41 +020091 for name := range binFiles {
Serge Bazanskib76580c2021-03-31 22:07:01 +020092 names = append(names, fmt.Sprintf("%q", name))
93 }
94 msg = fmt.Sprintf(": %s", strings.Join(names, ", "))
95 }
96 log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) does not contain go and/or gofmt, found %s", gorootBin, msg)
97 }
98
99 // Make new PATH.
100 path := os.Getenv("PATH")
101 if path == "" {
102 path = gorootBin
103 } else {
104 path = fmt.Sprintf("%s:%s", gorootBin, path)
105 }
106
107 cmd := exec.Command(os.Args[1], os.Args[2:]...)
108
109 // Copy current env into command's env, filtering out GOTOOLWRAP env vars
110 // and PATH (which we set ourselves).
111 for _, v := range os.Environ() {
112 if strings.HasPrefix(v, "GOTOOLWRAP_GOROOT=") {
113 continue
114 }
115 if strings.HasPrefix(v, "GOTOOLWRAP_GOPATH=") {
116 continue
117 }
Tim Windelschmidtddc5e6a2024-04-23 23:44:34 +0200118 if strings.HasPrefix(v, "GOTOOLWRAP_COPYOUT=") {
119 continue
120 }
Serge Bazanskib76580c2021-03-31 22:07:01 +0200121 if strings.HasPrefix(v, "PATH=") {
122 continue
123 }
124 cmd.Env = append(cmd.Env, v)
125 }
Lorenz Brunc2e3b1b2021-11-11 11:06:41 +0100126 // Create a temporary cache directory and remove everything afterwards.
127 // Based on code from
128 // @io_bazel_rules_go//go/tools/builders:stdliblist.go L174
129 tempDir, err := os.MkdirTemp("", "gocache")
130 if err != nil {
131 log.Fatalf("Cannot create temporary directory: %v", err)
132 }
133 defer os.RemoveAll(tempDir)
134
Serge Bazanskib76580c2021-03-31 22:07:01 +0200135 cmd.Env = append(cmd.Env,
Tim Windelschmidtddc5e6a2024-04-23 23:44:34 +0200136 "GOROOT="+gorootAbs,
137 "GOPATH="+gopathAbs,
138 "PATH="+path,
Tim Windelschmidt92316fd2024-04-18 23:06:40 +0200139 "GO111MODULE=off",
Tim Windelschmidtddc5e6a2024-04-23 23:44:34 +0200140 "GOCACHE="+tempDir,
Serge Bazanskib76580c2021-03-31 22:07:01 +0200141 )
142
143 // Run the command interactively.
144 cmd.Stdout = os.Stdout
145 cmd.Stderr = os.Stderr
146 cmd.Stdin = os.Stdin
147 if err := cmd.Run(); err != nil {
148 var exitErr *exec.ExitError
149 if errors.As(err, &exitErr) {
150 os.Exit(exitErr.ExitCode())
151 } else {
152 log.Fatalf("Could not run %q: %v", os.Args[1], err)
153 }
154 }
Tim Windelschmidtddc5e6a2024-04-23 23:44:34 +0200155
156 copyout := os.Getenv("GOTOOLWRAP_COPYOUT")
157 if len(copyout) != 0 {
158 for _, pair := range strings.Split(copyout, ";") {
159 parts := strings.Split(pair, ":")
160 if len(parts) != 2 {
161 log.Fatalf("GOTOOL_COPYOUT invalid pair: %q", pair)
162 }
163 from := filepath.Join(gopathAbs, "src", parts[0])
164 to := parts[1]
165 log.Printf("gotoolwrap: Copying %s to %s...", from, to)
166 data, err := os.ReadFile(from)
167 if err != nil {
168 log.Fatalf("gotoolwrap: read failed: %v", err)
169 }
170 err = os.MkdirAll(filepath.Dir(to), 0755)
171 if err != nil {
172 log.Fatalf("gotoolwrap: mkdir failed: %v", err)
173 }
174 err = os.WriteFile(to, data, 0644)
175 if err != nil {
176 log.Fatalf("gotoolwrap: write failed: %v", err)
177 }
178 }
179 }
Serge Bazanskib76580c2021-03-31 22:07:01 +0200180}