github.com/jmigpin/editor@v1.6.0/core/godebug/flags.go (about)

     1  package godebug
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"io"
     7  	"path/filepath"
     8  
     9  	"github.com/jmigpin/editor/util/flagutil"
    10  )
    11  
    12  type flags struct {
    13  	stderr io.Writer
    14  
    15  	mode struct {
    16  		run     bool
    17  		test    bool
    18  		build   bool
    19  		connect bool
    20  	}
    21  
    22  	verbose             bool
    23  	work                bool
    24  	syncSend            bool
    25  	stringifyBytesRunes bool
    26  	srcLines            bool
    27  	toolExec            string // ex: "wine" will run "wine args..."
    28  	outFilename         string // build, ex: -o filename
    29  	address             string // build/connect
    30  	env                 []string
    31  	paths               []string // dirs/files to annotate (args from cmd line)
    32  	usePkgLinks         bool
    33  
    34  	unknownArgs []string // unknown args to pass down to tooling
    35  	unnamedArgs []string // args without name (ex: filenames)
    36  	binaryArgs  []string // to be passed to the binary when running
    37  
    38  }
    39  
    40  func (fl *flags) parseArgs(args []string) error {
    41  	if len(args) == 0 {
    42  		return fl.usageErr()
    43  	}
    44  	name := "GoDebug " + args[0]
    45  	switch args[0] {
    46  	case "run":
    47  		fl.mode.run = true
    48  		return fl.parseRunArgs(name, args[1:])
    49  	case "test":
    50  		fl.mode.test = true
    51  		return fl.parseTestArgs(name, args[1:])
    52  	case "build":
    53  		fl.mode.build = true
    54  		return fl.parseBuildArgs(name, args[1:])
    55  	case "connect":
    56  		fl.mode.connect = true
    57  		return fl.parseConnectArgs(name, args[1:])
    58  	default:
    59  		return fl.usageErr()
    60  	}
    61  }
    62  
    63  func (fl *flags) usageErr() error {
    64  	fl.printCmdUsage()
    65  	return flag.ErrHelp
    66  }
    67  
    68  func (fl *flags) printCmdUsage() {
    69  	fmt.Fprint(fl.stderr, cmdUsage())
    70  }
    71  
    72  //----------
    73  
    74  func (fl *flags) parseRunArgs(name string, args []string) error {
    75  	fs := fl.newFlagSet(name)
    76  
    77  	fl.addPathsFlag(fs)
    78  	fl.addWorkFlag(fs)
    79  	fl.addVerboseFlag(fs)
    80  	fl.addToolExecFlag(fs)
    81  	fl.addSyncSendFlag(fs)
    82  	fl.addStringifyBytesRunesFlag(fs)
    83  	fl.addSrcLinesFlag(fs)
    84  	fl.addEnvFlag(fs)
    85  	fl.addUsePkgLinksFlag(fs)
    86  
    87  	m := goBuildBooleanFlags()
    88  	return fl.parse(name, fs, args, m)
    89  }
    90  
    91  func (fl *flags) parseTestArgs(name string, args []string) error {
    92  	// support test "-args" special flag
    93  	for i, a := range args {
    94  		if a == "-args" || a == "--args" {
    95  			args, fl.binaryArgs = args[:i], args[i+1:] // exclude
    96  			break
    97  		}
    98  	}
    99  
   100  	fs := fl.newFlagSet(name)
   101  
   102  	fl.addPathsFlag(fs)
   103  	fl.addWorkFlag(fs)
   104  	fl.addVerboseFlag(fs)
   105  	fl.addToolExecFlag(fs)
   106  	fl.addSyncSendFlag(fs)
   107  	fl.addStringifyBytesRunesFlag(fs)
   108  	fl.addSrcLinesFlag(fs)
   109  	fl.addEnvFlag(fs)
   110  	fl.addTestRunFlag(fs)
   111  	fl.addTestVFlag(fs)
   112  	fl.addUsePkgLinksFlag(fs)
   113  
   114  	m := joinMaps(goBuildBooleanFlags(), goTestBooleanFlags())
   115  	return fl.parse(name, fs, args, m)
   116  }
   117  
   118  func (fl *flags) parseBuildArgs(name string, args []string) error {
   119  	fs := fl.newFlagSet(name)
   120  
   121  	fl.addPathsFlag(fs)
   122  	fl.addWorkFlag(fs)
   123  	fl.addVerboseFlag(fs)
   124  	fl.addSyncSendFlag(fs)
   125  	fl.addStringifyBytesRunesFlag(fs)
   126  	fl.addSrcLinesFlag(fs)
   127  	fl.addEnvFlag(fs)
   128  	fl.addAddressFlag(fs)
   129  	fl.addOutFilenameFlag(fs)
   130  	fl.addUsePkgLinksFlag(fs)
   131  
   132  	m := goBuildBooleanFlags()
   133  	return fl.parse(name, fs, args, m)
   134  }
   135  
   136  func (fl *flags) parseConnectArgs(name string, args []string) error {
   137  	fs := fl.newFlagSet(name)
   138  
   139  	fl.addAddressFlag(fs)
   140  	fl.addToolExecFlag(fs)
   141  
   142  	// commented: strict parsing, no unknown flags allowed
   143  	//return fl.parse(name, fs, args)
   144  	return fs.Parse(args)
   145  }
   146  
   147  //----------
   148  
   149  func (fl *flags) addWorkFlag(fs *flag.FlagSet) {
   150  	fs.BoolVar(&fl.work, "work", false, "print workdir and don't cleanup on exit")
   151  }
   152  func (fl *flags) addVerboseFlag(fs *flag.FlagSet) {
   153  	fs.BoolVar(&fl.verbose, "verbose", false, "print extra godebug build info")
   154  }
   155  func (fl *flags) addSyncSendFlag(fs *flag.FlagSet) {
   156  	fs.BoolVar(&fl.syncSend, "syncsend", false, "Don't send msgs in chunks (slow). Useful to get msgs before a crash.")
   157  }
   158  func (fl *flags) addStringifyBytesRunesFlag(fs *flag.FlagSet) {
   159  	fs.BoolVar(&fl.stringifyBytesRunes, "sbr", false, "Stringify bytes/runes as string (ex: [97 98 99] outputs as \"abc\")")
   160  }
   161  func (fl *flags) addSrcLinesFlag(fs *flag.FlagSet) {
   162  	fs.BoolVar(&fl.srcLines, "srclines", true, "add src reference lines to the compilation such that in case of panics, the stack refers to the original src file")
   163  }
   164  func (fl *flags) addToolExecFlag(fs *flag.FlagSet) {
   165  	fs.StringVar(&fl.toolExec, "toolexec", "", "a program to invoke before the program argument. Useful to run a tool with the output file (ex: wine)")
   166  }
   167  func (fl *flags) addAddressFlag(fs *flag.FlagSet) {
   168  	fs.StringVar(&fl.address, "addr", ":8078", "address to connect to, built into the binary")
   169  }
   170  func (fl *flags) addOutFilenameFlag(fs *flag.FlagSet) {
   171  	fs.StringVar(&fl.outFilename, "o", "", "output filename")
   172  }
   173  func (fl *flags) addTestRunFlag(fs *flag.FlagSet) {
   174  	ff := flagutil.StringFuncFlag(func(s string) error {
   175  		u := "-test.run=" + s
   176  		fl.binaryArgs = append([]string{u}, fl.binaryArgs...)
   177  		return nil
   178  	})
   179  	fs.Var(ff, "run", "`string` with test name pattern to run")
   180  }
   181  func (fl *flags) addTestVFlag(fs *flag.FlagSet) {
   182  	ff := flagutil.BoolFuncFlag(func(s string) error {
   183  		u := "-test.v"
   184  		fl.binaryArgs = append([]string{u}, fl.binaryArgs...)
   185  		return nil
   186  	})
   187  	fs.Var(ff, "v", "`bool` verbose")
   188  }
   189  
   190  func (fl *flags) addEnvFlag(fs *flag.FlagSet) {
   191  	ff := flagutil.StringFuncFlag(func(s string) error {
   192  		fl.env = filepath.SplitList(s)
   193  		return nil
   194  	})
   195  	// The type in usage doc is the backquoted "string" (detected by flagset)
   196  	usage := fmt.Sprintf("`string` with env variables (ex: \"N1=V1%c...\"'", filepath.ListSeparator)
   197  	fs.Var(ff, "env", usage)
   198  }
   199  
   200  func (fl *flags) addPathsFlag(fs *flag.FlagSet) {
   201  	ff := flagutil.StringFuncFlag(func(s string) error {
   202  		fl.paths = splitCommaList(s)
   203  		return nil
   204  	})
   205  	fs.Var(ff, "paths", "comma-separated `string` of dirs/files to annotate (also see the \"//godebug:annotate*\" source code directives to set files to be annotated)")
   206  }
   207  
   208  func (fl *flags) addUsePkgLinksFlag(fs *flag.FlagSet) {
   209  	fs.BoolVar(&fl.usePkgLinks, "usepkglinks", true, "Use symbolic links to some pkgs directories to build the godebug binary. Helps solving new behaviour introduced by go1.19.x. that fails when an overlaid file depends on a new external module.")
   210  }
   211  
   212  //----------
   213  
   214  func (fl *flags) newFlagSet(name string) *flag.FlagSet {
   215  	fs := flag.NewFlagSet(name, flag.ContinueOnError)
   216  	return fs
   217  }
   218  
   219  func (fl *flags) parse(name string, fs *flag.FlagSet, args []string, isBool map[string]bool) error {
   220  
   221  	// don't show err "flag provided but not defined"
   222  	fs.SetOutput(io.Discard)
   223  
   224  	unknown, unnamed, binary, err := flagutil.ParseFlagSetSets(fs, args, isBool)
   225  	if err != nil {
   226  		if err == flag.ErrHelp {
   227  			fmt.Fprintf(fl.stderr, "Usage of %v:\n", name)
   228  			fs.SetOutput(fl.stderr)
   229  			fs.PrintDefaults()
   230  		}
   231  		return err
   232  	}
   233  	fl.unknownArgs = unknown
   234  	fl.unnamedArgs = unnamed
   235  	fl.binaryArgs = append(fl.binaryArgs, binary...)
   236  
   237  	//spew.Dump(fl.flags)
   238  
   239  	return nil
   240  }
   241  
   242  //----------
   243  //----------
   244  //----------
   245  
   246  func cmdUsage() string {
   247  	return `Usage:
   248  	GoDebug <command> [arguments]
   249  The commands are:
   250  	run		build and run program with godebug data
   251  	test		test packages compiled with godebug data
   252  	build 	build binary with godebug data (allows remote debug)
   253  	connect	connect to a binary built with godebug data (allows remote debug)
   254  Env variables:
   255  	GODEBUG_BUILD_FLAGS	comma separated flags for build
   256  Examples:
   257  	GoDebug run
   258  	GoDebug run -help
   259  	GoDebug run main.go -arg1 -arg2
   260  	GoDebug run -paths=dir1,file2.go,dir3 main.go -arg1 -arg2
   261  	GoDebug run -tags=xproto main.go
   262  	GoDebug run -env=GODEBUG_BUILD_FLAGS=-tags=xproto main.go
   263  	GoDebug test
   264  	GoDebug test -help
   265  	GoDebug test -run=mytest -v
   266  	GoDebug test a_test.go -test.run=mytest -test.v
   267  	GoDebug test a_test.go -test.count 5
   268  	GoDebug build -help
   269  	GoDebug build -addr=:8078 main.go
   270  	GoDebug connect -help
   271  	GoDebug connect -addr=:8078
   272  `
   273  }
   274  
   275  //----------
   276  //----------
   277  //----------
   278  
   279  func joinMaps(ms ...map[string]bool) map[string]bool {
   280  	m := map[string]bool{}
   281  	for _, m2 := range ms {
   282  		for k, v := range m2 {
   283  			m[k] = v
   284  		}
   285  	}
   286  	return m
   287  }
   288  
   289  func goBuildBooleanFlags() map[string]bool {
   290  	return map[string]bool{
   291  		"a":          true,
   292  		"i":          true,
   293  		"n":          true,
   294  		"v":          true,
   295  		"x":          true,
   296  		"race":       true,
   297  		"msan":       true,
   298  		"asan":       true,
   299  		"work":       true,
   300  		"linkshared": true,
   301  		"modcacherw": true,
   302  		"trimpath":   true,
   303  		"buildvcs":   true,
   304  	}
   305  }
   306  func goTestBooleanFlags() map[string]bool {
   307  	return map[string]bool{
   308  		"c":        true,
   309  		"json":     true,
   310  		"cover":    true,
   311  		"failfast": true,
   312  		"short":    true,
   313  		"benchmem": true,
   314  	}
   315  }