|  | // datafile provides an abstraction for accessing files passed through the data | 
|  | // attribute in a Bazel build rule. | 
|  | // | 
|  | // It thinly wraps around the Bazel/Go runfile library (to allow running from | 
|  | // outside `bazel run`). | 
|  | package datafile | 
|  |  | 
|  | import ( | 
|  | "bufio" | 
|  | "fmt" | 
|  | "log" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "strings" | 
|  |  | 
|  | "github.com/bazelbuild/rules_go/go/tools/bazel" | 
|  | ) | 
|  |  | 
|  | // parseManifest takes a bazel runfile MANIFEST and parses it into a map from | 
|  | // workspace-relative path to absolute path, flattening all workspaces into a | 
|  | // single tree. | 
|  | func parseManifest(path string) (map[string]string, error) { | 
|  | f, err := os.Open(path) | 
|  | if err != nil { | 
|  | return nil, fmt.Errorf("could not open MANIFEST: %v", err) | 
|  | } | 
|  | defer f.Close() | 
|  |  | 
|  | manifest := make(map[string]string) | 
|  | scanner := bufio.NewScanner(f) | 
|  | for scanner.Scan() { | 
|  | parts := strings.Split(scanner.Text(), " ") | 
|  | if len(parts) != 2 { | 
|  | continue | 
|  | } | 
|  | fpathParts := strings.Split(parts[0], string(os.PathSeparator)) | 
|  | fpath := strings.Join(fpathParts[1:], string(os.PathSeparator)) | 
|  | manifest[fpath] = parts[1] | 
|  | } | 
|  | return manifest, nil | 
|  | } | 
|  |  | 
|  | // ResolveRunfile tries to resolve a workspace-relative file path into an | 
|  | // absolute path with the use of bazel runfiles, through either the original | 
|  | // Bazel/Go runfile integration or a wrapper that also supports running from | 
|  | // outside `bazel run`. | 
|  | func ResolveRunfile(path string) (string, error) { | 
|  | var errEx error | 
|  | ep, err := os.Executable() | 
|  | if err == nil { | 
|  | rfdir := ep + ".runfiles" | 
|  | mfpath := filepath.Join(rfdir, "MANIFEST") | 
|  | if stat, err := os.Stat(rfdir); err == nil && stat.IsDir() { | 
|  | // We have a runfiles directory, parse MANIFEST and resolve files this way. | 
|  | manifest, err := parseManifest(mfpath) | 
|  | if err == nil { | 
|  | tpath := manifest[path] | 
|  | if tpath == "" { | 
|  | errEx = fmt.Errorf("not in MANIFEST") | 
|  | } else { | 
|  | return tpath, err | 
|  | } | 
|  | } else { | 
|  | errEx = err | 
|  | } | 
|  | } else { | 
|  | errEx = err | 
|  | } | 
|  | } | 
|  |  | 
|  | // Try runfiles just in case. | 
|  | rf, errRF := bazel.Runfile(path) | 
|  | if errRF == nil { | 
|  | return rf, nil | 
|  | } | 
|  | return "", fmt.Errorf("could not resolve via executable location (%v) and runfile resolution failed: %v", errEx, errRF) | 
|  | } | 
|  |  | 
|  | // Get tries to read a workspace-relative file path through the use of Bazel | 
|  | // runfiles, including for cases when executables are running outside `bazel | 
|  | // run`. | 
|  | func Get(path string) ([]byte, error) { | 
|  | rfpath, err := ResolveRunfile(path) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return os.ReadFile(rfpath) | 
|  | } | 
|  |  | 
|  | // MustGet either successfully resolves a file through Get() or logs an error | 
|  | // (through the stdlib log library) and stops execution. This should thus only | 
|  | // be used in binaries which use the log library. | 
|  | func MustGet(path string) []byte { | 
|  | res, err := Get(path) | 
|  | if err != nil { | 
|  | log.Fatalf("Could not get datafile %s: %v", path, err) | 
|  | } | 
|  | return res | 
|  | } |