1 // Copyright 2018 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.
5 // This file caches information about which standard library types, methods,
6 // and functions appeared in what version of Go
22 // apiVersions is a map of packages to information about those packages'
23 // symbols and when they were added to Go.
25 // Only things added after Go1 are tracked. Version strings are of the
26 // form "1.1", "1.2", etc.
27 type apiVersions map[string]pkgAPIVersions // keyed by Go package ("net/http")
29 // pkgAPIVersions contains information about which version of Go added
30 // certain package symbols.
32 // Only things added after Go1 are tracked. Version strings are of the
33 // form "1.1", "1.2", etc.
34 type pkgAPIVersions struct {
35 typeSince map[string]string // "Server" -> "1.7"
36 methodSince map[string]map[string]string // "*Server" ->"Shutdown"->1.8
37 funcSince map[string]string // "NewServer" -> "1.7"
38 fieldSince map[string]map[string]string // "ClientTrace" -> "Got1xxResponse" -> "1.11"
41 // sinceVersionFunc returns a string (such as "1.7") specifying which Go
42 // version introduced a symbol, unless it was introduced in Go1, in
43 // which case it returns the empty string.
45 // The kind is one of "type", "method", or "func".
47 // The receiver is only used for "methods" and specifies the receiver type,
50 // The name is the symbol name ("Server") and the pkg is the package
52 func (v apiVersions) sinceVersionFunc(kind, receiver, name, pkg string) string {
56 return pv.funcSince[name]
58 return pv.typeSince[name]
60 return pv.methodSince[receiver][name]
65 // versionedRow represents an API feature, a parsed line of a
66 // $GOROOT/api/go.*txt file.
67 type versionedRow struct {
68 pkg string // "net/http"
69 kind string // "type", "func", "method", "field" TODO: "const", "var"
70 recv string // for methods, the receiver type ("Server", "*Server")
71 name string // name of type, (struct) field, func, method
72 structName string // for struct fields, the outer struct name
75 // versionParser parses $GOROOT/api/go*.txt files and stores them in in its rows field.
76 type versionParser struct {
77 res apiVersions // initialized lazily
80 // parseFile parses the named $GOROOT/api/goVERSION.txt file.
82 // For each row, it updates the corresponding entry in
83 // vp.res to VERSION, overwriting any previous value.
84 // As a special case, if goVERSION is "go1", it deletes
85 // from the map instead.
86 func (vp *versionParser) parseFile(name string) error {
87 f, err := os.Open(name)
93 base := filepath.Base(name)
94 ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
96 sc := bufio.NewScanner(f)
98 row, ok := parseRow(sc.Text())
103 vp.res = make(apiVersions)
105 pkgi, ok := vp.res[row.pkg]
107 pkgi = pkgAPIVersions{
108 typeSince: make(map[string]string),
109 methodSince: make(map[string]map[string]string),
110 funcSince: make(map[string]string),
111 fieldSince: make(map[string]map[string]string),
113 vp.res[row.pkg] = pkgi
118 delete(pkgi.funcSince, row.name)
121 pkgi.funcSince[row.name] = ver
124 delete(pkgi.typeSince, row.name)
127 pkgi.typeSince[row.name] = ver
130 delete(pkgi.methodSince[row.recv], row.name)
133 if _, ok := pkgi.methodSince[row.recv]; !ok {
134 pkgi.methodSince[row.recv] = make(map[string]string)
136 pkgi.methodSince[row.recv][row.name] = ver
139 delete(pkgi.fieldSince[row.structName], row.name)
142 if _, ok := pkgi.fieldSince[row.structName]; !ok {
143 pkgi.fieldSince[row.structName] = make(map[string]string)
145 pkgi.fieldSince[row.structName][row.name] = ver
151 func parseRow(s string) (vr versionedRow, ok bool) {
152 if !strings.HasPrefix(s, "pkg ") {
153 // Skip comments, blank lines, etc.
156 rest := s[len("pkg "):]
157 endPkg := strings.IndexFunc(rest, func(r rune) bool { return !(unicode.IsLetter(r) || r == '/' || unicode.IsDigit(r)) })
161 vr.pkg, rest = rest[:endPkg], rest[endPkg:]
162 if !strings.HasPrefix(rest, ", ") {
163 // If the part after the pkg name isn't ", ", then it's a OS/ARCH-dependent line of the form:
164 // pkg syscall (darwin-amd64), const ImplementsGetwd = false
165 // We skip those for now.
168 rest = rest[len(", "):]
171 case strings.HasPrefix(rest, "type "):
172 rest = rest[len("type "):]
173 sp := strings.IndexByte(rest, ' ')
177 vr.name, rest = rest[:sp], rest[sp+1:]
178 if !strings.HasPrefix(rest, "struct, ") {
182 rest = rest[len("struct, "):]
183 if i := strings.IndexByte(rest, ' '); i != -1 {
185 vr.structName = vr.name
189 case strings.HasPrefix(rest, "func "):
191 rest = rest[len("func "):]
192 if i := strings.IndexByte(rest, '('); i != -1 {
196 case strings.HasPrefix(rest, "method "): // "method (*File) SetModTime(time.Time)"
198 rest = rest[len("method "):] // "(*File) SetModTime(time.Time)"
199 sp := strings.IndexByte(rest, ' ')
203 vr.recv = strings.Trim(rest[:sp], "()") // "*File"
204 rest = rest[sp+1:] // SetMode(os.FileMode)
205 paren := strings.IndexByte(rest, '(')
209 vr.name = rest[:paren]
212 return // TODO: handle more cases
215 // InitVersionInfo parses the $GOROOT/api/go*.txt API definition files to discover
216 // which API features were added in which Go releases.
217 func (c *Corpus) InitVersionInfo() {
219 c.pkgAPIInfo, err = parsePackageAPIInfo()
221 // TODO: consider making this fatal, after the Go 1.11 cycle.
222 log.Printf("godoc: error parsing API version files: %v", err)
226 func parsePackageAPIInfo() (apiVersions, error) {
228 if os.Getenv("GOROOT") == "" {
229 apiGlob = filepath.Join(build.Default.GOROOT, "api", "go*.txt")
231 apiGlob = filepath.Join(os.Getenv("GOROOT"), "api", "go*.txt")
234 files, err := filepath.Glob(apiGlob)
239 // Process files in go1.n, go1.n-1, ..., go1.2, go1.1, go1 order.
241 // It's rare, but the signature of an identifier may change
242 // (for example, a function that accepts a type replaced with
243 // an alias), and so an existing symbol may show up again in
244 // a later api/go1.N.txt file. Parsing in reverse version
245 // order means we end up with the earliest version of Go
246 // when the symbol was added. See golang.org/issue/44081.
248 ver := func(name string) int {
249 base := filepath.Base(name)
250 ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go1.")
254 v, _ := strconv.Atoi(ver)
257 sort.Slice(files, func(i, j int) bool { return ver(files[i]) > ver(files[j]) })
258 vp := new(versionParser)
259 for _, f := range files {
260 if err := vp.parseFile(f); err != nil {