Alan Donovan | 713699d | 2013-08-27 18:49:13 -0400 | [diff] [blame] | 1 | // Copyright 2013 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Alan Donovan | 3fc0fc1 | 2014-01-16 09:33:58 -0500 | [diff] [blame] | 5 | package loader |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 6 | |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 7 | import ( |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 8 | "go/ast" |
| 9 | "go/build" |
| 10 | "go/parser" |
| 11 | "go/token" |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 12 | "io" |
| 13 | "os" |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 14 | "strconv" |
Alan Donovan | e2921e1 | 2013-09-04 13:15:49 -0400 | [diff] [blame] | 15 | "sync" |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 16 | |
| 17 | "golang.org/x/tools/go/buildutil" |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 18 | ) |
| 19 | |
Alan Donovan | 8a9ac1b | 2015-04-02 13:05:03 -0400 | [diff] [blame] | 20 | // We use a counting semaphore to limit |
| 21 | // the number of parallel I/O calls per process. |
Russ Cox | 93604a3 | 2015-08-18 13:24:30 -0400 | [diff] [blame] | 22 | var ioLimit = make(chan bool, 10) |
Alan Donovan | 8a9ac1b | 2015-04-02 13:05:03 -0400 | [diff] [blame] | 23 | |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 24 | // parseFiles parses the Go source files within directory dir and |
| 25 | // returns the ASTs of the ones that could be at least partially parsed, |
| 26 | // along with a list of I/O and parse errors encountered. |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 27 | // |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 28 | // I/O is done via ctxt, which may specify a virtual file system. |
| 29 | // displayPath is used to transform the filenames attached to the ASTs. |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 30 | func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 31 | if displayPath == nil { |
| 32 | displayPath = func(path string) string { return path } |
| 33 | } |
Alan Donovan | e2921e1 | 2013-09-04 13:15:49 -0400 | [diff] [blame] | 34 | var wg sync.WaitGroup |
| 35 | n := len(files) |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 36 | parsed := make([]*ast.File, n) |
| 37 | errors := make([]error, n) |
Alan Donovan | e2921e1 | 2013-09-04 13:15:49 -0400 | [diff] [blame] | 38 | for i, file := range files { |
Alan Donovan | 0dda50d | 2015-01-30 13:30:23 -0500 | [diff] [blame] | 39 | if !buildutil.IsAbsPath(ctxt, file) { |
| 40 | file = buildutil.JoinPath(ctxt, dir, file) |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 41 | } |
Alan Donovan | e2921e1 | 2013-09-04 13:15:49 -0400 | [diff] [blame] | 42 | wg.Add(1) |
| 43 | go func(i int, file string) { |
Russ Cox | 93604a3 | 2015-08-18 13:24:30 -0400 | [diff] [blame] | 44 | ioLimit <- true // wait |
Alan Donovan | 8a9ac1b | 2015-04-02 13:05:03 -0400 | [diff] [blame] | 45 | defer func() { |
| 46 | wg.Done() |
Russ Cox | 93604a3 | 2015-08-18 13:24:30 -0400 | [diff] [blame] | 47 | <-ioLimit // signal |
Alan Donovan | 8a9ac1b | 2015-04-02 13:05:03 -0400 | [diff] [blame] | 48 | }() |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 49 | var rd io.ReadCloser |
| 50 | var err error |
| 51 | if ctxt.OpenFile != nil { |
| 52 | rd, err = ctxt.OpenFile(file) |
| 53 | } else { |
| 54 | rd, err = os.Open(file) |
| 55 | } |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 56 | if err != nil { |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 57 | errors[i] = err // open failed |
Alan Donovan | e3dc58c | 2014-03-14 16:17:53 -0400 | [diff] [blame] | 58 | return |
| 59 | } |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 60 | |
| 61 | // ParseFile may return both an AST and an error. |
Alan Donovan | 2414677 | 2014-03-27 12:50:26 -0400 | [diff] [blame] | 62 | parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode) |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 63 | rd.Close() |
Alan Donovan | e2921e1 | 2013-09-04 13:15:49 -0400 | [diff] [blame] | 64 | }(i, file) |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 65 | } |
Alan Donovan | e2921e1 | 2013-09-04 13:15:49 -0400 | [diff] [blame] | 66 | wg.Wait() |
| 67 | |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 68 | // Eliminate nils, preserving order. |
| 69 | var o int |
| 70 | for _, f := range parsed { |
| 71 | if f != nil { |
| 72 | parsed[o] = f |
| 73 | o++ |
Alan Donovan | e2921e1 | 2013-09-04 13:15:49 -0400 | [diff] [blame] | 74 | } |
| 75 | } |
Alan Donovan | f0ff511 | 2014-06-13 11:32:46 -0400 | [diff] [blame] | 76 | parsed = parsed[:o] |
| 77 | |
| 78 | o = 0 |
| 79 | for _, err := range errors { |
| 80 | if err != nil { |
| 81 | errors[o] = err |
| 82 | o++ |
| 83 | } |
| 84 | } |
| 85 | errors = errors[:o] |
| 86 | |
| 87 | return parsed, errors |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 88 | } |
| 89 | |
Alan Donovan | d6e83e5 | 2015-12-18 21:02:28 -0500 | [diff] [blame] | 90 | // scanImports returns the set of all import paths from all |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 91 | // import specs in the specified files. |
| 92 | func scanImports(files []*ast.File) map[string]bool { |
| 93 | imports := make(map[string]bool) |
| 94 | for _, f := range files { |
| 95 | for _, decl := range f.Decls { |
| 96 | if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { |
| 97 | for _, spec := range decl.Specs { |
| 98 | spec := spec.(*ast.ImportSpec) |
| 99 | |
| 100 | // NB: do not assume the program is well-formed! |
| 101 | path, err := strconv.Unquote(spec.Path.Value) |
| 102 | if err != nil { |
| 103 | continue // quietly ignore the error |
| 104 | } |
Alan Donovan | ae186f5 | 2016-01-08 14:46:14 -0500 | [diff] [blame] | 105 | if path == "C" { |
| 106 | continue // skip pseudopackage |
Alan Donovan | 9c9660e | 2014-12-15 13:20:21 -0500 | [diff] [blame] | 107 | } |
| 108 | imports[path] = true |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | return imports |
| 114 | } |
| 115 | |
Alan Donovan | be28dbb | 2013-05-31 16:14:13 -0400 | [diff] [blame] | 116 | // ---------- Internal helpers ---------- |
| 117 | |
Alan Donovan | e8afbfa | 2014-01-15 21:37:55 -0500 | [diff] [blame] | 118 | // TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) |
| 119 | func tokenFileContainsPos(f *token.File, pos token.Pos) bool { |
| 120 | p := int(pos) |
| 121 | base := f.Base() |
| 122 | return base <= p && p < base+f.Size() |
| 123 | } |