| 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. |
| 9 | package main |
| 10 | |
| 11 | import ( |
| 12 | "context" |
| 13 | "encoding/json" |
| 14 | "flag" |
| 15 | "fmt" |
| 16 | "go/types" |
| 17 | "os" |
| 18 | "sort" |
| 19 | "strings" |
| 20 | |
| 21 | "golang.org/x/tools/go/packages" |
| 22 | "golang.org/x/tools/go/types/typeutil" |
| 23 | "golang.org/x/tools/internal/tool" |
| 24 | ) |
| 25 | |
| 26 | func main() { |
| 27 | tool.Main(context.Background(), &application{Mode: "imports"}, os.Args[1:]) |
| 28 | } |
| 29 | |
| 30 | type application struct { |
| 31 | // Embed the basic profiling flags supported by the tool package |
| 32 | tool.Profile |
| 33 | |
| 34 | Deps bool `flag:"deps" help:"show dependencies too"` |
| 35 | Test bool `flag:"test" help:"include any tests implied by the patterns"` |
| 36 | Mode string `flag:"mode" help:"mode (one of files, imports, types, syntax, allsyntax)"` |
| 37 | Private bool `flag:"private" help:"show non-exported declarations too"` |
| 38 | PrintJSON bool `flag:"json" help:"print package in JSON form"` |
| 39 | BuildFlags stringListValue `flag:"buildflag" help:"pass argument to underlying build system (may be repeated)"` |
| 40 | } |
| 41 | |
| 42 | // Name implements tool.Application returning the binary name. |
| 43 | func (app *application) Name() string { return "gopackages" } |
| 44 | |
| 45 | // Usage implements tool.Application returning empty extra argument usage. |
| 46 | func (app *application) Usage() string { return "package..." } |
| 47 | |
| 48 | // ShortHelp implements tool.Application returning the main binary help. |
| 49 | func (app *application) ShortHelp() string { |
| 50 | return "gopackages loads, parses, type-checks, and prints one or more Go packages." |
| 51 | } |
| 52 | |
| 53 | // DetailedHelp implements tool.Application returning the main binary help. |
| 54 | func (app *application) DetailedHelp(f *flag.FlagSet) { |
| 55 | fmt.Fprint(f.Output(), ` |
| 56 | Packages are specified using the notation of "go list", |
| 57 | or other underlying build system. |
| 58 | |
| 59 | Flags: |
| 60 | `) |
| 61 | f.PrintDefaults() |
| 62 | } |
| 63 | |
| 64 | // Run takes the args after flag processing and performs the specified query. |
| 65 | func (app *application) Run(ctx context.Context, args ...string) error { |
| 66 | if len(args) == 0 { |
| 67 | return tool.CommandLineErrorf("not enough arguments") |
| 68 | } |
| 69 | |
| 70 | // Load, parse, and type-check the packages named on the command line. |
| 71 | cfg := &packages.Config{ |
| 72 | Mode: packages.LoadSyntax, |
| 73 | Tests: app.Test, |
| 74 | BuildFlags: app.BuildFlags, |
| 75 | } |
| 76 | |
| 77 | // -mode flag |
| 78 | switch strings.ToLower(app.Mode) { |
| 79 | case "files": |
| 80 | cfg.Mode = packages.LoadFiles |
| 81 | case "imports": |
| 82 | cfg.Mode = packages.LoadImports |
| 83 | case "types": |
| 84 | cfg.Mode = packages.LoadTypes |
| 85 | case "syntax": |
| 86 | cfg.Mode = packages.LoadSyntax |
| 87 | case "allsyntax": |
| 88 | cfg.Mode = packages.LoadAllSyntax |
| 89 | default: |
| 90 | return tool.CommandLineErrorf("invalid mode: %s", app.Mode) |
| 91 | } |
| 92 | |
| 93 | lpkgs, err := packages.Load(cfg, args...) |
| 94 | if err != nil { |
| 95 | return err |
| 96 | } |
| 97 | |
| 98 | // -deps: print dependencies too. |
| 99 | if app.Deps { |
| 100 | // We can't use packages.All because |
| 101 | // we need an ordered traversal. |
| 102 | var all []*packages.Package // postorder |
| 103 | seen := make(map[*packages.Package]bool) |
| 104 | var visit func(*packages.Package) |
| 105 | visit = func(lpkg *packages.Package) { |
| 106 | if !seen[lpkg] { |
| 107 | seen[lpkg] = true |
| 108 | |
| 109 | // visit imports |
| 110 | var importPaths []string |
| 111 | for path := range lpkg.Imports { |
| 112 | importPaths = append(importPaths, path) |
| 113 | } |
| 114 | sort.Strings(importPaths) // for determinism |
| 115 | for _, path := range importPaths { |
| 116 | visit(lpkg.Imports[path]) |
| 117 | } |
| 118 | |
| 119 | all = append(all, lpkg) |
| 120 | } |
| 121 | } |
| 122 | for _, lpkg := range lpkgs { |
| 123 | visit(lpkg) |
| 124 | } |
| 125 | lpkgs = all |
| 126 | } |
| 127 | |
| 128 | for _, lpkg := range lpkgs { |
| 129 | app.print(lpkg) |
| 130 | } |
| 131 | return nil |
| 132 | } |
| 133 | |
| 134 | func (app *application) print(lpkg *packages.Package) { |
| 135 | if app.PrintJSON { |
| 136 | data, _ := json.MarshalIndent(lpkg, "", "\t") |
| 137 | os.Stdout.Write(data) |
| 138 | return |
| 139 | } |
| 140 | // title |
| 141 | var kind string |
| 142 | // TODO(matloob): If IsTest is added back print "test command" or |
| 143 | // "test package" for packages with IsTest == true. |
| 144 | if lpkg.Name == "main" { |
| 145 | kind += "command" |
| 146 | } else { |
| 147 | kind += "package" |
| 148 | } |
| 149 | fmt.Printf("Go %s %q:\n", kind, lpkg.ID) // unique ID |
| 150 | fmt.Printf("\tpackage %s\n", lpkg.Name) |
| 151 | |
| 152 | // characterize type info |
| 153 | if lpkg.Types == nil { |
| 154 | fmt.Printf("\thas no exported type info\n") |
| 155 | } else if !lpkg.Types.Complete() { |
| 156 | fmt.Printf("\thas incomplete exported type info\n") |
| 157 | } else if len(lpkg.Syntax) == 0 { |
| 158 | fmt.Printf("\thas complete exported type info\n") |
| 159 | } else { |
| 160 | fmt.Printf("\thas complete exported type info and typed ASTs\n") |
| 161 | } |
| 162 | if lpkg.Types != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 { |
| 163 | fmt.Printf("\thas an error among its dependencies\n") |
| 164 | } |
| 165 | |
| 166 | // source files |
| 167 | for _, src := range lpkg.GoFiles { |
| 168 | fmt.Printf("\tfile %s\n", src) |
| 169 | } |
| 170 | |
| 171 | // imports |
| 172 | var lines []string |
| 173 | for importPath, imp := range lpkg.Imports { |
| 174 | var line string |
| 175 | if imp.ID == importPath { |
| 176 | line = fmt.Sprintf("\timport %q", importPath) |
| 177 | } else { |
| 178 | line = fmt.Sprintf("\timport %q => %q", importPath, imp.ID) |
| 179 | } |
| 180 | lines = append(lines, line) |
| 181 | } |
| 182 | sort.Strings(lines) |
| 183 | for _, line := range lines { |
| 184 | fmt.Println(line) |
| 185 | } |
| 186 | |
| 187 | // errors |
| 188 | for _, err := range lpkg.Errors { |
| 189 | fmt.Printf("\t%s\n", err) |
| 190 | } |
| 191 | |
| 192 | // package members (TypeCheck or WholeProgram mode) |
| 193 | if lpkg.Types != nil { |
| 194 | qual := types.RelativeTo(lpkg.Types) |
| 195 | scope := lpkg.Types.Scope() |
| 196 | for _, name := range scope.Names() { |
| 197 | obj := scope.Lookup(name) |
| 198 | if !obj.Exported() && !app.Private { |
| 199 | continue // skip unexported names |
| 200 | } |
| 201 | |
| 202 | fmt.Printf("\t%s\n", types.ObjectString(obj, qual)) |
| 203 | if _, ok := obj.(*types.TypeName); ok { |
| 204 | for _, meth := range typeutil.IntuitiveMethodSet(obj.Type(), nil) { |
| 205 | if !meth.Obj().Exported() && !app.Private { |
| 206 | continue // skip unexported names |
| 207 | } |
| 208 | fmt.Printf("\t%s\n", types.SelectionString(meth, qual)) |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | fmt.Println() |
| 215 | } |
| 216 | |
| 217 | // stringListValue is a flag.Value that accumulates strings. |
| 218 | // e.g. --flag=one --flag=two would produce []string{"one", "two"}. |
| 219 | type stringListValue []string |
| 220 | |
| 221 | func newStringListValue(val []string, p *[]string) *stringListValue { |
| 222 | *p = val |
| 223 | return (*stringListValue)(p) |
| 224 | } |
| 225 | |
| 226 | func (ss *stringListValue) Get() interface{} { return []string(*ss) } |
| 227 | |
| 228 | func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) } |
| 229 | |
| 230 | func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil } |
| 231 |
Members