Initial commit: Go 1.23 release state
This commit is contained in:
3448
src/cmd/go/alldocs.go
Normal file
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
49
src/cmd/go/chdir_test.go
Normal 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)
|
||||
}
|
||||
7
src/cmd/go/export_test.go
Normal file
7
src/cmd/go/export_test.go
Normal 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
10
src/cmd/go/go11.go
Normal 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
|
||||
22
src/cmd/go/go_boring_test.go
Normal file
22
src/cmd/go/go_boring_test.go
Normal 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
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
137
src/cmd/go/go_unix_test.go
Normal 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")
|
||||
}
|
||||
50
src/cmd/go/go_windows_test.go
Normal file
50
src/cmd/go/go_windows_test.go
Normal 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
63
src/cmd/go/help_test.go
Normal 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
41
src/cmd/go/init_test.go
Normal 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")
|
||||
}
|
||||
28
src/cmd/go/internal/auth/auth.go
Normal file
28
src/cmd/go/internal/auth/auth.go
Normal 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
|
||||
}
|
||||
110
src/cmd/go/internal/auth/netrc.go
Normal file
110
src/cmd/go/internal/auth/netrc.go
Normal 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))
|
||||
}
|
||||
58
src/cmd/go/internal/auth/netrc_test.go
Normal file
58
src/cmd/go/internal/auth/netrc_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
223
src/cmd/go/internal/base/base.go
Normal file
223
src/cmd/go/internal/base/base.go
Normal 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()
|
||||
46
src/cmd/go/internal/base/env.go
Normal file
46
src/cmd/go/internal/base/env.go
Normal 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)
|
||||
}
|
||||
85
src/cmd/go/internal/base/flag.go
Normal file
85
src/cmd/go/internal/base/flag.go
Normal 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")
|
||||
}
|
||||
162
src/cmd/go/internal/base/goflags.go
Normal file
162
src/cmd/go/internal/base/goflags.go
Normal 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
|
||||
}
|
||||
84
src/cmd/go/internal/base/limit.go
Normal file
84
src/cmd/go/internal/base/limit.go
Normal 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")
|
||||
}
|
||||
79
src/cmd/go/internal/base/path.go
Normal file
79
src/cmd/go/internal/base/path.go
Normal 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
|
||||
}
|
||||
31
src/cmd/go/internal/base/signal.go
Normal file
31
src/cmd/go/internal/base/signal.go
Normal 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)
|
||||
}
|
||||
17
src/cmd/go/internal/base/signal_notunix.go
Normal file
17
src/cmd/go/internal/base/signal_notunix.go
Normal 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
|
||||
18
src/cmd/go/internal/base/signal_unix.go
Normal file
18
src/cmd/go/internal/base/signal_unix.go
Normal 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
|
||||
41
src/cmd/go/internal/base/tool.go
Normal file
41
src/cmd/go/internal/base/tool.go
Normal 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]
|
||||
224
src/cmd/go/internal/bug/bug.go
Normal file
224
src/cmd/go/internal/bug/bug.go
Normal 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
627
src/cmd/go/internal/cache/cache.go
vendored
Normal 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
285
src/cmd/go/internal/cache/cache_test.go
vendored
Normal 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
109
src/cmd/go/internal/cache/default.go
vendored
Normal 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
190
src/cmd/go/internal/cache/hash.go
vendored
Normal 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
51
src/cmd/go/internal/cache/hash_test.go
vendored
Normal 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
427
src/cmd/go/internal/cache/prog.go
vendored
Normal 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()
|
||||
}
|
||||
21
src/cmd/go/internal/cfg/bench_test.go
Normal file
21
src/cmd/go/internal/cfg/bench_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
638
src/cmd/go/internal/cfg/cfg.go
Normal file
638
src/cmd/go/internal/cfg/cfg.go
Normal 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
|
||||
}
|
||||
21
src/cmd/go/internal/cfg/lookpath.go
Normal file
21
src/cmd/go/internal/cfg/lookpath.go
Normal 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)
|
||||
})
|
||||
}
|
||||
401
src/cmd/go/internal/clean/clean.go
Normal file
401
src/cmd/go/internal/clean/clean.go
Normal 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)
|
||||
}
|
||||
122
src/cmd/go/internal/cmdflag/flag.go
Normal file
122
src/cmd/go/internal/cmdflag/flag.go
Normal 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
|
||||
}
|
||||
134
src/cmd/go/internal/doc/doc.go
Normal file
134
src/cmd/go/internal/doc/doc.go
Normal 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)
|
||||
}
|
||||
750
src/cmd/go/internal/envcmd/env.go
Normal file
750
src/cmd/go/internal/envcmd/env.go
Normal 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])
|
||||
})
|
||||
}
|
||||
93
src/cmd/go/internal/envcmd/env_test.go
Normal file
93
src/cmd/go/internal/envcmd/env_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
85
src/cmd/go/internal/fix/fix.go
Normal file
85
src/cmd/go/internal/fix/fix.go
Normal 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))
|
||||
}
|
||||
}
|
||||
115
src/cmd/go/internal/fmtcmd/fmt.go
Normal file
115
src/cmd/go/internal/fmtcmd/fmt.go
Normal 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"
|
||||
}
|
||||
790
src/cmd/go/internal/fsys/fsys.go
Normal file
790
src/cmd/go/internal/fsys/fsys.go
Normal 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)
|
||||
}
|
||||
1140
src/cmd/go/internal/fsys/fsys_test.go
Normal file
1140
src/cmd/go/internal/fsys/fsys_test.go
Normal file
File diff suppressed because it is too large
Load Diff
510
src/cmd/go/internal/generate/generate.go
Normal file
510
src/cmd/go/internal/generate/generate.go
Normal 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)
|
||||
}
|
||||
}
|
||||
259
src/cmd/go/internal/generate/generate_test.go
Normal file
259
src/cmd/go/internal/generate/generate_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/cmd/go/internal/gover/gomod.go
Normal file
43
src/cmd/go/internal/gover/gomod.go
Normal 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
|
||||
}
|
||||
75
src/cmd/go/internal/gover/gover.go
Normal file
75
src/cmd/go/internal/gover/gover.go
Normal 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)
|
||||
}
|
||||
142
src/cmd/go/internal/gover/gover_test.go
Normal file
142
src/cmd/go/internal/gover/gover_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/cmd/go/internal/gover/local.go
Normal file
42
src/cmd/go/internal/gover/local.go
Normal 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
|
||||
}
|
||||
127
src/cmd/go/internal/gover/mod.go
Normal file
127
src/cmd/go/internal/gover/mod.go
Normal 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)
|
||||
}
|
||||
72
src/cmd/go/internal/gover/mod_test.go
Normal file
72
src/cmd/go/internal/gover/mod_test.go
Normal 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
|
||||
}
|
||||
108
src/cmd/go/internal/gover/toolchain.go
Normal file
108
src/cmd/go/internal/gover/toolchain.go
Normal 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)
|
||||
}
|
||||
19
src/cmd/go/internal/gover/toolchain_test.go
Normal file
19
src/cmd/go/internal/gover/toolchain_test.go
Normal 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", ""},
|
||||
}
|
||||
78
src/cmd/go/internal/gover/version.go
Normal file
78
src/cmd/go/internal/gover/version.go
Normal 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
|
||||
}
|
||||
192
src/cmd/go/internal/help/help.go
Normal file
192
src/cmd/go/internal/help/help.go
Normal 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()
|
||||
}
|
||||
965
src/cmd/go/internal/help/helpdoc.go
Normal file
965
src/cmd/go/internal/help/helpdoc.go
Normal 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.
|
||||
`,
|
||||
}
|
||||
374
src/cmd/go/internal/imports/build.go
Normal file
374
src/cmd/go/internal/imports/build.go
Normal 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,
|
||||
}
|
||||
263
src/cmd/go/internal/imports/read.go
Normal file
263
src/cmd/go/internal/imports/read.go
Normal 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
|
||||
}
|
||||
254
src/cmd/go/internal/imports/read_test.go
Normal file
254
src/cmd/go/internal/imports/read_test.go
Normal 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) })
|
||||
}
|
||||
107
src/cmd/go/internal/imports/scan.go
Normal file
107
src/cmd/go/internal/imports/scan.go
Normal 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
|
||||
}
|
||||
93
src/cmd/go/internal/imports/scan_test.go
Normal file
93
src/cmd/go/internal/imports/scan_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
61
src/cmd/go/internal/imports/tags.go
Normal file
61
src/cmd/go/internal/imports/tags.go
Normal 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
|
||||
}
|
||||
3
src/cmd/go/internal/imports/testdata/android/.h.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/android/.h.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package android
|
||||
|
||||
import _ "h"
|
||||
3
src/cmd/go/internal/imports/testdata/android/a_android.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/android/a_android.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package android
|
||||
|
||||
import _ "a"
|
||||
3
src/cmd/go/internal/imports/testdata/android/b_android_arm64.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/android/b_android_arm64.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package android
|
||||
|
||||
import _ "b"
|
||||
3
src/cmd/go/internal/imports/testdata/android/c_linux.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/android/c_linux.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package android
|
||||
|
||||
import _ "c"
|
||||
3
src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package android
|
||||
|
||||
import _ "d"
|
||||
6
src/cmd/go/internal/imports/testdata/android/e.go
vendored
Normal file
6
src/cmd/go/internal/imports/testdata/android/e.go
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build android
|
||||
// +build android
|
||||
|
||||
package android
|
||||
|
||||
import _ "e"
|
||||
6
src/cmd/go/internal/imports/testdata/android/f.go
vendored
Normal file
6
src/cmd/go/internal/imports/testdata/android/f.go
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package android
|
||||
|
||||
import _ "f"
|
||||
6
src/cmd/go/internal/imports/testdata/android/g.go
vendored
Normal file
6
src/cmd/go/internal/imports/testdata/android/g.go
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build !android
|
||||
// +build !android
|
||||
|
||||
package android
|
||||
|
||||
import _ "g"
|
||||
1
src/cmd/go/internal/imports/testdata/android/tags.txt
vendored
Normal file
1
src/cmd/go/internal/imports/testdata/android/tags.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
android arm64
|
||||
6
src/cmd/go/internal/imports/testdata/android/want.txt
vendored
Normal file
6
src/cmd/go/internal/imports/testdata/android/want.txt
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
3
src/cmd/go/internal/imports/testdata/illumos/.h.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/illumos/.h.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package android
|
||||
|
||||
import _ "h"
|
||||
3
src/cmd/go/internal/imports/testdata/illumos/a_illumos.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/illumos/a_illumos.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package illumos
|
||||
|
||||
import _ "a"
|
||||
3
src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package illumos
|
||||
|
||||
import _ "b"
|
||||
3
src/cmd/go/internal/imports/testdata/illumos/c_solaris.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/illumos/c_solaris.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package illumos
|
||||
|
||||
import _ "c"
|
||||
3
src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package illumos
|
||||
|
||||
import _ "d"
|
||||
6
src/cmd/go/internal/imports/testdata/illumos/e.go
vendored
Normal file
6
src/cmd/go/internal/imports/testdata/illumos/e.go
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build illumos
|
||||
// +build illumos
|
||||
|
||||
package illumos
|
||||
|
||||
import _ "e"
|
||||
6
src/cmd/go/internal/imports/testdata/illumos/f.go
vendored
Normal file
6
src/cmd/go/internal/imports/testdata/illumos/f.go
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build solaris
|
||||
// +build solaris
|
||||
|
||||
package illumos
|
||||
|
||||
import _ "f"
|
||||
6
src/cmd/go/internal/imports/testdata/illumos/g.go
vendored
Normal file
6
src/cmd/go/internal/imports/testdata/illumos/g.go
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build !illumos
|
||||
// +build !illumos
|
||||
|
||||
package illumos
|
||||
|
||||
import _ "g"
|
||||
1
src/cmd/go/internal/imports/testdata/illumos/tags.txt
vendored
Normal file
1
src/cmd/go/internal/imports/testdata/illumos/tags.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
illumos amd64
|
||||
6
src/cmd/go/internal/imports/testdata/illumos/want.txt
vendored
Normal file
6
src/cmd/go/internal/imports/testdata/illumos/want.txt
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
1
src/cmd/go/internal/imports/testdata/star/tags.txt
vendored
Normal file
1
src/cmd/go/internal/imports/testdata/star/tags.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*
|
||||
4
src/cmd/go/internal/imports/testdata/star/want.txt
vendored
Normal file
4
src/cmd/go/internal/imports/testdata/star/want.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import1
|
||||
import2
|
||||
import3
|
||||
import4
|
||||
3
src/cmd/go/internal/imports/testdata/star/x.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/star/x.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package x
|
||||
|
||||
import "import1"
|
||||
6
src/cmd/go/internal/imports/testdata/star/x1.go
vendored
Normal file
6
src/cmd/go/internal/imports/testdata/star/x1.go
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build blahblh && linux && !linux && windows && darwin
|
||||
// +build blahblh,linux,!linux,windows,darwin
|
||||
|
||||
package x
|
||||
|
||||
import "import4"
|
||||
3
src/cmd/go/internal/imports/testdata/star/x_darwin.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/star/x_darwin.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package xxxx
|
||||
|
||||
import "import3"
|
||||
3
src/cmd/go/internal/imports/testdata/star/x_windows.go
vendored
Normal file
3
src/cmd/go/internal/imports/testdata/star/x_windows.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package x
|
||||
|
||||
import "import2"
|
||||
39
src/cmd/go/internal/list/context.go
Normal file
39
src/cmd/go/internal/list/context.go
Normal 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,
|
||||
}
|
||||
}
|
||||
1003
src/cmd/go/internal/list/list.go
Normal file
1003
src/cmd/go/internal/list/list.go
Normal file
File diff suppressed because it is too large
Load Diff
96
src/cmd/go/internal/load/flag.go
Normal file
96
src/cmd/go/internal/load/flag.go
Normal 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
|
||||
}
|
||||
135
src/cmd/go/internal/load/flag_test.go
Normal file
135
src/cmd/go/internal/load/flag_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
137
src/cmd/go/internal/load/godebug.go
Normal file
137
src/cmd/go/internal/load/godebug.go
Normal 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
|
||||
}
|
||||
18
src/cmd/go/internal/load/path.go
Normal file
18
src/cmd/go/internal/load/path.go
Normal 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
|
||||
}
|
||||
3532
src/cmd/go/internal/load/pkg.go
Normal file
3532
src/cmd/go/internal/load/pkg.go
Normal file
File diff suppressed because it is too large
Load Diff
82
src/cmd/go/internal/load/pkg_test.go
Normal file
82
src/cmd/go/internal/load/pkg_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/cmd/go/internal/load/search.go
Normal file
57
src/cmd/go/internal/load/search.go
Normal 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) }
|
||||
}
|
||||
}
|
||||
988
src/cmd/go/internal/load/test.go
Normal file
988
src/cmd/go/internal/load/test.go
Normal 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}}
|
||||
}
|
||||
|
||||
`)
|
||||
83
src/cmd/go/internal/lockedfile/internal/filelock/filelock.go
Normal file
83
src/cmd/go/internal/lockedfile/internal/filelock/filelock.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user