blob: 0f621f2b87e44809835588cdb8db67c5e44c91a2 [file] [log] [blame]
Dan Willemsen0043c0e2016-09-18 20:27:41 -07001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Microfactory is a tool to incrementally compile a go program. It's similar
16// to `go install`, but doesn't require a GOPATH. A package->path mapping can
17// be specified as command line options:
18//
19// -pkg-path android/soong=build/soong
20// -pkg-path github.com/google/blueprint=build/blueprint
21//
22// The paths can be relative to the current working directory, or an absolute
23// path. Both packages and paths are compared with full directory names, so the
24// android/soong-test package wouldn't be mapped in the above case.
25//
26// Microfactory will ignore *_test.go files, and limits *_darwin.go and
27// *_linux.go files to MacOS and Linux respectively. It does not support build
28// tags or any other suffixes.
29//
30// Builds are incremental by package. All input files are hashed, and if the
31// hash of an input or dependency changes, the package is rebuilt.
32//
33// It also exposes the -trimpath option from go's compiler so that embedded
34// path names (such as in log.Llongfile) are relative paths instead of absolute
35// paths.
36//
37// If you don't have a previously built version of Microfactory, when used with
38// -s <microfactory_src_dir> -b <microfactory_bin_file>, Microfactory can
39// rebuild itself as necessary. Combined with a shell script like soong_ui.bash
40// that uses `go run` to run Microfactory for the first time, go programs can be
41// quickly bootstrapped entirely from source (and a standard go distribution).
42package main
43
44import (
45 "bytes"
46 "crypto/sha1"
47 "flag"
48 "fmt"
49 "go/ast"
50 "go/parser"
51 "go/token"
52 "io"
53 "io/ioutil"
54 "os"
55 "os/exec"
56 "path/filepath"
57 "runtime"
58 "sort"
59 "strconv"
60 "strings"
61 "sync"
62 "syscall"
Dan Willemsencae59bc2017-07-13 14:27:31 -070063 "time"
Dan Willemsen0043c0e2016-09-18 20:27:41 -070064)
65
66var (
67 race = false
68 verbose = false
69
70 goToolDir = filepath.Join(runtime.GOROOT(), "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH)
Dan Willemsenfde85342017-02-22 22:03:04 -080071 goVersion = findGoVersion()
Dan Willemsen0043c0e2016-09-18 20:27:41 -070072)
73
Dan Willemsenfde85342017-02-22 22:03:04 -080074func findGoVersion() string {
75 if version, err := ioutil.ReadFile(filepath.Join(runtime.GOROOT(), "VERSION")); err == nil {
76 return string(version)
77 }
78
79 cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "version")
80 if version, err := cmd.Output(); err == nil {
81 return string(version)
82 } else {
83 panic(fmt.Sprintf("Unable to discover go version: %v", err))
84 }
85}
86
Dan Willemsen0043c0e2016-09-18 20:27:41 -070087type GoPackage struct {
88 Name string
89
90 // Inputs
Jeff Gaston1ae73a62017-03-31 14:08:00 -070091 directDeps []*GoPackage // specified directly by the module
92 allDeps []*GoPackage // direct dependencies and transitive dependencies
93 files []string
Dan Willemsen0043c0e2016-09-18 20:27:41 -070094
95 // Outputs
96 pkgDir string
97 output string
98 hashResult []byte
99
100 // Status
101 mutex sync.Mutex
102 compiled bool
103 failed error
104 rebuilt bool
105}
106
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700107// LinkedHashMap<string, GoPackage>
108type linkedDepSet struct {
109 packageSet map[string](*GoPackage)
110 packageList []*GoPackage
111}
112
113func newDepSet() *linkedDepSet {
114 return &linkedDepSet{packageSet: make(map[string]*GoPackage)}
115}
116func (s *linkedDepSet) tryGetByName(name string) (*GoPackage, bool) {
117 pkg, contained := s.packageSet[name]
118 return pkg, contained
119}
120func (s *linkedDepSet) getByName(name string) *GoPackage {
121 pkg, _ := s.tryGetByName(name)
122 return pkg
123}
124func (s *linkedDepSet) add(name string, goPackage *GoPackage) {
125 s.packageSet[name] = goPackage
126 s.packageList = append(s.packageList, goPackage)
127}
128func (s *linkedDepSet) ignore(name string) {
129 s.packageSet[name] = nil
130}
131
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700132// FindDeps searches all applicable go files in `path`, parses all of them
133// for import dependencies that exist in pkgMap, then recursively does the
134// same for all of those dependencies.
135func (p *GoPackage) FindDeps(path string, pkgMap *pkgPathMapping) error {
Dan Willemsencae59bc2017-07-13 14:27:31 -0700136 defer un(trace("findDeps"))
137
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700138 depSet := newDepSet()
139 err := p.findDeps(path, pkgMap, depSet)
140 if err != nil {
141 return err
142 }
143 p.allDeps = depSet.packageList
144 return nil
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700145}
146
147// findDeps is the recursive version of FindDeps. allPackages is the map of
148// all locally defined packages so that the same dependency of two different
149// packages is only resolved once.
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700150func (p *GoPackage) findDeps(path string, pkgMap *pkgPathMapping, allPackages *linkedDepSet) error {
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700151 // If this ever becomes too slow, we can look at reading the files once instead of twice
152 // But that just complicates things today, and we're already really fast.
153 foundPkgs, err := parser.ParseDir(token.NewFileSet(), path, func(fi os.FileInfo) bool {
154 name := fi.Name()
155 if fi.IsDir() || strings.HasSuffix(name, "_test.go") || name[0] == '.' || name[0] == '_' {
156 return false
157 }
158 if runtime.GOOS != "darwin" && strings.HasSuffix(name, "_darwin.go") {
159 return false
160 }
161 if runtime.GOOS != "linux" && strings.HasSuffix(name, "_linux.go") {
162 return false
163 }
164 return true
165 }, parser.ImportsOnly)
166 if err != nil {
167 return fmt.Errorf("Error parsing directory %q: %v", path, err)
168 }
169
170 var foundPkg *ast.Package
171 // foundPkgs is a map[string]*ast.Package, but we only want one package
172 if len(foundPkgs) != 1 {
173 return fmt.Errorf("Expected one package in %q, got %d", path, len(foundPkgs))
174 }
175 // Extract the first (and only) entry from the map.
176 for _, pkg := range foundPkgs {
177 foundPkg = pkg
178 }
179
180 var deps []string
181 localDeps := make(map[string]bool)
182
183 for filename, astFile := range foundPkg.Files {
184 p.files = append(p.files, filename)
185
186 for _, importSpec := range astFile.Imports {
187 name, err := strconv.Unquote(importSpec.Path.Value)
188 if err != nil {
189 return fmt.Errorf("%s: invalid quoted string: <%s> %v", filename, importSpec.Path.Value, err)
190 }
191
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700192 if pkg, ok := allPackages.tryGetByName(name); ok {
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700193 if pkg != nil {
194 if _, ok := localDeps[name]; !ok {
195 deps = append(deps, name)
196 localDeps[name] = true
197 }
198 }
199 continue
200 }
201
202 var pkgPath string
203 if path, ok, err := pkgMap.Path(name); err != nil {
204 return err
205 } else if !ok {
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700206 // Probably in the stdlib, but if not, then the compiler will fail with a reasonable error message
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700207 // Mark it as such so that we don't try to decode its path again.
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700208 allPackages.ignore(name)
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700209 continue
210 } else {
211 pkgPath = path
212 }
213
214 pkg := &GoPackage{
215 Name: name,
216 }
217 deps = append(deps, name)
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700218 allPackages.add(name, pkg)
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700219 localDeps[name] = true
220
221 if err := pkg.findDeps(pkgPath, pkgMap, allPackages); err != nil {
222 return err
223 }
224 }
225 }
226
227 sort.Strings(p.files)
228
229 if verbose {
230 fmt.Fprintf(os.Stderr, "Package %q depends on %v\n", p.Name, deps)
231 }
232
Dan Willemsen38cef8a2017-07-13 14:46:27 -0700233 sort.Strings(deps)
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700234 for _, dep := range deps {
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700235 p.directDeps = append(p.directDeps, allPackages.getByName(dep))
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700236 }
237
238 return nil
239}
240
241func (p *GoPackage) Compile(outDir, trimPath string) error {
242 p.mutex.Lock()
243 defer p.mutex.Unlock()
244 if p.compiled {
245 return p.failed
246 }
247 p.compiled = true
248
249 // Build all dependencies in parallel, then fail if any of them failed.
250 var wg sync.WaitGroup
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700251 for _, dep := range p.directDeps {
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700252 wg.Add(1)
253 go func(dep *GoPackage) {
254 defer wg.Done()
255 dep.Compile(outDir, trimPath)
256 }(dep)
257 }
258 wg.Wait()
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700259 for _, dep := range p.directDeps {
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700260 if dep.failed != nil {
261 p.failed = dep.failed
262 return p.failed
263 }
264 }
265
Dan Willemsencae59bc2017-07-13 14:27:31 -0700266 endTrace := trace("check compile %s", p.Name)
267
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700268 p.pkgDir = filepath.Join(outDir, p.Name)
269 p.output = filepath.Join(p.pkgDir, p.Name) + ".a"
270 shaFile := p.output + ".hash"
271
272 hash := sha1.New()
Dan Willemsenfde85342017-02-22 22:03:04 -0800273 fmt.Fprintln(hash, runtime.GOOS, runtime.GOARCH, goVersion)
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700274
275 cmd := exec.Command(filepath.Join(goToolDir, "compile"),
276 "-o", p.output,
277 "-p", p.Name,
278 "-complete", "-pack", "-nolocalimports")
279 if race {
280 cmd.Args = append(cmd.Args, "-race")
281 fmt.Fprintln(hash, "-race")
282 }
283 if trimPath != "" {
284 cmd.Args = append(cmd.Args, "-trimpath", trimPath)
285 fmt.Fprintln(hash, trimPath)
286 }
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700287 for _, dep := range p.directDeps {
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700288 cmd.Args = append(cmd.Args, "-I", dep.pkgDir)
289 hash.Write(dep.hashResult)
290 }
291 for _, filename := range p.files {
292 cmd.Args = append(cmd.Args, filename)
293 fmt.Fprintln(hash, filename)
294
295 // Hash the contents of the input files
296 f, err := os.Open(filename)
297 if err != nil {
298 f.Close()
299 err = fmt.Errorf("%s: %v", filename, err)
300 p.failed = err
301 return err
302 }
303 _, err = io.Copy(hash, f)
304 if err != nil {
305 f.Close()
306 err = fmt.Errorf("%s: %v", filename, err)
307 p.failed = err
308 return err
309 }
310 f.Close()
311 }
312 p.hashResult = hash.Sum(nil)
313
314 var rebuild bool
315 if _, err := os.Stat(p.output); err != nil {
316 rebuild = true
317 }
318 if !rebuild {
319 if oldSha, err := ioutil.ReadFile(shaFile); err == nil {
320 rebuild = !bytes.Equal(oldSha, p.hashResult)
321 } else {
322 rebuild = true
323 }
324 }
325
Dan Willemsencae59bc2017-07-13 14:27:31 -0700326 endTrace()
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700327 if !rebuild {
328 return nil
329 }
Dan Willemsencae59bc2017-07-13 14:27:31 -0700330 defer un(trace("compile %s", p.Name))
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700331
332 err := os.RemoveAll(p.pkgDir)
333 if err != nil {
334 err = fmt.Errorf("%s: %v", p.Name, err)
335 p.failed = err
336 return err
337 }
338
339 err = os.MkdirAll(filepath.Dir(p.output), 0777)
340 if err != nil {
341 err = fmt.Errorf("%s: %v", p.Name, err)
342 p.failed = err
343 return err
344 }
345
346 cmd.Stdin = nil
347 cmd.Stdout = os.Stdout
348 cmd.Stderr = os.Stderr
349 if verbose {
350 fmt.Fprintln(os.Stderr, cmd.Args)
351 }
352 err = cmd.Run()
353 if err != nil {
354 err = fmt.Errorf("%s: %v", p.Name, err)
355 p.failed = err
356 return err
357 }
358
359 err = ioutil.WriteFile(shaFile, p.hashResult, 0666)
360 if err != nil {
361 err = fmt.Errorf("%s: %v", p.Name, err)
362 p.failed = err
363 return err
364 }
365
366 p.rebuilt = true
367
368 return nil
369}
370
371func (p *GoPackage) Link(out string) error {
372 if p.Name != "main" {
373 return fmt.Errorf("Can only link main package")
374 }
Dan Willemsencae59bc2017-07-13 14:27:31 -0700375 endTrace := trace("check link %s", p.Name)
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700376
377 shaFile := filepath.Join(filepath.Dir(out), "."+filepath.Base(out)+"_hash")
378
379 if !p.rebuilt {
380 if _, err := os.Stat(out); err != nil {
381 p.rebuilt = true
382 } else if oldSha, err := ioutil.ReadFile(shaFile); err != nil {
383 p.rebuilt = true
384 } else {
385 p.rebuilt = !bytes.Equal(oldSha, p.hashResult)
386 }
387 }
Dan Willemsencae59bc2017-07-13 14:27:31 -0700388 endTrace()
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700389 if !p.rebuilt {
390 return nil
391 }
Dan Willemsencae59bc2017-07-13 14:27:31 -0700392 defer un(trace("link %s", p.Name))
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700393
394 err := os.Remove(shaFile)
395 if err != nil && !os.IsNotExist(err) {
396 return err
397 }
398 err = os.Remove(out)
399 if err != nil && !os.IsNotExist(err) {
400 return err
401 }
402
403 cmd := exec.Command(filepath.Join(goToolDir, "link"), "-o", out)
404 if race {
405 cmd.Args = append(cmd.Args, "-race")
406 }
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700407 for _, dep := range p.allDeps {
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700408 cmd.Args = append(cmd.Args, "-L", dep.pkgDir)
409 }
410 cmd.Args = append(cmd.Args, p.output)
411 cmd.Stdin = nil
412 cmd.Stdout = os.Stdout
413 cmd.Stderr = os.Stderr
414 if verbose {
415 fmt.Fprintln(os.Stderr, cmd.Args)
416 }
417 err = cmd.Run()
418 if err != nil {
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700419 return fmt.Errorf("command %s failed with error %v", cmd.Args, err)
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700420 }
421
422 return ioutil.WriteFile(shaFile, p.hashResult, 0666)
423}
424
425// rebuildMicrofactory checks to see if microfactory itself needs to be rebuilt,
Dan Willemsencae59bc2017-07-13 14:27:31 -0700426// and if does, it will launch a new copy and return true. Otherwise it will return
427// false to continue executing.
428func rebuildMicrofactory(mybin, mysrc string, pkgMap *pkgPathMapping) bool {
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700429 intermediates := filepath.Join(filepath.Dir(mybin), "."+filepath.Base(mybin)+"_intermediates")
430
431 err := os.MkdirAll(intermediates, 0777)
432 if err != nil {
433 fmt.Fprintln(os.Stderr, "Failed to create intermediates directory: %v", err)
434 os.Exit(1)
435 }
436
437 pkg := &GoPackage{
438 Name: "main",
439 }
440
441 if err := pkg.FindDeps(mysrc, pkgMap); err != nil {
442 fmt.Fprintln(os.Stderr, err)
443 os.Exit(1)
444 }
445
446 if err := pkg.Compile(intermediates, mysrc); err != nil {
447 fmt.Fprintln(os.Stderr, err)
448 os.Exit(1)
449 }
450
451 if err := pkg.Link(mybin); err != nil {
452 fmt.Fprintln(os.Stderr, err)
453 os.Exit(1)
454 }
455
456 if !pkg.rebuilt {
Dan Willemsencae59bc2017-07-13 14:27:31 -0700457 return false
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700458 }
459
460 cmd := exec.Command(mybin, os.Args[1:]...)
461 cmd.Stdin = os.Stdin
462 cmd.Stdout = os.Stdout
463 cmd.Stderr = os.Stderr
464 if err := cmd.Run(); err == nil {
Dan Willemsencae59bc2017-07-13 14:27:31 -0700465 return true
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700466 } else if e, ok := err.(*exec.ExitError); ok {
467 os.Exit(e.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
468 }
469 os.Exit(1)
Dan Willemsencae59bc2017-07-13 14:27:31 -0700470 return true
471}
472
473var traceFile *os.File
474
475func trace(format string, a ...interface{}) func() {
476 if traceFile == nil {
477 return func() {}
478 }
479 s := strings.TrimSpace(fmt.Sprintf(format, a...))
480 fmt.Fprintf(traceFile, "%d B %s\n", time.Now().UnixNano()/1000, s)
481 return func() {
482 fmt.Fprintf(traceFile, "%d E %s\n", time.Now().UnixNano()/1000, s)
483 }
484}
485
486func un(f func()) {
487 f()
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700488}
489
490func main() {
491 var output, mysrc, mybin, trimPath string
492 var pkgMap pkgPathMapping
493
494 flags := flag.NewFlagSet("", flag.ExitOnError)
495 flags.BoolVar(&race, "race", false, "enable data race detection.")
496 flags.BoolVar(&verbose, "v", false, "Verbose")
497 flags.StringVar(&output, "o", "", "Output file")
498 flags.StringVar(&mysrc, "s", "", "Microfactory source directory (for rebuilding microfactory if necessary)")
499 flags.StringVar(&mybin, "b", "", "Microfactory binary location")
500 flags.StringVar(&trimPath, "trimpath", "", "remove prefix from recorded source file paths")
501 flags.Var(&pkgMap, "pkg-path", "Mapping of package prefixes to file paths")
502 err := flags.Parse(os.Args[1:])
503
504 if err == flag.ErrHelp || flags.NArg() != 1 || output == "" {
505 fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "-o out/binary <main-package>")
506 flags.PrintDefaults()
507 os.Exit(1)
508 }
509
Dan Willemsencae59bc2017-07-13 14:27:31 -0700510 tracePath := filepath.Join(filepath.Dir(output), "."+filepath.Base(output)+".trace")
511 traceFile, err = os.OpenFile(tracePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
512 if err != nil {
513 traceFile = nil
514 }
515 if executable, err := os.Executable(); err == nil {
516 defer un(trace("microfactory %s", executable))
517 } else {
518 defer un(trace("microfactory <unknown>"))
519 }
520
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700521 if mybin != "" && mysrc != "" {
Dan Willemsencae59bc2017-07-13 14:27:31 -0700522 if rebuildMicrofactory(mybin, mysrc, &pkgMap) {
523 return
524 }
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700525 }
526
527 mainPackage := &GoPackage{
528 Name: "main",
529 }
530
531 if path, ok, err := pkgMap.Path(flags.Arg(0)); err != nil {
532 fmt.Fprintln(os.Stderr, "Error finding main path:", err)
533 os.Exit(1)
534 } else if !ok {
535 fmt.Fprintln(os.Stderr, "Cannot find path for", flags.Arg(0))
536 } else {
537 if err := mainPackage.FindDeps(path, &pkgMap); err != nil {
538 fmt.Fprintln(os.Stderr, err)
539 os.Exit(1)
540 }
541 }
542
543 intermediates := filepath.Join(filepath.Dir(output), "."+filepath.Base(output)+"_intermediates")
544
545 err = os.MkdirAll(intermediates, 0777)
546 if err != nil {
547 fmt.Fprintln(os.Stderr, "Failed to create intermediates directory: %ve", err)
548 os.Exit(1)
549 }
550
551 err = mainPackage.Compile(intermediates, trimPath)
552 if err != nil {
553 fmt.Fprintln(os.Stderr, "Failed to compile:", err)
554 os.Exit(1)
555 }
556
557 err = mainPackage.Link(output)
558 if err != nil {
Jeff Gaston1ae73a62017-03-31 14:08:00 -0700559 fmt.Fprintln(os.Stderr, "microfactory.go failed to link:", err)
Dan Willemsen0043c0e2016-09-18 20:27:41 -0700560 os.Exit(1)
561 }
562}
563
564// pkgPathMapping can be used with flag.Var to parse -pkg-path arguments of
565// <package-prefix>=<path-prefix> mappings.
566type pkgPathMapping struct {
567 pkgs []string
568
569 paths map[string]string
570}
571
572func (pkgPathMapping) String() string {
573 return "<package-prefix>=<path-prefix>"
574}
575
576func (p *pkgPathMapping) Set(value string) error {
577 equalPos := strings.Index(value, "=")
578 if equalPos == -1 {
579 return fmt.Errorf("Argument must be in the form of: %q", p.String())
580 }
581
582 pkgPrefix := strings.TrimSuffix(value[:equalPos], "/")
583 pathPrefix := strings.TrimSuffix(value[equalPos+1:], "/")
584
585 if p.paths == nil {
586 p.paths = make(map[string]string)
587 }
588 if _, ok := p.paths[pkgPrefix]; ok {
589 return fmt.Errorf("Duplicate package prefix: %q", pkgPrefix)
590 }
591
592 p.pkgs = append(p.pkgs, pkgPrefix)
593 p.paths[pkgPrefix] = pathPrefix
594
595 return nil
596}
597
598// Path takes a package name, applies the path mappings and returns the resulting path.
599//
600// If the package isn't mapped, we'll return false to prevent compilation attempts.
601func (p *pkgPathMapping) Path(pkg string) (string, bool, error) {
602 if p.paths == nil {
603 return "", false, fmt.Errorf("No package mappings")
604 }
605
606 for _, pkgPrefix := range p.pkgs {
607 if pkg == pkgPrefix {
608 return p.paths[pkgPrefix], true, nil
609 } else if strings.HasPrefix(pkg, pkgPrefix+"/") {
610 return filepath.Join(p.paths[pkgPrefix], strings.TrimPrefix(pkg, pkgPrefix+"/")), true, nil
611 }
612 }
613
614 return "", false, nil
615}