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