func setupWorkspace()

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
}