Skip to content

Commit 293d43e

Browse files
committed
cmd/go: add index creation methods
This change functions to scan modules and packages into an intermediate RawPackage struct and also functions to write them out to and index. Change-Id: Ia1a3b58b992e9be52c5d1397e85c642f902011cb Reviewed-on: https://go-review.googlesource.com/c/go/+/398415 Run-TryBot: Michael Matloob <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Reviewed-by: Bryan Mills <[email protected]>
1 parent 8a56c77 commit 293d43e

File tree

4 files changed

+452
-4
lines changed

4 files changed

+452
-4
lines changed

src/cmd/go/internal/modindex/build.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ package modindex
99

1010
import (
1111
"bytes"
12+
"cmd/go/internal/fsys"
1213
"errors"
1314
"fmt"
1415
"go/ast"
1516
"go/build/constraint"
1617
"go/token"
1718
"io"
1819
"io/fs"
19-
"os"
2020
"path/filepath"
2121
"sort"
2222
"strings"
@@ -130,9 +130,9 @@ func (ctxt *Context) isAbsPath(path string) bool {
130130
return filepath.IsAbs(path)
131131
}
132132

133-
// isDir calls ctxt.IsDir (if not nil) or else uses os.Stat.
133+
// isDir calls ctxt.IsDir (if not nil) or else uses fsys.Stat.
134134
func isDir(path string) bool {
135-
fi, err := os.Stat(path)
135+
fi, err := fsys.Stat(path)
136136
return err == nil && fi.IsDir()
137137
}
138138

@@ -476,7 +476,7 @@ func getFileInfo(dir, name string, fset *token.FileSet) (*fileInfo, error) {
476476
return info, nil
477477
}
478478

479-
f, err := os.Open(info.name)
479+
f, err := fsys.Open(info.name)
480480
if err != nil {
481481
return nil, err
482482
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
This file documents the index format that is read and written by this package.
2+
The index format is an encoding of a series of RawPackage structs
3+
4+
Field names refer to fields on RawPackage and rawFile.
5+
The file uses little endian encoding for the uint32s.
6+
Strings are written into the string table at the end of the file. Each string
7+
is null-terminated. String offsets are relative to the start of the string table.
8+
Bools are written as uint32s: 0 for false and 1 for true.
9+
10+
“go index v0\n”
11+
str uint32 - offset of string table
12+
n uint32 - number of packages
13+
dirnames [n]uint32 - offsets to package names in string table; names sorted by raw string
14+
packages [n]uint32 - offset where package begins
15+
for each RawPackage:
16+
error uint32 - string offset // error is produced by fsys.ReadDir or fmt.Errorf
17+
path uint32 - string offset
18+
dir uint32 - string offset (directory path relative to module root)
19+
len(sourceFiles) uint32
20+
sourceFiles [n]uint32 - offset to source file (relative to start of index file)
21+
for each sourceFile:
22+
error - string offset // error is either produced by fmt.Errorf,errors.New or is io.EOF
23+
parseError - string offset // if non-empty, a json-encoded parseError struct (see below). Is either produced by io.ReadAll,os.ReadFile,errors.New or is scanner.Error,scanner.ErrorList
24+
name - string offset
25+
synopsis - string offset
26+
pkgName - string offset
27+
ignoreFile - int32 bool // report the file in Ignored(Go|Other)Files because there was an error reading it or parsing its build constraints.
28+
binaryOnly uint32 bool
29+
cgoDirectives string offset // the #cgo directive lines in the comment on import "C"
30+
goBuildConstraint - string offset
31+
len(plusBuildConstraints) - uint32
32+
plusBuildConstraints - [n]uint32 (string offsets)
33+
len(imports) uint32
34+
for each rawImport:
35+
path - string offset
36+
position - file, offset, line, column - uint32
37+
len(embeds) numEmbeds uint32
38+
for each embed:
39+
pattern - string offset
40+
position - file, offset, line, column - uint32
41+
[string table]
42+
43+
// parseError struct
44+
type parseError struct {
45+
ErrorList *scanner.ErrorList // non-nil if the error was an ErrorList, nil otherwise
46+
ErrorString string // non-empty for all other cases
47+
}

src/cmd/go/internal/modindex/scan.go

+246
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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

Comments
 (0)