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

381 lines
9.5 KiB
Go
Raw Normal View History

// Copyright 2015 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 objabi
import (
"flag"
"fmt"
"internal/bisect"
"internal/buildcfg"
"io"
"log"
"os"
"reflect"
"sort"
"strconv"
"strings"
)
func Flagcount(name, usage string, val *int) {
flag.Var((*count)(val), name, usage)
}
func Flagfn1(name, usage string, f func(string)) {
flag.Var(fn1(f), name, usage)
}
func Flagprint(w io.Writer) {
flag.CommandLine.SetOutput(w)
flag.PrintDefaults()
}
func Flagparse(usage func()) {
flag.Usage = usage
os.Args = expandArgs(os.Args)
flag.Parse()
}
// expandArgs expands "response files" arguments in the provided slice.
//
// A "response file" argument starts with '@' and the rest of that
// argument is a filename with CR-or-CRLF-separated arguments. Each
// argument in the named files can also contain response file
// arguments. See Issue 18468.
//
// The returned slice 'out' aliases 'in' iff the input did not contain
// any response file arguments.
//
// TODO: handle relative paths of recursive expansions in different directories?
// Is there a spec for this? Are relative paths allowed?
func expandArgs(in []string) (out []string) {
// out is nil until we see a "@" argument.
for i, s := range in {
if strings.HasPrefix(s, "@") {
if out == nil {
out = make([]string, 0, len(in)*2)
out = append(out, in[:i]...)
}
slurp, err := os.ReadFile(s[1:])
if err != nil {
log.Fatal(err)
}
args := strings.Split(strings.TrimSpace(strings.ReplaceAll(string(slurp), "\r", "")), "\n")
for i, arg := range args {
args[i] = DecodeArg(arg)
}
out = append(out, expandArgs(args)...)
} else if out != nil {
out = append(out, s)
}
}
if out == nil {
return in
}
return
}
func AddVersionFlag() {
flag.Var(versionFlag{}, "V", "print version and exit")
}
var buildID string // filled in by linker
type versionFlag struct{}
func (versionFlag) IsBoolFlag() bool { return true }
func (versionFlag) Get() any { return nil }
func (versionFlag) String() string { return "" }
func (versionFlag) Set(s string) error {
name := os.Args[0]
name = name[strings.LastIndex(name, `/`)+1:]
name = name[strings.LastIndex(name, `\`)+1:]
cmd/go: switch to entirely content-based staleness determination This CL changes the go command to base all its rebuilding decisions on the content of the files being processed and not their file system modification times. It also eliminates the special handling of release toolchains, which were previously considered always up-to-date because modification time order could not be trusted when unpacking a pre-built release. The go command previously tracked "build IDs" as a backup to modification times, to catch changes not reflected in modification times. For example, if you remove one .go file in a package with multiple .go files, there is no modification time remaining in the system that indicates that the installed package is out of date. The old build ID was the hash of a list of file names and a few other factors, expected to change if those factors changed. This CL moves to using this kind of build ID as the only way to detect staleness, making sure that the build ID hash includes all possible factors that need to influence the rebuild decision. One such factor is the compiler flags. As of this CL, if you run go build -gcflags -N cmd/gofmt you will get a gofmt where every package is built with -N, regardless of what may or may not be installed already. Another such factor is the linker flags. As of this CL, if you run go install myprog go install -ldflags=-s myprog the second go install will now correctly build a new myprog with the updated linker flags. (Previously the installed myprog appeared up-to-date, because the ldflags were not included in the build ID.) Because we have more precise information we can also validate whether the target of a "go test -c" operation is already the right binary and therefore can avoid a rebuild. This CL sets us up for having a more general build artifact cache, maybe even a step toward not having a pkg directory with .a files, but this CL does not take that step. For now the result of go install is the same as it ever was; we just do a better job of what needs to be installed. This CL does slow down builds a small amount by reading all the dependent source files in full. (The go command already read the beginning of every dependent source file to discover build tags and imports.) On my MacBook Pro, before this CL all.bash takes 3m58s, while after this CL and a few optimizations stacked above it all.bash takes 4m28s. Given that CL 73850 cut 1m43s off the all.bash time earlier today, we can afford adding 30s back for now. More optimizations are planned that should make the go command more efficient than it was even before this CL. Fixes #15799. Fixes #18369. Fixes #19340. Fixes #21477. Change-Id: I10d7ca0e31ca3f58aabb9b1f11e2e3d9d18f0bc9 Reviewed-on: https://go-review.googlesource.com/73212 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: David Crawshaw <crawshaw@golang.org>
2017-10-11 20:39:28 -04:00
name = strings.TrimSuffix(name, ".exe")
cmd/internal/objabi: make GOEXPERIMENT be a diff from default experiments Right now the rules around handling default-on experiments are complicated and a bit inconsistent. Notably, objabi.GOEXPERIMENT is set to a comma-separated list of enabled experiments, but this may not be the string a user should set the GOEXPERIMENT environment variable to get that list of experiments: if an experiment is enabled by default but gets turned off by GOEXPERIMENT, then the string we report needs to include "no"+experiment to capture that default override. This complication also seeps into the version string we print for "go tool compile -V", etc. This logic is further complicated by the fact that it only wants to include an experiment string if the set of experiments varies from the default. This CL rethinks how we handle default-on experiments. Now that experiment state is all captured in a struct, we can simplify a lot of this logic. objabi.GOEXPERIMENT will be set based on the delta from the default set of experiments, which reflects what a user would actually need to pass on the command line. Likewise, we include this delta in the "-V" output, which simplifies this logic because if there's nothing to show in the version string, the delta will be empty. Change-Id: I7ed307329541fc2c9f90edd463fbaf8e0cc9e8ee Reviewed-on: https://go-review.googlesource.com/c/go/+/307819 Trust: Austin Clements <austin@google.com> Run-TryBot: Austin Clements <austin@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
2021-04-06 16:11:17 -04:00
p := ""
if s == "goexperiment" {
// test/run.go uses this to discover the full set of
// experiment tags. Report everything.
internal/buildcfg: extract logic specific to cmd/go cmd/go/internal/cfg duplicates many of the fields of internal/buildcfg, but initializes them from a Go environment file in addition to the usual process environment. internal/buildcfg doesn't (and shouldn't) know or care about that environment file, but prior to this CL it exposed hooks for cmd/go/internal/cfg to write data back to internal/buildcfg to incorporate information from the file. It also produced quirky GOEXPERIMENT strings when a non-trivial default was overridden, seemingly so that 'go env' would produce those same quirky strings in edge-cases where they are needed. This change reverses that information flow: internal/buildcfg now exports a structured type with methods — instead of top-level functions communicating through global state — so that cmd/go can utilize its marshaling and unmarshaling functionality without also needing to write results back into buildcfg package state. The quirks specific to 'go env' have been eliminated by distinguishing between the raw GOEXPERIMENT value set by the user (which is what we should report from 'go env') and the cleaned, canonical equivalent (which is what we should use in the build cache key). For #51461. Change-Id: I4ef5b7c58b1fb3468497649a6d2fb6c19aa06c70 Reviewed-on: https://go-review.googlesource.com/c/go/+/393574 Trust: Bryan Mills <bcmills@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-16 16:25:47 -04:00
p = " X:" + strings.Join(buildcfg.Experiment.All(), ",")
} else {
internal/buildcfg: extract logic specific to cmd/go cmd/go/internal/cfg duplicates many of the fields of internal/buildcfg, but initializes them from a Go environment file in addition to the usual process environment. internal/buildcfg doesn't (and shouldn't) know or care about that environment file, but prior to this CL it exposed hooks for cmd/go/internal/cfg to write data back to internal/buildcfg to incorporate information from the file. It also produced quirky GOEXPERIMENT strings when a non-trivial default was overridden, seemingly so that 'go env' would produce those same quirky strings in edge-cases where they are needed. This change reverses that information flow: internal/buildcfg now exports a structured type with methods — instead of top-level functions communicating through global state — so that cmd/go can utilize its marshaling and unmarshaling functionality without also needing to write results back into buildcfg package state. The quirks specific to 'go env' have been eliminated by distinguishing between the raw GOEXPERIMENT value set by the user (which is what we should report from 'go env') and the cleaned, canonical equivalent (which is what we should use in the build cache key). For #51461. Change-Id: I4ef5b7c58b1fb3468497649a6d2fb6c19aa06c70 Reviewed-on: https://go-review.googlesource.com/c/go/+/393574 Trust: Bryan Mills <bcmills@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-16 16:25:47 -04:00
// If the enabled experiments differ from the baseline,
// include that difference.
internal/buildcfg: extract logic specific to cmd/go cmd/go/internal/cfg duplicates many of the fields of internal/buildcfg, but initializes them from a Go environment file in addition to the usual process environment. internal/buildcfg doesn't (and shouldn't) know or care about that environment file, but prior to this CL it exposed hooks for cmd/go/internal/cfg to write data back to internal/buildcfg to incorporate information from the file. It also produced quirky GOEXPERIMENT strings when a non-trivial default was overridden, seemingly so that 'go env' would produce those same quirky strings in edge-cases where they are needed. This change reverses that information flow: internal/buildcfg now exports a structured type with methods — instead of top-level functions communicating through global state — so that cmd/go can utilize its marshaling and unmarshaling functionality without also needing to write results back into buildcfg package state. The quirks specific to 'go env' have been eliminated by distinguishing between the raw GOEXPERIMENT value set by the user (which is what we should report from 'go env') and the cleaned, canonical equivalent (which is what we should use in the build cache key). For #51461. Change-Id: I4ef5b7c58b1fb3468497649a6d2fb6c19aa06c70 Reviewed-on: https://go-review.googlesource.com/c/go/+/393574 Trust: Bryan Mills <bcmills@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
2022-03-16 16:25:47 -04:00
if goexperiment := buildcfg.Experiment.String(); goexperiment != "" {
p = " X:" + goexperiment
}
}
// The go command invokes -V=full to get a unique identifier
// for this tool. It is assumed that the release version is sufficient
// for releases, but during development we include the full
// build ID of the binary, so that if the compiler is changed and
// rebuilt, we notice and rebuild all packages.
if s == "full" {
if strings.Contains(buildcfg.Version, "devel") {
p += " buildID=" + buildID
}
}
fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p)
os.Exit(0)
return nil
}
// count is a flag.Value that is like a flag.Bool and a flag.Int.
// If used as -name, it increments the count, but -name=x sets the count.
// Used for verbose flag -v.
type count int
func (c *count) String() string {
return fmt.Sprint(int(*c))
}
func (c *count) Set(s string) error {
switch s {
case "true":
*c++
case "false":
*c = 0
default:
n, err := strconv.Atoi(s)
if err != nil {
return fmt.Errorf("invalid count %q", s)
}
*c = count(n)
}
return nil
}
func (c *count) Get() any {
return int(*c)
}
func (c *count) IsBoolFlag() bool {
return true
}
func (c *count) IsCountFlag() bool {
return true
}
type fn1 func(string)
func (f fn1) Set(s string) error {
f(s)
return nil
}
func (f fn1) String() string { return "" }
// DecodeArg decodes an argument.
//
// This function is public for testing with the parallel encoder.
func DecodeArg(arg string) string {
// If no encoding, fastpath out.
if !strings.ContainsAny(arg, "\\\n") {
return arg
}
var b strings.Builder
var wasBS bool
for _, r := range arg {
if wasBS {
switch r {
case '\\':
b.WriteByte('\\')
case 'n':
b.WriteByte('\n')
default:
// This shouldn't happen. The only backslashes that reach here
// should encode '\n' and '\\' exclusively.
panic("badly formatted input")
}
} else if r == '\\' {
wasBS = true
continue
} else {
b.WriteRune(r)
}
wasBS = false
}
return b.String()
}
type debugField struct {
name string
help string
concurrentOk bool // true if this field/flag is compatible with concurrent compilation
val any // *int or *string
}
type DebugFlag struct {
tab map[string]debugField
concurrentOk *bool // this is non-nil only for compiler's DebugFlags, but only compiler has concurrent:ok fields
debugSSA DebugSSA // this is non-nil only for compiler's DebugFlags.
}
// A DebugSSA function is called to set a -d ssa/... option.
// If nil, those options are reported as invalid options.
// If DebugSSA returns a non-empty string, that text is reported as a compiler error.
// If phase is "help", it should print usage information and terminate the process.
type DebugSSA func(phase, flag string, val int, valString string) string
// NewDebugFlag constructs a DebugFlag for the fields of debug, which
// must be a pointer to a struct.
//
// Each field of *debug is a different value, named for the lower-case of the field name.
// Each field must be an int or string and must have a `help` struct tag.
// There may be an "Any bool" field, which will be set if any debug flags are set.
//
// The returned flag takes a comma-separated list of settings.
// Each setting is name=value; for ints, name is short for name=1.
//
// If debugSSA is non-nil, any debug flags of the form ssa/... will be
// passed to debugSSA for processing.
func NewDebugFlag(debug any, debugSSA DebugSSA) *DebugFlag {
flag := &DebugFlag{
tab: make(map[string]debugField),
debugSSA: debugSSA,
}
v := reflect.ValueOf(debug).Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
ptr := v.Field(i).Addr().Interface()
if f.Name == "ConcurrentOk" {
switch ptr := ptr.(type) {
default:
panic("debug.ConcurrentOk must have type bool")
case *bool:
flag.concurrentOk = ptr
}
continue
}
name := strings.ToLower(f.Name)
help := f.Tag.Get("help")
if help == "" {
panic(fmt.Sprintf("debug.%s is missing help text", f.Name))
}
concurrent := f.Tag.Get("concurrent")
switch ptr.(type) {
default:
panic(fmt.Sprintf("debug.%s has invalid type %v (must be int, string, or *bisect.Matcher)", f.Name, f.Type))
case *int, *string, **bisect.Matcher:
// ok
}
flag.tab[name] = debugField{name, help, concurrent == "ok", ptr}
}
return flag
}
func (f *DebugFlag) Set(debugstr string) error {
if debugstr == "" {
return nil
}
all: replace strings.Split with strings.SplitSeq In Go 1.25+, strings.SplitSeq offers better performance. Here are the benchmark results comparing strings.Split and strings.SplitSeq in a for-loop, with the benchmark code located in src/strings/iter_test.go: goos: darwin goarch: amd64 pkg: cmd/go/internal/auth cpu: Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ ParseGitAuth/standard-8 281.4n ± 1% 218.0n ± 11% -22.54% (p=0.000 n=10) ParseGitAuth/with_url-8 549.1n ± 1% 480.5n ± 13% -12.48% (p=0.002 n=10) ParseGitAuth/minimal-8 235.4n ± 1% 197.3n ± 7% -16.20% (p=0.000 n=10) ParseGitAuth/complex-8 797.6n ± 2% 805.2n ± 4% ~ (p=0.481 n=10) ParseGitAuth/empty-8 87.48n ± 3% 63.25n ± 6% -27.71% (p=0.000 n=10) ParseGitAuth/malformed-8 228.8n ± 1% 171.2n ± 3% -25.17% (p=0.000 n=10) geomean 288.9n 237.7n -17.72% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ ParseGitAuth/standard-8 192.00 ± 0% 96.00 ± 0% -50.00% (p=0.000 n=10) ParseGitAuth/with_url-8 400.0 ± 0% 288.0 ± 0% -28.00% (p=0.000 n=10) ParseGitAuth/minimal-8 144.00 ± 0% 80.00 ± 0% -44.44% (p=0.000 n=10) ParseGitAuth/complex-8 528.0 ± 0% 400.0 ± 0% -24.24% (p=0.000 n=10) ParseGitAuth/empty-8 32.00 ± 0% 16.00 ± 0% -50.00% (p=0.000 n=10) ParseGitAuth/malformed-8 176.00 ± 0% 80.00 ± 0% -54.55% (p=0.000 n=10) geomean 179.0 102.1 -42.96% │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ ParseGitAuth/standard-8 3.000 ± 0% 2.000 ± 0% -33.33% (p=0.000 n=10) ParseGitAuth/with_url-8 4.000 ± 0% 3.000 ± 0% -25.00% (p=0.000 n=10) ParseGitAuth/minimal-8 3.000 ± 0% 2.000 ± 0% -33.33% (p=0.000 n=10) ParseGitAuth/complex-8 4.000 ± 0% 3.000 ± 0% -25.00% (p=0.000 n=10) ParseGitAuth/empty-8 2.000 ± 0% 1.000 ± 0% -50.00% (p=0.000 n=10) ParseGitAuth/malformed-8 3.000 ± 0% 2.000 ± 0% -33.33% (p=0.000 n=10) geomean 3.086 2.040 -33.91% Updates #69315. Change-Id: Id0219edea45d9658d527b863162ebe917e7821d9 GitHub-Last-Rev: 392b315e122f2c9ef8703ca2dbce8f82ec198556 GitHub-Pull-Request: golang/go#75259 Reviewed-on: https://go-review.googlesource.com/c/go/+/701015 Reviewed-by: Keith Randall <khr@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Knyszek <mknyszek@google.com> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com> Reviewed-by: Keith Randall <khr@google.com> Auto-Submit: Emmanuel Odeke <emmanuel@orijtech.com>
2025-09-15 14:39:58 +00:00
for name := range strings.SplitSeq(debugstr, ",") {
if name == "" {
continue
}
// display help about the debug option itself and quit
if name == "help" {
fmt.Print(debugHelpHeader)
maxLen, names := 0, []string{}
if f.debugSSA != nil {
maxLen = len("ssa/help")
}
for name := range f.tab {
if len(name) > maxLen {
maxLen = len(name)
}
names = append(names, name)
}
sort.Strings(names)
// Indent multi-line help messages.
nl := fmt.Sprintf("\n\t%-*s\t", maxLen, "")
for _, name := range names {
help := f.tab[name].help
fmt.Printf("\t%-*s\t%s\n", maxLen, name, strings.ReplaceAll(help, "\n", nl))
}
if f.debugSSA != nil {
// ssa options have their own help
fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging")
}
os.Exit(0)
}
val, valstring, haveInt := 1, "", true
if i := strings.IndexAny(name, "=:"); i >= 0 {
var err error
name, valstring = name[:i], name[i+1:]
val, err = strconv.Atoi(valstring)
if err != nil {
val, haveInt = 1, false
}
}
if t, ok := f.tab[name]; ok {
switch vp := t.val.(type) {
case nil:
// Ignore
case *string:
*vp = valstring
case *int:
if !haveInt {
log.Fatalf("invalid debug value %v", name)
}
*vp = val
case **bisect.Matcher:
var err error
*vp, err = bisect.New(valstring)
if err != nil {
log.Fatalf("debug flag %v: %v", name, err)
}
default:
panic("bad debugtab type")
}
// assembler DebugFlags don't have a ConcurrentOk field to reset, so check against that.
if !t.concurrentOk && f.concurrentOk != nil {
*f.concurrentOk = false
}
} else if f.debugSSA != nil && strings.HasPrefix(name, "ssa/") {
// expect form ssa/phase/flag
// e.g. -d=ssa/generic_cse/time
// _ in phase name also matches space
phase := name[4:]
flag := "debug" // default flag is debug
if i := strings.Index(phase, "/"); i >= 0 {
flag = phase[i+1:]
phase = phase[:i]
}
err := f.debugSSA(phase, flag, val, valstring)
if err != "" {
log.Fatal(err)
}
// Setting this false for -d=ssa/... preserves old behavior
// of turning off concurrency for any debug flags.
// It's not known for sure if this is necessary, but it is safe.
*f.concurrentOk = false
} else {
return fmt.Errorf("unknown debug key %s\n", name)
}
}
return nil
}
const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
<key> is one of:
`
func (f *DebugFlag) String() string {
return ""
}