1
0
mirror of https://github.com/golang/go synced 2024-09-30 12:08:32 -06:00
go/internal/tool/tool.go
hartzell 5adc211bf7 tools/internal/tool: refactor tool.Main() for testabilty
In it previous implementation, tool.Main was meant to be called from
an applications main().  If it encountered an error it would print the
error to standard error and exit with a non-zero status (2).

It is also called recursively and in various test functions.

Exiting on an error makes testing difficult, unnecessarily.

This change breaks the functionality into to parts: an outer
tool.MustMain() that is intended to be called by main() functions and
an inner Main that is used by MustMain() and by test functions.

None of the existing test functions use Main()'s error value, but the
failure case tests for the command line invocation of rename (#194878)
require it.

Fixes #34291

Change-Id: Id0d80fc4670d56c87398b86b1ad9fdf7a676c95b
GitHub-Last-Rev: cd64995c91c94b997754c8d8b1004afc488bf8b7
GitHub-Pull-Request: golang/tools#159
Reviewed-on: https://go-review.googlesource.com/c/tools/+/195338
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-09-19 22:27:22 +00:00

206 lines
6.4 KiB
Go

// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package tool is an opinionated harness for writing Go tools.
package tool
import (
"context"
"flag"
"fmt"
"log"
"os"
"reflect"
"runtime"
"runtime/pprof"
"runtime/trace"
"time"
)
// This file is a very opinionated harness for writing your main function.
// The original version of the file is in golang.org/x/tools/internal/tool.
//
// It adds a method to the Application type
// Main(name, usage string, args []string)
// which should normally be invoked from a true main as follows:
// func main() {
// (&Application{}).Main("myapp", "non-flag-command-line-arg-help", os.Args[1:])
// }
// It recursively scans the application object for fields with a tag containing
// `flag:"flagname" help:"short help text"``
// uses all those fields to build command line flags.
// It expects the Application type to have a method
// Run(context.Context, args...string) error
// which it invokes only after all command line flag processing has been finished.
// If Run returns an error, the error will be printed to stderr and the
// application will quit with a non zero exit status.
// Profile can be embedded in your application struct to automatically
// add command line arguments and handling for the common profiling methods.
type Profile struct {
CPU string `flag:"profile.cpu" help:"write CPU profile to this file"`
Memory string `flag:"profile.mem" help:"write memory profile to this file"`
Trace string `flag:"profile.trace" help:"write trace log to this file"`
}
// Application is the interface that must be satisfied by an object passed to Main.
type Application interface {
// Name returns the application's name. It is used in help and error messages.
Name() string
// Most of the help usage is automatically generated, this string should only
// describe the contents of non flag arguments.
Usage() string
// ShortHelp returns the one line overview of the command.
ShortHelp() string
// DetailedHelp should print a detailed help message. It will only ever be shown
// when the ShortHelp is also printed, so there is no need to duplicate
// anything from there.
// It is passed the flag set so it can print the default values of the flags.
// It should use the flag sets configured Output to write the help to.
DetailedHelp(*flag.FlagSet)
// Run is invoked after all flag processing, and inside the profiling and
// error handling harness.
Run(ctx context.Context, args ...string) error
}
// This is the type returned by CommandLineErrorf, which causes the outer main
// to trigger printing of the command line help.
type commandLineError string
func (e commandLineError) Error() string { return string(e) }
// CommandLineErrorf is like fmt.Errorf except that it returns a value that
// triggers printing of the command line help.
// In general you should use this when generating command line validation errors.
func CommandLineErrorf(message string, args ...interface{}) error {
return commandLineError(fmt.Sprintf(message, args...))
}
// Main should be invoked directly by main function.
// It will only return if there was no error. If an error
// was encountered it is printed to standard error and the
// application exits with an exit code of 2.
func Main(ctx context.Context, app Application, args []string) {
s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
s.Usage = func() {
fmt.Fprint(s.Output(), app.ShortHelp())
fmt.Fprintf(s.Output(), "\n\nUsage: %v [flags] %v\n", app.Name(), app.Usage())
app.DetailedHelp(s)
}
if err := Run(ctx, app, args); err != nil {
fmt.Fprintf(s.Output(), "%s: %v\n", app.Name(), err)
if _, printHelp := err.(commandLineError); printHelp {
s.Usage()
}
os.Exit(2)
}
}
// Run is the inner loop for Main; invoked by Main, recursively by
// Run, and by various tests. It runs the application and returns an
// error.
func Run(ctx context.Context, app Application, args []string) error {
s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
p := addFlags(s, reflect.StructField{}, reflect.ValueOf(app))
s.Parse(args)
if p != nil && p.CPU != "" {
f, err := os.Create(p.CPU)
if err != nil {
return err
}
if err := pprof.StartCPUProfile(f); err != nil {
return err
}
defer pprof.StopCPUProfile()
}
if p != nil && p.Trace != "" {
f, err := os.Create(p.Trace)
if err != nil {
return err
}
if err := trace.Start(f); err != nil {
return err
}
defer func() {
trace.Stop()
log.Printf("To view the trace, run:\n$ go tool trace view %s", p.Trace)
}()
}
if p != nil && p.Memory != "" {
f, err := os.Create(p.Memory)
if err != nil {
return err
}
defer func() {
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.Printf("Writing memory profile: %v", err)
}
f.Close()
}()
}
return app.Run(ctx, s.Args()...)
}
// addFlags scans fields of structs recursively to find things with flag tags
// and add them to the flag set.
func addFlags(f *flag.FlagSet, field reflect.StructField, value reflect.Value) *Profile {
// is it a field we are allowed to reflect on?
if field.PkgPath != "" {
return nil
}
// now see if is actually a flag
flagName, isFlag := field.Tag.Lookup("flag")
help := field.Tag.Get("help")
if !isFlag {
// not a flag, but it might be a struct with flags in it
if value.Elem().Kind() != reflect.Struct {
return nil
}
p, _ := value.Interface().(*Profile)
// go through all the fields of the struct
sv := value.Elem()
for i := 0; i < sv.Type().NumField(); i++ {
child := sv.Type().Field(i)
v := sv.Field(i)
// make sure we have a pointer
if v.Kind() != reflect.Ptr {
v = v.Addr()
}
// check if that field is a flag or contains flags
if fp := addFlags(f, child, v); fp != nil {
p = fp
}
}
return p
}
switch v := value.Interface().(type) {
case flag.Value:
f.Var(v, flagName, help)
case *bool:
f.BoolVar(v, flagName, *v, help)
case *time.Duration:
f.DurationVar(v, flagName, *v, help)
case *float64:
f.Float64Var(v, flagName, *v, help)
case *int64:
f.Int64Var(v, flagName, *v, help)
case *int:
f.IntVar(v, flagName, *v, help)
case *string:
f.StringVar(v, flagName, *v, help)
case *uint:
f.UintVar(v, flagName, *v, help)
case *uint64:
f.Uint64Var(v, flagName, *v, help)
default:
log.Fatalf("Cannot understand flag of type %T", v)
}
return nil
}