Source file src/cmd/internal/objabi/flag.go

     1  // Copyright 2015 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 objabi
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"internal/bisect"
    11  	"internal/buildcfg"
    12  	"io"
    13  	"log"
    14  	"os"
    15  	"reflect"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  )
    20  
    21  func Flagcount(name, usage string, val *int) {
    22  	flag.Var((*count)(val), name, usage)
    23  }
    24  
    25  func Flagfn1(name, usage string, f func(string)) {
    26  	flag.Var(fn1(f), name, usage)
    27  }
    28  
    29  func Flagprint(w io.Writer) {
    30  	flag.CommandLine.SetOutput(w)
    31  	flag.PrintDefaults()
    32  }
    33  
    34  func Flagparse(usage func()) {
    35  	flag.Usage = usage
    36  	os.Args = expandArgs(os.Args)
    37  	flag.Parse()
    38  }
    39  
    40  // expandArgs expands "response files" arguments in the provided slice.
    41  //
    42  // A "response file" argument starts with '@' and the rest of that
    43  // argument is a filename with CR-or-CRLF-separated arguments. Each
    44  // argument in the named files can also contain response file
    45  // arguments. See Issue 18468.
    46  //
    47  // The returned slice 'out' aliases 'in' iff the input did not contain
    48  // any response file arguments.
    49  //
    50  // TODO: handle relative paths of recursive expansions in different directories?
    51  // Is there a spec for this? Are relative paths allowed?
    52  func expandArgs(in []string) (out []string) {
    53  	// out is nil until we see a "@" argument.
    54  	for i, s := range in {
    55  		if strings.HasPrefix(s, "@") {
    56  			if out == nil {
    57  				out = make([]string, 0, len(in)*2)
    58  				out = append(out, in[:i]...)
    59  			}
    60  			slurp, err := os.ReadFile(s[1:])
    61  			if err != nil {
    62  				log.Fatal(err)
    63  			}
    64  			args := strings.Split(strings.TrimSpace(strings.ReplaceAll(string(slurp), "\r", "")), "\n")
    65  			for i, arg := range args {
    66  				args[i] = DecodeArg(arg)
    67  			}
    68  			out = append(out, expandArgs(args)...)
    69  		} else if out != nil {
    70  			out = append(out, s)
    71  		}
    72  	}
    73  	if out == nil {
    74  		return in
    75  	}
    76  	return
    77  }
    78  
    79  func AddVersionFlag() {
    80  	flag.Var(versionFlag{}, "V", "print version and exit")
    81  }
    82  
    83  var buildID string // filled in by linker
    84  
    85  type versionFlag struct{}
    86  
    87  func (versionFlag) IsBoolFlag() bool { return true }
    88  func (versionFlag) Get() any         { return nil }
    89  func (versionFlag) String() string   { return "" }
    90  func (versionFlag) Set(s string) error {
    91  	name := os.Args[0]
    92  	name = name[strings.LastIndex(name, `/`)+1:]
    93  	name = name[strings.LastIndex(name, `\`)+1:]
    94  	name = strings.TrimSuffix(name, ".exe")
    95  
    96  	p := ""
    97  
    98  	// If the enabled experiments differ from the baseline,
    99  	// include that difference.
   100  	if goexperiment := buildcfg.Experiment.String(); goexperiment != "" {
   101  		p = " X:" + goexperiment
   102  	}
   103  
   104  	// The go command invokes -V=full to get a unique identifier
   105  	// for this tool. It is assumed that the release version is sufficient
   106  	// for releases, but during development we include the full
   107  	// build ID of the binary, so that if the compiler is changed and
   108  	// rebuilt, we notice and rebuild all packages.
   109  	if s == "full" {
   110  		if strings.Contains(buildcfg.Version, "devel") {
   111  			p += " buildID=" + buildID
   112  		}
   113  	}
   114  
   115  	fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p)
   116  	os.Exit(0)
   117  	return nil
   118  }
   119  
   120  // count is a flag.Value that is like a flag.Bool and a flag.Int.
   121  // If used as -name, it increments the count, but -name=x sets the count.
   122  // Used for verbose flag -v.
   123  type count int
   124  
   125  func (c *count) String() string {
   126  	return fmt.Sprint(int(*c))
   127  }
   128  
   129  func (c *count) Set(s string) error {
   130  	switch s {
   131  	case "true":
   132  		*c++
   133  	case "false":
   134  		*c = 0
   135  	default:
   136  		n, err := strconv.Atoi(s)
   137  		if err != nil {
   138  			return fmt.Errorf("invalid count %q", s)
   139  		}
   140  		*c = count(n)
   141  	}
   142  	return nil
   143  }
   144  
   145  func (c *count) Get() any {
   146  	return int(*c)
   147  }
   148  
   149  func (c *count) IsBoolFlag() bool {
   150  	return true
   151  }
   152  
   153  func (c *count) IsCountFlag() bool {
   154  	return true
   155  }
   156  
   157  type fn1 func(string)
   158  
   159  func (f fn1) Set(s string) error {
   160  	f(s)
   161  	return nil
   162  }
   163  
   164  func (f fn1) String() string { return "" }
   165  
   166  // DecodeArg decodes an argument.
   167  //
   168  // This function is public for testing with the parallel encoder.
   169  func DecodeArg(arg string) string {
   170  	// If no encoding, fastpath out.
   171  	if !strings.ContainsAny(arg, "\\\n") {
   172  		return arg
   173  	}
   174  
   175  	var b strings.Builder
   176  	var wasBS bool
   177  	for _, r := range arg {
   178  		if wasBS {
   179  			switch r {
   180  			case '\\':
   181  				b.WriteByte('\\')
   182  			case 'n':
   183  				b.WriteByte('\n')
   184  			default:
   185  				// This shouldn't happen. The only backslashes that reach here
   186  				// should encode '\n' and '\\' exclusively.
   187  				panic("badly formatted input")
   188  			}
   189  		} else if r == '\\' {
   190  			wasBS = true
   191  			continue
   192  		} else {
   193  			b.WriteRune(r)
   194  		}
   195  		wasBS = false
   196  	}
   197  	return b.String()
   198  }
   199  
   200  type debugField struct {
   201  	name         string
   202  	help         string
   203  	concurrentOk bool // true if this field/flag is compatible with concurrent compilation
   204  	val          any  // *int or *string
   205  }
   206  
   207  type DebugFlag struct {
   208  	tab          map[string]debugField
   209  	concurrentOk *bool    // this is non-nil only for compiler's DebugFlags, but only compiler has concurrent:ok fields
   210  	debugSSA     DebugSSA // this is non-nil only for compiler's DebugFlags.
   211  }
   212  
   213  // A DebugSSA function is called to set a -d ssa/... option.
   214  // If nil, those options are reported as invalid options.
   215  // If DebugSSA returns a non-empty string, that text is reported as a compiler error.
   216  // If phase is "help", it should print usage information and terminate the process.
   217  type DebugSSA func(phase, flag string, val int, valString string) string
   218  
   219  // NewDebugFlag constructs a DebugFlag for the fields of debug, which
   220  // must be a pointer to a struct.
   221  //
   222  // Each field of *debug is a different value, named for the lower-case of the field name.
   223  // Each field must be an int or string and must have a `help` struct tag.
   224  // There may be an "Any bool" field, which will be set if any debug flags are set.
   225  //
   226  // The returned flag takes a comma-separated list of settings.
   227  // Each setting is name=value; for ints, name is short for name=1.
   228  //
   229  // If debugSSA is non-nil, any debug flags of the form ssa/... will be
   230  // passed to debugSSA for processing.
   231  func NewDebugFlag(debug any, debugSSA DebugSSA) *DebugFlag {
   232  	flag := &DebugFlag{
   233  		tab:      make(map[string]debugField),
   234  		debugSSA: debugSSA,
   235  	}
   236  
   237  	v := reflect.ValueOf(debug).Elem()
   238  	t := v.Type()
   239  	for i := 0; i < t.NumField(); i++ {
   240  		f := t.Field(i)
   241  		ptr := v.Field(i).Addr().Interface()
   242  		if f.Name == "ConcurrentOk" {
   243  			switch ptr := ptr.(type) {
   244  			default:
   245  				panic("debug.ConcurrentOk must have type bool")
   246  			case *bool:
   247  				flag.concurrentOk = ptr
   248  			}
   249  			continue
   250  		}
   251  		name := strings.ToLower(f.Name)
   252  		help := f.Tag.Get("help")
   253  		if help == "" {
   254  			panic(fmt.Sprintf("debug.%s is missing help text", f.Name))
   255  		}
   256  		concurrent := f.Tag.Get("concurrent")
   257  
   258  		switch ptr.(type) {
   259  		default:
   260  			panic(fmt.Sprintf("debug.%s has invalid type %v (must be int, string, or *bisect.Matcher)", f.Name, f.Type))
   261  		case *int, *string, **bisect.Matcher:
   262  			// ok
   263  		}
   264  		flag.tab[name] = debugField{name, help, concurrent == "ok", ptr}
   265  	}
   266  
   267  	return flag
   268  }
   269  
   270  func (f *DebugFlag) Set(debugstr string) error {
   271  	if debugstr == "" {
   272  		return nil
   273  	}
   274  	for name := range strings.SplitSeq(debugstr, ",") {
   275  		if name == "" {
   276  			continue
   277  		}
   278  		// display help about the debug option itself and quit
   279  		if name == "help" {
   280  			fmt.Print(debugHelpHeader)
   281  			maxLen, names := 0, []string{}
   282  			if f.debugSSA != nil {
   283  				maxLen = len("ssa/help")
   284  			}
   285  			for name := range f.tab {
   286  				if len(name) > maxLen {
   287  					maxLen = len(name)
   288  				}
   289  				names = append(names, name)
   290  			}
   291  			sort.Strings(names)
   292  			// Indent multi-line help messages.
   293  			nl := fmt.Sprintf("\n\t%-*s\t", maxLen, "")
   294  			for _, name := range names {
   295  				help := f.tab[name].help
   296  				fmt.Printf("\t%-*s\t%s\n", maxLen, name, strings.ReplaceAll(help, "\n", nl))
   297  			}
   298  			if f.debugSSA != nil {
   299  				// ssa options have their own help
   300  				fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging")
   301  			}
   302  			os.Exit(0)
   303  		}
   304  
   305  		val, valstring, haveInt := 1, "", true
   306  		if i := strings.IndexAny(name, "=:"); i >= 0 {
   307  			var err error
   308  			name, valstring = name[:i], name[i+1:]
   309  			val, err = strconv.Atoi(valstring)
   310  			if err != nil {
   311  				val, haveInt = 1, false
   312  			}
   313  		}
   314  
   315  		if t, ok := f.tab[name]; ok {
   316  			switch vp := t.val.(type) {
   317  			case nil:
   318  				// Ignore
   319  			case *string:
   320  				*vp = valstring
   321  			case *int:
   322  				if !haveInt {
   323  					log.Fatalf("invalid debug value %v", name)
   324  				}
   325  				*vp = val
   326  			case **bisect.Matcher:
   327  				var err error
   328  				*vp, err = bisect.New(valstring)
   329  				if err != nil {
   330  					log.Fatalf("debug flag %v: %v", name, err)
   331  				}
   332  			default:
   333  				panic("bad debugtab type")
   334  			}
   335  			// assembler DebugFlags don't have a ConcurrentOk field to reset, so check against that.
   336  			if !t.concurrentOk && f.concurrentOk != nil {
   337  				*f.concurrentOk = false
   338  			}
   339  		} else if f.debugSSA != nil && strings.HasPrefix(name, "ssa/") {
   340  			// expect form ssa/phase/flag
   341  			// e.g. -d=ssa/generic_cse/time
   342  			// _ in phase name also matches space
   343  			phase := name[4:]
   344  			flag := "debug" // default flag is debug
   345  			if i := strings.Index(phase, "/"); i >= 0 {
   346  				flag = phase[i+1:]
   347  				phase = phase[:i]
   348  			}
   349  			err := f.debugSSA(phase, flag, val, valstring)
   350  			if err != "" {
   351  				log.Fatal(err)
   352  			}
   353  			// Setting this false for -d=ssa/... preserves old behavior
   354  			// of turning off concurrency for any debug flags.
   355  			// It's not known for sure if this is necessary, but it is safe.
   356  			*f.concurrentOk = false
   357  
   358  		} else {
   359  			return fmt.Errorf("unknown debug key %s\n", name)
   360  		}
   361  	}
   362  
   363  	return nil
   364  }
   365  
   366  const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
   367  
   368  <key> is one of:
   369  
   370  `
   371  
   372  func (f *DebugFlag) String() string {
   373  	return ""
   374  }
   375  

View as plain text