|
| 1 | +package modindex |
| 2 | + |
| 3 | +import ( |
| 4 | + "cmd/go/internal/base" |
| 5 | + "cmd/go/internal/fsys" |
| 6 | + "cmd/go/internal/par" |
| 7 | + "encoding/json" |
| 8 | + "errors" |
| 9 | + "fmt" |
| 10 | + "go/doc" |
| 11 | + "go/scanner" |
| 12 | + "go/token" |
| 13 | + "io/fs" |
| 14 | + "path/filepath" |
| 15 | + "strings" |
| 16 | +) |
| 17 | + |
| 18 | +// indexModule indexes the module at the given directory and returns its |
| 19 | +// encoded representation. |
| 20 | +func indexModule(modroot string) ([]byte, error) { |
| 21 | + var packages []*rawPackage |
| 22 | + err := fsys.Walk(modroot, func(path string, info fs.FileInfo, err error) error { |
| 23 | + if err != nil { |
| 24 | + return err |
| 25 | + } |
| 26 | + if !info.IsDir() { |
| 27 | + return nil |
| 28 | + } |
| 29 | + // stop at module boundaries |
| 30 | + if modroot != path { |
| 31 | + if fi, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() { |
| 32 | + return filepath.SkipDir |
| 33 | + } |
| 34 | + } |
| 35 | + // TODO(matloob): what do we do about symlinks |
| 36 | + rel, err := filepath.Rel(modroot, path) |
| 37 | + if err != nil { |
| 38 | + panic(err) |
| 39 | + } |
| 40 | + packages = append(packages, importRaw(modroot, rel)) |
| 41 | + return nil |
| 42 | + }) |
| 43 | + if err != nil { |
| 44 | + return nil, err |
| 45 | + } |
| 46 | + return encodeModule(packages) |
| 47 | +} |
| 48 | + |
| 49 | +// rawPackage holds the information from each package that's needed to |
| 50 | +// fill a build.Package once the context is available. |
| 51 | +type rawPackage struct { |
| 52 | + error string |
| 53 | + dir string // directory containing package sources, relative to the module root |
| 54 | + |
| 55 | + // Source files |
| 56 | + sourceFiles []*rawFile |
| 57 | +} |
| 58 | + |
| 59 | +type parseError struct { |
| 60 | + ErrorList *scanner.ErrorList |
| 61 | + ErrorString string |
| 62 | +} |
| 63 | + |
| 64 | +// parseErrorToString converts the error from parsing the file into a string |
| 65 | +// representation. A nil error is converted to an empty string, and all other |
| 66 | +// errors are converted to a JSON-marshalled parseError struct, with ErrorList |
| 67 | +// set for errors of type scanner.ErrorList, and ErrorString set to the error's |
| 68 | +// string representation for all other errors. |
| 69 | +func parseErrorToString(err error) string { |
| 70 | + if err == nil { |
| 71 | + return "" |
| 72 | + } |
| 73 | + var p parseError |
| 74 | + if e, ok := err.(scanner.ErrorList); ok { |
| 75 | + p.ErrorList = &e |
| 76 | + } else { |
| 77 | + p.ErrorString = e.Error() |
| 78 | + } |
| 79 | + s, err := json.Marshal(p) |
| 80 | + if err != nil { |
| 81 | + panic(err) // This should be impossible because scanner.Error contains only strings and ints. |
| 82 | + } |
| 83 | + return string(s) |
| 84 | +} |
| 85 | + |
| 86 | +// parseErrorFrom string converts a string produced by parseErrorToString back |
| 87 | +// to an error. An empty string is converted to a nil error, and all |
| 88 | +// other strings are expected to be JSON-marshalled parseError structs. |
| 89 | +// The two functions are meant to preserve the structure of an |
| 90 | +// error of type scanner.ErrorList in a round trip, but may not preserve the |
| 91 | +// structure of other errors. |
| 92 | +func parseErrorFromString(s string) error { |
| 93 | + if s == "" { |
| 94 | + return nil |
| 95 | + } |
| 96 | + var p parseError |
| 97 | + if err := json.Unmarshal([]byte(s), &p); err != nil { |
| 98 | + base.Fatalf(`go: invalid parse error value in index: %q. This indicates a corrupted index. Run "go clean -cache" to reset the module cache.`, s) |
| 99 | + } |
| 100 | + if p.ErrorList != nil { |
| 101 | + return *p.ErrorList |
| 102 | + } |
| 103 | + return errors.New(p.ErrorString) |
| 104 | +} |
| 105 | + |
| 106 | +// rawFile is the struct representation of the file holding all |
| 107 | +// information in its fields. |
| 108 | +type rawFile struct { |
| 109 | + error string |
| 110 | + parseError string |
| 111 | + |
| 112 | + name string |
| 113 | + synopsis string // doc.Synopsis of package comment... Compute synopsis on all of these? |
| 114 | + pkgName string |
| 115 | + ignoreFile bool // starts with _ or . or should otherwise always be ignored |
| 116 | + binaryOnly bool // cannot be rebuilt from source (has //go:binary-only-package comment) |
| 117 | + cgoDirectives string // the #cgo directive lines in the comment on import "C" |
| 118 | + goBuildConstraint string |
| 119 | + plusBuildConstraints []string |
| 120 | + imports []rawImport |
| 121 | + embeds []embed |
| 122 | +} |
| 123 | + |
| 124 | +type rawImport struct { |
| 125 | + path string |
| 126 | + position token.Position |
| 127 | +} |
| 128 | + |
| 129 | +type embed struct { |
| 130 | + pattern string |
| 131 | + position token.Position |
| 132 | +} |
| 133 | + |
| 134 | +var pkgcache par.Cache // for packages not in modcache |
| 135 | + |
| 136 | +// importRaw fills the rawPackage from the package files in srcDir. |
| 137 | +// dir is the package's path relative to the modroot. |
| 138 | +func importRaw(modroot, reldir string) *rawPackage { |
| 139 | + p := &rawPackage{ |
| 140 | + dir: reldir, |
| 141 | + } |
| 142 | + |
| 143 | + absdir := filepath.Join(modroot, reldir) |
| 144 | + |
| 145 | + // We still haven't checked |
| 146 | + // that p.dir directory exists. This is the right time to do that check. |
| 147 | + // We can't do it earlier, because we want to gather partial information for the |
| 148 | + // non-nil *Package returned when an error occurs. |
| 149 | + // We need to do this before we return early on FindOnly flag. |
| 150 | + if !isDir(absdir) { |
| 151 | + // package was not found |
| 152 | + p.error = fmt.Errorf("cannot find package in:\n\t%s", absdir).Error() |
| 153 | + return p |
| 154 | + } |
| 155 | + |
| 156 | + entries, err := fsys.ReadDir(absdir) |
| 157 | + if err != nil { |
| 158 | + p.error = err.Error() |
| 159 | + return p |
| 160 | + } |
| 161 | + |
| 162 | + fset := token.NewFileSet() |
| 163 | + for _, d := range entries { |
| 164 | + if d.IsDir() { |
| 165 | + continue |
| 166 | + } |
| 167 | + if d.Mode()&fs.ModeSymlink != 0 { |
| 168 | + if isDir(filepath.Join(absdir, d.Name())) { |
| 169 | + // Symlinks to directories are not source files. |
| 170 | + continue |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + name := d.Name() |
| 175 | + ext := nameExt(name) |
| 176 | + |
| 177 | + if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") { |
| 178 | + continue |
| 179 | + } |
| 180 | + info, err := getFileInfo(absdir, name, fset) |
| 181 | + if err != nil { |
| 182 | + p.sourceFiles = append(p.sourceFiles, &rawFile{name: name, error: err.Error()}) |
| 183 | + continue |
| 184 | + } else if info == nil { |
| 185 | + p.sourceFiles = append(p.sourceFiles, &rawFile{name: name, ignoreFile: true}) |
| 186 | + continue |
| 187 | + } |
| 188 | + rf := &rawFile{ |
| 189 | + name: name, |
| 190 | + goBuildConstraint: info.goBuildConstraint, |
| 191 | + plusBuildConstraints: info.plusBuildConstraints, |
| 192 | + binaryOnly: info.binaryOnly, |
| 193 | + } |
| 194 | + if info.parsed != nil { |
| 195 | + rf.pkgName = info.parsed.Name.Name |
| 196 | + } |
| 197 | + |
| 198 | + // Going to save the file. For non-Go files, can stop here. |
| 199 | + p.sourceFiles = append(p.sourceFiles, rf) |
| 200 | + if ext != ".go" { |
| 201 | + continue |
| 202 | + } |
| 203 | + |
| 204 | + if info.parseErr != nil { |
| 205 | + rf.parseError = parseErrorToString(info.parseErr) |
| 206 | + // Fall through: we might still have a partial AST in info.Parsed, |
| 207 | + // and we want to list files with parse errors anyway. |
| 208 | + } |
| 209 | + |
| 210 | + if info.parsed != nil && info.parsed.Doc != nil { |
| 211 | + rf.synopsis = doc.Synopsis(info.parsed.Doc.Text()) |
| 212 | + } |
| 213 | + |
| 214 | + var cgoDirectives []string |
| 215 | + for _, imp := range info.imports { |
| 216 | + if imp.path == "C" { |
| 217 | + cgoDirectives = append(cgoDirectives, extractCgoDirectives(imp.doc.Text())...) |
| 218 | + } |
| 219 | + rf.imports = append(rf.imports, rawImport{path: imp.path, position: fset.Position(imp.pos)}) |
| 220 | + } |
| 221 | + rf.cgoDirectives = strings.Join(cgoDirectives, "\n") |
| 222 | + for _, emb := range info.embeds { |
| 223 | + rf.embeds = append(rf.embeds, embed{emb.pattern, emb.pos}) |
| 224 | + } |
| 225 | + |
| 226 | + } |
| 227 | + return p |
| 228 | +} |
| 229 | + |
| 230 | +// extractCgoDirectives filters only the lines containing #cgo directives from the input, |
| 231 | +// which is the comment on import "C". |
| 232 | +func extractCgoDirectives(doc string) []string { |
| 233 | + var out []string |
| 234 | + for _, line := range strings.Split(doc, "\n") { |
| 235 | + // Line is |
| 236 | + // #cgo [GOOS/GOARCH...] LDFLAGS: stuff |
| 237 | + // |
| 238 | + line = strings.TrimSpace(line) |
| 239 | + if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') { |
| 240 | + continue |
| 241 | + } |
| 242 | + |
| 243 | + out = append(out, line) |
| 244 | + } |
| 245 | + return out |
| 246 | +} |
0 commit comments