2016-03-01 22:57:46 +00:00
|
|
|
// Copyright 2015 The Go Authors. All rights reserved.
|
2015-02-13 14:40:36 -05:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
2017-04-18 12:53:25 -07:00
|
|
|
package objabi
|
2015-02-13 14:40:36 -05:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
2023-08-09 00:53:03 -07:00
|
|
|
"internal/bisect"
|
2021-04-15 23:05:49 -04:00
|
|
|
"internal/buildcfg"
|
2017-12-24 16:50:28 +00:00
|
|
|
"io"
|
2018-04-30 19:01:57 +00:00
|
|
|
"log"
|
2015-02-13 14:40:36 -05:00
|
|
|
"os"
|
2021-09-17 10:07:41 -04:00
|
|
|
"reflect"
|
|
|
|
|
"sort"
|
2015-02-13 14:40:36 -05:00
|
|
|
"strconv"
|
2017-10-05 15:37:13 -04:00
|
|
|
"strings"
|
2015-02-13 14:40:36 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-24 16:50:28 +00:00
|
|
|
func Flagprint(w io.Writer) {
|
|
|
|
|
flag.CommandLine.SetOutput(w)
|
2015-02-13 14:40:36 -05:00
|
|
|
flag.PrintDefaults()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Flagparse(usage func()) {
|
|
|
|
|
flag.Usage = usage
|
2018-04-30 19:01:57 +00:00
|
|
|
os.Args = expandArgs(os.Args)
|
2015-02-13 14:40:36 -05:00
|
|
|
flag.Parse()
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-30 19:01:57 +00:00
|
|
|
// 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]...)
|
|
|
|
|
}
|
2022-08-28 03:38:00 +08:00
|
|
|
slurp, err := os.ReadFile(s[1:])
|
2018-04-30 19:01:57 +00:00
|
|
|
if err != nil {
|
|
|
|
|
log.Fatal(err)
|
|
|
|
|
}
|
2025-04-14 15:34:30 +00:00
|
|
|
args := strings.Split(strings.TrimSpace(strings.ReplaceAll(string(slurp), "\r", "")), "\n")
|
2020-11-24 12:58:21 -05:00
|
|
|
for i, arg := range args {
|
|
|
|
|
args[i] = DecodeArg(arg)
|
|
|
|
|
}
|
2018-04-30 19:01:57 +00:00
|
|
|
out = append(out, expandArgs(args)...)
|
|
|
|
|
} else if out != nil {
|
|
|
|
|
out = append(out, s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if out == nil {
|
|
|
|
|
return in
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-05 15:37:13 -04:00
|
|
|
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 }
|
2025-11-11 14:48:22 -05:00
|
|
|
func (versionFlag) Get() any { return nil }
|
2017-10-05 15:37:13 -04:00
|
|
|
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")
|
2019-07-15 11:33:26 -04:00
|
|
|
|
2021-04-06 16:11:17 -04:00
|
|
|
p := ""
|
|
|
|
|
|
2021-04-14 13:25:31 -04:00
|
|
|
if s == "goexperiment" {
|
|
|
|
|
// test/run.go uses this to discover the full set of
|
|
|
|
|
// experiment tags. Report everything.
|
2022-03-16 16:25:47 -04:00
|
|
|
p = " X:" + strings.Join(buildcfg.Experiment.All(), ",")
|
2021-04-14 13:25:31 -04:00
|
|
|
} else {
|
2022-03-16 16:25:47 -04:00
|
|
|
// If the enabled experiments differ from the baseline,
|
2021-04-14 13:25:31 -04:00
|
|
|
// include that difference.
|
2022-03-16 16:25:47 -04:00
|
|
|
if goexperiment := buildcfg.Experiment.String(); goexperiment != "" {
|
2021-04-14 13:25:31 -04:00
|
|
|
p = " X:" + goexperiment
|
|
|
|
|
}
|
2017-10-05 15:37:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.
|
2018-11-13 10:09:22 -05:00
|
|
|
if s == "full" {
|
2025-04-25 19:43:16 -04:00
|
|
|
if strings.Contains(buildcfg.Version, "devel") {
|
2018-11-13 10:09:22 -05:00
|
|
|
p += " buildID=" + buildID
|
|
|
|
|
}
|
2017-10-05 15:37:13 -04:00
|
|
|
}
|
2018-11-13 10:09:22 -05:00
|
|
|
|
2021-04-15 23:05:49 -04:00
|
|
|
fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p)
|
2017-10-05 15:37:13 -04:00
|
|
|
os.Exit(0)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-13 14:40:36 -05:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-11 14:48:22 -05:00
|
|
|
func (c *count) Get() any {
|
2017-10-17 17:09:54 -04:00
|
|
|
return int(*c)
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-13 14:40:36 -05:00
|
|
|
func (c *count) IsBoolFlag() bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-17 17:09:54 -04:00
|
|
|
func (c *count) IsCountFlag() bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-13 14:40:36 -05:00
|
|
|
type fn1 func(string)
|
|
|
|
|
|
|
|
|
|
func (f fn1) Set(s string) error {
|
|
|
|
|
f(s)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f fn1) String() string { return "" }
|
2020-11-24 12:58:21 -05:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 21:42:25 +08:00
|
|
|
var b strings.Builder
|
2020-11-24 12:58:21 -05:00
|
|
|
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()
|
|
|
|
|
}
|
2021-09-17 10:07:41 -04:00
|
|
|
|
|
|
|
|
type debugField struct {
|
2022-10-25 23:01:44 -04:00
|
|
|
name string
|
|
|
|
|
help string
|
2025-11-11 14:48:22 -05:00
|
|
|
concurrentOk bool // true if this field/flag is compatible with concurrent compilation
|
|
|
|
|
val any // *int or *string
|
2021-09-17 10:07:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type DebugFlag struct {
|
2022-10-25 23:01:44 -04:00
|
|
|
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.
|
2021-09-17 10:07:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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.
|
2025-11-11 14:48:22 -05:00
|
|
|
func NewDebugFlag(debug any, debugSSA DebugSSA) *DebugFlag {
|
2021-09-17 10:07:41 -04:00
|
|
|
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()
|
2022-10-25 23:01:44 -04:00
|
|
|
if f.Name == "ConcurrentOk" {
|
2021-09-17 10:07:41 -04:00
|
|
|
switch ptr := ptr.(type) {
|
|
|
|
|
default:
|
2022-10-25 23:01:44 -04:00
|
|
|
panic("debug.ConcurrentOk must have type bool")
|
2021-09-17 10:07:41 -04:00
|
|
|
case *bool:
|
2022-10-25 23:01:44 -04:00
|
|
|
flag.concurrentOk = ptr
|
2021-09-17 10:07:41 -04:00
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
name := strings.ToLower(f.Name)
|
|
|
|
|
help := f.Tag.Get("help")
|
|
|
|
|
if help == "" {
|
|
|
|
|
panic(fmt.Sprintf("debug.%s is missing help text", f.Name))
|
|
|
|
|
}
|
2022-10-25 23:01:44 -04:00
|
|
|
concurrent := f.Tag.Get("concurrent")
|
|
|
|
|
|
2021-09-17 10:07:41 -04:00
|
|
|
switch ptr.(type) {
|
|
|
|
|
default:
|
2023-08-09 00:53:03 -07:00
|
|
|
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:
|
2021-09-17 10:07:41 -04:00
|
|
|
// ok
|
|
|
|
|
}
|
2022-10-25 23:01:44 -04:00
|
|
|
flag.tab[name] = debugField{name, help, concurrent == "ok", ptr}
|
2021-09-17 10:07:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return flag
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (f *DebugFlag) Set(debugstr string) error {
|
|
|
|
|
if debugstr == "" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-09-15 14:39:58 +00:00
|
|
|
for name := range strings.SplitSeq(debugstr, ",") {
|
2021-09-17 10:07:41 -04:00
|
|
|
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
|
2025-04-14 15:34:30 +00:00
|
|
|
fmt.Printf("\t%-*s\t%s\n", maxLen, name, strings.ReplaceAll(help, "\n", nl))
|
2021-09-17 10:07:41 -04:00
|
|
|
}
|
|
|
|
|
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
|
2023-08-09 00:53:03 -07:00
|
|
|
case **bisect.Matcher:
|
|
|
|
|
var err error
|
|
|
|
|
*vp, err = bisect.New(valstring)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("debug flag %v: %v", name, err)
|
|
|
|
|
}
|
2021-09-17 10:07:41 -04:00
|
|
|
default:
|
|
|
|
|
panic("bad debugtab type")
|
|
|
|
|
}
|
2022-10-25 23:01:44 -04:00
|
|
|
// assembler DebugFlags don't have a ConcurrentOk field to reset, so check against that.
|
|
|
|
|
if !t.concurrentOk && f.concurrentOk != nil {
|
|
|
|
|
*f.concurrentOk = false
|
|
|
|
|
}
|
2021-09-17 10:07:41 -04:00
|
|
|
} 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 != "" {
|
2024-09-04 13:14:17 -04:00
|
|
|
log.Fatal(err)
|
2021-09-17 10:07:41 -04:00
|
|
|
}
|
2022-10-25 23:01:44 -04:00
|
|
|
// 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
|
|
|
|
|
|
2021-09-17 10:07:41 -04:00
|
|
|
} 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 ""
|
|
|
|
|
}
|