Source file src/cmd/export/main.go

     1  // Copyright 2026 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  // The export command writes type information of Go programs.
     6  // It should not be relied upon except by go list -export.
     7  //
     8  // It supports the following command-line protocol:
     9  //
    10  //	-V=full			print tool version
    11  //	unit.cfg		description of unit to export
    12  package main
    13  
    14  import (
    15  	"cmd/internal/objabi"
    16  	"encoding/json"
    17  	"flag"
    18  	"fmt"
    19  	"go/ast"
    20  	"go/build"
    21  	"go/parser"
    22  	"go/token"
    23  	"go/types"
    24  	"log"
    25  	"os"
    26  	"strings"
    27  
    28  	"golang.org/x/tools/go/gcexportdata"
    29  )
    30  
    31  const message = `export is a tool to write type information of Go programs.
    32  
    33  Note that this tool is experimental and its interface is subject to change.
    34  It should not be depended upon directly.
    35  
    36  Usage of 'go tool export':
    37  Export type information specified by a config file:
    38  	go tool export unit.cfg
    39  `
    40  
    41  func usage() {
    42  	fmt.Fprint(os.Stderr, message)
    43  	fmt.Fprintln(os.Stderr, "\nFlags:")
    44  	flag.PrintDefaults()
    45  	os.Exit(2)
    46  }
    47  
    48  // config defines the JSON schema of the file passed by 'go list -export'.
    49  type config struct {
    50  	ImportPath  string            // package path
    51  	Compiler    string            // gc or gccgo, provided to makeTypesImporter
    52  	GoVersion   string            // minimum required Go version, such as "go1.21.0"
    53  	GoFiles     []string          // absolute paths to package source files
    54  	ImportMap   map[string]string // maps import path to package path
    55  	PackageFile map[string]string // maps package path to file of type information
    56  	Output      string            // where to write file of type information
    57  }
    58  
    59  func main() {
    60  	log.SetFlags(0)
    61  	log.SetPrefix("export: ")
    62  
    63  	objabi.AddVersionFlag()
    64  	objabi.Flagparse(usage)
    65  
    66  	args := flag.Args()
    67  	if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
    68  		flag.Usage()
    69  	}
    70  	if err := export(args[0]); err != nil {
    71  		log.Fatal(err)
    72  	}
    73  }
    74  
    75  func export(configFile string) error {
    76  	cfg, err := readConfig(configFile)
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	// TODO: Determine if it's useful for go list -export to return partial type
    82  	// information, perhaps with the -e flag.
    83  	// Load, parse, type check.
    84  	fset := token.NewFileSet()
    85  	var files []*ast.File
    86  	for _, name := range cfg.GoFiles {
    87  		f, err := parser.ParseFile(fset, name, nil, parser.SkipObjectResolution)
    88  		if err != nil {
    89  			return err
    90  		}
    91  		files = append(files, f)
    92  	}
    93  	tc := &types.Config{
    94  		Importer:  makeTypesImporter(cfg, fset),
    95  		Sizes:     types.SizesFor(cfg.Compiler, build.Default.GOARCH),
    96  		GoVersion: cfg.GoVersion,
    97  	}
    98  	pkg, err := tc.Check(cfg.ImportPath, fset, files, nil)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// Write export data.
   104  	f, err := os.Create(cfg.Output)
   105  	if err != nil {
   106  		return err
   107  	}
   108  	if err := gcexportdata.Write(f, fset, pkg); err != nil {
   109  		f.Close() // ignore error
   110  		return err
   111  	}
   112  	return f.Close()
   113  }
   114  
   115  func readConfig(filename string) (*config, error) {
   116  	data, err := os.ReadFile(filename)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	cfg := new(config)
   121  	if err := json.Unmarshal(data, cfg); err != nil {
   122  		return nil, fmt.Errorf("can't decode input %s: %v", filename, err)
   123  	}
   124  	if len(cfg.GoFiles) == 0 {
   125  		return nil, fmt.Errorf("no files in package %s", cfg.ImportPath)
   126  	}
   127  	return cfg, nil
   128  }
   129  
   130  func makeTypesImporter(cfg *config, fset *token.FileSet) types.Importer {
   131  	imports := make(map[string]*types.Package)
   132  	imports["unsafe"] = types.Unsafe
   133  	return importerFunc(func(importPath string) (*types.Package, error) {
   134  		pkgPath, ok := cfg.ImportMap[importPath]
   135  		if !ok {
   136  			return nil, fmt.Errorf("can't resolve import %s", importPath)
   137  		}
   138  		// Check for cache hit.
   139  		if pkg, ok := imports[pkgPath]; ok && pkg.Complete() {
   140  			return pkg, nil
   141  		}
   142  		// Miss.
   143  		cfgPath, ok := cfg.PackageFile[pkgPath]
   144  		if !ok {
   145  			return nil, fmt.Errorf("can't resolve export %s", pkgPath)
   146  		}
   147  		f, err := os.Open(cfgPath)
   148  		if err != nil {
   149  			return nil, err
   150  		}
   151  		defer f.Close()
   152  		return gcexportdata.Read(f, fset, imports, pkgPath)
   153  	})
   154  }
   155  
   156  type importerFunc func(string) (*types.Package, error)
   157  
   158  func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
   159  

View as plain text