1
2
3 package gexec
4
5 import (
6 "errors"
7 "fmt"
8 "go/build"
9 "os"
10 "os/exec"
11 "path"
12 "path/filepath"
13 "runtime"
14 "strings"
15 "sync"
16
17 "github.com/onsi/gomega/internal/gutil"
18 )
19
20 var (
21 mu sync.Mutex
22 tmpDir string
23 )
24
25
32 func Build(packagePath string, args ...string) (compiledPath string, err error) {
33 return doBuild(build.Default.GOPATH, packagePath, nil, args...)
34 }
35
36
39 func BuildWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) {
40 return doBuild(build.Default.GOPATH, packagePath, env, args...)
41 }
42
43
46 func BuildIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) {
47 return doBuild(gopath, packagePath, nil, args...)
48 }
49
50 func doBuild(gopath, packagePath string, env []string, args ...string) (compiledPath string, err error) {
51 executable, err := newExecutablePath(gopath, packagePath)
52 if err != nil {
53 return "", err
54 }
55
56 cmdArgs := append([]string{"build"}, args...)
57 cmdArgs = append(cmdArgs, "-o", executable, packagePath)
58
59 build := exec.Command("go", cmdArgs...)
60 build.Env = replaceGoPath(os.Environ(), gopath)
61 build.Env = append(build.Env, env...)
62
63 output, err := build.CombinedOutput()
64 if err != nil {
65 return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
66 }
67
68 return executable, nil
69 }
70
71
78 func CompileTest(packagePath string, args ...string) (compiledPath string, err error) {
79 return doCompileTest(build.Default.GOPATH, packagePath, nil, args...)
80 }
81
82
85 func GetAndCompileTest(packagePath string, args ...string) (compiledPath string, err error) {
86 if err := getForTest(build.Default.GOPATH, packagePath, []string{"GO111MODULE=off"}); err != nil {
87 return "", err
88 }
89
90 return doCompileTest(build.Default.GOPATH, packagePath, []string{"GO111MODULE=off"}, args...)
91 }
92
93
96 func CompileTestWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) {
97 return doCompileTest(build.Default.GOPATH, packagePath, env, args...)
98 }
99
100
103 func GetAndCompileTestWithEnvironment(packagePath string, env []string, args ...string) (compiledPath string, err error) {
104 if err := getForTest(build.Default.GOPATH, packagePath, append(env, "GO111MODULE=off")); err != nil {
105 return "", err
106 }
107
108 return doCompileTest(build.Default.GOPATH, packagePath, append(env, "GO111MODULE=off"), args...)
109 }
110
111
114 func CompileTestIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) {
115 return doCompileTest(gopath, packagePath, nil, args...)
116 }
117
118
121 func GetAndCompileTestIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) {
122 if err := getForTest(gopath, packagePath, []string{"GO111MODULE=off"}); err != nil {
123 return "", err
124 }
125
126 return doCompileTest(gopath, packagePath, []string{"GO111MODULE=off"}, args...)
127 }
128
129 func isLocalPackage(packagePath string) bool {
130 return strings.HasPrefix(packagePath, ".")
131 }
132
133 func getForTest(gopath, packagePath string, env []string) error {
134 if isLocalPackage(packagePath) {
135 return nil
136 }
137
138 return doGet(gopath, packagePath, env, "-t")
139 }
140
141 func doGet(gopath, packagePath string, env []string, args ...string) error {
142 args = append(args, packagePath)
143 args = append([]string{"get"}, args...)
144
145 goGet := exec.Command("go", args...)
146 goGet.Dir = gopath
147 goGet.Env = replaceGoPath(os.Environ(), gopath)
148 goGet.Env = append(goGet.Env, env...)
149
150 output, err := goGet.CombinedOutput()
151 if err != nil {
152 return fmt.Errorf("Failed to get %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
153 }
154
155 return nil
156 }
157
158 func doCompileTest(gopath, packagePath string, env []string, args ...string) (compiledPath string, err error) {
159 executable, err := newExecutablePath(gopath, packagePath, ".test")
160 if err != nil {
161 return "", err
162 }
163
164 cmdArgs := append([]string{"test", "-c"}, args...)
165 cmdArgs = append(cmdArgs, "-o", executable, packagePath)
166
167 build := exec.Command("go", cmdArgs...)
168 build.Env = replaceGoPath(os.Environ(), gopath)
169 build.Env = append(build.Env, env...)
170
171 output, err := build.CombinedOutput()
172 if err != nil {
173 return "", fmt.Errorf("Failed to build %s:\n\nError:\n%s\n\nOutput:\n%s", packagePath, err, string(output))
174 }
175
176 return executable, nil
177 }
178
179 func replaceGoPath(environ []string, newGoPath string) []string {
180 newEnviron := []string{}
181 for _, v := range environ {
182 if !strings.HasPrefix(v, "GOPATH=") {
183 newEnviron = append(newEnviron, v)
184 }
185 }
186 return append(newEnviron, "GOPATH="+newGoPath)
187 }
188
189 func newExecutablePath(gopath, packagePath string, suffixes ...string) (string, error) {
190 tmpDir, err := temporaryDirectory()
191 if err != nil {
192 return "", err
193 }
194
195 if len(gopath) == 0 {
196 return "", errors.New("$GOPATH not provided when building " + packagePath)
197 }
198
199 executable := filepath.Join(tmpDir, path.Base(packagePath))
200
201 if runtime.GOOS == "windows" {
202 executable += ".exe"
203 }
204
205 return executable, nil
206 }
207
208
212 func CleanupBuildArtifacts() {
213 mu.Lock()
214 defer mu.Unlock()
215 if tmpDir != "" {
216 os.RemoveAll(tmpDir)
217 tmpDir = ""
218 }
219 }
220
221 func temporaryDirectory() (string, error) {
222 var err error
223 mu.Lock()
224 defer mu.Unlock()
225 if tmpDir == "" {
226 tmpDir, err = gutil.MkdirTemp("", "gexec_artifacts")
227 if err != nil {
228 return "", err
229 }
230 }
231
232 return gutil.MkdirTemp(tmpDir, "g")
233 }
234
View as plain text