blob: a65780dece08bfe7c0cd0161e034ff2ac3548712 [file] [log] [blame]
Serge Bazanski97783222021-12-14 16:04:26 +01001// datafile provides an abstraction for accessing files passed through the data
2// attribute in a Bazel build rule.
3//
4// It thinly wraps around the Bazel/Go runfile library (to allow running from
5// outside `bazel run`).
6package datafile
7
8import (
9 "bufio"
10 "fmt"
11 "log"
12 "os"
13 "path/filepath"
14 "strings"
15
16 "github.com/bazelbuild/rules_go/go/tools/bazel"
17)
18
19// parseManifest takes a bazel runfile MANIFEST and parses it into a map from
20// workspace-relative path to absolute path, flattening all workspaces into a
21// single tree.
22func parseManifest(path string) (map[string]string, error) {
23 f, err := os.Open(path)
24 if err != nil {
25 return nil, fmt.Errorf("could not open MANIFEST: %v", err)
26 }
27 defer f.Close()
28
29 manifest := make(map[string]string)
30 scanner := bufio.NewScanner(f)
31 for scanner.Scan() {
32 parts := strings.Split(scanner.Text(), " ")
33 if len(parts) != 2 {
34 continue
35 }
36 fpathParts := strings.Split(parts[0], string(os.PathSeparator))
37 fpath := strings.Join(fpathParts[1:], string(os.PathSeparator))
38 manifest[fpath] = parts[1]
39 }
40 return manifest, nil
41}
42
43// resolveRunfile tries to resolve a workspace-relative file path into an
44// absolute path with the use of bazel runfiles, through either the original
45// Bazel/Go runfile integration or a wrapper that also supports running from
46// outside `bazel run`.
47func resolveRunfile(path string) (string, error) {
48 var errEx error
49 ep, err := os.Executable()
50 if err == nil {
51 rfdir := ep + ".runfiles"
52 mfpath := filepath.Join(rfdir, "MANIFEST")
53 if stat, err := os.Stat(rfdir); err == nil && stat.IsDir() {
54 // We have a runfiles directory, parse MANIFEST and resolve files this way.
55 manifest, err := parseManifest(mfpath)
56 if err == nil {
57 tpath := manifest[path]
58 if tpath == "" {
59 errEx = fmt.Errorf("not in MANIFEST")
60 } else {
61 return tpath, err
62 }
63 } else {
64 errEx = err
65 }
66 } else {
67 errEx = err
68 }
69 }
70
71 // Try runfiles just in case.
72 rf, errRF := bazel.Runfile(path)
73 if errRF == nil {
74 return rf, nil
75 }
76 return "", fmt.Errorf("could not resolve via executable location (%v) and runfile resolution failed: %v", errEx, errRF)
77}
78
79// Get tries to read a workspace-relative file path through the use of Bazel
80// runfiles, including for cases when executables are running outside `bazel
81// run`.
82func Get(path string) ([]byte, error) {
83 rfpath, err := resolveRunfile(path)
84 if err != nil {
85 return nil, err
86 }
87 return os.ReadFile(rfpath)
88}
89
90// MustGet either successfully resolves a file through Get() or logs an error
91// (through the stdlib log library) and stops execution. This should thus only
92// be used in binaries which use the log library.
93func MustGet(path string) []byte {
94 res, err := Get(path)
95 if err != nil {
96 log.Fatalf("Could not get datafile %s: %v", path, err)
97 }
98 return res
99}