forked from hswaw/hscloud
Dariusz Niemczyk
7094d69a70
Change-Id: I5d287d53b31c36ef19f2ea4ebc7a0647c87f2e29 Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1532 Reviewed-by: q3k <q3k@hackerspace.pl>
101 lines
2.4 KiB
Go
101 lines
2.4 KiB
Go
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
|
|
}
|