Initial commit: Go 1.23 release state

This commit is contained in:
Vorapol Rinsatitnon
2024-09-21 23:49:08 +10:00
commit 17cd57a668
13231 changed files with 3114330 additions and 0 deletions

3448
src/cmd/go/alldocs.go Normal file

File diff suppressed because it is too large Load Diff

49
src/cmd/go/chdir_test.go Normal file
View File

@@ -0,0 +1,49 @@
// Copyright 2022 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 main
import (
"cmd/go/internal/base"
"os"
"strings"
"testing"
)
func TestChdir(t *testing.T) {
// We want -C to apply to every go subcommand.
// Test that every command either has a -C flag registered
// or has CustomFlags set. In the latter case, the command
// must be explicitly tested in TestScript/chdir.
script, err := os.ReadFile("testdata/script/chdir.txt")
if err != nil {
t.Fatal(err)
}
var walk func(string, *base.Command)
walk = func(name string, cmd *base.Command) {
if len(cmd.Commands) > 0 {
for _, sub := range cmd.Commands {
walk(name+" "+sub.Name(), sub)
}
return
}
if !cmd.Runnable() {
return
}
if cmd.CustomFlags {
if !strings.Contains(string(script), "# "+name+"\n") {
t.Errorf("%s has custom flags, not tested in testdata/script/chdir.txt", name)
}
return
}
f := cmd.Flag.Lookup("C")
if f == nil {
t.Errorf("%s has no -C flag", name)
} else if f.Usage != "AddChdirFlag" {
t.Errorf("%s has -C flag but not from AddChdirFlag", name)
}
}
walk("go", base.Go)
}

View File

@@ -0,0 +1,7 @@
// 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 main
func Main() { main() }

10
src/cmd/go/go11.go Normal file
View File

@@ -0,0 +1,10 @@
// Copyright 2013 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.
//go:build go1.1
package main
// Test that go1.1 tag above is included in builds. main.go refers to this definition.
const go11tag = true

View File

@@ -0,0 +1,22 @@
// 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.
//go:build boringcrypto
package main_test
import "testing"
func TestBoringInternalLink(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
tg.parallel()
tg.tempFile("main.go", `package main
import "crypto/sha1"
func main() {
sha1.New()
}`)
tg.run("build", "-ldflags=-w -extld=false", tg.path("main.go"))
tg.run("build", "-ldflags=-extld=false", tg.path("main.go"))
}

2803
src/cmd/go/go_test.go Normal file

File diff suppressed because it is too large Load Diff

137
src/cmd/go/go_unix_test.go Normal file
View File

@@ -0,0 +1,137 @@
// 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.
//go:build unix
package main_test
import (
"bufio"
"context"
"internal/testenv"
"io"
"os"
"os/exec"
"slices"
"strings"
"syscall"
"testing"
)
func TestGoBuildUmask(t *testing.T) {
// Do not use tg.parallel; avoid other tests seeing umask manipulation.
mask := syscall.Umask(0077) // prohibit low bits
defer syscall.Umask(mask)
tg := testgo(t)
defer tg.cleanup()
tg.tempFile("x.go", `package main; func main() {}`)
// We have set a umask, but if the parent directory happens to have a default
// ACL, the umask may be ignored. To prevent spurious failures from an ACL,
// we compare the file created by "go build" against a file written explicitly
// by os.WriteFile.
//
// (See https://go.dev/issue/62724, https://go.dev/issue/17909.)
control := tg.path("control")
tg.creatingTemp(control)
if err := os.WriteFile(control, []byte("#!/bin/sh\nexit 0"), 0777); err != nil {
t.Fatal(err)
}
cfi, err := os.Stat(control)
if err != nil {
t.Fatal(err)
}
exe := tg.path("x")
tg.creatingTemp(exe)
tg.run("build", "-o", exe, tg.path("x.go"))
fi, err := os.Stat(exe)
if err != nil {
t.Fatal(err)
}
got, want := fi.Mode(), cfi.Mode()
if got == want {
t.Logf("wrote x with mode %v", got)
} else {
t.Fatalf("wrote x with mode %v, wanted no 0077 bits (%v)", got, want)
}
}
// TestTestInterrupt verifies the fix for issue #60203.
//
// If the whole process group for a 'go test' invocation receives
// SIGINT (as would be sent by pressing ^C on a console),
// it should return quickly, not deadlock.
func TestTestInterrupt(t *testing.T) {
if testing.Short() {
t.Skipf("skipping in short mode: test executes many subprocesses")
}
// Don't run this test in parallel, for the same reason.
tg := testgo(t)
defer tg.cleanup()
tg.setenv("GOROOT", testGOROOT)
ctx, cancel := context.WithCancel(context.Background())
cmd := testenv.CommandContext(t, ctx, tg.goTool(), "test", "std", "-short", "-count=1")
cmd.Dir = tg.execDir
// Override $TMPDIR when running the tests: since we're terminating the tests
// with a signal they might fail to clean up some temp files, and we don't
// want that to cause an "unexpected files" failure at the end of the run.
cmd.Env = append(slices.Clip(tg.env), tempEnvName()+"="+t.TempDir())
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
cmd.Cancel = func() error {
pgid := cmd.Process.Pid
return syscall.Kill(-pgid, syscall.SIGINT)
}
pipe, err := cmd.StdoutPipe()
if err != nil {
t.Fatal(err)
}
t.Logf("running %v", cmd)
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
stdout := new(strings.Builder)
r := bufio.NewReader(pipe)
line, err := r.ReadString('\n')
if err != nil {
t.Fatal(err)
}
stdout.WriteString(line)
// The output line for some test was written, so we know things are in progress.
//
// Cancel the rest of the run by sending SIGINT to the process group:
// it should finish up and exit with a nonzero status,
// not have to be killed with SIGKILL.
cancel()
io.Copy(stdout, r)
if stdout.Len() > 0 {
t.Logf("stdout:\n%s", stdout)
}
err = cmd.Wait()
ee, _ := err.(*exec.ExitError)
if ee == nil {
t.Fatalf("unexpectedly finished with nonzero status")
}
if len(ee.Stderr) > 0 {
t.Logf("stderr:\n%s", ee.Stderr)
}
if !ee.Exited() {
t.Fatalf("'go test' did not exit after interrupt: %v", err)
}
t.Logf("interrupted tests without deadlocking")
}

View File

@@ -0,0 +1,50 @@
// Copyright 2014 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 main_test
import (
"internal/testenv"
"os"
"path/filepath"
"strings"
"testing"
"cmd/go/internal/robustio"
)
func TestAbsolutePath(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
tg.parallel()
tmp, err := os.MkdirTemp("", "TestAbsolutePath")
if err != nil {
t.Fatal(err)
}
defer robustio.RemoveAll(tmp)
file := filepath.Join(tmp, "a.go")
err = os.WriteFile(file, []byte{}, 0644)
if err != nil {
t.Fatal(err)
}
dir := filepath.Join(tmp, "dir")
err = os.Mkdir(dir, 0777)
if err != nil {
t.Fatal(err)
}
noVolume := file[len(filepath.VolumeName(file)):]
wrongPath := filepath.Join(dir, noVolume)
cmd := testenv.Command(t, tg.goTool(), "build", noVolume)
cmd.Dir = dir
output, err := cmd.CombinedOutput()
if err == nil {
t.Fatal("build should fail")
}
if strings.Contains(string(output), wrongPath) {
t.Fatalf("wrong output found: %v %v", err, string(output))
}
}

63
src/cmd/go/help_test.go Normal file
View File

@@ -0,0 +1,63 @@
// Copyright 2018 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 main_test
import (
"flag"
"go/format"
"internal/diff"
"internal/testenv"
"os"
"strings"
"testing"
)
var fixDocs = flag.Bool("fixdocs", false, "if true, update alldocs.go")
func TestDocsUpToDate(t *testing.T) {
testenv.MustHaveGoBuild(t)
if !*fixDocs {
t.Parallel()
}
// We run 'go help documentation' as a subprocess instead of
// calling help.Help directly because it may be sensitive to
// init-time configuration
cmd := testenv.Command(t, testGo, "help", "documentation")
// Unset GO111MODULE so that the 'go get' section matches
// the default 'go get' implementation.
cmd.Env = append(cmd.Environ(), "GO111MODULE=")
cmd.Stderr = new(strings.Builder)
out, err := cmd.Output()
if err != nil {
t.Fatalf("%v: %v\n%s", cmd, err, cmd.Stderr)
}
alldocs, err := format.Source(out)
if err != nil {
t.Fatalf("format.Source($(%v)): %v", cmd, err)
}
const srcPath = `alldocs.go`
old, err := os.ReadFile(srcPath)
if err != nil {
t.Fatalf("error reading %s: %v", srcPath, err)
}
diff := diff.Diff(srcPath, old, "go help documentation | gofmt", alldocs)
if diff == nil {
t.Logf("%s is up to date.", srcPath)
return
}
if *fixDocs {
if err := os.WriteFile(srcPath, alldocs, 0666); err != nil {
t.Fatal(err)
}
t.Logf("wrote %d bytes to %s", len(alldocs), srcPath)
} else {
t.Logf("\n%s", diff)
t.Errorf("%s is stale. To update, run 'go generate cmd/go'.", srcPath)
}
}

41
src/cmd/go/init_test.go Normal file
View File

@@ -0,0 +1,41 @@
// Copyright 2018 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 main_test
import (
"internal/testenv"
"sync/atomic"
"testing"
)
// BenchmarkExecGoEnv measures how long it takes for 'go env GOARCH' to run.
// Since 'go' is executed, remember to run 'go install cmd/go' before running
// the benchmark if any changes were done.
func BenchmarkExecGoEnv(b *testing.B) {
testenv.MustHaveExec(b)
gotool, err := testenv.GoTool()
if err != nil {
b.Fatal(err)
}
// We collect extra metrics.
var n, userTime, systemTime int64
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
cmd := testenv.Command(b, gotool, "env", "GOARCH")
if err := cmd.Run(); err != nil {
b.Fatal(err)
}
atomic.AddInt64(&n, 1)
atomic.AddInt64(&userTime, int64(cmd.ProcessState.UserTime()))
atomic.AddInt64(&systemTime, int64(cmd.ProcessState.SystemTime()))
}
})
b.ReportMetric(float64(userTime)/float64(n), "user-ns/op")
b.ReportMetric(float64(systemTime)/float64(n), "sys-ns/op")
}

View File

@@ -0,0 +1,28 @@
// Copyright 2019 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 auth provides access to user-provided authentication credentials.
package auth
import "net/http"
// AddCredentials fills in the user's credentials for req, if any.
// The return value reports whether any matching credentials were found.
func AddCredentials(req *http.Request) (added bool) {
host := req.Host
if host == "" {
host = req.URL.Hostname()
}
// TODO(golang.org/issue/26232): Support arbitrary user-provided credentials.
netrcOnce.Do(readNetrc)
for _, l := range netrc {
if l.machine == host {
req.SetBasicAuth(l.login, l.password)
return true
}
}
return false
}

View File

@@ -0,0 +1,110 @@
// Copyright 2019 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 auth
import (
"os"
"path/filepath"
"runtime"
"strings"
"sync"
)
type netrcLine struct {
machine string
login string
password string
}
var (
netrcOnce sync.Once
netrc []netrcLine
netrcErr error
)
func parseNetrc(data string) []netrcLine {
// See https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html
// for documentation on the .netrc format.
var nrc []netrcLine
var l netrcLine
inMacro := false
for _, line := range strings.Split(data, "\n") {
if inMacro {
if line == "" {
inMacro = false
}
continue
}
f := strings.Fields(line)
i := 0
for ; i < len(f)-1; i += 2 {
// Reset at each "machine" token.
// “The auto-login process searches the .netrc file for a machine token
// that matches […]. Once a match is made, the subsequent .netrc tokens
// are processed, stopping when the end of file is reached or another
// machine or a default token is encountered.”
switch f[i] {
case "machine":
l = netrcLine{machine: f[i+1]}
case "default":
break
case "login":
l.login = f[i+1]
case "password":
l.password = f[i+1]
case "macdef":
// “A macro is defined with the specified name; its contents begin with
// the next .netrc line and continue until a null line (consecutive
// new-line characters) is encountered.”
inMacro = true
}
if l.machine != "" && l.login != "" && l.password != "" {
nrc = append(nrc, l)
l = netrcLine{}
}
}
if i < len(f) && f[i] == "default" {
// “There can be only one default token, and it must be after all machine tokens.”
break
}
}
return nrc
}
func netrcPath() (string, error) {
if env := os.Getenv("NETRC"); env != "" {
return env, nil
}
dir, err := os.UserHomeDir()
if err != nil {
return "", err
}
base := ".netrc"
if runtime.GOOS == "windows" {
base = "_netrc"
}
return filepath.Join(dir, base), nil
}
func readNetrc() {
path, err := netrcPath()
if err != nil {
netrcErr = err
return
}
data, err := os.ReadFile(path)
if err != nil {
if !os.IsNotExist(err) {
netrcErr = err
}
return
}
netrc = parseNetrc(string(data))
}

View File

@@ -0,0 +1,58 @@
// Copyright 2018 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 auth
import (
"reflect"
"testing"
)
var testNetrc = `
machine incomplete
password none
machine api.github.com
login user
password pwd
machine incomlete.host
login justlogin
machine test.host
login user2
password pwd2
machine oneline login user3 password pwd3
machine ignore.host macdef ignore
login nobody
password nothing
machine hasmacro.too macdef ignore-next-lines login user4 password pwd4
login nobody
password nothing
default
login anonymous
password gopher@golang.org
machine after.default
login oops
password too-late-in-file
`
func TestParseNetrc(t *testing.T) {
lines := parseNetrc(testNetrc)
want := []netrcLine{
{"api.github.com", "user", "pwd"},
{"test.host", "user2", "pwd2"},
{"oneline", "user3", "pwd3"},
{"hasmacro.too", "user4", "pwd4"},
}
if !reflect.DeepEqual(lines, want) {
t.Errorf("parseNetrc:\nhave %q\nwant %q", lines, want)
}
}

View File

@@ -0,0 +1,223 @@
// Copyright 2017 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 base defines shared basic pieces of the go command,
// in particular logging and the Command structure.
package base
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/exec"
"reflect"
"strings"
"sync"
"cmd/go/internal/cfg"
"cmd/go/internal/str"
)
// A Command is an implementation of a go command
// like go build or go fix.
type Command struct {
// Run runs the command.
// The args are the arguments after the command name.
Run func(ctx context.Context, cmd *Command, args []string)
// UsageLine is the one-line usage message.
// The words between "go" and the first flag or argument in the line are taken to be the command name.
UsageLine string
// Short is the short description shown in the 'go help' output.
Short string
// Long is the long message shown in the 'go help <this-command>' output.
Long string
// Flag is a set of flags specific to this command.
Flag flag.FlagSet
// CustomFlags indicates that the command will do its own
// flag parsing.
CustomFlags bool
// Commands lists the available commands and help topics.
// The order here is the order in which they are printed by 'go help'.
// Note that subcommands are in general best avoided.
Commands []*Command
}
var Go = &Command{
UsageLine: "go",
Long: `Go is a tool for managing Go source code.`,
// Commands initialized in package main
}
// Lookup returns the subcommand with the given name, if any.
// Otherwise it returns nil.
//
// Lookup ignores subcommands that have len(c.Commands) == 0 and c.Run == nil.
// Such subcommands are only for use as arguments to "help".
func (c *Command) Lookup(name string) *Command {
for _, sub := range c.Commands {
if sub.Name() == name && (len(c.Commands) > 0 || c.Runnable()) {
return sub
}
}
return nil
}
// hasFlag reports whether a command or any of its subcommands contain the given
// flag.
func hasFlag(c *Command, name string) bool {
if f := c.Flag.Lookup(name); f != nil {
return true
}
for _, sub := range c.Commands {
if hasFlag(sub, name) {
return true
}
}
return false
}
// LongName returns the command's long name: all the words in the usage line between "go" and a flag or argument,
func (c *Command) LongName() string {
name := c.UsageLine
if i := strings.Index(name, " ["); i >= 0 {
name = name[:i]
}
if name == "go" {
return ""
}
return strings.TrimPrefix(name, "go ")
}
// Name returns the command's short name: the last word in the usage line before a flag or argument.
func (c *Command) Name() string {
name := c.LongName()
if i := strings.LastIndex(name, " "); i >= 0 {
name = name[i+1:]
}
return name
}
func (c *Command) Usage() {
fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine)
fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", c.LongName())
SetExitStatus(2)
Exit()
}
// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as importpath.
func (c *Command) Runnable() bool {
return c.Run != nil
}
var atExitFuncs []func()
func AtExit(f func()) {
atExitFuncs = append(atExitFuncs, f)
}
func Exit() {
for _, f := range atExitFuncs {
f()
}
os.Exit(exitStatus)
}
func Fatalf(format string, args ...any) {
Errorf(format, args...)
Exit()
}
func Errorf(format string, args ...any) {
log.Printf(format, args...)
SetExitStatus(1)
}
func ExitIfErrors() {
if exitStatus != 0 {
Exit()
}
}
func Error(err error) {
// We use errors.Join to return multiple errors from various routines.
// If we receive multiple errors joined with a basic errors.Join,
// handle each one separately so that they all have the leading "go: " prefix.
// A plain interface check is not good enough because there might be
// other kinds of structured errors that are logically one unit and that
// add other context: only handling the wrapped errors would lose
// that context.
if err != nil && reflect.TypeOf(err).String() == "*errors.joinError" {
for _, e := range err.(interface{ Unwrap() []error }).Unwrap() {
Error(e)
}
return
}
Errorf("go: %v", err)
}
func Fatal(err error) {
Error(err)
Exit()
}
var exitStatus = 0
var exitMu sync.Mutex
func SetExitStatus(n int) {
exitMu.Lock()
if exitStatus < n {
exitStatus = n
}
exitMu.Unlock()
}
func GetExitStatus() int {
return exitStatus
}
// Run runs the command, with stdout and stderr
// connected to the go command's own stdout and stderr.
// If the command fails, Run reports the error using Errorf.
func Run(cmdargs ...any) {
cmdline := str.StringList(cmdargs...)
if cfg.BuildN || cfg.BuildX {
fmt.Printf("%s\n", strings.Join(cmdline, " "))
if cfg.BuildN {
return
}
}
cmd := exec.Command(cmdline[0], cmdline[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
Errorf("%v", err)
}
}
// RunStdin is like run but connects Stdin.
func RunStdin(cmdline []string) {
cmd := exec.Command(cmdline[0], cmdline[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = cfg.OrigEnv
StartSigHandlers()
if err := cmd.Run(); err != nil {
Errorf("%v", err)
}
}
// Usage is the usage-reporting function, filled in by package main
// but here for reference by other packages.
var Usage func()

View File

@@ -0,0 +1,46 @@
// Copyright 2017 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 base
import (
"cmd/go/internal/cfg"
"fmt"
"os"
"path/filepath"
"runtime"
)
// AppendPWD returns the result of appending PWD=dir to the environment base.
//
// The resulting environment makes os.Getwd more efficient for a subprocess
// running in dir, and also improves the accuracy of paths relative to dir
// if one or more elements of dir is a symlink.
func AppendPWD(base []string, dir string) []string {
// POSIX requires PWD to be absolute.
// Internally we only use absolute paths, so dir should already be absolute.
if !filepath.IsAbs(dir) {
panic(fmt.Sprintf("AppendPWD with relative path %q", dir))
}
return append(base, "PWD="+dir)
}
// AppendPATH returns the result of appending PATH=$GOROOT/bin:$PATH
// (or the platform equivalent) to the environment base.
func AppendPATH(base []string) []string {
if cfg.GOROOTbin == "" {
return base
}
pathVar := "PATH"
if runtime.GOOS == "plan9" {
pathVar = "path"
}
path := os.Getenv(pathVar)
if path == "" {
return append(base, pathVar+"="+cfg.GOROOTbin)
}
return append(base, pathVar+"="+cfg.GOROOTbin+string(os.PathListSeparator)+path)
}

View File

@@ -0,0 +1,85 @@
// Copyright 2017 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 base
import (
"flag"
"fmt"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/internal/quoted"
)
// A StringsFlag is a command-line flag that interprets its argument
// as a space-separated list of possibly-quoted strings.
type StringsFlag []string
func (v *StringsFlag) Set(s string) error {
var err error
*v, err = quoted.Split(s)
if *v == nil {
*v = []string{}
}
return err
}
func (v *StringsFlag) String() string {
return "<StringsFlag>"
}
// explicitStringFlag is like a regular string flag, but it also tracks whether
// the string was set explicitly to a non-empty value.
type explicitStringFlag struct {
value *string
explicit *bool
}
func (f explicitStringFlag) String() string {
if f.value == nil {
return ""
}
return *f.value
}
func (f explicitStringFlag) Set(v string) error {
*f.value = v
if v != "" {
*f.explicit = true
}
return nil
}
// AddBuildFlagsNX adds the -n and -x build flags to the flag set.
func AddBuildFlagsNX(flags *flag.FlagSet) {
flags.BoolVar(&cfg.BuildN, "n", false, "")
flags.BoolVar(&cfg.BuildX, "x", false, "")
}
// AddChdirFlag adds the -C flag to the flag set.
func AddChdirFlag(flags *flag.FlagSet) {
// The usage message is never printed, but it's used in chdir_test.go
// to identify that the -C flag is from AddChdirFlag.
flags.Func("C", "AddChdirFlag", ChdirFlag)
}
// AddModFlag adds the -mod build flag to the flag set.
func AddModFlag(flags *flag.FlagSet) {
flags.Var(explicitStringFlag{value: &cfg.BuildMod, explicit: &cfg.BuildModExplicit}, "mod", "")
}
// AddModCommonFlags adds the module-related flags common to build commands
// and 'go mod' subcommands.
func AddModCommonFlags(flags *flag.FlagSet) {
flags.BoolVar(&cfg.ModCacheRW, "modcacherw", false, "")
flags.StringVar(&cfg.ModFile, "modfile", "", "")
flags.StringVar(&fsys.OverlayFile, "overlay", "", "")
}
func ChdirFlag(s string) error {
// main handles -C by removing it from the command line.
// If we see one during flag parsing, that's an error.
return fmt.Errorf("-C flag must be first flag on command line")
}

View File

@@ -0,0 +1,162 @@
// Copyright 2018 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 base
import (
"flag"
"fmt"
"runtime"
"strings"
"cmd/go/internal/cfg"
"cmd/internal/quoted"
)
var goflags []string // cached $GOFLAGS list; can be -x or --x form
// GOFLAGS returns the flags from $GOFLAGS.
// The list can be assumed to contain one string per flag,
// with each string either beginning with -name or --name.
func GOFLAGS() []string {
InitGOFLAGS()
return goflags
}
// InitGOFLAGS initializes the goflags list from $GOFLAGS.
// If goflags is already initialized, it does nothing.
func InitGOFLAGS() {
if goflags != nil { // already initialized
return
}
// Ignore bad flag in go env and go bug, because
// they are what people reach for when debugging
// a problem, and maybe they're debugging GOFLAGS.
// (Both will show the GOFLAGS setting if let succeed.)
hideErrors := cfg.CmdName == "env" || cfg.CmdName == "bug"
var err error
goflags, err = quoted.Split(cfg.Getenv("GOFLAGS"))
if err != nil {
if hideErrors {
return
}
Fatalf("go: parsing $GOFLAGS: %v", err)
}
if len(goflags) == 0 {
// nothing to do; avoid work on later InitGOFLAGS call
goflags = []string{}
return
}
// Each of the words returned by strings.Fields must be its own flag.
// To set flag arguments use -x=value instead of -x value.
// For boolean flags, -x is fine instead of -x=true.
for _, f := range goflags {
// Check that every flag looks like -x --x -x=value or --x=value.
if !strings.HasPrefix(f, "-") || f == "-" || f == "--" || strings.HasPrefix(f, "---") || strings.HasPrefix(f, "-=") || strings.HasPrefix(f, "--=") {
if hideErrors {
continue
}
Fatalf("go: parsing $GOFLAGS: non-flag %q", f)
}
name := f[1:]
if name[0] == '-' {
name = name[1:]
}
if i := strings.Index(name, "="); i >= 0 {
name = name[:i]
}
if !hasFlag(Go, name) {
if hideErrors {
continue
}
Fatalf("go: parsing $GOFLAGS: unknown flag -%s", name)
}
}
}
// boolFlag is the optional interface for flag.Value known to the flag package.
// (It is not clear why package flag does not export this interface.)
type boolFlag interface {
flag.Value
IsBoolFlag() bool
}
// SetFromGOFLAGS sets the flags in the given flag set using settings in $GOFLAGS.
func SetFromGOFLAGS(flags *flag.FlagSet) {
InitGOFLAGS()
// This loop is similar to flag.Parse except that it ignores
// unknown flags found in goflags, so that setting, say, GOFLAGS=-ldflags=-w
// does not break commands that don't have a -ldflags.
// It also adjusts the output to be clear that the reported problem is from $GOFLAGS.
where := "$GOFLAGS"
if runtime.GOOS == "windows" {
where = "%GOFLAGS%"
}
for _, goflag := range goflags {
name, value, hasValue := goflag, "", false
// Ignore invalid flags like '=' or '=value'.
// If it is not reported in InitGOFlags it means we don't want to report it.
if i := strings.Index(goflag, "="); i == 0 {
continue
} else if i > 0 {
name, value, hasValue = goflag[:i], goflag[i+1:], true
}
if strings.HasPrefix(name, "--") {
name = name[1:]
}
f := flags.Lookup(name[1:])
if f == nil {
continue
}
// Use flags.Set consistently (instead of f.Value.Set) so that a subsequent
// call to flags.Visit will correctly visit the flags that have been set.
if fb, ok := f.Value.(boolFlag); ok && fb.IsBoolFlag() {
if hasValue {
if err := flags.Set(f.Name, value); err != nil {
fmt.Fprintf(flags.Output(), "go: invalid boolean value %q for flag %s (from %s): %v\n", value, name, where, err)
flags.Usage()
}
} else {
if err := flags.Set(f.Name, "true"); err != nil {
fmt.Fprintf(flags.Output(), "go: invalid boolean flag %s (from %s): %v\n", name, where, err)
flags.Usage()
}
}
} else {
if !hasValue {
fmt.Fprintf(flags.Output(), "go: flag needs an argument: %s (from %s)\n", name, where)
flags.Usage()
}
if err := flags.Set(f.Name, value); err != nil {
fmt.Fprintf(flags.Output(), "go: invalid value %q for flag %s (from %s): %v\n", value, name, where, err)
flags.Usage()
}
}
}
}
// InGOFLAGS returns whether GOFLAGS contains the given flag, such as "-mod".
func InGOFLAGS(flag string) bool {
for _, goflag := range GOFLAGS() {
name := goflag
if strings.HasPrefix(name, "--") {
name = name[1:]
}
if i := strings.Index(name, "="); i >= 0 {
name = name[:i]
}
if name == flag {
return true
}
}
return false
}

View File

@@ -0,0 +1,84 @@
// Copyright 2023 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 base
import (
"fmt"
"internal/godebug"
"runtime"
"strconv"
"sync"
)
var NetLimitGodebug = godebug.New("#cmdgonetlimit")
// NetLimit returns the limit on concurrent network operations
// configured by GODEBUG=cmdgonetlimit, if any.
//
// A limit of 0 (indicated by 0, true) means that network operations should not
// be allowed.
func NetLimit() (int, bool) {
netLimitOnce.Do(func() {
s := NetLimitGodebug.Value()
if s == "" {
return
}
n, err := strconv.Atoi(s)
if err != nil {
Fatalf("invalid %s: %v", NetLimitGodebug.Name(), err)
}
if n < 0 {
// Treat negative values as unlimited.
return
}
netLimitSem = make(chan struct{}, n)
})
return cap(netLimitSem), netLimitSem != nil
}
// AcquireNet acquires a semaphore token for a network operation.
func AcquireNet() (release func(), err error) {
hasToken := false
if n, ok := NetLimit(); ok {
if n == 0 {
return nil, fmt.Errorf("network disabled by %v=%v", NetLimitGodebug.Name(), NetLimitGodebug.Value())
}
netLimitSem <- struct{}{}
hasToken = true
}
checker := new(netTokenChecker)
runtime.SetFinalizer(checker, (*netTokenChecker).panicUnreleased)
return func() {
if checker.released {
panic("internal error: net token released twice")
}
checker.released = true
if hasToken {
<-netLimitSem
}
runtime.SetFinalizer(checker, nil)
}, nil
}
var (
netLimitOnce sync.Once
netLimitSem chan struct{}
)
type netTokenChecker struct {
released bool
// We want to use a finalizer to check that all acquired tokens are returned,
// so we arbitrarily pad the tokens with a string to defeat the runtime's
// “tiny allocator”.
unusedAvoidTinyAllocator string
}
func (c *netTokenChecker) panicUnreleased() {
panic("internal error: net token acquired but not released")
}

View File

@@ -0,0 +1,79 @@
// Copyright 2017 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 base
import (
"os"
"path/filepath"
"runtime"
"strings"
"sync"
)
var cwd string
var cwdOnce sync.Once
// UncachedCwd returns the current working directory.
// Most callers should use Cwd, which caches the result for future use.
// UncachedCwd is appropriate to call early in program startup before flag parsing,
// because the -C flag may change the current directory.
func UncachedCwd() string {
wd, err := os.Getwd()
if err != nil {
Fatalf("cannot determine current directory: %v", err)
}
return wd
}
// Cwd returns the current working directory at the time of the first call.
func Cwd() string {
cwdOnce.Do(func() {
cwd = UncachedCwd()
})
return cwd
}
// ShortPath returns an absolute or relative name for path, whatever is shorter.
func ShortPath(path string) string {
if rel, err := filepath.Rel(Cwd(), path); err == nil && len(rel) < len(path) {
return rel
}
return path
}
// RelPaths returns a copy of paths with absolute paths
// made relative to the current directory if they would be shorter.
func RelPaths(paths []string) []string {
var out []string
for _, p := range paths {
rel, err := filepath.Rel(Cwd(), p)
if err == nil && len(rel) < len(p) {
p = rel
}
out = append(out, p)
}
return out
}
// IsTestFile reports whether the source file is a set of tests and should therefore
// be excluded from coverage analysis.
func IsTestFile(file string) bool {
// We don't cover tests, only the code they test.
return strings.HasSuffix(file, "_test.go")
}
// IsNull reports whether the path is a common name for the null device.
// It returns true for /dev/null on Unix, or NUL (case-insensitive) on Windows.
func IsNull(path string) bool {
if path == os.DevNull {
return true
}
if runtime.GOOS == "windows" {
if strings.EqualFold(path, "NUL") {
return true
}
}
return false
}

View File

@@ -0,0 +1,31 @@
// Copyright 2012 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 base
import (
"os"
"os/signal"
"sync"
)
// Interrupted is closed when the go command receives an interrupt signal.
var Interrupted = make(chan struct{})
// processSignals setups signal handler.
func processSignals() {
sig := make(chan os.Signal, 1)
signal.Notify(sig, signalsToIgnore...)
go func() {
<-sig
close(Interrupted)
}()
}
var onceProcessSignals sync.Once
// StartSigHandlers starts the signal handlers.
func StartSigHandlers() {
onceProcessSignals.Do(processSignals)
}

View File

@@ -0,0 +1,17 @@
// Copyright 2012 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.
//go:build plan9 || windows
package base
import (
"os"
)
var signalsToIgnore = []os.Signal{os.Interrupt}
// SignalTrace is the signal to send to make a Go program
// crash with a stack trace (no such signal in this case).
var SignalTrace os.Signal = nil

View File

@@ -0,0 +1,18 @@
// Copyright 2012 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.
//go:build unix || js || wasip1
package base
import (
"os"
"syscall"
)
var signalsToIgnore = []os.Signal{os.Interrupt, syscall.SIGQUIT}
// SignalTrace is the signal to send to make a Go program
// crash with a stack trace.
var SignalTrace os.Signal = syscall.SIGQUIT

View File

@@ -0,0 +1,41 @@
// Copyright 2017 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 base
import (
"fmt"
"go/build"
"os"
"path/filepath"
"cmd/go/internal/cfg"
"cmd/go/internal/par"
)
// Tool returns the path to the named tool (for example, "vet").
// If the tool cannot be found, Tool exits the process.
func Tool(toolName string) string {
toolPath, err := ToolPath(toolName)
if err != nil && len(cfg.BuildToolexec) == 0 {
// Give a nice message if there is no tool with that name.
fmt.Fprintf(os.Stderr, "go: no such tool %q\n", toolName)
SetExitStatus(2)
Exit()
}
return toolPath
}
// ToolPath returns the path at which we expect to find the named tool
// (for example, "vet"), and the error (if any) from statting that path.
func ToolPath(toolName string) (string, error) {
toolPath := filepath.Join(build.ToolDir, toolName) + cfg.ToolExeSuffix()
err := toolStatCache.Do(toolPath, func() error {
_, err := os.Stat(toolPath)
return err
})
return toolPath, err
}
var toolStatCache par.Cache[string, error]

View File

@@ -0,0 +1,224 @@
// Copyright 2016 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 bug implements the “go bug” command.
package bug
import (
"bytes"
"context"
"fmt"
"io"
urlpkg "net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/envcmd"
"cmd/go/internal/web"
"cmd/go/internal/work"
)
var CmdBug = &base.Command{
Run: runBug,
UsageLine: "go bug",
Short: "start a bug report",
Long: `
Bug opens the default browser and starts a new bug report.
The report includes useful system information.
`,
}
func init() {
CmdBug.Flag.BoolVar(&cfg.BuildV, "v", false, "")
base.AddChdirFlag(&CmdBug.Flag)
}
func runBug(ctx context.Context, cmd *base.Command, args []string) {
if len(args) > 0 {
base.Fatalf("go: bug takes no arguments")
}
work.BuildInit()
var buf strings.Builder
buf.WriteString(bugHeader)
printGoVersion(&buf)
buf.WriteString("### Does this issue reproduce with the latest release?\n\n\n")
printEnvDetails(&buf)
buf.WriteString(bugFooter)
body := buf.String()
url := "https://github.com/golang/go/issues/new?body=" + urlpkg.QueryEscape(body)
if !web.OpenBrowser(url) {
fmt.Print("Please file a new issue at golang.org/issue/new using this template:\n\n")
fmt.Print(body)
}
}
const bugHeader = `<!-- Please answer these questions before submitting your issue. Thanks! -->
`
const bugFooter = `### What did you do?
<!--
If possible, provide a recipe for reproducing the error.
A complete runnable program is good.
A link on play.golang.org is best.
-->
### What did you expect to see?
### What did you see instead?
`
func printGoVersion(w io.Writer) {
fmt.Fprintf(w, "### What version of Go are you using (`go version`)?\n\n")
fmt.Fprintf(w, "<pre>\n")
fmt.Fprintf(w, "$ go version\n")
fmt.Fprintf(w, "go version %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
fmt.Fprintf(w, "</pre>\n")
fmt.Fprintf(w, "\n")
}
func printEnvDetails(w io.Writer) {
fmt.Fprintf(w, "### What operating system and processor architecture are you using (`go env`)?\n\n")
fmt.Fprintf(w, "<details><summary><code>go env</code> Output</summary><br><pre>\n")
fmt.Fprintf(w, "$ go env\n")
printGoEnv(w)
printGoDetails(w)
printOSDetails(w)
printCDetails(w)
fmt.Fprintf(w, "</pre></details>\n\n")
}
func printGoEnv(w io.Writer) {
env := envcmd.MkEnv()
env = append(env, envcmd.ExtraEnvVars()...)
env = append(env, envcmd.ExtraEnvVarsCostly()...)
envcmd.PrintEnv(w, env, false)
}
func printGoDetails(w io.Writer) {
gocmd := filepath.Join(runtime.GOROOT(), "bin/go")
printCmdOut(w, "GOROOT/bin/go version: ", gocmd, "version")
printCmdOut(w, "GOROOT/bin/go tool compile -V: ", gocmd, "tool", "compile", "-V")
}
func printOSDetails(w io.Writer) {
switch runtime.GOOS {
case "darwin", "ios":
printCmdOut(w, "uname -v: ", "uname", "-v")
printCmdOut(w, "", "sw_vers")
case "linux":
printCmdOut(w, "uname -sr: ", "uname", "-sr")
printCmdOut(w, "", "lsb_release", "-a")
printGlibcVersion(w)
case "openbsd", "netbsd", "freebsd", "dragonfly":
printCmdOut(w, "uname -v: ", "uname", "-v")
case "illumos", "solaris":
// Be sure to use the OS-supplied uname, in "/usr/bin":
printCmdOut(w, "uname -srv: ", "/usr/bin/uname", "-srv")
out, err := os.ReadFile("/etc/release")
if err == nil {
fmt.Fprintf(w, "/etc/release: %s\n", out)
} else {
if cfg.BuildV {
fmt.Printf("failed to read /etc/release: %v\n", err)
}
}
}
}
func printCDetails(w io.Writer) {
printCmdOut(w, "lldb --version: ", "lldb", "--version")
cmd := exec.Command("gdb", "--version")
out, err := cmd.Output()
if err == nil {
// There's apparently no combination of command line flags
// to get gdb to spit out its version without the license and warranty.
// Print up to the first newline.
fmt.Fprintf(w, "gdb --version: %s\n", firstLine(out))
} else {
if cfg.BuildV {
fmt.Printf("failed to run gdb --version: %v\n", err)
}
}
}
// printCmdOut prints the output of running the given command.
// It ignores failures; 'go bug' is best effort.
func printCmdOut(w io.Writer, prefix, path string, args ...string) {
cmd := exec.Command(path, args...)
out, err := cmd.Output()
if err != nil {
if cfg.BuildV {
fmt.Printf("%s %s: %v\n", path, strings.Join(args, " "), err)
}
return
}
fmt.Fprintf(w, "%s%s\n", prefix, bytes.TrimSpace(out))
}
// firstLine returns the first line of a given byte slice.
func firstLine(buf []byte) []byte {
idx := bytes.IndexByte(buf, '\n')
if idx > 0 {
buf = buf[:idx]
}
return bytes.TrimSpace(buf)
}
// printGlibcVersion prints information about the glibc version.
// It ignores failures.
func printGlibcVersion(w io.Writer) {
tempdir := os.TempDir()
if tempdir == "" {
return
}
src := []byte(`int main() {}`)
srcfile := filepath.Join(tempdir, "go-bug.c")
outfile := filepath.Join(tempdir, "go-bug")
err := os.WriteFile(srcfile, src, 0644)
if err != nil {
return
}
defer os.Remove(srcfile)
cmd := exec.Command("gcc", "-o", outfile, srcfile)
if _, err = cmd.CombinedOutput(); err != nil {
return
}
defer os.Remove(outfile)
cmd = exec.Command("ldd", outfile)
out, err := cmd.CombinedOutput()
if err != nil {
return
}
re := regexp.MustCompile(`libc\.so[^ ]* => ([^ ]+)`)
m := re.FindStringSubmatch(string(out))
if m == nil {
return
}
cmd = exec.Command(m[1])
out, err = cmd.Output()
if err != nil {
return
}
fmt.Fprintf(w, "%s: %s\n", m[1], firstLine(out))
// print another line (the one containing version string) in case of musl libc
if idx := bytes.IndexByte(out, '\n'); bytes.Contains(out, []byte("musl")) && idx > -1 {
fmt.Fprintf(w, "%s\n", firstLine(out[idx+1:]))
}
}

627
src/cmd/go/internal/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,627 @@
// Copyright 2017 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 cache implements a build artifact cache.
package cache
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"internal/godebug"
"io"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"cmd/go/internal/lockedfile"
"cmd/go/internal/mmap"
)
// An ActionID is a cache action key, the hash of a complete description of a
// repeatable computation (command line, environment variables,
// input file contents, executable contents).
type ActionID [HashSize]byte
// An OutputID is a cache output key, the hash of an output of a computation.
type OutputID [HashSize]byte
// Cache is the interface as used by the cmd/go.
type Cache interface {
// Get returns the cache entry for the provided ActionID.
// On miss, the error type should be of type *entryNotFoundError.
//
// After a success call to Get, OutputFile(Entry.OutputID) must
// exist on disk for until Close is called (at the end of the process).
Get(ActionID) (Entry, error)
// Put adds an item to the cache.
//
// The seeker is only used to seek to the beginning. After a call to Put,
// the seek position is not guaranteed to be in any particular state.
//
// As a special case, if the ReadSeeker is of type noVerifyReadSeeker,
// the verification from GODEBUG=goverifycache=1 is skipped.
//
// After a success call to Get, OutputFile(Entry.OutputID) must
// exist on disk for until Close is called (at the end of the process).
Put(ActionID, io.ReadSeeker) (_ OutputID, size int64, _ error)
// Close is called at the end of the go process. Implementations can do
// cache cleanup work at this phase, or wait for and report any errors from
// background cleanup work started earlier. Any cache trimming should in one
// process should not violate cause the invariants of this interface to be
// violated in another process. Namely, a cache trim from one process should
// not delete an ObjectID from disk that was recently Get or Put from
// another process. As a rule of thumb, don't trim things used in the last
// day.
Close() error
// OutputFile returns the path on disk where OutputID is stored.
//
// It's only called after a successful get or put call so it doesn't need
// to return an error; it's assumed that if the previous get or put succeeded,
// it's already on disk.
OutputFile(OutputID) string
// FuzzDir returns where fuzz files are stored.
FuzzDir() string
}
// A Cache is a package cache, backed by a file system directory tree.
type DiskCache struct {
dir string
now func() time.Time
}
// Open opens and returns the cache in the given directory.
//
// It is safe for multiple processes on a single machine to use the
// same cache directory in a local file system simultaneously.
// They will coordinate using operating system file locks and may
// duplicate effort but will not corrupt the cache.
//
// However, it is NOT safe for multiple processes on different machines
// to share a cache directory (for example, if the directory were stored
// in a network file system). File locking is notoriously unreliable in
// network file systems and may not suffice to protect the cache.
func Open(dir string) (*DiskCache, error) {
info, err := os.Stat(dir)
if err != nil {
return nil, err
}
if !info.IsDir() {
return nil, &fs.PathError{Op: "open", Path: dir, Err: fmt.Errorf("not a directory")}
}
for i := 0; i < 256; i++ {
name := filepath.Join(dir, fmt.Sprintf("%02x", i))
if err := os.MkdirAll(name, 0777); err != nil {
return nil, err
}
}
c := &DiskCache{
dir: dir,
now: time.Now,
}
return c, nil
}
// fileName returns the name of the file corresponding to the given id.
func (c *DiskCache) fileName(id [HashSize]byte, key string) string {
return filepath.Join(c.dir, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+"-"+key)
}
// An entryNotFoundError indicates that a cache entry was not found, with an
// optional underlying reason.
type entryNotFoundError struct {
Err error
}
func (e *entryNotFoundError) Error() string {
if e.Err == nil {
return "cache entry not found"
}
return fmt.Sprintf("cache entry not found: %v", e.Err)
}
func (e *entryNotFoundError) Unwrap() error {
return e.Err
}
const (
// action entry file is "v1 <hex id> <hex out> <decimal size space-padded to 20 bytes> <unixnano space-padded to 20 bytes>\n"
hexSize = HashSize * 2
entrySize = 2 + 1 + hexSize + 1 + hexSize + 1 + 20 + 1 + 20 + 1
)
// verify controls whether to run the cache in verify mode.
// In verify mode, the cache always returns errMissing from Get
// but then double-checks in Put that the data being written
// exactly matches any existing entry. This provides an easy
// way to detect program behavior that would have been different
// had the cache entry been returned from Get.
//
// verify is enabled by setting the environment variable
// GODEBUG=gocacheverify=1.
var verify = false
var errVerifyMode = errors.New("gocacheverify=1")
// DebugTest is set when GODEBUG=gocachetest=1 is in the environment.
var DebugTest = false
func init() { initEnv() }
var (
gocacheverify = godebug.New("gocacheverify")
gocachehash = godebug.New("gocachehash")
gocachetest = godebug.New("gocachetest")
)
func initEnv() {
if gocacheverify.Value() == "1" {
gocacheverify.IncNonDefault()
verify = true
}
if gocachehash.Value() == "1" {
gocachehash.IncNonDefault()
debugHash = true
}
if gocachetest.Value() == "1" {
gocachetest.IncNonDefault()
DebugTest = true
}
}
// Get looks up the action ID in the cache,
// returning the corresponding output ID and file size, if any.
// Note that finding an output ID does not guarantee that the
// saved file for that output ID is still available.
func (c *DiskCache) Get(id ActionID) (Entry, error) {
if verify {
return Entry{}, &entryNotFoundError{Err: errVerifyMode}
}
return c.get(id)
}
type Entry struct {
OutputID OutputID
Size int64
Time time.Time // when added to cache
}
// get is Get but does not respect verify mode, so that Put can use it.
func (c *DiskCache) get(id ActionID) (Entry, error) {
missing := func(reason error) (Entry, error) {
return Entry{}, &entryNotFoundError{Err: reason}
}
f, err := os.Open(c.fileName(id, "a"))
if err != nil {
return missing(err)
}
defer f.Close()
entry := make([]byte, entrySize+1) // +1 to detect whether f is too long
if n, err := io.ReadFull(f, entry); n > entrySize {
return missing(errors.New("too long"))
} else if err != io.ErrUnexpectedEOF {
if err == io.EOF {
return missing(errors.New("file is empty"))
}
return missing(err)
} else if n < entrySize {
return missing(errors.New("entry file incomplete"))
}
if entry[0] != 'v' || entry[1] != '1' || entry[2] != ' ' || entry[3+hexSize] != ' ' || entry[3+hexSize+1+hexSize] != ' ' || entry[3+hexSize+1+hexSize+1+20] != ' ' || entry[entrySize-1] != '\n' {
return missing(errors.New("invalid header"))
}
eid, entry := entry[3:3+hexSize], entry[3+hexSize:]
eout, entry := entry[1:1+hexSize], entry[1+hexSize:]
esize, entry := entry[1:1+20], entry[1+20:]
etime, entry := entry[1:1+20], entry[1+20:]
var buf [HashSize]byte
if _, err := hex.Decode(buf[:], eid); err != nil {
return missing(fmt.Errorf("decoding ID: %v", err))
} else if buf != id {
return missing(errors.New("mismatched ID"))
}
if _, err := hex.Decode(buf[:], eout); err != nil {
return missing(fmt.Errorf("decoding output ID: %v", err))
}
i := 0
for i < len(esize) && esize[i] == ' ' {
i++
}
size, err := strconv.ParseInt(string(esize[i:]), 10, 64)
if err != nil {
return missing(fmt.Errorf("parsing size: %v", err))
} else if size < 0 {
return missing(errors.New("negative size"))
}
i = 0
for i < len(etime) && etime[i] == ' ' {
i++
}
tm, err := strconv.ParseInt(string(etime[i:]), 10, 64)
if err != nil {
return missing(fmt.Errorf("parsing timestamp: %v", err))
} else if tm < 0 {
return missing(errors.New("negative timestamp"))
}
c.used(c.fileName(id, "a"))
return Entry{buf, size, time.Unix(0, tm)}, nil
}
// GetFile looks up the action ID in the cache and returns
// the name of the corresponding data file.
func GetFile(c Cache, id ActionID) (file string, entry Entry, err error) {
entry, err = c.Get(id)
if err != nil {
return "", Entry{}, err
}
file = c.OutputFile(entry.OutputID)
info, err := os.Stat(file)
if err != nil {
return "", Entry{}, &entryNotFoundError{Err: err}
}
if info.Size() != entry.Size {
return "", Entry{}, &entryNotFoundError{Err: errors.New("file incomplete")}
}
return file, entry, nil
}
// GetBytes looks up the action ID in the cache and returns
// the corresponding output bytes.
// GetBytes should only be used for data that can be expected to fit in memory.
func GetBytes(c Cache, id ActionID) ([]byte, Entry, error) {
entry, err := c.Get(id)
if err != nil {
return nil, entry, err
}
data, _ := os.ReadFile(c.OutputFile(entry.OutputID))
if sha256.Sum256(data) != entry.OutputID {
return nil, entry, &entryNotFoundError{Err: errors.New("bad checksum")}
}
return data, entry, nil
}
// GetMmap looks up the action ID in the cache and returns
// the corresponding output bytes.
// GetMmap should only be used for data that can be expected to fit in memory.
func GetMmap(c Cache, id ActionID) ([]byte, Entry, error) {
entry, err := c.Get(id)
if err != nil {
return nil, entry, err
}
md, err := mmap.Mmap(c.OutputFile(entry.OutputID))
if err != nil {
return nil, Entry{}, err
}
if int64(len(md.Data)) != entry.Size {
return nil, Entry{}, &entryNotFoundError{Err: errors.New("file incomplete")}
}
return md.Data, entry, nil
}
// OutputFile returns the name of the cache file storing output with the given OutputID.
func (c *DiskCache) OutputFile(out OutputID) string {
file := c.fileName(out, "d")
c.used(file)
return file
}
// Time constants for cache expiration.
//
// We set the mtime on a cache file on each use, but at most one per mtimeInterval (1 hour),
// to avoid causing many unnecessary inode updates. The mtimes therefore
// roughly reflect "time of last use" but may in fact be older by at most an hour.
//
// We scan the cache for entries to delete at most once per trimInterval (1 day).
//
// When we do scan the cache, we delete entries that have not been used for
// at least trimLimit (5 days). Statistics gathered from a month of usage by
// Go developers found that essentially all reuse of cached entries happened
// within 5 days of the previous reuse. See golang.org/issue/22990.
const (
mtimeInterval = 1 * time.Hour
trimInterval = 24 * time.Hour
trimLimit = 5 * 24 * time.Hour
)
// used makes a best-effort attempt to update mtime on file,
// so that mtime reflects cache access time.
//
// Because the reflection only needs to be approximate,
// and to reduce the amount of disk activity caused by using
// cache entries, used only updates the mtime if the current
// mtime is more than an hour old. This heuristic eliminates
// nearly all of the mtime updates that would otherwise happen,
// while still keeping the mtimes useful for cache trimming.
func (c *DiskCache) used(file string) {
info, err := os.Stat(file)
if err == nil && c.now().Sub(info.ModTime()) < mtimeInterval {
return
}
os.Chtimes(file, c.now(), c.now())
}
func (c *DiskCache) Close() error { return c.Trim() }
// Trim removes old cache entries that are likely not to be reused.
func (c *DiskCache) Trim() error {
now := c.now()
// We maintain in dir/trim.txt the time of the last completed cache trim.
// If the cache has been trimmed recently enough, do nothing.
// This is the common case.
// If the trim file is corrupt, detected if the file can't be parsed, or the
// trim time is too far in the future, attempt the trim anyway. It's possible that
// the cache was full when the corruption happened. Attempting a trim on
// an empty cache is cheap, so there wouldn't be a big performance hit in that case.
if data, err := lockedfile.Read(filepath.Join(c.dir, "trim.txt")); err == nil {
if t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64); err == nil {
lastTrim := time.Unix(t, 0)
if d := now.Sub(lastTrim); d < trimInterval && d > -mtimeInterval {
return nil
}
}
}
// Trim each of the 256 subdirectories.
// We subtract an additional mtimeInterval
// to account for the imprecision of our "last used" mtimes.
cutoff := now.Add(-trimLimit - mtimeInterval)
for i := 0; i < 256; i++ {
subdir := filepath.Join(c.dir, fmt.Sprintf("%02x", i))
c.trimSubdir(subdir, cutoff)
}
// Ignore errors from here: if we don't write the complete timestamp, the
// cache will appear older than it is, and we'll trim it again next time.
var b bytes.Buffer
fmt.Fprintf(&b, "%d", now.Unix())
if err := lockedfile.Write(filepath.Join(c.dir, "trim.txt"), &b, 0666); err != nil {
return err
}
return nil
}
// trimSubdir trims a single cache subdirectory.
func (c *DiskCache) trimSubdir(subdir string, cutoff time.Time) {
// Read all directory entries from subdir before removing
// any files, in case removing files invalidates the file offset
// in the directory scan. Also, ignore error from f.Readdirnames,
// because we don't care about reporting the error and we still
// want to process any entries found before the error.
f, err := os.Open(subdir)
if err != nil {
return
}
names, _ := f.Readdirnames(-1)
f.Close()
for _, name := range names {
// Remove only cache entries (xxxx-a and xxxx-d).
if !strings.HasSuffix(name, "-a") && !strings.HasSuffix(name, "-d") {
continue
}
entry := filepath.Join(subdir, name)
info, err := os.Stat(entry)
if err == nil && info.ModTime().Before(cutoff) {
os.Remove(entry)
}
}
}
// putIndexEntry adds an entry to the cache recording that executing the action
// with the given id produces an output with the given output id (hash) and size.
func (c *DiskCache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify bool) error {
// Note: We expect that for one reason or another it may happen
// that repeating an action produces a different output hash
// (for example, if the output contains a time stamp or temp dir name).
// While not ideal, this is also not a correctness problem, so we
// don't make a big deal about it. In particular, we leave the action
// cache entries writable specifically so that they can be overwritten.
//
// Setting GODEBUG=gocacheverify=1 does make a big deal:
// in verify mode we are double-checking that the cache entries
// are entirely reproducible. As just noted, this may be unrealistic
// in some cases but the check is also useful for shaking out real bugs.
entry := fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano())
if verify && allowVerify {
old, err := c.get(id)
if err == nil && (old.OutputID != out || old.Size != size) {
// panic to show stack trace, so we can see what code is generating this cache entry.
msg := fmt.Sprintf("go: internal cache error: cache verify failed: id=%x changed:<<<\n%s\n>>>\nold: %x %d\nnew: %x %d", id, reverseHash(id), out, size, old.OutputID, old.Size)
panic(msg)
}
}
file := c.fileName(id, "a")
// Copy file to cache directory.
mode := os.O_WRONLY | os.O_CREATE
f, err := os.OpenFile(file, mode, 0666)
if err != nil {
return err
}
_, err = f.WriteString(entry)
if err == nil {
// Truncate the file only *after* writing it.
// (This should be a no-op, but truncate just in case of previous corruption.)
//
// This differs from os.WriteFile, which truncates to 0 *before* writing
// via os.O_TRUNC. Truncating only after writing ensures that a second write
// of the same content to the same file is idempotent, and does not — even
// temporarily! — undo the effect of the first write.
err = f.Truncate(int64(len(entry)))
}
if closeErr := f.Close(); err == nil {
err = closeErr
}
if err != nil {
// TODO(bcmills): This Remove potentially races with another go command writing to file.
// Can we eliminate it?
os.Remove(file)
return err
}
os.Chtimes(file, c.now(), c.now()) // mainly for tests
return nil
}
// noVerifyReadSeeker is an io.ReadSeeker wrapper sentinel type
// that says that Cache.Put should skip the verify check
// (from GODEBUG=goverifycache=1).
type noVerifyReadSeeker struct {
io.ReadSeeker
}
// Put stores the given output in the cache as the output for the action ID.
// It may read file twice. The content of file must not change between the two passes.
func (c *DiskCache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
wrapper, isNoVerify := file.(noVerifyReadSeeker)
if isNoVerify {
file = wrapper.ReadSeeker
}
return c.put(id, file, !isNoVerify)
}
// PutNoVerify is like Put but disables the verify check
// when GODEBUG=goverifycache=1 is set.
// It is meant for data that is OK to cache but that we expect to vary slightly from run to run,
// like test output containing times and the like.
func PutNoVerify(c Cache, id ActionID, file io.ReadSeeker) (OutputID, int64, error) {
return c.Put(id, noVerifyReadSeeker{file})
}
func (c *DiskCache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) {
// Compute output ID.
h := sha256.New()
if _, err := file.Seek(0, 0); err != nil {
return OutputID{}, 0, err
}
size, err := io.Copy(h, file)
if err != nil {
return OutputID{}, 0, err
}
var out OutputID
h.Sum(out[:0])
// Copy to cached output file (if not already present).
if err := c.copyFile(file, out, size); err != nil {
return out, size, err
}
// Add to cache index.
return out, size, c.putIndexEntry(id, out, size, allowVerify)
}
// PutBytes stores the given bytes in the cache as the output for the action ID.
func PutBytes(c Cache, id ActionID, data []byte) error {
_, _, err := c.Put(id, bytes.NewReader(data))
return err
}
// copyFile copies file into the cache, expecting it to have the given
// output ID and size, if that file is not present already.
func (c *DiskCache) copyFile(file io.ReadSeeker, out OutputID, size int64) error {
name := c.fileName(out, "d")
info, err := os.Stat(name)
if err == nil && info.Size() == size {
// Check hash.
if f, err := os.Open(name); err == nil {
h := sha256.New()
io.Copy(h, f)
f.Close()
var out2 OutputID
h.Sum(out2[:0])
if out == out2 {
return nil
}
}
// Hash did not match. Fall through and rewrite file.
}
// Copy file to cache directory.
mode := os.O_RDWR | os.O_CREATE
if err == nil && info.Size() > size { // shouldn't happen but fix in case
mode |= os.O_TRUNC
}
f, err := os.OpenFile(name, mode, 0666)
if err != nil {
return err
}
defer f.Close()
if size == 0 {
// File now exists with correct size.
// Only one possible zero-length file, so contents are OK too.
// Early return here makes sure there's a "last byte" for code below.
return nil
}
// From here on, if any of the I/O writing the file fails,
// we make a best-effort attempt to truncate the file f
// before returning, to avoid leaving bad bytes in the file.
// Copy file to f, but also into h to double-check hash.
if _, err := file.Seek(0, 0); err != nil {
f.Truncate(0)
return err
}
h := sha256.New()
w := io.MultiWriter(f, h)
if _, err := io.CopyN(w, file, size-1); err != nil {
f.Truncate(0)
return err
}
// Check last byte before writing it; writing it will make the size match
// what other processes expect to find and might cause them to start
// using the file.
buf := make([]byte, 1)
if _, err := file.Read(buf); err != nil {
f.Truncate(0)
return err
}
h.Write(buf)
sum := h.Sum(nil)
if !bytes.Equal(sum, out[:]) {
f.Truncate(0)
return fmt.Errorf("file content changed underfoot")
}
// Commit cache file entry.
if _, err := f.Write(buf); err != nil {
f.Truncate(0)
return err
}
if err := f.Close(); err != nil {
// Data might not have been written,
// but file may look like it is the right size.
// To be extra careful, remove cached file.
os.Remove(name)
return err
}
os.Chtimes(name, c.now(), c.now()) // mainly for tests
return nil
}
// FuzzDir returns a subdirectory within the cache for storing fuzzing data.
// The subdirectory may not exist.
//
// This directory is managed by the internal/fuzz package. Files in this
// directory aren't removed by the 'go clean -cache' command or by Trim.
// They may be removed with 'go clean -fuzzcache'.
//
// TODO(#48526): make Trim remove unused files from this directory.
func (c *DiskCache) FuzzDir() string {
return filepath.Join(c.dir, "fuzz")
}

285
src/cmd/go/internal/cache/cache_test.go vendored Normal file
View File

@@ -0,0 +1,285 @@
// Copyright 2017 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 cache
import (
"bytes"
"encoding/binary"
"fmt"
"internal/testenv"
"os"
"path/filepath"
"testing"
"time"
)
func init() {
verify = false // even if GODEBUG is set
}
func TestBasic(t *testing.T) {
dir, err := os.MkdirTemp("", "cachetest-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
_, err = Open(filepath.Join(dir, "notexist"))
if err == nil {
t.Fatal(`Open("tmp/notexist") succeeded, want failure`)
}
cdir := filepath.Join(dir, "c1")
if err := os.Mkdir(cdir, 0777); err != nil {
t.Fatal(err)
}
c1, err := Open(cdir)
if err != nil {
t.Fatalf("Open(c1) (create): %v", err)
}
if err := c1.putIndexEntry(dummyID(1), dummyID(12), 13, true); err != nil {
t.Fatalf("addIndexEntry: %v", err)
}
if err := c1.putIndexEntry(dummyID(1), dummyID(2), 3, true); err != nil { // overwrite entry
t.Fatalf("addIndexEntry: %v", err)
}
if entry, err := c1.Get(dummyID(1)); err != nil || entry.OutputID != dummyID(2) || entry.Size != 3 {
t.Fatalf("c1.Get(1) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(2), 3)
}
c2, err := Open(cdir)
if err != nil {
t.Fatalf("Open(c2) (reuse): %v", err)
}
if entry, err := c2.Get(dummyID(1)); err != nil || entry.OutputID != dummyID(2) || entry.Size != 3 {
t.Fatalf("c2.Get(1) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(2), 3)
}
if err := c2.putIndexEntry(dummyID(2), dummyID(3), 4, true); err != nil {
t.Fatalf("addIndexEntry: %v", err)
}
if entry, err := c1.Get(dummyID(2)); err != nil || entry.OutputID != dummyID(3) || entry.Size != 4 {
t.Fatalf("c1.Get(2) = %x, %v, %v, want %x, %v, nil", entry.OutputID, entry.Size, err, dummyID(3), 4)
}
}
func TestGrowth(t *testing.T) {
dir, err := os.MkdirTemp("", "cachetest-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
c, err := Open(dir)
if err != nil {
t.Fatalf("Open: %v", err)
}
n := 10000
if testing.Short() {
n = 10
}
for i := 0; i < n; i++ {
if err := c.putIndexEntry(dummyID(i), dummyID(i*99), int64(i)*101, true); err != nil {
t.Fatalf("addIndexEntry: %v", err)
}
id := ActionID(dummyID(i))
entry, err := c.Get(id)
if err != nil {
t.Fatalf("Get(%x): %v", id, err)
}
if entry.OutputID != dummyID(i*99) || entry.Size != int64(i)*101 {
t.Errorf("Get(%x) = %x, %d, want %x, %d", id, entry.OutputID, entry.Size, dummyID(i*99), int64(i)*101)
}
}
for i := 0; i < n; i++ {
id := ActionID(dummyID(i))
entry, err := c.Get(id)
if err != nil {
t.Fatalf("Get2(%x): %v", id, err)
}
if entry.OutputID != dummyID(i*99) || entry.Size != int64(i)*101 {
t.Errorf("Get2(%x) = %x, %d, want %x, %d", id, entry.OutputID, entry.Size, dummyID(i*99), int64(i)*101)
}
}
}
func TestVerifyPanic(t *testing.T) {
os.Setenv("GODEBUG", "gocacheverify=1")
initEnv()
defer func() {
os.Unsetenv("GODEBUG")
verify = false
}()
if !verify {
t.Fatal("initEnv did not set verify")
}
dir, err := os.MkdirTemp("", "cachetest-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
c, err := Open(dir)
if err != nil {
t.Fatalf("Open: %v", err)
}
id := ActionID(dummyID(1))
if err := PutBytes(c, id, []byte("abc")); err != nil {
t.Fatal(err)
}
defer func() {
if err := recover(); err != nil {
t.Log(err)
return
}
}()
PutBytes(c, id, []byte("def"))
t.Fatal("mismatched Put did not panic in verify mode")
}
func dummyID(x int) [HashSize]byte {
var out [HashSize]byte
binary.LittleEndian.PutUint64(out[:], uint64(x))
return out
}
func TestCacheTrim(t *testing.T) {
dir, err := os.MkdirTemp("", "cachetest-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
c, err := Open(dir)
if err != nil {
t.Fatalf("Open: %v", err)
}
const start = 1000000000
now := int64(start)
c.now = func() time.Time { return time.Unix(now, 0) }
checkTime := func(name string, mtime int64) {
t.Helper()
file := filepath.Join(c.dir, name[:2], name)
info, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if info.ModTime().Unix() != mtime {
t.Fatalf("%s mtime = %d, want %d", name, info.ModTime().Unix(), mtime)
}
}
id := ActionID(dummyID(1))
PutBytes(c, id, []byte("abc"))
entry, _ := c.Get(id)
PutBytes(c, ActionID(dummyID(2)), []byte("def"))
mtime := now
checkTime(fmt.Sprintf("%x-a", id), mtime)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime)
// Get should not change recent mtimes.
now = start + 10
c.Get(id)
checkTime(fmt.Sprintf("%x-a", id), mtime)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime)
// Get should change distant mtimes.
now = start + 5000
mtime2 := now
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
checkTime(fmt.Sprintf("%x-a", id), mtime2)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime2)
// Trim should leave everything alone: it's all too new.
if err := c.Trim(); err != nil {
if testenv.SyscallIsNotSupported(err) {
t.Skipf("skipping: Trim is unsupported (%v)", err)
}
t.Fatal(err)
}
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
data, err := os.ReadFile(filepath.Join(dir, "trim.txt"))
if err != nil {
t.Fatal(err)
}
checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)
// Trim less than a day later should not do any work at all.
now = start + 80000
if err := c.Trim(); err != nil {
t.Fatal(err)
}
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
data2, err := os.ReadFile(filepath.Join(dir, "trim.txt"))
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, data2) {
t.Fatalf("second trim did work: %q -> %q", data, data2)
}
// Fast forward and do another trim just before the 5 day cutoff.
// Note that because of usedQuantum the cutoff is actually 5 days + 1 hour.
// We used c.Get(id) just now, so 5 days later it should still be kept.
// On the other hand almost a full day has gone by since we wrote dummyID(2)
// and we haven't looked at it since, so 5 days later it should be gone.
now += 5 * 86400
checkTime(fmt.Sprintf("%x-a", dummyID(2)), start)
if err := c.Trim(); err != nil {
t.Fatal(err)
}
if _, err := c.Get(id); err != nil {
t.Fatal(err)
}
c.OutputFile(entry.OutputID)
mtime3 := now
if _, err := c.Get(dummyID(2)); err == nil { // haven't done a Get for this since original write above
t.Fatalf("Trim did not remove dummyID(2)")
}
// The c.Get(id) refreshed id's mtime again.
// Check that another 5 days later it is still not gone,
// but check by using checkTime, which doesn't bring mtime forward.
now += 5 * 86400
if err := c.Trim(); err != nil {
t.Fatal(err)
}
checkTime(fmt.Sprintf("%x-a", id), mtime3)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)
// Half a day later Trim should still be a no-op, because there was a Trim recently.
// Even though the entry for id is now old enough to be trimmed,
// it gets a reprieve until the time comes for a new Trim scan.
now += 86400 / 2
if err := c.Trim(); err != nil {
t.Fatal(err)
}
checkTime(fmt.Sprintf("%x-a", id), mtime3)
checkTime(fmt.Sprintf("%x-d", entry.OutputID), mtime3)
// Another half a day later, Trim should actually run, and it should remove id.
now += 86400/2 + 1
if err := c.Trim(); err != nil {
t.Fatal(err)
}
if _, err := c.Get(dummyID(1)); err == nil {
t.Fatal("Trim did not remove dummyID(1)")
}
}

109
src/cmd/go/internal/cache/default.go vendored Normal file
View File

@@ -0,0 +1,109 @@
// Copyright 2017 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 cache
import (
"fmt"
"os"
"path/filepath"
"sync"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"internal/goexperiment"
)
// Default returns the default cache to use.
// It never returns nil.
func Default() Cache {
defaultOnce.Do(initDefaultCache)
return defaultCache
}
var (
defaultOnce sync.Once
defaultCache Cache
)
// cacheREADME is a message stored in a README in the cache directory.
// Because the cache lives outside the normal Go trees, we leave the
// README as a courtesy to explain where it came from.
const cacheREADME = `This directory holds cached build artifacts from the Go build system.
Run "go clean -cache" if the directory is getting too large.
Run "go clean -fuzzcache" to delete the fuzz cache.
See golang.org to learn more about Go.
`
// initDefaultCache does the work of finding the default cache
// the first time Default is called.
func initDefaultCache() {
dir, _ := DefaultDir()
if dir == "off" {
if defaultDirErr != nil {
base.Fatalf("build cache is required, but could not be located: %v", defaultDirErr)
}
base.Fatalf("build cache is disabled by GOCACHE=off, but required as of Go 1.12")
}
if err := os.MkdirAll(dir, 0777); err != nil {
base.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
}
if _, err := os.Stat(filepath.Join(dir, "README")); err != nil {
// Best effort.
os.WriteFile(filepath.Join(dir, "README"), []byte(cacheREADME), 0666)
}
diskCache, err := Open(dir)
if err != nil {
base.Fatalf("failed to initialize build cache at %s: %s\n", dir, err)
}
if v := cfg.Getenv("GOCACHEPROG"); v != "" && goexperiment.CacheProg {
defaultCache = startCacheProg(v, diskCache)
} else {
defaultCache = diskCache
}
}
var (
defaultDirOnce sync.Once
defaultDir string
defaultDirChanged bool // effective value differs from $GOCACHE
defaultDirErr error
)
// DefaultDir returns the effective GOCACHE setting.
// It returns "off" if the cache is disabled,
// and reports whether the effective value differs from GOCACHE.
func DefaultDir() (string, bool) {
// Save the result of the first call to DefaultDir for later use in
// initDefaultCache. cmd/go/main.go explicitly sets GOCACHE so that
// subprocesses will inherit it, but that means initDefaultCache can't
// otherwise distinguish between an explicit "off" and a UserCacheDir error.
defaultDirOnce.Do(func() {
defaultDir = cfg.Getenv("GOCACHE")
if defaultDir != "" {
defaultDirChanged = true
if filepath.IsAbs(defaultDir) || defaultDir == "off" {
return
}
defaultDir = "off"
defaultDirErr = fmt.Errorf("GOCACHE is not an absolute path")
return
}
// Compute default location.
dir, err := os.UserCacheDir()
if err != nil {
defaultDir = "off"
defaultDirChanged = true
defaultDirErr = fmt.Errorf("GOCACHE is not defined and %v", err)
return
}
defaultDir = filepath.Join(dir, "go-build")
})
return defaultDir, defaultDirChanged
}

190
src/cmd/go/internal/cache/hash.go vendored Normal file
View File

@@ -0,0 +1,190 @@
// Copyright 2017 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 cache
import (
"bytes"
"crypto/sha256"
"fmt"
"hash"
"io"
"os"
"runtime"
"strings"
"sync"
)
var debugHash = false // set when GODEBUG=gocachehash=1
// HashSize is the number of bytes in a hash.
const HashSize = 32
// A Hash provides access to the canonical hash function used to index the cache.
// The current implementation uses salted SHA256, but clients must not assume this.
type Hash struct {
h hash.Hash
name string // for debugging
buf *bytes.Buffer // for verify
}
// hashSalt is a salt string added to the beginning of every hash
// created by NewHash. Using the Go version makes sure that different
// versions of the go command (or even different Git commits during
// work on the development branch) do not address the same cache
// entries, so that a bug in one version does not affect the execution
// of other versions. This salt will result in additional ActionID files
// in the cache, but not additional copies of the large output files,
// which are still addressed by unsalted SHA256.
//
// We strip any GOEXPERIMENTs the go tool was built with from this
// version string on the assumption that they shouldn't affect go tool
// execution. This allows bootstrapping to converge faster: dist builds
// go_bootstrap without any experiments, so by stripping experiments
// go_bootstrap and the final go binary will use the same salt.
var hashSalt = []byte(stripExperiment(runtime.Version()))
// stripExperiment strips any GOEXPERIMENT configuration from the Go
// version string.
func stripExperiment(version string) string {
if i := strings.Index(version, " X:"); i >= 0 {
return version[:i]
}
return version
}
// Subkey returns an action ID corresponding to mixing a parent
// action ID with a string description of the subkey.
func Subkey(parent ActionID, desc string) ActionID {
h := sha256.New()
h.Write([]byte("subkey:"))
h.Write(parent[:])
h.Write([]byte(desc))
var out ActionID
h.Sum(out[:0])
if debugHash {
fmt.Fprintf(os.Stderr, "HASH subkey %x %q = %x\n", parent, desc, out)
}
if verify {
hashDebug.Lock()
hashDebug.m[out] = fmt.Sprintf("subkey %x %q", parent, desc)
hashDebug.Unlock()
}
return out
}
// NewHash returns a new Hash.
// The caller is expected to Write data to it and then call Sum.
func NewHash(name string) *Hash {
h := &Hash{h: sha256.New(), name: name}
if debugHash {
fmt.Fprintf(os.Stderr, "HASH[%s]\n", h.name)
}
h.Write(hashSalt)
if verify {
h.buf = new(bytes.Buffer)
}
return h
}
// Write writes data to the running hash.
func (h *Hash) Write(b []byte) (int, error) {
if debugHash {
fmt.Fprintf(os.Stderr, "HASH[%s]: %q\n", h.name, b)
}
if h.buf != nil {
h.buf.Write(b)
}
return h.h.Write(b)
}
// Sum returns the hash of the data written previously.
func (h *Hash) Sum() [HashSize]byte {
var out [HashSize]byte
h.h.Sum(out[:0])
if debugHash {
fmt.Fprintf(os.Stderr, "HASH[%s]: %x\n", h.name, out)
}
if h.buf != nil {
hashDebug.Lock()
if hashDebug.m == nil {
hashDebug.m = make(map[[HashSize]byte]string)
}
hashDebug.m[out] = h.buf.String()
hashDebug.Unlock()
}
return out
}
// In GODEBUG=gocacheverify=1 mode,
// hashDebug holds the input to every computed hash ID,
// so that we can work backward from the ID involved in a
// cache entry mismatch to a description of what should be there.
var hashDebug struct {
sync.Mutex
m map[[HashSize]byte]string
}
// reverseHash returns the input used to compute the hash id.
func reverseHash(id [HashSize]byte) string {
hashDebug.Lock()
s := hashDebug.m[id]
hashDebug.Unlock()
return s
}
var hashFileCache struct {
sync.Mutex
m map[string][HashSize]byte
}
// FileHash returns the hash of the named file.
// It caches repeated lookups for a given file,
// and the cache entry for a file can be initialized
// using SetFileHash.
// The hash used by FileHash is not the same as
// the hash used by NewHash.
func FileHash(file string) ([HashSize]byte, error) {
hashFileCache.Lock()
out, ok := hashFileCache.m[file]
hashFileCache.Unlock()
if ok {
return out, nil
}
h := sha256.New()
f, err := os.Open(file)
if err != nil {
if debugHash {
fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err)
}
return [HashSize]byte{}, err
}
_, err = io.Copy(h, f)
f.Close()
if err != nil {
if debugHash {
fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err)
}
return [HashSize]byte{}, err
}
h.Sum(out[:0])
if debugHash {
fmt.Fprintf(os.Stderr, "HASH %s: %x\n", file, out)
}
SetFileHash(file, out)
return out, nil
}
// SetFileHash sets the hash returned by FileHash for file.
func SetFileHash(file string, sum [HashSize]byte) {
hashFileCache.Lock()
if hashFileCache.m == nil {
hashFileCache.m = make(map[string][HashSize]byte)
}
hashFileCache.m[file] = sum
hashFileCache.Unlock()
}

51
src/cmd/go/internal/cache/hash_test.go vendored Normal file
View File

@@ -0,0 +1,51 @@
// Copyright 2017 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 cache
import (
"fmt"
"os"
"testing"
)
func TestHash(t *testing.T) {
oldSalt := hashSalt
hashSalt = nil
defer func() {
hashSalt = oldSalt
}()
h := NewHash("alice")
h.Write([]byte("hello world"))
sum := fmt.Sprintf("%x", h.Sum())
want := "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
if sum != want {
t.Errorf("hash(hello world) = %v, want %v", sum, want)
}
}
func TestHashFile(t *testing.T) {
f, err := os.CreateTemp("", "cmd-go-test-")
if err != nil {
t.Fatal(err)
}
name := f.Name()
fmt.Fprintf(f, "hello world")
defer os.Remove(name)
if err := f.Close(); err != nil {
t.Fatal(err)
}
var h ActionID // make sure hash result is assignable to ActionID
h, err = FileHash(name)
if err != nil {
t.Fatal(err)
}
sum := fmt.Sprintf("%x", h)
want := "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
if sum != want {
t.Errorf("hash(hello world) = %v, want %v", sum, want)
}
}

427
src/cmd/go/internal/cache/prog.go vendored Normal file
View File

@@ -0,0 +1,427 @@
// Copyright 2023 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 cache
import (
"bufio"
"cmd/go/internal/base"
"cmd/internal/quoted"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"os/exec"
"sync"
"sync/atomic"
"time"
)
// ProgCache implements Cache via JSON messages over stdin/stdout to a child
// helper process which can then implement whatever caching policy/mechanism it
// wants.
//
// See https://github.com/golang/go/issues/59719
type ProgCache struct {
cmd *exec.Cmd
stdout io.ReadCloser // from the child process
stdin io.WriteCloser // to the child process
bw *bufio.Writer // to stdin
jenc *json.Encoder // to bw
// can are the commands that the child process declared that it supports.
// This is effectively the versioning mechanism.
can map[ProgCmd]bool
// fuzzDirCache is another Cache implementation to use for the FuzzDir
// method. In practice this is the default GOCACHE disk-based
// implementation.
//
// TODO(bradfitz): maybe this isn't ideal. But we'd need to extend the Cache
// interface and the fuzzing callers to be less disk-y to do more here.
fuzzDirCache Cache
closing atomic.Bool
ctx context.Context // valid until Close via ctxClose
ctxCancel context.CancelFunc // called on Close
readLoopDone chan struct{} // closed when readLoop returns
mu sync.Mutex // guards following fields
nextID int64
inFlight map[int64]chan<- *ProgResponse
outputFile map[OutputID]string // object => abs path on disk
// writeMu serializes writing to the child process.
// It must never be held at the same time as mu.
writeMu sync.Mutex
}
// ProgCmd is a command that can be issued to a child process.
//
// If the interface needs to grow, we can add new commands or new versioned
// commands like "get2".
type ProgCmd string
const (
cmdGet = ProgCmd("get")
cmdPut = ProgCmd("put")
cmdClose = ProgCmd("close")
)
// ProgRequest is the JSON-encoded message that's sent from cmd/go to
// the GOCACHEPROG child process over stdin. Each JSON object is on its
// own line. A ProgRequest of Type "put" with BodySize > 0 will be followed
// by a line containing a base64-encoded JSON string literal of the body.
type ProgRequest struct {
// ID is a unique number per process across all requests.
// It must be echoed in the ProgResponse from the child.
ID int64
// Command is the type of request.
// The cmd/go tool will only send commands that were declared
// as supported by the child.
Command ProgCmd
// ActionID is non-nil for get and puts.
ActionID []byte `json:",omitempty"` // or nil if not used
// ObjectID is set for Type "put" and "output-file".
ObjectID []byte `json:",omitempty"` // or nil if not used
// Body is the body for "put" requests. It's sent after the JSON object
// as a base64-encoded JSON string when BodySize is non-zero.
// It's sent as a separate JSON value instead of being a struct field
// send in this JSON object so large values can be streamed in both directions.
// The base64 string body of a ProgRequest will always be written
// immediately after the JSON object and a newline.
Body io.Reader `json:"-"`
// BodySize is the number of bytes of Body. If zero, the body isn't written.
BodySize int64 `json:",omitempty"`
}
// ProgResponse is the JSON response from the child process to cmd/go.
//
// With the exception of the first protocol message that the child writes to its
// stdout with ID==0 and KnownCommands populated, these are only sent in
// response to a ProgRequest from cmd/go.
//
// ProgResponses can be sent in any order. The ID must match the request they're
// replying to.
type ProgResponse struct {
ID int64 // that corresponds to ProgRequest; they can be answered out of order
Err string `json:",omitempty"` // if non-empty, the error
// KnownCommands is included in the first message that cache helper program
// writes to stdout on startup (with ID==0). It includes the
// ProgRequest.Command types that are supported by the program.
//
// This lets us extend the protocol gracefully over time (adding "get2",
// etc), or fail gracefully when needed. It also lets us verify the program
// wants to be a cache helper.
KnownCommands []ProgCmd `json:",omitempty"`
// For Get requests.
Miss bool `json:",omitempty"` // cache miss
OutputID []byte `json:",omitempty"`
Size int64 `json:",omitempty"` // in bytes
Time *time.Time `json:",omitempty"` // an Entry.Time; when the object was added to the docs
// DiskPath is the absolute path on disk of the ObjectID corresponding
// a "get" request's ActionID (on cache hit) or a "put" request's
// provided ObjectID.
DiskPath string `json:",omitempty"`
}
// startCacheProg starts the prog binary (with optional space-separated flags)
// and returns a Cache implementation that talks to it.
//
// It blocks a few seconds to wait for the child process to successfully start
// and advertise its capabilities.
func startCacheProg(progAndArgs string, fuzzDirCache Cache) Cache {
if fuzzDirCache == nil {
panic("missing fuzzDirCache")
}
args, err := quoted.Split(progAndArgs)
if err != nil {
base.Fatalf("GOCACHEPROG args: %v", err)
}
var prog string
if len(args) > 0 {
prog = args[0]
args = args[1:]
}
ctx, ctxCancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, prog, args...)
out, err := cmd.StdoutPipe()
if err != nil {
base.Fatalf("StdoutPipe to GOCACHEPROG: %v", err)
}
in, err := cmd.StdinPipe()
if err != nil {
base.Fatalf("StdinPipe to GOCACHEPROG: %v", err)
}
cmd.Stderr = os.Stderr
cmd.Cancel = in.Close
if err := cmd.Start(); err != nil {
base.Fatalf("error starting GOCACHEPROG program %q: %v", prog, err)
}
pc := &ProgCache{
ctx: ctx,
ctxCancel: ctxCancel,
fuzzDirCache: fuzzDirCache,
cmd: cmd,
stdout: out,
stdin: in,
bw: bufio.NewWriter(in),
inFlight: make(map[int64]chan<- *ProgResponse),
outputFile: make(map[OutputID]string),
readLoopDone: make(chan struct{}),
}
// Register our interest in the initial protocol message from the child to
// us, saying what it can do.
capResc := make(chan *ProgResponse, 1)
pc.inFlight[0] = capResc
pc.jenc = json.NewEncoder(pc.bw)
go pc.readLoop(pc.readLoopDone)
// Give the child process a few seconds to report its capabilities. This
// should be instant and not require any slow work by the program.
timer := time.NewTicker(5 * time.Second)
defer timer.Stop()
for {
select {
case <-timer.C:
log.Printf("# still waiting for GOCACHEPROG %v ...", prog)
case capRes := <-capResc:
can := map[ProgCmd]bool{}
for _, cmd := range capRes.KnownCommands {
can[cmd] = true
}
if len(can) == 0 {
base.Fatalf("GOCACHEPROG %v declared no supported commands", prog)
}
pc.can = can
return pc
}
}
}
func (c *ProgCache) readLoop(readLoopDone chan<- struct{}) {
defer close(readLoopDone)
jd := json.NewDecoder(c.stdout)
for {
res := new(ProgResponse)
if err := jd.Decode(res); err != nil {
if c.closing.Load() {
return // quietly
}
if err == io.EOF {
c.mu.Lock()
inFlight := len(c.inFlight)
c.mu.Unlock()
base.Fatalf("GOCACHEPROG exited pre-Close with %v pending requests", inFlight)
}
base.Fatalf("error reading JSON from GOCACHEPROG: %v", err)
}
c.mu.Lock()
ch, ok := c.inFlight[res.ID]
delete(c.inFlight, res.ID)
c.mu.Unlock()
if ok {
ch <- res
} else {
base.Fatalf("GOCACHEPROG sent response for unknown request ID %v", res.ID)
}
}
}
func (c *ProgCache) send(ctx context.Context, req *ProgRequest) (*ProgResponse, error) {
resc := make(chan *ProgResponse, 1)
if err := c.writeToChild(req, resc); err != nil {
return nil, err
}
select {
case res := <-resc:
if res.Err != "" {
return nil, errors.New(res.Err)
}
return res, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
func (c *ProgCache) writeToChild(req *ProgRequest, resc chan<- *ProgResponse) (err error) {
c.mu.Lock()
c.nextID++
req.ID = c.nextID
c.inFlight[req.ID] = resc
c.mu.Unlock()
defer func() {
if err != nil {
c.mu.Lock()
delete(c.inFlight, req.ID)
c.mu.Unlock()
}
}()
c.writeMu.Lock()
defer c.writeMu.Unlock()
if err := c.jenc.Encode(req); err != nil {
return err
}
if err := c.bw.WriteByte('\n'); err != nil {
return err
}
if req.Body != nil && req.BodySize > 0 {
if err := c.bw.WriteByte('"'); err != nil {
return err
}
e := base64.NewEncoder(base64.StdEncoding, c.bw)
wrote, err := io.Copy(e, req.Body)
if err != nil {
return err
}
if err := e.Close(); err != nil {
return nil
}
if wrote != req.BodySize {
return fmt.Errorf("short write writing body to GOCACHEPROG for action %x, object %x: wrote %v; expected %v",
req.ActionID, req.ObjectID, wrote, req.BodySize)
}
if _, err := c.bw.WriteString("\"\n"); err != nil {
return err
}
}
if err := c.bw.Flush(); err != nil {
return err
}
return nil
}
func (c *ProgCache) Get(a ActionID) (Entry, error) {
if !c.can[cmdGet] {
// They can't do a "get". Maybe they're a write-only cache.
//
// TODO(bradfitz,bcmills): figure out the proper error type here. Maybe
// errors.ErrUnsupported? Is entryNotFoundError even appropriate? There
// might be places where we rely on the fact that a recent Put can be
// read through a corresponding Get. Audit callers and check, and document
// error types on the Cache interface.
return Entry{}, &entryNotFoundError{}
}
res, err := c.send(c.ctx, &ProgRequest{
Command: cmdGet,
ActionID: a[:],
})
if err != nil {
return Entry{}, err // TODO(bradfitz): or entryNotFoundError? Audit callers.
}
if res.Miss {
return Entry{}, &entryNotFoundError{}
}
e := Entry{
Size: res.Size,
}
if res.Time != nil {
e.Time = *res.Time
} else {
e.Time = time.Now()
}
if res.DiskPath == "" {
return Entry{}, &entryNotFoundError{errors.New("GOCACHEPROG didn't populate DiskPath on get hit")}
}
if copy(e.OutputID[:], res.OutputID) != len(res.OutputID) {
return Entry{}, &entryNotFoundError{errors.New("incomplete ProgResponse OutputID")}
}
c.noteOutputFile(e.OutputID, res.DiskPath)
return e, nil
}
func (c *ProgCache) noteOutputFile(o OutputID, diskPath string) {
c.mu.Lock()
defer c.mu.Unlock()
c.outputFile[o] = diskPath
}
func (c *ProgCache) OutputFile(o OutputID) string {
c.mu.Lock()
defer c.mu.Unlock()
return c.outputFile[o]
}
func (c *ProgCache) Put(a ActionID, file io.ReadSeeker) (_ OutputID, size int64, _ error) {
// Compute output ID.
h := sha256.New()
if _, err := file.Seek(0, 0); err != nil {
return OutputID{}, 0, err
}
size, err := io.Copy(h, file)
if err != nil {
return OutputID{}, 0, err
}
var out OutputID
h.Sum(out[:0])
if _, err := file.Seek(0, 0); err != nil {
return OutputID{}, 0, err
}
if !c.can[cmdPut] {
// Child is a read-only cache. Do nothing.
return out, size, nil
}
res, err := c.send(c.ctx, &ProgRequest{
Command: cmdPut,
ActionID: a[:],
ObjectID: out[:],
Body: file,
BodySize: size,
})
if err != nil {
return OutputID{}, 0, err
}
if res.DiskPath == "" {
return OutputID{}, 0, errors.New("GOCACHEPROG didn't return DiskPath in put response")
}
c.noteOutputFile(out, res.DiskPath)
return out, size, err
}
func (c *ProgCache) Close() error {
c.closing.Store(true)
var err error
// First write a "close" message to the child so it can exit nicely
// and clean up if it wants. Only after that exchange do we cancel
// the context that kills the process.
if c.can[cmdClose] {
_, err = c.send(c.ctx, &ProgRequest{Command: cmdClose})
}
c.ctxCancel()
<-c.readLoopDone
return err
}
func (c *ProgCache) FuzzDir() string {
// TODO(bradfitz): figure out what to do here. For now just use the
// disk-based default.
return c.fuzzDirCache.FuzzDir()
}

View File

@@ -0,0 +1,21 @@
// Copyright 2023 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 cfg
import (
"internal/testenv"
"testing"
)
func BenchmarkLookPath(b *testing.B) {
testenv.MustHaveExecPath(b, "go")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := LookPath("go")
if err != nil {
b.Fatal(err)
}
}
}

View File

@@ -0,0 +1,638 @@
// Copyright 2017 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 cfg holds configuration shared by multiple parts
// of the go command.
package cfg
import (
"bytes"
"context"
"fmt"
"go/build"
"internal/buildcfg"
"internal/cfg"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"cmd/go/internal/fsys"
)
// Global build parameters (used during package load)
var (
Goos = envOr("GOOS", build.Default.GOOS)
Goarch = envOr("GOARCH", build.Default.GOARCH)
ExeSuffix = exeSuffix()
// ModulesEnabled specifies whether the go command is running
// in module-aware mode (as opposed to GOPATH mode).
// It is equal to modload.Enabled, but not all packages can import modload.
ModulesEnabled bool
)
func exeSuffix() string {
if Goos == "windows" {
return ".exe"
}
return ""
}
// Configuration for tools installed to GOROOT/bin.
// Normally these match runtime.GOOS and runtime.GOARCH,
// but when testing a cross-compiled cmd/go they will
// indicate the GOOS and GOARCH of the installed cmd/go
// rather than the test binary.
var (
installedGOOS string
installedGOARCH string
)
// ToolExeSuffix returns the suffix for executables installed
// in build.ToolDir.
func ToolExeSuffix() string {
if installedGOOS == "windows" {
return ".exe"
}
return ""
}
// These are general "build flags" used by build and other commands.
var (
BuildA bool // -a flag
BuildBuildmode string // -buildmode flag
BuildBuildvcs = "auto" // -buildvcs flag: "true", "false", or "auto"
BuildContext = defaultContext()
BuildMod string // -mod flag
BuildModExplicit bool // whether -mod was set explicitly
BuildModReason string // reason -mod was set, if set by default
BuildLinkshared bool // -linkshared flag
BuildMSan bool // -msan flag
BuildASan bool // -asan flag
BuildCover bool // -cover flag
BuildCoverMode string // -covermode flag
BuildCoverPkg []string // -coverpkg flag
BuildN bool // -n flag
BuildO string // -o flag
BuildP = runtime.GOMAXPROCS(0) // -p flag
BuildPGO string // -pgo flag
BuildPkgdir string // -pkgdir flag
BuildRace bool // -race flag
BuildToolexec []string // -toolexec flag
BuildToolchainName string
BuildTrimpath bool // -trimpath flag
BuildV bool // -v flag
BuildWork bool // -work flag
BuildX bool // -x flag
ModCacheRW bool // -modcacherw flag
ModFile string // -modfile flag
CmdName string // "build", "install", "list", "mod tidy", etc.
DebugActiongraph string // -debug-actiongraph flag (undocumented, unstable)
DebugTrace string // -debug-trace flag
DebugRuntimeTrace string // -debug-runtime-trace flag (undocumented, unstable)
// GoPathError is set when GOPATH is not set. it contains an
// explanation why GOPATH is unset.
GoPathError string
GOPATHChanged bool
CGOChanged bool
)
func defaultContext() build.Context {
ctxt := build.Default
ctxt.JoinPath = filepath.Join // back door to say "do not use go command"
// Override defaults computed in go/build with defaults
// from go environment configuration file, if known.
ctxt.GOPATH, GOPATHChanged = EnvOrAndChanged("GOPATH", gopath(ctxt))
ctxt.GOOS = Goos
ctxt.GOARCH = Goarch
// Clear the GOEXPERIMENT-based tool tags, which we will recompute later.
var save []string
for _, tag := range ctxt.ToolTags {
if !strings.HasPrefix(tag, "goexperiment.") {
save = append(save, tag)
}
}
ctxt.ToolTags = save
// The go/build rule for whether cgo is enabled is:
// 1. If $CGO_ENABLED is set, respect it.
// 2. Otherwise, if this is a cross-compile, disable cgo.
// 3. Otherwise, use built-in default for GOOS/GOARCH.
//
// Recreate that logic here with the new GOOS/GOARCH setting.
// We need to run steps 2 and 3 to determine what the default value
// of CgoEnabled would be for computing CGOChanged.
defaultCgoEnabled := ctxt.CgoEnabled
if ctxt.GOOS != runtime.GOOS || ctxt.GOARCH != runtime.GOARCH {
defaultCgoEnabled = false
} else {
// Use built-in default cgo setting for GOOS/GOARCH.
// Note that ctxt.GOOS/GOARCH are derived from the preference list
// (1) environment, (2) go/env file, (3) runtime constants,
// while go/build.Default.GOOS/GOARCH are derived from the preference list
// (1) environment, (2) runtime constants.
//
// We know ctxt.GOOS/GOARCH == runtime.GOOS/GOARCH;
// no matter how that happened, go/build.Default will make the
// same decision (either the environment variables are set explicitly
// to match the runtime constants, or else they are unset, in which
// case go/build falls back to the runtime constants), so
// go/build.Default.GOOS/GOARCH == runtime.GOOS/GOARCH.
// So ctxt.CgoEnabled (== go/build.Default.CgoEnabled) is correct
// as is and can be left unmodified.
//
// All that said, starting in Go 1.20 we layer one more rule
// on top of the go/build decision: if CC is unset and
// the default C compiler we'd look for is not in the PATH,
// we automatically default cgo to off.
// This makes go builds work automatically on systems
// without a C compiler installed.
if ctxt.CgoEnabled {
if os.Getenv("CC") == "" {
cc := DefaultCC(ctxt.GOOS, ctxt.GOARCH)
if _, err := LookPath(cc); err != nil {
defaultCgoEnabled = false
}
}
}
}
ctxt.CgoEnabled = defaultCgoEnabled
if v := Getenv("CGO_ENABLED"); v == "0" || v == "1" {
ctxt.CgoEnabled = v[0] == '1'
}
CGOChanged = ctxt.CgoEnabled != defaultCgoEnabled
ctxt.OpenFile = func(path string) (io.ReadCloser, error) {
return fsys.Open(path)
}
ctxt.ReadDir = fsys.ReadDir
ctxt.IsDir = func(path string) bool {
isDir, err := fsys.IsDir(path)
return err == nil && isDir
}
return ctxt
}
func init() {
SetGOROOT(Getenv("GOROOT"), false)
}
// SetGOROOT sets GOROOT and associated variables to the given values.
//
// If isTestGo is true, build.ToolDir is set based on the TESTGO_GOHOSTOS and
// TESTGO_GOHOSTARCH environment variables instead of runtime.GOOS and
// runtime.GOARCH.
func SetGOROOT(goroot string, isTestGo bool) {
BuildContext.GOROOT = goroot
GOROOT = goroot
if goroot == "" {
GOROOTbin = ""
GOROOTpkg = ""
GOROOTsrc = ""
} else {
GOROOTbin = filepath.Join(goroot, "bin")
GOROOTpkg = filepath.Join(goroot, "pkg")
GOROOTsrc = filepath.Join(goroot, "src")
}
installedGOOS = runtime.GOOS
installedGOARCH = runtime.GOARCH
if isTestGo {
if testOS := os.Getenv("TESTGO_GOHOSTOS"); testOS != "" {
installedGOOS = testOS
}
if testArch := os.Getenv("TESTGO_GOHOSTARCH"); testArch != "" {
installedGOARCH = testArch
}
}
if runtime.Compiler != "gccgo" {
if goroot == "" {
build.ToolDir = ""
} else {
// Note that we must use the installed OS and arch here: the tool
// directory does not move based on environment variables, and even if we
// are testing a cross-compiled cmd/go all of the installed packages and
// tools would have been built using the native compiler and linker (and
// would spuriously appear stale if we used a cross-compiled compiler and
// linker).
//
// This matches the initialization of ToolDir in go/build, except for
// using ctxt.GOROOT and the installed GOOS and GOARCH rather than the
// GOROOT, GOOS, and GOARCH reported by the runtime package.
build.ToolDir = filepath.Join(GOROOTpkg, "tool", installedGOOS+"_"+installedGOARCH)
}
}
}
// Experiment configuration.
var (
// RawGOEXPERIMENT is the GOEXPERIMENT value set by the user.
RawGOEXPERIMENT = envOr("GOEXPERIMENT", buildcfg.DefaultGOEXPERIMENT)
// CleanGOEXPERIMENT is the minimal GOEXPERIMENT value needed to reproduce the
// experiments enabled by RawGOEXPERIMENT.
CleanGOEXPERIMENT = RawGOEXPERIMENT
Experiment *buildcfg.ExperimentFlags
ExperimentErr error
)
func init() {
Experiment, ExperimentErr = buildcfg.ParseGOEXPERIMENT(Goos, Goarch, RawGOEXPERIMENT)
if ExperimentErr != nil {
return
}
// GOEXPERIMENT is valid, so convert it to canonical form.
CleanGOEXPERIMENT = Experiment.String()
// Add build tags based on the experiments in effect.
exps := Experiment.Enabled()
expTags := make([]string, 0, len(exps)+len(BuildContext.ToolTags))
for _, exp := range exps {
expTags = append(expTags, "goexperiment."+exp)
}
BuildContext.ToolTags = append(expTags, BuildContext.ToolTags...)
}
// An EnvVar is an environment variable Name=Value.
type EnvVar struct {
Name string
Value string
Changed bool // effective Value differs from default
}
// OrigEnv is the original environment of the program at startup.
var OrigEnv []string
// CmdEnv is the new environment for running go tool commands.
// User binaries (during go test or go run) are run with OrigEnv,
// not CmdEnv.
var CmdEnv []EnvVar
var envCache struct {
once sync.Once
m map[string]string
goroot map[string]string
}
// EnvFile returns the name of the Go environment configuration file,
// and reports whether the effective value differs from the default.
func EnvFile() (string, bool, error) {
if file := os.Getenv("GOENV"); file != "" {
if file == "off" {
return "", false, fmt.Errorf("GOENV=off")
}
return file, true, nil
}
dir, err := os.UserConfigDir()
if err != nil {
return "", false, err
}
if dir == "" {
return "", false, fmt.Errorf("missing user-config dir")
}
return filepath.Join(dir, "go/env"), false, nil
}
func initEnvCache() {
envCache.m = make(map[string]string)
envCache.goroot = make(map[string]string)
if file, _, _ := EnvFile(); file != "" {
readEnvFile(file, "user")
}
goroot := findGOROOT(envCache.m["GOROOT"])
if goroot != "" {
readEnvFile(filepath.Join(goroot, "go.env"), "GOROOT")
}
// Save the goroot for func init calling SetGOROOT,
// and also overwrite anything that might have been in go.env.
// It makes no sense for GOROOT/go.env to specify
// a different GOROOT.
envCache.m["GOROOT"] = goroot
}
func readEnvFile(file string, source string) {
if file == "" {
return
}
data, err := os.ReadFile(file)
if err != nil {
return
}
for len(data) > 0 {
// Get next line.
line := data
i := bytes.IndexByte(data, '\n')
if i >= 0 {
line, data = line[:i], data[i+1:]
} else {
data = nil
}
i = bytes.IndexByte(line, '=')
if i < 0 || line[0] < 'A' || 'Z' < line[0] {
// Line is missing = (or empty) or a comment or not a valid env name. Ignore.
// This should not happen in the user file, since the file should be maintained almost
// exclusively by "go env -w", but better to silently ignore than to make
// the go command unusable just because somehow the env file has
// gotten corrupted.
// In the GOROOT/go.env file, we expect comments.
continue
}
key, val := line[:i], line[i+1:]
if source == "GOROOT" {
envCache.goroot[string(key)] = string(val)
// In the GOROOT/go.env file, do not overwrite fields loaded from the user's go/env file.
if _, ok := envCache.m[string(key)]; ok {
continue
}
}
envCache.m[string(key)] = string(val)
}
}
// Getenv gets the value for the configuration key.
// It consults the operating system environment
// and then the go/env file.
// If Getenv is called for a key that cannot be set
// in the go/env file (for example GODEBUG), it panics.
// This ensures that CanGetenv is accurate, so that
// 'go env -w' stays in sync with what Getenv can retrieve.
func Getenv(key string) string {
if !CanGetenv(key) {
switch key {
case "CGO_TEST_ALLOW", "CGO_TEST_DISALLOW", "CGO_test_ALLOW", "CGO_test_DISALLOW":
// used by internal/work/security_test.go; allow
default:
panic("internal error: invalid Getenv " + key)
}
}
val := os.Getenv(key)
if val != "" {
return val
}
envCache.once.Do(initEnvCache)
return envCache.m[key]
}
// CanGetenv reports whether key is a valid go/env configuration key.
func CanGetenv(key string) bool {
envCache.once.Do(initEnvCache)
if _, ok := envCache.m[key]; ok {
// Assume anything in the user file or go.env file is valid.
return true
}
return strings.Contains(cfg.KnownEnv, "\t"+key+"\n")
}
var (
GOROOT string
// Either empty or produced by filepath.Join(GOROOT, …).
GOROOTbin string
GOROOTpkg string
GOROOTsrc string
GOBIN = Getenv("GOBIN")
GOMODCACHE, GOMODCACHEChanged = EnvOrAndChanged("GOMODCACHE", gopathDir("pkg/mod"))
// Used in envcmd.MkEnv and build ID computations.
GOARM64, goARM64Changed = EnvOrAndChanged("GOARM64", fmt.Sprint(buildcfg.GOARM64))
GOARM, goARMChanged = EnvOrAndChanged("GOARM", fmt.Sprint(buildcfg.GOARM))
GO386, go386Changed = EnvOrAndChanged("GO386", buildcfg.GO386)
GOAMD64, goAMD64Changed = EnvOrAndChanged("GOAMD64", fmt.Sprintf("%s%d", "v", buildcfg.GOAMD64))
GOMIPS, goMIPSChanged = EnvOrAndChanged("GOMIPS", buildcfg.GOMIPS)
GOMIPS64, goMIPS64Changed = EnvOrAndChanged("GOMIPS64", buildcfg.GOMIPS64)
GOPPC64, goPPC64Changed = EnvOrAndChanged("GOPPC64", fmt.Sprintf("%s%d", "power", buildcfg.GOPPC64))
GORISCV64, goRISCV64Changed = EnvOrAndChanged("GORISCV64", fmt.Sprintf("rva%du64", buildcfg.GORISCV64))
GOWASM, goWASMChanged = EnvOrAndChanged("GOWASM", fmt.Sprint(buildcfg.GOWASM))
GOPROXY, GOPROXYChanged = EnvOrAndChanged("GOPROXY", "")
GOSUMDB, GOSUMDBChanged = EnvOrAndChanged("GOSUMDB", "")
GOPRIVATE = Getenv("GOPRIVATE")
GONOPROXY, GONOPROXYChanged = EnvOrAndChanged("GONOPROXY", GOPRIVATE)
GONOSUMDB, GONOSUMDBChanged = EnvOrAndChanged("GONOSUMDB", GOPRIVATE)
GOINSECURE = Getenv("GOINSECURE")
GOVCS = Getenv("GOVCS")
)
// EnvOrAndChanged returns the environment variable value
// and reports whether it differs from the default value.
func EnvOrAndChanged(name, def string) (v string, changed bool) {
val := Getenv(name)
if val != "" {
v = val
if g, ok := envCache.goroot[name]; ok {
changed = val != g
} else {
changed = val != def
}
return v, changed
}
return def, false
}
var SumdbDir = gopathDir("pkg/sumdb")
// GetArchEnv returns the name and setting of the
// GOARCH-specific architecture environment variable.
// If the current architecture has no GOARCH-specific variable,
// GetArchEnv returns empty key and value.
func GetArchEnv() (key, val string, changed bool) {
switch Goarch {
case "arm":
return "GOARM", GOARM, goARMChanged
case "arm64":
return "GOARM64", GOARM64, goARM64Changed
case "386":
return "GO386", GO386, go386Changed
case "amd64":
return "GOAMD64", GOAMD64, goAMD64Changed
case "mips", "mipsle":
return "GOMIPS", GOMIPS, goMIPSChanged
case "mips64", "mips64le":
return "GOMIPS64", GOMIPS64, goMIPS64Changed
case "ppc64", "ppc64le":
return "GOPPC64", GOPPC64, goPPC64Changed
case "riscv64":
return "GORISCV64", GORISCV64, goRISCV64Changed
case "wasm":
return "GOWASM", GOWASM, goWASMChanged
}
return "", "", false
}
// envOr returns Getenv(key) if set, or else def.
func envOr(key, def string) string {
val := Getenv(key)
if val == "" {
val = def
}
return val
}
// There is a copy of findGOROOT, isSameDir, and isGOROOT in
// x/tools/cmd/godoc/goroot.go.
// Try to keep them in sync for now.
// findGOROOT returns the GOROOT value, using either an explicitly
// provided environment variable, a GOROOT that contains the current
// os.Executable value, or else the GOROOT that the binary was built
// with from runtime.GOROOT().
//
// There is a copy of this code in x/tools/cmd/godoc/goroot.go.
func findGOROOT(env string) string {
if env == "" {
// Not using Getenv because findGOROOT is called
// to find the GOROOT/go.env file. initEnvCache
// has passed in the setting from the user go/env file.
env = os.Getenv("GOROOT")
}
if env != "" {
return filepath.Clean(env)
}
def := ""
if r := runtime.GOROOT(); r != "" {
def = filepath.Clean(r)
}
if runtime.Compiler == "gccgo" {
// gccgo has no real GOROOT, and it certainly doesn't
// depend on the executable's location.
return def
}
// canonical returns a directory path that represents
// the same directory as dir,
// preferring the spelling in def if the two are the same.
canonical := func(dir string) string {
if isSameDir(def, dir) {
return def
}
return dir
}
exe, err := os.Executable()
if err == nil {
exe, err = filepath.Abs(exe)
if err == nil {
// cmd/go may be installed in GOROOT/bin or GOROOT/bin/GOOS_GOARCH,
// depending on whether it was cross-compiled with a different
// GOHOSTOS (see https://go.dev/issue/62119). Try both.
if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
return canonical(dir)
}
if dir := filepath.Join(exe, "../../.."); isGOROOT(dir) {
return canonical(dir)
}
// Depending on what was passed on the command line, it is possible
// that os.Executable is a symlink (like /usr/local/bin/go) referring
// to a binary installed in a real GOROOT elsewhere
// (like /usr/lib/go/bin/go).
// Try to find that GOROOT by resolving the symlinks.
exe, err = filepath.EvalSymlinks(exe)
if err == nil {
if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
return canonical(dir)
}
if dir := filepath.Join(exe, "../../.."); isGOROOT(dir) {
return canonical(dir)
}
}
}
}
return def
}
// isSameDir reports whether dir1 and dir2 are the same directory.
func isSameDir(dir1, dir2 string) bool {
if dir1 == dir2 {
return true
}
info1, err1 := os.Stat(dir1)
info2, err2 := os.Stat(dir2)
return err1 == nil && err2 == nil && os.SameFile(info1, info2)
}
// isGOROOT reports whether path looks like a GOROOT.
//
// It does this by looking for the path/pkg/tool directory,
// which is necessary for useful operation of the cmd/go tool,
// and is not typically present in a GOPATH.
//
// There is a copy of this code in x/tools/cmd/godoc/goroot.go.
func isGOROOT(path string) bool {
stat, err := os.Stat(filepath.Join(path, "pkg", "tool"))
if err != nil {
return false
}
return stat.IsDir()
}
func gopathDir(rel string) string {
list := filepath.SplitList(BuildContext.GOPATH)
if len(list) == 0 || list[0] == "" {
return ""
}
return filepath.Join(list[0], rel)
}
// Keep consistent with go/build.defaultGOPATH.
func gopath(ctxt build.Context) string {
if len(ctxt.GOPATH) > 0 {
return ctxt.GOPATH
}
env := "HOME"
if runtime.GOOS == "windows" {
env = "USERPROFILE"
} else if runtime.GOOS == "plan9" {
env = "home"
}
if home := os.Getenv(env); home != "" {
def := filepath.Join(home, "go")
if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) {
GoPathError = "cannot set GOROOT as GOPATH"
}
return ""
}
GoPathError = fmt.Sprintf("%s is not set", env)
return ""
}
// WithBuildXWriter returns a Context in which BuildX output is written
// to given io.Writer.
func WithBuildXWriter(ctx context.Context, xLog io.Writer) context.Context {
return context.WithValue(ctx, buildXContextKey{}, xLog)
}
type buildXContextKey struct{}
// BuildXWriter returns nil if BuildX is false, or
// the writer to which BuildX output should be written otherwise.
func BuildXWriter(ctx context.Context) (io.Writer, bool) {
if !BuildX {
return nil, false
}
if v := ctx.Value(buildXContextKey{}); v != nil {
return v.(io.Writer), true
}
return os.Stderr, true
}

View File

@@ -0,0 +1,21 @@
// Copyright 2023 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 cfg
import (
"cmd/go/internal/par"
"os/exec"
)
var lookPathCache par.ErrCache[string, string]
// LookPath wraps exec.LookPath and caches the result
// which can be called by multiple Goroutines at the same time.
func LookPath(file string) (path string, err error) {
return lookPathCache.Do(file,
func() (string, error) {
return exec.LookPath(file)
})
}

View File

@@ -0,0 +1,401 @@
// Copyright 2012 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 clean implements the “go clean” command.
package clean
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"cmd/go/internal/base"
"cmd/go/internal/cache"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/lockedfile"
"cmd/go/internal/modfetch"
"cmd/go/internal/modload"
"cmd/go/internal/str"
"cmd/go/internal/work"
)
var CmdClean = &base.Command{
UsageLine: "go clean [-i] [-r] [-cache] [-testcache] [-modcache] [-fuzzcache] [build flags] [packages]",
Short: "remove object files and cached files",
Long: `
Clean removes object files from package source directories.
The go command builds most objects in a temporary directory,
so go clean is mainly concerned with object files left by other
tools or by manual invocations of go build.
If a package argument is given or the -i or -r flag is set,
clean removes the following files from each of the
source directories corresponding to the import paths:
_obj/ old object directory, left from Makefiles
_test/ old test directory, left from Makefiles
_testmain.go old gotest file, left from Makefiles
test.out old test log, left from Makefiles
build.out old test log, left from Makefiles
*.[568ao] object files, left from Makefiles
DIR(.exe) from go build
DIR.test(.exe) from go test -c
MAINFILE(.exe) from go build MAINFILE.go
*.so from SWIG
In the list, DIR represents the final path element of the
directory, and MAINFILE is the base name of any Go source
file in the directory that is not included when building
the package.
The -i flag causes clean to remove the corresponding installed
archive or binary (what 'go install' would create).
The -n flag causes clean to print the remove commands it would execute,
but not run them.
The -r flag causes clean to be applied recursively to all the
dependencies of the packages named by the import paths.
The -x flag causes clean to print remove commands as it executes them.
The -cache flag causes clean to remove the entire go build cache.
The -testcache flag causes clean to expire all test results in the
go build cache.
The -modcache flag causes clean to remove the entire module
download cache, including unpacked source code of versioned
dependencies.
The -fuzzcache flag causes clean to remove files stored in the Go build
cache for fuzz testing. The fuzzing engine caches files that expand
code coverage, so removing them may make fuzzing less effective until
new inputs are found that provide the same coverage. These files are
distinct from those stored in testdata directory; clean does not remove
those files.
For more about build flags, see 'go help build'.
For more about specifying packages, see 'go help packages'.
`,
}
var (
cleanI bool // clean -i flag
cleanR bool // clean -r flag
cleanCache bool // clean -cache flag
cleanFuzzcache bool // clean -fuzzcache flag
cleanModcache bool // clean -modcache flag
cleanTestcache bool // clean -testcache flag
)
func init() {
// break init cycle
CmdClean.Run = runClean
CmdClean.Flag.BoolVar(&cleanI, "i", false, "")
CmdClean.Flag.BoolVar(&cleanR, "r", false, "")
CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "")
CmdClean.Flag.BoolVar(&cleanFuzzcache, "fuzzcache", false, "")
CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "")
CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "")
// -n and -x are important enough to be
// mentioned explicitly in the docs but they
// are part of the build flags.
work.AddBuildFlags(CmdClean, work.DefaultBuildFlags)
}
func runClean(ctx context.Context, cmd *base.Command, args []string) {
if len(args) > 0 {
cacheFlag := ""
switch {
case cleanCache:
cacheFlag = "-cache"
case cleanTestcache:
cacheFlag = "-testcache"
case cleanFuzzcache:
cacheFlag = "-fuzzcache"
case cleanModcache:
cacheFlag = "-modcache"
}
if cacheFlag != "" {
base.Fatalf("go: clean %s cannot be used with package arguments", cacheFlag)
}
}
// golang.org/issue/29925: only load packages before cleaning if
// either the flags and arguments explicitly imply a package,
// or no other target (such as a cache) was requested to be cleaned.
cleanPkg := len(args) > 0 || cleanI || cleanR
if (!modload.Enabled() || modload.HasModRoot()) &&
!cleanCache && !cleanModcache && !cleanTestcache && !cleanFuzzcache {
cleanPkg = true
}
if cleanPkg {
for _, pkg := range load.PackagesAndErrors(ctx, load.PackageOpts{}, args) {
clean(pkg)
}
}
sh := work.NewShell("", fmt.Print)
if cleanCache {
dir, _ := cache.DefaultDir()
if dir != "off" {
// Remove the cache subdirectories but not the top cache directory.
// The top cache directory may have been created with special permissions
// and not something that we want to remove. Also, we'd like to preserve
// the access log for future analysis, even if the cache is cleared.
subdirs, _ := filepath.Glob(filepath.Join(str.QuoteGlob(dir), "[0-9a-f][0-9a-f]"))
printedErrors := false
if len(subdirs) > 0 {
if err := sh.RemoveAll(subdirs...); err != nil && !printedErrors {
printedErrors = true
base.Error(err)
}
}
logFile := filepath.Join(dir, "log.txt")
if err := sh.RemoveAll(logFile); err != nil && !printedErrors {
printedErrors = true
base.Error(err)
}
}
}
if cleanTestcache && !cleanCache {
// Instead of walking through the entire cache looking for test results,
// we write a file to the cache indicating that all test results from before
// right now are to be ignored.
dir, _ := cache.DefaultDir()
if dir != "off" {
f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt"))
if err == nil {
now := time.Now().UnixNano()
buf, _ := io.ReadAll(f)
prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64)
if now > prev {
if err = f.Truncate(0); err == nil {
if _, err = f.Seek(0, 0); err == nil {
_, err = fmt.Fprintf(f, "%d\n", now)
}
}
}
if closeErr := f.Close(); err == nil {
err = closeErr
}
}
if err != nil {
if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) {
base.Error(err)
}
}
}
}
if cleanModcache {
if cfg.GOMODCACHE == "" {
base.Fatalf("go: cannot clean -modcache without a module cache")
}
if cfg.BuildN || cfg.BuildX {
sh.ShowCmd("", "rm -rf %s", cfg.GOMODCACHE)
}
if !cfg.BuildN {
if err := modfetch.RemoveAll(cfg.GOMODCACHE); err != nil {
base.Error(err)
}
}
}
if cleanFuzzcache {
fuzzDir := cache.Default().FuzzDir()
if err := sh.RemoveAll(fuzzDir); err != nil {
base.Error(err)
}
}
}
var cleaned = map[*load.Package]bool{}
// TODO: These are dregs left by Makefile-based builds.
// Eventually, can stop deleting these.
var cleanDir = map[string]bool{
"_test": true,
"_obj": true,
}
var cleanFile = map[string]bool{
"_testmain.go": true,
"test.out": true,
"build.out": true,
"a.out": true,
}
var cleanExt = map[string]bool{
".5": true,
".6": true,
".8": true,
".a": true,
".o": true,
".so": true,
}
func clean(p *load.Package) {
if cleaned[p] {
return
}
cleaned[p] = true
if p.Dir == "" {
base.Errorf("%v", p.Error)
return
}
dirs, err := os.ReadDir(p.Dir)
if err != nil {
base.Errorf("go: %s: %v", p.Dir, err)
return
}
sh := work.NewShell("", fmt.Print)
packageFile := map[string]bool{}
if p.Name != "main" {
// Record which files are not in package main.
// The others are.
keep := func(list []string) {
for _, f := range list {
packageFile[f] = true
}
}
keep(p.GoFiles)
keep(p.CgoFiles)
keep(p.TestGoFiles)
keep(p.XTestGoFiles)
}
_, elem := filepath.Split(p.Dir)
var allRemove []string
// Remove dir-named executable only if this is package main.
if p.Name == "main" {
allRemove = append(allRemove,
elem,
elem+".exe",
p.DefaultExecName(),
p.DefaultExecName()+".exe",
)
}
// Remove package test executables.
allRemove = append(allRemove,
elem+".test",
elem+".test.exe",
p.DefaultExecName()+".test",
p.DefaultExecName()+".test.exe",
)
// Remove a potential executable, test executable for each .go file in the directory that
// is not part of the directory's package.
for _, dir := range dirs {
name := dir.Name()
if packageFile[name] {
continue
}
if dir.IsDir() {
continue
}
if base, found := strings.CutSuffix(name, "_test.go"); found {
allRemove = append(allRemove, base+".test", base+".test.exe")
}
if base, found := strings.CutSuffix(name, ".go"); found {
// TODO(adg,rsc): check that this .go file is actually
// in "package main", and therefore capable of building
// to an executable file.
allRemove = append(allRemove, base, base+".exe")
}
}
if cfg.BuildN || cfg.BuildX {
sh.ShowCmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
}
toRemove := map[string]bool{}
for _, name := range allRemove {
toRemove[name] = true
}
for _, dir := range dirs {
name := dir.Name()
if dir.IsDir() {
// TODO: Remove once Makefiles are forgotten.
if cleanDir[name] {
if err := sh.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
base.Error(err)
}
}
continue
}
if cfg.BuildN {
continue
}
if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
removeFile(filepath.Join(p.Dir, name))
}
}
if cleanI && p.Target != "" {
if cfg.BuildN || cfg.BuildX {
sh.ShowCmd("", "rm -f %s", p.Target)
}
if !cfg.BuildN {
removeFile(p.Target)
}
}
if cleanR {
for _, p1 := range p.Internal.Imports {
clean(p1)
}
}
}
// removeFile tries to remove file f, if error other than file doesn't exist
// occurs, it will report the error.
func removeFile(f string) {
err := os.Remove(f)
if err == nil || os.IsNotExist(err) {
return
}
// Windows does not allow deletion of a binary file while it is executing.
if runtime.GOOS == "windows" {
// Remove lingering ~ file from last attempt.
if _, err2 := os.Stat(f + "~"); err2 == nil {
os.Remove(f + "~")
}
// Try to move it out of the way. If the move fails,
// which is likely, we'll try again the
// next time we do an install of this binary.
if err2 := os.Rename(f, f+"~"); err2 == nil {
os.Remove(f + "~")
return
}
}
base.Error(err)
}

View File

@@ -0,0 +1,122 @@
// Copyright 2017 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 cmdflag handles flag processing common to several go tools.
package cmdflag
import (
"errors"
"flag"
"fmt"
"strings"
)
// The flag handling part of go commands such as test is large and distracting.
// We can't use the standard flag package because some of the flags from
// our command line are for us, and some are for the binary we're running,
// and some are for both.
// ErrFlagTerminator indicates the distinguished token "--", which causes the
// flag package to treat all subsequent arguments as non-flags.
var ErrFlagTerminator = errors.New("flag terminator")
// A FlagNotDefinedError indicates a flag-like argument that does not correspond
// to any registered flag in a FlagSet.
type FlagNotDefinedError struct {
RawArg string // the original argument, like --foo or -foo=value
Name string
HasValue bool // is this the -foo=value or --foo=value form?
Value string // only provided if HasValue is true
}
func (e FlagNotDefinedError) Error() string {
return fmt.Sprintf("flag provided but not defined: -%s", e.Name)
}
// A NonFlagError indicates an argument that is not a syntactically-valid flag.
type NonFlagError struct {
RawArg string
}
func (e NonFlagError) Error() string {
return fmt.Sprintf("not a flag: %q", e.RawArg)
}
// ParseOne sees if args[0] is present in the given flag set and if so,
// sets its value and returns the flag along with the remaining (unused) arguments.
//
// ParseOne always returns either a non-nil Flag or a non-nil error,
// and always consumes at least one argument (even on error).
//
// Unlike (*flag.FlagSet).Parse, ParseOne does not log its own errors.
func ParseOne(fs *flag.FlagSet, args []string) (f *flag.Flag, remainingArgs []string, err error) {
// This function is loosely derived from (*flag.FlagSet).parseOne.
raw, args := args[0], args[1:]
arg := raw
if strings.HasPrefix(arg, "--") {
if arg == "--" {
return nil, args, ErrFlagTerminator
}
arg = arg[1:] // reduce two minuses to one
}
switch arg {
case "-?", "-h", "-help":
return nil, args, flag.ErrHelp
}
if len(arg) < 2 || arg[0] != '-' || arg[1] == '-' || arg[1] == '=' {
return nil, args, NonFlagError{RawArg: raw}
}
name, value, hasValue := strings.Cut(arg[1:], "=")
f = fs.Lookup(name)
if f == nil {
return nil, args, FlagNotDefinedError{
RawArg: raw,
Name: name,
HasValue: hasValue,
Value: value,
}
}
// Use fs.Set instead of f.Value.Set below so that any subsequent call to
// fs.Visit will correctly visit the flags that have been set.
failf := func(format string, a ...any) (*flag.Flag, []string, error) {
return f, args, fmt.Errorf(format, a...)
}
if fv, ok := f.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg
if hasValue {
if err := fs.Set(name, value); err != nil {
return failf("invalid boolean value %q for -%s: %v", value, name, err)
}
} else {
if err := fs.Set(name, "true"); err != nil {
return failf("invalid boolean flag %s: %v", name, err)
}
}
} else {
// It must have a value, which might be the next argument.
if !hasValue && len(args) > 0 {
// value is the next arg
hasValue = true
value, args = args[0], args[1:]
}
if !hasValue {
return failf("flag needs an argument: -%s", name)
}
if err := fs.Set(name, value); err != nil {
return failf("invalid value %q for flag -%s: %v", value, name, err)
}
}
return f, args, nil
}
type boolFlag interface {
IsBoolFlag() bool
}

View File

@@ -0,0 +1,134 @@
// 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 doc implements the “go doc” command.
package doc
import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"context"
)
var CmdDoc = &base.Command{
Run: runDoc,
UsageLine: "go doc [doc flags] [package|[package.]symbol[.methodOrField]]",
CustomFlags: true,
Short: "show documentation for package or symbol",
Long: `
Doc prints the documentation comments associated with the item identified by its
arguments (a package, const, func, type, var, method, or struct field)
followed by a one-line summary of each of the first-level items "under"
that item (package-level declarations for a package, methods for a type,
etc.).
Doc accepts zero, one, or two arguments.
Given no arguments, that is, when run as
go doc
it prints the package documentation for the package in the current directory.
If the package is a command (package main), the exported symbols of the package
are elided from the presentation unless the -cmd flag is provided.
When run with one argument, the argument is treated as a Go-syntax-like
representation of the item to be documented. What the argument selects depends
on what is installed in GOROOT and GOPATH, as well as the form of the argument,
which is schematically one of these:
go doc <pkg>
go doc <sym>[.<methodOrField>]
go doc [<pkg>.]<sym>[.<methodOrField>]
go doc [<pkg>.][<sym>.]<methodOrField>
The first item in this list matched by the argument is the one whose documentation
is printed. (See the examples below.) However, if the argument starts with a capital
letter it is assumed to identify a symbol or method in the current directory.
For packages, the order of scanning is determined lexically in breadth-first order.
That is, the package presented is the one that matches the search and is nearest
the root and lexically first at its level of the hierarchy. The GOROOT tree is
always scanned in its entirety before GOPATH.
If there is no package specified or matched, the package in the current
directory is selected, so "go doc Foo" shows the documentation for symbol Foo in
the current package.
The package path must be either a qualified path or a proper suffix of a
path. The go tool's usual package mechanism does not apply: package path
elements like . and ... are not implemented by go doc.
When run with two arguments, the first is a package path (full path or suffix),
and the second is a symbol, or symbol with method or struct field:
go doc <pkg> <sym>[.<methodOrField>]
In all forms, when matching symbols, lower-case letters in the argument match
either case but upper-case letters match exactly. This means that there may be
multiple matches of a lower-case argument in a package if different symbols have
different cases. If this occurs, documentation for all matches is printed.
Examples:
go doc
Show documentation for current package.
go doc Foo
Show documentation for Foo in the current package.
(Foo starts with a capital letter so it cannot match
a package path.)
go doc encoding/json
Show documentation for the encoding/json package.
go doc json
Shorthand for encoding/json.
go doc json.Number (or go doc json.number)
Show documentation and method summary for json.Number.
go doc json.Number.Int64 (or go doc json.number.int64)
Show documentation for json.Number's Int64 method.
go doc cmd/doc
Show package docs for the doc command.
go doc -cmd cmd/doc
Show package docs and exported symbols within the doc command.
go doc template.new
Show documentation for html/template's New function.
(html/template is lexically before text/template)
go doc text/template.new # One argument
Show documentation for text/template's New function.
go doc text/template new # Two arguments
Show documentation for text/template's New function.
At least in the current tree, these invocations all print the
documentation for json.Decoder's Decode method:
go doc json.Decoder.Decode
go doc json.decoder.decode
go doc json.decode
cd go/src/encoding/json; go doc decode
Flags:
-all
Show all the documentation for the package.
-c
Respect case when matching symbols.
-cmd
Treat a command (package main) like a regular package.
Otherwise package main's exported symbols are hidden
when showing the package's top-level documentation.
-short
One-line representation for each symbol.
-src
Show the full source code for the symbol. This will
display the full Go source of its declaration and
definition, such as a function definition (including
the body), type declaration or enclosing const
block. The output may therefore include unexported
details.
-u
Show documentation for unexported as well as exported
symbols, methods, and fields.
`,
}
func runDoc(ctx context.Context, cmd *base.Command, args []string) {
base.Run(cfg.BuildToolexec, base.Tool("doc"), args)
}

View File

@@ -0,0 +1,750 @@
// Copyright 2012 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 envcmd implements the “go env” command.
package envcmd
import (
"bytes"
"context"
"encoding/json"
"fmt"
"go/build"
"internal/buildcfg"
"io"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"unicode"
"unicode/utf8"
"cmd/go/internal/base"
"cmd/go/internal/cache"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/load"
"cmd/go/internal/modload"
"cmd/go/internal/work"
"cmd/internal/quoted"
"cmd/internal/telemetry"
)
var CmdEnv = &base.Command{
UsageLine: "go env [-json] [-changed] [-u] [-w] [var ...]",
Short: "print Go environment information",
Long: `
Env prints Go environment information.
By default env prints information as a shell script
(on Windows, a batch file). If one or more variable
names is given as arguments, env prints the value of
each named variable on its own line.
The -json flag prints the environment in JSON format
instead of as a shell script.
The -u flag requires one or more arguments and unsets
the default setting for the named environment variables,
if one has been set with 'go env -w'.
The -w flag requires one or more arguments of the
form NAME=VALUE and changes the default settings
of the named environment variables to the given values.
The -changed flag prints only those settings whose effective
value differs from the default value that would be obtained in
an empty environment with no prior uses of the -w flag.
For more about environment variables, see 'go help environment'.
`,
}
func init() {
CmdEnv.Run = runEnv // break init cycle
base.AddChdirFlag(&CmdEnv.Flag)
base.AddBuildFlagsNX(&CmdEnv.Flag)
}
var (
envJson = CmdEnv.Flag.Bool("json", false, "")
envU = CmdEnv.Flag.Bool("u", false, "")
envW = CmdEnv.Flag.Bool("w", false, "")
envChanged = CmdEnv.Flag.Bool("changed", false, "")
)
func MkEnv() []cfg.EnvVar {
envFile, envFileChanged, _ := cfg.EnvFile()
env := []cfg.EnvVar{
{Name: "GO111MODULE", Value: cfg.Getenv("GO111MODULE")},
{Name: "GOARCH", Value: cfg.Goarch, Changed: cfg.Goarch != runtime.GOARCH},
{Name: "GOBIN", Value: cfg.GOBIN},
{Name: "GOCACHE"},
{Name: "GOENV", Value: envFile, Changed: envFileChanged},
{Name: "GOEXE", Value: cfg.ExeSuffix},
// List the raw value of GOEXPERIMENT, not the cleaned one.
// The set of default experiments may change from one release
// to the next, so a GOEXPERIMENT setting that is redundant
// with the current toolchain might actually be relevant with
// a different version (for example, when bisecting a regression).
{Name: "GOEXPERIMENT", Value: cfg.RawGOEXPERIMENT},
{Name: "GOFLAGS", Value: cfg.Getenv("GOFLAGS")},
{Name: "GOHOSTARCH", Value: runtime.GOARCH},
{Name: "GOHOSTOS", Value: runtime.GOOS},
{Name: "GOINSECURE", Value: cfg.GOINSECURE},
{Name: "GOMODCACHE", Value: cfg.GOMODCACHE, Changed: cfg.GOMODCACHEChanged},
{Name: "GONOPROXY", Value: cfg.GONOPROXY, Changed: cfg.GONOPROXYChanged},
{Name: "GONOSUMDB", Value: cfg.GONOSUMDB, Changed: cfg.GONOSUMDBChanged},
{Name: "GOOS", Value: cfg.Goos, Changed: cfg.Goos != runtime.GOOS},
{Name: "GOPATH", Value: cfg.BuildContext.GOPATH, Changed: cfg.GOPATHChanged},
{Name: "GOPRIVATE", Value: cfg.GOPRIVATE},
{Name: "GOPROXY", Value: cfg.GOPROXY, Changed: cfg.GOPROXYChanged},
{Name: "GOROOT", Value: cfg.GOROOT},
{Name: "GOSUMDB", Value: cfg.GOSUMDB, Changed: cfg.GOSUMDBChanged},
{Name: "GOTMPDIR", Value: cfg.Getenv("GOTMPDIR")},
{Name: "GOTOOLCHAIN"},
{Name: "GOTOOLDIR", Value: build.ToolDir},
{Name: "GOVCS", Value: cfg.GOVCS},
{Name: "GOVERSION", Value: runtime.Version()},
{Name: "GODEBUG", Value: os.Getenv("GODEBUG")},
{Name: "GOTELEMETRY", Value: telemetry.Mode()},
{Name: "GOTELEMETRYDIR", Value: telemetry.Dir()},
}
for i := range env {
switch env[i].Name {
case "GO111MODULE":
if env[i].Value != "on" && env[i].Value != "" {
env[i].Changed = true
}
case "GOBIN", "GOEXPERIMENT", "GOFLAGS", "GOINSECURE", "GOPRIVATE", "GOTMPDIR", "GOVCS":
if env[i].Value != "" {
env[i].Changed = true
}
case "GOCACHE":
env[i].Value, env[i].Changed = cache.DefaultDir()
case "GOTOOLCHAIN":
env[i].Value, env[i].Changed = cfg.EnvOrAndChanged("GOTOOLCHAIN", "")
case "GODEBUG":
env[i].Changed = env[i].Value != ""
}
}
if work.GccgoBin != "" {
env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoBin, Changed: true})
} else {
env = append(env, cfg.EnvVar{Name: "GCCGO", Value: work.GccgoName})
}
goarch, val, changed := cfg.GetArchEnv()
if goarch != "" {
env = append(env, cfg.EnvVar{Name: goarch, Value: val, Changed: changed})
}
cc := cfg.Getenv("CC")
ccChanged := true
if cc == "" {
ccChanged = false
cc = cfg.DefaultCC(cfg.Goos, cfg.Goarch)
}
cxx := cfg.Getenv("CXX")
cxxChanged := true
if cxx == "" {
cxxChanged = false
cxx = cfg.DefaultCXX(cfg.Goos, cfg.Goarch)
}
ar, arChanged := cfg.EnvOrAndChanged("AR", "ar")
env = append(env, cfg.EnvVar{Name: "AR", Value: ar, Changed: arChanged})
env = append(env, cfg.EnvVar{Name: "CC", Value: cc, Changed: ccChanged})
env = append(env, cfg.EnvVar{Name: "CXX", Value: cxx, Changed: cxxChanged})
if cfg.BuildContext.CgoEnabled {
env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "1", Changed: cfg.CGOChanged})
} else {
env = append(env, cfg.EnvVar{Name: "CGO_ENABLED", Value: "0", Changed: cfg.CGOChanged})
}
return env
}
func findEnv(env []cfg.EnvVar, name string) string {
for _, e := range env {
if e.Name == name {
return e.Value
}
}
if cfg.CanGetenv(name) {
return cfg.Getenv(name)
}
return ""
}
// ExtraEnvVars returns environment variables that should not leak into child processes.
func ExtraEnvVars() []cfg.EnvVar {
gomod := ""
modload.Init()
if modload.HasModRoot() {
gomod = modload.ModFilePath()
} else if modload.Enabled() {
gomod = os.DevNull
}
modload.InitWorkfile()
gowork := modload.WorkFilePath()
// As a special case, if a user set off explicitly, report that in GOWORK.
if cfg.Getenv("GOWORK") == "off" {
gowork = "off"
}
return []cfg.EnvVar{
{Name: "GOMOD", Value: gomod},
{Name: "GOWORK", Value: gowork},
}
}
// ExtraEnvVarsCostly returns environment variables that should not leak into child processes
// but are costly to evaluate.
func ExtraEnvVarsCostly() []cfg.EnvVar {
b := work.NewBuilder("")
defer func() {
if err := b.Close(); err != nil {
base.Fatal(err)
}
}()
cppflags, cflags, cxxflags, fflags, ldflags, err := b.CFlags(&load.Package{})
if err != nil {
// Should not happen - b.CFlags was given an empty package.
fmt.Fprintf(os.Stderr, "go: invalid cflags: %v\n", err)
return nil
}
cmd := b.GccCmd(".", "")
join := func(s []string) string {
q, err := quoted.Join(s)
if err != nil {
return strings.Join(s, " ")
}
return q
}
ret := []cfg.EnvVar{
// Note: Update the switch in runEnv below when adding to this list.
{Name: "CGO_CFLAGS", Value: join(cflags)},
{Name: "CGO_CPPFLAGS", Value: join(cppflags)},
{Name: "CGO_CXXFLAGS", Value: join(cxxflags)},
{Name: "CGO_FFLAGS", Value: join(fflags)},
{Name: "CGO_LDFLAGS", Value: join(ldflags)},
{Name: "PKG_CONFIG", Value: b.PkgconfigCmd()},
{Name: "GOGCCFLAGS", Value: join(cmd[3:])},
}
for i := range ret {
ev := &ret[i]
switch ev.Name {
case "GOGCCFLAGS": // GOGCCFLAGS cannot be modified
case "CGO_CPPFLAGS":
ev.Changed = ev.Value != ""
case "PKG_CONFIG":
ev.Changed = ev.Value != cfg.DefaultPkgConfig
case "CGO_CXXFLAGS", "CGO_CFLAGS", "CGO_FFLAGS", "GGO_LDFLAGS":
ev.Changed = ev.Value != work.DefaultCFlags
}
}
return ret
}
// argKey returns the KEY part of the arg KEY=VAL, or else arg itself.
func argKey(arg string) string {
i := strings.Index(arg, "=")
if i < 0 {
return arg
}
return arg[:i]
}
func runEnv(ctx context.Context, cmd *base.Command, args []string) {
if *envJson && *envU {
base.Fatalf("go: cannot use -json with -u")
}
if *envJson && *envW {
base.Fatalf("go: cannot use -json with -w")
}
if *envU && *envW {
base.Fatalf("go: cannot use -u with -w")
}
// Handle 'go env -w' and 'go env -u' before calling buildcfg.Check,
// so they can be used to recover from an invalid configuration.
if *envW {
runEnvW(args)
return
}
if *envU {
runEnvU(args)
return
}
buildcfg.Check()
if cfg.ExperimentErr != nil {
base.Fatal(cfg.ExperimentErr)
}
for _, arg := range args {
if strings.Contains(arg, "=") {
base.Fatalf("go: invalid variable name %q (use -w to set variable)", arg)
}
}
env := cfg.CmdEnv
env = append(env, ExtraEnvVars()...)
if err := fsys.Init(base.Cwd()); err != nil {
base.Fatal(err)
}
// Do we need to call ExtraEnvVarsCostly, which is a bit expensive?
needCostly := false
if len(args) == 0 {
// We're listing all environment variables ("go env"),
// including the expensive ones.
needCostly = true
} else {
needCostly = false
checkCostly:
for _, arg := range args {
switch argKey(arg) {
case "CGO_CFLAGS",
"CGO_CPPFLAGS",
"CGO_CXXFLAGS",
"CGO_FFLAGS",
"CGO_LDFLAGS",
"PKG_CONFIG",
"GOGCCFLAGS":
needCostly = true
break checkCostly
}
}
}
if needCostly {
work.BuildInit()
env = append(env, ExtraEnvVarsCostly()...)
}
if len(args) > 0 {
// Show only the named vars.
if !*envChanged {
if *envJson {
var es []cfg.EnvVar
for _, name := range args {
e := cfg.EnvVar{Name: name, Value: findEnv(env, name)}
es = append(es, e)
}
env = es
} else {
// Print just the values, without names.
for _, name := range args {
fmt.Printf("%s\n", findEnv(env, name))
}
return
}
} else {
// Show only the changed, named vars.
var es []cfg.EnvVar
for _, name := range args {
for _, e := range env {
if e.Name == name {
es = append(es, e)
break
}
}
}
env = es
}
}
// print
if *envJson {
printEnvAsJSON(env, *envChanged)
} else {
PrintEnv(os.Stdout, env, *envChanged)
}
}
func runEnvW(args []string) {
// Process and sanity-check command line.
if len(args) == 0 {
base.Fatalf("go: no KEY=VALUE arguments given")
}
osEnv := make(map[string]string)
for _, e := range cfg.OrigEnv {
if i := strings.Index(e, "="); i >= 0 {
osEnv[e[:i]] = e[i+1:]
}
}
add := make(map[string]string)
for _, arg := range args {
key, val, found := strings.Cut(arg, "=")
if !found {
base.Fatalf("go: arguments must be KEY=VALUE: invalid argument: %s", arg)
}
if err := checkEnvWrite(key, val); err != nil {
base.Fatal(err)
}
if _, ok := add[key]; ok {
base.Fatalf("go: multiple values for key: %s", key)
}
add[key] = val
if osVal := osEnv[key]; osVal != "" && osVal != val {
fmt.Fprintf(os.Stderr, "warning: go env -w %s=... does not override conflicting OS environment variable\n", key)
}
}
if err := checkBuildConfig(add, nil); err != nil {
base.Fatal(err)
}
gotmp, okGOTMP := add["GOTMPDIR"]
if okGOTMP {
if !filepath.IsAbs(gotmp) && gotmp != "" {
base.Fatalf("go: GOTMPDIR must be an absolute path")
}
}
updateEnvFile(add, nil)
}
func runEnvU(args []string) {
// Process and sanity-check command line.
if len(args) == 0 {
base.Fatalf("go: 'go env -u' requires an argument")
}
del := make(map[string]bool)
for _, arg := range args {
if err := checkEnvWrite(arg, ""); err != nil {
base.Fatal(err)
}
del[arg] = true
}
if err := checkBuildConfig(nil, del); err != nil {
base.Fatal(err)
}
updateEnvFile(nil, del)
}
// checkBuildConfig checks whether the build configuration is valid
// after the specified configuration environment changes are applied.
func checkBuildConfig(add map[string]string, del map[string]bool) error {
// get returns the value for key after applying add and del and
// reports whether it changed. cur should be the current value
// (i.e., before applying changes) and def should be the default
// value (i.e., when no environment variables are provided at all).
get := func(key, cur, def string) (string, bool) {
if val, ok := add[key]; ok {
return val, true
}
if del[key] {
val := getOrigEnv(key)
if val == "" {
val = def
}
return val, true
}
return cur, false
}
goos, okGOOS := get("GOOS", cfg.Goos, build.Default.GOOS)
goarch, okGOARCH := get("GOARCH", cfg.Goarch, build.Default.GOARCH)
if okGOOS || okGOARCH {
if err := work.CheckGOOSARCHPair(goos, goarch); err != nil {
return err
}
}
goexperiment, okGOEXPERIMENT := get("GOEXPERIMENT", cfg.RawGOEXPERIMENT, buildcfg.DefaultGOEXPERIMENT)
if okGOEXPERIMENT {
if _, err := buildcfg.ParseGOEXPERIMENT(goos, goarch, goexperiment); err != nil {
return err
}
}
return nil
}
// PrintEnv prints the environment variables to w.
func PrintEnv(w io.Writer, env []cfg.EnvVar, onlyChanged bool) {
for _, e := range env {
if e.Name != "TERM" {
if runtime.GOOS != "plan9" && bytes.Contains([]byte(e.Value), []byte{0}) {
base.Fatalf("go: internal error: encountered null byte in environment variable %s on non-plan9 platform", e.Name)
}
if onlyChanged && !e.Changed {
continue
}
switch runtime.GOOS {
default:
fmt.Fprintf(w, "%s=%s\n", e.Name, shellQuote(e.Value))
case "plan9":
if strings.IndexByte(e.Value, '\x00') < 0 {
fmt.Fprintf(w, "%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''"))
} else {
v := strings.Split(e.Value, "\x00")
fmt.Fprintf(w, "%s=(", e.Name)
for x, s := range v {
if x > 0 {
fmt.Fprintf(w, " ")
}
fmt.Fprintf(w, "'%s'", strings.ReplaceAll(s, "'", "''"))
}
fmt.Fprintf(w, ")\n")
}
case "windows":
if hasNonGraphic(e.Value) {
base.Errorf("go: stripping unprintable or unescapable characters from %%%q%%", e.Name)
}
fmt.Fprintf(w, "set %s=%s\n", e.Name, batchEscape(e.Value))
}
}
}
}
func hasNonGraphic(s string) bool {
for _, c := range []byte(s) {
if c == '\r' || c == '\n' || (!unicode.IsGraphic(rune(c)) && !unicode.IsSpace(rune(c))) {
return true
}
}
return false
}
func shellQuote(s string) string {
var b bytes.Buffer
b.WriteByte('\'')
for _, x := range []byte(s) {
if x == '\'' {
// Close the single quoted string, add an escaped single quote,
// and start another single quoted string.
b.WriteString(`'\''`)
} else {
b.WriteByte(x)
}
}
b.WriteByte('\'')
return b.String()
}
func batchEscape(s string) string {
var b bytes.Buffer
for _, x := range []byte(s) {
if x == '\r' || x == '\n' || (!unicode.IsGraphic(rune(x)) && !unicode.IsSpace(rune(x))) {
b.WriteRune(unicode.ReplacementChar)
continue
}
switch x {
case '%':
b.WriteString("%%")
case '<', '>', '|', '&', '^':
// These are special characters that need to be escaped with ^. See
// https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/set_1.
b.WriteByte('^')
b.WriteByte(x)
default:
b.WriteByte(x)
}
}
return b.String()
}
func printEnvAsJSON(env []cfg.EnvVar, onlyChanged bool) {
m := make(map[string]string)
for _, e := range env {
if e.Name == "TERM" {
continue
}
if onlyChanged && !e.Changed {
continue
}
m[e.Name] = e.Value
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "\t")
if err := enc.Encode(m); err != nil {
base.Fatalf("go: %s", err)
}
}
func getOrigEnv(key string) string {
for _, v := range cfg.OrigEnv {
if v, found := strings.CutPrefix(v, key+"="); found {
return v
}
}
return ""
}
func checkEnvWrite(key, val string) error {
switch key {
case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOWORK", "GOTOOLDIR", "GOVERSION":
return fmt.Errorf("%s cannot be modified", key)
case "GOENV", "GODEBUG":
return fmt.Errorf("%s can only be set using the OS environment", key)
}
// To catch typos and the like, check that we know the variable.
// If it's already in the env file, we assume it's known.
if !cfg.CanGetenv(key) {
return fmt.Errorf("unknown go command variable %s", key)
}
// Some variables can only have one of a few valid values. If set to an
// invalid value, the next cmd/go invocation might fail immediately,
// even 'go env -w' itself.
switch key {
case "GO111MODULE":
switch val {
case "", "auto", "on", "off":
default:
return fmt.Errorf("invalid %s value %q", key, val)
}
case "GOPATH":
if strings.HasPrefix(val, "~") {
return fmt.Errorf("GOPATH entry cannot start with shell metacharacter '~': %q", val)
}
if !filepath.IsAbs(val) && val != "" {
return fmt.Errorf("GOPATH entry is relative; must be absolute path: %q", val)
}
case "GOMODCACHE":
if !filepath.IsAbs(val) && val != "" {
return fmt.Errorf("GOMODCACHE entry is relative; must be absolute path: %q", val)
}
case "CC", "CXX":
if val == "" {
break
}
args, err := quoted.Split(val)
if err != nil {
return fmt.Errorf("invalid %s: %v", key, err)
}
if len(args) == 0 {
return fmt.Errorf("%s entry cannot contain only space", key)
}
if !filepath.IsAbs(args[0]) && args[0] != filepath.Base(args[0]) {
return fmt.Errorf("%s entry is relative; must be absolute path: %q", key, args[0])
}
}
if !utf8.ValidString(val) {
return fmt.Errorf("invalid UTF-8 in %s=... value", key)
}
if strings.Contains(val, "\x00") {
return fmt.Errorf("invalid NUL in %s=... value", key)
}
if strings.ContainsAny(val, "\v\r\n") {
return fmt.Errorf("invalid newline in %s=... value", key)
}
return nil
}
func readEnvFileLines(mustExist bool) []string {
file, _, err := cfg.EnvFile()
if file == "" {
if mustExist {
base.Fatalf("go: cannot find go env config: %v", err)
}
return nil
}
data, err := os.ReadFile(file)
if err != nil && (!os.IsNotExist(err) || mustExist) {
base.Fatalf("go: reading go env config: %v", err)
}
lines := strings.SplitAfter(string(data), "\n")
if lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
} else {
lines[len(lines)-1] += "\n"
}
return lines
}
func updateEnvFile(add map[string]string, del map[string]bool) {
lines := readEnvFileLines(len(add) == 0)
// Delete all but last copy of any duplicated variables,
// since the last copy is the one that takes effect.
prev := make(map[string]int)
for l, line := range lines {
if key := lineToKey(line); key != "" {
if p, ok := prev[key]; ok {
lines[p] = ""
}
prev[key] = l
}
}
// Add variables (go env -w). Update existing lines in file if present, add to end otherwise.
for key, val := range add {
if p, ok := prev[key]; ok {
lines[p] = key + "=" + val + "\n"
delete(add, key)
}
}
for key, val := range add {
lines = append(lines, key+"="+val+"\n")
}
// Delete requested variables (go env -u).
for key := range del {
if p, ok := prev[key]; ok {
lines[p] = ""
}
}
// Sort runs of KEY=VALUE lines
// (that is, blocks of lines where blocks are separated
// by comments, blank lines, or invalid lines).
start := 0
for i := 0; i <= len(lines); i++ {
if i == len(lines) || lineToKey(lines[i]) == "" {
sortKeyValues(lines[start:i])
start = i + 1
}
}
file, _, err := cfg.EnvFile()
if file == "" {
base.Fatalf("go: cannot find go env config: %v", err)
}
data := []byte(strings.Join(lines, ""))
err = os.WriteFile(file, data, 0666)
if err != nil {
// Try creating directory.
os.MkdirAll(filepath.Dir(file), 0777)
err = os.WriteFile(file, data, 0666)
if err != nil {
base.Fatalf("go: writing go env config: %v", err)
}
}
}
// lineToKey returns the KEY part of the line KEY=VALUE or else an empty string.
func lineToKey(line string) string {
i := strings.Index(line, "=")
if i < 0 || strings.Contains(line[:i], "#") {
return ""
}
return line[:i]
}
// sortKeyValues sorts a sequence of lines by key.
// It differs from sort.Strings in that GO386= sorts after GO=.
func sortKeyValues(lines []string) {
sort.Slice(lines, func(i, j int) bool {
return lineToKey(lines[i]) < lineToKey(lines[j])
})
}

View File

@@ -0,0 +1,93 @@
// Copyright 2022 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.
//go:build unix || windows
package envcmd
import (
"bytes"
"cmd/go/internal/cfg"
"fmt"
"internal/testenv"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"unicode"
)
func FuzzPrintEnvEscape(f *testing.F) {
f.Add(`$(echo 'cc"'; echo 'OOPS="oops')`)
f.Add("$(echo shell expansion 1>&2)")
f.Add("''")
f.Add(`C:\"Program Files"\`)
f.Add(`\\"Quoted Host"\\share`)
f.Add("\xfb")
f.Add("0")
f.Add("")
f.Add("''''''''")
f.Add("\r")
f.Add("\n")
f.Add("E,%")
f.Fuzz(func(t *testing.T, s string) {
t.Parallel()
for _, c := range []byte(s) {
if c == 0 {
t.Skipf("skipping %q: contains a null byte. Null bytes can't occur in the environment"+
" outside of Plan 9, which has different code path than Windows and Unix that this test"+
" isn't testing.", s)
}
if c > unicode.MaxASCII {
t.Skipf("skipping %#q: contains a non-ASCII character %q", s, c)
}
if !unicode.IsGraphic(rune(c)) && !unicode.IsSpace(rune(c)) {
t.Skipf("skipping %#q: contains non-graphic character %q", s, c)
}
if runtime.GOOS == "windows" && c == '\r' || c == '\n' {
t.Skipf("skipping %#q on Windows: contains unescapable character %q", s, c)
}
}
var b bytes.Buffer
if runtime.GOOS == "windows" {
b.WriteString("@echo off\n")
}
PrintEnv(&b, []cfg.EnvVar{{Name: "var", Value: s}}, false)
var want string
if runtime.GOOS == "windows" {
fmt.Fprintf(&b, "echo \"%%var%%\"\n")
want += "\"" + s + "\"\r\n"
} else {
fmt.Fprintf(&b, "printf '%%s\\n' \"$var\"\n")
want += s + "\n"
}
scriptfilename := "script.sh"
if runtime.GOOS == "windows" {
scriptfilename = "script.bat"
}
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
scriptfile := filepath.Join(t.TempDir(), scriptfilename)
if err := os.WriteFile(scriptfile, b.Bytes(), 0777); err != nil {
t.Fatal(err)
}
cmd = testenv.Command(t, "cmd.exe", "/C", scriptfile)
} else {
cmd = testenv.Command(t, "sh", "-c", b.String())
}
out, err := cmd.Output()
t.Log(string(out))
if err != nil {
t.Fatal(err)
}
if string(out) != want {
t.Fatalf("output of running PrintEnv script and echoing variable: got: %q, want: %q",
string(out), want)
}
})
}

View File

@@ -0,0 +1,85 @@
// Copyright 2011 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 fix implements the “go fix” command.
package fix
import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/modload"
"cmd/go/internal/str"
"cmd/go/internal/work"
"context"
"fmt"
"go/build"
"os"
)
var CmdFix = &base.Command{
UsageLine: "go fix [-fix list] [packages]",
Short: "update packages to use new APIs",
Long: `
Fix runs the Go fix command on the packages named by the import paths.
The -fix flag sets a comma-separated list of fixes to run.
The default is all known fixes.
(Its value is passed to 'go tool fix -r'.)
For more about fix, see 'go doc cmd/fix'.
For more about specifying packages, see 'go help packages'.
To run fix with other options, run 'go tool fix'.
See also: go fmt, go vet.
`,
}
var fixes = CmdFix.Flag.String("fix", "", "comma-separated list of fixes to apply")
func init() {
work.AddBuildFlags(CmdFix, work.DefaultBuildFlags)
CmdFix.Run = runFix // fix cycle
}
func runFix(ctx context.Context, cmd *base.Command, args []string) {
pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{}, args)
w := 0
for _, pkg := range pkgs {
if pkg.Error != nil {
base.Errorf("%v", pkg.Error)
continue
}
pkgs[w] = pkg
w++
}
pkgs = pkgs[:w]
printed := false
for _, pkg := range pkgs {
if modload.Enabled() && pkg.Module != nil && !pkg.Module.Main {
if !printed {
fmt.Fprintf(os.Stderr, "go: not fixing packages in dependency modules\n")
printed = true
}
continue
}
// Use pkg.gofiles instead of pkg.Dir so that
// the command only applies to this package,
// not to packages in subdirectories.
files := base.RelPaths(pkg.InternalAllGoFiles())
goVersion := ""
if pkg.Module != nil {
goVersion = "go" + pkg.Module.GoVersion
} else if pkg.Standard {
goVersion = build.Default.ReleaseTags[len(build.Default.ReleaseTags)-1]
}
var fixArg []string
if *fixes != "" {
fixArg = []string{"-r=" + *fixes}
}
base.Run(str.StringList(cfg.BuildToolexec, base.Tool("fix"), "-go="+goVersion, fixArg, files))
}
}

View File

@@ -0,0 +1,115 @@
// Copyright 2011 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 fmtcmd implements the “go fmt” command.
package fmtcmd
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/modload"
"cmd/internal/sys"
)
func init() {
base.AddBuildFlagsNX(&CmdFmt.Flag)
base.AddChdirFlag(&CmdFmt.Flag)
base.AddModFlag(&CmdFmt.Flag)
base.AddModCommonFlags(&CmdFmt.Flag)
}
var CmdFmt = &base.Command{
Run: runFmt,
UsageLine: "go fmt [-n] [-x] [packages]",
Short: "gofmt (reformat) package sources",
Long: `
Fmt runs the command 'gofmt -l -w' on the packages named
by the import paths. It prints the names of the files that are modified.
For more about gofmt, see 'go doc cmd/gofmt'.
For more about specifying packages, see 'go help packages'.
The -n flag prints commands that would be executed.
The -x flag prints commands as they are executed.
The -mod flag's value sets which module download mode
to use: readonly or vendor. See 'go help modules' for more.
To run gofmt with specific options, run gofmt itself.
See also: go fix, go vet.
`,
}
func runFmt(ctx context.Context, cmd *base.Command, args []string) {
printed := false
gofmt := gofmtPath()
gofmtArgs := []string{gofmt, "-l", "-w"}
gofmtArgLen := len(gofmt) + len(" -l -w")
baseGofmtArgs := len(gofmtArgs)
baseGofmtArgLen := gofmtArgLen
for _, pkg := range load.PackagesAndErrors(ctx, load.PackageOpts{}, args) {
if modload.Enabled() && pkg.Module != nil && !pkg.Module.Main {
if !printed {
fmt.Fprintf(os.Stderr, "go: not formatting packages in dependency modules\n")
printed = true
}
continue
}
if pkg.Error != nil {
var nogo *load.NoGoError
var embed *load.EmbedError
if (errors.As(pkg.Error, &nogo) || errors.As(pkg.Error, &embed)) && len(pkg.InternalAllGoFiles()) > 0 {
// Skip this error, as we will format
// all files regardless.
} else {
base.Errorf("%v", pkg.Error)
continue
}
}
// Use pkg.gofiles instead of pkg.Dir so that
// the command only applies to this package,
// not to packages in subdirectories.
files := base.RelPaths(pkg.InternalAllGoFiles())
for _, file := range files {
gofmtArgs = append(gofmtArgs, file)
gofmtArgLen += 1 + len(file) // plus separator
if gofmtArgLen >= sys.ExecArgLengthLimit {
base.Run(gofmtArgs)
gofmtArgs = gofmtArgs[:baseGofmtArgs]
gofmtArgLen = baseGofmtArgLen
}
}
}
if len(gofmtArgs) > baseGofmtArgs {
base.Run(gofmtArgs)
}
}
func gofmtPath() string {
gofmt := "gofmt" + cfg.ToolExeSuffix()
gofmtPath := filepath.Join(cfg.GOBIN, gofmt)
if _, err := os.Stat(gofmtPath); err == nil {
return gofmtPath
}
gofmtPath = filepath.Join(cfg.GOROOT, "bin", gofmt)
if _, err := os.Stat(gofmtPath); err == nil {
return gofmtPath
}
// fallback to looking for gofmt in $PATH
return "gofmt"
}

View File

@@ -0,0 +1,790 @@
// Copyright 2020 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 fsys is an abstraction for reading files that
// allows for virtual overlays on top of the files on disk.
package fsys
import (
"encoding/json"
"errors"
"fmt"
"internal/godebug"
"io"
"io/fs"
"log"
"os"
pathpkg "path"
"path/filepath"
"runtime"
"runtime/debug"
"sort"
"strings"
"sync"
"time"
)
// Trace emits a trace event for the operation and file path to the trace log,
// but only when $GODEBUG contains gofsystrace=1.
// The traces are appended to the file named by the $GODEBUG setting gofsystracelog, or else standard error.
// For debugging, if the $GODEBUG setting gofsystracestack is non-empty, then trace events for paths
// matching that glob pattern (using path.Match) will be followed by a full stack trace.
func Trace(op, path string) {
if !doTrace {
return
}
traceMu.Lock()
defer traceMu.Unlock()
fmt.Fprintf(traceFile, "%d gofsystrace %s %s\n", os.Getpid(), op, path)
if pattern := gofsystracestack.Value(); pattern != "" {
if match, _ := pathpkg.Match(pattern, path); match {
traceFile.Write(debug.Stack())
}
}
}
var (
doTrace bool
traceFile *os.File
traceMu sync.Mutex
gofsystrace = godebug.New("#gofsystrace")
gofsystracelog = godebug.New("#gofsystracelog")
gofsystracestack = godebug.New("#gofsystracestack")
)
func init() {
if gofsystrace.Value() != "1" {
return
}
doTrace = true
if f := gofsystracelog.Value(); f != "" {
// Note: No buffering on writes to this file, so no need to worry about closing it at exit.
var err error
traceFile, err = os.OpenFile(f, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Fatal(err)
}
} else {
traceFile = os.Stderr
}
}
// OverlayFile is the path to a text file in the OverlayJSON format.
// It is the value of the -overlay flag.
var OverlayFile string
// OverlayJSON is the format overlay files are expected to be in.
// The Replace map maps from overlaid paths to replacement paths:
// the Go command will forward all reads trying to open
// each overlaid path to its replacement path, or consider the overlaid
// path not to exist if the replacement path is empty.
type OverlayJSON struct {
Replace map[string]string
}
type node struct {
actualFilePath string // empty if a directory
children map[string]*node // path element → file or directory
}
func (n *node) isDir() bool {
return n.actualFilePath == "" && n.children != nil
}
func (n *node) isDeleted() bool {
return n.actualFilePath == "" && n.children == nil
}
// TODO(matloob): encapsulate these in an io/fs-like interface
var overlay map[string]*node // path -> file or directory node
var cwd string // copy of base.Cwd() to avoid dependency
// canonicalize a path for looking it up in the overlay.
// Important: filepath.Join(cwd, path) doesn't always produce
// the correct absolute path if path is relative, because on
// Windows producing the correct absolute path requires making
// a syscall. So this should only be used when looking up paths
// in the overlay, or canonicalizing the paths in the overlay.
func canonicalize(path string) string {
if path == "" {
return ""
}
if filepath.IsAbs(path) {
return filepath.Clean(path)
}
if v := filepath.VolumeName(cwd); v != "" && path[0] == filepath.Separator {
// On Windows filepath.Join(cwd, path) doesn't always work. In general
// filepath.Abs needs to make a syscall on Windows. Elsewhere in cmd/go
// use filepath.Join(cwd, path), but cmd/go specifically supports Windows
// paths that start with "\" which implies the path is relative to the
// volume of the working directory. See golang.org/issue/8130.
return filepath.Join(v, path)
}
// Make the path absolute.
return filepath.Join(cwd, path)
}
// Init initializes the overlay, if one is being used.
func Init(wd string) error {
if overlay != nil {
// already initialized
return nil
}
cwd = wd
if OverlayFile == "" {
return nil
}
Trace("ReadFile", OverlayFile)
b, err := os.ReadFile(OverlayFile)
if err != nil {
return fmt.Errorf("reading overlay file: %v", err)
}
var overlayJSON OverlayJSON
if err := json.Unmarshal(b, &overlayJSON); err != nil {
return fmt.Errorf("parsing overlay JSON: %v", err)
}
return initFromJSON(overlayJSON)
}
func initFromJSON(overlayJSON OverlayJSON) error {
// Canonicalize the paths in the overlay map.
// Use reverseCanonicalized to check for collisions:
// no two 'from' paths should canonicalize to the same path.
overlay = make(map[string]*node)
reverseCanonicalized := make(map[string]string) // inverse of canonicalize operation, to check for duplicates
// Build a table of file and directory nodes from the replacement map.
// Remove any potential non-determinism from iterating over map by sorting it.
replaceFrom := make([]string, 0, len(overlayJSON.Replace))
for k := range overlayJSON.Replace {
replaceFrom = append(replaceFrom, k)
}
sort.Strings(replaceFrom)
for _, from := range replaceFrom {
to := overlayJSON.Replace[from]
// Canonicalize paths and check for a collision.
if from == "" {
return fmt.Errorf("empty string key in overlay file Replace map")
}
cfrom := canonicalize(from)
if to != "" {
// Don't canonicalize "", meaning to delete a file, because then it will turn into ".".
to = canonicalize(to)
}
if otherFrom, seen := reverseCanonicalized[cfrom]; seen {
return fmt.Errorf(
"paths %q and %q both canonicalize to %q in overlay file Replace map", otherFrom, from, cfrom)
}
reverseCanonicalized[cfrom] = from
from = cfrom
// Create node for overlaid file.
dir, base := filepath.Dir(from), filepath.Base(from)
if n, ok := overlay[from]; ok {
// All 'from' paths in the overlay are file paths. Since the from paths
// are in a map, they are unique, so if the node already exists we added
// it below when we create parent directory nodes. That is, that
// both a file and a path to one of its parent directories exist as keys
// in the Replace map.
//
// This only applies if the overlay directory has any files or directories
// in it: placeholder directories that only contain deleted files don't
// count. They are safe to be overwritten with actual files.
for _, f := range n.children {
if !f.isDeleted() {
return fmt.Errorf("invalid overlay: path %v is used as both file and directory", from)
}
}
}
overlay[from] = &node{actualFilePath: to}
// Add parent directory nodes to overlay structure.
childNode := overlay[from]
for {
dirNode := overlay[dir]
if dirNode == nil || dirNode.isDeleted() {
dirNode = &node{children: make(map[string]*node)}
overlay[dir] = dirNode
}
if childNode.isDeleted() {
// Only create one parent for a deleted file:
// the directory only conditionally exists if
// there are any non-deleted children, so
// we don't create their parents.
if dirNode.isDir() {
dirNode.children[base] = childNode
}
break
}
if !dirNode.isDir() {
// This path already exists as a file, so it can't be a parent
// directory. See comment at error above.
return fmt.Errorf("invalid overlay: path %v is used as both file and directory", dir)
}
dirNode.children[base] = childNode
parent := filepath.Dir(dir)
if parent == dir {
break // reached the top; there is no parent
}
dir, base = parent, filepath.Base(dir)
childNode = dirNode
}
}
return nil
}
// IsDir returns true if path is a directory on disk or in the
// overlay.
func IsDir(path string) (bool, error) {
Trace("IsDir", path)
path = canonicalize(path)
if _, ok := parentIsOverlayFile(path); ok {
return false, nil
}
if n, ok := overlay[path]; ok {
return n.isDir(), nil
}
fi, err := os.Stat(path)
if err != nil {
return false, err
}
return fi.IsDir(), nil
}
// parentIsOverlayFile returns whether name or any of
// its parents are files in the overlay, and the first parent found,
// including name itself, that's a file in the overlay.
func parentIsOverlayFile(name string) (string, bool) {
if overlay != nil {
// Check if name can't possibly be a directory because
// it or one of its parents is overlaid with a file.
// TODO(matloob): Maybe save this to avoid doing it every time?
prefix := name
for {
node := overlay[prefix]
if node != nil && !node.isDir() {
return prefix, true
}
parent := filepath.Dir(prefix)
if parent == prefix {
break
}
prefix = parent
}
}
return "", false
}
// errNotDir is used to communicate from ReadDir to IsDirWithGoFiles
// that the argument is not a directory, so that IsDirWithGoFiles doesn't
// return an error.
var errNotDir = errors.New("not a directory")
func nonFileInOverlayError(overlayPath string) error {
return fmt.Errorf("replacement path %q is a directory, not a file", overlayPath)
}
// readDir reads a dir on disk, returning an error that is errNotDir if the dir is not a directory.
// Unfortunately, the error returned by os.ReadDir if dir is not a directory
// can vary depending on the OS (Linux, Mac, Windows return ENOTDIR; BSD returns EINVAL).
func readDir(dir string) ([]fs.FileInfo, error) {
entries, err := os.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return nil, err
}
if dirfi, staterr := os.Stat(dir); staterr == nil && !dirfi.IsDir() {
return nil, &fs.PathError{Op: "ReadDir", Path: dir, Err: errNotDir}
}
return nil, err
}
fis := make([]fs.FileInfo, 0, len(entries))
for _, entry := range entries {
info, err := entry.Info()
if err != nil {
continue
}
fis = append(fis, info)
}
return fis, nil
}
// ReadDir provides a slice of fs.FileInfo entries corresponding
// to the overlaid files in the directory.
func ReadDir(dir string) ([]fs.FileInfo, error) {
Trace("ReadDir", dir)
dir = canonicalize(dir)
if _, ok := parentIsOverlayFile(dir); ok {
return nil, &fs.PathError{Op: "ReadDir", Path: dir, Err: errNotDir}
}
dirNode := overlay[dir]
if dirNode == nil {
return readDir(dir)
}
if dirNode.isDeleted() {
return nil, &fs.PathError{Op: "ReadDir", Path: dir, Err: fs.ErrNotExist}
}
diskfis, err := readDir(dir)
if err != nil && !os.IsNotExist(err) && !errors.Is(err, errNotDir) {
return nil, err
}
// Stat files in overlay to make composite list of fileinfos
files := make(map[string]fs.FileInfo)
for _, f := range diskfis {
files[f.Name()] = f
}
for name, to := range dirNode.children {
switch {
case to.isDir():
files[name] = fakeDir(name)
case to.isDeleted():
delete(files, name)
default:
// To keep the data model simple, if the overlay contains a symlink we
// always stat through it (using Stat, not Lstat). That way we don't need
// to worry about the interaction between Lstat and directories: if a
// symlink in the overlay points to a directory, we reject it like an
// ordinary directory.
fi, err := os.Stat(to.actualFilePath)
if err != nil {
files[name] = missingFile(name)
continue
} else if fi.IsDir() {
return nil, &fs.PathError{Op: "Stat", Path: filepath.Join(dir, name), Err: nonFileInOverlayError(to.actualFilePath)}
}
// Add a fileinfo for the overlaid file, so that it has
// the original file's name, but the overlaid file's metadata.
files[name] = fakeFile{name, fi}
}
}
sortedFiles := diskfis[:0]
for _, f := range files {
sortedFiles = append(sortedFiles, f)
}
sort.Slice(sortedFiles, func(i, j int) bool { return sortedFiles[i].Name() < sortedFiles[j].Name() })
return sortedFiles, nil
}
// OverlayPath returns the path to the overlaid contents of the
// file, the empty string if the overlay deletes the file, or path
// itself if the file is not in the overlay, the file is a directory
// in the overlay, or there is no overlay.
// It returns true if the path is overlaid with a regular file
// or deleted, and false otherwise.
func OverlayPath(path string) (string, bool) {
if p, ok := overlay[canonicalize(path)]; ok && !p.isDir() {
return p.actualFilePath, ok
}
return path, false
}
// Open opens the file at or overlaid on the given path.
func Open(path string) (*os.File, error) {
Trace("Open", path)
return openFile(path, os.O_RDONLY, 0)
}
func openFile(path string, flag int, perm os.FileMode) (*os.File, error) {
cpath := canonicalize(path)
if node, ok := overlay[cpath]; ok {
// Opening a file in the overlay.
if node.isDir() {
return nil, &fs.PathError{Op: "OpenFile", Path: path, Err: errors.New("fsys.OpenFile doesn't support opening directories yet")}
}
// We can't open overlaid paths for write.
if perm != os.FileMode(os.O_RDONLY) {
return nil, &fs.PathError{Op: "OpenFile", Path: path, Err: errors.New("overlaid files can't be opened for write")}
}
return os.OpenFile(node.actualFilePath, flag, perm)
}
if parent, ok := parentIsOverlayFile(filepath.Dir(cpath)); ok {
// The file is deleted explicitly in the Replace map,
// or implicitly because one of its parent directories was
// replaced by a file.
return nil, &fs.PathError{
Op: "Open",
Path: path,
Err: fmt.Errorf("file %s does not exist: parent directory %s is replaced by a file in overlay", path, parent),
}
}
return os.OpenFile(cpath, flag, perm)
}
// ReadFile reads the file at or overlaid on the given path.
func ReadFile(path string) ([]byte, error) {
f, err := Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return io.ReadAll(f)
}
// IsDirWithGoFiles reports whether dir is a directory containing Go files
// either on disk or in the overlay.
func IsDirWithGoFiles(dir string) (bool, error) {
Trace("IsDirWithGoFiles", dir)
fis, err := ReadDir(dir)
if os.IsNotExist(err) || errors.Is(err, errNotDir) {
return false, nil
}
if err != nil {
return false, err
}
var firstErr error
for _, fi := range fis {
if fi.IsDir() {
continue
}
// TODO(matloob): this enforces that the "from" in the map
// has a .go suffix, but the actual destination file
// doesn't need to have a .go suffix. Is this okay with the
// compiler?
if !strings.HasSuffix(fi.Name(), ".go") {
continue
}
if fi.Mode().IsRegular() {
return true, nil
}
// fi is the result of an Lstat, so it doesn't follow symlinks.
// But it's okay if the file is a symlink pointing to a regular
// file, so use os.Stat to follow symlinks and check that.
actualFilePath, _ := OverlayPath(filepath.Join(dir, fi.Name()))
fi, err := os.Stat(actualFilePath)
if err == nil && fi.Mode().IsRegular() {
return true, nil
}
if err != nil && firstErr == nil {
firstErr = err
}
}
// No go files found in directory.
return false, firstErr
}
// walk recursively descends path, calling walkFn. Copied, with some
// modifications from path/filepath.walk.
func walk(path string, info fs.FileInfo, walkFn filepath.WalkFunc) error {
if err := walkFn(path, info, nil); err != nil || !info.IsDir() {
return err
}
fis, err := ReadDir(path)
if err != nil {
return walkFn(path, info, err)
}
for _, fi := range fis {
filename := filepath.Join(path, fi.Name())
if err := walk(filename, fi, walkFn); err != nil {
if !fi.IsDir() || err != filepath.SkipDir {
return err
}
}
}
return nil
}
// Walk walks the file tree rooted at root, calling walkFn for each file or
// directory in the tree, including root.
func Walk(root string, walkFn filepath.WalkFunc) error {
Trace("Walk", root)
info, err := Lstat(root)
if err != nil {
err = walkFn(root, nil, err)
} else {
err = walk(root, info, walkFn)
}
if err == filepath.SkipDir {
return nil
}
return err
}
// Lstat implements a version of os.Lstat that operates on the overlay filesystem.
func Lstat(path string) (fs.FileInfo, error) {
Trace("Lstat", path)
return overlayStat(path, os.Lstat, "lstat")
}
// Stat implements a version of os.Stat that operates on the overlay filesystem.
func Stat(path string) (fs.FileInfo, error) {
Trace("Stat", path)
return overlayStat(path, os.Stat, "stat")
}
// overlayStat implements lstat or Stat (depending on whether os.Lstat or os.Stat is passed in).
func overlayStat(path string, osStat func(string) (fs.FileInfo, error), opName string) (fs.FileInfo, error) {
cpath := canonicalize(path)
if _, ok := parentIsOverlayFile(filepath.Dir(cpath)); ok {
return nil, &fs.PathError{Op: opName, Path: cpath, Err: fs.ErrNotExist}
}
node, ok := overlay[cpath]
if !ok {
// The file or directory is not overlaid.
return osStat(path)
}
switch {
case node.isDeleted():
return nil, &fs.PathError{Op: opName, Path: cpath, Err: fs.ErrNotExist}
case node.isDir():
return fakeDir(filepath.Base(path)), nil
default:
// To keep the data model simple, if the overlay contains a symlink we
// always stat through it (using Stat, not Lstat). That way we don't need to
// worry about the interaction between Lstat and directories: if a symlink
// in the overlay points to a directory, we reject it like an ordinary
// directory.
fi, err := os.Stat(node.actualFilePath)
if err != nil {
return nil, err
}
if fi.IsDir() {
return nil, &fs.PathError{Op: opName, Path: cpath, Err: nonFileInOverlayError(node.actualFilePath)}
}
return fakeFile{name: filepath.Base(path), real: fi}, nil
}
}
// fakeFile provides an fs.FileInfo implementation for an overlaid file,
// so that the file has the name of the overlaid file, but takes all
// other characteristics of the replacement file.
type fakeFile struct {
name string
real fs.FileInfo
}
func (f fakeFile) Name() string { return f.name }
func (f fakeFile) Size() int64 { return f.real.Size() }
func (f fakeFile) Mode() fs.FileMode { return f.real.Mode() }
func (f fakeFile) ModTime() time.Time { return f.real.ModTime() }
func (f fakeFile) IsDir() bool { return f.real.IsDir() }
func (f fakeFile) Sys() any { return f.real.Sys() }
func (f fakeFile) String() string {
return fs.FormatFileInfo(f)
}
// missingFile provides an fs.FileInfo for an overlaid file where the
// destination file in the overlay doesn't exist. It returns zero values
// for the fileInfo methods other than Name, set to the file's name, and Mode
// set to ModeIrregular.
type missingFile string
func (f missingFile) Name() string { return string(f) }
func (f missingFile) Size() int64 { return 0 }
func (f missingFile) Mode() fs.FileMode { return fs.ModeIrregular }
func (f missingFile) ModTime() time.Time { return time.Unix(0, 0) }
func (f missingFile) IsDir() bool { return false }
func (f missingFile) Sys() any { return nil }
func (f missingFile) String() string {
return fs.FormatFileInfo(f)
}
// fakeDir provides an fs.FileInfo implementation for directories that are
// implicitly created by overlaid files. Each directory in the
// path of an overlaid file is considered to exist in the overlay filesystem.
type fakeDir string
func (f fakeDir) Name() string { return string(f) }
func (f fakeDir) Size() int64 { return 0 }
func (f fakeDir) Mode() fs.FileMode { return fs.ModeDir | 0500 }
func (f fakeDir) ModTime() time.Time { return time.Unix(0, 0) }
func (f fakeDir) IsDir() bool { return true }
func (f fakeDir) Sys() any { return nil }
func (f fakeDir) String() string {
return fs.FormatFileInfo(f)
}
// Glob is like filepath.Glob but uses the overlay file system.
func Glob(pattern string) (matches []string, err error) {
Trace("Glob", pattern)
// Check pattern is well-formed.
if _, err := filepath.Match(pattern, ""); err != nil {
return nil, err
}
if !hasMeta(pattern) {
if _, err = Lstat(pattern); err != nil {
return nil, nil
}
return []string{pattern}, nil
}
dir, file := filepath.Split(pattern)
volumeLen := 0
if runtime.GOOS == "windows" {
volumeLen, dir = cleanGlobPathWindows(dir)
} else {
dir = cleanGlobPath(dir)
}
if !hasMeta(dir[volumeLen:]) {
return glob(dir, file, nil)
}
// Prevent infinite recursion. See issue 15879.
if dir == pattern {
return nil, filepath.ErrBadPattern
}
var m []string
m, err = Glob(dir)
if err != nil {
return
}
for _, d := range m {
matches, err = glob(d, file, matches)
if err != nil {
return
}
}
return
}
// cleanGlobPath prepares path for glob matching.
func cleanGlobPath(path string) string {
switch path {
case "":
return "."
case string(filepath.Separator):
// do nothing to the path
return path
default:
return path[0 : len(path)-1] // chop off trailing separator
}
}
func volumeNameLen(path string) int {
isSlash := func(c uint8) bool {
return c == '\\' || c == '/'
}
if len(path) < 2 {
return 0
}
// with drive letter
c := path[0]
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
return 2
}
// is it UNC? https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
!isSlash(path[2]) && path[2] != '.' {
// first, leading `\\` and next shouldn't be `\`. its server name.
for n := 3; n < l-1; n++ {
// second, next '\' shouldn't be repeated.
if isSlash(path[n]) {
n++
// third, following something characters. its share name.
if !isSlash(path[n]) {
if path[n] == '.' {
break
}
for ; n < l; n++ {
if isSlash(path[n]) {
break
}
}
return n
}
break
}
}
}
return 0
}
// cleanGlobPathWindows is windows version of cleanGlobPath.
func cleanGlobPathWindows(path string) (prefixLen int, cleaned string) {
vollen := volumeNameLen(path)
switch {
case path == "":
return 0, "."
case vollen+1 == len(path) && os.IsPathSeparator(path[len(path)-1]): // /, \, C:\ and C:/
// do nothing to the path
return vollen + 1, path
case vollen == len(path) && len(path) == 2: // C:
return vollen, path + "." // convert C: into C:.
default:
if vollen >= len(path) {
vollen = len(path) - 1
}
return vollen, path[0 : len(path)-1] // chop off trailing separator
}
}
// glob searches for files matching pattern in the directory dir
// and appends them to matches. If the directory cannot be
// opened, it returns the existing matches. New matches are
// added in lexicographical order.
func glob(dir, pattern string, matches []string) (m []string, e error) {
m = matches
fi, err := Stat(dir)
if err != nil {
return // ignore I/O error
}
if !fi.IsDir() {
return // ignore I/O error
}
list, err := ReadDir(dir)
if err != nil {
return // ignore I/O error
}
var names []string
for _, info := range list {
names = append(names, info.Name())
}
sort.Strings(names)
for _, n := range names {
matched, err := filepath.Match(pattern, n)
if err != nil {
return m, err
}
if matched {
m = append(m, filepath.Join(dir, n))
}
}
return
}
// hasMeta reports whether path contains any of the magic characters
// recognized by filepath.Match.
func hasMeta(path string) bool {
magicChars := `*?[`
if runtime.GOOS != "windows" {
magicChars = `*?[\`
}
return strings.ContainsAny(path, magicChars)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,510 @@
// Copyright 2011 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 generate implements the “go generate” command.
package generate
import (
"bufio"
"bytes"
"context"
"fmt"
"go/parser"
"go/token"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/load"
"cmd/go/internal/modload"
"cmd/go/internal/str"
"cmd/go/internal/work"
)
var CmdGenerate = &base.Command{
Run: runGenerate,
UsageLine: "go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages]",
Short: "generate Go files by processing source",
Long: `
Generate runs commands described by directives within existing
files. Those commands can run any process but the intent is to
create or update Go source files.
Go generate is never run automatically by go build, go test,
and so on. It must be run explicitly.
Go generate scans the file for directives, which are lines of
the form,
//go:generate command argument...
(note: no leading spaces and no space in "//go") where command
is the generator to be run, corresponding to an executable file
that can be run locally. It must either be in the shell path
(gofmt), a fully qualified path (/usr/you/bin/mytool), or a
command alias, described below.
Note that go generate does not parse the file, so lines that look
like directives in comments or multiline strings will be treated
as directives.
The arguments to the directive are space-separated tokens or
double-quoted strings passed to the generator as individual
arguments when it is run.
Quoted strings use Go syntax and are evaluated before execution; a
quoted string appears as a single argument to the generator.
To convey to humans and machine tools that code is generated,
generated source should have a line that matches the following
regular expression (in Go syntax):
^// Code generated .* DO NOT EDIT\.$
This line must appear before the first non-comment, non-blank
text in the file.
Go generate sets several variables when it runs the generator:
$GOARCH
The execution architecture (arm, amd64, etc.)
$GOOS
The execution operating system (linux, windows, etc.)
$GOFILE
The base name of the file.
$GOLINE
The line number of the directive in the source file.
$GOPACKAGE
The name of the package of the file containing the directive.
$GOROOT
The GOROOT directory for the 'go' command that invoked the
generator, containing the Go toolchain and standard library.
$DOLLAR
A dollar sign.
$PATH
The $PATH of the parent process, with $GOROOT/bin
placed at the beginning. This causes generators
that execute 'go' commands to use the same 'go'
as the parent 'go generate' command.
Other than variable substitution and quoted-string evaluation, no
special processing such as "globbing" is performed on the command
line.
As a last step before running the command, any invocations of any
environment variables with alphanumeric names, such as $GOFILE or
$HOME, are expanded throughout the command line. The syntax for
variable expansion is $NAME on all operating systems. Due to the
order of evaluation, variables are expanded even inside quoted
strings. If the variable NAME is not set, $NAME expands to the
empty string.
A directive of the form,
//go:generate -command xxx args...
specifies, for the remainder of this source file only, that the
string xxx represents the command identified by the arguments. This
can be used to create aliases or to handle multiword generators.
For example,
//go:generate -command foo go tool foo
specifies that the command "foo" represents the generator
"go tool foo".
Generate processes packages in the order given on the command line,
one at a time. If the command line lists .go files from a single directory,
they are treated as a single package. Within a package, generate processes the
source files in a package in file name order, one at a time. Within
a source file, generate runs generators in the order they appear
in the file, one at a time. The go generate tool also sets the build
tag "generate" so that files may be examined by go generate but ignored
during build.
For packages with invalid code, generate processes only source files with a
valid package clause.
If any generator returns an error exit status, "go generate" skips
all further processing for that package.
The generator is run in the package's source directory.
Go generate accepts two specific flags:
-run=""
if non-empty, specifies a regular expression to select
directives whose full original source text (excluding
any trailing spaces and final newline) matches the
expression.
-skip=""
if non-empty, specifies a regular expression to suppress
directives whose full original source text (excluding
any trailing spaces and final newline) matches the
expression. If a directive matches both the -run and
the -skip arguments, it is skipped.
It also accepts the standard build flags including -v, -n, and -x.
The -v flag prints the names of packages and files as they are
processed.
The -n flag prints commands that would be executed.
The -x flag prints commands as they are executed.
For more about build flags, see 'go help build'.
For more about specifying packages, see 'go help packages'.
`,
}
var (
generateRunFlag string // generate -run flag
generateRunRE *regexp.Regexp // compiled expression for -run
generateSkipFlag string // generate -skip flag
generateSkipRE *regexp.Regexp // compiled expression for -skip
)
func init() {
work.AddBuildFlags(CmdGenerate, work.DefaultBuildFlags)
CmdGenerate.Flag.StringVar(&generateRunFlag, "run", "", "")
CmdGenerate.Flag.StringVar(&generateSkipFlag, "skip", "", "")
}
func runGenerate(ctx context.Context, cmd *base.Command, args []string) {
modload.InitWorkfile()
if generateRunFlag != "" {
var err error
generateRunRE, err = regexp.Compile(generateRunFlag)
if err != nil {
log.Fatalf("generate: %s", err)
}
}
if generateSkipFlag != "" {
var err error
generateSkipRE, err = regexp.Compile(generateSkipFlag)
if err != nil {
log.Fatalf("generate: %s", err)
}
}
cfg.BuildContext.BuildTags = append(cfg.BuildContext.BuildTags, "generate")
// Even if the arguments are .go files, this loop suffices.
printed := false
pkgOpts := load.PackageOpts{IgnoreImports: true}
for _, pkg := range load.PackagesAndErrors(ctx, pkgOpts, args) {
if modload.Enabled() && pkg.Module != nil && !pkg.Module.Main {
if !printed {
fmt.Fprintf(os.Stderr, "go: not generating in packages in dependency modules\n")
printed = true
}
continue
}
if pkg.Error != nil && len(pkg.InternalAllGoFiles()) == 0 {
// A directory only contains a Go package if it has at least
// one .go source file, so the fact that there are no files
// implies that the package couldn't be found.
base.Errorf("%v", pkg.Error)
}
for _, file := range pkg.InternalGoFiles() {
if !generate(file) {
break
}
}
for _, file := range pkg.InternalXGoFiles() {
if !generate(file) {
break
}
}
}
base.ExitIfErrors()
}
// generate runs the generation directives for a single file.
func generate(absFile string) bool {
src, err := os.ReadFile(absFile)
if err != nil {
log.Fatalf("generate: %s", err)
}
// Parse package clause
filePkg, err := parser.ParseFile(token.NewFileSet(), "", src, parser.PackageClauseOnly)
if err != nil {
// Invalid package clause - ignore file.
return true
}
g := &Generator{
r: bytes.NewReader(src),
path: absFile,
pkg: filePkg.Name.String(),
commands: make(map[string][]string),
}
return g.run()
}
// A Generator represents the state of a single Go source file
// being scanned for generator commands.
type Generator struct {
r io.Reader
path string // full rooted path name.
dir string // full rooted directory of file.
file string // base name of file.
pkg string
commands map[string][]string
lineNum int // current line number.
env []string
}
// run runs the generators in the current file.
func (g *Generator) run() (ok bool) {
// Processing below here calls g.errorf on failure, which does panic(stop).
// If we encounter an error, we abort the package.
defer func() {
e := recover()
if e != nil {
ok = false
if e != stop {
panic(e)
}
base.SetExitStatus(1)
}
}()
g.dir, g.file = filepath.Split(g.path)
g.dir = filepath.Clean(g.dir) // No final separator please.
if cfg.BuildV {
fmt.Fprintf(os.Stderr, "%s\n", base.ShortPath(g.path))
}
// Scan for lines that start "//go:generate".
// Can't use bufio.Scanner because it can't handle long lines,
// which are likely to appear when using generate.
input := bufio.NewReader(g.r)
var err error
// One line per loop.
for {
g.lineNum++ // 1-indexed.
var buf []byte
buf, err = input.ReadSlice('\n')
if err == bufio.ErrBufferFull {
// Line too long - consume and ignore.
if isGoGenerate(buf) {
g.errorf("directive too long")
}
for err == bufio.ErrBufferFull {
_, err = input.ReadSlice('\n')
}
if err != nil {
break
}
continue
}
if err != nil {
// Check for marker at EOF without final \n.
if err == io.EOF && isGoGenerate(buf) {
err = io.ErrUnexpectedEOF
}
break
}
if !isGoGenerate(buf) {
continue
}
if generateRunFlag != "" && !generateRunRE.Match(bytes.TrimSpace(buf)) {
continue
}
if generateSkipFlag != "" && generateSkipRE.Match(bytes.TrimSpace(buf)) {
continue
}
g.setEnv()
words := g.split(string(buf))
if len(words) == 0 {
g.errorf("no arguments to directive")
}
if words[0] == "-command" {
g.setShorthand(words)
continue
}
// Run the command line.
if cfg.BuildN || cfg.BuildX {
fmt.Fprintf(os.Stderr, "%s\n", strings.Join(words, " "))
}
if cfg.BuildN {
continue
}
g.exec(words)
}
if err != nil && err != io.EOF {
g.errorf("error reading %s: %s", base.ShortPath(g.path), err)
}
return true
}
func isGoGenerate(buf []byte) bool {
return bytes.HasPrefix(buf, []byte("//go:generate ")) || bytes.HasPrefix(buf, []byte("//go:generate\t"))
}
// setEnv sets the extra environment variables used when executing a
// single go:generate command.
func (g *Generator) setEnv() {
env := []string{
"GOROOT=" + cfg.GOROOT,
"GOARCH=" + cfg.BuildContext.GOARCH,
"GOOS=" + cfg.BuildContext.GOOS,
"GOFILE=" + g.file,
"GOLINE=" + strconv.Itoa(g.lineNum),
"GOPACKAGE=" + g.pkg,
"DOLLAR=" + "$",
}
env = base.AppendPATH(env)
env = base.AppendPWD(env, g.dir)
g.env = env
}
// split breaks the line into words, evaluating quoted
// strings and evaluating environment variables.
// The initial //go:generate element is present in line.
func (g *Generator) split(line string) []string {
// Parse line, obeying quoted strings.
var words []string
line = line[len("//go:generate ") : len(line)-1] // Drop preamble and final newline.
// There may still be a carriage return.
if len(line) > 0 && line[len(line)-1] == '\r' {
line = line[:len(line)-1]
}
// One (possibly quoted) word per iteration.
Words:
for {
line = strings.TrimLeft(line, " \t")
if len(line) == 0 {
break
}
if line[0] == '"' {
for i := 1; i < len(line); i++ {
c := line[i] // Only looking for ASCII so this is OK.
switch c {
case '\\':
if i+1 == len(line) {
g.errorf("bad backslash")
}
i++ // Absorb next byte (If it's a multibyte we'll get an error in Unquote).
case '"':
word, err := strconv.Unquote(line[0 : i+1])
if err != nil {
g.errorf("bad quoted string")
}
words = append(words, word)
line = line[i+1:]
// Check the next character is space or end of line.
if len(line) > 0 && line[0] != ' ' && line[0] != '\t' {
g.errorf("expect space after quoted argument")
}
continue Words
}
}
g.errorf("mismatched quoted string")
}
i := strings.IndexAny(line, " \t")
if i < 0 {
i = len(line)
}
words = append(words, line[0:i])
line = line[i:]
}
// Substitute command if required.
if len(words) > 0 && g.commands[words[0]] != nil {
// Replace 0th word by command substitution.
//
// Force a copy of the command definition to
// ensure words doesn't end up as a reference
// to the g.commands content.
tmpCmdWords := append([]string(nil), (g.commands[words[0]])...)
words = append(tmpCmdWords, words[1:]...)
}
// Substitute environment variables.
for i, word := range words {
words[i] = os.Expand(word, g.expandVar)
}
return words
}
var stop = fmt.Errorf("error in generation")
// errorf logs an error message prefixed with the file and line number.
// It then exits the program (with exit status 1) because generation stops
// at the first error.
func (g *Generator) errorf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "%s:%d: %s\n", base.ShortPath(g.path), g.lineNum,
fmt.Sprintf(format, args...))
panic(stop)
}
// expandVar expands the $XXX invocation in word. It is called
// by os.Expand.
func (g *Generator) expandVar(word string) string {
w := word + "="
for _, e := range g.env {
if strings.HasPrefix(e, w) {
return e[len(w):]
}
}
return os.Getenv(word)
}
// setShorthand installs a new shorthand as defined by a -command directive.
func (g *Generator) setShorthand(words []string) {
// Create command shorthand.
if len(words) == 1 {
g.errorf("no command specified for -command")
}
command := words[1]
if g.commands[command] != nil {
g.errorf("command %q multiply defined", command)
}
g.commands[command] = slices.Clip(words[2:])
}
// exec runs the command specified by the argument. The first word is
// the command name itself.
func (g *Generator) exec(words []string) {
path := words[0]
if path != "" && !strings.Contains(path, string(os.PathSeparator)) {
// If a generator says '//go:generate go run <blah>' it almost certainly
// intends to use the same 'go' as 'go generate' itself.
// Prefer to resolve the binary from GOROOT/bin, and for consistency
// prefer to resolve any other commands there too.
gorootBinPath, err := cfg.LookPath(filepath.Join(cfg.GOROOTbin, path))
if err == nil {
path = gorootBinPath
}
}
cmd := exec.Command(path, words[1:]...)
cmd.Args[0] = words[0] // Overwrite with the original in case it was rewritten above.
// Standard in and out of generator should be the usual.
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Run the command in the package directory.
cmd.Dir = g.dir
cmd.Env = str.StringList(cfg.OrigEnv, g.env)
err := cmd.Run()
if err != nil {
g.errorf("running %q: %s", words[0], err)
}
}

View File

@@ -0,0 +1,259 @@
// Copyright 2011 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 generate
import (
"internal/testenv"
"os"
"path/filepath"
"reflect"
"runtime"
"testing"
)
type splitTest struct {
in string
out []string
}
// Same as above, except including source line number to set
type splitTestWithLine struct {
in string
out []string
lineNumber int
}
const anyLineNo = 0
var splitTests = []splitTest{
{"", nil},
{"x", []string{"x"}},
{" a b\tc ", []string{"a", "b", "c"}},
{` " a " `, []string{" a "}},
{"$GOARCH", []string{runtime.GOARCH}},
{"$GOOS", []string{runtime.GOOS}},
{"$GOFILE", []string{"proc.go"}},
{"$GOPACKAGE", []string{"sys"}},
{"a $XXNOTDEFINEDXX b", []string{"a", "", "b"}},
{"/$XXNOTDEFINED/", []string{"//"}},
{"/$DOLLAR/", []string{"/$/"}},
{"yacc -o $GOARCH/yacc_$GOFILE", []string{"go", "tool", "yacc", "-o", runtime.GOARCH + "/yacc_proc.go"}},
}
func TestGenerateCommandParse(t *testing.T) {
dir := filepath.Join(testenv.GOROOT(t), "src", "sys")
g := &Generator{
r: nil, // Unused here.
path: filepath.Join(dir, "proc.go"),
dir: dir,
file: "proc.go",
pkg: "sys",
commands: make(map[string][]string),
}
g.setEnv()
g.setShorthand([]string{"-command", "yacc", "go", "tool", "yacc"})
for _, test := range splitTests {
// First with newlines.
got := g.split("//go:generate " + test.in + "\n")
if !reflect.DeepEqual(got, test.out) {
t.Errorf("split(%q): got %q expected %q", test.in, got, test.out)
}
// Then with CRLFs, thank you Windows.
got = g.split("//go:generate " + test.in + "\r\n")
if !reflect.DeepEqual(got, test.out) {
t.Errorf("split(%q): got %q expected %q", test.in, got, test.out)
}
}
}
// These environment variables will be undefined before the splitTestWithLine tests
var undefEnvList = []string{
"_XYZZY_",
}
// These environment variables will be defined before the splitTestWithLine tests
var defEnvMap = map[string]string{
"_PLUGH_": "SomeVal",
"_X": "Y",
}
// TestGenerateCommandShortHand - similar to TestGenerateCommandParse,
// except:
// 1. if the result starts with -command, record that shorthand
// before moving on to the next test.
// 2. If a source line number is specified, set that in the parser
// before executing the test. i.e., execute the split as if it
// processing that source line.
func TestGenerateCommandShorthand(t *testing.T) {
dir := filepath.Join(testenv.GOROOT(t), "src", "sys")
g := &Generator{
r: nil, // Unused here.
path: filepath.Join(dir, "proc.go"),
dir: dir,
file: "proc.go",
pkg: "sys",
commands: make(map[string][]string),
}
var inLine string
var expected, got []string
g.setEnv()
// Set up the system environment variables
for i := range undefEnvList {
os.Unsetenv(undefEnvList[i])
}
for k := range defEnvMap {
os.Setenv(k, defEnvMap[k])
}
// simple command from environment variable
inLine = "//go:generate -command CMD0 \"ab${_X}cd\""
expected = []string{"-command", "CMD0", "abYcd"}
got = g.split(inLine + "\n")
if !reflect.DeepEqual(got, expected) {
t.Errorf("split(%q): got %q expected %q", inLine, got, expected)
}
// try again, with an extra level of indirection (should leave variable in command)
inLine = "//go:generate -command CMD0 \"ab${DOLLAR}{_X}cd\""
expected = []string{"-command", "CMD0", "ab${_X}cd"}
got = g.split(inLine + "\n")
if !reflect.DeepEqual(got, expected) {
t.Errorf("split(%q): got %q expected %q", inLine, got, expected)
}
// Now the interesting part, record that output as a command
g.setShorthand(got)
// see that the command still substitutes correctly from env. variable
inLine = "//go:generate CMD0"
expected = []string{"abYcd"}
got = g.split(inLine + "\n")
if !reflect.DeepEqual(got, expected) {
t.Errorf("split(%q): got %q expected %q", inLine, got, expected)
}
// Now change the value of $X and see if the recorded definition is
// still intact (vs. having the $_X already substituted out)
os.Setenv("_X", "Z")
inLine = "//go:generate CMD0"
expected = []string{"abZcd"}
got = g.split(inLine + "\n")
if !reflect.DeepEqual(got, expected) {
t.Errorf("split(%q): got %q expected %q", inLine, got, expected)
}
// What if the variable is now undefined? Should be empty substitution.
os.Unsetenv("_X")
inLine = "//go:generate CMD0"
expected = []string{"abcd"}
got = g.split(inLine + "\n")
if !reflect.DeepEqual(got, expected) {
t.Errorf("split(%q): got %q expected %q", inLine, got, expected)
}
// Try another undefined variable as an extra check
os.Unsetenv("_Z")
inLine = "//go:generate -command CMD1 \"ab${_Z}cd\""
expected = []string{"-command", "CMD1", "abcd"}
got = g.split(inLine + "\n")
if !reflect.DeepEqual(got, expected) {
t.Errorf("split(%q): got %q expected %q", inLine, got, expected)
}
g.setShorthand(got)
inLine = "//go:generate CMD1"
expected = []string{"abcd"}
got = g.split(inLine + "\n")
if !reflect.DeepEqual(got, expected) {
t.Errorf("split(%q): got %q expected %q", inLine, got, expected)
}
const val = "someNewValue"
os.Setenv("_Z", val)
// try again with the properly-escaped variable.
inLine = "//go:generate -command CMD2 \"ab${DOLLAR}{_Z}cd\""
expected = []string{"-command", "CMD2", "ab${_Z}cd"}
got = g.split(inLine + "\n")
if !reflect.DeepEqual(got, expected) {
t.Errorf("split(%q): got %q expected %q", inLine, got, expected)
}
g.setShorthand(got)
inLine = "//go:generate CMD2"
expected = []string{"ab" + val + "cd"}
got = g.split(inLine + "\n")
if !reflect.DeepEqual(got, expected) {
t.Errorf("split(%q): got %q expected %q", inLine, got, expected)
}
}
// Command-related tests for TestGenerateCommandShortHand2
// -- Note line numbers included to check substitutions from "build-in" variable - $GOLINE
var splitTestsLines = []splitTestWithLine{
{"-command TEST1 $GOLINE", []string{"-command", "TEST1", "22"}, 22},
{"-command TEST2 ${DOLLAR}GOLINE", []string{"-command", "TEST2", "$GOLINE"}, 26},
{"TEST1", []string{"22"}, 33},
{"TEST2", []string{"66"}, 66},
{"TEST1 ''", []string{"22", "''"}, 99},
{"TEST2 ''", []string{"44", "''"}, 44},
}
// TestGenerateCommandShortHand - similar to TestGenerateCommandParse,
// except:
// 1. if the result starts with -command, record that shorthand
// before moving on to the next test.
// 2. If a source line number is specified, set that in the parser
// before executing the test. i.e., execute the split as if it
// processing that source line.
func TestGenerateCommandShortHand2(t *testing.T) {
dir := filepath.Join(testenv.GOROOT(t), "src", "sys")
g := &Generator{
r: nil, // Unused here.
path: filepath.Join(dir, "proc.go"),
dir: dir,
file: "proc.go",
pkg: "sys",
commands: make(map[string][]string),
}
g.setEnv()
for _, test := range splitTestsLines {
// if the test specified a line number, reflect that
if test.lineNumber != anyLineNo {
g.lineNum = test.lineNumber
g.setEnv()
}
// First with newlines.
got := g.split("//go:generate " + test.in + "\n")
if !reflect.DeepEqual(got, test.out) {
t.Errorf("split(%q): got %q expected %q", test.in, got, test.out)
}
// Then with CRLFs, thank you Windows.
got = g.split("//go:generate " + test.in + "\r\n")
if !reflect.DeepEqual(got, test.out) {
t.Errorf("split(%q): got %q expected %q", test.in, got, test.out)
}
if got[0] == "-command" { // record commands
g.setShorthand(got)
}
}
}

View File

@@ -0,0 +1,43 @@
// Copyright 2023 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 gover
import (
"bytes"
"strings"
)
var nl = []byte("\n")
// GoModLookup takes go.mod or go.work content,
// finds the first line in the file starting with the given key,
// and returns the value associated with that key.
//
// Lookup should only be used with non-factored verbs
// such as "go" and "toolchain", usually to find versions
// or version-like strings.
func GoModLookup(gomod []byte, key string) string {
for len(gomod) > 0 {
var line []byte
line, gomod, _ = bytes.Cut(gomod, nl)
line = bytes.TrimSpace(line)
if v, ok := parseKey(line, key); ok {
return v
}
}
return ""
}
func parseKey(line []byte, key string) (string, bool) {
if !strings.HasPrefix(string(line), key) {
return "", false
}
s := strings.TrimPrefix(string(line), key)
if len(s) == 0 || (s[0] != ' ' && s[0] != '\t') {
return "", false
}
s, _, _ = strings.Cut(s, "//") // strip comments
return strings.TrimSpace(s), true
}

View File

@@ -0,0 +1,75 @@
// Copyright 2023 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 gover implements support for Go toolchain versions like 1.21.0 and 1.21rc1.
// (For historical reasons, Go does not use semver for its toolchains.)
// This package provides the same basic analysis that golang.org/x/mod/semver does for semver.
// It also provides some helpers for extracting versions from go.mod files
// and for dealing with module.Versions that may use Go versions or semver
// depending on the module path.
package gover
import (
"internal/gover"
)
// Compare returns -1, 0, or +1 depending on whether
// x < y, x == y, or x > y, interpreted as toolchain versions.
// The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21".
// Malformed versions compare less than well-formed versions and equal to each other.
// The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0".
func Compare(x, y string) int {
return gover.Compare(x, y)
}
// Max returns the maximum of x and y interpreted as toolchain versions,
// compared using Compare.
// If x and y compare equal, Max returns x.
func Max(x, y string) string {
return gover.Max(x, y)
}
// IsLang reports whether v denotes the overall Go language version
// and not a specific release. Starting with the Go 1.21 release, "1.x" denotes
// the overall language version; the first release is "1.x.0".
// The distinction is important because the relative ordering is
//
// 1.21 < 1.21rc1 < 1.21.0
//
// meaning that Go 1.21rc1 and Go 1.21.0 will both handle go.mod files that
// say "go 1.21", but Go 1.21rc1 will not handle files that say "go 1.21.0".
func IsLang(x string) bool {
return gover.IsLang(x)
}
// Lang returns the Go language version. For example, Lang("1.2.3") == "1.2".
func Lang(x string) string {
return gover.Lang(x)
}
// IsPrerelease reports whether v denotes a Go prerelease version.
func IsPrerelease(x string) bool {
return gover.Parse(x).Kind != ""
}
// Prev returns the Go major release immediately preceding v,
// or v itself if v is the first Go major release (1.0) or not a supported
// Go version.
//
// Examples:
//
// Prev("1.2") = "1.1"
// Prev("1.3rc4") = "1.2"
func Prev(x string) string {
v := gover.Parse(x)
if gover.CmpInt(v.Minor, "1") <= 0 {
return v.Major
}
return v.Major + "." + gover.DecInt(v.Minor)
}
// IsValid reports whether the version x is valid.
func IsValid(x string) bool {
return gover.IsValid(x)
}

View File

@@ -0,0 +1,142 @@
// Copyright 2023 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 gover
import (
"reflect"
"testing"
)
func TestCompare(t *testing.T) { test2(t, compareTests, "Compare", Compare) }
var compareTests = []testCase2[string, string, int]{
{"", "", 0},
{"x", "x", 0},
{"", "x", 0},
{"1", "1.1", -1},
{"1.5", "1.6", -1},
{"1.5", "1.10", -1},
{"1.6", "1.6.1", -1},
{"1.19", "1.19.0", 0},
{"1.19rc1", "1.19", -1},
{"1.20", "1.20.0", 0},
{"1.20rc1", "1.20", -1},
{"1.21", "1.21.0", -1},
{"1.21", "1.21rc1", -1},
{"1.21rc1", "1.21.0", -1},
{"1.6", "1.19", -1},
{"1.19", "1.19.1", -1},
{"1.19rc1", "1.19", -1},
{"1.19rc1", "1.19.1", -1},
{"1.19rc1", "1.19rc2", -1},
{"1.19.0", "1.19.1", -1},
{"1.19rc1", "1.19.0", -1},
{"1.19alpha3", "1.19beta2", -1},
{"1.19beta2", "1.19rc1", -1},
{"1.1", "1.99999999999999998", -1},
{"1.99999999999999998", "1.99999999999999999", -1},
}
func TestLang(t *testing.T) { test1(t, langTests, "Lang", Lang) }
var langTests = []testCase1[string, string]{
{"1.2rc3", "1.2"},
{"1.2.3", "1.2"},
{"1.2", "1.2"},
{"1", "1"},
{"1.999testmod", "1.999"},
}
func TestIsLang(t *testing.T) { test1(t, isLangTests, "IsLang", IsLang) }
var isLangTests = []testCase1[string, bool]{
{"1.2rc3", false},
{"1.2.3", false},
{"1.999testmod", false},
{"1.22", true},
{"1.21", true},
{"1.20", false}, // == 1.20.0
{"1.19", false}, // == 1.20.0
{"1.3", false}, // == 1.3.0
{"1.2", false}, // == 1.2.0
{"1", false}, // == 1.0.0
}
func TestPrev(t *testing.T) { test1(t, prevTests, "Prev", Prev) }
var prevTests = []testCase1[string, string]{
{"", ""},
{"0", "0"},
{"1.3rc4", "1.2"},
{"1.3.5", "1.2"},
{"1.3", "1.2"},
{"1", "1"},
{"1.99999999999999999", "1.99999999999999998"},
{"1.40000000000000000", "1.39999999999999999"},
}
func TestIsValid(t *testing.T) { test1(t, isValidTests, "IsValid", IsValid) }
var isValidTests = []testCase1[string, bool]{
{"1.2rc3", true},
{"1.2.3", true},
{"1.999testmod", true},
{"1.600+auto", false},
{"1.22", true},
{"1.21.0", true},
{"1.21rc2", true},
{"1.21", true},
{"1.20.0", true},
{"1.20", true},
{"1.19", true},
{"1.3", true},
{"1.2", true},
{"1", true},
}
type testCase1[In, Out any] struct {
in In
out Out
}
type testCase2[In1, In2, Out any] struct {
in1 In1
in2 In2
out Out
}
type testCase3[In1, In2, In3, Out any] struct {
in1 In1
in2 In2
in3 In3
out Out
}
func test1[In, Out any](t *testing.T, tests []testCase1[In, Out], name string, f func(In) Out) {
t.Helper()
for _, tt := range tests {
if out := f(tt.in); !reflect.DeepEqual(out, tt.out) {
t.Errorf("%s(%v) = %v, want %v", name, tt.in, out, tt.out)
}
}
}
func test2[In1, In2, Out any](t *testing.T, tests []testCase2[In1, In2, Out], name string, f func(In1, In2) Out) {
t.Helper()
for _, tt := range tests {
if out := f(tt.in1, tt.in2); !reflect.DeepEqual(out, tt.out) {
t.Errorf("%s(%+v, %+v) = %+v, want %+v", name, tt.in1, tt.in2, out, tt.out)
}
}
}
func test3[In1, In2, In3, Out any](t *testing.T, tests []testCase3[In1, In2, In3, Out], name string, f func(In1, In2, In3) Out) {
t.Helper()
for _, tt := range tests {
if out := f(tt.in1, tt.in2, tt.in3); !reflect.DeepEqual(out, tt.out) {
t.Errorf("%s(%+v, %+v, %+v) = %+v, want %+v", name, tt.in1, tt.in2, tt.in3, out, tt.out)
}
}
}

View File

@@ -0,0 +1,42 @@
// Copyright 2023 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 gover
import (
"internal/goversion"
"runtime"
"strconv"
)
// TestVersion is initialized in the go command test binary
// to be $TESTGO_VERSION, to allow tests to override the
// go command's idea of its own version as returned by Local.
var TestVersion string
// Local returns the local Go version, the one implemented by this go command.
func Local() string {
v, _ := local()
return v
}
// LocalToolchain returns the local toolchain name, the one implemented by this go command.
func LocalToolchain() string {
_, t := local()
return t
}
func local() (goVers, toolVers string) {
toolVers = runtime.Version()
if TestVersion != "" {
toolVers = TestVersion
}
goVers = FromToolchain(toolVers)
if goVers == "" {
// Development branch. Use "Dev" version with just 1.N, no rc1 or .0 suffix.
goVers = "1." + strconv.Itoa(goversion.Version)
toolVers = "go" + goVers
}
return goVers, toolVers
}

View File

@@ -0,0 +1,127 @@
// Copyright 2023 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 gover
import (
"sort"
"strings"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
)
// IsToolchain reports whether the module path corresponds to the
// virtual, non-downloadable module tracking go or toolchain directives in the go.mod file.
//
// Note that IsToolchain only matches "go" and "toolchain", not the
// real, downloadable module "golang.org/toolchain" containing toolchain files.
//
// IsToolchain("go") = true
// IsToolchain("toolchain") = true
// IsToolchain("golang.org/x/tools") = false
// IsToolchain("golang.org/toolchain") = false
func IsToolchain(path string) bool {
return path == "go" || path == "toolchain"
}
// ModCompare returns the result of comparing the versions x and y
// for the module with the given path.
// The path is necessary because the "go" and "toolchain" modules
// use a different version syntax and semantics (gover, this package)
// than most modules (semver).
func ModCompare(path string, x, y string) int {
if path == "go" {
return Compare(x, y)
}
if path == "toolchain" {
return Compare(maybeToolchainVersion(x), maybeToolchainVersion(y))
}
return semver.Compare(x, y)
}
// ModSort is like module.Sort but understands the "go" and "toolchain"
// modules and their version ordering.
func ModSort(list []module.Version) {
sort.Slice(list, func(i, j int) bool {
mi := list[i]
mj := list[j]
if mi.Path != mj.Path {
return mi.Path < mj.Path
}
// To help go.sum formatting, allow version/file.
// Compare semver prefix by semver rules,
// file by string order.
vi := mi.Version
vj := mj.Version
var fi, fj string
if k := strings.Index(vi, "/"); k >= 0 {
vi, fi = vi[:k], vi[k:]
}
if k := strings.Index(vj, "/"); k >= 0 {
vj, fj = vj[:k], vj[k:]
}
if vi != vj {
return ModCompare(mi.Path, vi, vj) < 0
}
return fi < fj
})
}
// ModIsValid reports whether vers is a valid version syntax for the module with the given path.
func ModIsValid(path, vers string) bool {
if IsToolchain(path) {
if path == "toolchain" {
return IsValid(FromToolchain(vers))
}
return IsValid(vers)
}
return semver.IsValid(vers)
}
// ModIsPrefix reports whether v is a valid version syntax prefix for the module with the given path.
// The caller is assumed to have checked that ModIsValid(path, vers) is true.
func ModIsPrefix(path, vers string) bool {
if IsToolchain(path) {
if path == "toolchain" {
return IsLang(FromToolchain(vers))
}
return IsLang(vers)
}
// Semver
dots := 0
for i := 0; i < len(vers); i++ {
switch vers[i] {
case '-', '+':
return false
case '.':
dots++
if dots >= 2 {
return false
}
}
}
return true
}
// ModIsPrerelease reports whether v is a prerelease version for the module with the given path.
// The caller is assumed to have checked that ModIsValid(path, vers) is true.
func ModIsPrerelease(path, vers string) bool {
if IsToolchain(path) {
return IsPrerelease(vers)
}
return semver.Prerelease(vers) != ""
}
// ModMajorMinor returns the "major.minor" truncation of the version v,
// for use as a prefix in "@patch" queries.
func ModMajorMinor(path, vers string) string {
if IsToolchain(path) {
if path == "toolchain" {
return "go" + Lang(FromToolchain(vers))
}
return Lang(vers)
}
return semver.MajorMinor(vers)
}

View File

@@ -0,0 +1,72 @@
// Copyright 2023 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 gover
import (
"slices"
"strings"
"testing"
"golang.org/x/mod/module"
)
func TestIsToolchain(t *testing.T) { test1(t, isToolchainTests, "IsToolchain", IsToolchain) }
var isToolchainTests = []testCase1[string, bool]{
{"go", true},
{"toolchain", true},
{"anything", false},
{"golang.org/toolchain", false},
}
func TestModCompare(t *testing.T) { test3(t, modCompareTests, "ModCompare", ModCompare) }
var modCompareTests = []testCase3[string, string, string, int]{
{"go", "1.2", "1.3", -1},
{"go", "v1.2", "v1.3", 0}, // equal because invalid
{"go", "1.2", "1.2", 0},
{"toolchain", "go1.2", "go1.3", -1},
{"toolchain", "go1.2", "go1.2", 0},
{"toolchain", "1.2", "1.3", -1}, // accepted but non-standard
{"toolchain", "v1.2", "v1.3", 0}, // equal because invalid
{"rsc.io/quote", "v1.2", "v1.3", -1},
{"rsc.io/quote", "1.2", "1.3", 0}, // equal because invalid
}
func TestModIsValid(t *testing.T) { test2(t, modIsValidTests, "ModIsValid", ModIsValid) }
var modIsValidTests = []testCase2[string, string, bool]{
{"go", "1.2", true},
{"go", "v1.2", false},
{"toolchain", "go1.2", true},
{"toolchain", "v1.2", false},
{"rsc.io/quote", "v1.2", true},
{"rsc.io/quote", "1.2", false},
}
func TestModSort(t *testing.T) {
test1(t, modSortTests, "ModSort", func(list []module.Version) []module.Version {
out := slices.Clone(list)
ModSort(out)
return out
})
}
var modSortTests = []testCase1[[]module.Version, []module.Version]{
{
mvl(`z v1.1; a v1.2; a v1.1; go 1.3; toolchain 1.3; toolchain 1.2; go 1.2`),
mvl(`a v1.1; a v1.2; go 1.2; go 1.3; toolchain 1.2; toolchain 1.3; z v1.1`),
},
}
func mvl(s string) []module.Version {
var list []module.Version
for _, f := range strings.Split(s, ";") {
f = strings.TrimSpace(f)
path, vers, _ := strings.Cut(f, " ")
list = append(list, module.Version{Path: path, Version: vers})
}
return list
}

View File

@@ -0,0 +1,108 @@
// Copyright 2023 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 gover
import (
"cmd/go/internal/base"
"context"
"errors"
"fmt"
"strings"
)
// FromToolchain returns the Go version for the named toolchain,
// derived from the name itself (not by running the toolchain).
// A toolchain is named "goVERSION".
// A suffix after the VERSION introduced by a -, space, or tab is removed.
// Examples:
//
// FromToolchain("go1.2.3") == "1.2.3"
// FromToolchain("go1.2.3-bigcorp") == "1.2.3"
// FromToolchain("invalid") == ""
func FromToolchain(name string) string {
if strings.ContainsAny(name, "\\/") {
// The suffix must not include a path separator, since that would cause
// exec.LookPath to resolve it from a relative directory instead of from
// $PATH.
return ""
}
var v string
if strings.HasPrefix(name, "go") {
v = name[2:]
} else {
return ""
}
// Some builds use custom suffixes; strip them.
if i := strings.IndexAny(v, " \t-"); i >= 0 {
v = v[:i]
}
if !IsValid(v) {
return ""
}
return v
}
func maybeToolchainVersion(name string) string {
if IsValid(name) {
return name
}
return FromToolchain(name)
}
// ToolchainMax returns the maximum of x and y interpreted as toolchain names,
// compared using Compare(FromToolchain(x), FromToolchain(y)).
// If x and y compare equal, Max returns x.
func ToolchainMax(x, y string) string {
if Compare(FromToolchain(x), FromToolchain(y)) < 0 {
return y
}
return x
}
// Startup records the information that went into the startup-time version switch.
// It is initialized by switchGoToolchain.
var Startup struct {
GOTOOLCHAIN string // $GOTOOLCHAIN setting
AutoFile string // go.mod or go.work file consulted
AutoGoVersion string // go line found in file
AutoToolchain string // toolchain line found in file
}
// A TooNewError explains that a module is too new for this version of Go.
type TooNewError struct {
What string
GoVersion string
Toolchain string // for callers if they want to use it, but not printed
}
func (e *TooNewError) Error() string {
var explain string
if Startup.GOTOOLCHAIN != "" && Startup.GOTOOLCHAIN != "auto" {
explain = "; GOTOOLCHAIN=" + Startup.GOTOOLCHAIN
}
if Startup.AutoFile != "" && (Startup.AutoGoVersion != "" || Startup.AutoToolchain != "") {
explain += fmt.Sprintf("; %s sets ", base.ShortPath(Startup.AutoFile))
if Startup.AutoToolchain != "" {
explain += "toolchain " + Startup.AutoToolchain
} else {
explain += "go " + Startup.AutoGoVersion
}
}
return fmt.Sprintf("%v requires go >= %v (running go %v%v)", e.What, e.GoVersion, Local(), explain)
}
var ErrTooNew = errors.New("module too new")
func (e *TooNewError) Is(err error) bool {
return err == ErrTooNew
}
// A Switcher provides the ability to switch to a new toolchain in response to TooNewErrors.
// See [cmd/go/internal/toolchain.Switcher] for documentation.
type Switcher interface {
Error(err error)
Switch(ctx context.Context)
}

View File

@@ -0,0 +1,19 @@
// Copyright 2023 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 gover
import "testing"
func TestFromToolchain(t *testing.T) { test1(t, fromToolchainTests, "FromToolchain", FromToolchain) }
var fromToolchainTests = []testCase1[string, string]{
{"go1.2.3", "1.2.3"},
{"1.2.3", ""},
{"go1.2.3+bigcorp", ""},
{"go1.2.3-bigcorp", "1.2.3"},
{"go1.2.3-bigcorp more text", "1.2.3"},
{"gccgo-go1.23rc4", ""},
{"gccgo-go1.23rc4-bigdwarf", ""},
}

View File

@@ -0,0 +1,78 @@
// Copyright 2023 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 gover
import "golang.org/x/mod/modfile"
const (
// narrowAllVersion is the Go version at which the
// module-module "all" pattern no longer closes over the dependencies of
// tests outside of the main module.
NarrowAllVersion = "1.16"
// DefaultGoModVersion is the Go version to assume for go.mod files
// that do not declare a Go version. The go command has been
// writing go versions to modules since Go 1.12, so a go.mod
// without a version is either very old or recently hand-written.
// Since we can't tell which, we have to assume it's very old.
// The semantics of the go.mod changed at Go 1.17 to support
// graph pruning. If see a go.mod without a go line, we have to
// assume Go 1.16 so that we interpret the requirements correctly.
// Note that this default must stay at Go 1.16; it cannot be moved forward.
DefaultGoModVersion = "1.16"
// DefaultGoWorkVersion is the Go version to assume for go.work files
// that do not declare a Go version. Workspaces were added in Go 1.18,
// so use that.
DefaultGoWorkVersion = "1.18"
// ExplicitIndirectVersion is the Go version at which a
// module's go.mod file is expected to list explicit requirements on every
// module that provides any package transitively imported by that module.
//
// Other indirect dependencies of such a module can be safely pruned out of
// the module graph; see https://golang.org/ref/mod#graph-pruning.
ExplicitIndirectVersion = "1.17"
// separateIndirectVersion is the Go version at which
// "// indirect" dependencies are added in a block separate from the direct
// ones. See https://golang.org/issue/45965.
SeparateIndirectVersion = "1.17"
// tidyGoModSumVersion is the Go version at which
// 'go mod tidy' preserves go.mod checksums needed to build test dependencies
// of packages in "all", so that 'go test all' can be run without checksum
// errors.
// See https://go.dev/issue/56222.
TidyGoModSumVersion = "1.21"
// goStrictVersion is the Go version at which the Go versions
// became "strict" in the sense that, restricted to modules at this version
// or later, every module must have a go version line ≥ all its dependencies.
// It is also the version after which "too new" a version is considered a fatal error.
GoStrictVersion = "1.21"
// ExplicitModulesTxtImportVersion is the Go version at which vendored packages need to be present
// in modules.txt to be imported.
ExplicitModulesTxtImportVersion = "1.23"
)
// FromGoMod returns the go version from the go.mod file.
// It returns DefaultGoModVersion if the go.mod file does not contain a go line or if mf is nil.
func FromGoMod(mf *modfile.File) string {
if mf == nil || mf.Go == nil {
return DefaultGoModVersion
}
return mf.Go.Version
}
// FromGoWork returns the go version from the go.mod file.
// It returns DefaultGoWorkVersion if the go.mod file does not contain a go line or if wf is nil.
func FromGoWork(wf *modfile.WorkFile) string {
if wf == nil || wf.Go == nil {
return DefaultGoWorkVersion
}
return wf.Go.Version
}

View File

@@ -0,0 +1,192 @@
// Copyright 2017 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 help implements the “go help” command.
package help
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"text/template"
"unicode"
"unicode/utf8"
"cmd/go/internal/base"
"cmd/internal/telemetry/counter"
)
var counterErrorsHelpUnknownTopic = counter.New("go/errors:help-unknown-topic")
// Help implements the 'help' command.
func Help(w io.Writer, args []string) {
// 'go help documentation' generates doc.go.
if len(args) == 1 && args[0] == "documentation" {
fmt.Fprintln(w, "// Copyright 2011 The Go Authors. All rights reserved.")
fmt.Fprintln(w, "// Use of this source code is governed by a BSD-style")
fmt.Fprintln(w, "// license that can be found in the LICENSE file.")
fmt.Fprintln(w)
fmt.Fprintln(w, "// Code generated by 'go test cmd/go -v -run=^TestDocsUpToDate$ -fixdocs'; DO NOT EDIT.")
fmt.Fprintln(w, "// Edit the documentation in other files and then execute 'go generate cmd/go' to generate this one.")
fmt.Fprintln(w)
buf := new(strings.Builder)
PrintUsage(buf, base.Go)
usage := &base.Command{Long: buf.String()}
cmds := []*base.Command{usage}
for _, cmd := range base.Go.Commands {
cmds = append(cmds, cmd)
cmds = append(cmds, cmd.Commands...)
}
tmpl(&commentWriter{W: w}, documentationTemplate, cmds)
fmt.Fprintln(w, "package main")
return
}
cmd := base.Go
Args:
for i, arg := range args {
for _, sub := range cmd.Commands {
if sub.Name() == arg {
cmd = sub
continue Args
}
}
// helpSuccess is the help command using as many args as possible that would succeed.
helpSuccess := "go help"
if i > 0 {
helpSuccess += " " + strings.Join(args[:i], " ")
}
counterErrorsHelpUnknownTopic.Inc()
fmt.Fprintf(os.Stderr, "go help %s: unknown help topic. Run '%s'.\n", strings.Join(args, " "), helpSuccess)
base.SetExitStatus(2) // failed at 'go help cmd'
base.Exit()
}
if len(cmd.Commands) > 0 {
PrintUsage(os.Stdout, cmd)
} else {
tmpl(os.Stdout, helpTemplate, cmd)
}
// not exit 2: succeeded at 'go help cmd'.
return
}
var usageTemplate = `{{.Long | trim}}
Usage:
{{.UsageLine}} <command> [arguments]
The commands are:
{{range .Commands}}{{if or (.Runnable) .Commands}}
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
Use "go help{{with .LongName}} {{.}}{{end}} <command>" for more information about a command.
{{if eq (.UsageLine) "go"}}
Additional help topics:
{{range .Commands}}{{if and (not .Runnable) (not .Commands)}}
{{.Name | printf "%-15s"}} {{.Short}}{{end}}{{end}}
Use "go help{{with .LongName}} {{.}}{{end}} <topic>" for more information about that topic.
{{end}}
`
var helpTemplate = `{{if .Runnable}}usage: {{.UsageLine}}
{{end}}{{.Long | trim}}
`
var documentationTemplate = `{{range .}}{{if .Short}}{{.Short | capitalize}}
{{end}}{{if .Commands}}` + usageTemplate + `{{else}}{{if .Runnable}}Usage:
{{.UsageLine}}
{{end}}{{.Long | trim}}
{{end}}{{end}}`
// commentWriter writes a Go comment to the underlying io.Writer,
// using line comment form (//).
type commentWriter struct {
W io.Writer
wroteSlashes bool // Wrote "//" at the beginning of the current line.
}
func (c *commentWriter) Write(p []byte) (int, error) {
var n int
for i, b := range p {
if !c.wroteSlashes {
s := "//"
if b != '\n' {
s = "// "
}
if _, err := io.WriteString(c.W, s); err != nil {
return n, err
}
c.wroteSlashes = true
}
n0, err := c.W.Write(p[i : i+1])
n += n0
if err != nil {
return n, err
}
if b == '\n' {
c.wroteSlashes = false
}
}
return len(p), nil
}
// An errWriter wraps a writer, recording whether a write error occurred.
type errWriter struct {
w io.Writer
err error
}
func (w *errWriter) Write(b []byte) (int, error) {
n, err := w.w.Write(b)
if err != nil {
w.err = err
}
return n, err
}
// tmpl executes the given template text on data, writing the result to w.
func tmpl(w io.Writer, text string, data any) {
t := template.New("top")
t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
template.Must(t.Parse(text))
ew := &errWriter{w: w}
err := t.Execute(ew, data)
if ew.err != nil {
// I/O error writing. Ignore write on closed pipe.
if strings.Contains(ew.err.Error(), "pipe") {
base.SetExitStatus(1)
base.Exit()
}
base.Fatalf("writing output: %v", ew.err)
}
if err != nil {
panic(err)
}
}
func capitalize(s string) string {
if s == "" {
return s
}
r, n := utf8.DecodeRuneInString(s)
return string(unicode.ToTitle(r)) + s[n:]
}
func PrintUsage(w io.Writer, cmd *base.Command) {
bw := bufio.NewWriter(w)
tmpl(bw, usageTemplate, cmd)
bw.Flush()
}

View File

@@ -0,0 +1,965 @@
// Copyright 2011 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 help
import "cmd/go/internal/base"
var HelpC = &base.Command{
UsageLine: "c",
Short: "calling between Go and C",
Long: `
There are two different ways to call between Go and C/C++ code.
The first is the cgo tool, which is part of the Go distribution. For
information on how to use it see the cgo documentation (go doc cmd/cgo).
The second is the SWIG program, which is a general tool for
interfacing between languages. For information on SWIG see
http://swig.org/. When running go build, any file with a .swig
extension will be passed to SWIG. Any file with a .swigcxx extension
will be passed to SWIG with the -c++ option.
When either cgo or SWIG is used, go build will pass any .c, .m, .s, .S
or .sx files to the C compiler, and any .cc, .cpp, .cxx files to the C++
compiler. The CC or CXX environment variables may be set to determine
the C or C++ compiler, respectively, to use.
`,
}
var HelpPackages = &base.Command{
UsageLine: "packages",
Short: "package lists and patterns",
Long: `
Many commands apply to a set of packages:
go <action> [packages]
Usually, [packages] is a list of import paths.
An import path that is a rooted path or that begins with
a . or .. element is interpreted as a file system path and
denotes the package in that directory.
Otherwise, the import path P denotes the package found in
the directory DIR/src/P for some DIR listed in the GOPATH
environment variable (For more details see: 'go help gopath').
If no import paths are given, the action applies to the
package in the current directory.
There are four reserved names for paths that should not be used
for packages to be built with the go tool:
- "main" denotes the top-level package in a stand-alone executable.
- "all" expands to all packages found in all the GOPATH
trees. For example, 'go list all' lists all the packages on the local
system. When using modules, "all" expands to all packages in
the main module and their dependencies, including dependencies
needed by tests of any of those.
- "std" is like all but expands to just the packages in the standard
Go library.
- "cmd" expands to the Go repository's commands and their
internal libraries.
Import paths beginning with "cmd/" only match source code in
the Go repository.
An import path is a pattern if it includes one or more "..." wildcards,
each of which can match any string, including the empty string and
strings containing slashes. Such a pattern expands to all package
directories found in the GOPATH trees with names matching the
patterns.
To make common patterns more convenient, there are two special cases.
First, /... at the end of the pattern can match an empty string,
so that net/... matches both net and packages in its subdirectories, like net/http.
Second, any slash-separated pattern element containing a wildcard never
participates in a match of the "vendor" element in the path of a vendored
package, so that ./... does not match packages in subdirectories of
./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
Note, however, that a directory named vendor that itself contains code
is not a vendored package: cmd/vendor would be a command named vendor,
and the pattern cmd/... matches it.
See golang.org/s/go15vendor for more about vendoring.
An import path can also name a package to be downloaded from
a remote repository. Run 'go help importpath' for details.
Every package in a program must have a unique import path.
By convention, this is arranged by starting each path with a
unique prefix that belongs to you. For example, paths used
internally at Google all begin with 'google', and paths
denoting remote repositories begin with the path to the code,
such as 'github.com/user/repo'.
Packages in a program need not have unique package names,
but there are two reserved package names with special meaning.
The name main indicates a command, not a library.
Commands are built into binaries and cannot be imported.
The name documentation indicates documentation for
a non-Go program in the directory. Files in package documentation
are ignored by the go command.
As a special case, if the package list is a list of .go files from a
single directory, the command is applied to a single synthesized
package made up of exactly those files, ignoring any build constraints
in those files and ignoring any other files in the directory.
Directory and file names that begin with "." or "_" are ignored
by the go tool, as are directories named "testdata".
`,
}
var HelpImportPath = &base.Command{
UsageLine: "importpath",
Short: "import path syntax",
Long: `
An import path (see 'go help packages') denotes a package stored in the local
file system. In general, an import path denotes either a standard package (such
as "unicode/utf8") or a package found in one of the work spaces (For more
details see: 'go help gopath').
Relative import paths
An import path beginning with ./ or ../ is called a relative path.
The toolchain supports relative import paths as a shortcut in two ways.
First, a relative path can be used as a shorthand on the command line.
If you are working in the directory containing the code imported as
"unicode" and want to run the tests for "unicode/utf8", you can type
"go test ./utf8" instead of needing to specify the full path.
Similarly, in the reverse situation, "go test .." will test "unicode" from
the "unicode/utf8" directory. Relative patterns are also allowed, like
"go test ./..." to test all subdirectories. See 'go help packages' for details
on the pattern syntax.
Second, if you are compiling a Go program not in a work space,
you can use a relative path in an import statement in that program
to refer to nearby code also not in a work space.
This makes it easy to experiment with small multipackage programs
outside of the usual work spaces, but such programs cannot be
installed with "go install" (there is no work space in which to install them),
so they are rebuilt from scratch each time they are built.
To avoid ambiguity, Go programs cannot use relative import paths
within a work space.
Remote import paths
Certain import paths also
describe how to obtain the source code for the package using
a revision control system.
A few common code hosting sites have special syntax:
Bitbucket (Git, Mercurial)
import "bitbucket.org/user/project"
import "bitbucket.org/user/project/sub/directory"
GitHub (Git)
import "github.com/user/project"
import "github.com/user/project/sub/directory"
Launchpad (Bazaar)
import "launchpad.net/project"
import "launchpad.net/project/series"
import "launchpad.net/project/series/sub/directory"
import "launchpad.net/~user/project/branch"
import "launchpad.net/~user/project/branch/sub/directory"
IBM DevOps Services (Git)
import "hub.jazz.net/git/user/project"
import "hub.jazz.net/git/user/project/sub/directory"
For code hosted on other servers, import paths may either be qualified
with the version control type, or the go tool can dynamically fetch
the import path over https/http and discover where the code resides
from a <meta> tag in the HTML.
To declare the code location, an import path of the form
repository.vcs/path
specifies the given repository, with or without the .vcs suffix,
using the named version control system, and then the path inside
that repository. The supported version control systems are:
Bazaar .bzr
Fossil .fossil
Git .git
Mercurial .hg
Subversion .svn
For example,
import "example.org/user/foo.hg"
denotes the root directory of the Mercurial repository at
example.org/user/foo or foo.hg, and
import "example.org/repo.git/foo/bar"
denotes the foo/bar directory of the Git repository at
example.org/repo or repo.git.
When a version control system supports multiple protocols,
each is tried in turn when downloading. For example, a Git
download tries https://, then git+ssh://.
By default, downloads are restricted to known secure protocols
(e.g. https, ssh). To override this setting for Git downloads, the
GIT_ALLOW_PROTOCOL environment variable can be set (For more details see:
'go help environment').
If the import path is not a known code hosting site and also lacks a
version control qualifier, the go tool attempts to fetch the import
over https/http and looks for a <meta> tag in the document's HTML
<head>.
The meta tag has the form:
<meta name="go-import" content="import-prefix vcs repo-root">
The import-prefix is the import path corresponding to the repository
root. It must be a prefix or an exact match of the package being
fetched with "go get". If it's not an exact match, another http
request is made at the prefix to verify the <meta> tags match.
The meta tag should appear as early in the file as possible.
In particular, it should appear before any raw JavaScript or CSS,
to avoid confusing the go command's restricted parser.
The vcs is one of "bzr", "fossil", "git", "hg", "svn".
The repo-root is the root of the version control system
containing a scheme and not containing a .vcs qualifier.
For example,
import "example.org/pkg/foo"
will result in the following requests:
https://example.org/pkg/foo?go-get=1 (preferred)
http://example.org/pkg/foo?go-get=1 (fallback, only with use of correctly set GOINSECURE)
If that page contains the meta tag
<meta name="go-import" content="example.org git https://code.org/r/p/exproj">
the go tool will verify that https://example.org/?go-get=1 contains the
same meta tag and then git clone https://code.org/r/p/exproj into
GOPATH/src/example.org.
When using GOPATH, downloaded packages are written to the first directory
listed in the GOPATH environment variable.
(See 'go help gopath-get' and 'go help gopath'.)
When using modules, downloaded packages are stored in the module cache.
See https://golang.org/ref/mod#module-cache.
When using modules, an additional variant of the go-import meta tag is
recognized and is preferred over those listing version control systems.
That variant uses "mod" as the vcs in the content value, as in:
<meta name="go-import" content="example.org mod https://code.org/moduleproxy">
This tag means to fetch modules with paths beginning with example.org
from the module proxy available at the URL https://code.org/moduleproxy.
See https://golang.org/ref/mod#goproxy-protocol for details about the
proxy protocol.
Import path checking
When the custom import path feature described above redirects to a
known code hosting site, each of the resulting packages has two possible
import paths, using the custom domain or the known hosting site.
A package statement is said to have an "import comment" if it is immediately
followed (before the next newline) by a comment of one of these two forms:
package math // import "path"
package math /* import "path" */
The go command will refuse to install a package with an import comment
unless it is being referred to by that import path. In this way, import comments
let package authors make sure the custom import path is used and not a
direct path to the underlying code hosting site.
Import path checking is disabled for code found within vendor trees.
This makes it possible to copy code into alternate locations in vendor trees
without needing to update import comments.
Import path checking is also disabled when using modules.
Import path comments are obsoleted by the go.mod file's module statement.
See https://golang.org/s/go14customimport for details.
`,
}
var HelpGopath = &base.Command{
UsageLine: "gopath",
Short: "GOPATH environment variable",
Long: `
The Go path is used to resolve import statements.
It is implemented by and documented in the go/build package.
The GOPATH environment variable lists places to look for Go code.
On Unix, the value is a colon-separated string.
On Windows, the value is a semicolon-separated string.
On Plan 9, the value is a list.
If the environment variable is unset, GOPATH defaults
to a subdirectory named "go" in the user's home directory
($HOME/go on Unix, %USERPROFILE%\go on Windows),
unless that directory holds a Go distribution.
Run "go env GOPATH" to see the current GOPATH.
See https://golang.org/wiki/SettingGOPATH to set a custom GOPATH.
Each directory listed in GOPATH must have a prescribed structure:
The src directory holds source code. The path below src
determines the import path or executable name.
The pkg directory holds installed package objects.
As in the Go tree, each target operating system and
architecture pair has its own subdirectory of pkg
(pkg/GOOS_GOARCH).
If DIR is a directory listed in the GOPATH, a package with
source in DIR/src/foo/bar can be imported as "foo/bar" and
has its compiled form installed to "DIR/pkg/GOOS_GOARCH/foo/bar.a".
The bin directory holds compiled commands.
Each command is named for its source directory, but only
the final element, not the entire path. That is, the
command with source in DIR/src/foo/quux is installed into
DIR/bin/quux, not DIR/bin/foo/quux. The "foo/" prefix is stripped
so that you can add DIR/bin to your PATH to get at the
installed commands. If the GOBIN environment variable is
set, commands are installed to the directory it names instead
of DIR/bin. GOBIN must be an absolute path.
Here's an example directory layout:
GOPATH=/home/user/go
/home/user/go/
src/
foo/
bar/ (go code in package bar)
x.go
quux/ (go code in package main)
y.go
bin/
quux (installed command)
pkg/
linux_amd64/
foo/
bar.a (installed package object)
Go searches each directory listed in GOPATH to find source code,
but new packages are always downloaded into the first directory
in the list.
See https://golang.org/doc/code.html for an example.
GOPATH and Modules
When using modules, GOPATH is no longer used for resolving imports.
However, it is still used to store downloaded source code (in GOPATH/pkg/mod)
and compiled commands (in GOPATH/bin).
Internal Directories
Code in or below a directory named "internal" is importable only
by code in the directory tree rooted at the parent of "internal".
Here's an extended version of the directory layout above:
/home/user/go/
src/
crash/
bang/ (go code in package bang)
b.go
foo/ (go code in package foo)
f.go
bar/ (go code in package bar)
x.go
internal/
baz/ (go code in package baz)
z.go
quux/ (go code in package main)
y.go
The code in z.go is imported as "foo/internal/baz", but that
import statement can only appear in source files in the subtree
rooted at foo. The source files foo/f.go, foo/bar/x.go, and
foo/quux/y.go can all import "foo/internal/baz", but the source file
crash/bang/b.go cannot.
See https://golang.org/s/go14internal for details.
Vendor Directories
Go 1.6 includes support for using local copies of external dependencies
to satisfy imports of those dependencies, often referred to as vendoring.
Code below a directory named "vendor" is importable only
by code in the directory tree rooted at the parent of "vendor",
and only using an import path that omits the prefix up to and
including the vendor element.
Here's the example from the previous section,
but with the "internal" directory renamed to "vendor"
and a new foo/vendor/crash/bang directory added:
/home/user/go/
src/
crash/
bang/ (go code in package bang)
b.go
foo/ (go code in package foo)
f.go
bar/ (go code in package bar)
x.go
vendor/
crash/
bang/ (go code in package bang)
b.go
baz/ (go code in package baz)
z.go
quux/ (go code in package main)
y.go
The same visibility rules apply as for internal, but the code
in z.go is imported as "baz", not as "foo/vendor/baz".
Code in vendor directories deeper in the source tree shadows
code in higher directories. Within the subtree rooted at foo, an import
of "crash/bang" resolves to "foo/vendor/crash/bang", not the
top-level "crash/bang".
Code in vendor directories is not subject to import path
checking (see 'go help importpath').
When 'go get' checks out or updates a git repository, it now also
updates submodules.
Vendor directories do not affect the placement of new repositories
being checked out for the first time by 'go get': those are always
placed in the main GOPATH, never in a vendor subtree.
See https://golang.org/s/go15vendor for details.
`,
}
var HelpEnvironment = &base.Command{
UsageLine: "environment",
Short: "environment variables",
Long: `
The go command and the tools it invokes consult environment variables
for configuration. If an environment variable is unset or empty, the go
command uses a sensible default setting. To see the effective setting of
the variable <NAME>, run 'go env <NAME>'. To change the default setting,
run 'go env -w <NAME>=<VALUE>'. Defaults changed using 'go env -w'
are recorded in a Go environment configuration file stored in the
per-user configuration directory, as reported by os.UserConfigDir.
The location of the configuration file can be changed by setting
the environment variable GOENV, and 'go env GOENV' prints the
effective location, but 'go env -w' cannot change the default location.
See 'go help env' for details.
General-purpose environment variables:
GO111MODULE
Controls whether the go command runs in module-aware mode or GOPATH mode.
May be "off", "on", or "auto".
See https://golang.org/ref/mod#mod-commands.
GCCGO
The gccgo command to run for 'go build -compiler=gccgo'.
GOARCH
The architecture, or processor, for which to compile code.
Examples are amd64, 386, arm, ppc64.
GOBIN
The directory where 'go install' will install a command.
GOCACHE
The directory where the go command will store cached
information for reuse in future builds.
GOMODCACHE
The directory where the go command will store downloaded modules.
GODEBUG
Enable various debugging facilities. See https://go.dev/doc/godebug
for details.
GOENV
The location of the Go environment configuration file.
Cannot be set using 'go env -w'.
Setting GOENV=off in the environment disables the use of the
default configuration file.
GOFLAGS
A space-separated list of -flag=value settings to apply
to go commands by default, when the given flag is known by
the current command. Each entry must be a standalone flag.
Because the entries are space-separated, flag values must
not contain spaces. Flags listed on the command line
are applied after this list and therefore override it.
GOINSECURE
Comma-separated list of glob patterns (in the syntax of Go's path.Match)
of module path prefixes that should always be fetched in an insecure
manner. Only applies to dependencies that are being fetched directly.
GOINSECURE does not disable checksum database validation. GOPRIVATE or
GONOSUMDB may be used to achieve that.
GOOS
The operating system for which to compile code.
Examples are linux, darwin, windows, netbsd.
GOPATH
Controls where various files are stored. See: 'go help gopath'.
GOPROXY
URL of Go module proxy. See https://golang.org/ref/mod#environment-variables
and https://golang.org/ref/mod#module-proxy for details.
GOPRIVATE, GONOPROXY, GONOSUMDB
Comma-separated list of glob patterns (in the syntax of Go's path.Match)
of module path prefixes that should always be fetched directly
or that should not be compared against the checksum database.
See https://golang.org/ref/mod#private-modules.
GOROOT
The root of the go tree.
GOSUMDB
The name of checksum database to use and optionally its public key and
URL. See https://golang.org/ref/mod#authenticating.
GOTOOLCHAIN
Controls which Go toolchain is used. See https://go.dev/doc/toolchain.
GOTMPDIR
The directory where the go command will write
temporary source files, packages, and binaries.
GOVCS
Lists version control commands that may be used with matching servers.
See 'go help vcs'.
GOWORK
In module aware mode, use the given go.work file as a workspace file.
By default or when GOWORK is "auto", the go command searches for a
file named go.work in the current directory and then containing directories
until one is found. If a valid go.work file is found, the modules
specified will collectively be used as the main modules. If GOWORK
is "off", or a go.work file is not found in "auto" mode, workspace
mode is disabled.
Environment variables for use with cgo:
AR
The command to use to manipulate library archives when
building with the gccgo compiler.
The default is 'ar'.
CC
The command to use to compile C code.
CGO_ENABLED
Whether the cgo command is supported. Either 0 or 1.
CGO_CFLAGS
Flags that cgo will pass to the compiler when compiling
C code.
CGO_CFLAGS_ALLOW
A regular expression specifying additional flags to allow
to appear in #cgo CFLAGS source code directives.
Does not apply to the CGO_CFLAGS environment variable.
CGO_CFLAGS_DISALLOW
A regular expression specifying flags that must be disallowed
from appearing in #cgo CFLAGS source code directives.
Does not apply to the CGO_CFLAGS environment variable.
CGO_CPPFLAGS, CGO_CPPFLAGS_ALLOW, CGO_CPPFLAGS_DISALLOW
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
but for the C preprocessor.
CGO_CXXFLAGS, CGO_CXXFLAGS_ALLOW, CGO_CXXFLAGS_DISALLOW
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
but for the C++ compiler.
CGO_FFLAGS, CGO_FFLAGS_ALLOW, CGO_FFLAGS_DISALLOW
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
but for the Fortran compiler.
CGO_LDFLAGS, CGO_LDFLAGS_ALLOW, CGO_LDFLAGS_DISALLOW
Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW,
but for the linker.
CXX
The command to use to compile C++ code.
FC
The command to use to compile Fortran code.
PKG_CONFIG
Path to pkg-config tool.
Architecture-specific environment variables:
GOARM
For GOARCH=arm, the ARM architecture for which to compile.
Valid values are 5, 6, 7.
The value can be followed by an option specifying how to implement floating point instructions.
Valid options are ,softfloat (default for 5) and ,hardfloat (default for 6 and 7).
GOARM64
For GOARCH=arm64, the ARM64 architecture for which to compile.
Valid values are v8.0 (default), v8.{1-9}, v9.{0-5}.
The value can be followed by an option specifying extensions implemented by target hardware.
Valid options are ,lse and ,crypto.
Note that some extensions are enabled by default starting from a certain GOARM64 version;
for example, lse is enabled by default starting from v8.1.
GO386
For GOARCH=386, how to implement floating point instructions.
Valid values are sse2 (default), softfloat.
GOAMD64
For GOARCH=amd64, the microarchitecture level for which to compile.
Valid values are v1 (default), v2, v3, v4.
See https://golang.org/wiki/MinimumRequirements#amd64
GOMIPS
For GOARCH=mips{,le}, whether to use floating point instructions.
Valid values are hardfloat (default), softfloat.
GOMIPS64
For GOARCH=mips64{,le}, whether to use floating point instructions.
Valid values are hardfloat (default), softfloat.
GOPPC64
For GOARCH=ppc64{,le}, the target ISA (Instruction Set Architecture).
Valid values are power8 (default), power9, power10.
GORISCV64
For GOARCH=riscv64, the RISC-V user-mode application profile for which
to compile. Valid values are rva20u64 (default), rva22u64.
See https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc
GOWASM
For GOARCH=wasm, comma-separated list of experimental WebAssembly features to use.
Valid values are satconv, signext.
Environment variables for use with code coverage:
GOCOVERDIR
Directory into which to write code coverage data files
generated by running a "go build -cover" binary.
Requires that GOEXPERIMENT=coverageredesign is enabled.
Special-purpose environment variables:
GCCGOTOOLDIR
If set, where to find gccgo tools, such as cgo.
The default is based on how gccgo was configured.
GOEXPERIMENT
Comma-separated list of toolchain experiments to enable or disable.
The list of available experiments may change arbitrarily over time.
See src/internal/goexperiment/flags.go for currently valid values.
Warning: This variable is provided for the development and testing
of the Go toolchain itself. Use beyond that purpose is unsupported.
GO_EXTLINK_ENABLED
Whether the linker should use external linking mode
when using -linkmode=auto with code that uses cgo.
Set to 0 to disable external linking mode, 1 to enable it.
GIT_ALLOW_PROTOCOL
Defined by Git. A colon-separated list of schemes that are allowed
to be used with git fetch/clone. If set, any scheme not explicitly
mentioned will be considered insecure by 'go get'.
Because the variable is defined by Git, the default value cannot
be set using 'go env -w'.
Additional information available from 'go env' but not read from the environment:
GOEXE
The executable file name suffix (".exe" on Windows, "" on other systems).
GOGCCFLAGS
A space-separated list of arguments supplied to the CC command.
GOHOSTARCH
The architecture (GOARCH) of the Go toolchain binaries.
GOHOSTOS
The operating system (GOOS) of the Go toolchain binaries.
GOMOD
The absolute path to the go.mod of the main module.
If module-aware mode is enabled, but there is no go.mod, GOMOD will be
os.DevNull ("/dev/null" on Unix-like systems, "NUL" on Windows).
If module-aware mode is disabled, GOMOD will be the empty string.
GOTOOLDIR
The directory where the go tools (compile, cover, doc, etc...) are installed.
GOVERSION
The version of the installed Go tree, as reported by runtime.Version.
`,
}
var HelpFileType = &base.Command{
UsageLine: "filetype",
Short: "file types",
Long: `
The go command examines the contents of a restricted set of files
in each directory. It identifies which files to examine based on
the extension of the file name. These extensions are:
.go
Go source files.
.c, .h
C source files.
If the package uses cgo or SWIG, these will be compiled with the
OS-native compiler (typically gcc); otherwise they will
trigger an error.
.cc, .cpp, .cxx, .hh, .hpp, .hxx
C++ source files. Only useful with cgo or SWIG, and always
compiled with the OS-native compiler.
.m
Objective-C source files. Only useful with cgo, and always
compiled with the OS-native compiler.
.s, .S, .sx
Assembler source files.
If the package uses cgo or SWIG, these will be assembled with the
OS-native assembler (typically gcc (sic)); otherwise they
will be assembled with the Go assembler.
.swig, .swigcxx
SWIG definition files.
.syso
System object files.
Files of each of these types except .syso may contain build
constraints, but the go command stops scanning for build constraints
at the first item in the file that is not a blank line or //-style
line comment. See the go/build package documentation for
more details.
`,
}
var HelpBuildmode = &base.Command{
UsageLine: "buildmode",
Short: "build modes",
Long: `
The 'go build' and 'go install' commands take a -buildmode argument which
indicates which kind of object file is to be built. Currently supported values
are:
-buildmode=archive
Build the listed non-main packages into .a files. Packages named
main are ignored.
-buildmode=c-archive
Build the listed main package, plus all packages it imports,
into a C archive file. The only callable symbols will be those
functions exported using a cgo //export comment. Requires
exactly one main package to be listed.
-buildmode=c-shared
Build the listed main package, plus all packages it imports,
into a C shared library. The only callable symbols will
be those functions exported using a cgo //export comment.
Requires exactly one main package to be listed.
-buildmode=default
Listed main packages are built into executables and listed
non-main packages are built into .a files (the default
behavior).
-buildmode=shared
Combine all the listed non-main packages into a single shared
library that will be used when building with the -linkshared
option. Packages named main are ignored.
-buildmode=exe
Build the listed main packages and everything they import into
executables. Packages not named main are ignored.
-buildmode=pie
Build the listed main packages and everything they import into
position independent executables (PIE). Packages not named
main are ignored.
-buildmode=plugin
Build the listed main packages, plus all packages that they
import, into a Go plugin. Packages not named main are ignored.
On AIX, when linking a C program that uses a Go archive built with
-buildmode=c-archive, you must pass -Wl,-bnoobjreorder to the C compiler.
`,
}
var HelpCache = &base.Command{
UsageLine: "cache",
Short: "build and test caching",
Long: `
The go command caches build outputs for reuse in future builds.
The default location for cache data is a subdirectory named go-build
in the standard user cache directory for the current operating system.
Setting the GOCACHE environment variable overrides this default,
and running 'go env GOCACHE' prints the current cache directory.
The go command periodically deletes cached data that has not been
used recently. Running 'go clean -cache' deletes all cached data.
The build cache correctly accounts for changes to Go source files,
compilers, compiler options, and so on: cleaning the cache explicitly
should not be necessary in typical use. However, the build cache
does not detect changes to C libraries imported with cgo.
If you have made changes to the C libraries on your system, you
will need to clean the cache explicitly or else use the -a build flag
(see 'go help build') to force rebuilding of packages that
depend on the updated C libraries.
The go command also caches successful package test results.
See 'go help test' for details. Running 'go clean -testcache' removes
all cached test results (but not cached build results).
The go command also caches values used in fuzzing with 'go test -fuzz',
specifically, values that expanded code coverage when passed to a
fuzz function. These values are not used for regular building and
testing, but they're stored in a subdirectory of the build cache.
Running 'go clean -fuzzcache' removes all cached fuzzing values.
This may make fuzzing less effective, temporarily.
The GODEBUG environment variable can enable printing of debugging
information about the state of the cache:
GODEBUG=gocacheverify=1 causes the go command to bypass the
use of any cache entries and instead rebuild everything and check
that the results match existing cache entries.
GODEBUG=gocachehash=1 causes the go command to print the inputs
for all of the content hashes it uses to construct cache lookup keys.
The output is voluminous but can be useful for debugging the cache.
GODEBUG=gocachetest=1 causes the go command to print details of its
decisions about whether to reuse a cached test result.
`,
}
var HelpBuildConstraint = &base.Command{
UsageLine: "buildconstraint",
Short: "build constraints",
Long: `
A build constraint, also known as a build tag, is a condition under which a
file should be included in the package. Build constraints are given by a
line comment that begins
//go:build
Build constraints can also be used to downgrade the language version
used to compile a file.
Constraints may appear in any kind of source file (not just Go), but
they must appear near the top of the file, preceded
only by blank lines and other comments. These rules mean that in Go
files a build constraint must appear before the package clause.
To distinguish build constraints from package documentation,
a build constraint should be followed by a blank line.
A build constraint comment is evaluated as an expression containing
build tags combined by ||, &&, and ! operators and parentheses.
Operators have the same meaning as in Go.
For example, the following build constraint constrains a file to
build when the "linux" and "386" constraints are satisfied, or when
"darwin" is satisfied and "cgo" is not:
//go:build (linux && 386) || (darwin && !cgo)
It is an error for a file to have more than one //go:build line.
During a particular build, the following build tags are satisfied:
- the target operating system, as spelled by runtime.GOOS, set with the
GOOS environment variable.
- the target architecture, as spelled by runtime.GOARCH, set with the
GOARCH environment variable.
- any architecture features, in the form GOARCH.feature
(for example, "amd64.v2"), as detailed below.
- "unix", if GOOS is a Unix or Unix-like system.
- the compiler being used, either "gc" or "gccgo"
- "cgo", if the cgo command is supported (see CGO_ENABLED in
'go help environment').
- a term for each Go major release, through the current version:
"go1.1" from Go version 1.1 onward, "go1.12" from Go 1.12, and so on.
- any additional tags given by the -tags flag (see 'go help build').
There are no separate build tags for beta or minor releases.
If a file's name, after stripping the extension and a possible _test suffix,
matches any of the following patterns:
*_GOOS
*_GOARCH
*_GOOS_GOARCH
(example: source_windows_amd64.go) where GOOS and GOARCH represent
any known operating system and architecture values respectively, then
the file is considered to have an implicit build constraint requiring
those terms (in addition to any explicit constraints in the file).
Using GOOS=android matches build tags and files as for GOOS=linux
in addition to android tags and files.
Using GOOS=illumos matches build tags and files as for GOOS=solaris
in addition to illumos tags and files.
Using GOOS=ios matches build tags and files as for GOOS=darwin
in addition to ios tags and files.
The defined architecture feature build tags are:
- For GOARCH=386, GO386=387 and GO386=sse2
set the 386.387 and 386.sse2 build tags, respectively.
- For GOARCH=amd64, GOAMD64=v1, v2, and v3
correspond to the amd64.v1, amd64.v2, and amd64.v3 feature build tags.
- For GOARCH=arm, GOARM=5, 6, and 7
correspond to the arm.5, arm.6, and arm.7 feature build tags.
- For GOARCH=arm64, GOARM64=v8.{0-9} and v9.{0-5}
correspond to the arm64.v8.{0-9} and arm64.v9.{0-5} feature build tags.
- For GOARCH=mips or mipsle,
GOMIPS=hardfloat and softfloat
correspond to the mips.hardfloat and mips.softfloat
(or mipsle.hardfloat and mipsle.softfloat) feature build tags.
- For GOARCH=mips64 or mips64le,
GOMIPS64=hardfloat and softfloat
correspond to the mips64.hardfloat and mips64.softfloat
(or mips64le.hardfloat and mips64le.softfloat) feature build tags.
- For GOARCH=ppc64 or ppc64le,
GOPPC64=power8, power9, and power10 correspond to the
ppc64.power8, ppc64.power9, and ppc64.power10
(or ppc64le.power8, ppc64le.power9, and ppc64le.power10)
feature build tags.
- For GOARCH=riscv64,
GORISCV64=rva20u64 and rva22u64 correspond to the riscv64.rva20u64
and riscv64.rva22u64 build tags.
- For GOARCH=wasm, GOWASM=satconv and signext
correspond to the wasm.satconv and wasm.signext feature build tags.
For GOARCH=amd64, arm, ppc64, ppc64le, and riscv64, a particular feature level
sets the feature build tags for all previous levels as well.
For example, GOAMD64=v2 sets the amd64.v1 and amd64.v2 feature flags.
This ensures that code making use of v2 features continues to compile
when, say, GOAMD64=v4 is introduced.
Code handling the absence of a particular feature level
should use a negation:
//go:build !amd64.v2
To keep a file from being considered for any build:
//go:build ignore
(Any other unsatisfied word will work as well, but "ignore" is conventional.)
To build a file only when using cgo, and only on Linux and OS X:
//go:build cgo && (linux || darwin)
Such a file is usually paired with another file implementing the
default functionality for other systems, which in this case would
carry the constraint:
//go:build !(cgo && (linux || darwin))
Naming a file dns_windows.go will cause it to be included only when
building the package for Windows; similarly, math_386.s will be included
only when building the package for 32-bit x86.
Go versions 1.16 and earlier used a different syntax for build constraints,
with a "// +build" prefix. The gofmt command will add an equivalent //go:build
constraint when encountering the older syntax.
In modules with a Go version of 1.21 or later, if a file's build constraint
has a term for a Go major release, the language version used when compiling
the file will be the minimum version implied by the build constraint.
`,
}

View File

@@ -0,0 +1,374 @@
// Copyright 2018 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.
// Copied from Go distribution src/go/build/build.go, syslist.go.
// That package does not export the ability to process raw file data,
// although we could fake it with an appropriate build.Context
// and a lot of unwrapping.
// More importantly, that package does not implement the tags["*"]
// special case, in which both tag and !tag are considered to be true
// for essentially all tags (except "ignore").
//
// If we added this API to go/build directly, we wouldn't need this
// file anymore, but this API is not terribly general-purpose and we
// don't really want to commit to any public form of it, nor do we
// want to move the core parts of go/build into a top-level internal package.
// These details change very infrequently, so the copy is fine.
package imports
import (
"bytes"
"cmd/go/internal/cfg"
"errors"
"fmt"
"go/build/constraint"
"strings"
"unicode"
)
var (
bSlashSlash = []byte("//")
bStarSlash = []byte("*/")
bSlashStar = []byte("/*")
bPlusBuild = []byte("+build")
goBuildComment = []byte("//go:build")
errMultipleGoBuild = errors.New("multiple //go:build comments")
)
func isGoBuildComment(line []byte) bool {
if !bytes.HasPrefix(line, goBuildComment) {
return false
}
line = bytes.TrimSpace(line)
rest := line[len(goBuildComment):]
return len(rest) == 0 || len(bytes.TrimSpace(rest)) < len(rest)
}
// ShouldBuild reports whether it is okay to use this file,
// The rule is that in the file's leading run of // comments
// and blank lines, which must be followed by a blank line
// (to avoid including a Go package clause doc comment),
// lines beginning with '// +build' are taken as build directives.
//
// The file is accepted only if each such line lists something
// matching the file. For example:
//
// // +build windows linux
//
// marks the file as applicable only on Windows and Linux.
//
// If tags["*"] is true, then ShouldBuild will consider every
// build tag except "ignore" to be both true and false for
// the purpose of satisfying build tags, in order to estimate
// (conservatively) whether a file could ever possibly be used
// in any build.
func ShouldBuild(content []byte, tags map[string]bool) bool {
// Identify leading run of // comments and blank lines,
// which must be followed by a blank line.
// Also identify any //go:build comments.
content, goBuild, _, err := parseFileHeader(content)
if err != nil {
return false
}
// If //go:build line is present, it controls.
// Otherwise fall back to +build processing.
var shouldBuild bool
switch {
case goBuild != nil:
x, err := constraint.Parse(string(goBuild))
if err != nil {
return false
}
shouldBuild = eval(x, tags, true)
default:
shouldBuild = true
p := content
for len(p) > 0 {
line := p
if i := bytes.IndexByte(line, '\n'); i >= 0 {
line, p = line[:i], p[i+1:]
} else {
p = p[len(p):]
}
line = bytes.TrimSpace(line)
if !bytes.HasPrefix(line, bSlashSlash) || !bytes.Contains(line, bPlusBuild) {
continue
}
text := string(line)
if !constraint.IsPlusBuild(text) {
continue
}
if x, err := constraint.Parse(text); err == nil {
if !eval(x, tags, true) {
shouldBuild = false
}
}
}
}
return shouldBuild
}
func parseFileHeader(content []byte) (trimmed, goBuild []byte, sawBinaryOnly bool, err error) {
end := 0
p := content
ended := false // found non-blank, non-// line, so stopped accepting // +build lines
inSlashStar := false // in /* */ comment
Lines:
for len(p) > 0 {
line := p
if i := bytes.IndexByte(line, '\n'); i >= 0 {
line, p = line[:i], p[i+1:]
} else {
p = p[len(p):]
}
line = bytes.TrimSpace(line)
if len(line) == 0 && !ended { // Blank line
// Remember position of most recent blank line.
// When we find the first non-blank, non-// line,
// this "end" position marks the latest file position
// where a // +build line can appear.
// (It must appear _before_ a blank line before the non-blank, non-// line.
// Yes, that's confusing, which is part of why we moved to //go:build lines.)
// Note that ended==false here means that inSlashStar==false,
// since seeing a /* would have set ended==true.
end = len(content) - len(p)
continue Lines
}
if !bytes.HasPrefix(line, bSlashSlash) { // Not comment line
ended = true
}
if !inSlashStar && isGoBuildComment(line) {
if goBuild != nil {
return nil, nil, false, errMultipleGoBuild
}
goBuild = line
}
Comments:
for len(line) > 0 {
if inSlashStar {
if i := bytes.Index(line, bStarSlash); i >= 0 {
inSlashStar = false
line = bytes.TrimSpace(line[i+len(bStarSlash):])
continue Comments
}
continue Lines
}
if bytes.HasPrefix(line, bSlashSlash) {
continue Lines
}
if bytes.HasPrefix(line, bSlashStar) {
inSlashStar = true
line = bytes.TrimSpace(line[len(bSlashStar):])
continue Comments
}
// Found non-comment text.
break Lines
}
}
return content[:end], goBuild, sawBinaryOnly, nil
}
// matchTag reports whether the tag name is valid and tags[name] is true.
// As a special case, if tags["*"] is true and name is not empty or ignore,
// then matchTag will return prefer instead of the actual answer,
// which allows the caller to pretend in that case that most tags are
// both true and false.
func matchTag(name string, tags map[string]bool, prefer bool) bool {
// Tags must be letters, digits, underscores or dots.
// Unlike in Go identifiers, all digits are fine (e.g., "386").
for _, c := range name {
if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' {
return false
}
}
if tags["*"] && name != "" && name != "ignore" {
// Special case for gathering all possible imports:
// if we put * in the tags map then all tags
// except "ignore" are considered both present and not
// (so we return true no matter how 'want' is set).
return prefer
}
if tags[name] {
return true
}
switch name {
case "linux":
return tags["android"]
case "solaris":
return tags["illumos"]
case "darwin":
return tags["ios"]
case "unix":
return unixOS[cfg.BuildContext.GOOS]
default:
return false
}
}
// eval is like
//
// x.Eval(func(tag string) bool { return matchTag(tag, tags) })
//
// except that it implements the special case for tags["*"] meaning
// all tags are both true and false at the same time.
func eval(x constraint.Expr, tags map[string]bool, prefer bool) bool {
switch x := x.(type) {
case *constraint.TagExpr:
return matchTag(x.Tag, tags, prefer)
case *constraint.NotExpr:
return !eval(x.X, tags, !prefer)
case *constraint.AndExpr:
return eval(x.X, tags, prefer) && eval(x.Y, tags, prefer)
case *constraint.OrExpr:
return eval(x.X, tags, prefer) || eval(x.Y, tags, prefer)
}
panic(fmt.Sprintf("unexpected constraint expression %T", x))
}
// Eval is like
//
// x.Eval(func(tag string) bool { return matchTag(tag, tags) })
//
// except that it implements the special case for tags["*"] meaning
// all tags are both true and false at the same time.
func Eval(x constraint.Expr, tags map[string]bool, prefer bool) bool {
return eval(x, tags, prefer)
}
// MatchFile returns false if the name contains a $GOOS or $GOARCH
// suffix which does not match the current system.
// The recognized name formats are:
//
// name_$(GOOS).*
// name_$(GOARCH).*
// name_$(GOOS)_$(GOARCH).*
// name_$(GOOS)_test.*
// name_$(GOARCH)_test.*
// name_$(GOOS)_$(GOARCH)_test.*
//
// Exceptions:
//
// if GOOS=android, then files with GOOS=linux are also matched.
// if GOOS=illumos, then files with GOOS=solaris are also matched.
// if GOOS=ios, then files with GOOS=darwin are also matched.
//
// If tags["*"] is true, then MatchFile will consider all possible
// GOOS and GOARCH to be available and will consequently
// always return true.
func MatchFile(name string, tags map[string]bool) bool {
if tags["*"] {
return true
}
if dot := strings.Index(name, "."); dot != -1 {
name = name[:dot]
}
// Before Go 1.4, a file called "linux.go" would be equivalent to having a
// build tag "linux" in that file. For Go 1.4 and beyond, we require this
// auto-tagging to apply only to files with a non-empty prefix, so
// "foo_linux.go" is tagged but "linux.go" is not. This allows new operating
// systems, such as android, to arrive without breaking existing code with
// innocuous source code in "android.go". The easiest fix: cut everything
// in the name before the initial _.
i := strings.Index(name, "_")
if i < 0 {
return true
}
name = name[i:] // ignore everything before first _
l := strings.Split(name, "_")
if n := len(l); n > 0 && l[n-1] == "test" {
l = l[:n-1]
}
n := len(l)
if n >= 2 && KnownOS[l[n-2]] && KnownArch[l[n-1]] {
return matchTag(l[n-2], tags, true) && matchTag(l[n-1], tags, true)
}
if n >= 1 && KnownOS[l[n-1]] {
return matchTag(l[n-1], tags, true)
}
if n >= 1 && KnownArch[l[n-1]] {
return matchTag(l[n-1], tags, true)
}
return true
}
var KnownOS = map[string]bool{
"aix": true,
"android": true,
"darwin": true,
"dragonfly": true,
"freebsd": true,
"hurd": true,
"illumos": true,
"ios": true,
"js": true,
"linux": true,
"nacl": true, // legacy; don't remove
"netbsd": true,
"openbsd": true,
"plan9": true,
"solaris": true,
"wasip1": true,
"windows": true,
"zos": true,
}
// unixOS is the set of GOOS values matched by the "unix" build tag.
// This is not used for filename matching.
// This is the same list as in go/build/syslist.go and cmd/dist/build.go.
var unixOS = map[string]bool{
"aix": true,
"android": true,
"darwin": true,
"dragonfly": true,
"freebsd": true,
"hurd": true,
"illumos": true,
"ios": true,
"linux": true,
"netbsd": true,
"openbsd": true,
"solaris": true,
}
var KnownArch = map[string]bool{
"386": true,
"amd64": true,
"amd64p32": true, // legacy; don't remove
"arm": true,
"armbe": true,
"arm64": true,
"arm64be": true,
"ppc64": true,
"ppc64le": true,
"mips": true,
"mipsle": true,
"mips64": true,
"mips64le": true,
"mips64p32": true,
"mips64p32le": true,
"loong64": true,
"ppc": true,
"riscv": true,
"riscv64": true,
"s390": true,
"s390x": true,
"sparc": true,
"sparc64": true,
"wasm": true,
}

View File

@@ -0,0 +1,263 @@
// Copyright 2012 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.
// Copied from Go distribution src/go/build/read.go.
package imports
import (
"bufio"
"bytes"
"errors"
"io"
"unicode/utf8"
)
type importReader struct {
b *bufio.Reader
buf []byte
peek byte
err error
eof bool
nerr int
}
var bom = []byte{0xef, 0xbb, 0xbf}
func newImportReader(b *bufio.Reader) *importReader {
// Remove leading UTF-8 BOM.
// Per https://golang.org/ref/spec#Source_code_representation:
// a compiler may ignore a UTF-8-encoded byte order mark (U+FEFF)
// if it is the first Unicode code point in the source text.
if leadingBytes, err := b.Peek(3); err == nil && bytes.Equal(leadingBytes, bom) {
b.Discard(3)
}
return &importReader{b: b}
}
func isIdent(c byte) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
}
var (
errSyntax = errors.New("syntax error")
errNUL = errors.New("unexpected NUL in input")
)
// syntaxError records a syntax error, but only if an I/O error has not already been recorded.
func (r *importReader) syntaxError() {
if r.err == nil {
r.err = errSyntax
}
}
// readByte reads the next byte from the input, saves it in buf, and returns it.
// If an error occurs, readByte records the error in r.err and returns 0.
func (r *importReader) readByte() byte {
c, err := r.b.ReadByte()
if err == nil {
r.buf = append(r.buf, c)
if c == 0 {
err = errNUL
}
}
if err != nil {
if err == io.EOF {
r.eof = true
} else if r.err == nil {
r.err = err
}
c = 0
}
return c
}
// peekByte returns the next byte from the input reader but does not advance beyond it.
// If skipSpace is set, peekByte skips leading spaces and comments.
func (r *importReader) peekByte(skipSpace bool) byte {
if r.err != nil {
if r.nerr++; r.nerr > 10000 {
panic("go/build: import reader looping")
}
return 0
}
// Use r.peek as first input byte.
// Don't just return r.peek here: it might have been left by peekByte(false)
// and this might be peekByte(true).
c := r.peek
if c == 0 {
c = r.readByte()
}
for r.err == nil && !r.eof {
if skipSpace {
// For the purposes of this reader, semicolons are never necessary to
// understand the input and are treated as spaces.
switch c {
case ' ', '\f', '\t', '\r', '\n', ';':
c = r.readByte()
continue
case '/':
c = r.readByte()
if c == '/' {
for c != '\n' && r.err == nil && !r.eof {
c = r.readByte()
}
} else if c == '*' {
var c1 byte
for (c != '*' || c1 != '/') && r.err == nil {
if r.eof {
r.syntaxError()
}
c, c1 = c1, r.readByte()
}
} else {
r.syntaxError()
}
c = r.readByte()
continue
}
}
break
}
r.peek = c
return r.peek
}
// nextByte is like peekByte but advances beyond the returned byte.
func (r *importReader) nextByte(skipSpace bool) byte {
c := r.peekByte(skipSpace)
r.peek = 0
return c
}
// readKeyword reads the given keyword from the input.
// If the keyword is not present, readKeyword records a syntax error.
func (r *importReader) readKeyword(kw string) {
r.peekByte(true)
for i := 0; i < len(kw); i++ {
if r.nextByte(false) != kw[i] {
r.syntaxError()
return
}
}
if isIdent(r.peekByte(false)) {
r.syntaxError()
}
}
// readIdent reads an identifier from the input.
// If an identifier is not present, readIdent records a syntax error.
func (r *importReader) readIdent() {
c := r.peekByte(true)
if !isIdent(c) {
r.syntaxError()
return
}
for isIdent(r.peekByte(false)) {
r.peek = 0
}
}
// readString reads a quoted string literal from the input.
// If an identifier is not present, readString records a syntax error.
func (r *importReader) readString(save *[]string) {
switch r.nextByte(true) {
case '`':
start := len(r.buf) - 1
for r.err == nil {
if r.nextByte(false) == '`' {
if save != nil {
*save = append(*save, string(r.buf[start:]))
}
break
}
if r.eof {
r.syntaxError()
}
}
case '"':
start := len(r.buf) - 1
for r.err == nil {
c := r.nextByte(false)
if c == '"' {
if save != nil {
*save = append(*save, string(r.buf[start:]))
}
break
}
if r.eof || c == '\n' {
r.syntaxError()
}
if c == '\\' {
r.nextByte(false)
}
}
default:
r.syntaxError()
}
}
// readImport reads an import clause - optional identifier followed by quoted string -
// from the input.
func (r *importReader) readImport(imports *[]string) {
c := r.peekByte(true)
if c == '.' {
r.peek = 0
} else if isIdent(c) {
r.readIdent()
}
r.readString(imports)
}
// ReadComments is like io.ReadAll, except that it only reads the leading
// block of comments in the file.
func ReadComments(f io.Reader) ([]byte, error) {
r := newImportReader(bufio.NewReader(f))
r.peekByte(true)
if r.err == nil && !r.eof {
// Didn't reach EOF, so must have found a non-space byte. Remove it.
r.buf = r.buf[:len(r.buf)-1]
}
return r.buf, r.err
}
// ReadImports is like io.ReadAll, except that it expects a Go file as input
// and stops reading the input once the imports have completed.
func ReadImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) {
r := newImportReader(bufio.NewReader(f))
r.readKeyword("package")
r.readIdent()
for r.peekByte(true) == 'i' {
r.readKeyword("import")
if r.peekByte(true) == '(' {
r.nextByte(false)
for r.peekByte(true) != ')' && r.err == nil {
r.readImport(imports)
}
r.nextByte(false)
} else {
r.readImport(imports)
}
}
// If we stopped successfully before EOF, we read a byte that told us we were done.
// Return all but that last byte, which would cause a syntax error if we let it through.
if r.err == nil && !r.eof {
return r.buf[:len(r.buf)-1], nil
}
// If we stopped for a syntax error, consume the whole file so that
// we are sure we don't change the errors that go/parser returns.
if r.err == errSyntax && !reportSyntaxError {
r.err = nil
for r.err == nil && !r.eof {
r.readByte()
}
}
return r.buf, r.err
}

View File

@@ -0,0 +1,254 @@
// Copyright 2012 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.
// Copied from Go distribution src/go/build/read.go.
package imports
import (
"io"
"strings"
"testing"
)
const quote = "`"
type readTest struct {
// Test input contains where readImports should stop.
in string
err string
}
var readImportsTests = []readTest{
{
`package p`,
"",
},
{
`package p; import "x"`,
"",
},
{
`package p; import . "x"`,
"",
},
{
`package p; import "x";var x = 1`,
"",
},
{
`package p
// comment
import "x"
import _ "x"
import a "x"
/* comment */
import (
"x" /* comment */
_ "x"
a "x" // comment
` + quote + `x` + quote + `
_ /*comment*/ ` + quote + `x` + quote + `
a ` + quote + `x` + quote + `
)
import (
)
import ()
import()import()import()
import();import();import()
var x = 1
`,
"",
},
{
"\ufeff𝔻" + `package p; import "x";var x = 1`,
"",
},
}
var readCommentsTests = []readTest{
{
`package p`,
"",
},
{
`package p; import "x"`,
"",
},
{
`package p; import . "x"`,
"",
},
{
"\ufeff𝔻" + `package p; import . "x"`,
"",
},
{
`// foo
/* bar */
/* quux */ // baz
/*/ zot */
// asdf
Hello, world`,
"",
},
{
"\ufeff𝔻" + `// foo
/* bar */
/* quux */ // baz
/*/ zot */
// asdf
Hello, world`,
"",
},
}
func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, error)) {
for i, tt := range tests {
var in, testOut string
j := strings.Index(tt.in, "")
if j < 0 {
in = tt.in
testOut = tt.in
} else {
in = tt.in[:j] + tt.in[j+len(""):]
testOut = tt.in[:j]
}
d := strings.Index(tt.in, "𝔻")
if d >= 0 {
in = in[:d] + in[d+len("𝔻"):]
testOut = testOut[d+len("𝔻"):]
}
r := strings.NewReader(in)
buf, err := read(r)
if err != nil {
if tt.err == "" {
t.Errorf("#%d: err=%q, expected success (%q)", i, err, string(buf))
continue
}
if !strings.Contains(err.Error(), tt.err) {
t.Errorf("#%d: err=%q, expected %q", i, err, tt.err)
continue
}
continue
}
if err == nil && tt.err != "" {
t.Errorf("#%d: success, expected %q", i, tt.err)
continue
}
out := string(buf)
if out != testOut {
t.Errorf("#%d: wrong output:\nhave %q\nwant %q\n", i, out, testOut)
}
}
}
func TestReadImports(t *testing.T) {
testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return ReadImports(r, true, nil) })
}
func TestReadComments(t *testing.T) {
testRead(t, readCommentsTests, ReadComments)
}
var readFailuresTests = []readTest{
{
`package`,
"syntax error",
},
{
"package p\n\x00\nimport `math`\n",
"unexpected NUL in input",
},
{
`package p; import`,
"syntax error",
},
{
`package p; import "`,
"syntax error",
},
{
"package p; import ` \n\n",
"syntax error",
},
{
`package p; import "x`,
"syntax error",
},
{
`package p; import _`,
"syntax error",
},
{
`package p; import _ "`,
"syntax error",
},
{
`package p; import _ "x`,
"syntax error",
},
{
`package p; import .`,
"syntax error",
},
{
`package p; import . "`,
"syntax error",
},
{
`package p; import . "x`,
"syntax error",
},
{
`package p; import (`,
"syntax error",
},
{
`package p; import ("`,
"syntax error",
},
{
`package p; import ("x`,
"syntax error",
},
{
`package p; import ("x"`,
"syntax error",
},
}
func TestReadFailures(t *testing.T) {
// Errors should be reported (true arg to readImports).
testRead(t, readFailuresTests, func(r io.Reader) ([]byte, error) { return ReadImports(r, true, nil) })
}
func TestReadFailuresIgnored(t *testing.T) {
// Syntax errors should not be reported (false arg to readImports).
// Instead, entire file should be the output and no error.
// Convert tests not to return syntax errors.
tests := make([]readTest, len(readFailuresTests))
copy(tests, readFailuresTests)
for i := range tests {
tt := &tests[i]
if !strings.Contains(tt.err, "NUL") {
tt.err = ""
}
}
testRead(t, tests, func(r io.Reader) ([]byte, error) { return ReadImports(r, false, nil) })
}

View File

@@ -0,0 +1,107 @@
// Copyright 2018 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 imports
import (
"fmt"
"io/fs"
"path/filepath"
"sort"
"strconv"
"strings"
"cmd/go/internal/fsys"
)
func ScanDir(dir string, tags map[string]bool) ([]string, []string, error) {
infos, err := fsys.ReadDir(dir)
if err != nil {
return nil, nil, err
}
var files []string
for _, info := range infos {
name := info.Name()
// If the directory entry is a symlink, stat it to obtain the info for the
// link target instead of the link itself.
if info.Mode()&fs.ModeSymlink != 0 {
info, err = fsys.Stat(filepath.Join(dir, name))
if err != nil {
continue // Ignore broken symlinks.
}
}
if info.Mode().IsRegular() && !strings.HasPrefix(name, "_") && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && MatchFile(name, tags) {
files = append(files, filepath.Join(dir, name))
}
}
return scanFiles(files, tags, false)
}
func ScanFiles(files []string, tags map[string]bool) ([]string, []string, error) {
return scanFiles(files, tags, true)
}
func scanFiles(files []string, tags map[string]bool, explicitFiles bool) ([]string, []string, error) {
imports := make(map[string]bool)
testImports := make(map[string]bool)
numFiles := 0
Files:
for _, name := range files {
r, err := fsys.Open(name)
if err != nil {
return nil, nil, err
}
var list []string
data, err := ReadImports(r, false, &list)
r.Close()
if err != nil {
return nil, nil, fmt.Errorf("reading %s: %v", name, err)
}
// import "C" is implicit requirement of cgo tag.
// When listing files on the command line (explicitFiles=true)
// we do not apply build tag filtering but we still do apply
// cgo filtering, so no explicitFiles check here.
// Why? Because we always have, and it's not worth breaking
// that behavior now.
for _, path := range list {
if path == `"C"` && !tags["cgo"] && !tags["*"] {
continue Files
}
}
if !explicitFiles && !ShouldBuild(data, tags) {
continue
}
numFiles++
m := imports
if strings.HasSuffix(name, "_test.go") {
m = testImports
}
for _, p := range list {
q, err := strconv.Unquote(p)
if err != nil {
continue
}
m[q] = true
}
}
if numFiles == 0 {
return nil, nil, ErrNoGo
}
return keys(imports), keys(testImports), nil
}
var ErrNoGo = fmt.Errorf("no Go source files")
func keys(m map[string]bool) []string {
var list []string
for k := range m {
list = append(list, k)
}
sort.Strings(list)
return list
}

View File

@@ -0,0 +1,93 @@
// Copyright 2018 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 imports
import (
"bytes"
"internal/testenv"
"os"
"path"
"path/filepath"
"strings"
"testing"
)
func TestScan(t *testing.T) {
testenv.MustHaveGoBuild(t)
imports, testImports, err := ScanDir(filepath.Join(testenv.GOROOT(t), "src/encoding/json"), Tags())
if err != nil {
t.Fatal(err)
}
foundBase64 := false
for _, p := range imports {
if p == "encoding/base64" {
foundBase64 = true
}
if p == "encoding/binary" {
// A dependency but not an import
t.Errorf("json reported as importing encoding/binary but does not")
}
if p == "net/http" {
// A test import but not an import
t.Errorf("json reported as importing net/http but does not")
}
}
if !foundBase64 {
t.Errorf("json missing import encoding/base64 (%q)", imports)
}
foundHTTP := false
for _, p := range testImports {
if p == "net/http" {
foundHTTP = true
}
if p == "unicode/utf16" {
// A package import but not a test import
t.Errorf("json reported as test-importing unicode/utf16 but does not")
}
}
if !foundHTTP {
t.Errorf("json missing test import net/http (%q)", testImports)
}
}
func TestScanDir(t *testing.T) {
testenv.MustHaveGoBuild(t)
dirs, err := os.ReadDir("testdata")
if err != nil {
t.Fatal(err)
}
for _, dir := range dirs {
if !dir.IsDir() || strings.HasPrefix(dir.Name(), ".") {
continue
}
t.Run(dir.Name(), func(t *testing.T) {
tagsData, err := os.ReadFile(filepath.Join("testdata", dir.Name(), "tags.txt"))
if err != nil {
t.Fatalf("error reading tags: %v", err)
}
tags := make(map[string]bool)
for _, t := range strings.Fields(string(tagsData)) {
tags[t] = true
}
wantData, err := os.ReadFile(filepath.Join("testdata", dir.Name(), "want.txt"))
if err != nil {
t.Fatalf("error reading want: %v", err)
}
want := string(bytes.TrimSpace(wantData))
imports, _, err := ScanDir(path.Join("testdata", dir.Name()), tags)
if err != nil {
t.Fatal(err)
}
got := strings.Join(imports, "\n")
if got != want {
t.Errorf("ScanDir: got imports:\n%s\n\nwant:\n%s", got, want)
}
})
}
}

View File

@@ -0,0 +1,61 @@
// Copyright 2018 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 imports
import (
"cmd/go/internal/cfg"
"sync"
)
var (
tags map[string]bool
tagsOnce sync.Once
)
// Tags returns a set of build tags that are true for the target platform.
// It includes GOOS, GOARCH, the compiler, possibly "cgo",
// release tags like "go1.13", and user-specified build tags.
func Tags() map[string]bool {
tagsOnce.Do(func() {
tags = loadTags()
})
return tags
}
func loadTags() map[string]bool {
tags := map[string]bool{
cfg.BuildContext.GOOS: true,
cfg.BuildContext.GOARCH: true,
cfg.BuildContext.Compiler: true,
}
if cfg.BuildContext.CgoEnabled {
tags["cgo"] = true
}
for _, tag := range cfg.BuildContext.BuildTags {
tags[tag] = true
}
for _, tag := range cfg.BuildContext.ToolTags {
tags[tag] = true
}
for _, tag := range cfg.BuildContext.ReleaseTags {
tags[tag] = true
}
return tags
}
var (
anyTags map[string]bool
anyTagsOnce sync.Once
)
// AnyTags returns a special set of build tags that satisfy nearly all
// build tag expressions. Only "ignore" and malformed build tag requirements
// are considered false.
func AnyTags() map[string]bool {
anyTagsOnce.Do(func() {
anyTags = map[string]bool{"*": true}
})
return anyTags
}

View File

@@ -0,0 +1,3 @@
package android
import _ "h"

View File

@@ -0,0 +1,3 @@
package android
import _ "a"

View File

@@ -0,0 +1,3 @@
package android
import _ "b"

View File

@@ -0,0 +1,3 @@
package android
import _ "c"

View File

@@ -0,0 +1,3 @@
package android
import _ "d"

View File

@@ -0,0 +1,6 @@
//go:build android
// +build android
package android
import _ "e"

View File

@@ -0,0 +1,6 @@
//go:build linux
// +build linux
package android
import _ "f"

View File

@@ -0,0 +1,6 @@
//go:build !android
// +build !android
package android
import _ "g"

View File

@@ -0,0 +1 @@
android arm64

View File

@@ -0,0 +1,6 @@
a
b
c
d
e
f

View File

@@ -0,0 +1,3 @@
package android
import _ "h"

View File

@@ -0,0 +1,3 @@
package illumos
import _ "a"

View File

@@ -0,0 +1,3 @@
package illumos
import _ "b"

View File

@@ -0,0 +1,3 @@
package illumos
import _ "c"

View File

@@ -0,0 +1,3 @@
package illumos
import _ "d"

View File

@@ -0,0 +1,6 @@
//go:build illumos
// +build illumos
package illumos
import _ "e"

View File

@@ -0,0 +1,6 @@
//go:build solaris
// +build solaris
package illumos
import _ "f"

View File

@@ -0,0 +1,6 @@
//go:build !illumos
// +build !illumos
package illumos
import _ "g"

View File

@@ -0,0 +1 @@
illumos amd64

View File

@@ -0,0 +1,6 @@
a
b
c
d
e
f

View File

@@ -0,0 +1 @@
*

View File

@@ -0,0 +1,4 @@
import1
import2
import3
import4

View File

@@ -0,0 +1,3 @@
package x
import "import1"

View File

@@ -0,0 +1,6 @@
//go:build blahblh && linux && !linux && windows && darwin
// +build blahblh,linux,!linux,windows,darwin
package x
import "import4"

View File

@@ -0,0 +1,3 @@
package xxxx
import "import3"

View File

@@ -0,0 +1,3 @@
package x
import "import2"

View File

@@ -0,0 +1,39 @@
// Copyright 2014 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 list
import (
"go/build"
)
type Context struct {
GOARCH string `json:",omitempty"` // target architecture
GOOS string `json:",omitempty"` // target operating system
GOROOT string `json:",omitempty"` // Go root
GOPATH string `json:",omitempty"` // Go path
CgoEnabled bool `json:",omitempty"` // whether cgo can be used
UseAllFiles bool `json:",omitempty"` // use files regardless of //go:build lines, file names
Compiler string `json:",omitempty"` // compiler to assume when computing target paths
BuildTags []string `json:",omitempty"` // build constraints to match in +build lines
ToolTags []string `json:",omitempty"` // toolchain-specific build constraints
ReleaseTags []string `json:",omitempty"` // releases the current release is compatible with
InstallSuffix string `json:",omitempty"` // suffix to use in the name of the install dir
}
func newContext(c *build.Context) *Context {
return &Context{
GOARCH: c.GOARCH,
GOOS: c.GOOS,
GOROOT: c.GOROOT,
GOPATH: c.GOPATH,
CgoEnabled: c.CgoEnabled,
UseAllFiles: c.UseAllFiles,
Compiler: c.Compiler,
BuildTags: c.BuildTags,
ToolTags: c.ToolTags,
ReleaseTags: c.ReleaseTags,
InstallSuffix: c.InstallSuffix,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,96 @@
// Copyright 2017 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 load
import (
"cmd/go/internal/base"
"cmd/internal/quoted"
"fmt"
"strings"
)
var (
BuildAsmflags PerPackageFlag // -asmflags
BuildGcflags PerPackageFlag // -gcflags
BuildLdflags PerPackageFlag // -ldflags
BuildGccgoflags PerPackageFlag // -gccgoflags
)
// A PerPackageFlag is a command-line flag implementation (a flag.Value)
// that allows specifying different effective flags for different packages.
// See 'go help build' for more details about per-package flags.
type PerPackageFlag struct {
raw string
present bool
values []ppfValue
}
// A ppfValue is a single <pattern>=<flags> per-package flag value.
type ppfValue struct {
match func(*Package) bool // compiled pattern
flags []string
}
// Set is called each time the flag is encountered on the command line.
func (f *PerPackageFlag) Set(v string) error {
return f.set(v, base.Cwd())
}
// set is the implementation of Set, taking a cwd (current working directory) for easier testing.
func (f *PerPackageFlag) set(v, cwd string) error {
f.raw = v
f.present = true
match := func(p *Package) bool { return p.Internal.CmdlinePkg || p.Internal.CmdlineFiles } // default predicate with no pattern
// For backwards compatibility with earlier flag splitting, ignore spaces around flags.
v = strings.TrimSpace(v)
if v == "" {
// Special case: -gcflags="" means no flags for command-line arguments
// (overrides previous -gcflags="-whatever").
f.values = append(f.values, ppfValue{match, []string{}})
return nil
}
if !strings.HasPrefix(v, "-") {
i := strings.Index(v, "=")
if i < 0 {
return fmt.Errorf("missing =<value> in <pattern>=<value>")
}
if i == 0 {
return fmt.Errorf("missing <pattern> in <pattern>=<value>")
}
if v[0] == '\'' || v[0] == '"' {
return fmt.Errorf("parameter may not start with quote character %c", v[0])
}
pattern := strings.TrimSpace(v[:i])
match = MatchPackage(pattern, cwd)
v = v[i+1:]
}
flags, err := quoted.Split(v)
if err != nil {
return err
}
if flags == nil {
flags = []string{}
}
f.values = append(f.values, ppfValue{match, flags})
return nil
}
func (f *PerPackageFlag) String() string { return f.raw }
// Present reports whether the flag appeared on the command line.
func (f *PerPackageFlag) Present() bool {
return f.present
}
// For returns the flags to use for the given package.
func (f *PerPackageFlag) For(p *Package) []string {
flags := []string{}
for _, v := range f.values {
if v.match(p) {
flags = v.flags
}
}
return flags
}

View File

@@ -0,0 +1,135 @@
// Copyright 2017 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 load
import (
"fmt"
"path/filepath"
"reflect"
"testing"
)
type ppfTestPackage struct {
path string
dir string
cmdline bool
flags []string
}
type ppfTest struct {
args []string
pkgs []ppfTestPackage
}
var ppfTests = []ppfTest{
// -gcflags=-S applies only to packages on command line.
{
args: []string{"-S"},
pkgs: []ppfTestPackage{
{cmdline: true, flags: []string{"-S"}},
{cmdline: false, flags: []string{}},
},
},
// -gcflags=-S -gcflags= overrides the earlier -S.
{
args: []string{"-S", ""},
pkgs: []ppfTestPackage{
{cmdline: true, flags: []string{}},
},
},
// -gcflags=net=-S applies only to package net
{
args: []string{"net=-S"},
pkgs: []ppfTestPackage{
{path: "math", cmdline: true, flags: []string{}},
{path: "net", flags: []string{"-S"}},
},
},
// -gcflags=net=-S -gcflags=net= also overrides the earlier -S
{
args: []string{"net=-S", "net="},
pkgs: []ppfTestPackage{
{path: "net", flags: []string{}},
},
},
// -gcflags=net/...=-S net math
// applies -S to net and net/http but not math
{
args: []string{"net/...=-S"},
pkgs: []ppfTestPackage{
{path: "net", flags: []string{"-S"}},
{path: "net/http", flags: []string{"-S"}},
{path: "math", flags: []string{}},
},
},
// -gcflags=net/...=-S -gcflags=-m net math
// applies -m to net and math and -S to other packages matching net/...
// (net matches too, but it was grabbed by the later -gcflags).
{
args: []string{"net/...=-S", "-m"},
pkgs: []ppfTestPackage{
{path: "net", cmdline: true, flags: []string{"-m"}},
{path: "math", cmdline: true, flags: []string{"-m"}},
{path: "net", cmdline: false, flags: []string{"-S"}},
{path: "net/http", flags: []string{"-S"}},
{path: "math", flags: []string{}},
},
},
// relative path patterns
// ppfDirTest(pattern, n, dirs...) says the first n dirs should match and the others should not.
ppfDirTest(".", 1, "/my/test/dir", "/my/test", "/my/test/other", "/my/test/dir/sub"),
ppfDirTest("..", 1, "/my/test", "/my/test/dir", "/my/test/other", "/my/test/dir/sub"),
ppfDirTest("./sub", 1, "/my/test/dir/sub", "/my/test", "/my/test/dir", "/my/test/other", "/my/test/dir/sub/sub"),
ppfDirTest("../other", 1, "/my/test/other", "/my/test", "/my/test/dir", "/my/test/other/sub", "/my/test/dir/other", "/my/test/dir/sub"),
ppfDirTest("./...", 3, "/my/test/dir", "/my/test/dir/sub", "/my/test/dir/sub/sub", "/my/test/other", "/my/test/other/sub"),
ppfDirTest("../...", 4, "/my/test/dir", "/my/test/other", "/my/test/dir/sub", "/my/test/other/sub", "/my/other/test"),
ppfDirTest("../...sub...", 3, "/my/test/dir/sub", "/my/test/othersub", "/my/test/yellowsubmarine", "/my/other/test"),
}
func ppfDirTest(pattern string, nmatch int, dirs ...string) ppfTest {
var pkgs []ppfTestPackage
for i, d := range dirs {
flags := []string{}
if i < nmatch {
flags = []string{"-S"}
}
pkgs = append(pkgs, ppfTestPackage{path: "p", dir: d, flags: flags})
}
return ppfTest{args: []string{pattern + "=-S"}, pkgs: pkgs}
}
func TestPerPackageFlag(t *testing.T) {
nativeDir := func(d string) string {
if filepath.Separator == '\\' {
return `C:` + filepath.FromSlash(d)
}
return d
}
for i, tt := range ppfTests {
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
ppFlags := new(PerPackageFlag)
for _, arg := range tt.args {
t.Logf("set(%s)", arg)
if err := ppFlags.set(arg, nativeDir("/my/test/dir")); err != nil {
t.Fatal(err)
}
}
for _, p := range tt.pkgs {
dir := nativeDir(p.dir)
flags := ppFlags.For(&Package{PackagePublic: PackagePublic{ImportPath: p.path, Dir: dir}, Internal: PackageInternal{CmdlinePkg: p.cmdline}})
if !reflect.DeepEqual(flags, p.flags) {
t.Errorf("For(%v, %v, %v) = %v, want %v", p.path, dir, p.cmdline, flags, p.flags)
}
}
})
}
}

View File

@@ -0,0 +1,137 @@
// Copyright 2023 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 load
import (
"errors"
"fmt"
"go/build"
"internal/godebugs"
"sort"
"strconv"
"strings"
"cmd/go/internal/gover"
"cmd/go/internal/modload"
)
var ErrNotGoDebug = errors.New("not //go:debug line")
func ParseGoDebug(text string) (key, value string, err error) {
if !strings.HasPrefix(text, "//go:debug") {
return "", "", ErrNotGoDebug
}
i := strings.IndexAny(text, " \t")
if i < 0 {
if strings.TrimSpace(text) == "//go:debug" {
return "", "", fmt.Errorf("missing key=value")
}
return "", "", ErrNotGoDebug
}
k, v, ok := strings.Cut(strings.TrimSpace(text[i:]), "=")
if !ok {
return "", "", fmt.Errorf("missing key=value")
}
if err := modload.CheckGodebug("//go:debug setting", k, v); err != nil {
return "", "", err
}
return k, v, nil
}
// defaultGODEBUG returns the default GODEBUG setting for the main package p.
// When building a test binary, directives, testDirectives, and xtestDirectives
// list additional directives from the package under test.
func defaultGODEBUG(p *Package, directives, testDirectives, xtestDirectives []build.Directive) string {
if p.Name != "main" {
return ""
}
goVersion := modload.MainModules.GoVersion()
if modload.RootMode == modload.NoRoot && p.Module != nil {
// This is go install pkg@version or go run pkg@version.
// Use the Go version from the package.
// If there isn't one, then assume Go 1.20,
// the last version before GODEBUGs were introduced.
goVersion = p.Module.GoVersion
if goVersion == "" {
goVersion = "1.20"
}
}
var m map[string]string
for _, g := range modload.MainModules.Godebugs() {
if m == nil {
m = make(map[string]string)
}
m[g.Key] = g.Value
}
for _, list := range [][]build.Directive{p.Internal.Build.Directives, directives, testDirectives, xtestDirectives} {
for _, d := range list {
k, v, err := ParseGoDebug(d.Text)
if err != nil {
continue
}
if m == nil {
m = make(map[string]string)
}
m[k] = v
}
}
if v, ok := m["default"]; ok {
delete(m, "default")
v = strings.TrimPrefix(v, "go")
if gover.IsValid(v) {
goVersion = v
}
}
defaults := godebugForGoVersion(goVersion)
if defaults != nil {
// Apply m on top of defaults.
for k, v := range m {
defaults[k] = v
}
m = defaults
}
var keys []string
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
var b strings.Builder
for _, k := range keys {
if b.Len() > 0 {
b.WriteString(",")
}
b.WriteString(k)
b.WriteString("=")
b.WriteString(m[k])
}
return b.String()
}
func godebugForGoVersion(v string) map[string]string {
if strings.Count(v, ".") >= 2 {
i := strings.Index(v, ".")
j := i + 1 + strings.Index(v[i+1:], ".")
v = v[:j]
}
if !strings.HasPrefix(v, "1.") {
return nil
}
n, err := strconv.Atoi(v[len("1."):])
if err != nil {
return nil
}
def := make(map[string]string)
for _, info := range godebugs.All {
if n < info.Changed {
def[info.Name] = info.Old
}
}
return def
}

View File

@@ -0,0 +1,18 @@
// Copyright 2017 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 load
import (
"path/filepath"
)
// expandPath returns the symlink-expanded form of path.
func expandPath(p string) string {
x, err := filepath.EvalSymlinks(p)
if err == nil {
return x
}
return p
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
// Copyright 2019 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 load
import (
"cmd/go/internal/cfg"
"testing"
)
func TestPkgDefaultExecName(t *testing.T) {
oldModulesEnabled := cfg.ModulesEnabled
defer func() { cfg.ModulesEnabled = oldModulesEnabled }()
for _, tt := range []struct {
in string
files []string
wantMod string
wantGopath string
}{
{"example.com/mycmd", []string{}, "mycmd", "mycmd"},
{"example.com/mycmd/v0", []string{}, "v0", "v0"},
{"example.com/mycmd/v1", []string{}, "v1", "v1"},
{"example.com/mycmd/v2", []string{}, "mycmd", "v2"}, // Semantic import versioning, use second last element in module mode.
{"example.com/mycmd/v3", []string{}, "mycmd", "v3"}, // Semantic import versioning, use second last element in module mode.
{"mycmd", []string{}, "mycmd", "mycmd"},
{"mycmd/v0", []string{}, "v0", "v0"},
{"mycmd/v1", []string{}, "v1", "v1"},
{"mycmd/v2", []string{}, "mycmd", "v2"}, // Semantic import versioning, use second last element in module mode.
{"v0", []string{}, "v0", "v0"},
{"v1", []string{}, "v1", "v1"},
{"v2", []string{}, "v2", "v2"},
{"command-line-arguments", []string{"output.go", "foo.go"}, "output", "output"},
} {
{
cfg.ModulesEnabled = true
pkg := new(Package)
pkg.ImportPath = tt.in
pkg.GoFiles = tt.files
pkg.Internal.CmdlineFiles = len(tt.files) > 0
gotMod := pkg.DefaultExecName()
if gotMod != tt.wantMod {
t.Errorf("pkg.DefaultExecName with ImportPath = %q in module mode = %v; want %v", tt.in, gotMod, tt.wantMod)
}
}
{
cfg.ModulesEnabled = false
pkg := new(Package)
pkg.ImportPath = tt.in
pkg.GoFiles = tt.files
pkg.Internal.CmdlineFiles = len(tt.files) > 0
gotGopath := pkg.DefaultExecName()
if gotGopath != tt.wantGopath {
t.Errorf("pkg.DefaultExecName with ImportPath = %q in gopath mode = %v; want %v", tt.in, gotGopath, tt.wantGopath)
}
}
}
}
func TestIsVersionElement(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
in string
want bool
}{
{"v0", false},
{"v05", false},
{"v1", false},
{"v2", true},
{"v3", true},
{"v9", true},
{"v10", true},
{"v11", true},
{"v", false},
{"vx", false},
} {
got := isVersionElement(tt.in)
if got != tt.want {
t.Errorf("isVersionElement(%q) = %v; want %v", tt.in, got, tt.want)
}
}
}

View File

@@ -0,0 +1,57 @@
// Copyright 2017 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 load
import (
"path/filepath"
"strings"
"cmd/go/internal/search"
"cmd/internal/pkgpattern"
)
// MatchPackage(pattern, cwd)(p) reports whether package p matches pattern in the working directory cwd.
func MatchPackage(pattern, cwd string) func(*Package) bool {
switch {
case search.IsRelativePath(pattern):
// Split pattern into leading pattern-free directory path
// (including all . and .. elements) and the final pattern.
var dir string
i := strings.Index(pattern, "...")
if i < 0 {
dir, pattern = pattern, ""
} else {
j := strings.LastIndex(pattern[:i], "/")
dir, pattern = pattern[:j], pattern[j+1:]
}
dir = filepath.Join(cwd, dir)
if pattern == "" {
return func(p *Package) bool { return p.Dir == dir }
}
matchPath := pkgpattern.MatchPattern(pattern)
return func(p *Package) bool {
// Compute relative path to dir and see if it matches the pattern.
rel, err := filepath.Rel(dir, p.Dir)
if err != nil {
// Cannot make relative - e.g. different drive letters on Windows.
return false
}
rel = filepath.ToSlash(rel)
if rel == ".." || strings.HasPrefix(rel, "../") {
return false
}
return matchPath(rel)
}
case pattern == "all":
return func(p *Package) bool { return true }
case pattern == "std":
return func(p *Package) bool { return p.Standard }
case pattern == "cmd":
return func(p *Package) bool { return p.Standard && strings.HasPrefix(p.ImportPath, "cmd/") }
default:
matchPath := pkgpattern.MatchPattern(pattern)
return func(p *Package) bool { return matchPath(p.ImportPath) }
}
}

View File

@@ -0,0 +1,988 @@
// Copyright 2018 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 load
import (
"bytes"
"context"
"errors"
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/parser"
"go/token"
"internal/lazytemplate"
"path/filepath"
"slices"
"sort"
"strings"
"unicode"
"unicode/utf8"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/str"
"cmd/go/internal/trace"
)
var TestMainDeps = []string{
// Dependencies for testmain.
"os",
"reflect",
"testing",
"testing/internal/testdeps",
}
type TestCover struct {
Mode string
Local bool
Pkgs []*Package
Paths []string
Vars []coverInfo
}
// TestPackagesFor is like TestPackagesAndErrors but it returns
// an error if the test packages or their dependencies have errors.
// Only test packages without errors are returned.
func TestPackagesFor(ctx context.Context, opts PackageOpts, p *Package, cover *TestCover) (pmain, ptest, pxtest *Package, err error) {
pmain, ptest, pxtest = TestPackagesAndErrors(ctx, nil, opts, p, cover)
for _, p1 := range []*Package{ptest, pxtest, pmain} {
if p1 == nil {
// pxtest may be nil
continue
}
if p1.Error != nil {
err = p1.Error
break
}
if p1.Incomplete {
ps := PackageList([]*Package{p1})
for _, p := range ps {
if p.Error != nil {
err = p.Error
break
}
}
break
}
}
if pmain.Error != nil || pmain.Incomplete {
pmain = nil
}
if ptest.Error != nil || ptest.Incomplete {
ptest = nil
}
if pxtest != nil && (pxtest.Error != nil || pxtest.Incomplete) {
pxtest = nil
}
return pmain, ptest, pxtest, err
}
// TestPackagesAndErrors returns three packages:
// - pmain, the package main corresponding to the test binary (running tests in ptest and pxtest).
// - ptest, the package p compiled with added "package p" test files.
// - pxtest, the result of compiling any "package p_test" (external) test files.
//
// If the package has no "package p_test" test files, pxtest will be nil.
// If the non-test compilation of package p can be reused
// (for example, if there are no "package p" test files and
// package p need not be instrumented for coverage or any other reason),
// then the returned ptest == p.
//
// If done is non-nil, TestPackagesAndErrors will finish filling out the returned
// package structs in a goroutine and call done once finished. The members of the
// returned packages should not be accessed until done is called.
//
// The caller is expected to have checked that len(p.TestGoFiles)+len(p.XTestGoFiles) > 0,
// or else there's no point in any of this.
func TestPackagesAndErrors(ctx context.Context, done func(), opts PackageOpts, p *Package, cover *TestCover) (pmain, ptest, pxtest *Package) {
ctx, span := trace.StartSpan(ctx, "load.TestPackagesAndErrors")
defer span.Done()
pre := newPreload()
defer pre.flush()
allImports := append([]string{}, p.TestImports...)
allImports = append(allImports, p.XTestImports...)
pre.preloadImports(ctx, opts, allImports, p.Internal.Build)
var ptestErr, pxtestErr *PackageError
var imports, ximports []*Package
var stk ImportStack
var testEmbed, xtestEmbed map[string][]string
var incomplete bool
stk.Push(p.ImportPath + " (test)")
rawTestImports := str.StringList(p.TestImports)
for i, path := range p.TestImports {
p1, err := loadImport(ctx, opts, pre, path, p.Dir, p, &stk, p.Internal.Build.TestImportPos[path], ResolveImport)
if err != nil && ptestErr == nil {
ptestErr = err
incomplete = true
}
if p1.Incomplete {
incomplete = true
}
p.TestImports[i] = p1.ImportPath
imports = append(imports, p1)
}
var err error
p.TestEmbedFiles, testEmbed, err = resolveEmbed(p.Dir, p.TestEmbedPatterns)
if err != nil {
ptestErr = &PackageError{
ImportStack: stk.Copy(),
Err: err,
}
incomplete = true
embedErr := err.(*EmbedError)
ptestErr.setPos(p.Internal.Build.TestEmbedPatternPos[embedErr.Pattern])
}
stk.Pop()
stk.Push(p.ImportPath + "_test")
pxtestNeedsPtest := false
var pxtestIncomplete bool
rawXTestImports := str.StringList(p.XTestImports)
for i, path := range p.XTestImports {
p1, err := loadImport(ctx, opts, pre, path, p.Dir, p, &stk, p.Internal.Build.XTestImportPos[path], ResolveImport)
if err != nil && pxtestErr == nil {
pxtestErr = err
}
if p1.Incomplete {
pxtestIncomplete = true
}
if p1.ImportPath == p.ImportPath {
pxtestNeedsPtest = true
} else {
ximports = append(ximports, p1)
}
p.XTestImports[i] = p1.ImportPath
}
p.XTestEmbedFiles, xtestEmbed, err = resolveEmbed(p.Dir, p.XTestEmbedPatterns)
if err != nil && pxtestErr == nil {
pxtestErr = &PackageError{
ImportStack: stk.Copy(),
Err: err,
}
embedErr := err.(*EmbedError)
pxtestErr.setPos(p.Internal.Build.XTestEmbedPatternPos[embedErr.Pattern])
}
pxtestIncomplete = pxtestIncomplete || pxtestErr != nil
stk.Pop()
// Test package.
if len(p.TestGoFiles) > 0 || p.Name == "main" || cover != nil && cover.Local {
ptest = new(Package)
*ptest = *p
ptest.Error = ptestErr
ptest.Incomplete = incomplete
ptest.ForTest = p.ImportPath
ptest.GoFiles = nil
ptest.GoFiles = append(ptest.GoFiles, p.GoFiles...)
ptest.GoFiles = append(ptest.GoFiles, p.TestGoFiles...)
ptest.Target = ""
// Note: The preparation of the vet config requires that common
// indexes in ptest.Imports and ptest.Internal.RawImports
// all line up (but RawImports can be shorter than the others).
// That is, for 0 ≤ i < len(RawImports),
// RawImports[i] is the import string in the program text, and
// Imports[i] is the expanded import string (vendoring applied or relative path expanded away).
// Any implicitly added imports appear in Imports and Internal.Imports
// but not RawImports (because they were not in the source code).
// We insert TestImports, imports, and rawTestImports at the start of
// these lists to preserve the alignment.
// Note that p.Internal.Imports may not be aligned with p.Imports/p.Internal.RawImports,
// but we insert at the beginning there too just for consistency.
ptest.Imports = str.StringList(p.TestImports, p.Imports)
ptest.Internal.Imports = append(imports, p.Internal.Imports...)
ptest.Internal.RawImports = str.StringList(rawTestImports, p.Internal.RawImports)
ptest.Internal.ForceLibrary = true
ptest.Internal.BuildInfo = nil
ptest.Internal.Build = new(build.Package)
*ptest.Internal.Build = *p.Internal.Build
m := map[string][]token.Position{}
for k, v := range p.Internal.Build.ImportPos {
m[k] = append(m[k], v...)
}
for k, v := range p.Internal.Build.TestImportPos {
m[k] = append(m[k], v...)
}
ptest.Internal.Build.ImportPos = m
if testEmbed == nil && len(p.Internal.Embed) > 0 {
testEmbed = map[string][]string{}
}
for k, v := range p.Internal.Embed {
testEmbed[k] = v
}
ptest.Internal.Embed = testEmbed
ptest.EmbedFiles = str.StringList(p.EmbedFiles, p.TestEmbedFiles)
ptest.Internal.OrigImportPath = p.Internal.OrigImportPath
ptest.Internal.PGOProfile = p.Internal.PGOProfile
ptest.Internal.Build.Directives = append(slices.Clip(p.Internal.Build.Directives), p.Internal.Build.TestDirectives...)
} else {
ptest = p
}
// External test package.
if len(p.XTestGoFiles) > 0 {
pxtest = &Package{
PackagePublic: PackagePublic{
Name: p.Name + "_test",
ImportPath: p.ImportPath + "_test",
Root: p.Root,
Dir: p.Dir,
Goroot: p.Goroot,
GoFiles: p.XTestGoFiles,
Imports: p.XTestImports,
ForTest: p.ImportPath,
Module: p.Module,
Error: pxtestErr,
Incomplete: pxtestIncomplete,
EmbedFiles: p.XTestEmbedFiles,
},
Internal: PackageInternal{
LocalPrefix: p.Internal.LocalPrefix,
Build: &build.Package{
ImportPos: p.Internal.Build.XTestImportPos,
Directives: p.Internal.Build.XTestDirectives,
},
Imports: ximports,
RawImports: rawXTestImports,
Asmflags: p.Internal.Asmflags,
Gcflags: p.Internal.Gcflags,
Ldflags: p.Internal.Ldflags,
Gccgoflags: p.Internal.Gccgoflags,
Embed: xtestEmbed,
OrigImportPath: p.Internal.OrigImportPath,
PGOProfile: p.Internal.PGOProfile,
},
}
if pxtestNeedsPtest {
pxtest.Internal.Imports = append(pxtest.Internal.Imports, ptest)
}
}
// Arrange for testing.Testing to report true.
ldflags := append(p.Internal.Ldflags, "-X", "testing.testBinary=1")
gccgoflags := append(p.Internal.Gccgoflags, "-Wl,--defsym,testing.gccgoTestBinary=1")
// Build main package.
pmain = &Package{
PackagePublic: PackagePublic{
Name: "main",
Dir: p.Dir,
GoFiles: []string{"_testmain.go"},
ImportPath: p.ImportPath + ".test",
Root: p.Root,
Imports: str.StringList(TestMainDeps),
Module: p.Module,
},
Internal: PackageInternal{
Build: &build.Package{Name: "main"},
BuildInfo: p.Internal.BuildInfo,
Asmflags: p.Internal.Asmflags,
Gcflags: p.Internal.Gcflags,
Ldflags: ldflags,
Gccgoflags: gccgoflags,
OrigImportPath: p.Internal.OrigImportPath,
PGOProfile: p.Internal.PGOProfile,
},
}
pb := p.Internal.Build
pmain.DefaultGODEBUG = defaultGODEBUG(pmain, pb.Directives, pb.TestDirectives, pb.XTestDirectives)
// The generated main also imports testing, regexp, and os.
// Also the linker introduces implicit dependencies reported by LinkerDeps.
stk.Push("testmain")
deps := TestMainDeps // cap==len, so safe for append
if cover != nil && cfg.Experiment.CoverageRedesign {
deps = append(deps, "internal/coverage/cfile")
}
ldDeps, err := LinkerDeps(p)
if err != nil && pmain.Error == nil {
pmain.Error = &PackageError{Err: err}
}
for _, d := range ldDeps {
deps = append(deps, d)
}
for _, dep := range deps {
if dep == ptest.ImportPath {
pmain.Internal.Imports = append(pmain.Internal.Imports, ptest)
} else {
p1, err := loadImport(ctx, opts, pre, dep, "", nil, &stk, nil, 0)
if err != nil && pmain.Error == nil {
pmain.Error = err
pmain.Incomplete = true
}
pmain.Internal.Imports = append(pmain.Internal.Imports, p1)
}
}
stk.Pop()
parallelizablePart := func() {
if cover != nil && cover.Pkgs != nil && !cfg.Experiment.CoverageRedesign {
// Add imports, but avoid duplicates.
seen := map[*Package]bool{p: true, ptest: true}
for _, p1 := range pmain.Internal.Imports {
seen[p1] = true
}
for _, p1 := range cover.Pkgs {
if seen[p1] {
// Don't add duplicate imports.
continue
}
seen[p1] = true
pmain.Internal.Imports = append(pmain.Internal.Imports, p1)
}
}
allTestImports := make([]*Package, 0, len(pmain.Internal.Imports)+len(imports)+len(ximports))
allTestImports = append(allTestImports, pmain.Internal.Imports...)
allTestImports = append(allTestImports, imports...)
allTestImports = append(allTestImports, ximports...)
setToolFlags(allTestImports...)
// Do initial scan for metadata needed for writing _testmain.go
// Use that metadata to update the list of imports for package main.
// The list of imports is used by recompileForTest and by the loop
// afterward that gathers t.Cover information.
t, err := loadTestFuncs(p)
if err != nil && pmain.Error == nil {
pmain.setLoadPackageDataError(err, p.ImportPath, &stk, nil)
}
t.Cover = cover
if len(ptest.GoFiles)+len(ptest.CgoFiles) > 0 {
pmain.Internal.Imports = append(pmain.Internal.Imports, ptest)
pmain.Imports = append(pmain.Imports, ptest.ImportPath)
t.ImportTest = true
}
if pxtest != nil {
pmain.Internal.Imports = append(pmain.Internal.Imports, pxtest)
pmain.Imports = append(pmain.Imports, pxtest.ImportPath)
t.ImportXtest = true
}
// Sort and dedup pmain.Imports.
// Only matters for go list -test output.
sort.Strings(pmain.Imports)
w := 0
for _, path := range pmain.Imports {
if w == 0 || path != pmain.Imports[w-1] {
pmain.Imports[w] = path
w++
}
}
pmain.Imports = pmain.Imports[:w]
pmain.Internal.RawImports = str.StringList(pmain.Imports)
// Replace pmain's transitive dependencies with test copies, as necessary.
cycleErr := recompileForTest(pmain, p, ptest, pxtest)
if cycleErr != nil {
ptest.Error = cycleErr
ptest.Incomplete = true
}
if cover != nil {
if cfg.Experiment.CoverageRedesign {
// Here ptest needs to inherit the proper coverage mode (since
// it contains p's Go files), whereas pmain contains only
// test harness code (don't want to instrument it, and
// we don't want coverage hooks in the pkg init).
ptest.Internal.Cover.Mode = p.Internal.Cover.Mode
pmain.Internal.Cover.Mode = "testmain"
}
// Should we apply coverage analysis locally, only for this
// package and only for this test? Yes, if -cover is on but
// -coverpkg has not specified a list of packages for global
// coverage.
if cover.Local {
ptest.Internal.Cover.Mode = cover.Mode
if !cfg.Experiment.CoverageRedesign {
var coverFiles []string
coverFiles = append(coverFiles, ptest.GoFiles...)
coverFiles = append(coverFiles, ptest.CgoFiles...)
ptest.Internal.CoverVars = DeclareCoverVars(ptest, coverFiles...)
}
}
if !cfg.Experiment.CoverageRedesign {
for _, cp := range pmain.Internal.Imports {
if len(cp.Internal.CoverVars) > 0 {
t.Cover.Vars = append(t.Cover.Vars, coverInfo{cp, cp.Internal.CoverVars})
}
}
}
}
data, err := formatTestmain(t)
if err != nil && pmain.Error == nil {
pmain.Error = &PackageError{Err: err}
pmain.Incomplete = true
}
// Set TestmainGo even if it is empty: the presence of a TestmainGo
// indicates that this package is, in fact, a test main.
pmain.Internal.TestmainGo = &data
}
if done != nil {
go func() {
parallelizablePart()
done()
}()
} else {
parallelizablePart()
}
return pmain, ptest, pxtest
}
// recompileForTest copies and replaces certain packages in pmain's dependency
// graph. This is necessary for two reasons. First, if ptest is different than
// preal, packages that import the package under test should get ptest instead
// of preal. This is particularly important if pxtest depends on functionality
// exposed in test sources in ptest. Second, if there is a main package
// (other than pmain) anywhere, we need to set p.Internal.ForceLibrary and
// clear p.Internal.BuildInfo in the test copy to prevent link conflicts.
// This may happen if both -coverpkg and the command line patterns include
// multiple main packages.
func recompileForTest(pmain, preal, ptest, pxtest *Package) *PackageError {
// The "test copy" of preal is ptest.
// For each package that depends on preal, make a "test copy"
// that depends on ptest. And so on, up the dependency tree.
testCopy := map[*Package]*Package{preal: ptest}
for _, p := range PackageList([]*Package{pmain}) {
if p == preal {
continue
}
// Copy on write.
didSplit := p == pmain || p == pxtest || p == ptest
split := func() {
if didSplit {
return
}
didSplit = true
if testCopy[p] != nil {
panic("recompileForTest loop")
}
p1 := new(Package)
testCopy[p] = p1
*p1 = *p
p1.ForTest = preal.ImportPath
p1.Internal.Imports = make([]*Package, len(p.Internal.Imports))
copy(p1.Internal.Imports, p.Internal.Imports)
p1.Imports = make([]string, len(p.Imports))
copy(p1.Imports, p.Imports)
p = p1
p.Target = ""
p.Internal.BuildInfo = nil
p.Internal.ForceLibrary = true
p.Internal.PGOProfile = preal.Internal.PGOProfile
}
// Update p.Internal.Imports to use test copies.
for i, imp := range p.Internal.Imports {
if p1 := testCopy[imp]; p1 != nil && p1 != imp {
split()
// If the test dependencies cause a cycle with pmain, this is
// where it is introduced.
// (There are no cycles in the graph until this assignment occurs.)
p.Internal.Imports[i] = p1
}
}
// Force main packages the test imports to be built as libraries.
// Normal imports of main packages are forbidden by the package loader,
// but this can still happen if -coverpkg patterns include main packages:
// covered packages are imported by pmain. Linking multiple packages
// compiled with '-p main' causes duplicate symbol errors.
// See golang.org/issue/30907, golang.org/issue/34114.
if p.Name == "main" && p != pmain && p != ptest {
split()
}
// Split and attach PGO information to test dependencies if preal
// is built with PGO.
if preal.Internal.PGOProfile != "" && p.Internal.PGOProfile == "" {
split()
}
}
// Do search to find cycle.
// importerOf maps each import path to its importer nearest to p.
importerOf := map[*Package]*Package{}
for _, p := range ptest.Internal.Imports {
importerOf[p] = nil
}
// q is a breadth-first queue of packages to search for target.
// Every package added to q has a corresponding entry in pathTo.
//
// We search breadth-first for two reasons:
//
// 1. We want to report the shortest cycle.
//
// 2. If p contains multiple cycles, the first cycle we encounter might not
// contain target. To ensure termination, we have to break all cycles
// other than the first.
q := slices.Clip(ptest.Internal.Imports)
for len(q) > 0 {
p := q[0]
q = q[1:]
if p == ptest {
// The stack is supposed to be in the order x imports y imports z.
// We collect in the reverse order: z is imported by y is imported
// by x, and then we reverse it.
var stk []string
for p != nil {
stk = append(stk, p.ImportPath)
p = importerOf[p]
}
// complete the cycle: we set importer[p] = nil to break the cycle
// in importerOf, it's an implicit importerOf[p] == pTest. Add it
// back here since we reached nil in the loop above to demonstrate
// the cycle as (for example) package p imports package q imports package r
// imports package p.
stk = append(stk, ptest.ImportPath)
slices.Reverse(stk)
return &PackageError{
ImportStack: stk,
Err: errors.New("import cycle not allowed in test"),
IsImportCycle: true,
}
}
for _, dep := range p.Internal.Imports {
if _, ok := importerOf[dep]; !ok {
importerOf[dep] = p
q = append(q, dep)
}
}
}
return nil
}
// isTestFunc tells whether fn has the type of a testing function. arg
// specifies the parameter type we look for: B, F, M or T.
func isTestFunc(fn *ast.FuncDecl, arg string) bool {
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
fn.Type.Params.List == nil ||
len(fn.Type.Params.List) != 1 ||
len(fn.Type.Params.List[0].Names) > 1 {
return false
}
ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
if !ok {
return false
}
// We can't easily check that the type is *testing.M
// because we don't know how testing has been imported,
// but at least check that it's *M or *something.M.
// Same applies for B, F and T.
if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg {
return true
}
if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg {
return true
}
return false
}
// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer.
func isTest(name, prefix string) bool {
if !strings.HasPrefix(name, prefix) {
return false
}
if len(name) == len(prefix) { // "Test" is ok
return true
}
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
}
type coverInfo struct {
Package *Package
Vars map[string]*CoverVar
}
// loadTestFuncs returns the testFuncs describing the tests that will be run.
// The returned testFuncs is always non-nil, even if an error occurred while
// processing test files.
func loadTestFuncs(ptest *Package) (*testFuncs, error) {
t := &testFuncs{
Package: ptest,
}
var err error
for _, file := range ptest.TestGoFiles {
if lerr := t.load(filepath.Join(ptest.Dir, file), "_test", &t.ImportTest, &t.NeedTest); lerr != nil && err == nil {
err = lerr
}
}
for _, file := range ptest.XTestGoFiles {
if lerr := t.load(filepath.Join(ptest.Dir, file), "_xtest", &t.ImportXtest, &t.NeedXtest); lerr != nil && err == nil {
err = lerr
}
}
return t, err
}
// formatTestmain returns the content of the _testmain.go file for t.
func formatTestmain(t *testFuncs) ([]byte, error) {
var buf bytes.Buffer
tmpl := testmainTmpl
if cfg.Experiment.CoverageRedesign {
tmpl = testmainTmplNewCoverage
}
if err := tmpl.Execute(&buf, t); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
type testFuncs struct {
Tests []testFunc
Benchmarks []testFunc
FuzzTargets []testFunc
Examples []testFunc
TestMain *testFunc
Package *Package
ImportTest bool
NeedTest bool
ImportXtest bool
NeedXtest bool
Cover *TestCover
}
// ImportPath returns the import path of the package being tested, if it is within GOPATH.
// This is printed by the testing package when running benchmarks.
func (t *testFuncs) ImportPath() string {
pkg := t.Package.ImportPath
if strings.HasPrefix(pkg, "_/") {
return ""
}
if pkg == "command-line-arguments" {
return ""
}
return pkg
}
// Covered returns a string describing which packages are being tested for coverage.
// If the covered package is the same as the tested package, it returns the empty string.
// Otherwise it is a comma-separated human-readable list of packages beginning with
// " in", ready for use in the coverage message.
func (t *testFuncs) Covered() string {
if t.Cover == nil || t.Cover.Paths == nil {
return ""
}
return " in " + strings.Join(t.Cover.Paths, ", ")
}
func (t *testFuncs) CoverSelectedPackages() string {
if t.Cover == nil || t.Cover.Paths == nil {
return `[]string{"` + t.Package.ImportPath + `"}`
}
var sb strings.Builder
fmt.Fprintf(&sb, "[]string{")
for k, p := range t.Cover.Pkgs {
if k != 0 {
sb.WriteString(", ")
}
fmt.Fprintf(&sb, `"%s"`, p.ImportPath)
}
sb.WriteString("}")
return sb.String()
}
// Tested returns the name of the package being tested.
func (t *testFuncs) Tested() string {
return t.Package.Name
}
type testFunc struct {
Package string // imported package name (_test or _xtest)
Name string // function name
Output string // output, for examples
Unordered bool // output is allowed to be unordered.
}
var testFileSet = token.NewFileSet()
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
// Pass in the overlaid source if we have an overlay for this file.
src, err := fsys.Open(filename)
if err != nil {
return err
}
defer src.Close()
f, err := parser.ParseFile(testFileSet, filename, src, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
return err
}
for _, d := range f.Decls {
n, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
if n.Recv != nil {
continue
}
name := n.Name.String()
switch {
case name == "TestMain":
if isTestFunc(n, "T") {
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
continue
}
err := checkTestFunc(n, "M")
if err != nil {
return err
}
if t.TestMain != nil {
return errors.New("multiple definitions of TestMain")
}
t.TestMain = &testFunc{pkg, name, "", false}
*doImport, *seen = true, true
case isTest(name, "Test"):
err := checkTestFunc(n, "T")
if err != nil {
return err
}
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
case isTest(name, "Benchmark"):
err := checkTestFunc(n, "B")
if err != nil {
return err
}
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
case isTest(name, "Fuzz"):
err := checkTestFunc(n, "F")
if err != nil {
return err
}
t.FuzzTargets = append(t.FuzzTargets, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
}
}
ex := doc.Examples(f)
sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order })
for _, e := range ex {
*doImport = true // import test file whether executed or not
if e.Output == "" && !e.EmptyOutput {
// Don't run examples with no output.
continue
}
t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
*seen = true
}
return nil
}
func checkTestFunc(fn *ast.FuncDecl, arg string) error {
var why string
if !isTestFunc(fn, arg) {
why = fmt.Sprintf("must be: func %s(%s *testing.%s)", fn.Name.String(), strings.ToLower(arg), arg)
}
if fn.Type.TypeParams.NumFields() > 0 {
why = "test functions cannot have type parameters"
}
if why != "" {
pos := testFileSet.Position(fn.Pos())
return fmt.Errorf("%s: wrong signature for %s, %s", pos, fn.Name.String(), why)
}
return nil
}
var testmainTmpl = lazytemplate.New("main", `
// Code generated by 'go test'. DO NOT EDIT.
package main
import (
"os"
{{if .TestMain}}
"reflect"
{{end}}
"testing"
"testing/internal/testdeps"
{{if .ImportTest}}
{{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
{{end}}
{{if .ImportXtest}}
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
{{if .Cover}}
{{range $i, $p := .Cover.Vars}}
_cover{{$i}} {{$p.Package.ImportPath | printf "%q"}}
{{end}}
{{end}}
)
var tests = []testing.InternalTest{
{{range .Tests}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var benchmarks = []testing.InternalBenchmark{
{{range .Benchmarks}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var fuzzTargets = []testing.InternalFuzzTarget{
{{range .FuzzTargets}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var examples = []testing.InternalExample{
{{range .Examples}}
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
{{end}}
}
func init() {
testdeps.ImportPath = {{.ImportPath | printf "%q"}}
}
{{if .Cover}}
// Only updated by init functions, so no need for atomicity.
var (
coverCounters = make(map[string][]uint32)
coverBlocks = make(map[string][]testing.CoverBlock)
)
func init() {
{{range $i, $p := .Cover.Vars}}
{{range $file, $cover := $p.Vars}}
coverRegisterFile({{printf "%q" $cover.File}}, _cover{{$i}}.{{$cover.Var}}.Count[:], _cover{{$i}}.{{$cover.Var}}.Pos[:], _cover{{$i}}.{{$cover.Var}}.NumStmt[:])
{{end}}
{{end}}
}
func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts []uint16) {
if 3*len(counter) != len(pos) || len(counter) != len(numStmts) {
panic("coverage: mismatched sizes")
}
if coverCounters[fileName] != nil {
// Already registered.
return
}
coverCounters[fileName] = counter
block := make([]testing.CoverBlock, len(counter))
for i := range counter {
block[i] = testing.CoverBlock{
Line0: pos[3*i+0],
Col0: uint16(pos[3*i+2]),
Line1: pos[3*i+1],
Col1: uint16(pos[3*i+2]>>16),
Stmts: numStmts[i],
}
}
coverBlocks[fileName] = block
}
{{end}}
func main() {
{{if .Cover}}
testing.RegisterCover(testing.Cover{
Mode: {{printf "%q" .Cover.Mode}},
Counters: coverCounters,
Blocks: coverBlocks,
CoveredPackages: {{printf "%q" .Covered}},
})
{{end}}
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
os.Exit(int(reflect.ValueOf(m).Elem().FieldByName("exitCode").Int()))
{{else}}
os.Exit(m.Run())
{{end}}
}
`)
var testmainTmplNewCoverage = lazytemplate.New("main", `
// Code generated by 'go test'. DO NOT EDIT.
package main
import (
"os"
{{if .TestMain}}
"reflect"
{{end}}
"testing"
"testing/internal/testdeps"
{{if .Cover}}
"internal/coverage/cfile"
{{end}}
{{if .ImportTest}}
{{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
{{end}}
{{if .ImportXtest}}
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
)
var tests = []testing.InternalTest{
{{range .Tests}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var benchmarks = []testing.InternalBenchmark{
{{range .Benchmarks}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var fuzzTargets = []testing.InternalFuzzTarget{
{{range .FuzzTargets}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var examples = []testing.InternalExample{
{{range .Examples}}
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
{{end}}
}
func init() {
{{if .Cover}}
testdeps.CoverMode = {{printf "%q" .Cover.Mode}}
testdeps.Covered = {{printf "%q" .Covered}}
testdeps.CoverSelectedPackages = {{printf "%s" .CoverSelectedPackages}}
testdeps.CoverSnapshotFunc = cfile.Snapshot
testdeps.CoverProcessTestDirFunc = cfile.ProcessCoverTestDir
testdeps.CoverMarkProfileEmittedFunc = cfile.MarkProfileEmitted
{{end}}
testdeps.ImportPath = {{.ImportPath | printf "%q"}}
}
func main() {
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
os.Exit(int(reflect.ValueOf(m).Elem().FieldByName("exitCode").Int()))
{{else}}
os.Exit(m.Run())
{{end}}
}
`)

View File

@@ -0,0 +1,83 @@
// Copyright 2018 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 filelock provides a platform-independent API for advisory file
// locking. Calls to functions in this package on platforms that do not support
// advisory locks will return errors for which IsNotSupported returns true.
package filelock
import (
"errors"
"io/fs"
)
// A File provides the minimal set of methods required to lock an open file.
// File implementations must be usable as map keys.
// The usual implementation is *os.File.
type File interface {
// Name returns the name of the file.
Name() string
// Fd returns a valid file descriptor.
// (If the File is an *os.File, it must not be closed.)
Fd() uintptr
// Stat returns the FileInfo structure describing file.
Stat() (fs.FileInfo, error)
}
// Lock places an advisory write lock on the file, blocking until it can be
// locked.
//
// If Lock returns nil, no other process will be able to place a read or write
// lock on the file until this process exits, closes f, or calls Unlock on it.
//
// If f's descriptor is already read- or write-locked, the behavior of Lock is
// unspecified.
//
// Closing the file may or may not release the lock promptly. Callers should
// ensure that Unlock is always called when Lock succeeds.
func Lock(f File) error {
return lock(f, writeLock)
}
// RLock places an advisory read lock on the file, blocking until it can be locked.
//
// If RLock returns nil, no other process will be able to place a write lock on
// the file until this process exits, closes f, or calls Unlock on it.
//
// If f is already read- or write-locked, the behavior of RLock is unspecified.
//
// Closing the file may or may not release the lock promptly. Callers should
// ensure that Unlock is always called if RLock succeeds.
func RLock(f File) error {
return lock(f, readLock)
}
// Unlock removes an advisory lock placed on f by this process.
//
// The caller must not attempt to unlock a file that is not locked.
func Unlock(f File) error {
return unlock(f)
}
// String returns the name of the function corresponding to lt
// (Lock, RLock, or Unlock).
func (lt lockType) String() string {
switch lt {
case readLock:
return "RLock"
case writeLock:
return "Lock"
default:
return "Unlock"
}
}
// IsNotSupported returns a boolean indicating whether the error is known to
// report that a function is not supported (possibly for a specific input).
// It is satisfied by errors.ErrUnsupported as well as some syscall errors.
func IsNotSupported(err error) bool {
return errors.Is(err, errors.ErrUnsupported)
}

View File

@@ -0,0 +1,210 @@
// Copyright 2018 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.
//go:build aix || (solaris && !illumos)
// This code implements the filelock API using POSIX 'fcntl' locks, which attach
// to an (inode, process) pair rather than a file descriptor. To avoid unlocking
// files prematurely when the same file is opened through different descriptors,
// we allow only one read-lock at a time.
//
// Most platforms provide some alternative API, such as an 'flock' system call
// or an F_OFD_SETLK command for 'fcntl', that allows for better concurrency and
// does not require per-inode bookkeeping in the application.
package filelock
import (
"errors"
"io"
"io/fs"
"math/rand"
"sync"
"syscall"
"time"
)
type lockType int16
const (
readLock lockType = syscall.F_RDLCK
writeLock lockType = syscall.F_WRLCK
)
type inode = uint64 // type of syscall.Stat_t.Ino
type inodeLock struct {
owner File
queue []<-chan File
}
var (
mu sync.Mutex
inodes = map[File]inode{}
locks = map[inode]inodeLock{}
)
func lock(f File, lt lockType) (err error) {
// POSIX locks apply per inode and process, and the lock for an inode is
// released when *any* descriptor for that inode is closed. So we need to
// synchronize access to each inode internally, and must serialize lock and
// unlock calls that refer to the same inode through different descriptors.
fi, err := f.Stat()
if err != nil {
return err
}
ino := fi.Sys().(*syscall.Stat_t).Ino
mu.Lock()
if i, dup := inodes[f]; dup && i != ino {
mu.Unlock()
return &fs.PathError{
Op: lt.String(),
Path: f.Name(),
Err: errors.New("inode for file changed since last Lock or RLock"),
}
}
inodes[f] = ino
var wait chan File
l := locks[ino]
if l.owner == f {
// This file already owns the lock, but the call may change its lock type.
} else if l.owner == nil {
// No owner: it's ours now.
l.owner = f
} else {
// Already owned: add a channel to wait on.
wait = make(chan File)
l.queue = append(l.queue, wait)
}
locks[ino] = l
mu.Unlock()
if wait != nil {
wait <- f
}
// Spurious EDEADLK errors arise on platforms that compute deadlock graphs at
// the process, rather than thread, level. Consider processes P and Q, with
// threads P.1, P.2, and Q.3. The following trace is NOT a deadlock, but will be
// reported as a deadlock on systems that consider only process granularity:
//
// P.1 locks file A.
// Q.3 locks file B.
// Q.3 blocks on file A.
// P.2 blocks on file B. (This is erroneously reported as a deadlock.)
// P.1 unlocks file A.
// Q.3 unblocks and locks file A.
// Q.3 unlocks files A and B.
// P.2 unblocks and locks file B.
// P.2 unlocks file B.
//
// These spurious errors were observed in practice on AIX and Solaris in
// cmd/go: see https://golang.org/issue/32817.
//
// We work around this bug by treating EDEADLK as always spurious. If there
// really is a lock-ordering bug between the interacting processes, it will
// become a livelock instead, but that's not appreciably worse than if we had
// a proper flock implementation (which generally does not even attempt to
// diagnose deadlocks).
//
// In the above example, that changes the trace to:
//
// P.1 locks file A.
// Q.3 locks file B.
// Q.3 blocks on file A.
// P.2 spuriously fails to lock file B and goes to sleep.
// P.1 unlocks file A.
// Q.3 unblocks and locks file A.
// Q.3 unlocks files A and B.
// P.2 wakes up and locks file B.
// P.2 unlocks file B.
//
// We know that the retry loop will not introduce a *spurious* livelock
// because, according to the POSIX specification, EDEADLK is only to be
// returned when “the lock is blocked by a lock from another process”.
// If that process is blocked on some lock that we are holding, then the
// resulting livelock is due to a real deadlock (and would manifest as such
// when using, for example, the flock implementation of this package).
// If the other process is *not* blocked on some other lock that we are
// holding, then it will eventually release the requested lock.
nextSleep := 1 * time.Millisecond
const maxSleep = 500 * time.Millisecond
for {
err = setlkw(f.Fd(), lt)
if err != syscall.EDEADLK {
break
}
time.Sleep(nextSleep)
nextSleep += nextSleep
if nextSleep > maxSleep {
nextSleep = maxSleep
}
// Apply 10% jitter to avoid synchronizing collisions when we finally unblock.
nextSleep += time.Duration((0.1*rand.Float64() - 0.05) * float64(nextSleep))
}
if err != nil {
unlock(f)
return &fs.PathError{
Op: lt.String(),
Path: f.Name(),
Err: err,
}
}
return nil
}
func unlock(f File) error {
var owner File
mu.Lock()
ino, ok := inodes[f]
if ok {
owner = locks[ino].owner
}
mu.Unlock()
if owner != f {
panic("unlock called on a file that is not locked")
}
err := setlkw(f.Fd(), syscall.F_UNLCK)
mu.Lock()
l := locks[ino]
if len(l.queue) == 0 {
// No waiters: remove the map entry.
delete(locks, ino)
} else {
// The first waiter is sending us their file now.
// Receive it and update the queue.
l.owner = <-l.queue[0]
l.queue = l.queue[1:]
locks[ino] = l
}
delete(inodes, f)
mu.Unlock()
return err
}
// setlkw calls FcntlFlock with F_SETLKW for the entire file indicated by fd.
func setlkw(fd uintptr, lt lockType) error {
for {
err := syscall.FcntlFlock(fd, syscall.F_SETLKW, &syscall.Flock_t{
Type: int16(lt),
Whence: io.SeekStart,
Start: 0,
Len: 0, // All bytes.
})
if err != syscall.EINTR {
return err
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2018 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.
//go:build !unix && !windows
package filelock
import (
"errors"
"io/fs"
)
type lockType int8
const (
readLock = iota + 1
writeLock
)
func lock(f File, lt lockType) error {
return &fs.PathError{
Op: lt.String(),
Path: f.Name(),
Err: errors.ErrUnsupported,
}
}
func unlock(f File) error {
return &fs.PathError{
Op: "Unlock",
Path: f.Name(),
Err: errors.ErrUnsupported,
}
}

Some files were not shown because too many files have changed in this diff Show More