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.
4
5// The gopackages command is a diagnostic tool that demonstrates
6// how to use golang.org/x/tools/go/packages to load, parse,
7// type-check, and print one or more Go packages.
8// Its precise output is unspecified and may change.
9package main
10
11import (
12	"encoding/json"
13	"flag"
14	"fmt"
15	"go/types"
16	"log"
17	"os"
18	"runtime"
19	"runtime/pprof"
20	"runtime/trace"
21	"sort"
22	"strings"
23
24	"golang.org/x/tools/go/packages"
25	"golang.org/x/tools/go/types/typeutil"
26)
27
28// flags
29var (
30	depsFlag  = flag.Bool("deps", false, "show dependencies too")
31	testFlag  = flag.Bool("test", false, "include any tests implied by the patterns")
32	mode      = flag.String("mode", "imports", "mode (one of files, imports, types, syntax, allsyntax)")
33	private   = flag.Bool("private", false, "show non-exported declarations too")
34	printJSON = flag.Bool("json", false, "print package in JSON form")
35
36	cpuprofile = flag.String("cpuprofile", "", "write CPU profile to this file")
37	memprofile = flag.String("memprofile", "", "write memory profile to this file")
38	traceFlag  = flag.String("trace", "", "write trace log to this file")
39
40	buildFlags stringListValue
41)
42
43func init() {
44	flag.Var(&buildFlags, "buildflag", "pass argument to underlying build system (may be repeated)")
45}
46
47func usage() {
48	fmt.Fprintln(os.Stderr, `Usage: gopackages [-deps] [-cgo] [-mode=...] [-private] package...
49
50The gopackages command loads, parses, type-checks,
51and prints one or more Go packages.
52
53Packages are specified using the notation of "go list",
54or other underlying build system.
55
56Flags:`)
57	flag.PrintDefaults()
58}
59
60func main() {
61	log.SetPrefix("gopackages: ")
62	log.SetFlags(0)
63	flag.Usage = usage
64	flag.Parse()
65
66	if len(flag.Args()) == 0 {
67		usage()
68		os.Exit(1)
69	}
70
71	if *cpuprofile != "" {
72		f, err := os.Create(*cpuprofile)
73		if err != nil {
74			log.Fatal(err)
75		}
76		if err := pprof.StartCPUProfile(f); err != nil {
77			log.Fatal(err)
78		}
79		// NB: profile won't be written in case of error.
80		defer pprof.StopCPUProfile()
81	}
82
83	if *traceFlag != "" {
84		f, err := os.Create(*traceFlag)
85		if err != nil {
86			log.Fatal(err)
87		}
88		if err := trace.Start(f); err != nil {
89			log.Fatal(err)
90		}
91		// NB: trace log won't be written in case of error.
92		defer func() {
93			trace.Stop()
94			log.Printf("To view the trace, run:\n$ go tool trace view %s", *traceFlag)
95		}()
96	}
97
98	if *memprofile != "" {
99		f, err := os.Create(*memprofile)
100		if err != nil {
101			log.Fatal(err)
102		}
103		// NB: memprofile won't be written in case of error.
104		defer func() {
105			runtime.GC() // get up-to-date statistics
106			if err := pprof.WriteHeapProfile(f); err != nil {
107				log.Fatalf("Writing memory profile: %v", err)
108			}
109			f.Close()
110		}()
111	}
112
113	// Load, parse, and type-check the packages named on the command line.
114	cfg := &packages.Config{
115		Mode:       packages.LoadSyntax,
116		Tests:      *testFlag,
117		BuildFlags: buildFlags,
118	}
119
120	// -mode flag
121	switch strings.ToLower(*mode) {
122	case "files":
123		cfg.Mode = packages.LoadFiles
124	case "imports":
125		cfg.Mode = packages.LoadImports
126	case "types":
127		cfg.Mode = packages.LoadTypes
128	case "syntax":
129		cfg.Mode = packages.LoadSyntax
130	case "allsyntax":
131		cfg.Mode = packages.LoadAllSyntax
132	default:
133		log.Fatalf("invalid mode: %s", *mode)
134	}
135
136	lpkgs, err := packages.Load(cfg, flag.Args()...)
137	if err != nil {
138		log.Fatal(err)
139	}
140
141	// -deps: print dependencies too.
142	if *depsFlag {
143		// We can't use packages.All because
144		// we need an ordered traversal.
145		var all []*packages.Package // postorder
146		seen := make(map[*packages.Package]bool)
147		var visit func(*packages.Package)
148		visit = func(lpkg *packages.Package) {
149			if !seen[lpkg] {
150				seen[lpkg] = true
151
152				// visit imports
153				var importPaths []string
154				for path := range lpkg.Imports {
155					importPaths = append(importPaths, path)
156				}
157				sort.Strings(importPaths) // for determinism
158				for _, path := range importPaths {
159					visit(lpkg.Imports[path])
160				}
161
162				all = append(all, lpkg)
163			}
164		}
165		for _, lpkg := range lpkgs {
166			visit(lpkg)
167		}
168		lpkgs = all
169	}
170
171	for _, lpkg := range lpkgs {
172		print(lpkg)
173	}
174}
175
176func print(lpkg *packages.Package) {
177	if *printJSON {
178		data, _ := json.MarshalIndent(lpkg, "", "\t")
179		os.Stdout.Write(data)
180		return
181	}
182	// title
183	var kind string
184	// TODO(matloob): If IsTest is added back print "test command" or
185	// "test package" for packages with IsTest == true.
186	if lpkg.Name == "main" {
187		kind += "command"
188	} else {
189		kind += "package"
190	}
191	fmt.Printf("Go %s %q:\n", kind, lpkg.ID) // unique ID
192	fmt.Printf("\tpackage %s\n", lpkg.Name)
193
194	// characterize type info
195	if lpkg.Types == nil {
196		fmt.Printf("\thas no exported type info\n")
197	} else if !lpkg.Types.Complete() {
198		fmt.Printf("\thas incomplete exported type info\n")
199	} else if len(lpkg.Syntax) == 0 {
200		fmt.Printf("\thas complete exported type info\n")
201	} else {
202		fmt.Printf("\thas complete exported type info and typed ASTs\n")
203	}
204	if lpkg.Types != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 {
205		fmt.Printf("\thas an error among its dependencies\n")
206	}
207
208	// source files
209	for _, src := range lpkg.GoFiles {
210		fmt.Printf("\tfile %s\n", src)
211	}
212
213	// imports
214	var lines []string
215	for importPath, imp := range lpkg.Imports {
216		var line string
217		if imp.ID == importPath {
218			line = fmt.Sprintf("\timport %q", importPath)
219		} else {
220			line = fmt.Sprintf("\timport %q => %q", importPath, imp.ID)
221		}
222		lines = append(lines, line)
223	}
224	sort.Strings(lines)
225	for _, line := range lines {
226		fmt.Println(line)
227	}
228
229	// errors
230	for _, err := range lpkg.Errors {
231		fmt.Printf("\t%s\n", err)
232	}
233
234	// package members (TypeCheck or WholeProgram mode)
235	if lpkg.Types != nil {
236		qual := types.RelativeTo(lpkg.Types)
237		scope := lpkg.Types.Scope()
238		for _, name := range scope.Names() {
239			obj := scope.Lookup(name)
240			if !obj.Exported() && !*private {
241				continue // skip unexported names
242			}
243
244			fmt.Printf("\t%s\n", types.ObjectString(obj, qual))
245			if _, ok := obj.(*types.TypeName); ok {
246				for _, meth := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
247					if !meth.Obj().Exported() && !*private {
248						continue // skip unexported names
249					}
250					fmt.Printf("\t%s\n", types.SelectionString(meth, qual))
251				}
252			}
253		}
254	}
255
256	fmt.Println()
257}
258
259// stringListValue is a flag.Value that accumulates strings.
260// e.g. --flag=one --flag=two would produce []string{"one", "two"}.
261type stringListValue []string
262
263func newStringListValue(val []string, p *[]string) *stringListValue {
264	*p = val
265	return (*stringListValue)(p)
266}
267
268func (ss *stringListValue) Get() interface{} { return []string(*ss) }
269
270func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) }
271
272func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil }
273