| 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 uint64(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 |
| } |