github.com/onsi/gomega@v1.32.0/gexec/build.go (about) 1 // untested sections: 5 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 /* 26 Build uses go build to compile the package at packagePath. The resulting binary is saved off in a temporary directory. 27 A path pointing to this binary is returned. 28 29 Build uses the $GOPATH set in your environment. If $GOPATH is not set and you are using Go 1.8+, 30 it will use the default GOPATH instead. It passes the variadic args on to `go build`. 31 */ 32 func Build(packagePath string, args ...string) (compiledPath string, err error) { 33 return doBuild(build.Default.GOPATH, packagePath, nil, args...) 34 } 35 36 /* 37 BuildWithEnvironment is identical to Build but allows you to specify env vars to be set at build time. 38 */ 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 /* 44 BuildIn is identical to Build but allows you to specify a custom $GOPATH (the first argument). 45 */ 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 /* 72 CompileTest uses go test to compile the test package at packagePath. The resulting binary is saved off in a temporary directory. 73 A path pointing to this binary is returned. 74 75 CompileTest uses the $GOPATH set in your environment. If $GOPATH is not set and you are using Go 1.8+, 76 it will use the default GOPATH instead. It passes the variadic args on to `go test`. 77 */ 78 func CompileTest(packagePath string, args ...string) (compiledPath string, err error) { 79 return doCompileTest(build.Default.GOPATH, packagePath, nil, args...) 80 } 81 82 /* 83 GetAndCompileTest is identical to CompileTest but `go get` the package before compiling tests. 84 */ 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 /* 94 CompileTestWithEnvironment is identical to CompileTest but allows you to specify env vars to be set at build time. 95 */ 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 /* 101 GetAndCompileTestWithEnvironment is identical to GetAndCompileTest but allows you to specify env vars to be set at build time. 102 */ 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 /* 112 CompileTestIn is identical to CompileTest but allows you to specify a custom $GOPATH (the first argument). 113 */ 114 func CompileTestIn(gopath string, packagePath string, args ...string) (compiledPath string, err error) { 115 return doCompileTest(gopath, packagePath, nil, args...) 116 } 117 118 /* 119 GetAndCompileTestIn is identical to GetAndCompileTest but allows you to specify a custom $GOPATH (the first argument). 120 */ 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 /* 209 You should call CleanupBuildArtifacts before your test ends to clean up any temporary artifacts generated by 210 gexec. In Ginkgo this is typically done in an AfterSuite callback. 211 */ 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 }