golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/internal/tool/tool.go (about) 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 // Package tool is a harness for writing Go tools. 6 package tool 7 8 import ( 9 "context" 10 "flag" 11 "fmt" 12 "log" 13 "os" 14 "reflect" 15 "runtime" 16 "runtime/pprof" 17 "runtime/trace" 18 "strings" 19 "time" 20 ) 21 22 // This file is a harness for writing your main function. 23 // The original version of the file is in golang.org/x/tools/internal/tool. 24 // 25 // It adds a method to the Application type 26 // Main(name, usage string, args []string) 27 // which should normally be invoked from a true main as follows: 28 // func main() { 29 // (&Application{}).Main("myapp", "non-flag-command-line-arg-help", os.Args[1:]) 30 // } 31 // It recursively scans the application object for fields with a tag containing 32 // `flag:"flagnames" help:"short help text"`` 33 // uses all those fields to build command line flags. It will split flagnames on 34 // commas and add a flag per name. 35 // It expects the Application type to have a method 36 // Run(context.Context, args...string) error 37 // which it invokes only after all command line flag processing has been finished. 38 // If Run returns an error, the error will be printed to stderr and the 39 // application will quit with a non zero exit status. 40 41 // Profile can be embedded in your application struct to automatically 42 // add command line arguments and handling for the common profiling methods. 43 type Profile struct { 44 CPU string `flag:"profile.cpu" help:"write CPU profile to this file"` 45 Memory string `flag:"profile.mem" help:"write memory profile to this file"` 46 Alloc string `flag:"profile.alloc" help:"write alloc profile to this file"` 47 Trace string `flag:"profile.trace" help:"write trace log to this file"` 48 } 49 50 // Application is the interface that must be satisfied by an object passed to Main. 51 type Application interface { 52 // Name returns the application's name. It is used in help and error messages. 53 Name() string 54 // Most of the help usage is automatically generated, this string should only 55 // describe the contents of non flag arguments. 56 Usage() string 57 // ShortHelp returns the one line overview of the command. 58 ShortHelp() string 59 // DetailedHelp should print a detailed help message. It will only ever be shown 60 // when the ShortHelp is also printed, so there is no need to duplicate 61 // anything from there. 62 // It is passed the flag set so it can print the default values of the flags. 63 // It should use the flag sets configured Output to write the help to. 64 DetailedHelp(*flag.FlagSet) 65 // Run is invoked after all flag processing, and inside the profiling and 66 // error handling harness. 67 Run(ctx context.Context, args ...string) error 68 } 69 70 type SubCommand interface { 71 Parent() string 72 } 73 74 // This is the type returned by CommandLineErrorf, which causes the outer main 75 // to trigger printing of the command line help. 76 type commandLineError string 77 78 func (e commandLineError) Error() string { return string(e) } 79 80 // CommandLineErrorf is like fmt.Errorf except that it returns a value that 81 // triggers printing of the command line help. 82 // In general you should use this when generating command line validation errors. 83 func CommandLineErrorf(message string, args ...interface{}) error { 84 return commandLineError(fmt.Sprintf(message, args...)) 85 } 86 87 // Main should be invoked directly by main function. 88 // It will only return if there was no error. If an error 89 // was encountered it is printed to standard error and the 90 // application exits with an exit code of 2. 91 func Main(ctx context.Context, app Application, args []string) { 92 s := flag.NewFlagSet(app.Name(), flag.ExitOnError) 93 if err := Run(ctx, s, app, args); err != nil { 94 fmt.Fprintf(s.Output(), "%s: %v\n", app.Name(), err) 95 if _, printHelp := err.(commandLineError); printHelp { 96 // TODO(adonovan): refine this. It causes 97 // any command-line error to result in the full 98 // usage message, which typically obscures 99 // the actual error. 100 s.Usage() 101 } 102 os.Exit(2) 103 } 104 } 105 106 // Run is the inner loop for Main; invoked by Main, recursively by 107 // Run, and by various tests. It runs the application and returns an 108 // error. 109 func Run(ctx context.Context, s *flag.FlagSet, app Application, args []string) (resultErr error) { 110 s.Usage = func() { 111 if app.ShortHelp() != "" { 112 fmt.Fprintf(s.Output(), "%s\n\nUsage:\n ", app.ShortHelp()) 113 if sub, ok := app.(SubCommand); ok && sub.Parent() != "" { 114 fmt.Fprintf(s.Output(), "%s [flags] %s", sub.Parent(), app.Name()) 115 } else { 116 fmt.Fprintf(s.Output(), "%s [flags]", app.Name()) 117 } 118 if usage := app.Usage(); usage != "" { 119 fmt.Fprintf(s.Output(), " %s", usage) 120 } 121 fmt.Fprint(s.Output(), "\n") 122 } 123 app.DetailedHelp(s) 124 } 125 p := addFlags(s, reflect.StructField{}, reflect.ValueOf(app)) 126 if err := s.Parse(args); err != nil { 127 return err 128 } 129 130 if p != nil && p.CPU != "" { 131 f, err := os.Create(p.CPU) 132 if err != nil { 133 return err 134 } 135 if err := pprof.StartCPUProfile(f); err != nil { 136 f.Close() // ignore error 137 return err 138 } 139 defer func() { 140 pprof.StopCPUProfile() 141 if closeErr := f.Close(); resultErr == nil { 142 resultErr = closeErr 143 } 144 }() 145 } 146 147 if p != nil && p.Trace != "" { 148 f, err := os.Create(p.Trace) 149 if err != nil { 150 return err 151 } 152 if err := trace.Start(f); err != nil { 153 f.Close() // ignore error 154 return err 155 } 156 defer func() { 157 trace.Stop() 158 if closeErr := f.Close(); resultErr == nil { 159 resultErr = closeErr 160 } 161 log.Printf("To view the trace, run:\n$ go tool trace view %s", p.Trace) 162 }() 163 } 164 165 if p != nil && p.Memory != "" { 166 f, err := os.Create(p.Memory) 167 if err != nil { 168 return err 169 } 170 defer func() { 171 runtime.GC() // get up-to-date statistics 172 if err := pprof.WriteHeapProfile(f); err != nil { 173 log.Printf("Writing memory profile: %v", err) 174 } 175 f.Close() 176 }() 177 } 178 179 if p != nil && p.Alloc != "" { 180 f, err := os.Create(p.Alloc) 181 if err != nil { 182 return err 183 } 184 defer func() { 185 if err := pprof.Lookup("allocs").WriteTo(f, 0); err != nil { 186 log.Printf("Writing alloc profile: %v", err) 187 } 188 f.Close() 189 }() 190 } 191 192 return app.Run(ctx, s.Args()...) 193 } 194 195 // addFlags scans fields of structs recursively to find things with flag tags 196 // and add them to the flag set. 197 func addFlags(f *flag.FlagSet, field reflect.StructField, value reflect.Value) *Profile { 198 // is it a field we are allowed to reflect on? 199 if field.PkgPath != "" { 200 return nil 201 } 202 // now see if is actually a flag 203 flagNames, isFlag := field.Tag.Lookup("flag") 204 help := field.Tag.Get("help") 205 if isFlag { 206 nameList := strings.Split(flagNames, ",") 207 // add the main flag 208 addFlag(f, value, nameList[0], help) 209 if len(nameList) > 1 { 210 // and now add any aliases using the same flag value 211 fv := f.Lookup(nameList[0]).Value 212 for _, flagName := range nameList[1:] { 213 f.Var(fv, flagName, help) 214 } 215 } 216 return nil 217 } 218 // not a flag, but it might be a struct with flags in it 219 value = resolve(value.Elem()) 220 if value.Kind() != reflect.Struct { 221 return nil 222 } 223 224 // TODO(adonovan): there's no need for this special treatment of Profile: 225 // The caller can use f.Lookup("profile.cpu") etc instead. 226 p, _ := value.Addr().Interface().(*Profile) 227 // go through all the fields of the struct 228 for i := 0; i < value.Type().NumField(); i++ { 229 child := value.Type().Field(i) 230 v := value.Field(i) 231 // make sure we have a pointer 232 if v.Kind() != reflect.Ptr { 233 v = v.Addr() 234 } 235 // check if that field is a flag or contains flags 236 if fp := addFlags(f, child, v); fp != nil { 237 p = fp 238 } 239 } 240 return p 241 } 242 243 func addFlag(f *flag.FlagSet, value reflect.Value, flagName string, help string) { 244 switch v := value.Interface().(type) { 245 case flag.Value: 246 f.Var(v, flagName, help) 247 case *bool: 248 f.BoolVar(v, flagName, *v, help) 249 case *time.Duration: 250 f.DurationVar(v, flagName, *v, help) 251 case *float64: 252 f.Float64Var(v, flagName, *v, help) 253 case *int64: 254 f.Int64Var(v, flagName, *v, help) 255 case *int: 256 f.IntVar(v, flagName, *v, help) 257 case *string: 258 f.StringVar(v, flagName, *v, help) 259 case *uint: 260 f.UintVar(v, flagName, *v, help) 261 case *uint64: 262 f.Uint64Var(v, flagName, *v, help) 263 default: 264 log.Fatalf("Cannot understand flag of type %T", v) 265 } 266 } 267 268 func resolve(v reflect.Value) reflect.Value { 269 for { 270 switch v.Kind() { 271 case reflect.Interface, reflect.Ptr: 272 v = v.Elem() 273 default: 274 return v 275 } 276 } 277 }