1 // Copyright 2022 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.
23 // moduleWalkErr returns filepath.SkipDir if the directory isn't relevant
24 // when indexing a module or generating a filehash, ErrNotIndexed,
25 // if the module shouldn't be indexed, and nil otherwise.
26 func moduleWalkErr(root string, path string, info fs.FileInfo, err error) error {
30 // stop at module boundaries
31 if info.IsDir() && path != root {
32 if fi, err := fsys.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
33 return filepath.SkipDir
36 if info.Mode()&fs.ModeSymlink != 0 {
37 if target, err := fsys.Stat(path); err == nil && target.IsDir() {
38 // return an error to make the module hash invalid.
39 // Symlink directories in modules are tricky, so we won't index
40 // modules that contain them.
41 // TODO(matloob): perhaps don't return this error if the symlink leads to
42 // a directory with a go.mod file.
49 // indexModule indexes the module at the given directory and returns its
50 // encoded representation. It returns ErrNotIndexed if the module can't
51 // be indexed because it contains symlinks.
52 func indexModule(modroot string) ([]byte, error) {
53 fsys.Trace("indexModule", modroot)
54 var packages []*rawPackage
56 // If the root itself is a symlink to a directory,
57 // we want to follow it (see https://go.dev/issue/50807).
58 // Add a trailing separator to force that to happen.
59 root := str.WithFilePathSeparator(modroot)
60 err := fsys.Walk(root, func(path string, info fs.FileInfo, err error) error {
61 if err := moduleWalkErr(root, path, info, err); err != nil {
68 if !strings.HasPrefix(path, root) {
69 panic(fmt.Errorf("path %v in walk doesn't have modroot %v as prefix", path, modroot))
71 rel := path[len(root):]
72 packages = append(packages, importRaw(modroot, rel))
78 return encodeModuleBytes(packages), nil
81 // indexPackage indexes the package at the given directory and returns its
82 // encoded representation. It returns ErrNotIndexed if the package can't
84 func indexPackage(modroot, pkgdir string) []byte {
85 fsys.Trace("indexPackage", pkgdir)
86 p := importRaw(modroot, relPath(pkgdir, modroot))
87 return encodePackageBytes(p)
90 // rawPackage holds the information from each package that's needed to
91 // fill a build.Package once the context is available.
92 type rawPackage struct {
94 dir string // directory containing package sources, relative to the module root
97 sourceFiles []*rawFile
100 type parseError struct {
101 ErrorList *scanner.ErrorList
105 // parseErrorToString converts the error from parsing the file into a string
106 // representation. A nil error is converted to an empty string, and all other
107 // errors are converted to a JSON-marshalled parseError struct, with ErrorList
108 // set for errors of type scanner.ErrorList, and ErrorString set to the error's
109 // string representation for all other errors.
110 func parseErrorToString(err error) string {
115 if e, ok := err.(scanner.ErrorList); ok {
118 p.ErrorString = e.Error()
120 s, err := json.Marshal(p)
122 panic(err) // This should be impossible because scanner.Error contains only strings and ints.
127 // parseErrorFromString converts a string produced by parseErrorToString back
128 // to an error. An empty string is converted to a nil error, and all
129 // other strings are expected to be JSON-marshalled parseError structs.
130 // The two functions are meant to preserve the structure of an
131 // error of type scanner.ErrorList in a round trip, but may not preserve the
132 // structure of other errors.
133 func parseErrorFromString(s string) error {
138 if err := json.Unmarshal([]byte(s), &p); err != nil {
139 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)
141 if p.ErrorList != nil {
144 return errors.New(p.ErrorString)
147 // rawFile is the struct representation of the file holding all
148 // information in its fields.
149 type rawFile struct {
154 synopsis string // doc.Synopsis of package comment... Compute synopsis on all of these?
156 ignoreFile bool // starts with _ or . or should otherwise always be ignored
157 binaryOnly bool // cannot be rebuilt from source (has //go:binary-only-package comment)
158 cgoDirectives string // the #cgo directive lines in the comment on import "C"
159 goBuildConstraint string
160 plusBuildConstraints []string
163 directives []build.Directive
166 type rawImport struct {
168 position token.Position
173 position token.Position
176 // importRaw fills the rawPackage from the package files in srcDir.
177 // dir is the package's path relative to the modroot.
178 func importRaw(modroot, reldir string) *rawPackage {
183 absdir := filepath.Join(modroot, reldir)
185 // We still haven't checked
186 // that p.dir directory exists. This is the right time to do that check.
187 // We can't do it earlier, because we want to gather partial information for the
188 // non-nil *build.Package returned when an error occurs.
189 // We need to do this before we return early on FindOnly flag.
191 // package was not found
192 p.error = fmt.Errorf("cannot find package in:\n\t%s", absdir).Error()
196 entries, err := fsys.ReadDir(absdir)
198 p.error = err.Error()
202 fset := token.NewFileSet()
203 for _, d := range entries {
207 if d.Mode()&fs.ModeSymlink != 0 {
208 if isDir(filepath.Join(absdir, d.Name())) {
209 // Symlinks to directories are not source files.
217 if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") {
220 info, err := getFileInfo(absdir, name, fset)
221 if err == errNonSource {
222 // not a source or object file. completely ignore in the index
224 } else if err != nil {
225 p.sourceFiles = append(p.sourceFiles, &rawFile{name: name, error: err.Error()})
227 } else if info == nil {
228 p.sourceFiles = append(p.sourceFiles, &rawFile{name: name, ignoreFile: true})
233 goBuildConstraint: info.goBuildConstraint,
234 plusBuildConstraints: info.plusBuildConstraints,
235 binaryOnly: info.binaryOnly,
236 directives: info.directives,
238 if info.parsed != nil {
239 rf.pkgName = info.parsed.Name.Name
242 // Going to save the file. For non-Go files, can stop here.
243 p.sourceFiles = append(p.sourceFiles, rf)
248 if info.parseErr != nil {
249 rf.parseError = parseErrorToString(info.parseErr)
250 // Fall through: we might still have a partial AST in info.Parsed,
251 // and we want to list files with parse errors anyway.
254 if info.parsed != nil && info.parsed.Doc != nil {
255 rf.synopsis = doc.Synopsis(info.parsed.Doc.Text())
258 var cgoDirectives []string
259 for _, imp := range info.imports {
261 cgoDirectives = append(cgoDirectives, extractCgoDirectives(imp.doc.Text())...)
263 rf.imports = append(rf.imports, rawImport{path: imp.path, position: fset.Position(imp.pos)})
265 rf.cgoDirectives = strings.Join(cgoDirectives, "\n")
266 for _, emb := range info.embeds {
267 rf.embeds = append(rf.embeds, embed{emb.pattern, emb.pos})
274 // extractCgoDirectives filters only the lines containing #cgo directives from the input,
275 // which is the comment on import "C".
276 func extractCgoDirectives(doc string) []string {
278 for _, line := range strings.Split(doc, "\n") {
280 // #cgo [GOOS/GOARCH...] LDFLAGS: stuff
282 line = strings.TrimSpace(line)
283 if len(line) < 5 || line[:4] != "#cgo" || (line[4] != ' ' && line[4] != '\t') {
287 out = append(out, line)