1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 package runfiles
37
38 import (
39 "bufio"
40 "errors"
41 "fmt"
42 "os"
43 "path/filepath"
44 "strings"
45 )
46
47 const (
48 directoryVar = "RUNFILES_DIR"
49 legacyDirectoryVar = "JAVA_RUNFILES"
50 manifestFileVar = "RUNFILES_MANIFEST_FILE"
51 )
52
53 type repoMappingKey struct {
54 sourceRepo string
55 targetRepoApparentName string
56 }
57
58
59
60
61
62 type Runfiles struct {
63
64
65 impl runfiles
66 env []string
67 repoMapping map[repoMappingKey]string
68 sourceRepo string
69 }
70
71 const noSourceRepoSentinel = "_not_a_valid_repository_name"
72
73
74
75
76
77
78
79 func New(opts ...Option) (*Runfiles, error) {
80 var o options
81 o.sourceRepo = noSourceRepoSentinel
82 for _, a := range opts {
83 a.apply(&o)
84 }
85
86 if o.sourceRepo == noSourceRepoSentinel {
87 o.sourceRepo = SourceRepo(CallerRepository())
88 }
89
90 if o.manifest == "" {
91 o.manifest = ManifestFile(os.Getenv(manifestFileVar))
92 }
93 if o.manifest != "" {
94 return o.manifest.new(o.sourceRepo)
95 }
96
97 if o.directory == "" {
98 o.directory = Directory(os.Getenv(directoryVar))
99 }
100 if o.directory != "" {
101 return o.directory.new(o.sourceRepo)
102 }
103
104 if o.program == "" {
105 o.program = ProgramName(os.Args[0])
106 }
107 manifest := ManifestFile(o.program + ".runfiles_manifest")
108 if stat, err := os.Stat(string(manifest)); err == nil && stat.Mode().IsRegular() {
109 return manifest.new(o.sourceRepo)
110 }
111
112 dir := Directory(o.program + ".runfiles")
113 if stat, err := os.Stat(string(dir)); err == nil && stat.IsDir() {
114 return dir.new(o.sourceRepo)
115 }
116
117 return nil, errors.New("runfiles: no runfiles found")
118 }
119
120
121
122
123
124
125
126
127
128
129
130
131 func (r *Runfiles) Rlocation(path string) (string, error) {
132 if r.impl == nil {
133 return "", errors.New("runfiles: uninitialized Runfiles object")
134 }
135
136 if path == "" {
137 return "", errors.New("runfiles: path may not be empty")
138 }
139 if err := isNormalizedPath(path); err != nil {
140 return "", err
141 }
142
143
144 if strings.HasPrefix(path, `\`) {
145 return "", fmt.Errorf("runfiles: path %q is absolute without a drive letter", path)
146 }
147 if filepath.IsAbs(path) {
148 return path, nil
149 }
150
151 mappedPath := path
152 split := strings.SplitN(path, "/", 2)
153 if len(split) == 2 {
154 key := repoMappingKey{r.sourceRepo, split[0]}
155 if targetRepoDirectory, exists := r.repoMapping[key]; exists {
156 mappedPath = targetRepoDirectory + "/" + split[1]
157 }
158 }
159
160 p, err := r.impl.path(mappedPath)
161 if err != nil {
162 return "", Error{path, err}
163 }
164 return p, nil
165 }
166
167 func isNormalizedPath(s string) error {
168 if strings.HasPrefix(s, "../") || strings.Contains(s, "/../") || strings.HasSuffix(s, "/..") {
169 return fmt.Errorf(`runfiles: path %q must not contain ".." segments`, s)
170 }
171 if strings.HasPrefix(s, "./") || strings.Contains(s, "/./") || strings.HasSuffix(s, "/.") {
172 return fmt.Errorf(`runfiles: path %q must not contain "." segments`, s)
173 }
174 if strings.Contains(s, "//") {
175 return fmt.Errorf(`runfiles: path %q must not contain "//"`, s)
176 }
177 return nil
178 }
179
180
181
182 func (r *Runfiles) loadRepoMapping() error {
183 repoMappingPath, err := r.impl.path(repoMappingRlocation)
184
185
186 if err != nil {
187 return nil
188 }
189 r.repoMapping, err = parseRepoMapping(repoMappingPath)
190
191 return err
192 }
193
194
195
196
197
198
199
200
201 func (r *Runfiles) Env() []string {
202 return r.env
203 }
204
205
206
207
208 func (r *Runfiles) WithSourceRepo(sourceRepo string) *Runfiles {
209 if r.sourceRepo == sourceRepo {
210 return r
211 }
212 clone := *r
213 clone.sourceRepo = sourceRepo
214 return &clone
215 }
216
217
218 type Option interface {
219 apply(*options)
220 }
221
222
223
224 type ProgramName string
225
226
227
228
229
230 type SourceRepo string
231
232
233 type Error struct {
234
235 Name string
236
237
238 Err error
239 }
240
241
242 func (e Error) Error() string {
243 return fmt.Sprintf("runfile %s: %s", e.Name, e.Err.Error())
244 }
245
246
247 func (e Error) Unwrap() error { return e.Err }
248
249
250
251 var ErrEmpty = errors.New("empty runfile")
252
253 type options struct {
254 program ProgramName
255 manifest ManifestFile
256 directory Directory
257 sourceRepo SourceRepo
258 }
259
260 func (p ProgramName) apply(o *options) { o.program = p }
261 func (m ManifestFile) apply(o *options) { o.manifest = m }
262 func (d Directory) apply(o *options) { o.directory = d }
263 func (sr SourceRepo) apply(o *options) { o.sourceRepo = sr }
264
265 type runfiles interface {
266 path(string) (string, error)
267 }
268
269
270
271 const repoMappingRlocation = "_repo_mapping"
272
273
274 func parseRepoMapping(path string) (map[repoMappingKey]string, error) {
275 r, err := os.Open(path)
276 if err != nil {
277
278
279
280
281 return nil, nil
282 }
283 defer r.Close()
284
285
286
287
288 s := bufio.NewScanner(r)
289 repoMapping := make(map[repoMappingKey]string)
290 for s.Scan() {
291 fields := strings.SplitN(s.Text(), ",", 3)
292 if len(fields) != 3 {
293 return nil, fmt.Errorf("runfiles: bad repo mapping line %q in file %s", s.Text(), path)
294 }
295 repoMapping[repoMappingKey{fields[0], fields[1]}] = fields[2]
296 }
297
298 if err = s.Err(); err != nil {
299 return nil, fmt.Errorf("runfiles: error parsing repo mapping file %s: %w", path, err)
300 }
301
302 return repoMapping, nil
303 }
304
View as plain text