in go/tools/bazel_testing/bazel_testing.go [235:403]
func setupWorkspace(args Args, files []string) (dir string, cleanup func() error, err error) {
var cleanups []func() error
cleanup = func() error {
var firstErr error
for i := len(cleanups) - 1; i >= 0; i-- {
if err := cleanups[i](); err != nil && firstErr == nil {
firstErr = err
}
}
return firstErr
}
defer func() {
if err != nil {
cleanup()
cleanup = func() error { return nil }
}
}()
// Find a suitable cache directory. We want something persistent where we
// can store a bazel output base across test runs, even for multiple tests.
var cacheDir, outBaseDir string
if tmpDir := os.Getenv("TEST_TMPDIR"); tmpDir != "" {
// TEST_TMPDIR is set by Bazel's test wrapper. Bazel itself uses this to
// detect that it's run by a test. When invoked like this, Bazel sets
// its output base directory to a temporary directory. This wastes a lot
// of time (a simple test takes 45s instead of 3s). We use TEST_TMPDIR
// to find a persistent location in the execroot. We won't pass TEST_TMPDIR
// to bazel in RunBazel.
tmpDir = filepath.Clean(tmpDir)
if i := strings.Index(tmpDir, string(os.PathSeparator)+"execroot"+string(os.PathSeparator)); i >= 0 {
outBaseDir = tmpDir[:i]
outputUserRoot = filepath.Dir(outBaseDir)
cacheDir = filepath.Join(outBaseDir, "bazel_testing")
} else {
cacheDir = filepath.Join(tmpDir, "bazel_testing")
}
} else {
// The test is not invoked by Bazel, so just use the user's cache.
cacheDir, err = os.UserCacheDir()
if err != nil {
return "", cleanup, err
}
cacheDir = filepath.Join(cacheDir, "bazel_testing")
}
// TODO(jayconrod): any other directories needed for caches?
execDir := filepath.Join(cacheDir, "bazel_go_test")
if err := os.RemoveAll(execDir); err != nil {
return "", cleanup, err
}
cleanups = append(cleanups, func() error { return os.RemoveAll(execDir) })
// Create the workspace directory.
mainDir := filepath.Join(execDir, "main")
if err := os.MkdirAll(mainDir, 0777); err != nil {
return "", cleanup, err
}
// Create a .bazelrc file if GO_BAZEL_TEST_BAZELFLAGS is set.
// The test can override this with its own .bazelrc or with flags in commands.
if flags := os.Getenv("GO_BAZEL_TEST_BAZELFLAGS"); flags != "" {
bazelrcPath := filepath.Join(mainDir, ".bazelrc")
content := "build " + flags
if err := ioutil.WriteFile(bazelrcPath, []byte(content), 0666); err != nil {
return "", cleanup, err
}
}
// Extract test files for the main workspace.
if err := extractTxtar(mainDir, args.Main); err != nil {
return "", cleanup, fmt.Errorf("building main workspace: %v", err)
}
// If some of the path arguments are missing an explicit workspace,
// read the workspace name from WORKSPACE. We need this to map arguments
// to runfiles in specific workspaces.
haveDefaultWorkspace := false
var defaultWorkspaceName string
for _, argPath := range files {
workspace, _, err := parseLocationArg(argPath)
if err == nil && workspace == "" {
haveDefaultWorkspace = true
cleanPath := path.Clean(argPath)
if cleanPath == "WORKSPACE" {
defaultWorkspaceName, err = loadWorkspaceName(cleanPath)
if err != nil {
return "", cleanup, fmt.Errorf("could not load default workspace name: %v", err)
}
break
}
}
}
if haveDefaultWorkspace && defaultWorkspaceName == "" {
return "", cleanup, fmt.Errorf("found files from default workspace, but not WORKSPACE")
}
// Index runfiles by workspace and short path. We need this to determine
// destination paths when we copy or link files.
runfiles, err := bazel.ListRunfiles()
if err != nil {
return "", cleanup, err
}
type runfileKey struct{ workspace, short string }
runfileMap := make(map[runfileKey]string)
for _, rf := range runfiles {
runfileMap[runfileKey{rf.Workspace, rf.ShortPath}] = rf.Path
}
// Copy or link file arguments from runfiles into fake workspace dirctories.
// Keep track of the workspace names we see, since we'll generate a WORKSPACE
// with local_repository rules later.
workspaceNames := make(map[string]bool)
for _, argPath := range files {
workspace, shortPath, err := parseLocationArg(argPath)
if err != nil {
return "", cleanup, err
}
if workspace == "" {
workspace = defaultWorkspaceName
}
workspaceNames[workspace] = true
srcPath, ok := runfileMap[runfileKey{workspace, shortPath}]
if !ok {
return "", cleanup, fmt.Errorf("unknown runfile: %s", argPath)
}
dstPath := filepath.Join(execDir, workspace, shortPath)
if err := copyOrLink(dstPath, srcPath); err != nil {
return "", cleanup, err
}
}
// If there's no WORKSPACE file, create one.
workspacePath := filepath.Join(mainDir, "WORKSPACE")
if _, err := os.Stat(workspacePath); os.IsNotExist(err) {
w, err := os.Create(workspacePath)
if err != nil {
return "", cleanup, err
}
defer func() {
if cerr := w.Close(); err == nil && cerr != nil {
err = cerr
}
}()
info := workspaceTemplateInfo{
Suffix: args.WorkspaceSuffix,
Nogo: args.Nogo,
}
for name := range workspaceNames {
info.WorkspaceNames = append(info.WorkspaceNames, name)
}
sort.Strings(info.WorkspaceNames)
if outBaseDir != "" {
goSDKPath := filepath.Join(outBaseDir, "external", "go_sdk")
rel, err := filepath.Rel(mainDir, goSDKPath)
if err != nil {
return "", cleanup, fmt.Errorf("could not find relative path from %q to %q for go_sdk", mainDir, goSDKPath)
}
rel = filepath.ToSlash(rel)
info.GoSDKPath = rel
}
if err := defaultWorkspaceTpl.Execute(w, info); err != nil {
return "", cleanup, err
}
}
return mainDir, cleanup, nil
}