package workspace import ( "fmt" "os" "path" "sync" "syscall" ) // isWorkspace returns whether a given string is a valid path pointing to a // Bazel workspace directory. func isWorkspace(dir string) bool { w := path.Join(dir, "WORKSPACE") if _, err := os.Stat(w); err == nil { return true } return false } // getPathFSID returns an opaque filesystem identifier for a given path. func getPathFSID(dir string) (uint64, error) { st, err := os.Stat(dir) if err != nil { // No need to wrap err, as stat errors are already quite explicit // (they also include the stat'd path). return 0, err } switch x := st.Sys().(type) { case *syscall.Stat_t: return x.Dev, nil default: return 0, fmt.Errorf("unsupported operating system (got stat type %+v)", st.Sys()) } } // lookForWorkspace recurses up a directory until it finds a WORKSPACE, the // root, or crosses a filesystem boundary. func lookForWorkspace(dir string) (string, error) { fsid, err := getPathFSID(dir) if err != nil { return "", fmt.Errorf("could not get initial FSID: %w", err) } for { if dir == "." || dir == "/" || dir == "" { return "", fmt.Errorf("got up to root before finding workspace") } fsid2, err := getPathFSID(dir) if err != nil { return "", fmt.Errorf("could not get parent FWID: %w", err) } if fsid2 != fsid { return "", fmt.Errorf("crossed filesystem boundaries before finding workspace") } if isWorkspace(dir) { return dir, nil } dir = path.Dir(dir) } } // Get returns the workspace directory from which a given // command line tool is running. This handles the following // cases: // // 1. The command line tool was invoked via `bazel run`. // 2. The command line tool was started in the workspace directory or a // subdirectory. // // If the workspace directory path cannot be inferred based on the above // assumptions, an error is returned. func Get() (string, error) { workspaceOnce.Do(func() { workspace, workspaceErr = get() }) return workspace, workspaceErr } var ( workspace string workspaceErr error workspaceOnce sync.Once ) func get() (string, error) { if p := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); p != "" && isWorkspace(p) { return p, nil } wd, err := os.Getwd() if err != nil { return "", err } p, err := lookForWorkspace(wd) if err != nil { return "", fmt.Errorf("not invoked from `bazel run` and could not find workspace root by traversing upwards: %w", err) } return p, nil }