blob: 6311b3f4122db5e4f556530cd12cf2fceec61a3c [file] [log] [blame]
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
}