golang.org/x/tools@v0.21.0/internal/stdlib/generate.go (about) 1 // Copyright 2024 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 5 //go:build ignore 6 // +build ignore 7 8 // The generate command reads all the GOROOT/api/go1.*.txt files and 9 // generates a single combined manifest.go file containing the Go 10 // standard library API symbols along with versions. 11 package main 12 13 import ( 14 "bytes" 15 "cmp" 16 "errors" 17 "fmt" 18 "go/format" 19 "go/types" 20 "io/fs" 21 "log" 22 "os" 23 "path/filepath" 24 "regexp" 25 "runtime" 26 "slices" 27 "strings" 28 29 "golang.org/x/tools/go/packages" 30 ) 31 32 func main() { 33 // Read and parse the GOROOT/api manifests. 34 symRE := regexp.MustCompile(`^pkg (\S+).*?, (var|func|type|const|method \([^)]*\)) ([A-Z]\w*)(.*)`) 35 pkgs := make(map[string]map[string]symInfo) // package -> symbol -> info 36 for minor := 0; ; minor++ { 37 base := "go1.txt" 38 if minor > 0 { 39 base = fmt.Sprintf("go1.%d.txt", minor) 40 } 41 filename := filepath.Join(runtime.GOROOT(), "api", base) 42 data, err := os.ReadFile(filename) 43 if err != nil { 44 if errors.Is(err, fs.ErrNotExist) { 45 break // all caught up 46 } 47 log.Fatal(err) 48 } 49 50 // parse 51 for linenum, line := range strings.Split(string(data), "\n") { 52 if line == "" || strings.HasPrefix(line, "#") { 53 continue 54 } 55 m := symRE.FindStringSubmatch(line) 56 if m == nil { 57 log.Fatalf("invalid input: %s:%d: %s", filename, linenum+1, line) 58 } 59 path, kind, sym, rest := m[1], m[2], m[3], m[4] 60 61 if _, recv, ok := strings.Cut(kind, "method "); ok { 62 // e.g. "method (*Func) Pos() token.Pos" 63 kind = "method" 64 65 recv := removeTypeParam(recv) // (*Foo[T]) -> (*Foo) 66 67 sym = recv + "." + sym // (*T).m 68 69 } else if _, field, ok := strings.Cut(rest, " struct, "); ok && kind == "type" { 70 // e.g. "type ParenExpr struct, Lparen token.Pos" 71 kind = "field" 72 name, typ, _ := strings.Cut(field, " ") 73 74 // The api script uses the name 75 // "embedded" (ambiguously) for 76 // the name of an anonymous field. 77 if name == "embedded" { 78 // Strip "*pkg.T" down to "T". 79 typ = strings.TrimPrefix(typ, "*") 80 if _, after, ok := strings.Cut(typ, "."); ok { 81 typ = after 82 } 83 typ = removeTypeParam(typ) // embedded Foo[T] -> Foo 84 name = typ 85 } 86 87 sym += "." + name // T.f 88 } 89 90 symbols, ok := pkgs[path] 91 if !ok { 92 symbols = make(map[string]symInfo) 93 pkgs[path] = symbols 94 } 95 96 // Don't overwrite earlier entries: 97 // enums are redeclared in later versions 98 // as their encoding changes; 99 // deprecations count as updates too. 100 if _, ok := symbols[sym]; !ok { 101 symbols[sym] = symInfo{kind, minor} 102 } 103 } 104 } 105 106 // The APIs of the syscall/js and unsafe packages need to be computed explicitly, 107 // because they're not included in the GOROOT/api/go1.*.txt files at this time. 108 pkgs["syscall/js"] = loadSymbols("syscall/js", "GOOS=js", "GOARCH=wasm") 109 pkgs["unsafe"] = exportedSymbols(types.Unsafe) // TODO(adonovan): set correct versions 110 111 // Write the combined manifest. 112 var buf bytes.Buffer 113 buf.WriteString(`// Copyright 2024 The Go Authors. All rights reserved. 114 // Use of this source code is governed by a BSD-style 115 // license that can be found in the LICENSE file. 116 117 // Code generated by generate.go. DO NOT EDIT. 118 119 package stdlib 120 121 var PackageSymbols = map[string][]Symbol{ 122 `) 123 124 for _, path := range sortedKeys(pkgs) { 125 pkg := pkgs[path] 126 fmt.Fprintf(&buf, "\t%q: {\n", path) 127 for _, name := range sortedKeys(pkg) { 128 info := pkg[name] 129 fmt.Fprintf(&buf, "\t\t{%q, %s, %d},\n", 130 name, strings.Title(info.kind), info.minor) 131 } 132 fmt.Fprintln(&buf, "},") 133 } 134 fmt.Fprintln(&buf, "}") 135 fmtbuf, err := format.Source(buf.Bytes()) 136 if err != nil { 137 log.Fatal(err) 138 } 139 if err := os.WriteFile("manifest.go", fmtbuf, 0666); err != nil { 140 log.Fatal(err) 141 } 142 } 143 144 type symInfo struct { 145 kind string // e.g. "func" 146 minor int // go1.%d 147 } 148 149 // loadSymbols computes the exported symbols in the specified package 150 // by parsing and type-checking the current source. 151 func loadSymbols(pkg string, extraEnv ...string) map[string]symInfo { 152 pkgs, err := packages.Load(&packages.Config{ 153 Mode: packages.NeedTypes, 154 Env: append(os.Environ(), extraEnv...), 155 }, pkg) 156 if err != nil { 157 log.Fatalln(err) 158 } else if len(pkgs) != 1 { 159 log.Fatalf("got %d packages, want one package %q", len(pkgs), pkg) 160 } 161 return exportedSymbols(pkgs[0].Types) 162 } 163 164 func exportedSymbols(pkg *types.Package) map[string]symInfo { 165 symbols := make(map[string]symInfo) 166 for _, name := range pkg.Scope().Names() { 167 if obj := pkg.Scope().Lookup(name); obj.Exported() { 168 var kind string 169 switch obj.(type) { 170 case *types.Func, *types.Builtin: 171 kind = "func" 172 case *types.Const: 173 kind = "const" 174 case *types.Var: 175 kind = "var" 176 case *types.TypeName: 177 kind = "type" 178 // TODO(adonovan): expand fields and methods of syscall/js.* 179 default: 180 log.Fatalf("unexpected object type: %v", obj) 181 } 182 symbols[name] = symInfo{kind: kind, minor: 0} // pretend go1.0 183 } 184 } 185 return symbols 186 } 187 188 func sortedKeys[M ~map[K]V, K cmp.Ordered, V any](m M) []K { 189 r := make([]K, 0, len(m)) 190 for k := range m { 191 r = append(r, k) 192 } 193 slices.Sort(r) 194 return r 195 } 196 197 func removeTypeParam(s string) string { 198 i := strings.IndexByte(s, '[') 199 j := strings.LastIndexByte(s, ']') 200 if i > 0 && j > i { 201 s = s[:i] + s[j+len("["):] 202 } 203 return s 204 }