// Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build ignore // +build ignore // The generate command reads all the GOROOT/api/go1.*.txt files and // generates a single combined manifest.go file containing the Go // standard library API symbols along with versions. package main import ( "bytes" "cmp" "errors" "fmt" "go/format" "go/types" "io/fs" "log" "os" "path/filepath" "regexp" "runtime" "slices" "strings" "golang.org/x/tools/go/packages" ) func main() { // Read and parse the GOROOT/api manifests. symRE := regexp.MustCompile(`^pkg (\S+).*?, (var|func|type|const|method \([^)]*\)) ([A-Z]\w*)(.*)`) pkgs := make(map[string]map[string]symInfo) // package -> symbol -> info for minor := 0; ; minor++ { base := "go1.txt" if minor > 0 { base = fmt.Sprintf("go1.%d.txt", minor) } filename := filepath.Join(runtime.GOROOT(), "api", base) data, err := os.ReadFile(filename) if err != nil { if errors.Is(err, fs.ErrNotExist) { break // all caught up } log.Fatal(err) } // parse for linenum, line := range strings.Split(string(data), "\n") { if line == "" || strings.HasPrefix(line, "#") { continue } m := symRE.FindStringSubmatch(line) if m == nil { log.Fatalf("invalid input: %s:%d: %s", filename, linenum+1, line) } path, kind, sym, rest := m[1], m[2], m[3], m[4] if _, recv, ok := strings.Cut(kind, "method "); ok { // e.g. "method (*Func) Pos() token.Pos" kind = "method" recv := removeTypeParam(recv) // (*Foo[T]) -> (*Foo) sym = recv + "." + sym // (*T).m } else if _, field, ok := strings.Cut(rest, " struct, "); ok && kind == "type" { // e.g. "type ParenExpr struct, Lparen token.Pos" kind = "field" name, typ, _ := strings.Cut(field, " ") // The api script uses the name // "embedded" (ambiguously) for // the name of an anonymous field. if name == "embedded" { // Strip "*pkg.T" down to "T". typ = strings.TrimPrefix(typ, "*") if _, after, ok := strings.Cut(typ, "."); ok { typ = after } typ = removeTypeParam(typ) // embedded Foo[T] -> Foo name = typ } sym += "." + name // T.f } symbols, ok := pkgs[path] if !ok { symbols = make(map[string]symInfo) pkgs[path] = symbols } // Don't overwrite earlier entries: // enums are redeclared in later versions // as their encoding changes; // deprecations count as updates too. if _, ok := symbols[sym]; !ok { symbols[sym] = symInfo{kind, minor} } } } // The APIs of the syscall/js and unsafe packages need to be computed explicitly, // because they're not included in the GOROOT/api/go1.*.txt files at this time. pkgs["syscall/js"] = loadSymbols("syscall/js", "GOOS=js", "GOARCH=wasm") pkgs["unsafe"] = exportedSymbols(types.Unsafe) // TODO(adonovan): set correct versions // Write the combined manifest. var buf bytes.Buffer buf.WriteString(`// Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Code generated by generate.go. DO NOT EDIT. package stdlib var PackageSymbols = map[string][]Symbol{ `) for _, path := range sortedKeys(pkgs) { pkg := pkgs[path] fmt.Fprintf(&buf, "\t%q: {\n", path) for _, name := range sortedKeys(pkg) { info := pkg[name] fmt.Fprintf(&buf, "\t\t{%q, %s, %d},\n", name, strings.Title(info.kind), info.minor) } fmt.Fprintln(&buf, "},") } fmt.Fprintln(&buf, "}") fmtbuf, err := format.Source(buf.Bytes()) if err != nil { log.Fatal(err) } if err := os.WriteFile("manifest.go", fmtbuf, 0666); err != nil { log.Fatal(err) } } type symInfo struct { kind string // e.g. "func" minor int // go1.%d } // loadSymbols computes the exported symbols in the specified package // by parsing and type-checking the current source. func loadSymbols(pkg string, extraEnv ...string) map[string]symInfo { pkgs, err := packages.Load(&packages.Config{ Mode: packages.NeedTypes, Env: append(os.Environ(), extraEnv...), }, pkg) if err != nil { log.Fatalln(err) } else if len(pkgs) != 1 { log.Fatalf("got %d packages, want one package %q", len(pkgs), pkg) } return exportedSymbols(pkgs[0].Types) } func exportedSymbols(pkg *types.Package) map[string]symInfo { symbols := make(map[string]symInfo) for _, name := range pkg.Scope().Names() { if obj := pkg.Scope().Lookup(name); obj.Exported() { var kind string switch obj.(type) { case *types.Func, *types.Builtin: kind = "func" case *types.Const: kind = "const" case *types.Var: kind = "var" case *types.TypeName: kind = "type" // TODO(adonovan): expand fields and methods of syscall/js.* default: log.Fatalf("unexpected object type: %v", obj) } symbols[name] = symInfo{kind: kind, minor: 0} // pretend go1.0 } } return symbols } func sortedKeys[M ~map[K]V, K cmp.Ordered, V any](m M) []K { r := make([]K, 0, len(m)) for k := range m { r = append(r, k) } slices.Sort(r) return r } func removeTypeParam(s string) string { i := strings.IndexByte(s, '[') j := strings.LastIndexByte(s, ']') if i > 0 && j > i { s = s[:i] + s[j+len("["):] } return s }