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  }