summaryrefslogtreecommitdiffstats
path: root/src/cmd/go/internal/imports
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/cmd/go/internal/imports
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/go/internal/imports')
-rw-r--r--src/cmd/go/internal/imports/build.go374
-rw-r--r--src/cmd/go/internal/imports/read.go263
-rw-r--r--src/cmd/go/internal/imports/read_test.go254
-rw-r--r--src/cmd/go/internal/imports/scan.go107
-rw-r--r--src/cmd/go/internal/imports/scan_test.go93
-rw-r--r--src/cmd/go/internal/imports/tags.go61
-rw-r--r--src/cmd/go/internal/imports/testdata/android/.h.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/a_android.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/b_android_arm64.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/c_linux.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/android/e.go6
-rw-r--r--src/cmd/go/internal/imports/testdata/android/f.go6
-rw-r--r--src/cmd/go/internal/imports/testdata/android/g.go6
-rw-r--r--src/cmd/go/internal/imports/testdata/android/tags.txt1
-rw-r--r--src/cmd/go/internal/imports/testdata/android/want.txt6
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/.h.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/a_illumos.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/c_solaris.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/e.go6
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/f.go6
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/g.go6
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/tags.txt1
-rw-r--r--src/cmd/go/internal/imports/testdata/illumos/want.txt6
-rw-r--r--src/cmd/go/internal/imports/testdata/star/tags.txt1
-rw-r--r--src/cmd/go/internal/imports/testdata/star/want.txt4
-rw-r--r--src/cmd/go/internal/imports/testdata/star/x.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/star/x1.go6
-rw-r--r--src/cmd/go/internal/imports/testdata/star/x_darwin.go3
-rw-r--r--src/cmd/go/internal/imports/testdata/star/x_windows.go3
32 files changed, 1252 insertions, 0 deletions
diff --git a/src/cmd/go/internal/imports/build.go b/src/cmd/go/internal/imports/build.go
new file mode 100644
index 0000000..3a4a66b
--- /dev/null
+++ b/src/cmd/go/internal/imports/build.go
@@ -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,
+}
diff --git a/src/cmd/go/internal/imports/read.go b/src/cmd/go/internal/imports/read.go
new file mode 100644
index 0000000..70d5190
--- /dev/null
+++ b/src/cmd/go/internal/imports/read.go
@@ -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
+}
diff --git a/src/cmd/go/internal/imports/read_test.go b/src/cmd/go/internal/imports/read_test.go
new file mode 100644
index 0000000..6a1a652
--- /dev/null
+++ b/src/cmd/go/internal/imports/read_test.go
@@ -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) })
+}
diff --git a/src/cmd/go/internal/imports/scan.go b/src/cmd/go/internal/imports/scan.go
new file mode 100644
index 0000000..ee11a87
--- /dev/null
+++ b/src/cmd/go/internal/imports/scan.go
@@ -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
+}
diff --git a/src/cmd/go/internal/imports/scan_test.go b/src/cmd/go/internal/imports/scan_test.go
new file mode 100644
index 0000000..56efa90
--- /dev/null
+++ b/src/cmd/go/internal/imports/scan_test.go
@@ -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)
+ }
+ })
+ }
+}
diff --git a/src/cmd/go/internal/imports/tags.go b/src/cmd/go/internal/imports/tags.go
new file mode 100644
index 0000000..d1467b8
--- /dev/null
+++ b/src/cmd/go/internal/imports/tags.go
@@ -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
+}
diff --git a/src/cmd/go/internal/imports/testdata/android/.h.go b/src/cmd/go/internal/imports/testdata/android/.h.go
new file mode 100644
index 0000000..53c529e
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/.h.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "h"
diff --git a/src/cmd/go/internal/imports/testdata/android/a_android.go b/src/cmd/go/internal/imports/testdata/android/a_android.go
new file mode 100644
index 0000000..2ed972e
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/a_android.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "a"
diff --git a/src/cmd/go/internal/imports/testdata/android/b_android_arm64.go b/src/cmd/go/internal/imports/testdata/android/b_android_arm64.go
new file mode 100644
index 0000000..ee9c312
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/b_android_arm64.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "b"
diff --git a/src/cmd/go/internal/imports/testdata/android/c_linux.go b/src/cmd/go/internal/imports/testdata/android/c_linux.go
new file mode 100644
index 0000000..91624ce
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/c_linux.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "c"
diff --git a/src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go b/src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go
new file mode 100644
index 0000000..34e07df
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/d_linux_arm64.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "d"
diff --git a/src/cmd/go/internal/imports/testdata/android/e.go b/src/cmd/go/internal/imports/testdata/android/e.go
new file mode 100644
index 0000000..f1b9c88
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/e.go
@@ -0,0 +1,6 @@
+//go:build android
+// +build android
+
+package android
+
+import _ "e"
diff --git a/src/cmd/go/internal/imports/testdata/android/f.go b/src/cmd/go/internal/imports/testdata/android/f.go
new file mode 100644
index 0000000..bb0ff7b
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/f.go
@@ -0,0 +1,6 @@
+//go:build linux
+// +build linux
+
+package android
+
+import _ "f"
diff --git a/src/cmd/go/internal/imports/testdata/android/g.go b/src/cmd/go/internal/imports/testdata/android/g.go
new file mode 100644
index 0000000..ee19424
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/g.go
@@ -0,0 +1,6 @@
+//go:build !android
+// +build !android
+
+package android
+
+import _ "g"
diff --git a/src/cmd/go/internal/imports/testdata/android/tags.txt b/src/cmd/go/internal/imports/testdata/android/tags.txt
new file mode 100644
index 0000000..aaf5a6b
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/tags.txt
@@ -0,0 +1 @@
+android arm64 \ No newline at end of file
diff --git a/src/cmd/go/internal/imports/testdata/android/want.txt b/src/cmd/go/internal/imports/testdata/android/want.txt
new file mode 100644
index 0000000..0fdf397
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/android/want.txt
@@ -0,0 +1,6 @@
+a
+b
+c
+d
+e
+f
diff --git a/src/cmd/go/internal/imports/testdata/illumos/.h.go b/src/cmd/go/internal/imports/testdata/illumos/.h.go
new file mode 100644
index 0000000..53c529e
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/.h.go
@@ -0,0 +1,3 @@
+package android
+
+import _ "h"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/a_illumos.go b/src/cmd/go/internal/imports/testdata/illumos/a_illumos.go
new file mode 100644
index 0000000..2e6cb50
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/a_illumos.go
@@ -0,0 +1,3 @@
+package illumos
+
+import _ "a"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go b/src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go
new file mode 100644
index 0000000..2834d80
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/b_illumos_amd64.go
@@ -0,0 +1,3 @@
+package illumos
+
+import _ "b"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/c_solaris.go b/src/cmd/go/internal/imports/testdata/illumos/c_solaris.go
new file mode 100644
index 0000000..d7f9462
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/c_solaris.go
@@ -0,0 +1,3 @@
+package illumos
+
+import _ "c"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go b/src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go
new file mode 100644
index 0000000..0f52c2b
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/d_solaris_amd64.go
@@ -0,0 +1,3 @@
+package illumos
+
+import _ "d"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/e.go b/src/cmd/go/internal/imports/testdata/illumos/e.go
new file mode 100644
index 0000000..fddf2c4
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/e.go
@@ -0,0 +1,6 @@
+//go:build illumos
+// +build illumos
+
+package illumos
+
+import _ "e"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/f.go b/src/cmd/go/internal/imports/testdata/illumos/f.go
new file mode 100644
index 0000000..4b6d528
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/f.go
@@ -0,0 +1,6 @@
+//go:build solaris
+// +build solaris
+
+package illumos
+
+import _ "f"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/g.go b/src/cmd/go/internal/imports/testdata/illumos/g.go
new file mode 100644
index 0000000..1bf826b
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/g.go
@@ -0,0 +1,6 @@
+//go:build !illumos
+// +build !illumos
+
+package illumos
+
+import _ "g"
diff --git a/src/cmd/go/internal/imports/testdata/illumos/tags.txt b/src/cmd/go/internal/imports/testdata/illumos/tags.txt
new file mode 100644
index 0000000..b6386a3
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/tags.txt
@@ -0,0 +1 @@
+illumos amd64
diff --git a/src/cmd/go/internal/imports/testdata/illumos/want.txt b/src/cmd/go/internal/imports/testdata/illumos/want.txt
new file mode 100644
index 0000000..0fdf397
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/illumos/want.txt
@@ -0,0 +1,6 @@
+a
+b
+c
+d
+e
+f
diff --git a/src/cmd/go/internal/imports/testdata/star/tags.txt b/src/cmd/go/internal/imports/testdata/star/tags.txt
new file mode 100644
index 0000000..f59ec20
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/tags.txt
@@ -0,0 +1 @@
+* \ No newline at end of file
diff --git a/src/cmd/go/internal/imports/testdata/star/want.txt b/src/cmd/go/internal/imports/testdata/star/want.txt
new file mode 100644
index 0000000..139f5f4
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/want.txt
@@ -0,0 +1,4 @@
+import1
+import2
+import3
+import4
diff --git a/src/cmd/go/internal/imports/testdata/star/x.go b/src/cmd/go/internal/imports/testdata/star/x.go
new file mode 100644
index 0000000..98f9191
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/x.go
@@ -0,0 +1,3 @@
+package x
+
+import "import1"
diff --git a/src/cmd/go/internal/imports/testdata/star/x1.go b/src/cmd/go/internal/imports/testdata/star/x1.go
new file mode 100644
index 0000000..eaaea97
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/x1.go
@@ -0,0 +1,6 @@
+//go:build blahblh && linux && !linux && windows && darwin
+// +build blahblh,linux,!linux,windows,darwin
+
+package x
+
+import "import4"
diff --git a/src/cmd/go/internal/imports/testdata/star/x_darwin.go b/src/cmd/go/internal/imports/testdata/star/x_darwin.go
new file mode 100644
index 0000000..a0c3fdd
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/x_darwin.go
@@ -0,0 +1,3 @@
+package xxxx
+
+import "import3"
diff --git a/src/cmd/go/internal/imports/testdata/star/x_windows.go b/src/cmd/go/internal/imports/testdata/star/x_windows.go
new file mode 100644
index 0000000..63c5082
--- /dev/null
+++ b/src/cmd/go/internal/imports/testdata/star/x_windows.go
@@ -0,0 +1,3 @@
+package x
+
+import "import2"